Skip to content

Instantly share code, notes, and snippets.

@daneko
Created June 14, 2020 06:53
Show Gist options
  • Save daneko/a5a3deda1c577ef52b18720a96023e57 to your computer and use it in GitHub Desktop.
Save daneko/a5a3deda1c577ef52b18720a96023e57 to your computer and use it in GitHub Desktop.
request decorator python
# 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