2016年の振り返り
はじめに
2016年の振り返りということをやってみる気になりました。今まで振り返りのような何かしらの人間味のある活動を避けたいという気持ちがあり。ただただ事実や試行の断片だけを出力していきたいという気持ちがあったのですが。それでは社会との接続が上手く行かないという感じがしてきはじめつつあり。人間味のある活動をやってみる気になり始めています。人間味のある人々は振り返りという名目で1年の間にどのようなことを行なっていたのか列挙してみてその時々に応じたコメントをするらしいので真似することにしました。
年初に書いていたことについて
今年の最初はバックエンドよりフロントエンドの方に力を入れるつもりだったようです。だいたいこのあたりの方針は4月位で転換してしまいました。この頃には陰も形なかったgoをやるみたいな感じになっています。数学関連云々は気力があればというようなオプショナルな形ですし。気力がなかったのでできませんでした。盆栽プロジェクト的なものは上手くできていないのですが。ここ最近はコードの生成にはまっているようでした。
自分用のメモを充実させることについて
今年の最初にあげたものの中では、自分用のメモを充実させるということに関してはうまくいっているような気がします。以前までは、gistにその時々の思いつきのコードを出力するだけで終わりにしていたのですが。文章を書くという行為や何かを説明するという行為が必要らしいということに気づき始めたのでした。
ところで文章というものは生産のコストが大きい。年の始めのころは真面目に文章を書くという意識でいたようなのですが、これを止めて自分用のメモの断片をgithubの特定のリポジトリにあげるというような行為に変えたところこれがすこぶる良い感じで今に至っても続いています(githubの草が不当に生えるという不具合が発生していますが)。これはreadmeが勝手にそこそこ良い感じでレンダリングされるというのと、gistのフラットな構造から脱却できたところが大きい気がします。加えて、自分自身の試行の整理の備忘録に文章はあまり必要なかったようでした。
以下の事を念頭におくことを忘れないという部分について
念頭に置くことがどうとか色々言っていますが。まとめると好き勝手やるということで。一方で好き勝手やった結果のデメリットをなるべく受け持たないようにしようという行動指針っぽいなにかです。これはこれで良いなと思ったのですが。最近は社会的な何かとの関わりがそれなりに必要なのではないかということを思いはじめて、github上でissueを書くだったり、pull requestを送るというようなそーしゃる活動のようなことをしていったほうが良いみたいな感覚になり始めています。
今年作ったリポジトリ
今年作ったリポジトリは、adventカレンダーのものと先述したメモ用のリポジトリを除くと、17個位みたいです。その内作っただけに近いものが5個位なのでおそらく10個程増えた感じになりそうです。そう言えば、以前よりはforkだったりが増えてますね。先の17個からforkしたものなどは除いています。相も変わらず作っては放置みたいなやつが多そうです。あんまり何処かでリリースのアナウンス的なものもしていませんでした。
- swagger-marshmallow-codegen
- dictknife
- swagger-bundler
- goconvert
- go-structjson
- kanagata
- kumonote
- magicalimport
- selfish
- django-aggressivequery
- django-returnfields
- console-angular
- postcss-restructure
- cssconflict
- cssdiff
swagger-marshmallow-codegen
これは最近作ったやつですね。コード生成楽しいみたいなやつです。昨日眠れないついでに真面目にどういう状況なのか記事を書いています。
dictknife
これはdictの操作用のライブラリの寄せ集めです。意外と色々なところの内部で使っていて個人的には便利です。元々の発端みたいな記事をdict遊びという名前で書いていたようです。個人的にはこのdict遊びみたいな試みが好きですし。こういうことが好きな人と日常的に交流できそうな何処かに所属したいみたいな気持ちがあったりします。
例えば、json-referenceで分割されたJSONのファイル群を1つにまとめる処理などがこういう感じで書けます。あとdictの再帰的なmergeやdeepequalなどが地味に便利でした。 pip install dictknife[load]
という感じでインストールすると、yamlとjsonのload,dumpの便利機能が使えるようになったりします。
swagger-bundler
分割されたswaggerのファイルをシュッと1つにまとめるやつです。これは最近新しい仕様で再実装したいので今はあんまり説明したくない感じです。次のバージョンでは、ファイルベースでの結合を止めて、真面目に名前空間をつける予定です。現在の挙動では何をimportしたのかわかんなくなるのが辛いという状況が発生しています。
goconvert
これは、goのコードを生成するためのpython用のライブラリ群です。主にgoの変換処理を生成しようと思って作業をしていた感じでした。作り途中です。qiitaに記事を書いていましたが全く興味は持たれなそうな感じでした。後述するgo-structjsonを使って、goのstruct定義をJSONファイルとして出力してあれこれやるみたいなやつです。
go-structjson
importのpathを見て再帰的にパッケージを見ながらAST使ってstruct定義をJSONとして抽出するみたいなやつです。goの情報を使ったコードをgo以外で書きたいと思ったので作りました。まじめに抽出する条件の絞込などができていなのでdefaultのexcludeの条件を外すとすぐに40000行とかのjsonを吐いたりしてしまいます。ツールとして作成した感じなのであんまりコードを綺麗に整える気にはなっていないので汚いです。
kanagata
これはpythonからmongodbのコードを触る時に、mongoengine位しか無さそうで。一方でmongoengineのdjangoのORMの方言みたいなインターフェイスがそもそもpythonが第二言語でしかない環境では無駄だろうという気持ちで作ったライブラリです。基本的には値の範囲を制限したdictとlistを作るライブラリです。ゆくゆくはswaggerの定義から自動生成をしようみたいなことを思っていましたが。今は止めてmarshmallowを使ってdictのvalidationをするだけで留めておこうみたいな気持ちになっています。もしかしたらmypyのtypeddictを使うというのが良いのかもしれません。
これを作った過程で、pythonのmongodbのclientのpymongoではcollections.UserDictは辞書として取り扱ってdeserialize/serializeできるのに対し、collecsions.UserListではエラーを吐くみたいな挙動になることがわかりました。jiraでチケットを作って(そういえばはじめてのjiraでした)、userlistに対応したpatchのようなものを投げてみましたが、adhocに個別に対応するのも馬鹿馬鹿しいしもっと汎用的な仕組みを後々用意するからそれで対応してみたいなことを言われたのであまり使い勝手が良くないです。(patchをあてないと、pymongoに値を受け渡すタイミングで、一度dictに変換する必要があります。)
kumonote
これは完全に途中で飽きて止めたリポジトリです。asyncioを使ってクローラーを作ろうと思ったらしいです。蜘蛛の手。
magicalimport
これは物理的なファイルパスをpythonで手軽にimportできるようにしようというライブラリです。plugin的なアーキテクチャを作る際に、わざわざパッケージを作らなくても済む様になるので意外と便利です。swagger-bundlerだったりswagger-marshmallow-codegenだったりの内部で使っています。
selfish
これはgoの練習用に作ったリポジトリです。gistにファイルをuploadするツールです。個人的にはこれでgistyを置き換えられたことでQoLが格段に向上しました。
具体的には-alias
というオプションを指定することで同一のgistに対する更新が手軽になったという辺りが大きいです。
# 最初のupload $ selfish -alias head *.go # 色々更新した後にgistに更新を反映 $ selfish -alias head *.go # 新しいものを作るときには `-alias` を外すか別の名前で実行 $ selfish *.py readme.md
django-aggressivequery
これはdjangoのqueryのoptimizerです。後に触れるdjango-returnfielldsで生成するqueryをそこそこ良い感じにするために使っています。具体的にはN+1をjoinやprefetchに置き換えて除去してくれます。とは言え、職が変わったので、もう仕事でdjangoを使う事は無さそうです。なのであんまり積極的にメンテする気もない感じではあります。
django-returnfields
これはdjango restframework用のライブラリです。fat apiを定義したときのN+1クエリー的なものを除去するのに便利というやつです。そう言えばこれを作る過程で異様にdjangoのorm関連の記事を書いた記憶をがあります。
- djangoでの集計は辛いという話 -- ORMは用法・用量を守って正しく使いましょう - podhmo's diary
- djangoでヘテロなリストのprefetch (generic foreign keyのprefetch_related) - podhmo's diary
- djangoでprefetch_relatedで使えるようになる 独自のディスクリプタを作ってみる - podhmo's diary
- もう少しだけdjangoのprefetch_relatedについて考えてみる(条件付加したrelationのeager loading) - podhmo's diary
- generic foreignkeyのsub relationをprefetchする方法 - podhmo's diary
- django-returnfieldsというパッケージを作っていました - podhmo's diary
djangoがオワコンなのか何なのか入門用の記事や特定のライブラリの紹介みたいな記事しか見かけないような気がします。
console-angular
これはangular1.xの挙動を確かめるためにhtmlやcssを書くのが馬鹿らしいと思って作ったパッケージだった記憶があります。内部的にはほとんど数行のラッパー。 元になった記事はこれっぽいです。
最近はangular1.x触っていないですし。今のおしごとのフロントエンドはangular(angular2)だったりですし。隔世の感があります。最近はフロントエンド触っていないので追いついていけてないですけれど。今だったらangularに限らずとりあえずbootcamp的にxxx-cliの内部を把握して、あとはテキトウに少しずつ新しい知識を集めていけば良いみたいなイメージでいます。
postcss-restructure
これはごみです。ほとんどpostcssのhello worldみたいなpluginな記憶。
cssconflict
これもごみです。これはcssのconflictした定義を見つけようと書き始めたものの、よく考えたらHTML部分も見なければレンダリングに使われる記述が決定できないみたいなことに気づいたみたいな経緯で放置されてます。
cssdiff
これはすごく巨大なcss同士の意味的なdiffを取ろうみたいなやつです。css parserでparseしたあとテキトウにdictでdiffを取るというだけなので数時間位で作った記憶があります。こういう便利ツールがstarを集めやすい(とは言え0件ではなく色がつくという程度)。
最後に
飽きました。(後で何か書く)
swagger-marshmallow-codegen というライブラリを作りました
swagger-marshmallow-codegen というライブラリを作りました。swaggerの定義ファイルからmarshmallow のschemaを生成するライブラリです。
ライブラリ?
正確にはライブラリでは無くコマンドです。marshmallowのschema定義のコードを生成するコマンドです。
(この後、どうして作ったのかみたいな文章が続きます。 使い方が知りたいだけの人は 使い方 の部分まで読み飛ばしてください。)
何で作ったの?
元々、connexion という swaggerの定義ファイルを見てflaskのrouting設定をやってくれるライブラリを使っていました。このconnexionがrequestとresponseをjsonschemaレベルではvalidationしてくれるのですが。値の変換が関わるような処理を行おうと思った場合に別途schemaの定義(例えば、marshmallowのschema)が必要になってしまうためです。
jsonschemaを使ったvalidationは基本的にはJSONの値のチェックまでしか行いません。例えば日付を意味する"yyyy-mm-dd"というフォーマットの文字列があったとして、渡された値が適切なフォーマットに基づいたものであるかのチェックしか行いません。一方で、内部のコード上では、渡された値を、その言語の内部表現の値として扱いたい場合が結構あります(例えば、文字列ではなく datetime.date
のオブジェクトとして扱う)。この値の変換部分を含んだschemaの定義を作ってあげようというのが理由の1つです。
何でライブラリ(メタプログラミング)ではなくコマンド(コード生成)として作ったの?
コード生成とメタプログラミングとの違いを検証してみたいというのが事の発端でした。正直なところこれがやりたかったことの1つで手段と目的が逆だったりもします。通常、pythonで何らかの定義ファイルから良い感じの便利な挙動を行うコードを書く時にはメタプログラミングを使います。大抵の場合宣言的な記述が書けるという触れ込みの機構は内部にメタクラスを抱えて、設定から条件に合致したオブジェクトを生成するという仕組みになっている物が多いです。
これはこれで便利なのですが。以下の点が時折不満に感じます。
- そもそも内部でどのような処理が行われているか追いづらい
- 特定のオブジェクトの処理だけに暫定的に特別な処理を追加しづらい
メタプログラミングのつらい話
最初の頃は、ユースケースも単純で、メタプログラミングを使ったライブラリを便利便利と使っているのですが。時間の経過とともに細かな状況に対応しなければいけなくなっていくことが多く。この細かな修正の対応のためには結局隠蔽されて自動で上手くやってくれていたハズの内部の処理について詳しく把握する必要があったりすることがあります。そしてそれはおそらく必ずやってきます。
また、内部の詳細を把握したとしてもそれで終わりではありません。望みの挙動を得るために、ライブラリの作成者が想定している作法に則った形で修正する必要が出てくる時にこの作法とやりたい挙動の整合性を取ることに時間が割かれてしまったりします。その上、結局、望みの挙動を実現するためのフックポイントにあたる箇所が見つからず、最悪、再実装かforkするという対応になってしまいます。
コード生成への期待の話
これがコード生成だと変わります。コード生成はメタプログラミングを完全に置き換えることはできませんが。実行時に行っている処理の一部を実際のpythonコードに直接置き換えることができます(N個の対象があったらN個の記述が生成されるということ)。この処理自体は静的なコマンドの実行に過ぎないものですし。
なによりメタプログラミングのコードとは異なり、一般的な処理として1つにまとまっていたコードに対して、個別のコードの記述に分けてくれます。このため、何か困ったことがあったら直接コードにデバッガーを仕掛けることは簡単ですし。ピンポイントで行える様になります。
処理自体を追うことに関しても基本的には温かみのある手書きのコードを機械によって生成しているにすぎないので処理内容の記述は素直で単調です。おそらく読みやすいはずです。メタプログラミングを使ったコードを読む場合とは異なり、抽象度の高いよく分からない海の中を変数やメソッドの名前だけを頼りに泳ぐみたいなことはする必要はありません(特殊な最適化などをおこなったりしていないのであれば)。
また、すごく差し迫った状況で、特定のオブジェクトに対する処理だけに今だけに限り処理内容を書き換えたいというときには、テキトウなTODOコメントとともに最悪直接該当部分のコードを書き換えて急場をしのぐということができます(あんまりオススメしないですが)。
加えて、mypyなどの静的な解析を行うツールとの相性もおそらくコード生成の方が良いです。メタプログラミングの入力と出力のような複雑な1つのコードを対象にするのではなく、生成された個別のコードを対象にできるので。ただしこれに関しては推測の域を出ていないのであまり詳しくは話しません。
既に実用できるレベルなの?
基本的な機能は揃っているので、swaggerの定義ファイルからのvalidationというレベルではおそらく使えるものになっています。ただ以下の点であんまり積極的には勧めません。
- 利用者が手軽に対応するマッピングの設定を追加できるようになっていない
- そもそもswaggerのdefinisionsを解析するだけではあまり便利にならない
利用者が手軽に対応するマッピングの設定を追加できるようになっていない
これはそのままの話です。swaggerでデフォルトで用意されているtype,formatの範囲で作業する分には問題ないのですが。自分で独自の型のマッピングを追加したいという場合があります。例えばMongoDBを使っている時には、type="string" format="objectID"
が指定されたフィールドの値は、内部では bson.ObjectId
として扱いたいと言うような場合や。正規表現やswaggerの組み込みの機能の範囲を越えたvalidationを新たに追加したい場合や。swaggerではメタデータはx-
を先頭に付与して指定するという仕様があるのですが、自分達で独自に定義したメタデータを使った自分たち独自の処理を追加したいといった場合には、おそらく現状では内部の実装を把握していないと対応できなそうです。
そもそもswaggerのdefinisionsを解析するだけではあまり便利にならない
これはわりと悲しいことですが事実で。swaggerの定義ファイルを書くということは単にAPIの仕様のドキュメントを作っているに過ぎません。そしてvalidationを追加するというのは、単に望まない入力をエラーとして弾くということしかしてません。なので元々正常な入力以外やってくることがないという前提に立つならば(例えば本当に初期段階のプロトタイピングのときなど)、全く何も価値を生み出していません。実質きれいなドキュメントができましたおしまいというレベルです。
swagger-marshmallow-codegenでschemaの定義は自動で生成することができるようになったのですが。まだオーバーヘッドの割にできることが少ないような気がします。schema定義だけで済む要件ならswaggerの定義ファイルを書くかわりに、schemaの定義(例えばmarshmallowのschema)を直接書けば良いだけだったりしますし。
使い方
ここからは使い方の話です。
インストール
まだ pypi にあげていないので各自インストールする必要があります(あとであげる)。
$ pip install swagger-marshmallow-codegen # まだできない
使い方
swagger-marshmallow-codegenにswaggerの定義ファイルを渡すだけです。簡単ですね。
$ swagger-marshmallow-codegen swagger.yaml > schema.py
実行例
簡単なもの
例えば、以下のようなyamlから以下の様なschemaの定義が生成されます。
swagger.yaml
definitions: person: type: object properties: name: type: string age: type: integer required: - name
schema.py
# -*- coding:utf-8 -*- from marshmallow import( Schema, fields ) class Person(Schema): name = fields.String(required=True) age = fields.Integer()
requiredの部分もみてくれます。
$ref
$ref
もみてくれます。stringやintegerなどのprimitiveな型の場合には新しいSchemaは定義されません。
swagger.yaml
# type array definitions: name: type: string description: "name of something" age: type: integer description: "age" person: type: object properties: name: $ref: "#/definitions/name" age: $ref: "#/definitions/age" skills: type: array items: $ref: "#/definitions/skill" required: - name skill: type: object properties: name: type: string required: - name
schema.py
# -*- coding:utf-8 -*- from marshmallow import( Schema, fields ) class Person(Schema): name = fields.String(required=True, description='name of something') age = fields.Integer(description='age') skills = fields.List(fields.Nested('Skill', )) class Skill(Schema): name = fields.String(required=True)
もう少し複雑な$ref
自分自身と同じ型を参照するrefなども問題ないです。これは主にmarshmallowのおかげですが。
swagger.yaml
definitions: name: type: string description: "name of something" age: type: integer description: "age" person: type: object properties: name: $ref: "#/definitions/name" age: $ref: "#/definitions/age" father: $ref: "#/definitions/person" mother: $ref: "#/definitions/person" skills: type: array items: $ref: "#/definitions/skill" required: - name skill: type: object properties: name: type: string required: - name
schema.py
class Person(Schema): name = fields.String(required=True, description='name of something') age = fields.Integer(description='age') father = fields.Nested('self') mother = fields.Nested('self') skills = fields.List(fields.Nested('Skill', )) class Skill(Schema): name = fields.String(required=True)
nestedに self
が渡されているのは自己参照するfieldの定義です。
ちなみに以下のように書き換えてあげると。自己参照ではなくなります。
--- 04person.yaml 2016-12-25 00:36:53.000000000 +0900 +++ 05person.yaml 2016-12-25 00:55:11.000000000 +0900 @@ -1,4 +1,4 @@ definitions: name: type: string @@ -6,6 +6,10 @@ age: type: integer description: "age" + father: + $ref: "#/definitions/person" + mother: + $ref: "#/definitions/person" person: type: object properties: @@ -14,9 +18,9 @@ age: $ref: "#/definitions/age" father: - $ref: "#/definitions/person" + $ref: "#/definitions/father" mother: - $ref: "#/definitions/person" + $ref: "#/definitions/mother" skills: type: array items:
schemaは増えます。
--- 04person.py 2016-12-27 08:50:33.000000000 +0900 +++ 05person.py 2016-12-27 08:50:33.000000000 +0900 @@ -1,10 +1,18 @@ class Person(Schema): name = fields.String(required=True, description='name of something') age = fields.Integer(description='age') - father = fields.Nested('self') - mother = fields.Nested('self') + father = fields.Nested('Father') + mother = fields.Nested('Mother') skills = fields.List(fields.Nested('Skill', )) +class Father(Person): + pass + + +class Mother(Person): + pass + + class Skill(Schema): name = fields.String(required=True)
即時定義の展開
swaggerの定義ファイルもjsonschemaと同様にobjectやarrayの定義中に直接リテラル的に定義を書くことができます。一応これにも対応しています。
swagger.yaml
definitions: person: type: object required: - id - name properties: id: type: string name: type: string age: type: integer skills: type: array items: type: object properties: name: type: string relations: type: array items: type: object properties: direction: type: string enum: - following - followed - bidirectional personId: type: string
schema.py
# -*- coding:utf-8 -*- from marshmallow import( Schema, fields ) from marshmallow.validate import OneOf class Person(Schema): id = fields.String(required=True) name = fields.String(required=True) age = fields.Integer() skills = fields.List(fields.Nested('PersonSkillsItem', )) relations = fields.List(fields.Nested('PersonRelationsItem', )) class PersonRelationsItem(Schema): direction = fields.String(validate=[OneOf(choices=['following', 'followed', 'bidirectional'], labels=[])]) personId = fields.String() class PersonSkillsItem(Schema): name = fields.String()
PersonalRelationsItemなど末尾にItemが付いた謎のschemaなどが現れました。実は、内部的には、以下のようなフラットな構造に変換してから処理を行なっています。
definitions: person: type: object required: - id - name properties: id: type: string name: type: string age: type: integer skills: $ref: '#/definitions/personSkills' relations: $ref: '#/definitions/personRelations' personRelations: type: array items: $ref: '#/definitions/personRelationsItem' personRelationsItem: type: object properties: direction: type: string enum: - following - followed - bidirectional personId: type: string personSkills: type: array items: $ref: '#/definitions/personSkillsItem' personSkillsItem: type: object properties: name: type: string
実は隠し機能として、--driver Flatten
のオプションをつけるとこのフラットな構造のyamlを出力することができます(とは言えdriverオプションは開発者用の隠し機能です)。
swagger-marshmallow-codegen --driver Flatten swagger.yaml > flatten.yaml
default値やvalidation
schema定義にdefaultの指定が含まれていた場合にはその設定も見ます。validationも少なくともswaggerで使えるもの(OpenAPI 2.0)に関しては一通り揃っているはずです。
swagger.yaml
definitions: default: properties: string: type: string default: "default" integer: type: integer default: 10 boolean: type: boolean default: true date: type: string format: date default: 2000-01-01 datetime: type: string format: date-time default: 2000-01-01T01:01:01Z object: type: object properties: name: type: string default: foo age: type: integer default: 20 default: name: foo age: 20 array: type: array items: type: integer default: - 1 - 2 - 3 length-validation: type: object properties: s0: type: string s1: type: string maxLength: 10 s2: type: string minLength: 5 s3: type: string maxLength: 10 minLength: 5 maximum-validation: type: object properties: n0: type: number maximum: 100 n1: type: number maximum: 100 exclusiveMaximum: true n2: type: number maximum: 100 exclusiveMaximum: false m0: type: number minimum: 100 m1: type: number minimum: 100 exclusiveMinimum: true m2: type: number minimum: 100 exclusiveMinimum: false regex-validation: type: object properties: team: type: string pattern: team[1-9][0-9]+ team2: type: string pattern: team[1-9][0-9]+ maxLength: 10 array-validation: type: object properties: nums: type: array items: type: integer maxItems: 10 minItems: 1 uniqueItems: true color: type: string enum: - R - G - B yen: type: integer enum: - 1 - 5 - 10 - 50 - 100 - 500 - 1000 - 5000 - 10000 huge-yen: type: integer multipleOf: 10000 enum-validation: type: object required: - name - color properties: name: type: string money: $ref: "#/definitions/yen" deposit: $ref: "#/definitions/huge-yen" color: $ref: "#/definitions/color"
schema.py
# -*- coding:utf-8 -*- from marshmallow import( Schema, fields ) import datetime from swagger_marshmallow_codegen.fields import( Date, DateTime ) from collections import OrderedDict from marshmallow.validate import( Length, OneOf, Regexp ) from swagger_marshmallow_codegen.validate import( ItemsRange, MultipleOf, Range, Unique ) import re class Default(Schema): string = fields.String(missing=lambda: 'default') integer = fields.Integer(missing=lambda: 10) boolean = fields.Boolean(missing=lambda: True) date = Date(missing=lambda: datetime.date(2000, 1, 1)) datetime = DateTime(missing=lambda: datetime.datetime(2000, 1, 1, 1, 1, 1)) object = fields.Nested('DefaultObject', missing=lambda: OrderedDict([('name', 'foo'), ('age', 20)])) array = fields.List(fields.Integer(missing=lambda: [1, 2, 3])) class DefaultObject(Schema): name = fields.String(missing=lambda: 'foo') age = fields.Integer(missing=lambda: 20) class Length_validation(Schema): s0 = fields.String() s1 = fields.String(validate=[Length(min=None, max=10, equal=None)]) s2 = fields.String(validate=[Length(min=5, max=None, equal=None)]) s3 = fields.String(validate=[Length(min=5, max=10, equal=None)]) class Maximum_validation(Schema): n0 = fields.Number(validate=[Range(min=None, max=100, exclusive_min=False, exclusive_max=False)]) n1 = fields.Number(validate=[Range(min=None, max=100, exclusive_min=False, exclusive_max=True)]) n2 = fields.Number(validate=[Range(min=None, max=100, exclusive_min=False, exclusive_max=False)]) m0 = fields.Number(validate=[Range(min=100, max=None, exclusive_min=False, exclusive_max=False)]) m1 = fields.Number(validate=[Range(min=100, max=None, exclusive_min=True, exclusive_max=False)]) m2 = fields.Number(validate=[Range(min=100, max=None, exclusive_min=False, exclusive_max=False)]) class Regex_validation(Schema): team = fields.String(validate=[Regexp(regex=re.compile('team[1-9][0-9]+'))]) team2 = fields.String(validate=[Length(min=None, max=10, equal=None), Regexp(regex=re.compile('team[1-9][0-9]+'))]) class Array_validation(Schema): nums = fields.List(fields.Integer(validate=[ItemsRange(min=1, max=10), Unique()])) class Enum_validation(Schema): name = fields.String(required=True) money = fields.Integer(validate=[OneOf(choices=[1, 5, 10, 50, 100, 500, 1000, 5000, 10000], labels=[])]) deposit = fields.Integer(validate=[MultipleOf(n=10000)]) color = fields.String(required=True, validate=[OneOf(choices=['R', 'G', 'B'], labels=[])])
追記
あと細かい話でいうと。pythonでは変数名としてinvalidな名前のフィールドにも対応しています(例えば、githubのemojisのAPIなどのような)。
swagger.yaml
definitions: emojis: type: object properties: "100": type: string "1234": type: string "+1": # 実は"+1"ではなく+1だと1として扱われてしまうというバグ?がある type: string "-1": type: string 8ball: type: string
schema.py
# -*- coding:utf-8 -*- from marshmallow import( Schema, fields ) class Emojis(Schema): n100 = fields.String(dump_to='100', load_from='100') n1234 = fields.String(dump_to='1234', load_from='1234') x1 = fields.String(dump_to='+1', load_from='+1') x_1 = fields.String(dump_to='-1', load_from='-1') n8ball = fields.String(dump_to='8ball', load_from='8ball')
こういう感じで。名前がちょっと良くないけれど。
from schema import Emojis print(Emojis().load({"+1": "hai", "-1": "hoi", "8ball": "o_0"})) # UnmarshalResult(data={'x1': 'hai', 'n8ball': 'o_0', 'x_1': 'hoi'}, errors={}) data, errs = Emojis().load({"+1": "hai", "-1": "hoi", "8ball": "o_0"}) print(Emojis().dump(data)) # MarshalResult(data={'8ball': 'o_0', '-1': 'hoi', '+1': 'hai'}, errors={})