Swagger 2.0 + Flask のPetstoreデモをとりあえず動かす

# 前提

 * 2018-04-29時点の情報 (Qiitaは編集すると日付が詐称できるので)
 * 環境は macOS High Sierra + pyenv + Docker (18.03.1-ce-mac64 (24245))
 * Swagger バージョンは2.0 (Swagger Hubで3.0系はまだ完全に動作しない)
 * Swaggerって呼ぶのかOpen APIって呼ぶのかもよく分かってない
 * IPアドレス指定については大分雑になってる。あくまでチュートリアル的なものなのでこのまま使い続けないこと

# 書いたきっかけ

flask-pythonに関してはSwagger Hubで自動生成されるREADME.mdの通りには動作しなかった。

# 実際に動作するまでやってみる

ここではSwagger HubでPetstoreの例を使う。

<img width="587" alt="スクリーンショット 2018-04-29 06.45.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/8ae0c370-efd4-f715-2709-b30a6a22251f.png">

ダウンロードする

<img width="467" alt="スクリーンショット 2018-04-29 06.45.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/10afab09-88a8-fbc0-d45f-1590cb7610ca.png">

zipファイルを展開して、今回はディレクトリ名を ``swagger-flask-petstore-demo`` とする

```shell-session
$ tree
.
├── Dockerfile
├── README.md
├── git_push.sh
├── requirements.txt
├── setup.py
├── swagger_server
│   ├── __init__.py
│   ├── __main__.py
│   ├── controllers
│   │   ├── __init__.py
│   │   ├── pet_controller.py
│   │   ├── store_controller.py
│   │   └── user_controller.py
│   ├── encoder.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── api_response.py
│   │   ├── base_model_.py
│   │   ├── category.py
│   │   ├── order.py
│   │   ├── pet.py
│   │   ├── tag.py
│   │   └── user.py
│   ├── swagger
│   │   └── swagger.yaml
│   ├── test
│   │   ├── __init__.py
│   │   ├── test_pet_controller.py
│   │   ├── test_store_controller.py
│   │   └── test_user_controller.py
│   └── util.py
├── test-requirements.txt
└── tox.ini

5 directories, 28 files
```

以降のターミナル操作はこのフォルダで行う。

swagger.yaml の次の部分を変更する

 * host -> とりま 127.0.0.1:8080
 * basePath -> ダウンロード時のままでもいいけどパスが煩雑になるので、ここでは /v1 にしてみる。

直した結果は以下の通り。

```yaml
---
swagger: "2.0"
info:
  description: "This is a sample Petstore server.  You can find \nout more about Swagger\
    \ at \n[http://swagger.io](http://swagger.io) or on \n[irc.freenode.net, #swagger](http://swagger.io/irc/).\n"
  version: "1.0.0"
  title: "Swagger Petstore"
  termsOfService: "http://swagger.io/terms/"
  contact:
    email: "apiteam@swagger.io"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
host: "127.0.0.1:8080"
basePath: "/v1"
tags:
- name: "pet"
  description: "Everything about your Pets"
  externalDocs:
    description: "Find out more"
    url: "http://swagger.io"
- name: "store"
  description: "Access to Petstore orders"
- name: "user"
  description: "Operations about user"
  externalDocs:
    description: "Find out more about our store"
    url: "http://swagger.io"
schemes:
- "https"
- "http"
paths:
  /pet:
    post:
      tags:
      - "pet"
      summary: "Add a new pet to the store"
      operationId: "add_pet"
      consumes:
      - "application/json"
      - "application/xml"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "Pet object that needs to be added to the store"
        required: true
        schema:
          $ref: "#/definitions/Pet"
      responses:
        405:
          description: "Invalid input"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
    put:
      tags:
      - "pet"
      summary: "Update an existing pet"
      operationId: "update_pet"
      consumes:
      - "application/json"
      - "application/xml"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "Pet object that needs to be added to the store"
        required: true
        schema:
          $ref: "#/definitions/Pet"
      responses:
        400:
          description: "Invalid ID supplied"
        404:
          description: "Pet not found"
        405:
          description: "Validation exception"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
  /pet/findByStatus:
    get:
      tags:
      - "pet"
      summary: "Finds Pets by status"
      description: "Multiple status values can be provided with comma separated strings"
      operationId: "find_pets_by_status"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "status"
        in: "query"
        description: "Status values that need to be considered for filter"
        required: true
        type: "array"
        items:
          type: "string"
          enum:
          - "available"
          - "pending"
          - "sold"
          default: "available"
        collectionFormat: "multi"
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Pet"
        400:
          description: "Invalid status value"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
  /pet/findByTags:
    get:
      tags:
      - "pet"
      summary: "Finds Pets by tags"
      description: "Muliple tags can be provided with comma separated strings. Use\\\
        \ \\ tag1, tag2, tag3 for testing."
      operationId: "find_pets_by_tags"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "tags"
        in: "query"
        description: "Tags to filter by"
        required: true
        type: "array"
        items:
          type: "string"
        collectionFormat: "multi"
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Pet"
        400:
          description: "Invalid tag value"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      deprecated: true
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
  /pet/{petId}:
    get:
      tags:
      - "pet"
      summary: "Find pet by ID"
      description: "Returns a single pet"
      operationId: "get_pet_by_id"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "petId"
        in: "path"
        description: "ID of pet to return"
        required: true
        type: "integer"
        format: "int64"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/Pet"
        400:
          description: "Invalid ID supplied"
        404:
          description: "Pet not found"
      security:
      - api_key: []
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
    post:
      tags:
      - "pet"
      summary: "Updates a pet in the store with form data"
      operationId: "update_pet_with_form"
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "petId"
        in: "path"
        description: "ID of pet that needs to be updated"
        required: true
        type: "integer"
        format: "int64"
      - name: "name"
        in: "formData"
        description: "Updated name of the pet"
        required: false
        type: "string"
      - name: "status"
        in: "formData"
        description: "Updated status of the pet"
        required: false
        type: "string"
      responses:
        405:
          description: "Invalid input"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
    delete:
      tags:
      - "pet"
      summary: "Deletes a pet"
      operationId: "delete_pet"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "api_key"
        in: "header"
        required: false
        type: "string"
      - name: "petId"
        in: "path"
        description: "Pet id to delete"
        required: true
        type: "integer"
        format: "int64"
      responses:
        400:
          description: "Invalid ID supplied"
        404:
          description: "Pet not found"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
  /pet/{petId}/uploadImage:
    post:
      tags:
      - "pet"
      summary: "uploads an image"
      operationId: "upload_file"
      consumes:
      - "multipart/form-data"
      produces:
      - "application/json"
      parameters:
      - name: "petId"
        in: "path"
        description: "ID of pet to update"
        required: true
        type: "integer"
        format: "int64"
      - name: "additionalMetadata"
        in: "formData"
        description: "Additional data to pass to server"
        required: false
        type: "string"
      - name: "file"
        in: "formData"
        description: "file to upload"
        required: false
        type: "file"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/ApiResponse"
      security:
      - petstore_auth:
        - "write:pets"
        - "read:pets"
      x-swagger-router-controller: "swagger_server.controllers.pet_controller"
  /store/inventory:
    get:
      tags:
      - "store"
      summary: "Returns pet inventories by status"
      description: "Returns a map of status codes to quantities"
      operationId: "get_inventory"
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "successful operation"
          schema:
            type: "object"
            additionalProperties:
              type: "integer"
              format: "int32"
      security:
      - api_key: []
      x-swagger-router-controller: "swagger_server.controllers.store_controller"
  /store/order:
    post:
      tags:
      - "store"
      summary: "Place an order for a pet"
      operationId: "place_order"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "order placed for purchasing the pet"
        required: true
        schema:
          $ref: "#/definitions/Order"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/Order"
        400:
          description: "Invalid Order"
      x-swagger-router-controller: "swagger_server.controllers.store_controller"
  /store/order/{orderId}:
    get:
      tags:
      - "store"
      summary: "Find purchase order by ID"
      description: "For valid response try integer IDs with value >= 1 and <= 10.\\\
        \ \\ Other values will generated exceptions"
      operationId: "get_order_by_id"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "orderId"
        in: "path"
        description: "ID of pet that needs to be fetched"
        required: true
        type: "integer"
        maximum: 10.0
        minimum: 1
        format: "int64"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/Order"
        400:
          description: "Invalid ID supplied"
        404:
          description: "Order not found"
      x-swagger-router-controller: "swagger_server.controllers.store_controller"
    delete:
      tags:
      - "store"
      summary: "Delete purchase order by ID"
      description: "For valid response try integer IDs with positive integer value.\\\
        \ \\ Negative or non-integer values will generate API errors"
      operationId: "delete_order"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "orderId"
        in: "path"
        description: "ID of the order that needs to be deleted"
        required: true
        type: "integer"
        minimum: 1
        format: "int64"
      responses:
        400:
          description: "Invalid ID supplied"
        404:
          description: "Order not found"
      x-swagger-router-controller: "swagger_server.controllers.store_controller"
  /user:
    post:
      tags:
      - "user"
      summary: "Create user"
      description: "This can only be done by the logged in user."
      operationId: "create_user"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "Created user object"
        required: true
        schema:
          $ref: "#/definitions/User"
      responses:
        default:
          description: "successful operation"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
  /user/createWithArray:
    post:
      tags:
      - "user"
      summary: "Creates list of users with given input array"
      operationId: "create_users_with_array_input"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "List of user object"
        required: true
        schema:
          type: "array"
          items:
            $ref: "#/definitions/User"
      responses:
        default:
          description: "successful operation"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
  /user/createWithList:
    post:
      tags:
      - "user"
      summary: "Creates list of users with given input array"
      operationId: "create_users_with_list_input"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - in: "body"
        name: "body"
        description: "List of user object"
        required: true
        schema:
          type: "array"
          items:
            $ref: "#/definitions/User"
      responses:
        default:
          description: "successful operation"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
  /user/login:
    get:
      tags:
      - "user"
      summary: "Logs user into the system"
      operationId: "login_user"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "username"
        in: "query"
        description: "The user name for login"
        required: true
        type: "string"
      - name: "password"
        in: "query"
        description: "The password for login in clear text"
        required: true
        type: "string"
      responses:
        200:
          description: "successful operation"
          schema:
            type: "string"
          headers:
            X-Rate-Limit:
              type: "integer"
              format: "int32"
              description: "calls per hour allowed by the user"
            X-Expires-After:
              type: "string"
              format: "date-time"
              description: "date in UTC when token expires"
        400:
          description: "Invalid username/password supplied"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
  /user/logout:
    get:
      tags:
      - "user"
      summary: "Logs out current logged in user session"
      operationId: "logout_user"
      produces:
      - "application/json"
      - "application/xml"
      parameters: []
      responses:
        default:
          description: "successful operation"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
  /user/{username}:
    get:
      tags:
      - "user"
      summary: "Get user by user name"
      operationId: "get_user_by_name"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "username"
        in: "path"
        description: "The name that needs to be fetched. Use user1 for testing."
        required: true
        type: "string"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/User"
        400:
          description: "Invalid username supplied"
        404:
          description: "User not found"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
    put:
      tags:
      - "user"
      summary: "Updated user"
      description: "This can only be done by the logged in user."
      operationId: "update_user"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "username"
        in: "path"
        description: "name that need to be updated"
        required: true
        type: "string"
      - in: "body"
        name: "body"
        description: "Updated user object"
        required: true
        schema:
          $ref: "#/definitions/User"
      responses:
        400:
          description: "Invalid user supplied"
        404:
          description: "User not found"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
    delete:
      tags:
      - "user"
      summary: "Delete user"
      description: "This can only be done by the logged in user."
      operationId: "delete_user"
      produces:
      - "application/json"
      - "application/xml"
      parameters:
      - name: "username"
        in: "path"
        description: "The name that needs to be deleted"
        required: true
        type: "string"
      responses:
        400:
          description: "Invalid username supplied"
        404:
          description: "User not found"
      x-swagger-router-controller: "swagger_server.controllers.user_controller"
securityDefinitions:
  petstore_auth:
    type: "oauth2"
    authorizationUrl: "http://petstore.swagger.io/oauth/dialog"
    flow: "implicit"
    scopes:
      write:pets: "modify pets in your account"
      read:pets: "read your pets"
  api_key:
    type: "apiKey"
    name: "api_key"
    in: "header"
definitions:
  Order:
    type: "object"
    properties:
      id:
        type: "integer"
        format: "int64"
      petId:
        type: "integer"
        format: "int64"
      quantity:
        type: "integer"
        format: "int32"
      shipDate:
        type: "string"
        format: "date-time"
      status:
        type: "string"
        description: "Order Status"
        enum:
        - "placed"
        - "approved"
        - "delivered"
      complete:
        type: "boolean"
        default: false
    example:
      petId: 6
      quantity: 1
      id: 0
      shipDate: "2000-01-23T04:56:07.000+00:00"
      complete: false
      status: "placed"
    xml:
      name: "Order"
  Category:
    type: "object"
    properties:
      id:
        type: "integer"
        format: "int64"
      name:
        type: "string"
    example:
      name: "name"
      id: 6
    xml:
      name: "Category"
  User:
    type: "object"
    properties:
      id:
        type: "integer"
        format: "int64"
      username:
        type: "string"
      firstName:
        type: "string"
      lastName:
        type: "string"
      email:
        type: "string"
      password:
        type: "string"
      phone:
        type: "string"
      userStatus:
        type: "integer"
        format: "int32"
        description: "User Status"
    example:
      firstName: "firstName"
      lastName: "lastName"
      password: "password"
      userStatus: 6
      phone: "phone"
      id: 0
      email: "email"
      username: "username"
    xml:
      name: "User"
  Tag:
    type: "object"
    properties:
      id:
        type: "integer"
        format: "int64"
      name:
        type: "string"
    example:
      name: "name"
      id: 1
    xml:
      name: "Tag"
  Pet:
    type: "object"
    required:
    - "name"
    - "photoUrls"
    properties:
      id:
        type: "integer"
        format: "int64"
      category:
        $ref: "#/definitions/Category"
      name:
        type: "string"
        example: "doggie"
      photoUrls:
        type: "array"
        xml:
          name: "photoUrl"
          wrapped: true
        items:
          type: "string"
      tags:
        type: "array"
        xml:
          name: "tag"
          wrapped: true
        items:
          $ref: "#/definitions/Tag"
      status:
        type: "string"
        description: "pet status in the store"
        enum:
        - "available"
        - "pending"
        - "sold"
    example:
      photoUrls:
      - "photoUrls"
      - "photoUrls"
      name: "doggie"
      id: 0
      category:
        name: "name"
        id: 6
      tags:
      - name: "name"
        id: 1
      - name: "name"
        id: 1
      status: "available"
    xml:
      name: "Pet"
  ApiResponse:
    type: "object"
    properties:
      code:
        type: "integer"
        format: "int32"
      type:
        type: "string"
      message:
        type: "string"
    example:
      code: 0
      type: "type"
      message: "message"
externalDocs:
  description: "Find out more about Swagger"
  url: "http://swagger.io"
```

``swagger_server/__main__.py`` で 0.0.0.0 を明示する。

```py
#!/usr/bin/env python3

import connexion

from swagger_server import encoder


def main():
    app = connexion.App(__name__, specification_dir='./swagger/')
    app.app.json_encoder = encoder.JSONEncoder
    app.add_api('swagger.yaml', arguments={'title': 'Swagger Petstore'})
    app.run(host='0.0.0.0', port=8080)


if __name__ == '__main__':
    main()
```

本稿執筆時点ではPython 3.5系でないとREADME.mdの指定しているtoxによるテストは失敗する。よって本稿ではpyenvとかで3.5系を使うことにする。なお、サーバ本体は3.6などでも動作したし、DockerfileではPythonのバージョン指定はない。

```shell-session
$ pyenv shell 3.5.5
$ python --version
Python 3.5.5
$ pip intsall -U pip  # Swaggerとは関係ないけどpipを最新の 10.0.1 にしておく
$ pip install -r requirements.txt
$ pip freeze
certifi==2018.4.16
chardet==3.0.4
click==6.7
clickclick==1.2.2
connexion==1.1.15
Flask==1.0
idna==2.6
inflection==0.3.1
itsdangerous==0.24
Jinja2==2.10
jsonschema==2.6.0
MarkupSafe==1.0
python-dateutil==2.6.0
PyYAML==3.12
requests==2.18.4
six==1.11.0
swagger-spec-validator==2.1.0
typing==3.6.4
urllib3==1.22
Werkzeug==0.14.1
```

まずローカルで直接サーバを起動する

```shell-session
$ python3 -m swagger_server
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
```

http://127.0.0.1:8080/v1/ui/ へブラウザでアクセスする。

<img width="986" alt="スクリーンショット 2018-04-29 07.03.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/233dcc8d-4619-a8b6-faa2-8b71abf9d65d.png">

めでたい。

さっそくpetをPOSTする例を動かす。「pet: Everything about your Pets」のところをクリックすると可能な操作一覧が出るのでPOSTを選ぶ。bodyの部分は最初空なので、右側の「Example Value」をクリックしてとりあえず埋める。

<img width="977" alt="スクリーンショット 2018-04-29 07.04.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/40bb1b27-991c-503a-5dc9-21967752fba4.png">

「Try it out!」をクリック

<img width="949" alt="スクリーンショット 2018-04-29 07.06.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/5d4b45d7-1442-6b52-0ec8-71aad3334116.png">

「Curl」の部分に対応する要求を送信してみる。レスポンスをより詳細に見るため -v も付けてみる。

筆者が使っているzshのせいかもしれないが、ブラウザに表示される \ 付きのJSONはまともなJSONとはみなされない。 jq を通してシェル経由でちゃんとしたJSONとなるものであることを確認してなげつける、か、curlでファイルを指定する

```shell-session
$ echo '{ "category": { "id": 6, "name": "name"}, "id": 0, "name": "doggie", "photoUrls": ["photoUrls",    "photoUrls"], "status": "available", "tags": [{"id": 1, "name": "name"}, {"id": 1, "name": "name"}]}' | jq .
{
  "category": {
    "id": 6,
    "name": "name"
  },
  "id": 0,
  "name": "doggie",
  "photoUrls": [
    "photoUrls",
    "photoUrls"
  ],
  "status": "available",
  "tags": [
    {
      "id": 1,
      "name": "name"
    },
    {
      "id": 1,
      "name": "name"
    }
  ]
}
$ curl -v -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ "category": { "id": 6, "name": "name"}, "id": 0, "name": "doggie", "photoUrls": ["photoUrls",    "photoUrls"], "status": "available", "tags": [{"id": 1, "name": "name"}, {"id": 1, "name": "name"}]}' 'http://127.0.0.1:8080/v1/pet'
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> PUT /v1/pet HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.54.0
> Content-Type: application/json
> Accept: application/json
> Content-Length: 199
>
* upload completely sent off: 199 out of 199 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 17
< Server: Werkzeug/0.14.1 Python/3.5.5
< Date: Sat, 28 Apr 2018 22:20:12 GMT
<
"do some magic!"
* Closing connection 0
```
この ```"do some magic!``` は swagger_server/controllers/pet_controller.py にある``add_pet()``関数の戻り値である。``add_pet()``が呼ばれるのはswagger.yamlの /pet に対するPOSTリクエストについて ``operationId: "add_pet"`` と書かれているから。

生成されたスタブの返答をそのまま返しているだけであって、DBに値が保存されたりはしない。その部分は作り込むことになる

```py
def add_pet(body):  # noqa: E501
    """Add a new pet to the store

     # noqa: E501

    :param body: Pet object that needs to be added to the store
    :type body: dict | bytes

    :rtype: None
    """
    if connexion.request.is_json:
        body = Pet.from_dict(connexion.request.get_json())  # noqa: E501
    return 'do some magic!'
```

この関数の戻り値 ``"do some magic"`` を ``"do some mowa magic"`` にしてサーバを再起動する。と値は変わる

```shell-session
$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ "category": { "id": 6, "name": "name"}, "id": 0, "name": "doggie", "photoUrls": ["photoUrls",    "photoUrls"], "status": "available", "tags": [{"id": 1, "name": "name"}, {"id": 1, "name": "name"}]}' 'http://127.0.0.1:8080/v1/pet'
"do some mowa magic!"
```

PUTリクエストは別の関数(``update_pet()``)が対応する。よって、PUTすると結果は(初期生成時の) ``"do some magic!"`` になる

```shell-session
$ curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ "category": { "id": 6, "name": "name"}, "id": 0, "name": "doggie", "photoUrls": ["photoUrls",    "photoUrls"], "status": "available", "tags": [{"id": 1, "name": "name"}, {"id": 1, "name": "name"}]}' 'http://127.0.0.1:8080/v1/pet'
"do some magic!"
```

自動生成されたプロジェクトにはDockerfileもあるのでそちらも使ってみる。ローカルで直接起動したサーバを停止し、

```shell-session
$ docker build --no-cache -t swagger_server .
Sending build context to Docker daemon  154.6kB
Step 1/9 : FROM python:3-alpine
 ---> 8eb1c554687d
Step 2/9 : RUN mkdir -p /usr/src/app
 ---> Running in 9b6f9a272ca0
Removing intermediate container 9b6f9a272ca0
 ---> 176af67c9017
Step 3/9 : WORKDIR /usr/src/app
Removing intermediate container 9551c3424c4a
 ---> f7072bc243f2
Step 4/9 : COPY requirements.txt /usr/src/app/
 ---> ca9f71347b7e
Step 5/9 : RUN pip3 install --no-cache-dir -r requirements.txt
 ---> Running in 71b2b320510e
Collecting connexion==1.1.15 (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/06/45/e87be44f33982b2ee7b4fd5b1fe8bb616a22517f751f8fe2ee0b5567d231/connexion-1.1.15-py2.py3-none-any.whl (1.0MB)
Collecting python_dateutil==2.6.0 (from -r requirements.txt (line 2))
  Downloading https://files.pythonhosted.org/packages/40/8b/275015d7a9ec293cf1bbf55433258fbc9d0711890a7f6dc538bac7b86bce/python_dateutil-2.6.0-py2.py3-none-any.whl (194kB)
Requirement already satisfied: setuptools>=21.0.0 in /usr/local/lib/python3.6/site-packages (from -r requirements.txt (line 3)) (39.0.1)
Collecting requests>=2.9.1 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/49/df/50aa1999ab9bde74656c2919d9c0c085fd2b3775fd3eca826012bef76d8c/requests-2.18.4-py2.py3-none-any.whl (88kB)
Collecting inflection>=0.3.1 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/d5/35/a6eb45b4e2356fe688b21570864d4aa0d0a880ce387defe9c589112077f8/inflection-0.3.1.tar.gz
Collecting clickclick>=1.2 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/b6/51/2b04f7a56dcbacc0e3a7cf726e1d88d28866bf488a7a0668582306e1e643/clickclick-1.2.2-py2.py3-none-any.whl
Collecting PyYAML>=3.11 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/4a/85/db5a2df477072b2902b0eb892feb37d88ac635d36245a72a6a69b23b383a/PyYAML-3.12.tar.gz (253kB)
Collecting six>=1.9 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Collecting swagger-spec-validator>=2.0.2 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/44/02/bcc0122d561d9727b8ca476058f2c57a37a1c86d0f7c9aec5543f3219cd0/swagger_spec_validator-2.1.0-py2.py3-none-any.whl
Collecting jsonschema>=2.5.1 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/77/de/47e35a97b2b05c2fadbec67d44cfcdcd09b8086951b331d82de90d2912da/jsonschema-2.6.0-py2.py3-none-any.whl
Collecting flask>=0.10.1 (from connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/55/b1/4365193655df97227ace49311365cc296e74b60c7f5c63d23cd30175e2f6/Flask-1.0-py2.py3-none-any.whl (97kB)
Collecting idna<2.7,>=2.5 (from requests>=2.9.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/27/cc/6dd9a3869f15c2edfab863b992838277279ce92663d334df9ecf5106f5c6/idna-2.6-py2.py3-none-any.whl (56kB)
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.9.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl (133kB)
Collecting certifi>=2017.4.17 (from requests>=2.9.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/7c/e6/92ad559b7192d846975fc916b65f667c7b8c3a32bea7372340bfe9a15fa5/certifi-2018.4.16-py2.py3-none-any.whl (150kB)
Collecting urllib3<1.23,>=1.21.1 (from requests>=2.9.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/63/cb/6965947c13a94236f6d4b8223e21beb4d576dc72e8130bd7880f600839b8/urllib3-1.22-py2.py3-none-any.whl (132kB)
Collecting click>=4.0 (from clickclick>=1.2->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/34/c1/8806f99713ddb993c5366c362b2f908f18269f8d792aff1abfd700775a77/click-6.7-py2.py3-none-any.whl (71kB)
Collecting Jinja2>=2.10 (from flask>=0.10.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting Werkzeug>=0.14 (from flask>=0.10.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
Collecting itsdangerous>=0.24 (from flask>=0.10.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz (46kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->flask>=0.10.1->connexion==1.1.15->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz
Installing collected packages: idna, chardet, certifi, urllib3, requests, inflection, PyYAML, click, clickclick, six, jsonschema, swagger-spec-validator, MarkupSafe, Jinja2, Werkzeug, itsdangerous, flask, connexion, python-dateutil
  Running setup.py install for inflection: started
    Running setup.py install for inflection: finished with status 'done'
  Running setup.py install for PyYAML: started
    Running setup.py install for PyYAML: finished with status 'done'
  Running setup.py install for MarkupSafe: started
    Running setup.py install for MarkupSafe: finished with status 'done'
  Running setup.py install for itsdangerous: started
    Running setup.py install for itsdangerous: finished with status 'done'
Successfully installed Jinja2-2.10 MarkupSafe-1.0 PyYAML-3.12 Werkzeug-0.14.1 certifi-2018.4.16 chardet-3.0.4 click-6.7 clickclick-1.2.2 connexion-1.1.15 flask-1.0 idna-2.6 inflection-0.3.1 itsdangerous-0.24 jsonschema-2.6.0 python-dateutil-2.6.0 requests-2.18.4 six-1.11.0 swagger-spec-validator-2.1.0 urllib3-1.22
Removing intermediate container 71b2b320510e
 ---> ed0d2de0fc8a
Step 6/9 : COPY . /usr/src/app
 ---> b40c2fda69f1
Step 7/9 : EXPOSE 8080
 ---> Running in d40b03e81b4a
Removing intermediate container d40b03e81b4a
 ---> 8058305f7fe8
Step 8/9 : ENTRYPOINT ["python3"]
 ---> Running in 8fea11ad827e
Removing intermediate container 8fea11ad827e
 ---> 1ad93d981fb6
Step 9/9 : CMD ["-m", "swagger_server"]
 ---> Running in f5ac6f82636e
Removing intermediate container f5ac6f82636e
 ---> 523a00b54d14
Successfully built 523a00b54d14
Successfully tagged swagger_server:latest
```

起動する

```shell-session
$ docker run -p 8080:8080 swagger_server
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
... OAuth2 token info URL missing. **IGNORING SECURITY REQUIREMENTS**
 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
```

```shell-session
$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ "category": { "id": 6, "name": "name"}, "id": 0, "name": "doggie", "photoUrls": ["photoUrls",    "photoUrls"], "status": "available", "tags": [{"id": 1, "name": "name"}, {"id": 1, "name": "name"}]}' 'http://127.0.0.1:8080/v1/pet'
"do some mowa magic!"
```

めでたい。

# テスト

自動生成されるテストコードに元のbasePathが含まれているので実際にテストを足すときには厄介かもしれないが、toxのテストを走らせること自体は難しくない

```shell-session
$ pip install tox
(略)
$ tox
(略)
py35 installed: certifi==2018.4.16,chardet==3.0.4,click==6.7,clickclick==1.2.2,connexion==1.1.15,coverage==4.5.1,Flask==1.0,Flask-Testing==0.6.1,idna==2.6,inflection==0.3.1,itsdangerous==0.24,Jinja2==2.10,jsonschema==2.6.0,MarkupSafe==1.0,nose==1.3.7,pluggy==0.6.0,py==1.5.3,python-dateutil==2.6.0,PyYAML==3.12,randomize==0.14,requests==2.18.4,six==1.11.0,swagger-server==1.0.0,swagger-spec-validator==2.1.0,typing==3.6.4,urllib3==1.22,Werkzeug==0.14.1
py35 runtests: PYTHONHASHSEED='2559809014'
py35 runtests: commands[0] | nosetests

----------------------------------------------------------------------
Ran 0 tests in 0.338s

OK
_____________________________________________________________________ summary ______________________________________________________________________
  py35: commands succeeded
  congratulations :)
```

テスト0件だが、とりあえずめでたい。

# 補足

Docker無し環境で動作させる際に ``swagger_server/__main__.py`` で ``host='127.0.0.1`` などとしてみたらDockerで動作しなくなった。127.0.0.1はDockerの外からアクセスできないループバックアドレスなので、8080:8080のポート対応をしてもそもそもDockerの外にサーバを晒していないことから、(Dockerの)外部からの接続に失敗する。

ただ、 ``0.0.0.0`` を指定したらしたで、FWとか設定してなければ同じネットワークの他のホストからもAPIサーバが見えてしまう。開発中とはいえ、やや心配になる。このあたりはしっかり使う場合はもう少し考えたい（か、Dockerオンリーの用途にしてしまいたい）気がする。細かいところなのでこれ以上は書かない。

自動生成コードだから仕方ないとは言え ``# noqa: E501`` (E501 line too long) をとりあえず付ける感じになってるのはいただけない、といったくらいか