Package: apispec-helper
apispec-helperThis project provides objective oriented helper classes collection for defining OpenAPI objects inapispec.NOTE:OpenAPI Specification version supported by this project is3.0.3.OverviewIn 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:integernext_page_url:type:stringprevious_page_url:type:stringdata:type:objectproperties:id:type:integername:type:stringWe'll create Dictionary below for apispecspec.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 likeDjango 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. AsOpenAPI Specificationshows, 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-helperimplements OpenAPI Objects definition with native Python Classes. We also provide helper Classes to simplify works when working with OpenAPI Objects and apispec.For instance, withapispec-helper, we can rewrite thePetsexample:classPaginatedData(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},))classPet(ComponentBase):def__init__(self):super().__init__(component_definition=Object(properties={"id":Integer(),"name":String()},))classPaginatedPet(PaginatedData):def__init__(self):super().__init__(data_schema=Array(description="Array of SupportedCurrency",items=Pet().component_name))# ... snipspec.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:integercurrency_code:type:stringPaginatedPet:properties:result_count:type:integernext_page_url:type:stringprevious_page_url:type:stringdata:items:$ref:'#/components/schemas/Pet'type:arraytype:objectYou can use any Object-Oriented approaches supported by Python withapispec-helper. Fields are explicitly defined in each helper classes instead of using**kwargs, which means modern Python IDEs(e.g.,PyCharmandVSCode) 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).How to useInstallationThis project is released on PyPI(Project Page). To install the latest version from PyPIpipinstall-uapispec-helperBasicsapispec-helperalready provides pre-defined OpenAPI Objects, which are categorized into 3 submodules:basic_typecomponentpathSubmoduleClassOpenAPI Objectbasic_typeExampleExample Objectbasic_typeExternalDocumentationExternal Documentation Objectbasic_typeServerServer ObjectSubmoduleClassOpenAPI ObjectNotecomponentEncodingEncoding ObjectcomponentMediaTypeMedia Type ObjectcomponentHeaderHeader Object- It can be a circular reference forcontentfield, so we define data type asdict[str, str]for convenience- You can use pre-definedcomponent.ParameterStyleenum's value forstylefieldcomponentEncodingEncoding ObjectcomponentLinkLink ObjectcomponentParameterParameter Object- You can use pre-definedcomponent.ParameterStyleClass's member forstylefield- You can use pre-definedcomponent.ParameterLocationenum's value forin_field- You can use pre-definedcomponent.CommonMediaTypeNameforcontentfield as content type namecomponentMediaTypeMedia Type Object- You can use pre-definedcomponent.CommonMediaTypeNameClass's member forcomponentRequestBodyRequest Body Object- You can use pre-definedcomponent.CommonMediaTypeNameClass's member forcontentfield as content type namecomponentResponseResponse Object- You can use pre-definedcomponent.CommonMediaTypeNameClass's member forcontentfield as content type namecomponentSecuritySchemeSecurity Scheme Object- You can use pre-definedcomopnent.SecuritySchemeTypeClass's member value fortype_field- You can use pre-definedcomponent.HTTPAuthenticationSchemeenum's value forschemefield- You can use pre-definedcomponent.APIKeyLocationenum's value forin_fieldcomponentOAuthFlowOAuth Flow ObjectSubmoduleClassOpenAPI ObjectNotepathPathItemPathItem ObjectAll Operations(get/put/post/delete/options/head/patch/trace) are defined throughOperationsClass to be compatible with apispec core APIs' inputpathOperationOperation ObjectAnd basic schema typing helper classesSubmoduleClassNotebasic_typeObjectFor Schema Objecttype: "object"basic_typeOneOfFor Schema Objecttype: "oneOfbasic_typeBooleanFor Schema Objecttype: "booleanbasic_typeNullFor Schema Objecttype: "nullbasic_typeArrayFor Schema Objecttype: "arraybasic_typeStringFor Schema Objecttype: "string. You can use pre-definedcomonent.PreDefineStringFormatClass's value forformat_fieldbasic_typeNumberFor Schema Objecttype: "number. You can use pre-definedcomonent.NumberFormatClass's value forformat_fieldbasic_typeIntegerFor Schema Objecttype: "integer. You can use pre-defined.comonent.IntegerFormatClass's value forformat_fieldbasic_typeOneOfFor Schema Objecttype: "oneOfbasic_typeOneOfFor Schema Objecttype: "oneOfRemember that whatapispec-helperdoes 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 usedict()build-in method. Thus, you can provide these Classes Objects as parameters as below:fromapispec_helper.basic_typeimportObject,Integer,IntegerFormat,StringfromapispecimportAPISpecpet=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 HelperIt can be redundant to import and calldic()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-helperprovides 2 Bases Classes,ComponentBaseandPathBase, to simplify these works.ComponentBaseTheComponentBaseis used as interface to generate input forspec.componentrelated apispec core APIs. It accepts only 1 argument,component_definition:classPet(ComponentBase):def__init__(self):super().__init__(component_definition=Object(properties={"id":Integer(),"name":String()},))After implementing it, child class will have 2 properties,component_nameandcomponent_definition, which return child class name as component name and OpenAPI Object dictionary, respectively:# return "Pet" as stringPet().component_name# return# {#     "type": "object",#     "properties": {#         "id": {#             "type": "integer"#         },#         "name": {#             "type": "string"#         }#     }# }Pet().component_definitionThey can be used ascomponent_idandcomponentforAPISpec.componentcore APIs.spec.component.schema(Pet().component_name,Pet().component_definition)To simplify API call,ComponentBasealso provides a special property,apispec_parameter, to generate dictionary of parameters which apispec core APIs require:spec.component.schema(**Pet().apispec_parameter)PathBaseThePathBaseis used as interface to generate input forAPISpec.pathrelated apispec core APIs. It accepts 2 arguments,pathandpath_item_definition:classGetPetAPI(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 forAPISpec.pathcore API:pathoperationssummarydescriptionparametersNOTE:Directly access properties undefined inPathItemwill cause theKeyErrorexception.# return "/pet"GetPetAPI().path# return# {#     "operations": {#         "get": {#             "responses": {#                 "200": {#                     "content": {#                         "application/json": {#                             "schema": "PaginatedSupportedCurrency"#                         }#                     }#                 }#             }#     }# }GetPetAPI().operations# throws KeyError as it's undefinedGetPetAPI().descriptionGetPetAPI().summaryGetPetAPI().parametersThey can be used as parameter with same name forAPISpec.pathcore API.spec.path(path=GetPetAPI().path,operations=GetPetAPI().operation)Similarly,PathBasehas theapispec_parameterproperty to generate parameter dictionary:spec.path(**GetPetAPI().apispec_parameter)Pre-Defined ClassAs notes theBasicsection show, theapispec-helperprovides Classes with pre-defined value members. You can leverage them when creating OpenAPI Objects. For instance, you can usecomonent.NumberFormatwhen specifyingNumberformatNumber(format_=NumberFormat.FLOAT)In yaml file, it will be replaced with "float"# ... snipttype:"number"format:"float"Keyword replacementSome keywords in OpenAPI Specification conflict with Python keywords. For instance,inandformat. These keywords are with underline(_) postfix to prevent conflict(in_andformat_) inapispec-helper. When converting these Objects to dictionary, underline in these keywords will be removed. We won't see them in dictionaries and yaml outputs.ReferencingThe apispec cangenerate reference statements, so for fields allow referencing, you can directly provide component names instead of adding"$ref": "..."by yourself, which also works inapispec-helper. Some Class fields acceptstrtype value instead of OpenAPI Object type, which means you can pass component name to apispec to generate references. See thePaginatedPetPetexample in theOverviewsection.TodoPRs are welcomed to this project:Unitest to cover all OpenAPI ObjectsSupport all fields and all OpenAPI ObjectsMore thorough API documents