PythonをREST APIのクライアントとして作成する際、dataclassesを使用しようと思いました。型が見た瞬間分かるし、しっかりしているからです。
でも、そのREST API は””やNoneのデータを受け付けない仕様でした。なので、JSONとして送信する際は、削除する必要があります。
作成に何故か苦労したので、その方法を書いておきます。
環境
- WSL2(ubuntu 22.04 LTS)
- Python: 3.10.4
dataclassとは
「dataclassとはデータを保持のためのclassである」とのことです。
ただ、実はそれほどdataclassには思い入れがなく、一つ前はgolangを書いていたので、当然データを先に定義するだろう
と思いこんで使っていたに過ぎません。
でもまぁ先にデータ定義しても悪いことはないので、使ってみようと思いました。
Noneや””が入っていていてはいけない
WebUIのFormからvalidateした結果、入力しないデータは””で入ってきます。もちろん必須であれば””をvalidateで弾いてもいいですが、Optionalな値だとします。
そして、Formの内容を結果的にJSONとして送信するのですが、サーバー側の仕様で、Noneや””が入ってるとinvalidでエラーになります。
サーバ側を変えてもらってもいいですが、送信すること自体意味がないのでクライアント側から送信しない方針がいいだろうと考えました。ここらへんはいろいろな事情によるでしょうけど。
意外とやりにくかった
簡単に実装できるかなと思ったのですが、意外に方法がわからず四苦八苦しました。
結局こうなった
サンプルコードは以下です。
from dataclasses import asdict, dataclass
from typing import List, Optional
@dataclass
class Five():
data3: Optional[str] = None
data4: Optional[str] = None
@dataclass
class Four():
five: List[Five]
data1: Optional[str] = None
data2: Optional[str] = None
@dataclass
class Three():
four: Four
@dataclass
class Two():
data_test1: Optional[str] = None
data_test2: Optional[str] = None
@dataclass
class Top():
two: Two
three: Three
# Dictで設定する方法
inited = {
"two": {
"data_test1": "data_test1"
},
"three": {
"four": {
"five": [{
"data3": "data3"
}],
"data2": "data2"
}
}
}
instance = Top(**inited)
print(repr(instance))
print(asdict(instance))
# dataclassesだけで実現する方法
top = Top(two=Two(data_test1="data_test1", data_test2=""), three=Three(four=Four(five=[Five(data3="data3")], data2="data2")))
print(repr(top))
# None or ""の場合は辞書から除く
print(asdict(top, dict_factory=lambda tuples: {tuple[0]: tuple[1] for tuple in tuples if tuple[1]}))
実行結果
上記を実行した結果は以下のとおりです。
op(two={'data_test1': 'data_test1'}, three={'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}})
{'two': {'data_test1': 'data_test1'}, 'three': {'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}}}
Top(two=Two(data_test1='data_test1', data_test2=''), three=Three(four=Four(five=[Five(data3='data3', data4=None)], data1=None, data2='data2')))
{'two': {'data_test1': 'data_test1'}, 'three': {'four': {'five': [{'data3': 'data3'}], 'data2': 'data2'}}}
主なところ
実現する上で、主なところは以下です。
- フィールドを
Optional[str] = None
にする - dict_factoryを指定してNoneや””を無視する
いろいろ検索しましたが、あまりちゃんと載っておらず、苦労しました。
上記でやりたいことができました。
参考
- https://gist.github.com/xhiroga/6c5a8ac4749d25f5ecbffcfad9e750e4
まんまのスニペット。 - https://www.lifewithpython.com/2022/09/python-dataclasses-dict-factory.html
dict_factoryが使えることに気がついたブログ