Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

apispec-helper

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apispec-helper

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

  • 0.2.4
  • PyPI
  • Socket score

Maintainers
1

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
SubmoduleClassOpenAPI Object
basic_typeExampleExample Object
basic_typeExternalDocumentationExternal Documentation Object
basic_typeServerServer Object
SubmoduleClassOpenAPI ObjectNote
componentEncodingEncoding Object
componentMediaTypeMedia Type Object
componentHeaderHeader 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
componentEncodingEncoding Object
componentLinkLink Object
componentParameterParameter 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
componentMediaTypeMedia Type Object- You can use pre-defined component.CommonMediaTypeName Class's member for
componentRequestBodyRequest Body Object- You can use pre-defined component.CommonMediaTypeName Class's member for content field as content type name
componentResponseResponse Object- You can use pre-defined component.CommonMediaTypeName Class's member for content field as content type name
componentSecuritySchemeSecurity 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
componentOAuthFlowOAuth Flow Object
SubmoduleClassOpenAPI ObjectNote
pathPathItemPathItem ObjectAll Operations(get / put / post / delete / options / head / patch / trace) are defined through Operations Class to be compatible with apispec core APIs' input
pathOperationOperation Object

And basic schema typing helper classes

SubmoduleClassNote
basic_typeObjectFor Schema Object type: "object"
basic_typeOneOfFor Schema Object type: "oneOf
basic_typeBooleanFor Schema Object type: "boolean
basic_typeNullFor Schema Object type: "null
basic_typeArrayFor Schema Object type: "array
basic_typeStringFor Schema Object type: "string. You can use pre-defined comonent.PreDefineStringFormat Class's value for format_ field
basic_typeNumberFor Schema Object type: "number. You can use pre-defined comonent.NumberFormat Class's value for format_ field
basic_typeIntegerFor Schema Object type: "integer. You can use pre-defined.comonent.IntegerFormat Class's value for format_ field
basic_typeOneOfFor Schema Object type: "oneOf
basic_typeOneOfFor 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

Keywords

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc