Skip to main content

This project provides objective oriented helper classes collection for defining OpenAPI objects in [apispec](https://pypi.org/project/apispec/).

Project description

apispec-helper

This project provides objective oriented helper classes collection for defining OpenAPI objects in apispec.

NOTE: OpenAPI Specification version supported by this project is 3.0.3.

Overview

In general, when using apispec to create OpenAPI Specifications, we pass OpenAPI Objects to core APIs through Python Dictionary:

spec.components.schema(
    "Gist",
    {
        "properties": {
            "id": {"type": "integer", "format": "int64"},
            "name": {"type": "string"},
        }
    },
)
spec.path(
    path="/gist/{gist_id}",
    operations=dict(
        get=dict(
            responses={"200": {"content": {"application/json": {"schema": "Gist"}}}}
        )
    ),
)

This approach is simple and straightforward. We can easily construct arbitrary OpenAPI Objects in this structure. However, it's not easy to reuse/extend these objects. Consider a paginated Response Object with the following schema:

components:
  schemas:
    Pets:
      properties:
        result_count:
          type: integer
        next_page_url:
          type: string
        previous_page_url:
          type: string
        data:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string

We'll create Dictionary below for apispec

spec.components.schema(
    "Pets",
    {
        "properties": {
            "result_count": {"type": "integer"},
            "next_page_url": {"type": "string"},
            "previous_page_url": {"type": "string"},
            "data": {
                "type": "array",
                "items": {
                    "type":"object",
                    "properties":{
                        "id": {"type": "integer"}, "name":{"type": "string"}
                    }
                } 
            }
        }
    }
)

However, in real world, everything won't be that simple. For instance, if most APIs' response in our application are paginated(e.g., automatically wrapped by backend middleware like Django REST framework), then the pagination metadata field, result_count / next_page_url / previous_page_url, will appear everywhere in source code. Though we can prevent the issue by create functions or class for help, we still need to maintain these utilities by ourselves.

Another disadvantage of the Dictionary approach is we need to lookup fields' definition for OpenAPI Objects. As OpenAPI Specification shows, there are hundreds of fields among different OpenAPI Objects. We need to construct Python dictionaries carefully to prevent us from providing wrong values. It is not a trivial work especially if there are hundreds of OpenAPI Objects or deeply nested OpenAPI Objects defined in our specification. We need a better approach can help us manage field types and provide hints/auto-completion when writing specification.

To address these disadvantage of using Dictionary to represent OpenAPI Objects, apispec-helper implements OpenAPI Objects definition with native Python Classes. We also provide helper Classes to simplify works when working with OpenAPI Objects and apispec.

For instance, with apispec-helper, we can rewrite the Pets example:

class PaginatedData(ComponentBase):
    def __init__(self, data_schema: Object):
        super().__init__(
            component_definition=Object(
                properties={
                    "result_count": Integer(),
                    "next_page_url": String(),
                    "previous_page_url": String(),
                    "data": data_schema
                },
            )
        )


class Pet(ComponentBase):
    def __init__(self):
        super().__init__(
            component_definition=Object(
                properties={
                    "id": Integer(),
                    "name": String()
                },
            )
        )


class PaginatedPet(PaginatedData):
    def __init__(self):
        super().__init__(
            data_schema=Array(
                description="Array of SupportedCurrency",
                items=Pet().component_name
            )
        )

# ... snip
        
spec.components.schema(
    Pet().component_name, Pet().component_definition
)

spec.components.schema(
    PaginatedPet().component_name, PaginatedPet().component_definition
)

It will generate result yaml like:

components:
  schemas:
    Pet:
      properties:
        id:
          type: integer
        currency_code:
          type: string
    PaginatedPet:
      properties:
        result_count:
          type: integer
        next_page_url:
          type: string
        previous_page_url:
          type: string
        data:
          items:
            $ref: '#/components/schemas/Pet'
          type: array
      type: object

You can use any Object-Oriented approaches supported by Python with apispec-helper. Fields are explicitly defined in each helper classes instead of using **kwargs, which means modern Python IDEs(e.g., PyCharm and VSCode) can generate hints and auto-complete your codes. Also, typing hints can help you pass correct Objects to fields(modern IDEs can check parameter types for you).

Auto-Complete

How to use

Installation

This project is released on PyPI(Project Page). To install the latest version from PyPI

pip install -u apispec-helper

Basics

apispec-helper already provides pre-defined OpenAPI Objects, which are categorized into 3 submodules:

  • basic_type
  • component
  • path
Submodule Class OpenAPI Object
basic_type Example Example Object
basic_type ExternalDocumentation External Documentation Object
basic_type Server Server Object
Submodule Class OpenAPI Object Note
component Encoding Encoding Object
component MediaType Media Type Object
component Header Header Object - It can be a circular reference for content field, so we define data type as dict[str, str] for convenience
- You can use pre-defined component.ParameterStyle enum's value for style field
component Encoding Encoding Object
component Link Link Object
component Parameter Parameter Object - You can use pre-defined component.ParameterStyle Class's member for style field
- You can use pre-defined component.ParameterLocation enum's value for in_ field
- You can use pre-defined component.CommonMediaTypeName for content field as content type name
component MediaType Media Type Object - You can use pre-defined component.CommonMediaTypeName Class's member for
component RequestBody Request Body Object - You can use pre-defined component.CommonMediaTypeName Class's member for content field as content type name
component Response Response Object - You can use pre-defined component.CommonMediaTypeName Class's member for content field as content type name
component SecurityScheme Security Scheme Object - You can use pre-defined comopnent.SecuritySchemeType Class's member value for type_ field
- You can use pre-defined component.HTTPAuthenticationScheme enum's value for scheme field
- You can use pre-defined component.APIKeyLocation enum's value for in_ field
component OAuthFlow OAuth Flow Object
Submodule Class OpenAPI Object Note
path PathItem PathItem Object All Operations(get / put / post / delete / options / head / patch / trace) are defined through Operations Class to be compatible with apispec core APIs' input
path Operation Operation Object

And basic schema typing helper classes

Submodule Class Note
basic_type Object For Schema Object type: "object"
basic_type OneOf For Schema Object type: "oneOf
basic_type Boolean For Schema Object type: "boolean
basic_type Null For Schema Object type: "null
basic_type Array For Schema Object type: "array
basic_type String For Schema Object type: "string. You can use pre-defined comonent.PreDefineStringFormat Class's value for format_ field
basic_type Number For Schema Object type: "number. You can use pre-defined comonent.NumberFormat Class's value for format_ field
basic_type Integer For Schema Object type: "integer. You can use pre-defined.comonent.IntegerFormat Class's value for format_ field
basic_type OneOf For Schema Object type: "oneOf
basic_type OneOf For Schema Object type: "oneOf

Remember that what apispec-helper does is still creating dictionary for apispec core APIs, but isn't changing apispec's behavior. To convert these Python Class Objects to Dictionary, you can use dict() build-in method. Thus, you can provide these Classes Objects as parameters as below:

from apispec_helper.basic_type import Object, Integer, IntegerFormat, String
from apispec import APISpec

pet = Object(
    properties={
        "id": Integer(format_=IntegerFormat.INT64),
        "name": String(maxLength=16, minLength=1)
    },
)

spec = APISpec(
    openapi_version="3.0.3",
    title="Tsinn",
    version="1.0.0"
)

spec.components.schema(
    "Pet", dict(pet)
)

Base Class Helper

It can be redundant to import and call dic() repeatedly each time calling apispec core APIs. Also, it takes extra efforts on managing input parameters to apispec core APIs, as apispec doesn't directly accept OpenAPI Objects as input. Therefore, apispec-helper provides 2 Bases Classes, ComponentBase and PathBase, to simplify these works.

ComponentBase

The ComponentBase is used as interface to generate input for spec.component related apispec core APIs. It accepts only 1 argument, component_definition:

class Pet(ComponentBase):
    def __init__(self):
        super().__init__(
            component_definition=Object(
                properties={
                    "id": Integer(),
                    "name": String()
                },
            )
        )

After implementing it, child class will have 2 properties, component_name and component_definition, which return child class name as component name and OpenAPI Object dictionary, respectively:

# return "Pet" as string
Pet().component_name

# return 
# {
#     "type": "object", 
#     "properties": {
#         "id": {
#             "type": "integer"
#         }, 
#         "name": {
#             "type": "string"
#         }
#     }
# }
Pet().component_definition

They can be used as component_id and component for APISpec.component core APIs.

spec.component.schema(
    Pet().component_name, Pet().component_definition
)

To simplify API call, ComponentBase also provides a special property, apispec_parameter, to generate dictionary of parameters which apispec core APIs require:

spec.component.schema(**Pet().apispec_parameter)

PathBase

The PathBase is used as interface to generate input for APISpec.path related apispec core APIs. It accepts 2 arguments, path and path_item_definition:

class GetPetAPI(PathBase):
    def __init__(self):
        super().__init__(
            path="/pet",
            path_item_definition=PathItem(
                Operations(
                    get=Operation(
                        responses={
                            "200": Response(
                                content={
                                    CommonMediaTypeName.APPLICATION_JSON.value: MediaType(
                                        schema=Pet().component_name
                                    )
                                }
                            )
                        }
                    )
                ),
            )
        )

And it generates 5 properties for APISpec.path core API:

  • path
  • operations
  • summary
  • description
  • parameters

NOTE: Directly access properties undefined in PathItem will cause the KeyError exception.

# return "/pet"
GetPetAPI().path

# return
# {
#     "operations": { 
#         "get": {
#             "responses": { 
#                 "200": {
#                     "content": { 
#                         "application/json": {
#                             "schema": "PaginatedSupportedCurrency" 
#                         }
#                     }
#                 }
#             }
#     }
# }
GetPetAPI().operations

# throws KeyError as it's undefined
GetPetAPI().description
GetPetAPI().summary
GetPetAPI().parameters

They can be used as parameter with same name for APISpec.path core API.

spec.path(
    path=GetPetAPI().path,
    operations=GetPetAPI().operation
)

Similarly, PathBase has the apispec_parameter property to generate parameter dictionary:

spec.path(**GetPetAPI().apispec_parameter)

Pre-Defined Class

As notes the Basic section show, the apispec-helper provides Classes with pre-defined value members. You can leverage them when creating OpenAPI Objects. For instance, you can use comonent.NumberFormat when specifying Number format

Number(
    format_=NumberFormat.FLOAT
)

In yaml file, it will be replaced with "float"

# ... snipt
type: "number"
format: "float"  

Keyword replacement

Some keywords in OpenAPI Specification conflict with Python keywords. For instance, in and format. These keywords are with underline(_) postfix to prevent conflict(in_ and format_) in apispec-helper. When converting these Objects to dictionary, underline in these keywords will be removed. We won't see them in dictionaries and yaml outputs.

Referencing

The apispec can generate reference statements, so for fields allow referencing, you can directly provide component names instead of adding "$ref": "..." by yourself, which also works in apispec-helper. Some Class fields accept str type value instead of OpenAPI Object type, which means you can pass component name to apispec to generate references. See the PaginatedPetPet example in the Overview section.

Todo

PRs are welcomed to this project:

  1. Unitest to cover all OpenAPI Objects
  2. Support all fields and all OpenAPI Objects
  3. More thorough API documents

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apispec_helper-0.2.4.tar.gz (16.5 kB view hashes)

Uploaded Source

Built Distribution

apispec_helper-0.2.4-py3-none-any.whl (19.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page