swagger-marshmallow-codegen でpaths以下も見るようにした
swagger-marshmallow-codegen でpaths以下も見るようにした。あまりきれいとはいえない感じかもしれないけれど。
paths以下を見るということ
今まではdefinitions以下しか見なかったのだけれど。通常swaggerでapiの定義をするときにはpaths以下にも色々書く。というよりrequestとresponseがどのようになっているかはpaths以下の定義を見る事が多い。例えば以下のようなAPI定義(apiaryのtuotialからもらってきた)はどうなっているかというと。
paths: /message/{name}: x-summary: Message operations x-description: Operation description in Markdown get: summary: Get a message of the day description: | Description of the operation in Markdown operationId: getMessage parameters: - name: name in: path description: name to include in the message type: string x-example: 'Hello, Adam!' responses: default: description: Bad request 200: description: Successful response schema: $ref: '#/definitions/Message' examples: 'application/json': message: 'Hello, Adam!' definitions: Message: required: - message properties: message: type: string default: 'Hello, Adam!'
これは GET /message/{name}
というようなAPIが存在していて、そのrequestの形式で許可するものはpathのみ(apiaryのsampleはqueryになっていたけれどそれは間違い。こちらでは修正している)。また、outputとしてstatus=200
のresponseは Message
のschemaになっている。
Input, Output
先程のAPI定義からswagger-marshmallo-codegenを使ってschemaのコードを生成してみる。今度からInput,Outputというclassも生成されるようになった。具体的には以下の様なもの。
# -*- coding:utf-8 -*- from marshmallow import ( Schema, fields ) class Message(Schema): message = fields.String(required=True, missing=lambda: 'Hello, Adam!') class MessageNameInput(object): class Get(object): """Get a message of the day""" class Path(Schema): name = fields.String(description='name to include in the message') class MessageNameOutput(object): class Get200(Message): """Successful response""" pass
APIのrequestとresponse毎にInput,Outputが存在していてそのバリエーション毎にクラスが別れている。外側のクラスはnamespaceみたいなもの。
今回のAPIに関して言えば、以下のようなrequestとresponseになる。
GET /message/adam {"message": "Hello, Adam!"}
それぞれ MessageNameInput
と MessageNameOutput
が対応している。
x-marshmallow-name
Input,Outputの名前はpathのpatternからすごく雑に変換して決めている。
/message/{name} -> /message/name -> message, name -> MessageName
気に入らない場合もあるだろうから、 x-marshmallow-name
で名前を決められるようにした。
--- 00schema.yaml 2017-01-17 06:29:00.000000000 +0900 +++ 01schema.yaml 2017-01-17 06:33:33.000000000 +0900 @@ -2,6 +2,7 @@ /message/{name}: x-summary: Message operations x-description: Operation description in Markdown + x-marshmallow-name: Message get: summary: Get a message of the day description: |
以下のような修正を加えると出力結果は以下の様に変わる。
# -*- coding:utf-8 -*- from marshmallow import ( Schema, fields ) class Message(Schema): message = fields.String(required=True, missing=lambda: 'Hello, Adam!') class MessageInput(object): class Get(object): """Get a message of the day""" class Path(Schema): name = fields.String(description='name to include in the message') class MessageOutput(object): class Get200(Message): """Successful response""" pass
もう少し複雑なもの
Path以外にもparametersは色々ある。query,formData,body,header (詳しくはここ)。それらも見る。
例えばGithubのAPIの一部のAPI定義をすこしだけ弄った以下のようなyamを渡すと以下のようなコードを生成する。
definitions: emailsPost: items: type: string pattern: ".+@.+" type: array label: properties: color: maxLength: 6 minLength: 6 type: string name: type: string url: type: string type: object labels: items: $ref: '#/definitions/label' type: array labelsBody: items: type: string type: array parameters: owner: description: Name of repository owner. in: path name: owner required: true type: string repo: description: Name of repository. in: path name: repo required: true type: string number: description: Number of issue. in: path name: number required: true type: integer X-Github-Media-Type: description: | You can check the current version of media type in responses. in: header name: X-GitHub-Media-Type type: string Accept: description: Is used to set specified media type. in: header name: Accept type: string X-RateLimit-Limit: in: header name: X-RateLimit-Limit type: integer X-RateLimit-Remaining: in: header name: X-RateLimit-Remaining type: integer X-RateLimit-Reset: in: header name: X-RateLimit-Reset type: integer X-GitHub-Request-Id: in: header name: X-GitHub-Request-Id type: integer responses: labels: description: OK schema: $ref: '#/definitions/labels' label-created: description: Created schema: $ref: '#/definitions/label' paths: '/repos/{owner}/{repo}/issues/{number}/labels': delete: description: Remove all labels from an issue. parameters: - $ref: "#/parameters/owner" - $ref: "#/parameters/repo" - $ref: "#/parameters/number" - $ref: "#/parameters/X-Github-Media-Type" - $ref: "#/parameters/Accept" - $ref: "#/parameters/X-RateLimit-Limit" - $ref: "#/parameters/X-RateLimit-Remaining" - $ref: "#/parameters/X-RateLimit-Reset" - $ref: "#/parameters/X-GitHub-Request-Id" responses: '204': description: | No content. '403': description: | API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details. get: description: List labels on an issue. parameters: - $ref: "#/parameters/owner" - $ref: "#/parameters/repo" - $ref: "#/parameters/number" - $ref: "#/parameters/X-Github-Media-Type" - $ref: "#/parameters/Accept" - $ref: "#/parameters/X-RateLimit-Limit" - $ref: "#/parameters/X-RateLimit-Remaining" - $ref: "#/parameters/X-RateLimit-Reset" - $ref: "#/parameters/X-GitHub-Request-Id" responses: '200': $ref: "#/responses/labels" '403': description: | API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details. x-marshmallow-name: IssuedLabels post: description: Add labels to an issue. parameters: - $ref: "#/parameters/owner" - $ref: "#/parameters/repo" - $ref: "#/parameters/number" - $ref: "#/parameters/X-Github-Media-Type" - $ref: "#/parameters/Accept" - $ref: "#/parameters/X-RateLimit-Limit" - $ref: "#/parameters/X-RateLimit-Remaining" - $ref: "#/parameters/X-RateLimit-Reset" - $ref: "#/parameters/X-GitHub-Request-Id" - in: body name: body required: true schema: $ref: '#/definitions/emailsPost' responses: '201': $ref: "#/responses/label-created" '403': description: | API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details. put: description: | Replace all labels for an issue. Sending an empty array ([]) will remove all Labels from the Issue. parameters: - $ref: "#/parameters/owner" - $ref: "#/parameters/repo" - $ref: "#/parameters/number" - $ref: "#/parameters/X-Github-Media-Type" - $ref: "#/parameters/Accept" - $ref: "#/parameters/X-RateLimit-Limit" - $ref: "#/parameters/X-RateLimit-Remaining" - $ref: "#/parameters/X-RateLimit-Reset" - $ref: "#/parameters/X-GitHub-Request-Id" - in: body name: body required: true schema: $ref: '#/definitions/emailsPost' responses: '201': $ref: "#/responses/label-created" '403': description: | API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details.
こういう感じ。
# -*- coding:utf-8 -*- from marshmallow import ( Schema, fields ) from marshmallow.validate import ( Length, Regexp ) from swagger_marshmallow_codegen.schema import ( PrimitiveValueSchema ) import re class Label(Schema): color = fields.String(validate=[Length(min=6, max=6, equal=None)]) name = fields.String() url = fields.String() class IssuedLabelsInput(object): class Delete(object): class Header(Schema): X_GitHub_Media_Type = fields.String(description='You can check the current version of media type in responses.\n', dump_to='X-GitHub-Media-Type', load_from='X-GitHub-Media-Type') Accept = fields.String(description='Is used to set specified media type.') X_RateLimit_Limit = fields.Integer(dump_to='X-RateLimit-Limit', load_from='X-RateLimit-Limit') X_RateLimit_Remaining = fields.Integer(dump_to='X-RateLimit-Remaining', load_from='X-RateLimit-Remaining') X_RateLimit_Reset = fields.Integer(dump_to='X-RateLimit-Reset', load_from='X-RateLimit-Reset') X_GitHub_Request_Id = fields.Integer(dump_to='X-GitHub-Request-Id', load_from='X-GitHub-Request-Id') class Path(Schema): owner = fields.String(description='Name of repository owner.') repo = fields.String(description='Name of repository.') number = fields.Integer(description='Number of issue.') class Get(object): class Header(Schema): X_GitHub_Media_Type = fields.String(description='You can check the current version of media type in responses.\n', dump_to='X-GitHub-Media-Type', load_from='X-GitHub-Media-Type') Accept = fields.String(description='Is used to set specified media type.') X_RateLimit_Limit = fields.Integer(dump_to='X-RateLimit-Limit', load_from='X-RateLimit-Limit') X_RateLimit_Remaining = fields.Integer(dump_to='X-RateLimit-Remaining', load_from='X-RateLimit-Remaining') X_RateLimit_Reset = fields.Integer(dump_to='X-RateLimit-Reset', load_from='X-RateLimit-Reset') X_GitHub_Request_Id = fields.Integer(dump_to='X-GitHub-Request-Id', load_from='X-GitHub-Request-Id') class Path(Schema): owner = fields.String(description='Name of repository owner.') repo = fields.String(description='Name of repository.') number = fields.Integer(description='Number of issue.') class Post(object): class Body(PrimitiveValueSchema): v = fields.String(validate=[Regexp(regex=re.compile('.+@.+'))]) class Header(Schema): X_GitHub_Media_Type = fields.String(description='You can check the current version of media type in responses.\n', dump_to='X-GitHub-Media-Type', load_from='X-GitHub-Media-Type') Accept = fields.String(description='Is used to set specified media type.') X_RateLimit_Limit = fields.Integer(dump_to='X-RateLimit-Limit', load_from='X-RateLimit-Limit') X_RateLimit_Remaining = fields.Integer(dump_to='X-RateLimit-Remaining', load_from='X-RateLimit-Remaining') X_RateLimit_Reset = fields.Integer(dump_to='X-RateLimit-Reset', load_from='X-RateLimit-Reset') X_GitHub_Request_Id = fields.Integer(dump_to='X-GitHub-Request-Id', load_from='X-GitHub-Request-Id') class Path(Schema): owner = fields.String(description='Name of repository owner.') repo = fields.String(description='Name of repository.') number = fields.Integer(description='Number of issue.') class Put(object): class Body(PrimitiveValueSchema): v = fields.String(validate=[Regexp(regex=re.compile('.+@.+'))]) class Header(Schema): X_GitHub_Media_Type = fields.String(description='You can check the current version of media type in responses.\n', dump_to='X-GitHub-Media-Type', load_from='X-GitHub-Media-Type') Accept = fields.String(description='Is used to set specified media type.') X_RateLimit_Limit = fields.Integer(dump_to='X-RateLimit-Limit', load_from='X-RateLimit-Limit') X_RateLimit_Remaining = fields.Integer(dump_to='X-RateLimit-Remaining', load_from='X-RateLimit-Remaining') X_RateLimit_Reset = fields.Integer(dump_to='X-RateLimit-Reset', load_from='X-RateLimit-Reset') X_GitHub_Request_Id = fields.Integer(dump_to='X-GitHub-Request-Id', load_from='X-GitHub-Request-Id') class Path(Schema): owner = fields.String(description='Name of repository owner.') repo = fields.String(description='Name of repository.') number = fields.Integer(description='Number of issue.') class IssuedLabelsOutput(object): class Get200(Label): """OK""" def __init__(self, *args, **kwargs): kwargs['many'] = True super().__init__(*args, **kwargs) class Post201(Label): """Created""" pass class Put201(Label): """Created""" pass