Created
June 14, 2020 06:53
-
-
Save daneko/a5a3deda1c577ef52b18720a96023e57 to your computer and use it in GitHub Desktop.
request decorator python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
from flask import Flask, request, jsonify | |
import dataclasses | |
import typing | |
| |
| |
app = Flask(__name__) | |
| |
| |
def dict_to_dataclass(k, d: dict, typechecker: typing.Callable[[str, typing.Any, typing.Any],None], parent_fields=None): | |
if parent_fields is None: | |
parent_fields = [] | |
fields = {} | |
for name, field in k.__dataclass_fields__.items(): | |
if name in d: | |
if dataclasses.is_dataclass(field.type): | |
if isinstance(d[name], dict): | |
parent_fields.append(name) | |
fields[name] = dict_to_dataclass(field.type, d[name], typechecker, parent_fields) | |
else: | |
with_parent_name = ".".join(parent_fields + [name]) | |
raise TypeError(f"type of '{with_parent_name}' must be dict; got {type(d[name]).__name__} instead") | |
else: | |
with_parent_name = "'" + ".".join(parent_fields + [name]) + "'" | |
typechecker(with_parent_name, d[name], field.type) | |
fields[name] = d[name] | |
try: | |
return k(**fields) | |
except TypeError as e: | |
msg = str(e) | |
if "missing" in msg: | |
in_parent_name = "" | |
if parent_fields: | |
in_parent_name = " in '" + ".".join(parent_fields) + "'" | |
missing_args = msg.split(":")[1].strip() | |
plural = "s" if "and" in missing_args else "" | |
raise TypeError(f'missing argument{plural}: {missing_args}{in_parent_name}') | |
raise | |
| |
| |
def dataclass_request_handler(klass: type, use_typeguard=False): | |
# 厳密な型チェックを行うかどうか | |
if use_typeguard: | |
from typeguard import check_type | |
typechcker = check_type | |
else: | |
typechcker = lambda *args: None | |
| |
def decorator(f: typing.Callable): | |
def handler(): | |
try: | |
# request.json を再帰的に klass で指定された dataclass に変換する | |
data = dict_to_dataclass(klass, request.json, typechcker) | |
except TypeError as e: | |
# 雑に 400 で返す | |
return jsonify({"error":str(e)}), 400 | |
return f(data) | |
return handler | |
return decorator | |
| |
| |
@dataclasses.dataclass | |
class DeepValue: | |
deep_key: typing.List[str] | |
| |
| |
@dataclasses.dataclass | |
class Request: | |
key1: int | |
key2: str | |
deep: DeepValue | |
optional: typing.Optional[str] = "default value" | |
| |
| |
@app.route("/", methods=["POST"]) | |
@dataclass_request_handler(Request, use_typeguard=True) | |
def index(req: Request): | |
""" | |
json 形式のリクエストを Request 型に変換した上で注入させる | |
""" | |
return jsonify(req) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment