From 0ae37ebde135ee6b0658f3c424e9af85ceec781b Mon Sep 17 00:00:00 2001 From: Lyuyang Hu Date: Tue, 10 May 2022 22:48:15 -0400 Subject: [PATCH] use python instead of python-experimental for py client --- .github/workflows/py-client-release.yml | 4 +- .gitignore | 1 + openapi/openapi.yaml | 11 +- .../README.mustache} | 18 +- .../README_common.mustache | 111 + .../README_onlypackage.mustache} | 27 +- .../__init__.mustache} | 0 .../__init__api.mustache} | 12 +- .../__init__apis.mustache} | 17 +- .../__init__model.mustache} | 2 +- .../__init__models.mustache} | 14 +- .../__init__package.mustache} | 12 +- .../api.mustache | 321 +++ .../api_client.mustache | 887 +++++++ .../api_doc.mustache | 79 + .../api_doc_example.mustache | 76 + .../api_test.mustache} | 18 +- .../asyncio/rest.mustache | 237 ++ .../configuration.mustache} | 157 +- .../exceptions.mustache} | 38 +- .../git_push.sh.mustache} | 7 +- .../gitignore.mustache} | 1 - .../gitlab-ci.mustache} | 14 +- .../model.mustache | 58 + .../model_doc.mustache | 37 + .../model_templates/classvars.mustache | 138 ++ .../docstring_allowed.mustache | 4 + .../docstring_init_required_kwargs.mustache | 30 + .../docstring_openapi_validations.mustache | 7 + .../model_templates/invalid_pos_args.mustache | 9 + ...method_from_openapi_data_composed.mustache | 66 + .../method_from_openapi_data_normal.mustache | 17 + .../method_from_openapi_data_shared.mustache | 49 + .../method_from_openapi_data_simple.mustache | 62 + .../method_init_composed.mustache | 80 + .../method_init_normal.mustache | 30 + .../method_init_shared.mustache | 52 + .../method_init_simple.mustache | 66 + .../method_set_attribute.mustache | 51 + .../methods_setattr_getattr_composed.mustache | 103 + .../methods_setattr_getattr_normal.mustache | 32 + .../model_templates/methods_shared.mustache | 34 + .../methods_todict_tostr_eq_shared.mustache | 24 + .../methods_tostr_eq_simple.mustache | 16 + .../model_templates/model_composed.mustache | 74 + .../model_templates/model_normal.mustache | 44 + .../model_templates/model_simple.mustache | 22 + .../model_templates/validations.mustache | 34 + .../model_test.mustache} | 18 +- .../model_utils.mustache | 1720 ++++++++++++++ .../partial_header.mustache | 17 + .../python_doc_auth_partial.mustache} | 34 +- .../requirements.mustache | 3 + .../rest.mustache | 338 +++ .../setup.mustache} | 38 +- .../setup_cfg.mustache} | 4 +- .../signing.mustache} | 3 +- .../test-requirements.mustache | 13 + .../tornado/rest.mustache | 222 ++ .../tox.mustache | 9 + .../travis.mustache} | 10 +- .../README_common.handlebars | 111 - .../api.handlebars | 26 - .../api_client.handlebars | 1379 ----------- .../api_doc.handlebars | 212 -- .../api_doc_example.handlebars | 163 -- .../api_doc_schema_type_hint.handlebars | 10 - .../endpoint.handlebars | 549 ----- .../endpoint_body_serialization.handlebars | 6 - .../endpoint_parameter.handlebars | 17 - .../model.handlebars | 17 - .../model_doc.handlebars | 9 - .../composed_schemas.handlebars | 86 - .../model_templates/dict_partial.handlebars | 54 - .../enum_value_to_name.handlebars | 12 - .../model_templates/enums.handlebars | 16 - .../imports_schema_types.handlebars | 42 - .../imports_schemas.handlebars | 6 - .../model_templates/new.handlebars | 53 - .../model_templates/schema.handlebars | 46 - .../schema_composed_or_anytype.handlebars | 48 - .../model_templates/schema_dict.handlebars | 23 - .../model_templates/schema_list.handlebars | 30 - .../model_templates/schema_simple.handlebars | 27 - .../model_templates/validations.handlebars | 50 - .../model_templates/var_equals_cls.handlebars | 1 - .../model_templates/xbase_schema.handlebars | 51 - .../partial_header.handlebars | 17 - .../requirements.handlebars | 5 - .../rest.handlebars | 251 -- .../schema_doc.handlebars | 32 - .../schemas.handlebars | 2038 ----------------- .../test-requirements.handlebars | 15 - .../tox.handlebars | 9 - 94 files changed, 5408 insertions(+), 5635 deletions(-) rename openapi/{python_explerimental_client_template/README.handlebars => python_client_urllib3_templates/README.mustache} (79%) create mode 100644 openapi/python_client_urllib3_templates/README_common.mustache rename openapi/{python_explerimental_client_template/README_onlypackage.handlebars => python_client_urllib3_templates/README_onlypackage.mustache} (76%) rename openapi/{python_explerimental_client_template/__init__.handlebars => python_client_urllib3_templates/__init__.mustache} (100%) rename openapi/{python_explerimental_client_template/__init__api.handlebars => python_client_urllib3_templates/__init__api.mustache} (74%) rename openapi/{python_explerimental_client_template/__init__apis.handlebars => python_client_urllib3_templates/__init__apis.mustache} (60%) rename openapi/{python_explerimental_client_template/__init__model.handlebars => python_client_urllib3_templates/__init__model.mustache} (85%) rename openapi/{python_explerimental_client_template/__init__models.handlebars => python_client_urllib3_templates/__init__models.mustache} (61%) rename openapi/{python_explerimental_client_template/__init__package.handlebars => python_client_urllib3_templates/__init__package.mustache} (81%) create mode 100644 openapi/python_client_urllib3_templates/api.mustache create mode 100644 openapi/python_client_urllib3_templates/api_client.mustache create mode 100644 openapi/python_client_urllib3_templates/api_doc.mustache create mode 100644 openapi/python_client_urllib3_templates/api_doc_example.mustache rename openapi/{python_explerimental_client_template/api_test.handlebars => python_client_urllib3_templates/api_test.mustache} (57%) create mode 100644 openapi/python_client_urllib3_templates/asyncio/rest.mustache rename openapi/{python_explerimental_client_template/configuration.handlebars => python_client_urllib3_templates/configuration.mustache} (88%) rename openapi/{python_explerimental_client_template/exceptions.handlebars => python_client_urllib3_templates/exceptions.mustache} (79%) rename openapi/{python_explerimental_client_template/git_push.sh.handlebars => python_client_urllib3_templates/git_push.sh.mustache} (87%) rename openapi/{python_explerimental_client_template/gitignore.handlebars => python_client_urllib3_templates/gitignore.mustache} (96%) rename openapi/{python_explerimental_client_template/gitlab-ci.handlebars => python_client_urllib3_templates/gitlab-ci.mustache} (79%) create mode 100644 openapi/python_client_urllib3_templates/model.mustache create mode 100644 openapi/python_client_urllib3_templates/model_doc.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/classvars.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/docstring_allowed.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/docstring_init_required_kwargs.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/docstring_openapi_validations.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/invalid_pos_args.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_composed.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_normal.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_shared.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_simple.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_init_composed.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_init_normal.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_init_shared.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_init_simple.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/method_set_attribute.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_composed.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_normal.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/methods_shared.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/methods_todict_tostr_eq_shared.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/methods_tostr_eq_simple.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/model_composed.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/model_normal.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/model_simple.mustache create mode 100644 openapi/python_client_urllib3_templates/model_templates/validations.mustache rename openapi/{python_explerimental_client_template/model_test.handlebars => python_client_urllib3_templates/model_test.mustache} (70%) create mode 100644 openapi/python_client_urllib3_templates/model_utils.mustache create mode 100644 openapi/python_client_urllib3_templates/partial_header.mustache rename openapi/{python_explerimental_client_template/doc_auth_partial.handlebars => python_client_urllib3_templates/python_doc_auth_partial.mustache} (92%) create mode 100644 openapi/python_client_urllib3_templates/requirements.mustache create mode 100644 openapi/python_client_urllib3_templates/rest.mustache rename openapi/{python_explerimental_client_template/setup.handlebars => python_client_urllib3_templates/setup.mustache} (51%) rename openapi/{python_explerimental_client_template/setup_cfg.handlebars => python_client_urllib3_templates/setup_cfg.mustache} (86%) rename openapi/{python_explerimental_client_template/signing.handlebars => python_client_urllib3_templates/signing.mustache} (99%) create mode 100644 openapi/python_client_urllib3_templates/test-requirements.mustache create mode 100644 openapi/python_client_urllib3_templates/tornado/rest.mustache create mode 100644 openapi/python_client_urllib3_templates/tox.mustache rename openapi/{python_explerimental_client_template/travis.handlebars => python_client_urllib3_templates/travis.mustache} (82%) delete mode 100644 openapi/python_explerimental_client_template/README_common.handlebars delete mode 100644 openapi/python_explerimental_client_template/api.handlebars delete mode 100644 openapi/python_explerimental_client_template/api_client.handlebars delete mode 100644 openapi/python_explerimental_client_template/api_doc.handlebars delete mode 100644 openapi/python_explerimental_client_template/api_doc_example.handlebars delete mode 100644 openapi/python_explerimental_client_template/api_doc_schema_type_hint.handlebars delete mode 100644 openapi/python_explerimental_client_template/endpoint.handlebars delete mode 100644 openapi/python_explerimental_client_template/endpoint_body_serialization.handlebars delete mode 100644 openapi/python_explerimental_client_template/endpoint_parameter.handlebars delete mode 100644 openapi/python_explerimental_client_template/model.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_doc.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/composed_schemas.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/dict_partial.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/enum_value_to_name.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/enums.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/imports_schema_types.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/imports_schemas.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/new.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/schema.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/schema_composed_or_anytype.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/schema_dict.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/schema_list.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/schema_simple.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/validations.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/var_equals_cls.handlebars delete mode 100644 openapi/python_explerimental_client_template/model_templates/xbase_schema.handlebars delete mode 100644 openapi/python_explerimental_client_template/partial_header.handlebars delete mode 100644 openapi/python_explerimental_client_template/requirements.handlebars delete mode 100644 openapi/python_explerimental_client_template/rest.handlebars delete mode 100644 openapi/python_explerimental_client_template/schema_doc.handlebars delete mode 100644 openapi/python_explerimental_client_template/schemas.handlebars delete mode 100644 openapi/python_explerimental_client_template/test-requirements.handlebars delete mode 100644 openapi/python_explerimental_client_template/tox.handlebars diff --git a/.github/workflows/py-client-release.yml b/.github/workflows/py-client-release.yml index e4d3056f..6e66ba6d 100644 --- a/.github/workflows/py-client-release.yml +++ b/.github/workflows/py-client-release.yml @@ -7,7 +7,7 @@ on: [ "openapi/openapi.yaml", ".github/workflows/py-client-release.yml", - "openapi/python_explerimental_client_template/*", + "openapi/python_client_urllib3_templates/*", ] jobs: @@ -33,7 +33,7 @@ jobs: - name: Generate Python Client run: | wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.4.0/openapi-generator-cli-5.4.0.jar -O openapi-generator-cli.jar - java -jar openapi-generator-cli.jar generate -i openapi/openapi.yaml -g python-experimental -o python_client -t openapi/python_explerimental_client_template "--additional-properties=packageName=explainaboard_api_client,packageVersion=${{ steps.gen_tag.outputs.tag }}" + java -jar openapi-generator-cli.jar generate -i openapi/openapi.yaml -g python -o python_client -t "openapi/python_client_urllib3_templates" "--additional-properties=packageName=explainaboard_api_client,packageVersion=${{ steps.gen_tag.outputs.tag }}" - name: "build" run: | cd python_client diff --git a/.gitignore b/.gitignore index b167700f..8e1553eb 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ swagger-codegen-cli*.jar openapi-generator-cli*.jar frontend/src/clients/openapi openapi/python_client +openapi/python_experimental_client_templates .env diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index fb5c7e5f..11db3aaf 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -689,7 +689,16 @@ components: format: url paper_info: $ref: "#/components/schemas/PaperInfo" - required: [task, is_private, model_name, metric_names, paper_info] + required: + [ + task, + is_private, + model_name, + metric_names, + paper_info, + source_language, + target_language, + ] SystemOutputProps: type: object diff --git a/openapi/python_explerimental_client_template/README.handlebars b/openapi/python_client_urllib3_templates/README.mustache similarity index 79% rename from openapi/python_explerimental_client_template/README.handlebars rename to openapi/python_client_urllib3_templates/README.mustache index aee6b66d..b0d33e53 100644 --- a/openapi/python_explerimental_client_template/README.handlebars +++ b/openapi/python_client_urllib3_templates/README.mustache @@ -1,25 +1,23 @@ # {{{projectName}}} -{{#if appDescriptionWithNewLines}} -{{{appDescriptionWithNewLines}}} -{{/if}} +{{#appDescriptionWithNewLines}} +{{{.}}} +{{/appDescriptionWithNewLines}} This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: {{appVersion}} - Package version: {{packageVersion}} -{{#unless hideGenerationTimestamp}} +{{^hideGenerationTimestamp}} - Build date: {{generatedDate}} -{{/unless}} +{{/hideGenerationTimestamp}} - Build package: {{generatorClass}} -{{#if infoUrl}} +{{#infoUrl}} For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) -{{/if}} +{{/infoUrl}} ## Requirements. -Python {{generatorLanguageVersion}} -v3.9 is needed so one can combine classmethod and property decorators to define -object schema properties as classes +Python {{{generatorLanguageVersion}}} ## Installation & Usage ### pip install diff --git a/openapi/python_client_urllib3_templates/README_common.mustache b/openapi/python_client_urllib3_templates/README_common.mustache new file mode 100644 index 00000000..614a9d0c --- /dev/null +++ b/openapi/python_client_urllib3_templates/README_common.mustache @@ -0,0 +1,111 @@ +```python +{{#apiInfo}}{{#apis}}{{#-last}}{{#hasHttpSignatureMethods}}import datetime{{/hasHttpSignatureMethods}}{{/-last}}{{/apis}}{{/apiInfo}} +import time +import {{{packageName}}} +from pprint import pprint +{{#apiInfo}} +{{#apis}} +{{#-first}} +from {{apiPackage}} import {{classFilename}} +{{#imports}} +{{{import}}} +{{/imports}} +{{#operations}} +{{#operation}} +{{#-first}} +{{> python_doc_auth_partial}} + +# Enter a context with an instance of the API client +with {{{packageName}}}.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = {{classFilename}}.{{{classname}}}(api_client) + {{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} + {{/allParams}} + + try: + {{#summary}} # {{{.}}} + {{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}} + pprint(api_response){{/returnType}} + except {{{packageName}}}.ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/-first}} +{{/operation}} +{{/operations}} +{{/-first}} +{{/apis}} +{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + +## Documentation For Authorization + +{{^authMethods}} + All endpoints do not require authorization. +{{/authMethods}} +{{#authMethods}} +{{#last}} Authentication schemes defined for the API:{{/last}} +## {{{name}}} + +{{#isApiKey}} +- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}} +{{#isBasicBasic}} +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} +- **Type**: Bearer authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} +- **Type**: HTTP signature authentication +{{/isHttpSignature}} +{{/isBasic}} +{{#isOAuth}} +- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} +{{/-last}}{{/apis}}{{/apiInfo}} + +## Notes for Large OpenAPI documents +If the OpenAPI document is large, imports in {{{packageName}}}.apis and {{{packageName}}}.models may fail with a +RecursionError indicating the maximum recursion limit has been exceeded. In that case, there are a couple of solutions: + +Solution 1: +Use specific imports for apis and models like: +- `from {{{packageName}}}.api.default_api import DefaultApi` +- `from {{{packageName}}}.model.pet import Pet` + +Solution 2: +Before importing the package, adjust the maximum recursion limit as shown below: +``` +import sys +sys.setrecursionlimit(1500) +import {{{packageName}}} +from {{{packageName}}}.apis import * +from {{{packageName}}}.models import * +``` diff --git a/openapi/python_explerimental_client_template/README_onlypackage.handlebars b/openapi/python_client_urllib3_templates/README_onlypackage.mustache similarity index 76% rename from openapi/python_explerimental_client_template/README_onlypackage.handlebars rename to openapi/python_client_urllib3_templates/README_onlypackage.mustache index 63f95937..ba08a3ac 100644 --- a/openapi/python_explerimental_client_template/README_onlypackage.handlebars +++ b/openapi/python_client_urllib3_templates/README_onlypackage.mustache @@ -1,23 +1,23 @@ # {{{projectName}}} -{{#if appDescription}} -{{{appDescription}}} -{{/if}} +{{#appDescription}} +{{{.}}} +{{/appDescription}} The `{{packageName}}` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: {{appVersion}} - Package version: {{packageVersion}} -{{#unless hideGenerationTimestamp}} +{{^hideGenerationTimestamp}} - Build date: {{generatedDate}} -{{/unless}} +{{/hideGenerationTimestamp}} - Build package: {{generatorClass}} -{{#if infoUrl}} +{{#infoUrl}} For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) -{{/if}} +{{/infoUrl}} ## Requirements. -Python {{generatorLanguageVersion}} +Python {{{generatorLanguageVersion}}} ## Installation & Usage @@ -25,15 +25,14 @@ This python library package is generated without supporting files like setup.py To be able to use it, you will need these dependencies in your own package that uses this library: -* urllib3 >= 1.15 -* certifi +* urllib3 >= 1.25.3 * python-dateutil -{{#if asyncio}} +{{#asyncio}} * aiohttp -{{/if}} -{{#if tornado}} +{{/asyncio}} +{{#tornado}} * tornado>=4.2,<5 -{{/if}} +{{/tornado}} ## Getting Started diff --git a/openapi/python_explerimental_client_template/__init__.handlebars b/openapi/python_client_urllib3_templates/__init__.mustache similarity index 100% rename from openapi/python_explerimental_client_template/__init__.handlebars rename to openapi/python_client_urllib3_templates/__init__.mustache diff --git a/openapi/python_explerimental_client_template/__init__api.handlebars b/openapi/python_client_urllib3_templates/__init__api.mustache similarity index 74% rename from openapi/python_explerimental_client_template/__init__api.handlebars rename to openapi/python_client_urllib3_templates/__init__api.mustache index 1e059f73..8a9e6b91 100644 --- a/openapi/python_explerimental_client_template/__init__api.handlebars +++ b/openapi/python_client_urllib3_templates/__init__api.mustache @@ -1,9 +1,9 @@ -{{#with apiInfo}} -{{#each apis}} -{{#if @first}} +{{#apiInfo}} +{{#apis}} +{{#-first}} # do not import all apis into this module because that uses a lot of memory and stack frames # if you need the ability to import all apis from one package, import them with # from {{packageName}}.apis import {{classname}} -{{/if}} -{{/each}} -{{/with}} +{{/-first}} +{{/apis}} +{{/apiInfo}} diff --git a/openapi/python_explerimental_client_template/__init__apis.handlebars b/openapi/python_client_urllib3_templates/__init__apis.mustache similarity index 60% rename from openapi/python_explerimental_client_template/__init__apis.handlebars rename to openapi/python_client_urllib3_templates/__init__apis.mustache index 06fb3361..927bb6d5 100644 --- a/openapi/python_explerimental_client_template/__init__apis.handlebars +++ b/openapi/python_client_urllib3_templates/__init__apis.mustache @@ -1,7 +1,6 @@ -{{#with apiInfo}} -{{#each apis}} -{{#if @first}} -# coding: utf-8 +{{#apiInfo}} +{{#apis}} +{{#-first}} # flake8: noqa @@ -10,7 +9,7 @@ # raise a `RecursionError`. # In order to avoid this, import only the API that you directly need like: # -# from {{packagename}}.{{apiPackage}}.{{classFilename}} import {{classname}} +# from {{packagename}}.api.{{classFilename}} import {{classname}} # # or import this package, but before doing it, use: # @@ -18,7 +17,7 @@ # sys.setrecursionlimit(n) # Import APIs into API package: -{{/if}} -from {{packageName}}.{{apiPackage}}.{{classFilename}} import {{classname}} -{{/each}} -{{/with}} +{{/-first}} +from {{apiPackage}}.{{classFilename}} import {{classname}} +{{/apis}} +{{/apiInfo}} diff --git a/openapi/python_explerimental_client_template/__init__model.handlebars b/openapi/python_client_urllib3_templates/__init__model.mustache similarity index 85% rename from openapi/python_explerimental_client_template/__init__model.handlebars rename to openapi/python_client_urllib3_templates/__init__model.mustache index b6b698b0..cfe32b78 100644 --- a/openapi/python_explerimental_client_template/__init__model.handlebars +++ b/openapi/python_client_urllib3_templates/__init__model.mustache @@ -2,4 +2,4 @@ # reference which would not work in python2 # do not import all models into this module because that uses a lot of memory and stack frames # if you need the ability to import all models from one package, import them with -# from {{packageName}}.models import ModelA, ModelB +# from {{packageName}.models import ModelA, ModelB diff --git a/openapi/python_explerimental_client_template/__init__models.handlebars b/openapi/python_client_urllib3_templates/__init__models.mustache similarity index 61% rename from openapi/python_explerimental_client_template/__init__models.handlebars rename to openapi/python_client_urllib3_templates/__init__models.mustache index 31eac9cd..76d91fc5 100644 --- a/openapi/python_explerimental_client_template/__init__models.handlebars +++ b/openapi/python_client_urllib3_templates/__init__models.mustache @@ -1,18 +1,16 @@ -# coding: utf-8 - # flake8: noqa # import all models into this package # if you have many models here with many references from one model to another this may # raise a RecursionError # to avoid this, import only the models that you directly need like: -# from from {{packageName}}.{{modelPackage}}.pet import Pet +# from from {{modelPackage}}.pet import Pet # or import this package, but before doing it, use: # import sys # sys.setrecursionlimit(n) -{{#each models}} -{{#with model}} -from {{packageName}}.{{modelPackage}}.{{classFilename}} import {{classname}} -{{/with}} -{{/each}} +{{#models}} +{{#model}} +from {{modelPackage}}.{{classFilename}} import {{classname}} +{{/model}} +{{/models}} diff --git a/openapi/python_explerimental_client_template/__init__package.handlebars b/openapi/python_client_urllib3_templates/__init__package.mustache similarity index 81% rename from openapi/python_explerimental_client_template/__init__package.handlebars rename to openapi/python_client_urllib3_templates/__init__package.mustache index 26350c72..6308ba68 100644 --- a/openapi/python_explerimental_client_template/__init__package.handlebars +++ b/openapi/python_client_urllib3_templates/__init__package.mustache @@ -1,5 +1,3 @@ -# coding: utf-8 - # flake8: noqa {{>partial_header}} @@ -11,9 +9,9 @@ from {{packageName}}.api_client import ApiClient # import Configuration from {{packageName}}.configuration import Configuration -{{#if hasHttpSignatureMethods}} +{{#hasHttpSignatureMethods}} from {{packageName}}.signing import HttpSigningConfiguration -{{/if}} +{{/hasHttpSignatureMethods}} # import exceptions from {{packageName}}.exceptions import OpenApiException @@ -22,7 +20,7 @@ from {{packageName}}.exceptions import ApiTypeError from {{packageName}}.exceptions import ApiValueError from {{packageName}}.exceptions import ApiKeyError from {{packageName}}.exceptions import ApiException -{{#if recursionLimit}} +{{#recursionLimit}} -__import__('sys').setrecursionlimit({{recursionLimit}}) -{{/if}} +__import__('sys').setrecursionlimit({{{.}}}) +{{/recursionLimit}} diff --git a/openapi/python_client_urllib3_templates/api.mustache b/openapi/python_client_urllib3_templates/api.mustache new file mode 100644 index 00000000..7acbd30f --- /dev/null +++ b/openapi/python_client_urllib3_templates/api.mustache @@ -0,0 +1,321 @@ +{{>partial_header}} + +import re # noqa: F401 +import sys # noqa: F401 + +from {{packageName}}.api_client import ApiClient, Endpoint as _Endpoint +from {{packageName}}.model_utils import ( # noqa: F401 + check_allowed_values, + check_validations, + date, + datetime, + file_type, + none_type, + validate_and_convert_types +) +{{#imports}} +{{{import}}} +{{/imports}} + + +class {{classname}}(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client +{{#operations}} +{{#operation}} + self.{{operationId}}_endpoint = _Endpoint( + settings={ + 'response_type': {{#returnType}}({{{.}}},){{/returnType}}{{^returnType}}None{{/returnType}}, +{{#authMethods}} +{{#-first}} + 'auth': [ +{{/-first}} + '{{name}}'{{^-last}},{{/-last}} +{{#-last}} + ], +{{/-last}} +{{/authMethods}} +{{^authMethods}} + 'auth': [], +{{/authMethods}} + 'endpoint_path': '{{{path}}}', + 'operation_id': '{{operationId}}', + 'http_method': '{{httpMethod}}', +{{#servers}} +{{#-first}} + 'servers': [ +{{/-first}} + { + 'url': "{{{url}}}", + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + {{#variables}} + {{#-first}} + 'variables': { + {{/-first}} + '{{{name}}}': { + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + 'default_value': "{{{defaultValue}}}", + {{#enumValues}} + {{#-first}} + 'enum_values': [ + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ] + {{/-last}} + {{/enumValues}} + }{{^-last}},{{/-last}} + {{#-last}} + } + {{/-last}} + {{/variables}} + }, +{{#-last}} + ] +{{/-last}} +{{/servers}} +{{^servers}} + 'servers': None, +{{/servers}} + }, + params_map={ + 'all': [ +{{#allParams}} + '{{paramName}}', +{{/allParams}} + ], +{{#requiredParams}} +{{#-first}} + 'required': [ +{{/-first}} + '{{paramName}}', +{{#-last}} + ], +{{/-last}} +{{/requiredParams}} +{{^requiredParams}} + 'required': [], +{{/requiredParams}} + 'nullable': [ +{{#allParams}} +{{#isNullable}} + '{{paramName}}', +{{/isNullable}} +{{/allParams}} + ], + 'enum': [ +{{#allParams}} +{{#isEnum}} + '{{paramName}}', +{{/isEnum}} +{{/allParams}} + ], + 'validation': [ +{{#allParams}} +{{#hasValidation}} + '{{paramName}}', +{{/hasValidation}} +{{/allParams}} + ] + }, + root_map={ + 'validations': { +{{#allParams}} +{{#hasValidation}} + ('{{paramName}}',): { +{{#maxLength}} + 'max_length': {{.}},{{/maxLength}}{{#minLength}} + 'min_length': {{.}},{{/minLength}}{{#maxItems}} + 'max_items': {{.}},{{/maxItems}}{{#minItems}} + 'min_items': {{.}},{{/minItems}}{{#maximum}} + {{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}{{^exclusiveMaximum}}'inclusive_maximum'{{/exclusiveMaximum}}: {{maximum}},{{/maximum}}{{#minimum}} + {{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}{{^exclusiveMinimum}}'inclusive_minimum'{{/exclusiveMinimum}}: {{minimum}},{{/minimum}}{{#pattern}} + 'regex': { + 'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}} + {{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}} + },{{/pattern}} + }, +{{/hasValidation}} +{{/allParams}} + }, + 'allowed_values': { +{{#allParams}} +{{#isEnum}} + ('{{paramName}}',): { +{{#isNullable}} + 'None': None,{{/isNullable}}{{#allowableValues}}{{#enumVars}} + "{{name}}": {{{value}}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + }, +{{/isEnum}} +{{/allParams}} + }, + 'openapi_types': { +{{#allParams}} + '{{paramName}}': + ({{{dataType}}},), +{{/allParams}} + }, + 'attribute_map': { +{{#allParams}} +{{^isBodyParam}} + '{{paramName}}': '{{baseName}}', +{{/isBodyParam}} +{{/allParams}} + }, + 'location_map': { +{{#allParams}} + '{{paramName}}': '{{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isCookieParam}}cookie{{/isCookieParam}}{{#isBodyParam}}body{{/isBodyParam}}', +{{/allParams}} + }, + 'collection_format_map': { +{{#allParams}} +{{#collectionFormat}} + '{{paramName}}': '{{collectionFormat}}', +{{/collectionFormat}} +{{/allParams}} + } + }, + headers_map={ +{{#hasProduces}} + 'accept': [ +{{#produces}} + '{{{mediaType}}}'{{^-last}},{{/-last}} +{{/produces}} + ], +{{/hasProduces}} +{{^hasProduces}} + 'accept': [], +{{/hasProduces}} +{{#hasConsumes}} + 'content_type': [ +{{#consumes}} + '{{{mediaType}}}'{{^-last}},{{/-last}} +{{/consumes}} + ] +{{/hasConsumes}} +{{^hasConsumes}} + 'content_type': [], +{{/hasConsumes}} + }, + api_client=api_client + ) +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} + def {{operationId}}( + self, +{{#requiredParams}} +{{^defaultValue}} + {{paramName}}: {{dataType}}, +{{/defaultValue}} +{{/requiredParams}} +{{#requiredParams}} +{{#defaultValue}} + {{paramName}}={{{defaultValue}}}, +{{/defaultValue}} +{{/requiredParams}} + **kwargs + ): + """{{{summary}}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{.}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.{{operationId}}({{#requiredParams}}{{^defaultValue}}{{paramName}}, {{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}}{{paramName}}={{{defaultValue}}}, {{/defaultValue}}{{/requiredParams}}async_req=True) + >>> result = thread.get() + +{{#requiredParams}} +{{#-last}} + Args: +{{/-last}} +{{/requiredParams}} +{{#requiredParams}} +{{^defaultValue}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{/requiredParams}} +{{#requiredParams}} +{{#defaultValue}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}, must be one of [{{{defaultValue}}}] +{{/defaultValue}} +{{/requiredParams}} + + Keyword Args:{{#optionalParams}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}}{{/optionalParams}} + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _content_type (str/None): force body content-type. + Default is None and content-type will be predicted by allowed + content-types and body. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + {{returnType}}{{^returnType}}None{{/returnType}} + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_spec_property_naming'] = kwargs.get( + '_spec_property_naming', False + ) + kwargs['_content_type'] = kwargs.get( + '_content_type') + kwargs['_host_index'] = kwargs.get('_host_index') +{{#requiredParams}} + kwargs['{{paramName}}'] = \ + {{paramName}} +{{/requiredParams}} + return self.{{operationId}}_endpoint.call_with_http_info(**kwargs) + +{{/operation}} +{{/operations}} diff --git a/openapi/python_client_urllib3_templates/api_client.mustache b/openapi/python_client_urllib3_templates/api_client.mustache new file mode 100644 index 00000000..39f33aee --- /dev/null +++ b/openapi/python_client_urllib3_templates/api_client.mustache @@ -0,0 +1,887 @@ +{{>partial_header}} + +import json +import atexit +import mimetypes +from multiprocessing.pool import ThreadPool +import io +import os +import re +import typing +from urllib.parse import quote +from urllib3.fields import RequestField + +{{#tornado}} +import tornado.gen +{{/tornado}} + +from {{packageName}} import rest +from {{packageName}}.configuration import Configuration +from {{packageName}}.exceptions import ApiTypeError, ApiValueError, ApiException +from {{packageName}}.model_utils import ( + ModelNormal, + ModelSimple, + ModelComposed, + check_allowed_values, + check_validations, + date, + datetime, + deserialize_file, + file_type, + model_to_dict, + none_type, + validate_and_convert_types +) + + +class ApiClient(object): + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + Do not edit the class manually. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + :param pool_threads: The number of threads to use for async requests + to the API. More threads means more concurrent API requests. + """ + + _pool = None + + def __init__(self, configuration=None, header_name=None, header_value=None, + cookie=None, pool_threads=1): + if configuration is None: + configuration = Configuration.get_default_copy() + self.configuration = configuration + self.pool_threads = pool_threads + + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = '{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}' + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + if self._pool: + self._pool.close() + self._pool.join() + self._pool = None + if hasattr(atexit, 'unregister'): + atexit.unregister(self.close) + + @property + def pool(self): + """Create thread pool on first request + avoids instantiating unused threadpool for blocking clients. + """ + if self._pool is None: + atexit.register(self.close) + self._pool = ThreadPool(self.pool_threads) + return self._pool + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + {{#tornado}} + @tornado.gen.coroutine + {{/tornado}} + {{#asyncio}}async {{/asyncio}}def __call_api( + self, + resource_path: str, + method: str, + path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + query_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + header_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + body: typing.Optional[typing.Any] = None, + post_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None, + response_type: typing.Optional[typing.Tuple[typing.Any]] = None, + auth_settings: typing.Optional[typing.List[str]] = None, + _return_http_data_only: typing.Optional[bool] = None, + collection_formats: typing.Optional[typing.Dict[str, str]] = None, + _preload_content: bool = True, + _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + _host: typing.Optional[str] = None, + _check_type: typing.Optional[bool] = None, + _content_type: typing.Optional[str] = None + ): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + if post_params or files: + post_params = post_params if post_params else [] + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + post_params.extend(self.files_parameters(files)) + if header_params['Content-Type'].startswith("multipart"): + post_params = self.parameters_to_multipart(post_params, + (dict) ) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # auth setting + self.update_params_for_auth(header_params, query_params, + auth_settings, resource_path, method, body) + + # request url + if _host is None: + url = self.configuration.host + resource_path + else: + # use server/host defined in path or operation instead + url = _host + resource_path + + try: + # perform request and return response + response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _preload_content=_preload_content, + _request_timeout=_request_timeout) + except ApiException as e: + e.body = e.body.decode('utf-8') + raise e + + self.last_response = response_data + + return_data = response_data + + if not _preload_content: + {{^tornado}} + return (return_data) + {{/tornado}} + {{#tornado}} + raise tornado.gen.Return(return_data) + {{/tornado}} + return return_data + + # deserialize response data + if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + + return_data = self.deserialize( + response_data, + response_type, + _check_type + ) + else: + return_data = None + +{{^tornado}} + if _return_http_data_only: + return (return_data) + else: + return (return_data, response_data.status, + response_data.getheaders()) +{{/tornado}} +{{#tornado}} + if _return_http_data_only: + raise tornado.gen.Return(return_data) + else: + raise tornado.gen.Return((return_data, response_data.status, + response_data.getheaders())) +{{/tornado}} + + def parameters_to_multipart(self, params, collection_types): + """Get parameters as list of tuples, formatting as json if value is collection_types + + :param params: Parameters as list of two-tuples + :param dict collection_types: Parameter collection types + :return: Parameters as list of tuple or urllib3.fields.RequestField + """ + new_params = [] + if collection_types is None: + collection_types = (dict) + for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 + if isinstance(v, collection_types): # v is instance of collection_type, formatting as application/json + v = json.dumps(v, ensure_ascii=False).encode("utf-8") + field = RequestField(k, v) + field.make_multipart(content_type="application/json; charset=utf-8") + new_params.append(field) + else: + new_params.append((k, v)) + return new_params + + @classmethod + def sanitize_for_serialization(cls, obj): + """Prepares data for transmission before it is sent with the rest client + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if isinstance(obj, (ModelNormal, ModelComposed)): + return { + key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() + } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) + elif isinstance(obj, (str, int, float, none_type, bool)): + return obj + elif isinstance(obj, (datetime, date)): + return obj.isoformat() + elif isinstance(obj, ModelSimple): + return cls.sanitize_for_serialization(obj.value) + elif isinstance(obj, (list, tuple)): + return [cls.sanitize_for_serialization(item) for item in obj] + if isinstance(obj, dict): + return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()} + raise ApiValueError('Unable to prepare type {} for serialization'.format(obj.__class__.__name__)) + + def deserialize(self, response, response_type, _check_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: For the response, a tuple containing: + valid classes + a list containing valid classes (for list schemas) + a dict containing a tuple of valid classes as the value + Example values: + (str,) + (Pet,) + (float, none_type) + ([int, none_type],) + ({str: (bool, str, int, float, date, datetime, str, none_type)},) + :param _check_type: boolean, whether to check the types of the data + received from the server + :type _check_type: bool + + :return: deserialized object. + """ + # handle file downloading + # save response body into a tmp file and return the instance + if response_type == (file_type,): + content_disposition = response.getheader("Content-Disposition") + return deserialize_file(response.data, self.configuration, + content_disposition=content_disposition) + + # fetch data from response object + try: + received_data = json.loads(response.data) + except ValueError: + received_data = response.data + + # store our data under the key of 'received_data' so users have some + # context if they are deserializing a string and the data type is wrong + deserialized_data = validate_and_convert_types( + received_data, + response_type, + ['received_data'], + True, + _check_type, + configuration=self.configuration + ) + return deserialized_data + + def call_api( + self, + resource_path: str, + method: str, + path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + query_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + header_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + body: typing.Optional[typing.Any] = None, + post_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None, + response_type: typing.Optional[typing.Tuple[typing.Any]] = None, + auth_settings: typing.Optional[typing.List[str]] = None, + async_req: typing.Optional[bool] = None, + _return_http_data_only: typing.Optional[bool] = None, + collection_formats: typing.Optional[typing.Dict[str, str]] = None, + _preload_content: bool = True, + _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + _host: typing.Optional[str] = None, + _check_type: typing.Optional[bool] = None + ): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an async_req request, set the async_req parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response_type: For the response, a tuple containing: + valid classes + a list containing valid classes (for list schemas) + a dict containing a tuple of valid classes as the value + Example values: + (str,) + (Pet,) + (float, none_type) + ([int, none_type],) + ({str: (bool, str, int, float, date, datetime, str, none_type)},) + :param files: key -> field name, value -> a list of open file + objects for `multipart/form-data`. + :type files: dict + :param async_req bool: execute request asynchronously + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :type collection_formats: dict, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _check_type: boolean describing if the data back from the server + should have its type checked. + :type _check_type: bool, optional + :return: + If async_req parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter async_req is False or missing, + then the method will return the response directly. + """ + if not async_req: + return self.__call_api(resource_path, method, + path_params, query_params, header_params, + body, post_params, files, + response_type, auth_settings, + _return_http_data_only, collection_formats, + _preload_content, _request_timeout, _host, + _check_type) + + return self.pool.apply_async(self.__call_api, (resource_path, + method, path_params, + query_params, + header_params, body, + post_params, files, + response_type, + auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, + _request_timeout, + _host, _check_type)) + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + else: + raise ApiValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): + """Builds form parameters. + + :param files: None or a dict with key=param_name and + value is a list of open file objects + :return: List of tuples of form parameters with file data + """ + if files is None: + return [] + + params = [] + for param_name, file_instances in files.items(): + if file_instances is None: + # if the file field is nullable, skip None values + continue + for file_instance in file_instances: + if file_instance is None: + # if the file field is nullable, skip None values + continue + if file_instance.closed is True: + raise ApiValueError( + "Cannot read a closed file. The passed in file_type " + "for %s must be open." % param_name + ) + filename = os.path.basename(file_instance.name) + filedata = self.get_file_data_and_close_file(file_instance) + mimetype = (mimetypes.guess_type(filename)[0] or + 'application/octet-stream') + params.append( + tuple([param_name, tuple([filename, filedata, mimetype])])) + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types, method=None, body=None): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :param method: http method (e.g. POST, PATCH). + :param body: http body to send. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return 'application/json' + + content_types = [x.lower() for x in content_types] + + if (method == 'PATCH' and + 'application/json-patch+json' in content_types and + isinstance(body, list)): + return 'application/json-patch+json' + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, queries, auth_settings, + resource_path, method, body): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + :param resource_path: A string representation of the HTTP request resource path. + :param method: A string representation of the HTTP request method. + :param body: A object representing the body of the HTTP request. + The object type is the return value of _encoder.default(). + """ + if not auth_settings: + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + if auth_setting['in'] == 'cookie': + headers['Cookie'] = auth_setting['value'] + elif auth_setting['in'] == 'header': + if auth_setting['type'] != 'http-signature': + headers[auth_setting['key']] = auth_setting['value'] +{{#hasHttpSignatureMethods}} + else: + # The HTTP signature scheme requires multiple HTTP headers + # that are calculated dynamically. + signing_info = self.configuration.signing_info + auth_headers = signing_info.get_http_signature_headers( + resource_path, method, headers, body, queries) + headers.update(auth_headers) +{{/hasHttpSignatureMethods}} + elif auth_setting['in'] == 'query': + queries.append((auth_setting['key'], auth_setting['value'])) + else: + raise ApiValueError( + 'Authentication token must be in `query` or `header`' + ) + + +class Endpoint(object): + def __init__(self, settings=None, params_map=None, root_map=None, + headers_map=None, api_client=None, callable=None): + """Creates an endpoint + + Args: + settings (dict): see below key value pairs + 'response_type' (tuple/None): response type + 'auth' (list): a list of auth type keys + 'endpoint_path' (str): the endpoint path + 'operation_id' (str): endpoint string identifier + 'http_method' (str): POST/PUT/PATCH/GET etc + 'servers' (list): list of str servers that this endpoint is at + params_map (dict): see below key value pairs + 'all' (list): list of str endpoint parameter names + 'required' (list): list of required parameter names + 'nullable' (list): list of nullable parameter names + 'enum' (list): list of parameters with enum values + 'validation' (list): list of parameters with validations + root_map + 'validations' (dict): the dict mapping endpoint parameter tuple + paths to their validation dictionaries + 'allowed_values' (dict): the dict mapping endpoint parameter + tuple paths to their allowed_values (enum) dictionaries + 'openapi_types' (dict): param_name to openapi type + 'attribute_map' (dict): param_name to camelCase name + 'location_map' (dict): param_name to 'body', 'file', 'form', + 'header', 'path', 'query' + collection_format_map (dict): param_name to `csv` etc. + headers_map (dict): see below key value pairs + 'accept' (list): list of Accept header strings + 'content_type' (list): list of Content-Type header strings + api_client (ApiClient) api client instance + callable (function): the function which is invoked when the + Endpoint is called + """ + self.settings = settings + self.params_map = params_map + self.params_map['all'].extend([ + 'async_req', + '_host_index', + '_preload_content', + '_request_timeout', + '_return_http_data_only', + '_check_input_type', + '_check_return_type', + '_content_type', + '_spec_property_naming' + ]) + self.params_map['nullable'].extend(['_request_timeout']) + self.validations = root_map['validations'] + self.allowed_values = root_map['allowed_values'] + self.openapi_types = root_map['openapi_types'] + extra_types = { + 'async_req': (bool,), + '_host_index': (none_type, int), + '_preload_content': (bool,), + '_request_timeout': (none_type, float, (float,), [float], int, (int,), [int]), + '_return_http_data_only': (bool,), + '_check_input_type': (bool,), + '_check_return_type': (bool,), + '_spec_property_naming': (bool,), + '_content_type': (none_type, str) + } + self.openapi_types.update(extra_types) + self.attribute_map = root_map['attribute_map'] + self.location_map = root_map['location_map'] + self.collection_format_map = root_map['collection_format_map'] + self.headers_map = headers_map + self.api_client = api_client + self.callable = callable + + def __validate_inputs(self, kwargs): + for param in self.params_map['enum']: + if param in kwargs: + check_allowed_values( + self.allowed_values, + (param,), + kwargs[param] + ) + + for param in self.params_map['validation']: + if param in kwargs: + check_validations( + self.validations, + (param,), + kwargs[param], + configuration=self.api_client.configuration + ) + + if kwargs['_check_input_type'] is False: + return + + for key, value in kwargs.items(): + fixed_val = validate_and_convert_types( + value, + self.openapi_types[key], + [key], + kwargs['_spec_property_naming'], + kwargs['_check_input_type'], + configuration=self.api_client.configuration + ) + kwargs[key] = fixed_val + + def __gather_params(self, kwargs): + params = { + 'body': None, + 'collection_format': {}, + 'file': {}, + 'form': [], + 'header': {}, + 'path': {}, + 'query': [] + } + + for param_name, param_value in kwargs.items(): + param_location = self.location_map.get(param_name) + if param_location is None: + continue + if param_location: + if param_location == 'body': + params['body'] = param_value + continue + base_name = self.attribute_map[param_name] + if (param_location == 'form' and + self.openapi_types[param_name] == (file_type,)): + params['file'][base_name] = [param_value] + elif (param_location == 'form' and + self.openapi_types[param_name] == ([file_type],)): + # param_value is already a list + params['file'][base_name] = param_value + elif param_location in {'form', 'query'}: + param_value_full = (base_name, param_value) + params[param_location].append(param_value_full) + if param_location not in {'form', 'query'}: + params[param_location][base_name] = param_value + collection_format = self.collection_format_map.get(param_name) + if collection_format: + params['collection_format'][base_name] = collection_format + + return params + + def __call__(self, *args, **kwargs): + """ This method is invoked when endpoints are called + Example: +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} + api_instance = {{classname}}() + api_instance.{{operationId}} # this is an instance of the class Endpoint + api_instance.{{operationId}}() # this invokes api_instance.{{operationId}}.__call__() + which then invokes the callable functions stored in that endpoint at + api_instance.{{operationId}}.callable or self.callable in this class +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} + """ + return self.callable(self, *args, **kwargs) + + def call_with_http_info(self, **kwargs): + + try: + index = self.api_client.configuration.server_operation_index.get( + self.settings['operation_id'], self.api_client.configuration.server_index + ) if kwargs['_host_index'] is None else kwargs['_host_index'] + server_variables = self.api_client.configuration.server_operation_variables.get( + self.settings['operation_id'], self.api_client.configuration.server_variables + ) + _host = self.api_client.configuration.get_host_from_settings( + index, variables=server_variables, servers=self.settings['servers'] + ) + except IndexError: + if self.settings['servers']: + raise ApiValueError( + "Invalid host index. Must be 0 <= index < %s" % + len(self.settings['servers']) + ) + _host = None + + for key, value in kwargs.items(): + if key not in self.params_map['all']: + raise ApiTypeError( + "Got an unexpected parameter '%s'" + " to method `%s`" % + (key, self.settings['operation_id']) + ) + # only throw this nullable ApiValueError if _check_input_type + # is False, if _check_input_type==True we catch this case + # in self.__validate_inputs + if (key not in self.params_map['nullable'] and value is None + and kwargs['_check_input_type'] is False): + raise ApiValueError( + "Value may not be None for non-nullable parameter `%s`" + " when calling `%s`" % + (key, self.settings['operation_id']) + ) + + for key in self.params_map['required']: + if key not in kwargs.keys(): + raise ApiValueError( + "Missing the required parameter `%s` when calling " + "`%s`" % (key, self.settings['operation_id']) + ) + + self.__validate_inputs(kwargs) + + params = self.__gather_params(kwargs) + + accept_headers_list = self.headers_map['accept'] + if accept_headers_list: + params['header']['Accept'] = self.api_client.select_header_accept( + accept_headers_list) + + if kwargs.get('_content_type'): + params['header']['Content-Type'] = kwargs['_content_type'] + else: + content_type_headers_list = self.headers_map['content_type'] + if content_type_headers_list: + if params['body'] != "": + header_list = self.api_client.select_header_content_type( + content_type_headers_list, self.settings['http_method'], + params['body']) + params['header']['Content-Type'] = header_list + + return self.api_client.call_api( + self.settings['endpoint_path'], self.settings['http_method'], + params['path'], + params['query'], + params['header'], + body=params['body'], + post_params=params['form'], + files=params['file'], + response_type=self.settings['response_type'], + auth_settings=self.settings['auth'], + async_req=kwargs['async_req'], + _check_type=kwargs['_check_return_type'], + _return_http_data_only=kwargs['_return_http_data_only'], + _preload_content=kwargs['_preload_content'], + _request_timeout=kwargs['_request_timeout'], + _host=_host, + collection_formats=params['collection_format']) diff --git a/openapi/python_client_urllib3_templates/api_doc.mustache b/openapi/python_client_urllib3_templates/api_doc.mustache new file mode 100644 index 00000000..1e8d4c12 --- /dev/null +++ b/openapi/python_client_urllib3_templates/api_doc.mustache @@ -0,0 +1,79 @@ +# {{packageName}}.{{classname}}{{#description}} +{{.}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +# **{{{operationId}}}** +> {{#returnType}}{{{.}}} {{/returnType}}{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}}) + +{{{summary}}}{{#notes}} + +{{{.}}}{{/notes}} + +### Example + +{{#hasAuthMethods}} +{{#authMethods}} +{{#isBasic}} +{{#isBasicBasic}} +* Basic Authentication ({{name}}): +{{/isBasicBasic}} +{{#isBasicBearer}} +* Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} Authentication ({{name}}): +{{/isBasicBearer}} +{{/isBasic}} +{{#isApiKey}} +* Api Key Authentication ({{name}}): +{{/isApiKey }} +{{#isOAuth}} +* OAuth Authentication ({{name}}): +{{/isOAuth }} +{{/authMethods}} +{{/hasAuthMethods}} + +{{> api_doc_example }} + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#requiredParams}}{{^defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**]({{baseType}}.md){{/baseType}}| {{description}} | +{{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**]({{baseType}}.md){{/baseType}}| {{description}} | defaults to {{{.}}} +{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**]({{baseType}}.md){{/baseType}}| {{description}} | [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/openapi/python_client_urllib3_templates/api_doc_example.mustache b/openapi/python_client_urllib3_templates/api_doc_example.mustache new file mode 100644 index 00000000..4d6d7bf1 --- /dev/null +++ b/openapi/python_client_urllib3_templates/api_doc_example.mustache @@ -0,0 +1,76 @@ +```python +import time +import {{{packageName}}} +from {{apiPackage}} import {{classFilename}} +{{#imports}} +{{{.}}} +{{/imports}} +from pprint import pprint +{{> python_doc_auth_partial}} +# Enter a context with an instance of the API client +{{#hasAuthMethods}} +with {{{packageName}}}.ApiClient(configuration) as api_client: +{{/hasAuthMethods}} +{{^hasAuthMethods}} +with {{{packageName}}}.ApiClient() as api_client: +{{/hasAuthMethods}} + # Create an instance of the API class + api_instance = {{classFilename}}.{{{classname}}}(api_client) +{{#requiredParams}} +{{^defaultValue}} + {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}} +{{/defaultValue}} +{{/requiredParams}} +{{#optionalParams}} + {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalParams}} +{{#requiredParams}} +{{#-last}} + + # example passing only required values which don't have defaults set + try: +{{#summary}} + # {{{.}}} +{{/summary}} + {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}}) +{{#returnType}} + pprint(api_response) +{{/returnType}} + except {{{packageName}}}.ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/-last}} +{{/requiredParams}} +{{#optionalParams}} +{{#-last}} + + # example passing only required values which don't have defaults set + # and optional values + try: +{{#summary}} + # {{{.}}} +{{/summary}} + {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#requiredParams}}{{^defaultValue}}{{paramName}}, {{/defaultValue}}{{/requiredParams}}{{#optionalParams}}{{paramName}}={{paramName}}{{^-last}}, {{/-last}}{{/optionalParams}}) +{{#returnType}} + pprint(api_response) +{{/returnType}} + except {{{packageName}}}.ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/-last}} +{{/optionalParams}} +{{^requiredParams}} +{{^optionalParams}} + + # example, this endpoint has no required or optional parameters + try: +{{#summary}} + # {{{.}}} +{{/summary}} + {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}() +{{#returnType}} + pprint(api_response) +{{/returnType}} + except {{{packageName}}}.ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/optionalParams}} +{{/requiredParams}} +``` diff --git a/openapi/python_explerimental_client_template/api_test.handlebars b/openapi/python_client_urllib3_templates/api_test.mustache similarity index 57% rename from openapi/python_explerimental_client_template/api_test.handlebars rename to openapi/python_client_urllib3_templates/api_test.mustache index 2f52783c..8f3d764d 100644 --- a/openapi/python_explerimental_client_template/api_test.handlebars +++ b/openapi/python_client_urllib3_templates/api_test.mustache @@ -1,14 +1,12 @@ -# coding: utf-8 - {{>partial_header}} import unittest import {{packageName}} -from {{packageName}}.{{apiPackage}}.{{classFilename}} import {{classname}} # noqa: E501 +from {{apiPackage}}.{{classFilename}} import {{classname}} # noqa: E501 -class {{#with operations}}Test{{classname}}(unittest.TestCase): +class {{#operations}}Test{{classname}}(unittest.TestCase): """{{classname}} unit test stubs""" def setUp(self): @@ -17,18 +15,18 @@ class {{#with operations}}Test{{classname}}(unittest.TestCase): def tearDown(self): pass - {{#each operation}} + {{#operation}} def test_{{operationId}}(self): """Test case for {{{operationId}}} -{{#if summary}} - {{{summary}}} # noqa: E501 -{{/if}} +{{#summary}} + {{{.}}} # noqa: E501 +{{/summary}} """ pass - {{/each}} -{{/with}} + {{/operation}} +{{/operations}} if __name__ == '__main__': unittest.main() diff --git a/openapi/python_client_urllib3_templates/asyncio/rest.mustache b/openapi/python_client_urllib3_templates/asyncio/rest.mustache new file mode 100644 index 00000000..d69e7fd8 --- /dev/null +++ b/openapi/python_client_urllib3_templates/asyncio/rest.mustache @@ -0,0 +1,237 @@ +{{>partial_header}} + +import io +import json +import logging +import re +import ssl + +import aiohttp +# python 2 and python 3 compatibility library +from six.moves.urllib.parse import urlencode + +from {{packageName}}.exceptions import ApiException, ApiValueError + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp, data): + self.aiohttp_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = data + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.aiohttp_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.aiohttp_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + + # maxsize is number of requests to host that are allowed in parallel + if maxsize is None: + maxsize = configuration.connection_pool_maxsize + + ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + connector = aiohttp.TCPConnector( + limit=maxsize, + ssl=ssl_context + ) + + self.proxy = configuration.proxy + self.proxy_headers = configuration.proxy_headers + + # https pool manager + self.pool_manager = aiohttp.ClientSession( + connector=connector, + trust_env=True + ) + + async def close(self): + await self.pool_manager.close() + + async def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Execute request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + timeout = _request_timeout or 5 * 60 + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + args = { + "method": method, + "url": url, + "timeout": timeout, + "headers": headers + } + + if self.proxy: + args["proxy"] = self.proxy + if self.proxy_headers: + args["proxy_headers"] = self.proxy_headers + + if query_params: + args["url"] += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body is not None: + body = json.dumps(body) + args["data"] = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + args["data"] = aiohttp.FormData(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by aiohttp + del headers['Content-Type'] + data = aiohttp.FormData() + for param in post_params: + k, v = param + if isinstance(v, tuple) and len(v) == 3: + data.add_field(k, + value=v[1], + filename=v[0], + content_type=v[2]) + else: + data.add_field(k, v) + args["data"] = data + + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + args["data"] = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + r = await self.pool_manager.request(**args) + if _preload_content: + + data = await r.read() + r = RESTResponse(r, data) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + async def GET(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def HEAD(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def OPTIONS(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return (await self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def POST(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return (await self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PATCH(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) diff --git a/openapi/python_explerimental_client_template/configuration.handlebars b/openapi/python_client_urllib3_templates/configuration.mustache similarity index 88% rename from openapi/python_explerimental_client_template/configuration.handlebars rename to openapi/python_client_urllib3_templates/configuration.mustache index 1fc2dbb2..8e586cf6 100644 --- a/openapi/python_explerimental_client_template/configuration.handlebars +++ b/openapi/python_client_urllib3_templates/configuration.mustache @@ -1,12 +1,10 @@ -# coding: utf-8 - {{>partial_header}} import copy import logging -{{#unless asyncio}} +{{^asyncio}} import multiprocessing -{{/unless}} +{{/asyncio}} import sys import urllib3 @@ -17,8 +15,7 @@ from {{packageName}}.exceptions import ApiValueError JSON_SCHEMA_VALIDATION_KEYWORDS = { 'multipleOf', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum', 'maxLength', - 'minLength', 'pattern', 'maxItems', 'minItems', - 'uniqueItems', 'maxProperties', 'minProperties', + 'minLength', 'pattern', 'maxItems', 'minItems' } class Configuration(object): @@ -61,10 +58,10 @@ class Configuration(object): disabled. This can be useful to troubleshoot data validation problem, such as when the OpenAPI document validation rules do not match the actual API data received by the server. -{{#if hasHttpSignatureMethods}} +{{#hasHttpSignatureMethods}} :param signing_info: Configuration parameters for the HTTP signature security scheme. Must be an instance of {{{packageName}}}.signing.HttpSigningConfiguration -{{/if}} +{{/hasHttpSignatureMethods}} :param server_index: Index to servers configuration. :param server_variables: Mapping with string values to replace variables in templated server configuration. The validation of enums is performed for @@ -74,10 +71,12 @@ class Configuration(object): :param server_operation_variables: Mapping from operation ID to a mapping with string values to replace variables in templated server configuration. The validation of enums is performed for variables with defined enum values before. + :param ssl_ca_cert: str - the path to a file of concatenated CA certificates + in PEM format -{{#if hasAuthMethods}} +{{#hasAuthMethods}} :Example: -{{#if hasApiKeyMethods}} +{{#hasApiKeyMethods}} API Key Authentication Example. Given the following security scheme in the OpenAPI specification: @@ -97,8 +96,8 @@ conf = {{{packageName}}}.Configuration( The following cookie will be added to the HTTP request: Cookie: JSESSIONID abc123 -{{/if}} -{{#if hasHttpBasicMethods}} +{{/hasApiKeyMethods}} +{{#hasHttpBasicMethods}} HTTP Basic Authentication Example. Given the following security scheme in the OpenAPI specification: @@ -115,8 +114,8 @@ conf = {{{packageName}}}.Configuration( password='the-password', ) -{{/if}} -{{#if hasHttpSignatureMethods}} +{{/hasHttpBasicMethods}} +{{#hasHttpSignatureMethods}} HTTP Signature Authentication Example. Given the following security scheme in the OpenAPI specification: @@ -134,7 +133,7 @@ conf = {{{packageName}}}.Configuration( 'Authorization' header, which is used to carry the signature. One may be tempted to sign all headers by default, but in practice it rarely works. - This is beccause explicit proxies, transparent proxies, TLS termination endpoints or + This is because explicit proxies, transparent proxies, TLS termination endpoints or load balancers may add/modify/remove headers. Include the HTTP headers that you know are not going to be modified in transit. @@ -156,23 +155,24 @@ conf = {{{packageName}}}.Configuration( signature_max_validity = datetime.timedelta(minutes=5) ) ) -{{/if}} -{{/if}} +{{/hasHttpSignatureMethods}} +{{/hasAuthMethods}} """ _default = None def __init__(self, host=None, api_key=None, api_key_prefix=None, - username=None, password=None, access_token=None, + username=None, password=None, discard_unknown_keys=False, disabled_client_side_validations="", -{{#if hasHttpSignatureMethods}} +{{#hasHttpSignatureMethods}} signing_info=None, -{{/if}} +{{/hasHttpSignatureMethods}} server_index=None, server_variables=None, server_operation_index=None, server_operation_variables=None, + ssl_ca_cert=None, ): """Constructor """ @@ -191,6 +191,7 @@ conf = {{{packageName}}}.Configuration( """Temp file folder for downloading files """ # Authentication Settings + self.access_token = access_token self.api_key = {} if api_key: self.api_key = api_key @@ -210,28 +211,15 @@ conf = {{{packageName}}}.Configuration( self.password = password """Password for HTTP basic authentication """ - self.access_token = access_token self.discard_unknown_keys = discard_unknown_keys self.disabled_client_side_validations = disabled_client_side_validations -{{#if hasHttpSignatureMethods}} +{{#hasHttpSignatureMethods}} if signing_info is not None: signing_info.host = host self.signing_info = signing_info """The HTTP signing configuration """ -{{/if}} -{{#if hasOAuthMethods}} - self.access_token = None - """access token for OAuth/Bearer - """ -{{/if}} -{{#unless hasOAuthMethods}} -{{#if hasBearerMethods}} - self.access_token = None - """access token for OAuth/Bearer - """ -{{/if}} -{{/unless}} +{{/hasHttpSignatureMethods}} self.logger = {} """Logging Settings """ @@ -258,7 +246,7 @@ conf = {{{packageName}}}.Configuration( Set this to false to skip verifying SSL certificate when calling API from https server. """ - self.ssl_ca_cert = None + self.ssl_ca_cert = ssl_ca_cert """Set this to customize the certificate file to verify the peer. """ self.cert_file = None @@ -271,13 +259,13 @@ conf = {{{packageName}}}.Configuration( """Set this to True/False to enable/disable SSL hostname verification. """ - {{#if asyncio}} + {{#asyncio}} self.connection_pool_maxsize = 100 """This value is passed to the aiohttp to limit simultaneous connections. Default values is 100, None means no-limit. """ - {{/if}} - {{#unless asyncio}} + {{/asyncio}} + {{^asyncio}} self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 """urllib3 connection pool's maximum number of connections saved per pool. urllib3 uses 1 connection as default value, but this is @@ -285,11 +273,14 @@ conf = {{{packageName}}}.Configuration( requests to the same host, which is often the case here. cpu_count * 5 is used as default value to increase performance. """ - {{/unless}} + {{/asyncio}} self.proxy = None """Proxy URL """ + self.no_proxy = None + """bypass proxy for host in the no_proxy list. + """ self.proxy_headers = None """Proxy headers """ @@ -328,12 +319,12 @@ conf = {{{packageName}}}.Configuration( raise ApiValueError( "Invalid keyword: '{0}''".format(v)) self._disabled_client_side_validations = s -{{#if hasHttpSignatureMethods}} +{{#hasHttpSignatureMethods}} if name == "signing_info" and value is not None: - # Ensure the host paramater from signing info is the same as + # Ensure the host parameter from signing info is the same as # Configuration.host. value.host = self.host -{{/if}} +{{/hasHttpSignatureMethods}} @classmethod def set_default(cls, default): @@ -483,21 +474,21 @@ conf = {{{packageName}}}.Configuration( :return: The Auth Settings information dict. """ auth = {} -{{#each authMethods}} -{{#if isApiKey}} - if '{{name}}' in self.api_key{{#each vendorExtensions.x-auth-id-alias}} or '{{.}}' in self.api_key{{/each}}: +{{#authMethods}} +{{#isApiKey}} + if '{{name}}' in self.api_key{{#vendorExtensions.x-auth-id-alias}} or '{{.}}' in self.api_key{{/vendorExtensions.x-auth-id-alias}}: auth['{{name}}'] = { 'type': 'api_key', - 'in': {{#if isKeyInCookie}}'cookie'{{/if}}{{#if isKeyInHeader}}'header'{{/if}}{{#if isKeyInQuery}}'query'{{/if}}, + 'in': {{#isKeyInCookie}}'cookie'{{/isKeyInCookie}}{{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, 'key': '{{keyParamName}}', 'value': self.get_api_key_with_prefix( - '{{name}}',{{#each vendorExtensions.x-auth-id-alias}} - alias='{{.}}',{{/each}} + '{{name}}',{{#vendorExtensions.x-auth-id-alias}} + alias='{{.}}',{{/vendorExtensions.x-auth-id-alias}} ), } -{{/if}} -{{#if isBasic}} - {{#if isBasicBasic}} +{{/isApiKey}} +{{#isBasic}} + {{#isBasicBasic}} if self.username is not None and self.password is not None: auth['{{name}}'] = { 'type': 'basic', @@ -505,20 +496,20 @@ conf = {{{packageName}}}.Configuration( 'key': 'Authorization', 'value': self.get_basic_auth_token() } - {{/if}} - {{#if isBasicBearer}} + {{/isBasicBasic}} + {{#isBasicBearer}} if self.access_token is not None: auth['{{name}}'] = { 'type': 'bearer', 'in': 'header', - {{#if bearerFormat}} - 'format': '{{{bearerFormat}}}', - {{/if}} + {{#bearerFormat}} + 'format': '{{{.}}}', + {{/bearerFormat}} 'key': 'Authorization', 'value': 'Bearer ' + self.access_token } - {{/if}} - {{#if isHttpSignature}} + {{/isBasicBearer}} + {{#isHttpSignature}} if self.signing_info is not None: auth['{{name}}'] = { 'type': 'http-signature', @@ -526,9 +517,9 @@ conf = {{{packageName}}}.Configuration( 'key': 'Authorization', 'value': None # Signature headers are calculated for every HTTP request } - {{/if}} -{{/if}} -{{#if isOAuth}} + {{/isHttpSignature}} +{{/isBasic}} +{{#isOAuth}} if self.access_token is not None: auth['{{name}}'] = { 'type': 'oauth2', @@ -536,8 +527,8 @@ conf = {{{packageName}}}.Configuration( 'key': 'Authorization', 'value': 'Bearer ' + self.access_token } -{{/if}} -{{/each}} +{{/isOAuth}} +{{/authMethods}} return auth def to_debug_report(self): @@ -558,33 +549,33 @@ conf = {{{packageName}}}.Configuration( :return: An array of host settings """ return [ - {{#each servers}} + {{#servers}} { 'url': "{{{url}}}", - 'description': "{{{description}}}{{#unless description}}No description provided{{/unless}}", - {{#each variables}} - {{#if @first}} + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + {{#variables}} + {{#-first}} 'variables': { - {{/if}} + {{/-first}} '{{{name}}}': { - 'description': "{{{description}}}{{#unless description}}No description provided{{/unless}}", + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", 'default_value': "{{{defaultValue}}}", - {{#each enumValues}} - {{#if @first}} + {{#enumValues}} + {{#-first}} 'enum_values': [ - {{/if}} - "{{{.}}}"{{#unless @last}},{{/unless}} - {{#if @last}} + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} ] - {{/if}} - {{/each}} - }{{#unless @last}},{{/unless}} - {{#if @last}} + {{/-last}} + {{/enumValues}} + }{{^-last}},{{/-last}} + {{#-last}} } - {{/if}} - {{/each}} - }{{#unless @last}},{{/unless}} - {{/each}} + {{/-last}} + {{/variables}} + }{{^-last}},{{/-last}} + {{/servers}} ] def get_host_from_settings(self, index, variables=None, servers=None): diff --git a/openapi/python_explerimental_client_template/exceptions.handlebars b/openapi/python_client_urllib3_templates/exceptions.mustache similarity index 79% rename from openapi/python_explerimental_client_template/exceptions.handlebars rename to openapi/python_client_urllib3_templates/exceptions.mustache index fa5f7534..046dcd9a 100644 --- a/openapi/python_explerimental_client_template/exceptions.handlebars +++ b/openapi/python_client_urllib3_templates/exceptions.mustache @@ -1,5 +1,3 @@ -# coding: utf-8 - {{>partial_header}} @@ -92,12 +90,12 @@ class ApiKeyError(OpenApiException, KeyError): class ApiException(OpenApiException): - def __init__(self, status=None, reason=None, api_response: '{{packageName}}.api_client.ApiResponse' = None): - if api_response: - self.status = api_response.response.status - self.reason = api_response.response.reason - self.body = api_response.response.data - self.headers = api_response.response.getheaders() + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() else: self.status = status self.reason = reason @@ -118,6 +116,30 @@ class ApiException(OpenApiException): return error_message +class NotFoundException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None): + super(NotFoundException, self).__init__(status, reason, http_resp) + + +class UnauthorizedException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None): + super(UnauthorizedException, self).__init__(status, reason, http_resp) + + +class ForbiddenException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None): + super(ForbiddenException, self).__init__(status, reason, http_resp) + + +class ServiceException(ApiException): + + def __init__(self, status=None, reason=None, http_resp=None): + super(ServiceException, self).__init__(status, reason, http_resp) + + def render_path(path_to_item): """Returns a string representation of a path""" result = "" diff --git a/openapi/python_explerimental_client_template/git_push.sh.handlebars b/openapi/python_client_urllib3_templates/git_push.sh.mustache similarity index 87% rename from openapi/python_explerimental_client_template/git_push.sh.handlebars rename to openapi/python_client_urllib3_templates/git_push.sh.mustache index 8b3f689c..0e3776ae 100644 --- a/openapi/python_explerimental_client_template/git_push.sh.handlebars +++ b/openapi/python_client_urllib3_templates/git_push.sh.mustache @@ -1,7 +1,7 @@ #!/bin/sh # ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ # -# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" git_user_id=$1 git_repo_id=$2 @@ -38,14 +38,14 @@ git add . git commit -m "$release_note" # Sets the new remote -git_remote=`git remote` +git_remote=$(git remote) if [ "$git_remote" = "" ]; then # git remote not defined if [ "$GIT_TOKEN" = "" ]; then echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git else - git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git fi fi @@ -55,4 +55,3 @@ git pull origin master # Pushes (Forces) the changes in the local repository up to the remote repository echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" git push origin master 2>&1 | grep -v 'To https' - diff --git a/openapi/python_explerimental_client_template/gitignore.handlebars b/openapi/python_client_urllib3_templates/gitignore.mustache similarity index 96% rename from openapi/python_explerimental_client_template/gitignore.handlebars rename to openapi/python_client_urllib3_templates/gitignore.mustache index a62e8aba..43995bd4 100644 --- a/openapi/python_explerimental_client_template/gitignore.handlebars +++ b/openapi/python_client_urllib3_templates/gitignore.mustache @@ -33,7 +33,6 @@ var/ # Installer logs pip-log.txt pip-delete-this-directory.txt -dev-requirements.txt.log # Unit test / coverage reports htmlcov/ diff --git a/openapi/python_explerimental_client_template/gitlab-ci.handlebars b/openapi/python_client_urllib3_templates/gitlab-ci.mustache similarity index 79% rename from openapi/python_explerimental_client_template/gitlab-ci.handlebars rename to openapi/python_client_urllib3_templates/gitlab-ci.mustache index 0cfe8f74..cf785a80 100644 --- a/openapi/python_explerimental_client_template/gitlab-ci.handlebars +++ b/openapi/python_client_urllib3_templates/gitlab-ci.mustache @@ -8,16 +8,13 @@ stages: script: - pip install -r requirements.txt - pip install -r test-requirements.txt - {{#if useNose}} + {{#useNose}} - nosetests - {{/if}} - {{#unless useNose}} + {{/useNose}} + {{^useNose}} - pytest --cov={{{packageName}}} - {{/unless}} + {{/useNose}} -test-3.5: - extends: .tests - image: python:3.5-alpine test-3.6: extends: .tests image: python:3.6-alpine @@ -27,3 +24,6 @@ test-3.7: test-3.8: extends: .tests image: python:3.8-alpine +test-3.9: + extends: .tests + image: python:3.9-alpine diff --git a/openapi/python_client_urllib3_templates/model.mustache b/openapi/python_client_urllib3_templates/model.mustache new file mode 100644 index 00000000..e2e70c83 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model.mustache @@ -0,0 +1,58 @@ +{{> partial_header }} + +import re # noqa: F401 +import sys # noqa: F401 + +from {{packageName}}.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel +) +from {{packageName}}.exceptions import ApiAttributeError + +{{#models}} +{{#model}} +{{#imports}} +{{#-first}} + +def lazy_import(): +{{/-first}} + {{{.}}} +{{/imports}} + + +{{^interfaces}} +{{#isArray}} +{{> model_templates/model_simple }} +{{/isArray}} +{{#isEnum}} +{{> model_templates/model_simple }} +{{/isEnum}} +{{#isAlias}} +{{> model_templates/model_simple }} +{{/isAlias}} +{{^isArray}} +{{^isEnum}} +{{^isAlias}} +{{> model_templates/model_normal }} +{{/isAlias}} +{{/isEnum}} +{{/isArray}} +{{/interfaces}} +{{#interfaces}} +{{#-last}} +{{> model_templates/model_composed }} +{{/-last}} +{{/interfaces}} +{{/model}} +{{/models}} diff --git a/openapi/python_client_urllib3_templates/model_doc.mustache b/openapi/python_client_urllib3_templates/model_doc.mustache new file mode 100644 index 00000000..3f7c8263 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_doc.mustache @@ -0,0 +1,37 @@ +{{#models}}{{#model}}# {{classname}} + +{{#description}}{{&description}} +{{/description}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#isEnum}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}{{#allowableValues}}{{#defaultValue}}, {{/defaultValue}} must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} +{{/isEnum}} +{{#isAlias}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}} +{{/isAlias}} +{{#isArray}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}}{{#arrayModelType}}[**{{dataType}}**]({{arrayModelType}}.md){{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}} +{{/isArray}} +{{#requiredVars}} +{{^defaultValue}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | {{description}} | {{#isReadOnly}}[readonly] {{/isReadOnly}} +{{/defaultValue}} +{{/requiredVars}} +{{#requiredVars}} +{{#defaultValue}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}defaults to {{{.}}}{{/defaultValue}} +{{/defaultValue}} +{{/requiredVars}} +{{#optionalVars}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | {{description}} | [optional] {{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalVars}} +{{#additionalProperties}} +**any string name** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | any string name can be used but the value must be the correct type | [optional] +{{/additionalProperties}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/openapi/python_client_urllib3_templates/model_templates/classvars.mustache b/openapi/python_client_urllib3_templates/model_templates/classvars.mustache new file mode 100644 index 00000000..0e5ff25b --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/classvars.mustache @@ -0,0 +1,138 @@ + allowed_values = { +{{#isEnum}} + ('value',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{#requiredVars}} +{{#isEnum}} + ('{{name}}',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{/requiredVars}} +{{#optionalVars}} +{{#isEnum}} + ('{{name}}',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{/optionalVars}} + } + + validations = { +{{#hasValidation}} + ('value',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{#requiredVars}} +{{#hasValidation}} + ('{{name}}',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{/requiredVars}} +{{#optionalVars}} +{{#hasValidation}} + ('{{name}}',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{/optionalVars}} + } + +{{#additionalProperties}} + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} + return ({{{dataType}}},) # noqa: E501 +{{/additionalProperties}} +{{^additionalProperties}} + additional_properties_type = None +{{/additionalProperties}} + + _nullable = {{#isNullable}}True{{/isNullable}}{{^isNullable}}False{{/isNullable}} + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} + return { +{{#isAlias}} + 'value': ({{{dataType}}},), +{{/isAlias}} +{{#isEnum}} + 'value': ({{{dataType}}},), +{{/isEnum}} +{{#isArray}} + 'value': ({{{dataType}}},), +{{/isArray}} +{{#requiredVars}} + '{{name}}': ({{{dataType}}},), # noqa: E501 +{{/requiredVars}} +{{#optionalVars}} + '{{name}}': ({{{dataType}}},), # noqa: E501 +{{/optionalVars}} + } + + @cached_property + def discriminator(): +{{^discriminator}} + return None +{{/discriminator}} +{{#discriminator}} +{{#mappedModels}} +{{#-first}} +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} +{{/-first}} +{{/mappedModels}} + val = { +{{#mappedModels}} + '{{mappingName}}': {{{modelName}}}, +{{/mappedModels}} + } + if not val: + return None + return {'{{{discriminatorName}}}': val}{{/discriminator}} \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/docstring_allowed.mustache b/openapi/python_client_urllib3_templates/model_templates/docstring_allowed.mustache new file mode 100644 index 00000000..ab20932b --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/docstring_allowed.mustache @@ -0,0 +1,4 @@ + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/docstring_init_required_kwargs.mustache b/openapi/python_client_urllib3_templates/model_templates/docstring_init_required_kwargs.mustache new file mode 100644 index 00000000..b09f3950 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/docstring_init_required_kwargs.mustache @@ -0,0 +1,30 @@ + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/docstring_openapi_validations.mustache b/openapi/python_client_urllib3_templates/model_templates/docstring_openapi_validations.mustache new file mode 100644 index 00000000..f933b86c --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/docstring_openapi_validations.mustache @@ -0,0 +1,7 @@ + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/invalid_pos_args.mustache b/openapi/python_client_urllib3_templates/model_templates/invalid_pos_args.mustache new file mode 100644 index 00000000..143d50c8 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/invalid_pos_args.mustache @@ -0,0 +1,9 @@ + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_composed.mustache b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_composed.mustache new file mode 100644 index 00000000..ba03dbc6 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_composed.mustache @@ -0,0 +1,66 @@ + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + + Keyword Args: +{{#requiredVars}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{/defaultValue}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{/requiredVars}} +{{> model_templates/docstring_init_required_kwargs }} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 +{{/optionalVars}} + """ + +{{#requiredVars}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + constant_args = { + '_check_type': _check_type, + '_path_to_item': _path_to_item, + '_spec_property_naming': _spec_property_naming, + '_configuration': _configuration, + '_visited_composed_classes': self._visited_composed_classes, + } + composed_info = validate_get_composed_info( + constant_args, kwargs, self) + self._composed_instances = composed_info[0] + self._var_name_to_model_instances = composed_info[1] + self._additional_properties_model_instances = composed_info[2] + discarded_args = composed_info[3] + + for var_name, var_value in kwargs.items(): + if var_name in discarded_args and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self._additional_properties_model_instances: + # discard variable. + continue + setattr(self, var_name, var_value) + + return self \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_normal.mustache b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_normal.mustache new file mode 100644 index 00000000..3b82ba7f --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_normal.mustache @@ -0,0 +1,17 @@ +{{> model_templates/method_from_openapi_data_shared }} + +{{#isEnum}} + self.value = value +{{/isEnum}} +{{#requiredVars}} + self.{{name}} = {{name}} +{{/requiredVars}} + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_shared.mustache b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_shared.mustache new file mode 100644 index 00000000..68c70c62 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_shared.mustache @@ -0,0 +1,49 @@ + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + +{{#requiredVars}} +{{#-first}} + Args: +{{/-first}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{#-last}} + +{{/-last}} +{{/requiredVars}} + Keyword Args: +{{#requiredVars}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{/defaultValue}} +{{/requiredVars}} +{{> model_templates/docstring_init_required_kwargs }} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 +{{/optionalVars}} + """ + +{{#requiredVars}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_simple.mustache b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_simple.mustache new file mode 100644 index 00000000..11f237c0 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_from_openapi_data_simple.mustache @@ -0,0 +1,62 @@ + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, *args, **kwargs): + """{{classname}} - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + + Keyword Args: + value ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{> model_templates/docstring_init_required_kwargs }} + """ + # required up here when default value is not given + _path_to_item = kwargs.pop('_path_to_item', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if 'value' in kwargs: + value = kwargs.pop('value') + elif args: + args = list(args) + value = args.pop(0) +{{#defaultValue}} + else: + value = {{{.}}} +{{/defaultValue}} +{{^defaultValue}} + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) +{{/defaultValue}} + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + return self \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_init_composed.mustache b/openapi/python_client_urllib3_templates/model_templates/method_init_composed.mustache new file mode 100644 index 00000000..ba7991e4 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_init_composed.mustache @@ -0,0 +1,80 @@ + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + '_composed_instances', + '_var_name_to_model_instances', + '_additional_properties_model_instances', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + + Keyword Args: +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{/defaultValue}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} +{{> model_templates/docstring_init_required_kwargs }} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 +{{/optionalVars}} + """ + +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + constant_args = { + '_check_type': _check_type, + '_path_to_item': _path_to_item, + '_spec_property_naming': _spec_property_naming, + '_configuration': _configuration, + '_visited_composed_classes': self._visited_composed_classes, + } + composed_info = validate_get_composed_info( + constant_args, kwargs, self) + self._composed_instances = composed_info[0] + self._var_name_to_model_instances = composed_info[1] + self._additional_properties_model_instances = composed_info[2] + discarded_args = composed_info[3] + + for var_name, var_value in kwargs.items(): + if var_name in discarded_args and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self._additional_properties_model_instances: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_init_normal.mustache b/openapi/python_client_urllib3_templates/model_templates/method_init_normal.mustache new file mode 100644 index 00000000..bc5e7686 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_init_normal.mustache @@ -0,0 +1,30 @@ + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + +{{> model_templates/method_init_shared }} + +{{#isEnum}} + self.value = value +{{/isEnum}} +{{#requiredVars}} +{{^isReadOnly}} + self.{{name}} = {{name}} +{{/isReadOnly}} +{{/requiredVars}} + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_init_shared.mustache b/openapi/python_client_urllib3_templates/model_templates/method_init_shared.mustache new file mode 100644 index 00000000..6d940909 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_init_shared.mustache @@ -0,0 +1,52 @@ + @convert_js_args_to_python_args + def __init__(self{{#requiredVars}}{{^isReadOnly}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/isReadOnly}}{{/requiredVars}}, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + +{{#requiredVars}} +{{^isReadOnly}} +{{#-first}} + Args: +{{/-first}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{#-last}} + +{{/-last}} +{{/isReadOnly}} +{{/requiredVars}} + Keyword Args: +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} +{{> model_templates/docstring_init_required_kwargs }} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 +{{/optionalVars}} + """ + +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_init_simple.mustache b/openapi/python_client_urllib3_templates/model_templates/method_init_simple.mustache new file mode 100644 index 00000000..ec111f6e --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_init_simple.mustache @@ -0,0 +1,66 @@ + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): + """{{classname}} - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + + Keyword Args: + value ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{> model_templates/docstring_init_required_kwargs }} + """ + # required up here when default value is not given + _path_to_item = kwargs.pop('_path_to_item', ()) + + if 'value' in kwargs: + value = kwargs.pop('value') + elif args: + args = list(args) + value = args.pop(0) +{{#defaultValue}} + else: + value = {{{.}}} +{{/defaultValue}} +{{^defaultValue}} + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) +{{/defaultValue}} + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/method_set_attribute.mustache b/openapi/python_client_urllib3_templates/model_templates/method_set_attribute.mustache new file mode 100644 index 00000000..8ba0529c --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/method_set_attribute.mustache @@ -0,0 +1,51 @@ + def set_attribute(self, name, value): + # this is only used to set properties on self + + path_to_item = [] + if self._path_to_item: + path_to_item.extend(self._path_to_item) + path_to_item.append(name) + + if name in self.openapi_types: + required_types_mixed = self.openapi_types[name] + elif self.additional_properties_type is None: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + path_to_item + ) + elif self.additional_properties_type is not None: + required_types_mixed = self.additional_properties_type + + if get_simple_class(name) != str: + error_msg = type_error_message( + var_name=name, + var_value=name, + valid_classes=(str,), + key_type=True + ) + raise ApiTypeError( + error_msg, + path_to_item=path_to_item, + valid_classes=(str,), + key_type=True + ) + + if self._check_type: + value = validate_and_convert_types( + value, required_types_mixed, path_to_item, self._spec_property_naming, + self._check_type, configuration=self._configuration) + if (name,) in self.allowed_values: + check_allowed_values( + self.allowed_values, + (name,), + value + ) + if (name,) in self.validations: + check_validations( + self.validations, + (name,), + value, + self._configuration + ) + self.__dict__['_data_store'][name] = value \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_composed.mustache b/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_composed.mustache new file mode 100644 index 00000000..ada739e7 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_composed.mustache @@ -0,0 +1,103 @@ + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" + if name in self.required_properties: + self.__dict__[name] = value + return + + """ + Use cases: + 1. additional_properties_type is None (additionalProperties == False in spec) + Check for property presence in self.openapi_types + if not present then throw an error + if present set in self, set attribute + always set on composed schemas + 2. additional_properties_type exists + set attribute on self + always set on composed schemas + """ + if self.additional_properties_type is None: + """ + For an attribute to exist on a composed schema it must: + - fulfill schema_requirements in the self composed schema not considering oneOf/anyOf/allOf schemas AND + - fulfill schema_requirements in each oneOf/anyOf/allOf schemas + + schema_requirements: + For an attribute to exist on a schema it must: + - be present in properties at the schema OR + - have additionalProperties unset (defaults additionalProperties = any type) OR + - have additionalProperties set + """ + if name not in self.openapi_types: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + # attribute must be set on self and composed instances + self.set_attribute(name, value) + for model_instance in self._composed_instances: + setattr(model_instance, name, value) + if name not in self._var_name_to_model_instances: + # we assigned an additional property + self.__dict__['_var_name_to_model_instances'][name] = self._composed_instances + [self] + return None + + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" + if name in self.required_properties: + return self.__dict__[name] + + # get the attribute from the correct instance + model_instances = self._var_name_to_model_instances.get(name) + values = [] + # A composed model stores self and child (oneof/anyOf/allOf) models under + # self._var_name_to_model_instances. + # Any property must exist in self and all model instances + # The value stored in all model instances must be the same + if model_instances: + for model_instance in model_instances: + if name in model_instance._data_store: + v = model_instance._data_store[name] + if v not in values: + values.append(v) + len_values = len(values) + if len_values == 0: + return default + elif len_values == 1: + return values[0] + elif len_values > 1: + raise ApiValueError( + "Values stored for property {0} in {1} differ when looking " + "at self and self's composed instances. All values must be " + "the same".format(name, type(self).__name__), + [e for e in [self._path_to_item, name] if e] + ) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + + def __contains__(self, name): + """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" + + if name in self.required_properties: + return name in self.__dict__ + + model_instances = self._var_name_to_model_instances.get( + name, self._additional_properties_model_instances) + + if model_instances: + for model_instance in model_instances: + if name in model_instance._data_store: + return True + + return False \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_normal.mustache b/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_normal.mustache new file mode 100644 index 00000000..8d2f48ea --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/methods_setattr_getattr_normal.mustache @@ -0,0 +1,32 @@ + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" + if name in self.required_properties: + self.__dict__[name] = value + return + + self.set_attribute(name, value) + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" + if name in self.required_properties: + return self.__dict__[name] + + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) + + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + + def __contains__(self, name): + """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" + if name in self.required_properties: + return name in self.__dict__ + + return name in self.__dict__['_data_store'] \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/methods_shared.mustache b/openapi/python_client_urllib3_templates/model_templates/methods_shared.mustache new file mode 100644 index 00000000..eddad253 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/methods_shared.mustache @@ -0,0 +1,34 @@ + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other + + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.{{#attrNoneIfUnset}}get{{/attrNoneIfUnset}}{{^attrNoneIfUnset}}__getitem__{{/attrNoneIfUnset}}(attr) + + def __copy__(self): + cls = self.__class__ + if self.get("_spec_property_naming", False): + return cls._new_from_openapi_data(**self.__dict__) + else: + return new_cls.__new__(cls, **self.__dict__) + + def __deepcopy__(self, memo): + cls = self.__class__ + + if self.get("_spec_property_naming", False): + new_inst = cls._new_from_openapi_data() + else: + new_inst = cls.__new__(cls) + + for k, v in self.__dict__.items(): + setattr(new_inst, k, deepcopy(v, memo)) + return new_inst diff --git a/openapi/python_client_urllib3_templates/model_templates/methods_todict_tostr_eq_shared.mustache b/openapi/python_client_urllib3_templates/model_templates/methods_todict_tostr_eq_shared.mustache new file mode 100644 index 00000000..17c44119 --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/methods_todict_tostr_eq_shared.mustache @@ -0,0 +1,24 @@ + def to_dict(self): + """Returns the model properties as a dict""" + return model_to_dict(self, serialize=False) + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, self.__class__): + return False + + if not set(self._data_store.keys()) == set(other._data_store.keys()): + return False + for _var_name, this_val in self._data_store.items(): + that_val = other._data_store[_var_name] + types = set() + types.add(this_val.__class__) + types.add(that_val.__class__) + vals_equal = this_val == that_val + if not vals_equal: + return False + return True \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/methods_tostr_eq_simple.mustache b/openapi/python_client_urllib3_templates/model_templates/methods_tostr_eq_simple.mustache new file mode 100644 index 00000000..4a7ef6ae --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/methods_tostr_eq_simple.mustache @@ -0,0 +1,16 @@ + def to_str(self): + """Returns the string representation of the model""" + return str(self.value) + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, self.__class__): + return False + + this_val = self._data_store['value'] + that_val = other._data_store['value'] + types = set() + types.add(this_val.__class__) + types.add(that_val.__class__) + vals_equal = this_val == that_val + return vals_equal \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/model_composed.mustache b/openapi/python_client_urllib3_templates/model_templates/model_composed.mustache new file mode 100644 index 00000000..a6d2277a --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/model_composed.mustache @@ -0,0 +1,74 @@ +class {{classname}}(ModelComposed): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: +{{> model_templates/docstring_allowed }} + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. +{{> model_templates/docstring_openapi_validations }} + """ + +{{> model_templates/classvars }} + + attribute_map = { +{{#requiredVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/requiredVars}} +{{#optionalVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/optionalVars}} + } + + read_only_vars = { +{{#requiredVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/requiredVars}} +{{#optionalVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/optionalVars}} + } + +{{> model_templates/method_from_openapi_data_composed }} + +{{> model_templates/method_init_composed }} + + @cached_property + def _composed_schemas(): + # we need this here to make our import statements work + # we must store _composed_schemas in here so the code is only run + # when we invoke this method. If we kept this at the class + # level we would get an error because the class level + # code would be run when this module is imported, and these composed + # classes don't exist yet because their module has not finished + # loading +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} + return { + 'anyOf': [ +{{#anyOf}} + {{{.}}}, +{{/anyOf}} + ], + 'allOf': [ +{{#allOf}} + {{{.}}}, +{{/allOf}} + ], + 'oneOf': [ +{{#oneOf}} + {{{.}}}, +{{/oneOf}} + ], + } \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/model_normal.mustache b/openapi/python_client_urllib3_templates/model_templates/model_normal.mustache new file mode 100644 index 00000000..513aa01e --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/model_normal.mustache @@ -0,0 +1,44 @@ +class {{classname}}(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: +{{> model_templates/docstring_allowed }} + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. +{{> model_templates/docstring_openapi_validations }} + """ + +{{> model_templates/classvars }} + + attribute_map = { +{{#requiredVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/requiredVars}} +{{#optionalVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/optionalVars}} + } + + read_only_vars = { +{{#requiredVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/requiredVars}} +{{#optionalVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/optionalVars}} + } + + _composed_schemas = {} + +{{> model_templates/method_from_openapi_data_normal}} + +{{> model_templates/method_init_normal}} \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/model_simple.mustache b/openapi/python_client_urllib3_templates/model_templates/model_simple.mustache new file mode 100644 index 00000000..1a41a9cf --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/model_simple.mustache @@ -0,0 +1,22 @@ +class {{classname}}(ModelSimple): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: +{{> model_templates/docstring_allowed }} +{{> model_templates/docstring_openapi_validations }} + """ + +{{> model_templates/classvars }} + + attribute_map = {} + + read_only_vars = set() + + _composed_schemas = None + +{{> model_templates/method_init_simple}} + +{{> model_templates/method_from_openapi_data_simple}} \ No newline at end of file diff --git a/openapi/python_client_urllib3_templates/model_templates/validations.mustache b/openapi/python_client_urllib3_templates/model_templates/validations.mustache new file mode 100644 index 00000000..758dc41d --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_templates/validations.mustache @@ -0,0 +1,34 @@ +{{#maxLength}} + 'max_length': {{.}}, +{{/maxLength}} +{{#minLength}} + 'min_length': {{.}}, +{{/minLength}} +{{#maxItems}} + 'max_items': {{.}}, +{{/maxItems}} +{{#minProperties}} + 'min_properties': {{.}}, +{{/minProperties}} +{{#maxProperties}} + 'max_properties': {{.}}, +{{/maxProperties}} +{{#minItems}} + 'min_items': {{.}}, +{{/minItems}} +{{#maximum}} + {{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}'inclusive_maximum': {{maximum}}, +{{/maximum}} +{{#minimum}} + {{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}'inclusive_minimum': {{minimum}}, +{{/minimum}} +{{#pattern}} + 'regex': { + 'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}} + {{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}} + }, +{{/pattern}} +{{#multipleOf}} + 'multiple_of': {{.}}, +{{/multipleOf}} + }, \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_test.handlebars b/openapi/python_client_urllib3_templates/model_test.mustache similarity index 70% rename from openapi/python_explerimental_client_template/model_test.handlebars rename to openapi/python_client_urllib3_templates/model_test.mustache index 48a4a7c8..aa95f649 100644 --- a/openapi/python_explerimental_client_template/model_test.handlebars +++ b/openapi/python_client_urllib3_templates/model_test.mustache @@ -1,13 +1,15 @@ -# coding: utf-8 - {{>partial_header}} +import sys import unittest import {{packageName}} -{{#each models}} -{{#with model}} -from {{packageName}}.{{modelPackage}}.{{classFilename}} import {{classname}} +{{#models}} +{{#model}} +{{#imports}} +{{{.}}} +{{/imports}} +from {{modelPackage}}.{{classFilename}} import {{classname}} class Test{{classname}}(unittest.TestCase): @@ -19,14 +21,14 @@ class Test{{classname}}(unittest.TestCase): def tearDown(self): pass - def test_{{classname}}(self): + def test{{classname}}(self): """Test {{classname}}""" # FIXME: construct object with mandatory attributes with example values # model = {{classname}}() # noqa: E501 pass -{{/with}} -{{/each}} +{{/model}} +{{/models}} if __name__ == '__main__': unittest.main() diff --git a/openapi/python_client_urllib3_templates/model_utils.mustache b/openapi/python_client_urllib3_templates/model_utils.mustache new file mode 100644 index 00000000..0e8ad8be --- /dev/null +++ b/openapi/python_client_urllib3_templates/model_utils.mustache @@ -0,0 +1,1720 @@ +{{>partial_header}} + +from datetime import date, datetime # noqa: F401 +from copy import deepcopy +import inspect +import io +import os +import pprint +import re +import tempfile + +from dateutil.parser import parse + +from {{packageName}}.exceptions import ( + ApiKeyError, + ApiAttributeError, + ApiTypeError, + ApiValueError, +) + +none_type = type(None) +file_type = io.IOBase + + +def convert_js_args_to_python_args(fn): + from functools import wraps + @wraps(fn) + def wrapped_init(_self, *args, **kwargs): + """ + An attribute named `self` received from the api will conflicts with the reserved `self` + parameter of a class method. During generation, `self` attributes are mapped + to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts. + """ + spec_property_naming = kwargs.get('_spec_property_naming', False) + if spec_property_naming: + kwargs = change_keys_js_to_python(kwargs, _self if isinstance(_self, type) else _self.__class__) + return fn(_self, *args, **kwargs) + return wrapped_init + + +class cached_property(object): + # this caches the result of the function call for fn with no inputs + # use this as a decorator on function methods that you want converted + # into cached properties + result_key = '_results' + + def __init__(self, fn): + self._fn = fn + + def __get__(self, instance, cls=None): + if self.result_key in vars(self): + return vars(self)[self.result_key] + else: + result = self._fn() + setattr(self, self.result_key, result) + return result + + +PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type) + +def allows_single_value_input(cls): + """ + This function returns True if the input composed schema model or any + descendant model allows a value only input + This is true for cases where oneOf contains items like: + oneOf: + - float + - NumberWithValidation + - StringEnum + - ArrayModel + - null + TODO: lru_cache this + """ + if ( + issubclass(cls, ModelSimple) or + cls in PRIMITIVE_TYPES + ): + return True + elif issubclass(cls, ModelComposed): + if not cls._composed_schemas['oneOf']: + return False + return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf']) + return False + +def composed_model_input_classes(cls): + """ + This function returns a list of the possible models that can be accepted as + inputs. + TODO: lru_cache this + """ + if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES: + return [cls] + elif issubclass(cls, ModelNormal): + if cls.discriminator is None: + return [cls] + else: + return get_discriminated_classes(cls) + elif issubclass(cls, ModelComposed): + if not cls._composed_schemas['oneOf']: + return [] + if cls.discriminator is None: + input_classes = [] + for c in cls._composed_schemas['oneOf']: + input_classes.extend(composed_model_input_classes(c)) + return input_classes + else: + return get_discriminated_classes(cls) + return [] + + +class OpenApiModel(object): + """The base class for all OpenAPIModels""" + +{{> model_templates/method_set_attribute }} + +{{> model_templates/methods_shared }} + + def __new__(cls, *args, **kwargs): + # this function uses the discriminator to + # pick a new schema/class to instantiate because a discriminator + # propertyName value was passed in + + if len(args) == 1: + arg = args[0] + if arg is None and is_type_nullable(cls): + # The input data is the 'null' value and the type is nullable. + return None + + if issubclass(cls, ModelComposed) and allows_single_value_input(cls): + model_kwargs = {} + oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) + return oneof_instance + + + visited_composed_classes = kwargs.get('_visited_composed_classes', ()) + if ( + cls.discriminator is None or + cls in visited_composed_classes + ): + # Use case 1: this openapi schema (cls) does not have a discriminator + # Use case 2: we have already visited this class before and are sure that we + # want to instantiate it this time. We have visited this class deserializing + # a payload with a discriminator. During that process we traveled through + # this class but did not make an instance of it. Now we are making an + # instance of a composed class which contains cls in it, so this time make an instance of cls. + # + # Here's an example of use case 2: If Animal has a discriminator + # petType and we pass in "Dog", and the class Dog + # allOf includes Animal, we move through Animal + # once using the discriminator, and pick Dog. + # Then in the composed schema dog Dog, we will make an instance of the + # Animal class (because Dal has allOf: Animal) but this time we won't travel + # through Animal's discriminator because we passed in + # _visited_composed_classes = (Animal,) + + return super(OpenApiModel, cls).__new__(cls) + + # Get the name and value of the discriminator property. + # The discriminator name is obtained from the discriminator meta-data + # and the discriminator value is obtained from the input data. + discr_propertyname_py = list(cls.discriminator.keys())[0] + discr_propertyname_js = cls.attribute_map[discr_propertyname_py] + if discr_propertyname_js in kwargs: + discr_value = kwargs[discr_propertyname_js] + elif discr_propertyname_py in kwargs: + discr_value = kwargs[discr_propertyname_py] + else: + # The input data does not contain the discriminator property. + path_to_item = kwargs.get('_path_to_item', ()) + raise ApiValueError( + "Cannot deserialize input data due to missing discriminator. " + "The discriminator property '%s' is missing at path: %s" % + (discr_propertyname_js, path_to_item) + ) + + # Implementation note: the last argument to get_discriminator_class + # is a list of visited classes. get_discriminator_class may recursively + # call itself and update the list of visited classes, and the initial + # value must be an empty list. Hence not using 'visited_composed_classes' + new_cls = get_discriminator_class( + cls, discr_propertyname_py, discr_value, []) + if new_cls is None: + path_to_item = kwargs.get('_path_to_item', ()) + disc_prop_value = kwargs.get( + discr_propertyname_js, kwargs.get(discr_propertyname_py)) + raise ApiValueError( + "Cannot deserialize input data due to invalid discriminator " + "value. The OpenAPI document has no mapping for discriminator " + "property '%s'='%s' at path: %s" % + (discr_propertyname_js, disc_prop_value, path_to_item) + ) + + if new_cls in visited_composed_classes: + # if we are making an instance of a composed schema Descendent + # which allOf includes Ancestor, then Ancestor contains + # a discriminator that includes Descendent. + # So if we make an instance of Descendent, we have to make an + # instance of Ancestor to hold the allOf properties. + # This code detects that use case and makes the instance of Ancestor + # For example: + # When making an instance of Dog, _visited_composed_classes = (Dog,) + # then we make an instance of Animal to include in dog._composed_instances + # so when we are here, cls is Animal + # cls.discriminator != None + # cls not in _visited_composed_classes + # new_cls = Dog + # but we know we know that we already have Dog + # because it is in visited_composed_classes + # so make Animal here + return super(OpenApiModel, cls).__new__(cls) + + # Build a list containing all oneOf and anyOf descendants. + oneof_anyof_classes = None + if cls._composed_schemas is not None: + oneof_anyof_classes = ( + cls._composed_schemas.get('oneOf', ()) + + cls._composed_schemas.get('anyOf', ())) + oneof_anyof_child = new_cls in oneof_anyof_classes + kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + + if cls._composed_schemas.get('allOf') and oneof_anyof_child: + # Validate that we can make self because when we make the + # new_cls it will not include the allOf validations in self + self_inst = super(OpenApiModel, cls).__new__(cls) + self_inst.__init__(*args, **kwargs) + + if kwargs.get("_spec_property_naming", False): + # when true, implies new is from deserialization + new_inst = new_cls._new_from_openapi_data(*args, **kwargs) + else: + new_inst = new_cls.__new__(new_cls, *args, **kwargs) + new_inst.__init__(*args, **kwargs) + + return new_inst + + + @classmethod + @convert_js_args_to_python_args + def _new_from_openapi_data(cls, *args, **kwargs): + # this function uses the discriminator to + # pick a new schema/class to instantiate because a discriminator + # propertyName value was passed in + + if len(args) == 1: + arg = args[0] + if arg is None and is_type_nullable(cls): + # The input data is the 'null' value and the type is nullable. + return None + + if issubclass(cls, ModelComposed) and allows_single_value_input(cls): + model_kwargs = {} + oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) + return oneof_instance + + + visited_composed_classes = kwargs.get('_visited_composed_classes', ()) + if ( + cls.discriminator is None or + cls in visited_composed_classes + ): + # Use case 1: this openapi schema (cls) does not have a discriminator + # Use case 2: we have already visited this class before and are sure that we + # want to instantiate it this time. We have visited this class deserializing + # a payload with a discriminator. During that process we traveled through + # this class but did not make an instance of it. Now we are making an + # instance of a composed class which contains cls in it, so this time make an instance of cls. + # + # Here's an example of use case 2: If Animal has a discriminator + # petType and we pass in "Dog", and the class Dog + # allOf includes Animal, we move through Animal + # once using the discriminator, and pick Dog. + # Then in the composed schema dog Dog, we will make an instance of the + # Animal class (because Dal has allOf: Animal) but this time we won't travel + # through Animal's discriminator because we passed in + # _visited_composed_classes = (Animal,) + + return cls._from_openapi_data(*args, **kwargs) + + # Get the name and value of the discriminator property. + # The discriminator name is obtained from the discriminator meta-data + # and the discriminator value is obtained from the input data. + discr_propertyname_py = list(cls.discriminator.keys())[0] + discr_propertyname_js = cls.attribute_map[discr_propertyname_py] + if discr_propertyname_js in kwargs: + discr_value = kwargs[discr_propertyname_js] + elif discr_propertyname_py in kwargs: + discr_value = kwargs[discr_propertyname_py] + else: + # The input data does not contain the discriminator property. + path_to_item = kwargs.get('_path_to_item', ()) + raise ApiValueError( + "Cannot deserialize input data due to missing discriminator. " + "The discriminator property '%s' is missing at path: %s" % + (discr_propertyname_js, path_to_item) + ) + + # Implementation note: the last argument to get_discriminator_class + # is a list of visited classes. get_discriminator_class may recursively + # call itself and update the list of visited classes, and the initial + # value must be an empty list. Hence not using 'visited_composed_classes' + new_cls = get_discriminator_class( + cls, discr_propertyname_py, discr_value, []) + if new_cls is None: + path_to_item = kwargs.get('_path_to_item', ()) + disc_prop_value = kwargs.get( + discr_propertyname_js, kwargs.get(discr_propertyname_py)) + raise ApiValueError( + "Cannot deserialize input data due to invalid discriminator " + "value. The OpenAPI document has no mapping for discriminator " + "property '%s'='%s' at path: %s" % + (discr_propertyname_js, disc_prop_value, path_to_item) + ) + + if new_cls in visited_composed_classes: + # if we are making an instance of a composed schema Descendent + # which allOf includes Ancestor, then Ancestor contains + # a discriminator that includes Descendent. + # So if we make an instance of Descendent, we have to make an + # instance of Ancestor to hold the allOf properties. + # This code detects that use case and makes the instance of Ancestor + # For example: + # When making an instance of Dog, _visited_composed_classes = (Dog,) + # then we make an instance of Animal to include in dog._composed_instances + # so when we are here, cls is Animal + # cls.discriminator != None + # cls not in _visited_composed_classes + # new_cls = Dog + # but we know we know that we already have Dog + # because it is in visited_composed_classes + # so make Animal here + return cls._from_openapi_data(*args, **kwargs) + + # Build a list containing all oneOf and anyOf descendants. + oneof_anyof_classes = None + if cls._composed_schemas is not None: + oneof_anyof_classes = ( + cls._composed_schemas.get('oneOf', ()) + + cls._composed_schemas.get('anyOf', ())) + oneof_anyof_child = new_cls in oneof_anyof_classes + kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + + if cls._composed_schemas.get('allOf') and oneof_anyof_child: + # Validate that we can make self because when we make the + # new_cls it will not include the allOf validations in self + self_inst = cls._from_openapi_data(*args, **kwargs) + + + new_inst = new_cls._new_from_openapi_data(*args, **kwargs) + return new_inst + + +class ModelSimple(OpenApiModel): + """the parent class of models whose type != object in their + swagger/openapi""" + +{{> model_templates/methods_setattr_getattr_normal }} + +{{> model_templates/methods_tostr_eq_simple }} + + +class ModelNormal(OpenApiModel): + """the parent class of models whose type == object in their + swagger/openapi""" + +{{> model_templates/methods_setattr_getattr_normal }} + +{{> model_templates/methods_todict_tostr_eq_shared}} + + +class ModelComposed(OpenApiModel): + """the parent class of models whose type == object in their + swagger/openapi and have oneOf/allOf/anyOf + + When one sets a property we use var_name_to_model_instances to store the value in + the correct class instances + run any type checking + validation code. + When one gets a property we use var_name_to_model_instances to get the value + from the correct class instances. + This allows multiple composed schemas to contain the same property with additive + constraints on the value. + + _composed_schemas (dict) stores the anyOf/allOf/oneOf classes + key (str): allOf/oneOf/anyOf + value (list): the classes in the XOf definition. + Note: none_type can be included when the openapi document version >= 3.1.0 + _composed_instances (list): stores a list of instances of the composed schemas + defined in _composed_schemas. When properties are accessed in the self instance, + they are returned from the self._data_store or the data stores in the instances + in self._composed_schemas + _var_name_to_model_instances (dict): maps between a variable name on self and + the composed instances (self included) which contain that data + key (str): property name + value (list): list of class instances, self or instances in _composed_instances + which contain the value that the key is referring to. + """ + +{{> model_templates/methods_setattr_getattr_composed }} + +{{> model_templates/methods_todict_tostr_eq_shared}} + + +COERCION_INDEX_BY_TYPE = { + ModelComposed: 0, + ModelNormal: 1, + ModelSimple: 2, + none_type: 3, # The type of 'None'. + list: 4, + dict: 5, + float: 6, + int: 7, + bool: 8, + datetime: 9, + date: 10, + str: 11, + file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type. +} + +# these are used to limit what type conversions we try to do +# when we have a valid type already and we want to try converting +# to another type +UPCONVERSION_TYPE_PAIRS = ( + (str, datetime), + (str, date), + (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. + (list, ModelComposed), + (dict, ModelComposed), + (str, ModelComposed), + (int, ModelComposed), + (float, ModelComposed), + (list, ModelComposed), + (list, ModelNormal), + (dict, ModelNormal), + (str, ModelSimple), + (int, ModelSimple), + (float, ModelSimple), + (list, ModelSimple), +) + +COERCIBLE_TYPE_PAIRS = { + False: ( # client instantiation of a model with client data + # (dict, ModelComposed), + # (list, ModelComposed), + # (dict, ModelNormal), + # (list, ModelNormal), + # (str, ModelSimple), + # (int, ModelSimple), + # (float, ModelSimple), + # (list, ModelSimple), + # (str, int), + # (str, float), + # (str, datetime), + # (str, date), + # (int, str), + # (float, str), + ), + True: ( # server -> client data + (dict, ModelComposed), + (list, ModelComposed), + (dict, ModelNormal), + (list, ModelNormal), + (str, ModelSimple), + (int, ModelSimple), + (float, ModelSimple), + (list, ModelSimple), + # (str, int), + # (str, float), + (str, datetime), + (str, date), + # (int, str), + # (float, str), + (str, file_type) + ), +} + + +def get_simple_class(input_value): + """Returns an input_value's simple class that we will use for type checking + Python2: + float and int will return int, where int is the python3 int backport + str and unicode will return str, where str is the python3 str backport + Note: float and int ARE both instances of int backport + Note: str_py2 and unicode_py2 are NOT both instances of str backport + + Args: + input_value (class/class_instance): the item for which we will return + the simple class + """ + if isinstance(input_value, type): + # input_value is a class + return input_value + elif isinstance(input_value, tuple): + return tuple + elif isinstance(input_value, list): + return list + elif isinstance(input_value, dict): + return dict + elif isinstance(input_value, none_type): + return none_type + elif isinstance(input_value, file_type): + return file_type + elif isinstance(input_value, bool): + # this must be higher than the int check because + # isinstance(True, int) == True + return bool + elif isinstance(input_value, int): + return int + elif isinstance(input_value, datetime): + # this must be higher than the date check because + # isinstance(datetime_instance, date) == True + return datetime + elif isinstance(input_value, date): + return date + elif isinstance(input_value, str): + return str + return type(input_value) + + +def check_allowed_values(allowed_values, input_variable_path, input_values): + """Raises an exception if the input_values are not allowed + + Args: + allowed_values (dict): the allowed_values dict + input_variable_path (tuple): the path to the input variable + input_values (list/str/int/float/date/datetime): the values that we + are checking to see if they are in allowed_values + """ + these_allowed_values = list(allowed_values[input_variable_path].values()) + if (isinstance(input_values, list) + and not set(input_values).issubset( + set(these_allowed_values))): + invalid_values = ", ".join( + map(str, set(input_values) - set(these_allowed_values))), + raise ApiValueError( + "Invalid values for `%s` [%s], must be a subset of [%s]" % + ( + input_variable_path[0], + invalid_values, + ", ".join(map(str, these_allowed_values)) + ) + ) + elif (isinstance(input_values, dict) + and not set( + input_values.keys()).issubset(set(these_allowed_values))): + invalid_values = ", ".join( + map(str, set(input_values.keys()) - set(these_allowed_values))) + raise ApiValueError( + "Invalid keys in `%s` [%s], must be a subset of [%s]" % + ( + input_variable_path[0], + invalid_values, + ", ".join(map(str, these_allowed_values)) + ) + ) + elif (not isinstance(input_values, (list, dict)) + and input_values not in these_allowed_values): + raise ApiValueError( + "Invalid value for `%s` (%s), must be one of %s" % + ( + input_variable_path[0], + input_values, + these_allowed_values + ) + ) + + +def is_json_validation_enabled(schema_keyword, configuration=None): + """Returns true if JSON schema validation is enabled for the specified + validation keyword. This can be used to skip JSON schema structural validation + as requested in the configuration. + + Args: + schema_keyword (string): the name of a JSON schema validation keyword. + configuration (Configuration): the configuration class. + """ + + return (configuration is None or + not hasattr(configuration, '_disabled_client_side_validations') or + schema_keyword not in configuration._disabled_client_side_validations) + + +def check_validations( + validations, input_variable_path, input_values, + configuration=None): + """Raises an exception if the input_values are invalid + + Args: + validations (dict): the validation dictionary. + input_variable_path (tuple): the path to the input variable. + input_values (list/str/int/float/date/datetime): the values that we + are checking. + configuration (Configuration): the configuration class. + """ + + if input_values is None: + return + + current_validations = validations[input_variable_path] + if (is_json_validation_enabled('multipleOf', configuration) and + 'multiple_of' in current_validations and + isinstance(input_values, (int, float)) and + not (float(input_values) / current_validations['multiple_of']).is_integer()): + # Note 'multipleOf' will be as good as the floating point arithmetic. + raise ApiValueError( + "Invalid value for `%s`, value must be a multiple of " + "`%s`" % ( + input_variable_path[0], + current_validations['multiple_of'] + ) + ) + + if (is_json_validation_enabled('maxLength', configuration) and + 'max_length' in current_validations and + len(input_values) > current_validations['max_length']): + raise ApiValueError( + "Invalid value for `%s`, length must be less than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['max_length'] + ) + ) + + if (is_json_validation_enabled('minLength', configuration) and + 'min_length' in current_validations and + len(input_values) < current_validations['min_length']): + raise ApiValueError( + "Invalid value for `%s`, length must be greater than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['min_length'] + ) + ) + + if (is_json_validation_enabled('maxItems', configuration) and + 'max_items' in current_validations and + len(input_values) > current_validations['max_items']): + raise ApiValueError( + "Invalid value for `%s`, number of items must be less than or " + "equal to `%s`" % ( + input_variable_path[0], + current_validations['max_items'] + ) + ) + + if (is_json_validation_enabled('minItems', configuration) and + 'min_items' in current_validations and + len(input_values) < current_validations['min_items']): + raise ValueError( + "Invalid value for `%s`, number of items must be greater than or " + "equal to `%s`" % ( + input_variable_path[0], + current_validations['min_items'] + ) + ) + + items = ('exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum', + 'inclusive_minimum') + if (any(item in current_validations for item in items)): + if isinstance(input_values, list): + max_val = max(input_values) + min_val = min(input_values) + elif isinstance(input_values, dict): + max_val = max(input_values.values()) + min_val = min(input_values.values()) + else: + max_val = input_values + min_val = input_values + + if (is_json_validation_enabled('exclusiveMaximum', configuration) and + 'exclusive_maximum' in current_validations and + max_val >= current_validations['exclusive_maximum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value less than `%s`" % ( + input_variable_path[0], + current_validations['exclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('maximum', configuration) and + 'inclusive_maximum' in current_validations and + max_val > current_validations['inclusive_maximum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value less than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['inclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('exclusiveMinimum', configuration) and + 'exclusive_minimum' in current_validations and + min_val <= current_validations['exclusive_minimum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value greater than `%s`" % + ( + input_variable_path[0], + current_validations['exclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('minimum', configuration) and + 'inclusive_minimum' in current_validations and + min_val < current_validations['inclusive_minimum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value greater than or equal " + "to `%s`" % ( + input_variable_path[0], + current_validations['inclusive_minimum'] + ) + ) + flags = current_validations.get('regex', {}).get('flags', 0) + if (is_json_validation_enabled('pattern', configuration) and + 'regex' in current_validations and + not re.search(current_validations['regex']['pattern'], + input_values, flags=flags)): + err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % ( + input_variable_path[0], + current_validations['regex']['pattern'] + ) + if flags != 0: + # Don't print the regex flags if the flags are not + # specified in the OAS document. + err_msg = r"%s with flags=`%s`" % (err_msg, flags) + raise ApiValueError(err_msg) + + +def order_response_types(required_types): + """Returns the required types sorted in coercion order + + Args: + required_types (list/tuple): collection of classes or instance of + list or dict with class information inside it. + + Returns: + (list): coercion order sorted collection of classes or instance + of list or dict with class information inside it. + """ + + def index_getter(class_or_instance): + if isinstance(class_or_instance, list): + return COERCION_INDEX_BY_TYPE[list] + elif isinstance(class_or_instance, dict): + return COERCION_INDEX_BY_TYPE[dict] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelComposed)): + return COERCION_INDEX_BY_TYPE[ModelComposed] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelNormal)): + return COERCION_INDEX_BY_TYPE[ModelNormal] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelSimple)): + return COERCION_INDEX_BY_TYPE[ModelSimple] + elif class_or_instance in COERCION_INDEX_BY_TYPE: + return COERCION_INDEX_BY_TYPE[class_or_instance] + raise ApiValueError("Unsupported type: %s" % class_or_instance) + + sorted_types = sorted( + required_types, + key=lambda class_or_instance: index_getter(class_or_instance) + ) + return sorted_types + + +def remove_uncoercible(required_types_classes, current_item, spec_property_naming, + must_convert=True): + """Only keeps the type conversions that are possible + + Args: + required_types_classes (tuple): tuple of classes that are required + these should be ordered by COERCION_INDEX_BY_TYPE + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + current_item (any): the current item (input data) to be converted + + Keyword Args: + must_convert (bool): if True the item to convert is of the wrong + type and we want a big list of coercibles + if False, we want a limited list of coercibles + + Returns: + (list): the remaining coercible required types, classes only + """ + current_type_simple = get_simple_class(current_item) + + results_classes = [] + for required_type_class in required_types_classes: + # convert our models to OpenApiModel + required_type_class_simplified = required_type_class + if isinstance(required_type_class_simplified, type): + if issubclass(required_type_class_simplified, ModelComposed): + required_type_class_simplified = ModelComposed + elif issubclass(required_type_class_simplified, ModelNormal): + required_type_class_simplified = ModelNormal + elif issubclass(required_type_class_simplified, ModelSimple): + required_type_class_simplified = ModelSimple + + if required_type_class_simplified == current_type_simple: + # don't consider converting to one's own class + continue + + class_pair = (current_type_simple, required_type_class_simplified) + if must_convert and class_pair in COERCIBLE_TYPE_PAIRS[spec_property_naming]: + results_classes.append(required_type_class) + elif class_pair in UPCONVERSION_TYPE_PAIRS: + results_classes.append(required_type_class) + return results_classes + +def get_discriminated_classes(cls): + """ + Returns all the classes that a discriminator converts to + TODO: lru_cache this + """ + possible_classes = [] + key = list(cls.discriminator.keys())[0] + if is_type_nullable(cls): + possible_classes.append(cls) + for discr_cls in cls.discriminator[key].values(): + if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None: + possible_classes.extend(get_discriminated_classes(discr_cls)) + else: + possible_classes.append(discr_cls) + return possible_classes + + +def get_possible_classes(cls, from_server_context): + # TODO: lru_cache this + possible_classes = [cls] + if from_server_context: + return possible_classes + if hasattr(cls, 'discriminator') and cls.discriminator is not None: + possible_classes = [] + possible_classes.extend(get_discriminated_classes(cls)) + elif issubclass(cls, ModelComposed): + possible_classes.extend(composed_model_input_classes(cls)) + return possible_classes + + +def get_required_type_classes(required_types_mixed, spec_property_naming): + """Converts the tuple required_types into a tuple and a dict described + below + + Args: + required_types_mixed (tuple/list): will contain either classes or + instance of list or dict + spec_property_naming (bool): if True these values came from the + server, and we use the data types in our endpoints. + If False, we are client side and we need to include + oneOf and discriminator classes inside the data types in our endpoints + + Returns: + (valid_classes, dict_valid_class_to_child_types_mixed): + valid_classes (tuple): the valid classes that the current item + should be + dict_valid_class_to_child_types_mixed (dict): + valid_class (class): this is the key + child_types_mixed (list/dict/tuple): describes the valid child + types + """ + valid_classes = [] + child_req_types_by_current_type = {} + for required_type in required_types_mixed: + if isinstance(required_type, list): + valid_classes.append(list) + child_req_types_by_current_type[list] = required_type + elif isinstance(required_type, tuple): + valid_classes.append(tuple) + child_req_types_by_current_type[tuple] = required_type + elif isinstance(required_type, dict): + valid_classes.append(dict) + child_req_types_by_current_type[dict] = required_type[str] + else: + valid_classes.extend(get_possible_classes(required_type, spec_property_naming)) + return tuple(valid_classes), child_req_types_by_current_type + + +def change_keys_js_to_python(input_dict, model_class): + """ + Converts from javascript_key keys in the input_dict to python_keys in + the output dict using the mapping in model_class. + If the input_dict contains a key which does not declared in the model_class, + the key is added to the output dict as is. The assumption is the model_class + may have undeclared properties (additionalProperties attribute in the OAS + document). + """ + + if getattr(model_class, 'attribute_map', None) is None: + return input_dict + output_dict = {} + reversed_attr_map = {value: key for key, value in + model_class.attribute_map.items()} + for javascript_key, value in input_dict.items(): + python_key = reversed_attr_map.get(javascript_key) + if python_key is None: + # if the key is unknown, it is in error or it is an + # additionalProperties variable + python_key = javascript_key + output_dict[python_key] = value + return output_dict + + +def get_type_error(var_value, path_to_item, valid_classes, key_type=False): + error_msg = type_error_message( + var_name=path_to_item[-1], + var_value=var_value, + valid_classes=valid_classes, + key_type=key_type + ) + return ApiTypeError( + error_msg, + path_to_item=path_to_item, + valid_classes=valid_classes, + key_type=key_type + ) + + +def deserialize_primitive(data, klass, path_to_item): + """Deserializes string to primitive type. + + :param data: str/int/float + :param klass: str/class the class to convert to + + :return: int, float, str, bool, date, datetime + """ + additional_message = "" + try: + if klass in {datetime, date}: + additional_message = ( + "If you need your parameter to have a fallback " + "string value, please set its type as `type: {}` in your " + "spec. That allows the value to be any type. " + ) + if klass == datetime: + if len(data) < 8: + raise ValueError("This is not a datetime") + # The string should be in iso8601 datetime format. + parsed_datetime = parse(data) + date_only = ( + parsed_datetime.hour == 0 and + parsed_datetime.minute == 0 and + parsed_datetime.second == 0 and + parsed_datetime.tzinfo is None and + 8 <= len(data) <= 10 + ) + if date_only: + raise ValueError("This is a date, not a datetime") + return parsed_datetime + elif klass == date: + if len(data) < 8: + raise ValueError("This is not a date") + return parse(data).date() + else: + converted_value = klass(data) + if isinstance(data, str) and klass == float: + if str(converted_value) != data: + # '7' -> 7.0 -> '7.0' != '7' + raise ValueError('This is not a float') + return converted_value + except (OverflowError, ValueError) as ex: + # parse can raise OverflowError + raise ApiValueError( + "{0}Failed to parse {1} as {2}".format( + additional_message, repr(data), klass.__name__ + ), + path_to_item=path_to_item + ) from ex + + +def get_discriminator_class(model_class, + discr_name, + discr_value, cls_visited): + """Returns the child class specified by the discriminator. + + Args: + model_class (OpenApiModel): the model class. + discr_name (string): the name of the discriminator property. + discr_value (any): the discriminator value. + cls_visited (list): list of model classes that have been visited. + Used to determine the discriminator class without + visiting circular references indefinitely. + + Returns: + used_model_class (class/None): the chosen child class that will be used + to deserialize the data, for example dog.Dog. + If a class is not found, None is returned. + """ + + if model_class in cls_visited: + # The class has already been visited and no suitable class was found. + return None + cls_visited.append(model_class) + used_model_class = None + if discr_name in model_class.discriminator: + class_name_to_discr_class = model_class.discriminator[discr_name] + used_model_class = class_name_to_discr_class.get(discr_value) + if used_model_class is None: + # We didn't find a discriminated class in class_name_to_discr_class. + # So look in the ancestor or descendant discriminators + # The discriminator mapping may exist in a descendant (anyOf, oneOf) + # or ancestor (allOf). + # Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat + # hierarchy, the discriminator mappings may be defined at any level + # in the hierarchy. + # Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig + # if we try to make BasquePig from mammal, we need to travel through + # the oneOf descendant discriminators to find BasquePig + descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \ + model_class._composed_schemas.get('anyOf', ()) + ancestor_classes = model_class._composed_schemas.get('allOf', ()) + possible_classes = descendant_classes + ancestor_classes + for cls in possible_classes: + # Check if the schema has inherited discriminators. + if hasattr(cls, 'discriminator') and cls.discriminator is not None: + used_model_class = get_discriminator_class( + cls, discr_name, discr_value, cls_visited) + if used_model_class is not None: + return used_model_class + return used_model_class + + +def deserialize_model(model_data, model_class, path_to_item, check_type, + configuration, spec_property_naming): + """Deserializes model_data to model instance. + + Args: + model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model + model_class (OpenApiModel): the model class + path_to_item (list): path to the model in the received data + check_type (bool): whether to check the data tupe for the values in + the model + configuration (Configuration): the instance to use to convert files + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + + Returns: + model instance + + Raise: + ApiTypeError + ApiValueError + ApiKeyError + """ + + kw_args = dict(_check_type=check_type, + _path_to_item=path_to_item, + _configuration=configuration, + _spec_property_naming=spec_property_naming) + + if issubclass(model_class, ModelSimple): + return model_class._new_from_openapi_data(model_data, **kw_args) + elif isinstance(model_data, list): + return model_class._new_from_openapi_data(*model_data, **kw_args) + if isinstance(model_data, dict): + kw_args.update(model_data) + return model_class._new_from_openapi_data(**kw_args) + elif isinstance(model_data, PRIMITIVE_TYPES): + return model_class._new_from_openapi_data(model_data, **kw_args) + + +def deserialize_file(response_data, configuration, content_disposition=None): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + Args: + param response_data (str): the file data to write + configuration (Configuration): the instance to use to convert files + + Keyword Args: + content_disposition (str): the value of the Content-Disposition + header + + Returns: + (file_type): the deserialized file which is open + The user is responsible for closing and reading the file + """ + fd, path = tempfile.mkstemp(dir=configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition).group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + if isinstance(response_data, str): + # change str to bytes so we can write it + response_data = response_data.encode('utf-8') + f.write(response_data) + + f = open(path, "rb") + return f + + +def attempt_convert_item(input_value, valid_classes, path_to_item, + configuration, spec_property_naming, key_type=False, + must_convert=False, check_type=True): + """ + Args: + input_value (any): the data to convert + valid_classes (any): the classes that are valid + path_to_item (list): the path to the item to convert + configuration (Configuration): the instance to use to convert files + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + key_type (bool): if True we need to convert a key type (not supported) + must_convert (bool): if True we must convert + check_type (bool): if True we check the type or the returned data in + ModelComposed/ModelNormal/ModelSimple instances + + Returns: + instance (any) the fixed item + + Raises: + ApiTypeError + ApiValueError + ApiKeyError + """ + valid_classes_ordered = order_response_types(valid_classes) + valid_classes_coercible = remove_uncoercible( + valid_classes_ordered, input_value, spec_property_naming) + if not valid_classes_coercible or key_type: + # we do not handle keytype errors, json will take care + # of this for us + if configuration is None or not configuration.discard_unknown_keys: + raise get_type_error(input_value, path_to_item, valid_classes, + key_type=key_type) + for valid_class in valid_classes_coercible: + try: + if issubclass(valid_class, OpenApiModel): + return deserialize_model(input_value, valid_class, + path_to_item, check_type, + configuration, spec_property_naming) + elif valid_class == file_type: + return deserialize_file(input_value, configuration) + return deserialize_primitive(input_value, valid_class, + path_to_item) + except (ApiTypeError, ApiValueError, ApiKeyError) as conversion_exc: + if must_convert: + raise conversion_exc + # if we have conversion errors when must_convert == False + # we ignore the exception and move on to the next class + continue + # we were unable to convert, must_convert == False + return input_value + + +def is_type_nullable(input_type): + """ + Returns true if None is an allowed value for the specified input_type. + + A type is nullable if at least one of the following conditions is true: + 1. The OAS 'nullable' attribute has been specified, + 1. The type is the 'null' type, + 1. The type is a anyOf/oneOf composed schema, and a child schema is + the 'null' type. + Args: + input_type (type): the class of the input_value that we are + checking + Returns: + bool + """ + if input_type is none_type: + return True + if issubclass(input_type, OpenApiModel) and input_type._nullable: + return True + if issubclass(input_type, ModelComposed): + # If oneOf/anyOf, check if the 'null' type is one of the allowed types. + for t in input_type._composed_schemas.get('oneOf', ()): + if is_type_nullable(t): return True + for t in input_type._composed_schemas.get('anyOf', ()): + if is_type_nullable(t): return True + return False + + +def is_valid_type(input_class_simple, valid_classes): + """ + Args: + input_class_simple (class): the class of the input_value that we are + checking + valid_classes (tuple): the valid classes that the current item + should be + Returns: + bool + """ + if issubclass(input_class_simple, OpenApiModel) and \ + valid_classes == (bool, date, datetime, dict, float, int, list, str, none_type,): + return True + valid_type = input_class_simple in valid_classes + if not valid_type and ( + issubclass(input_class_simple, OpenApiModel) or + input_class_simple is none_type): + for valid_class in valid_classes: + if input_class_simple is none_type and is_type_nullable(valid_class): + # Schema is oneOf/anyOf and the 'null' type is one of the allowed types. + return True + if not (issubclass(valid_class, OpenApiModel) and valid_class.discriminator): + continue + discr_propertyname_py = list(valid_class.discriminator.keys())[0] + discriminator_classes = ( + valid_class.discriminator[discr_propertyname_py].values() + ) + valid_type = is_valid_type(input_class_simple, discriminator_classes) + if valid_type: + return True + return valid_type + + +def validate_and_convert_types(input_value, required_types_mixed, path_to_item, + spec_property_naming, _check_type, configuration=None): + """Raises a TypeError is there is a problem, otherwise returns value + + Args: + input_value (any): the data to validate/convert + required_types_mixed (list/dict/tuple): A list of + valid classes, or a list tuples of valid classes, or a dict where + the value is a tuple of value classes + path_to_item: (list) the path to the data being validated + this stores a list of keys or indices to get to the data being + validated + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + _check_type: (boolean) if true, type will be checked and conversion + will be attempted. + configuration: (Configuration): the configuration class to use + when converting file_type items. + If passed, conversion will be attempted when possible + If not passed, no conversions will be attempted and + exceptions will be raised + + Returns: + the correctly typed value + + Raises: + ApiTypeError + """ + results = get_required_type_classes(required_types_mixed, spec_property_naming) + valid_classes, child_req_types_by_current_type = results + + input_class_simple = get_simple_class(input_value) + valid_type = is_valid_type(input_class_simple, valid_classes) + if not valid_type: + if configuration: + # if input_value is not valid_type try to convert it + converted_instance = attempt_convert_item( + input_value, + valid_classes, + path_to_item, + configuration, + spec_property_naming, + key_type=False, + must_convert=True, + check_type=_check_type + ) + return converted_instance + else: + raise get_type_error(input_value, path_to_item, valid_classes, + key_type=False) + + # input_value's type is in valid_classes + if len(valid_classes) > 1 and configuration: + # there are valid classes which are not the current class + valid_classes_coercible = remove_uncoercible( + valid_classes, input_value, spec_property_naming, must_convert=False) + if valid_classes_coercible: + converted_instance = attempt_convert_item( + input_value, + valid_classes_coercible, + path_to_item, + configuration, + spec_property_naming, + key_type=False, + must_convert=False, + check_type=_check_type + ) + return converted_instance + + if child_req_types_by_current_type == {}: + # all types are of the required types and there are no more inner + # variables left to look at + return input_value + inner_required_types = child_req_types_by_current_type.get( + type(input_value) + ) + if inner_required_types is None: + # for this type, there are not more inner variables left to look at + return input_value + if isinstance(input_value, list): + if input_value == []: + # allow an empty list + return input_value + for index, inner_value in enumerate(input_value): + inner_path = list(path_to_item) + inner_path.append(index) + input_value[index] = validate_and_convert_types( + inner_value, + inner_required_types, + inner_path, + spec_property_naming, + _check_type, + configuration=configuration + ) + elif isinstance(input_value, dict): + if input_value == {}: + # allow an empty dict + return input_value + for inner_key, inner_val in input_value.items(): + inner_path = list(path_to_item) + inner_path.append(inner_key) + if get_simple_class(inner_key) != str: + raise get_type_error(inner_key, inner_path, valid_classes, + key_type=True) + input_value[inner_key] = validate_and_convert_types( + inner_val, + inner_required_types, + inner_path, + spec_property_naming, + _check_type, + configuration=configuration + ) + return input_value + + +def model_to_dict(model_instance, serialize=True): + """Returns the model properties as a dict + + Args: + model_instance (one of your model instances): the model instance that + will be converted to a dict. + + Keyword Args: + serialize (bool): if True, the keys in the dict will be values from + attribute_map + """ + result = {} + extract_item = lambda item: (item[0], model_to_dict(item[1], serialize=serialize)) if hasattr(item[1], '_data_store') else item + + model_instances = [model_instance] + if model_instance._composed_schemas: + model_instances.extend(model_instance._composed_instances) + seen_json_attribute_names = set() + used_fallback_python_attribute_names = set() + py_to_json_map = {} + for model_instance in model_instances: + for attr, value in model_instance._data_store.items(): + if serialize: + # we use get here because additional property key names do not + # exist in attribute_map + try: + attr = model_instance.attribute_map[attr] + py_to_json_map.update(model_instance.attribute_map) + seen_json_attribute_names.add(attr) + except KeyError: + used_fallback_python_attribute_names.add(attr) + if isinstance(value, list): + if not value: + # empty list or None + result[attr] = value + else: + res = [] + for v in value: + if isinstance(v, PRIMITIVE_TYPES) or v is None: + res.append(v) + elif isinstance(v, ModelSimple): + res.append(v.value) + elif isinstance(v, dict): + res.append(dict(map( + extract_item, + v.items() + ))) + else: + res.append(model_to_dict(v, serialize=serialize)) + result[attr] = res + elif isinstance(value, dict): + result[attr] = dict(map( + extract_item, + value.items() + )) + elif isinstance(value, ModelSimple): + result[attr] = value.value + elif hasattr(value, '_data_store'): + result[attr] = model_to_dict(value, serialize=serialize) + else: + result[attr] = value + if serialize: + for python_key in used_fallback_python_attribute_names: + json_key = py_to_json_map.get(python_key) + if json_key is None: + continue + if python_key == json_key: + continue + json_key_assigned_no_need_for_python_key = json_key in seen_json_attribute_names + if json_key_assigned_no_need_for_python_key: + del result[python_key] + + return result + + +def type_error_message(var_value=None, var_name=None, valid_classes=None, + key_type=None): + """ + Keyword Args: + var_value (any): the variable which has the type_error + var_name (str): the name of the variable which has the typ error + valid_classes (tuple): the accepted classes for current_item's + value + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + """ + key_or_value = 'value' + if key_type: + key_or_value = 'key' + valid_classes_phrase = get_valid_classes_phrase(valid_classes) + msg = ( + "Invalid type for variable '{0}'. Required {1} type {2} and " + "passed type was {3}".format( + var_name, + key_or_value, + valid_classes_phrase, + type(var_value).__name__, + ) + ) + return msg + + +def get_valid_classes_phrase(input_classes): + """Returns a string phrase describing what types are allowed + """ + all_classes = list(input_classes) + all_classes = sorted(all_classes, key=lambda cls: cls.__name__) + all_class_names = [cls.__name__ for cls in all_classes] + if len(all_class_names) == 1: + return 'is {0}'.format(all_class_names[0]) + return "is one of [{0}]".format(", ".join(all_class_names)) + + +def get_allof_instances(self, model_args, constant_args): + """ + Args: + self: the class we are handling + model_args (dict): var_name to var_value + used to make instances + constant_args (dict): + metadata arguments: + _check_type + _path_to_item + _spec_property_naming + _configuration + _visited_composed_classes + + Returns + composed_instances (list) + """ + composed_instances = [] + for allof_class in self._composed_schemas['allOf']: + + try: + if constant_args.get('_spec_property_naming'): + allof_instance = allof_class._from_openapi_data(**model_args, **constant_args) + else: + allof_instance = allof_class(**model_args, **constant_args) + composed_instances.append(allof_instance) + except Exception as ex: + raise ApiValueError( + "Invalid inputs given to generate an instance of '%s'. The " + "input data was invalid for the allOf schema '%s' in the composed " + "schema '%s'. Error=%s" % ( + allof_class.__name__, + allof_class.__name__, + self.__class__.__name__, + str(ex) + ) + ) from ex + return composed_instances + + +def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): + """ + Find the oneOf schema that matches the input data (e.g. payload). + If exactly one schema matches the input data, an instance of that schema + is returned. + If zero or more than one schema match the input data, an exception is raised. + In OAS 3.x, the payload MUST, by validation, match exactly one of the + schemas described by oneOf. + + Args: + cls: the class we are handling + model_kwargs (dict): var_name to var_value + The input data, e.g. the payload that must match a oneOf schema + in the OpenAPI document. + constant_kwargs (dict): var_name to var_value + args that every model requires, including configuration, server + and path to item. + + Kwargs: + model_arg: (int, float, bool, str, date, datetime, ModelSimple, None): + the value to assign to a primitive class or ModelSimple class + Notes: + - this is only passed in when oneOf includes types which are not object + - None is used to suppress handling of model_arg, nullable models are handled in __new__ + + Returns + oneof_instance (instance) + """ + if len(cls._composed_schemas['oneOf']) == 0: + return None + + oneof_instances = [] + # Iterate over each oneOf schema and determine if the input data + # matches the oneOf schemas. + for oneof_class in cls._composed_schemas['oneOf']: + # The composed oneOf schema allows the 'null' type and the input data + # is the null value. This is a OAS >= 3.1 feature. + if oneof_class is none_type: + # skip none_types because we are deserializing dict data. + # none_type deserialization is handled in the __new__ method + continue + + single_value_input = allows_single_value_input(oneof_class) + + try: + if not single_value_input: + if constant_kwargs.get('_spec_property_naming'): + oneof_instance = oneof_class._from_openapi_data(**model_kwargs, **constant_kwargs) + else: + oneof_instance = oneof_class(**model_kwargs, **constant_kwargs) + else: + if issubclass(oneof_class, ModelSimple): + if constant_kwargs.get('_spec_property_naming'): + oneof_instance = oneof_class._from_openapi_data(model_arg, **constant_kwargs) + else: + oneof_instance = oneof_class(model_arg, **constant_kwargs) + elif oneof_class in PRIMITIVE_TYPES: + oneof_instance = validate_and_convert_types( + model_arg, + (oneof_class,), + constant_kwargs['_path_to_item'], + constant_kwargs['_spec_property_naming'], + constant_kwargs['_check_type'], + configuration=constant_kwargs['_configuration'] + ) + oneof_instances.append(oneof_instance) + except Exception: + pass + if len(oneof_instances) == 0: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. None " + "of the oneOf schemas matched the input data." % + cls.__name__ + ) + elif len(oneof_instances) > 1: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. Multiple " + "oneOf schemas matched the inputs, but a max of one is allowed." % + cls.__name__ + ) + return oneof_instances[0] + + +def get_anyof_instances(self, model_args, constant_args): + """ + Args: + self: the class we are handling + model_args (dict): var_name to var_value + The input data, e.g. the payload that must match at least one + anyOf child schema in the OpenAPI document. + constant_args (dict): var_name to var_value + args that every model requires, including configuration, server + and path to item. + + Returns + anyof_instances (list) + """ + anyof_instances = [] + if len(self._composed_schemas['anyOf']) == 0: + return anyof_instances + + for anyof_class in self._composed_schemas['anyOf']: + # The composed oneOf schema allows the 'null' type and the input data + # is the null value. This is a OAS >= 3.1 feature. + if anyof_class is none_type: + # skip none_types because we are deserializing dict data. + # none_type deserialization is handled in the __new__ method + continue + + try: + if constant_args.get('_spec_property_naming'): + anyof_instance = anyof_class._from_openapi_data(**model_args, **constant_args) + else: + anyof_instance = anyof_class(**model_args, **constant_args) + anyof_instances.append(anyof_instance) + except Exception: + pass + if len(anyof_instances) == 0: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. None of the " + "anyOf schemas matched the inputs." % + self.__class__.__name__ + ) + return anyof_instances + + +def get_discarded_args(self, composed_instances, model_args): + """ + Gathers the args that were discarded by configuration.discard_unknown_keys + """ + model_arg_keys = model_args.keys() + discarded_args = set() + # arguments passed to self were already converted to python names + # before __init__ was called + for instance in composed_instances: + if instance.__class__ in self._composed_schemas['allOf']: + try: + keys = instance.to_dict().keys() + discarded_keys = model_args - keys + discarded_args.update(discarded_keys) + except Exception: + # allOf integer schema will throw exception + pass + else: + try: + all_keys = set(model_to_dict(instance, serialize=False).keys()) + js_keys = model_to_dict(instance, serialize=True).keys() + all_keys.update(js_keys) + discarded_keys = model_arg_keys - all_keys + discarded_args.update(discarded_keys) + except Exception: + # allOf integer schema will throw exception + pass + return discarded_args + + +def validate_get_composed_info(constant_args, model_args, self): + """ + For composed schemas, generate schema instances for + all schemas in the oneOf/anyOf/allOf definition. If additional + properties are allowed, also assign those properties on + all matched schemas that contain additionalProperties. + Openapi schemas are python classes. + + Exceptions are raised if: + - 0 or > 1 oneOf schema matches the model_args input data + - no anyOf schema matches the model_args input data + - any of the allOf schemas do not match the model_args input data + + Args: + constant_args (dict): these are the args that every model requires + model_args (dict): these are the required and optional spec args that + were passed in to make this model + self (class): the class that we are instantiating + This class contains self._composed_schemas + + Returns: + composed_info (list): length three + composed_instances (list): the composed instances which are not + self + var_name_to_model_instances (dict): a dict going from var_name + to the model_instance which holds that var_name + the model_instance may be self or an instance of one of the + classes in self.composed_instances() + additional_properties_model_instances (list): a list of the + model instances which have the property + additional_properties_type. This list can include self + """ + # create composed_instances + composed_instances = [] + allof_instances = get_allof_instances(self, model_args, constant_args) + composed_instances.extend(allof_instances) + oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args) + if oneof_instance is not None: + composed_instances.append(oneof_instance) + anyof_instances = get_anyof_instances(self, model_args, constant_args) + composed_instances.extend(anyof_instances) + """ + set additional_properties_model_instances + additional properties must be evaluated at the schema level + so self's additional properties are most important + If self is a composed schema with: + - no properties defined in self + - additionalProperties: False + Then for object payloads every property is an additional property + and they are not allowed, so only empty dict is allowed + + Properties must be set on all matching schemas + so when a property is assigned toa composed instance, it must be set on all + composed instances regardless of additionalProperties presence + keeping it to prevent breaking changes in v5.0.1 + TODO remove cls._additional_properties_model_instances in 6.0.0 + """ + additional_properties_model_instances = [] + if self.additional_properties_type is not None: + additional_properties_model_instances = [self] + + """ + no need to set properties on self in here, they will be set in __init__ + By here all composed schema oneOf/anyOf/allOf instances have their properties set using + model_args + """ + discarded_args = get_discarded_args(self, composed_instances, model_args) + + # map variable names to composed_instances + var_name_to_model_instances = {} + for prop_name in model_args: + if prop_name not in discarded_args: + var_name_to_model_instances[prop_name] = [self] + composed_instances + + return [ + composed_instances, + var_name_to_model_instances, + additional_properties_model_instances, + discarded_args + ] diff --git a/openapi/python_client_urllib3_templates/partial_header.mustache b/openapi/python_client_urllib3_templates/partial_header.mustache new file mode 100644 index 00000000..dc3c8f3d --- /dev/null +++ b/openapi/python_client_urllib3_templates/partial_header.mustache @@ -0,0 +1,17 @@ +""" +{{#appName}} + {{{.}}} +{{/appName}} + +{{#appDescription}} + {{{.}}} # noqa: E501 +{{/appDescription}} + + {{#version}} + The version of the OpenAPI document: {{{.}}} + {{/version}} + {{#infoEmail}} + Contact: {{{.}}} + {{/infoEmail}} + Generated by: https://openapi-generator.tech +""" diff --git a/openapi/python_explerimental_client_template/doc_auth_partial.handlebars b/openapi/python_client_urllib3_templates/python_doc_auth_partial.mustache similarity index 92% rename from openapi/python_explerimental_client_template/doc_auth_partial.handlebars rename to openapi/python_client_urllib3_templates/python_doc_auth_partial.mustache index b16451d8..5106632d 100644 --- a/openapi/python_explerimental_client_template/doc_auth_partial.handlebars +++ b/openapi/python_client_urllib3_templates/python_doc_auth_partial.mustache @@ -4,29 +4,29 @@ configuration = {{{packageName}}}.Configuration( host = "{{{basePath}}}" ) -{{#if hasAuthMethods}} +{{#hasAuthMethods}} # The client must configure the authentication and authorization parameters # in accordance with the API server security policy. # Examples for each auth method are provided below, use the example that # satisfies your auth use case. -{{#each authMethods}} -{{#if isBasic}} -{{#if isBasicBasic}} +{{#authMethods}} +{{#isBasic}} +{{#isBasicBasic}} # Configure HTTP basic authorization: {{{name}}} configuration = {{{packageName}}}.Configuration( username = 'YOUR_USERNAME', password = 'YOUR_PASSWORD' ) -{{/if}} -{{#if isBasicBearer}} +{{/isBasicBasic}} +{{#isBasicBearer}} -# Configure Bearer authorization{{#if bearerFormat}} ({{{bearerFormat}}}){{/if}}: {{{name}}} +# Configure Bearer authorization{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}: {{{name}}} configuration = {{{packageName}}}.Configuration( access_token = 'YOUR_BEARER_TOKEN' ) -{{/if}} -{{#if isHttpSignature}} +{{/isBasicBearer}} +{{#isHttpSignature}} # Configure HTTP message signature: {{{name}}} # The HTTP Signature Header mechanism that can be used by a client to @@ -87,23 +87,23 @@ configuration = {{{packageName}}}.Configuration( signature_max_validity = datetime.timedelta(minutes=5) ) ) -{{/if}} -{{/if}} -{{#if isApiKey}} +{{/isHttpSignature}} +{{/isBasic}} +{{#isApiKey}} # Configure API key authorization: {{{name}}} configuration.api_key['{{{name}}}'] = 'YOUR_API_KEY' # Uncomment below to setup prefix (e.g. Bearer) for API key, if needed # configuration.api_key_prefix['{{name}}'] = 'Bearer' -{{/if}} -{{#if isOAuth}} +{{/isApiKey}} +{{#isOAuth}} # Configure OAuth2 access token for authorization: {{{name}}} configuration = {{{packageName}}}.Configuration( host = "{{{basePath}}}" ) configuration.access_token = 'YOUR_ACCESS_TOKEN' -{{/if}} -{{/each}} -{{/if}} +{{/isOAuth}} +{{/authMethods}} +{{/hasAuthMethods}} diff --git a/openapi/python_client_urllib3_templates/requirements.mustache b/openapi/python_client_urllib3_templates/requirements.mustache new file mode 100644 index 00000000..96947f60 --- /dev/null +++ b/openapi/python_client_urllib3_templates/requirements.mustache @@ -0,0 +1,3 @@ +python_dateutil >= 2.5.3 +setuptools >= 21.0.0 +urllib3 >= 1.25.3 diff --git a/openapi/python_client_urllib3_templates/rest.mustache b/openapi/python_client_urllib3_templates/rest.mustache new file mode 100644 index 00000000..d4293e70 --- /dev/null +++ b/openapi/python_client_urllib3_templates/rest.mustache @@ -0,0 +1,338 @@ +{{>partial_header}} + +import io +import json +import logging +import re +import ssl +from urllib.parse import urlencode +from urllib.parse import urlparse +from urllib.request import proxy_bypass_environment +import urllib3 +import ipaddress + +from {{packageName}}.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError + + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.urllib3_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = resp.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.urllib3_response.getheaders() + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.urllib3_response.getheader(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if configuration.retries is not None: + addition_pool_args['retries'] = configuration.retries + + if configuration.socket_options is not None: + addition_pool_args['socket_options'] = configuration.socket_options + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy and not should_bypass_proxies(configuration.host, no_proxy=configuration.no_proxy or ''): + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + proxy_headers=configuration.proxy_headers, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Perform requests. + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, float)): # noqa: E501,F821 + timeout = urllib3.Timeout(total=_request_timeout) + elif (isinstance(_request_timeout, tuple) and + len(_request_timeout) == 2): + timeout = urllib3.Timeout( + connect=_request_timeout[0], read=_request_timeout[1]) + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + # Only set a default Content-Type for POST, PUT, PATCH and OPTIONS requests + if (method != 'DELETE') and ('Content-Type' not in headers): + headers['Content-Type'] = 'application/json' + if query_params: + url += '?' + urlencode(query_params) + if ('Content-Type' not in headers) or (re.search('json', headers['Content-Type'], re.IGNORECASE)): + request_body = None + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=False, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=True, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str) or isinstance(body, bytes): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + fields=query_params, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + except urllib3.exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise ApiException(status=0, reason=msg) + + if _preload_content: + r = RESTResponse(r) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + if r.status == 401: + raise UnauthorizedException(http_resp=r) + + if r.status == 403: + raise ForbiddenException(http_resp=r) + + if r.status == 404: + raise NotFoundException(http_resp=r) + + if 500 <= r.status <= 599: + raise ServiceException(http_resp=r) + + raise ApiException(http_resp=r) + + return r + + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + +# end of class RESTClientObject +def is_ipv4(target): + """ Test if IPv4 address or not + """ + try: + chk = ipaddress.IPv4Address(target) + return True + except ipaddress.AddressValueError: + return False + +def in_ipv4net(target, net): + """ Test if target belongs to given IPv4 network + """ + try: + nw = ipaddress.IPv4Network(net) + ip = ipaddress.IPv4Address(target) + if ip in nw: + return True + return False + except ipaddress.AddressValueError: + return False + except ipaddress.NetmaskValueError: + return False + +def should_bypass_proxies(url, no_proxy=None): + """ Yet another requests.should_bypass_proxies + Test if proxies should not be used for a particular url. + """ + + parsed = urlparse(url) + + # special cases + if parsed.hostname in [None, '']: + return True + + # special cases + if no_proxy in [None , '']: + return False + if no_proxy == '*': + return True + + no_proxy = no_proxy.lower().replace(' ',''); + entries = ( + host for host in no_proxy.split(',') if host + ) + + if is_ipv4(parsed.hostname): + for item in entries: + if in_ipv4net(parsed.hostname, item): + return True + return proxy_bypass_environment(parsed.hostname, {'no': no_proxy} ) diff --git a/openapi/python_explerimental_client_template/setup.handlebars b/openapi/python_client_urllib3_templates/setup.mustache similarity index 51% rename from openapi/python_explerimental_client_template/setup.handlebars rename to openapi/python_client_urllib3_templates/setup.mustache index fb3e16a9..5f557825 100644 --- a/openapi/python_explerimental_client_template/setup.handlebars +++ b/openapi/python_client_urllib3_templates/setup.mustache @@ -1,12 +1,12 @@ -# coding: utf-8 - {{>partial_header}} -from setuptools import setup, find_namespace_packages # noqa: H301 +from setuptools import setup, find_packages # noqa: H301 NAME = "{{{projectName}}}" VERSION = "{{packageVersion}}" -{{#with apiInfo}} +{{#apiInfo}} +{{#apis}} +{{#-last}} # To install the library, run the following # # python setup.py install @@ -15,37 +15,37 @@ VERSION = "{{packageVersion}}" # http://pypi.python.org/pypi/setuptools REQUIRES = [ - "urllib3 >= 1.15", - "certifi", + "urllib3 >= 1.25.3", "python-dateutil", - "frozendict >= 2.0.3", -{{#if asyncio}} +{{#asyncio}} "aiohttp >= 3.0.0", -{{/if}} -{{#if tornado}} +{{/asyncio}} +{{#tornado}} "tornado>=4.2,<5", -{{/if}} -{{#if hasHttpSignatureMethods}} +{{/tornado}} +{{#hasHttpSignatureMethods}} "pem>=19.3.0", "pycryptodome>=3.9.0", -{{/if}} +{{/hasHttpSignatureMethods}} ] setup( name=NAME, version=VERSION, description="{{appName}}", - author="{{#if infoName}}{{infoName}}{{/if}}{{#unless infoName}}OpenAPI Generator community{{/unless}}", - author_email="{{#if infoEmail}}{{infoEmail}}{{/if}}{{#unless infoEmail}}team@openapitools.org{{/unless}}", + author="{{infoName}}{{^infoName}}OpenAPI Generator community{{/infoName}}", + author_email="{{infoEmail}}{{^infoEmail}}team@openapitools.org{{/infoEmail}}", url="{{packageUrl}}", keywords=["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"], python_requires="{{{generatorLanguageVersion}}}", install_requires=REQUIRES, - packages=find_namespace_packages(exclude=["test", "tests"]), + packages=find_packages(exclude=["test", "tests"]), include_package_data=True, - {{#if licenseInfo}}license="{{licenseInfo}}", - {{/if}}long_description="""\ + {{#licenseInfo}}license="{{.}}", + {{/licenseInfo}}long_description="""\ {{appDescription}} # noqa: E501 """ ) -{{/with}} +{{/-last}} +{{/apis}} +{{/apiInfo}} diff --git a/openapi/python_explerimental_client_template/setup_cfg.handlebars b/openapi/python_client_urllib3_templates/setup_cfg.mustache similarity index 86% rename from openapi/python_explerimental_client_template/setup_cfg.handlebars rename to openapi/python_client_urllib3_templates/setup_cfg.mustache index 8cb28d8c..931f02c5 100644 --- a/openapi/python_explerimental_client_template/setup_cfg.handlebars +++ b/openapi/python_client_urllib3_templates/setup_cfg.mustache @@ -1,4 +1,4 @@ -{{#if useNose}} +{{#useNose}} [nosetests] logging-clear-handlers=true verbosity=2 @@ -8,6 +8,6 @@ with-coverage=true cover-package={{{packageName}}} cover-erase=true -{{/if}} +{{/useNose}} [flake8] max-line-length=99 diff --git a/openapi/python_explerimental_client_template/signing.handlebars b/openapi/python_client_urllib3_templates/signing.mustache similarity index 99% rename from openapi/python_explerimental_client_template/signing.handlebars rename to openapi/python_client_urllib3_templates/signing.mustache index 26d2b8cb..e39866a5 100644 --- a/openapi/python_explerimental_client_template/signing.handlebars +++ b/openapi/python_client_urllib3_templates/signing.mustache @@ -1,4 +1,3 @@ -# coding: utf-8 {{>partial_header}} from base64 import b64encode @@ -334,7 +333,7 @@ class HttpSigningConfiguration(object): :return: A tuple of (digest, prefix). The digest is a hashing object that contains the cryptographic digest of the HTTP request. - The prefix is a string that identifies the cryptographc hash. It is used + The prefix is a string that identifies the cryptographic hash. It is used to generate the 'Digest' header as specified in RFC 3230. """ if self.hash_algorithm == HASH_SHA512: diff --git a/openapi/python_client_urllib3_templates/test-requirements.mustache b/openapi/python_client_urllib3_templates/test-requirements.mustache new file mode 100644 index 00000000..635b816e --- /dev/null +++ b/openapi/python_client_urllib3_templates/test-requirements.mustache @@ -0,0 +1,13 @@ +{{#useNose}} +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 +{{/useNose}} +{{^useNose}} +pytest-cov>=2.8.1 +{{/useNose}} +{{#hasHttpSignatureMethods}} +pycryptodome>=3.9.0 +{{/hasHttpSignatureMethods}} diff --git a/openapi/python_client_urllib3_templates/tornado/rest.mustache b/openapi/python_client_urllib3_templates/tornado/rest.mustache new file mode 100644 index 00000000..9e5cc120 --- /dev/null +++ b/openapi/python_client_urllib3_templates/tornado/rest.mustache @@ -0,0 +1,222 @@ +{{>partial_header}} + +import io +import json +import logging +import re + +# python 2 and python 3 compatibility library +from six.moves.urllib.parse import urlencode +import tornado +import tornado.gen +from tornado import httpclient +from urllib3.filepost import encode_multipart_formdata + +from {{packageName}}.exceptions import ApiException, ApiValueError + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.tornado_response = resp + self.status = resp.code + self.reason = resp.reason + + if resp.body: + self.data = resp.body + else: + self.data = None + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.tornado_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.tornado_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + + self.ca_certs = configuration.ssl_ca_cert + self.client_key = configuration.key_file + self.client_cert = configuration.cert_file + + self.proxy_port = self.proxy_host = None + + # https pool manager + if configuration.proxy: + self.proxy_port = 80 + self.proxy_host = configuration.proxy + + self.pool_manager = httpclient.AsyncHTTPClient() + + @tornado.gen.coroutine + def request(self, method, url, query_params=None, headers=None, body=None, + post_params=None, _preload_content=True, + _request_timeout=None): + """Execute Request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + request = httpclient.HTTPRequest(url) + request.allow_nonstandard_methods = True + request.ca_certs = self.ca_certs + request.client_key = self.client_key + request.client_cert = self.client_cert + request.proxy_host = self.proxy_host + request.proxy_port = self.proxy_port + request.method = method + if headers: + request.headers = headers + if 'Content-Type' not in headers: + request.headers['Content-Type'] = 'application/json' + request.request_timeout = _request_timeout or 5 * 60 + + post_params = post_params or {} + + if query_params: + request.url += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body: + body = json.dumps(body) + request.body = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + request.body = urlencode(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + multipart = encode_multipart_formdata(post_params) + request.body, headers['Content-Type'] = multipart + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + request.body = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + r = yield self.pool_manager.fetch(request, raise_error=False) + + if _preload_content: + + r = RESTResponse(r) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + raise tornado.gen.Return(r) + + @tornado.gen.coroutine + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + result = yield self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) diff --git a/openapi/python_client_urllib3_templates/tox.mustache b/openapi/python_client_urllib3_templates/tox.mustache new file mode 100644 index 00000000..4c771c47 --- /dev/null +++ b/openapi/python_client_urllib3_templates/tox.mustache @@ -0,0 +1,9 @@ +[tox] +envlist = py3 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + {{^useNose}}pytest --cov={{{packageName}}}{{/useNose}}{{#useNose}}nosetests{{/useNose}} diff --git a/openapi/python_explerimental_client_template/travis.handlebars b/openapi/python_client_urllib3_templates/travis.mustache similarity index 82% rename from openapi/python_explerimental_client_template/travis.handlebars rename to openapi/python_client_urllib3_templates/travis.mustache index 5e4e1f0c..a26c984f 100644 --- a/openapi/python_explerimental_client_template/travis.handlebars +++ b/openapi/python_client_urllib3_templates/travis.mustache @@ -1,18 +1,18 @@ # ref: https://docs.travis-ci.com/user/languages/python language: python python: - - "3.5" - "3.6" - "3.7" - "3.8" + - "3.9" # command to install dependencies install: - "pip install -r requirements.txt" - "pip install -r test-requirements.txt" # command to run tests -{{#if useNose}} +{{#useNose}} script: nosetests -{{/if}} -{{#unless useNose}} +{{/useNose}} +{{^useNose}} script: pytest --cov={{{packageName}}} -{{/unless}} +{{/useNose}} diff --git a/openapi/python_explerimental_client_template/README_common.handlebars b/openapi/python_explerimental_client_template/README_common.handlebars deleted file mode 100644 index 5c577351..00000000 --- a/openapi/python_explerimental_client_template/README_common.handlebars +++ /dev/null @@ -1,111 +0,0 @@ -```python -{{#with apiInfo}}{{#each apis}}{{#unless hasMore}}{{#if hasHttpSignatureMethods}}import datetime{{/if}}{{/unless}}{{/each}}{{/with}} -import time -import {{{packageName}}} -from pprint import pprint -{{#with apiInfo}} -{{#each apis}} -{{#if @first}} -from {{packageName}}.{{apiPackage}} import {{classFilename}} -{{#each imports}} -{{{import}}} -{{/each}} -{{#with operations}} -{{#each operation}} -{{#if @first}} -{{> doc_auth_partial}} - -# Enter a context with an instance of the API client -with {{{packageName}}}.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = {{classFilename}}.{{{classname}}}(api_client) - {{#each allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{#unless required}} (optional){{/unless}}{{#if defaultValue}} (default to {{{.}}}){{/if}} - {{/each}} - - try: - {{#if summary}} # {{{summary}}} - {{/if}} {{#if returnType}}api_response = {{/if}}api_instance.{{{operationId}}}({{#each allParams}}{{#if required}}{{paramName}}{{/if}}{{#unless required}}{{paramName}}={{paramName}}{{/unless}}{{#if hasMore}}, {{/if}}{{/each}}){{#if returnType}} - pprint(api_response){{/if}} - except {{{packageName}}}.ApiException as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -{{/if}} -{{/each}} -{{/with}} -{{/if}} -{{/each}} -{{/with}} -``` - -## Documentation for API Endpoints - -All URIs are relative to *{{basePath}}* - -Class | Method | HTTP request | Description ------------- | ------------- | ------------- | ------------- -{{#with apiInfo}}{{#each apis}}{{#with operations}}{{#each operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#if summary}}{{summary}}{{/if}} -{{/each}}{{/with}}{{/each}}{{/with}} - -## Documentation For Models - -{{#each models}}{{#with model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) -{{/with}}{{/each}} - -## Documentation For Authorization - -{{#unless authMethods}} - All endpoints do not require authorization. -{{/unless}} -{{#each authMethods}} -{{#if @last}} Authentication schemes defined for the API:{{/if}} -## {{{name}}} - -{{#if isApiKey}} -- **Type**: API key -- **API key parameter name**: {{{keyParamName}}} -- **Location**: {{#if isKeyInQuery}}URL query string{{/if}}{{#if isKeyInHeader}}HTTP header{{/if}} -{{/if}} -{{#if isBasic}} -{{#if isBasicBasic}} -- **Type**: HTTP basic authentication -{{/if}} -{{#if isBasicBearer}} -- **Type**: Bearer authentication{{#if bearerFormat}} ({{{bearerFormat}}}){{/if}} -{{/if}} -{{#if isHttpSignature}} -- **Type**: HTTP signature authentication -{{/if}} -{{/if}} -{{#if isOAuth}} -- **Type**: OAuth -- **Flow**: {{{flow}}} -- **Authorization URL**: {{{authorizationUrl}}} -- **Scopes**: {{#unless scopes}}N/A{{/unless}} -{{#each scopes}} - **{{{scope}}}**: {{{description}}} -{{/each}} -{{/if}} - -{{/each}} - -## Author - -{{#with apiInfo}}{{#each apis}}{{#unless hasMore}}{{infoEmail}} -{{/unless}}{{/each}}{{/with}} - -## Notes for Large OpenAPI documents -If the OpenAPI document is large, imports in {{{packageName}}}.apis and {{{packageName}}}.models may fail with a -RecursionError indicating the maximum recursion limit has been exceeded. In that case, there are a couple of solutions: - -Solution 1: -Use specific imports for apis and models like: -- `from {{{packageName}}}.{{apiPackage}}.default_api import DefaultApi` -- `from {{{packageName}}}.{{modelPackage}}.pet import Pet` - -Solution 1: -Before importing the package, adjust the maximum recursion limit as shown below: -``` -import sys -sys.setrecursionlimit(1500) -import {{{packageName}}} -from {{{packageName}}}.apis import * -from {{{packageName}}}.models import * -``` diff --git a/openapi/python_explerimental_client_template/api.handlebars b/openapi/python_explerimental_client_template/api.handlebars deleted file mode 100644 index c6c0b423..00000000 --- a/openapi/python_explerimental_client_template/api.handlebars +++ /dev/null @@ -1,26 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -from {{packageName}}.api_client import ApiClient -{{#with operations}} -{{#each operation}} -from {{packageName}}.api.{{classFilename}}_endpoints.{{operationId}} import {{operationIdCamelCase}} -{{/each}} -{{/with}} - - -{{#with operations}} -class {{classname}}( -{{#each operation}} - {{operationIdCamelCase}}, -{{/each}} - ApiClient, -): - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - pass -{{/with}} diff --git a/openapi/python_explerimental_client_template/api_client.handlebars b/openapi/python_explerimental_client_template/api_client.handlebars deleted file mode 100644 index 4dfc613a..00000000 --- a/openapi/python_explerimental_client_template/api_client.handlebars +++ /dev/null @@ -1,1379 +0,0 @@ -# coding: utf-8 -{{>partial_header}} - -from dataclasses import dataclass -from decimal import Decimal -import enum -import json -import os -import io -import atexit -from multiprocessing.pool import ThreadPool -import re -import tempfile -import typing -import urllib3 -from urllib3._collections import HTTPHeaderDict -from urllib.parse import quote -from urllib3.fields import RequestField as RequestFieldBase - -{{#if tornado}} -import tornado.gen -{{/if}} - -from {{packageName}} import rest -from {{packageName}}.configuration import Configuration -from {{packageName}}.exceptions import ApiTypeError, ApiValueError -from {{packageName}}.schemas import ( - NoneClass, - BoolClass, - Schema, - FileIO, - BinarySchema, - InstantiationMetadata, - date, - datetime, - none_type, - frozendict, - Unset, - unset, -) - - -class RequestField(RequestFieldBase): - def __eq__(self, other): - if not isinstance(other, RequestField): - return False - return self.__dict__ == other.__dict__ - - -class JSONEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, (str, int, float)): - # instances based on primitive classes - return obj - elif isinstance(obj, Decimal): - if obj.as_tuple().exponent >= 0: - return int(obj) - return float(obj) - elif isinstance(obj, NoneClass): - return None - elif isinstance(obj, BoolClass): - return bool(obj) - elif isinstance(obj, (dict, frozendict)): - return {key: self.default(val) for key, val in obj.items()} - elif isinstance(obj, (list, tuple)): - return [self.default(item) for item in obj] - raise ApiValueError('Unable to prepare type {} for serialization'.format(obj.__class__.__name__)) - - -class ParameterInType(enum.Enum): - QUERY = 'query' - HEADER = 'header' - PATH = 'path' - COOKIE = 'cookie' - - -class ParameterStyle(enum.Enum): - MATRIX = 'matrix' - LABEL = 'label' - FORM = 'form' - SIMPLE = 'simple' - SPACE_DELIMITED = 'spaceDelimited' - PIPE_DELIMITED = 'pipeDelimited' - DEEP_OBJECT = 'deepObject' - - -class ParameterSerializerBase: - @staticmethod - def __serialize_number( - in_data: typing.Union[int, float], name: str, prefix='' - ) -> typing.Tuple[typing.Tuple[str, str]]: - return tuple([(name, prefix + str(in_data))]) - - @staticmethod - def __serialize_str( - in_data: str, name: str, prefix='' - ) -> typing.Tuple[typing.Tuple[str, str]]: - return tuple([(name, prefix + quote(in_data))]) - - @staticmethod - def __serialize_bool(in_data: bool, name: str, prefix='') -> typing.Tuple[typing.Tuple[str, str]]: - if in_data: - return tuple([(name, prefix + 'true')]) - return tuple([(name, prefix + 'false')]) - - @staticmethod - def __urlencode(in_data: typing.Any) -> str: - return quote(str(in_data)) - - def __serialize_list( - self, - in_data: typing.List[typing.Any], - style: ParameterStyle, - name: str, - explode: bool, - empty_val: typing.Union[typing.Tuple[str, str], typing.Tuple] = tuple(), - prefix: str = '', - separator: str = ',', - ) -> typing.Tuple[typing.Union[typing.Tuple[str, str], typing.Tuple], ...]: - if not in_data: - return empty_val - if explode and style in { - ParameterStyle.FORM, - ParameterStyle.MATRIX, - ParameterStyle.SPACE_DELIMITED, - ParameterStyle.PIPE_DELIMITED - }: - if style is ParameterStyle.FORM: - return tuple((name, prefix + self.__urlencode(val)) for val in in_data) - else: - joined_vals = prefix + separator.join(name + '=' + self.__urlencode(val) for val in in_data) - else: - joined_vals = prefix + separator.join(map(self.__urlencode, in_data)) - return tuple([(name, joined_vals)]) - - def __form_item_representation(self, in_data: typing.Any) -> typing.Optional[str]: - if isinstance(in_data, none_type): - return None - elif isinstance(in_data, list): - if not in_data: - return None - raise ApiValueError('Unable to generate a form representation of {}'.format(in_data)) - elif isinstance(in_data, dict): - if not in_data: - return None - raise ApiValueError('Unable to generate a form representation of {}'.format(in_data)) - elif isinstance(in_data, (bool, bytes)): - raise ApiValueError('Unable to generate a form representation of {}'.format(in_data)) - # str, float, int - return self.__urlencode(in_data) - - def __serialize_dict( - self, - in_data: typing.Dict[str, typing.Any], - style: ParameterStyle, - name: str, - explode: bool, - empty_val: typing.Union[typing.Tuple[str, str], typing.Tuple] = tuple(), - prefix: str = '', - separator: str = ',', - ) -> typing.Tuple[typing.Tuple[str, str]]: - if not in_data: - return empty_val - if all(val is None for val in in_data.values()): - return empty_val - - form_items = {} - if style is ParameterStyle.FORM: - for key, val in in_data.items(): - new_val = self.__form_item_representation(val) - if new_val is None: - continue - form_items[key] = new_val - - if explode: - if style is ParameterStyle.FORM: - return tuple((key, prefix + val) for key, val in form_items.items()) - elif style in { - ParameterStyle.SIMPLE, - ParameterStyle.LABEL, - ParameterStyle.MATRIX, - ParameterStyle.SPACE_DELIMITED, - ParameterStyle.PIPE_DELIMITED - }: - joined_vals = prefix + separator.join(key + '=' + self.__urlencode(val) for key, val in in_data.items()) - else: - raise ApiValueError(f'Invalid style {style} for dict serialization with explode=True') - elif style is ParameterStyle.FORM: - joined_vals = prefix + separator.join(key + separator + val for key, val in form_items.items()) - else: - joined_vals = prefix + separator.join( - key + separator + self.__urlencode(val) for key, val in in_data.items()) - return tuple([(name, joined_vals)]) - - def _serialize_x( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list], - style: ParameterStyle, - name: str, - explode: bool, - empty_val: typing.Union[typing.Tuple[str, str], typing.Tuple] = (), - prefix: str = '', - separator: str = ',', - ) -> typing.Tuple[typing.Tuple[str, str], ...]: - if isinstance(in_data, none_type): - return empty_val - elif isinstance(in_data, bool): - # must be before int check - return self.__serialize_bool(in_data, name=name, prefix=prefix) - elif isinstance(in_data, (int, float)): - return self.__serialize_number(in_data, name=name, prefix=prefix) - elif isinstance(in_data, str): - return self.__serialize_str(in_data, name=name, prefix=prefix) - elif isinstance(in_data, list): - return self.__serialize_list( - in_data, - style=style, - name=name, - explode=explode, - empty_val=empty_val, - prefix=prefix, - separator=separator - ) - elif isinstance(in_data, dict): - return self.__serialize_dict( - in_data, - style=style, - name=name, - explode=explode, - empty_val=empty_val, - prefix=prefix, - separator=separator - ) - - -class StyleFormSerializer(ParameterSerializerBase): - - def _serialize_form( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list], - name: str, - explode: bool, - ) -> typing.Tuple[typing.Tuple[str, str], ...]: - return self._serialize_x(in_data, style=ParameterStyle.FORM, name=name, explode=explode) - - -class StyleSimpleSerializer(ParameterSerializerBase): - - def _serialize_simple_tuple( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list], - name: str, - explode: bool, - in_type: ParameterInType, - ) -> typing.Tuple[typing.Tuple[str, str], ...]: - if in_type is ParameterInType.HEADER: - empty_val = () - else: - empty_val = ((name, ''),) - return self._serialize_x(in_data, style=ParameterStyle.SIMPLE, name=name, explode=explode, empty_val=empty_val) - - -@dataclass -class ParameterBase: - name: str - in_type: ParameterInType - required: bool - style: typing.Optional[ParameterStyle] - explode: typing.Optional[bool] - allow_reserved: typing.Optional[bool] - schema: typing.Optional[typing.Type[Schema]] - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] - - __style_to_in_type = { - ParameterStyle.MATRIX: {ParameterInType.PATH}, - ParameterStyle.LABEL: {ParameterInType.PATH}, - ParameterStyle.FORM: {ParameterInType.QUERY, ParameterInType.COOKIE}, - ParameterStyle.SIMPLE: {ParameterInType.PATH, ParameterInType.HEADER}, - ParameterStyle.SPACE_DELIMITED: {ParameterInType.QUERY}, - ParameterStyle.PIPE_DELIMITED: {ParameterInType.QUERY}, - ParameterStyle.DEEP_OBJECT: {ParameterInType.QUERY}, - } - __in_type_to_default_style = { - ParameterInType.QUERY: ParameterStyle.FORM, - ParameterInType.PATH: ParameterStyle.SIMPLE, - ParameterInType.HEADER: ParameterStyle.SIMPLE, - ParameterInType.COOKIE: ParameterStyle.FORM, - } - __disallowed_header_names = {'Accept', 'Content-Type', 'Authorization'} - _json_encoder = JSONEncoder() - _json_content_type = 'application/json' - - @classmethod - def __verify_style_to_in_type(cls, style: typing.Optional[ParameterStyle], in_type: ParameterInType): - if style is None: - return - in_type_set = cls.__style_to_in_type[style] - if in_type not in in_type_set: - raise ValueError( - 'Invalid style and in_type combination. For style={} only in_type={} are allowed'.format( - style, in_type_set - ) - ) - - def __init__( - self, - name: str, - in_type: ParameterInType, - required: bool = False, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: typing.Optional[bool] = None, - schema: typing.Optional[typing.Type[Schema]] = None, - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None - ): - if schema is None and content is None: - raise ValueError('Value missing; Pass in either schema or content') - if schema and content: - raise ValueError('Too many values provided. Both schema and content were provided. Only one may be input') - if name in self.__disallowed_header_names and in_type is ParameterInType.HEADER: - raise ValueError('Invalid name, name may not be one of {}'.format(self.__disallowed_header_names)) - self.__verify_style_to_in_type(style, in_type) - if content is None and style is None: - style = self.__in_type_to_default_style[in_type] - if content is not None and in_type in self.__in_type_to_default_style and len(content) != 1: - raise ValueError('Invalid content length, content length must equal 1') - self.in_type = in_type - self.name = name - self.required = required - self.style = style - self.explode = explode - self.allow_reserved = allow_reserved - self.schema = schema - self.content = content - - @staticmethod - def _remove_empty_and_cast( - in_data: typing.Tuple[typing.Tuple[str, str]], - ) -> typing.Dict[str, str]: - data = tuple(t for t in in_data if t) - if not data: - return dict() - return dict(data) - - def _serialize_json( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list] - ) -> typing.Tuple[typing.Tuple[str, str]]: - return tuple([(self.name, json.dumps(in_data))]) - - -class PathParameter(ParameterBase, StyleSimpleSerializer): - - def __init__( - self, - name: str, - required: bool = False, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: typing.Optional[bool] = None, - schema: typing.Optional[typing.Type[Schema]] = None, - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None - ): - super().__init__( - name, - in_type=ParameterInType.PATH, - required=required, - style=style, - explode=explode, - allow_reserved=allow_reserved, - schema=schema, - content=content - ) - - def __serialize_label( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list] - ) -> typing.Dict[str, str]: - empty_val = ((self.name, ''),) - prefix = '.' - separator = '.' - return self._remove_empty_and_cast( - self._serialize_x( - in_data, - style=ParameterStyle.LABEL, - name=self.name, - explode=self.explode, - empty_val=empty_val, - prefix=prefix, - separator=separator - ) - ) - - def __serialize_matrix( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list] - ) -> typing.Dict[str, str]: - separator = ',' - if in_data == '': - prefix = ';' + self.name - elif isinstance(in_data, (dict, list)) and self.explode: - prefix = ';' - separator = ';' - else: - prefix = ';' + self.name + '=' - empty_val = ((self.name, ''),) - return self._remove_empty_and_cast( - self._serialize_x( - in_data, - style=ParameterStyle.MATRIX, - name=self.name, - explode=self.explode, - prefix=prefix, - empty_val=empty_val, - separator=separator - ) - ) - - def _serialize_simple( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list], - ) -> typing.Dict[str, str]: - tuple_data = self._serialize_simple_tuple(in_data, self.name, self.explode, self.in_type) - return self._remove_empty_and_cast(tuple_data) - - def serialize( - self, - in_data: typing.Union[ - Schema, Decimal, int, float, str, date, datetime, None, bool, list, tuple, dict, frozendict] - ) -> typing.Dict[str, str]: - if self.schema: - cast_in_data = self.schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - """ - simple -> path - path: - returns path_params: dict - label -> path - returns path_params - matrix -> path - returns path_params - """ - if self.style: - if self.style is ParameterStyle.SIMPLE: - return self._serialize_simple(cast_in_data) - elif self.style is ParameterStyle.LABEL: - return self.__serialize_label(cast_in_data) - elif self.style is ParameterStyle.MATRIX: - return self.__serialize_matrix(cast_in_data) - # self.content will be length one - for content_type, schema in self.content.items(): - cast_in_data = schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - if content_type == self._json_content_type: - tuple_data = self._serialize_json(cast_in_data) - return self._remove_empty_and_cast(tuple_data) - raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) - - -class QueryParameter(ParameterBase, StyleFormSerializer): - - def __init__( - self, - name: str, - required: bool = False, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: typing.Optional[bool] = None, - schema: typing.Optional[typing.Type[Schema]] = None, - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None - ): - super().__init__( - name, - in_type=ParameterInType.QUERY, - required=required, - style=style, - explode=explode, - allow_reserved=allow_reserved, - schema=schema, - content=content - ) - - def __serialize_space_delimited( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list] - ) -> typing.Tuple[typing.Tuple[str, str], ...]: - separator = '%20' - empty_val = () - return self._serialize_x( - in_data, - style=ParameterStyle.SPACE_DELIMITED, - name=self.name, - explode=self.explode, - separator=separator, - empty_val=empty_val - ) - - def __serialize_pipe_delimited( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list] - ) -> typing.Tuple[typing.Tuple[str, str], ...]: - separator = '|' - empty_val = () - return self._serialize_x( - in_data, - style=ParameterStyle.PIPE_DELIMITED, - name=self.name, - explode=self.explode, - separator=separator, - empty_val=empty_val - ) - - def serialize( - self, - in_data: typing.Union[ - Schema, Decimal, int, float, str, date, datetime, None, bool, list, tuple, dict, frozendict] - ) -> typing.Tuple[typing.Tuple[str, str]]: - if self.schema: - cast_in_data = self.schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - """ - form -> query - query: - - GET/HEAD/DELETE: could use fields - - PUT/POST: must use urlencode to send parameters - returns fields: tuple - spaceDelimited -> query - returns fields - pipeDelimited -> query - returns fields - deepObject -> query, https://github.com/OAI/OpenAPI-Specification/issues/1706 - returns fields - """ - if self.style: - # TODO update query ones to omit setting values when [] {} or None is input - if self.style is ParameterStyle.FORM: - return self._serialize_form(cast_in_data, explode=self.explode, name=self.name) - elif self.style is ParameterStyle.SPACE_DELIMITED: - return self.__serialize_space_delimited(cast_in_data) - elif self.style is ParameterStyle.PIPE_DELIMITED: - return self.__serialize_pipe_delimited(cast_in_data) - # self.content will be length one - for content_type, schema in self.content.items(): - cast_in_data = schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - if content_type == self._json_content_type: - return self._serialize_json(cast_in_data) - raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) - - -class CookieParameter(ParameterBase, StyleFormSerializer): - - def __init__( - self, - name: str, - required: bool = False, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: typing.Optional[bool] = None, - schema: typing.Optional[typing.Type[Schema]] = None, - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None - ): - super().__init__( - name, - in_type=ParameterInType.COOKIE, - required=required, - style=style, - explode=explode, - allow_reserved=allow_reserved, - schema=schema, - content=content - ) - - def serialize( - self, - in_data: typing.Union[ - Schema, Decimal, int, float, str, date, datetime, None, bool, list, tuple, dict, frozendict] - ) -> typing.Tuple[typing.Tuple[str, str]]: - if self.schema: - cast_in_data = self.schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - """ - form -> cookie - returns fields: tuple - """ - if self.style: - return self._serialize_form(cast_in_data, explode=self.explode, name=self.name) - # self.content will be length one - for content_type, schema in self.content.items(): - cast_in_data = schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - if content_type == self._json_content_type: - return self._serialize_json(cast_in_data) - raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) - - -class HeaderParameter(ParameterBase, StyleSimpleSerializer): - def __init__( - self, - name: str, - required: bool = False, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: typing.Optional[bool] = None, - schema: typing.Optional[typing.Type[Schema]] = None, - content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None - ): - super().__init__( - name, - in_type=ParameterInType.HEADER, - required=required, - style=style, - explode=explode, - allow_reserved=allow_reserved, - schema=schema, - content=content - ) - - @staticmethod - def __to_headers(in_data: typing.Tuple[typing.Tuple[str, str], ...]) -> HTTPHeaderDict[str, str]: - data = tuple(t for t in in_data if t) - headers = HTTPHeaderDict() - if not data: - return headers - headers.extend(data) - return headers - - def _serialize_simple( - self, - in_data: typing.Union[None, int, float, str, bool, dict, list], - ) -> HTTPHeaderDict[str, str]: - tuple_data = self._serialize_simple_tuple(in_data, self.name, self.explode, self.in_type) - return self.__to_headers(tuple_data) - - def serialize( - self, - in_data: typing.Union[ - Schema, Decimal, int, float, str, date, datetime, None, bool, list, tuple, dict, frozendict] - ) -> HTTPHeaderDict[str, str]: - if self.schema: - cast_in_data = self.schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - """ - simple -> header - headers: PoolManager needs a mapping, tuple is close - returns headers: dict - """ - if self.style: - return self._serialize_simple(cast_in_data) - # self.content will be length one - for content_type, schema in self.content.items(): - cast_in_data = schema(in_data) - cast_in_data = self._json_encoder.default(cast_in_data) - if content_type == self._json_content_type: - tuple_data = self._serialize_json(cast_in_data) - return self.__to_headers(tuple_data) - raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type)) - - -class Encoding: - def __init__( - self, - content_type: str, - headers: typing.Optional[typing.Dict[str, HeaderParameter]] = None, - style: typing.Optional[ParameterStyle] = None, - explode: bool = False, - allow_reserved: bool = False, - ): - self.content_type = content_type - self.headers = headers - self.style = style - self.explode = explode - self.allow_reserved = allow_reserved - - -class MediaType: - """ - Used to store request and response body schema information - encoding: - A map between a property name and its encoding information. - The key, being the property name, MUST exist in the schema as a property. - The encoding object SHALL only apply to requestBody objects when the media type is - multipart or application/x-www-form-urlencoded. - """ - - def __init__( - self, - schema: typing.Type[Schema], - encoding: typing.Optional[typing.Dict[str, Encoding]] = None, - ): - self.schema = schema - self.encoding = encoding - - -@dataclass -class ApiResponse: - response: urllib3.HTTPResponse - body: typing.Union[Unset, typing.Type[Schema]] - headers: typing.Union[Unset, typing.List[HeaderParameter]] - - def __init__( - self, - response: urllib3.HTTPResponse, - body: typing.Union[Unset, typing.Type[Schema]], - headers: typing.Union[Unset, typing.List[HeaderParameter]] - ): - """ - pycharm needs this to prevent 'Unexpected argument' warnings - """ - self.response = response - self.body = body - self.headers = headers - - -@dataclass -class ApiResponseWithoutDeserialization(ApiResponse): - response: urllib3.HTTPResponse - body: typing.Union[Unset, typing.Type[Schema]] = unset - headers: typing.Union[Unset, typing.List[HeaderParameter]] = unset - - -class OpenApiResponse: - def __init__( - self, - response_cls: typing.Type[ApiResponse] = ApiResponse, - content: typing.Optional[typing.Dict[str, MediaType]] = None, - headers: typing.Optional[typing.List[HeaderParameter]] = None, - ): - self.headers = headers - if content is not None and len(content) == 0: - raise ValueError('Invalid value for content, the content dict must have >= 1 entry') - self.content = content - self.response_cls = response_cls - - @staticmethod - def __deserialize_json(response: urllib3.HTTPResponse) -> typing.Any: - decoded_data = response.data.decode("utf-8") - return json.loads(decoded_data) - - @staticmethod - def __file_name_from_content_disposition(content_disposition: typing.Optional[str]) -> typing.Optional[str]: - if content_disposition is None: - return None - match = re.search('filename="(.+?)"', content_disposition) - if not match: - return None - return match.group(1) - - def __deserialize_application_octet_stream( - self, response: urllib3.HTTPResponse - ) -> typing.Union[bytes, io.BufferedReader]: - """ - urllib3 use cases: - 1. when preload_content=True (stream=False) then supports_chunked_reads is False and bytes are returned - 2. when preload_content=False (stream=True) then supports_chunked_reads is True and - a file will be written and returned - """ - if response.supports_chunked_reads(): - file_name = self.__file_name_from_content_disposition(response.headers.get('content-disposition')) - - if file_name is None: - _fd, path = tempfile.mkstemp() - else: - path = os.path.join(tempfile.gettempdir(), file_name) - # TODO get file_name from the filename at the end of the url if it exists - with open(path, 'wb') as new_file: - chunk_size = 1024 - while True: - data = response.read(chunk_size) - if not data: - break - new_file.write(data) - # release_conn is needed for streaming connections only - response.release_conn() - new_file = open(path, 'rb') - return new_file - else: - return response.data - - def deserialize(self, response: urllib3.HTTPResponse, configuration: Configuration) -> ApiResponse: - content_type = response.getheader('content-type') - deserialized_body = unset - streamed = response.supports_chunked_reads() - if self.content is not None: - if content_type == 'application/json': - body_data = self.__deserialize_json(response) - elif content_type == 'application/octet-stream': - body_data = self.__deserialize_application_octet_stream(response) - else: - raise NotImplementedError('Deserialization of {} has not yet been implemented'.format(content_type)) - body_schema = self.content[content_type].schema - _instantiation_metadata = InstantiationMetadata(from_server=True, configuration=configuration) - deserialized_body = body_schema._from_openapi_data( - body_data, _instantiation_metadata=_instantiation_metadata) - elif streamed: - response.release_conn() - - deserialized_headers = unset - if self.headers is not None: - deserialized_headers = unset - - return self.response_cls( - response=response, - headers=deserialized_headers, - body=deserialized_body - ) - - -class ApiClient: - """Generic API client for OpenAPI client library builds. - - OpenAPI generic API client. This client handles the client- - server communication, and is invariant across implementations. Specifics of - the methods and models for each application are generated from the OpenAPI - templates. - - NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - Do not edit the class manually. - - :param configuration: .Configuration object for this client - :param header_name: a header to pass when making calls to the API. - :param header_value: a header value to pass when making calls to - the API. - :param cookie: a cookie to include in the header when making calls - to the API - :param pool_threads: The number of threads to use for async requests - to the API. More threads means more concurrent API requests. - """ - - _pool = None - __json_encoder = JSONEncoder() - - def __init__( - self, - configuration: typing.Optional[Configuration] = None, - header_name: typing.Optional[str] = None, - header_value: typing.Optional[str] = None, - cookie: typing.Optional[str] = None, - pool_threads: int = 1 - ): - if configuration is None: - configuration = Configuration() - self.configuration = configuration - self.pool_threads = pool_threads - - self.rest_client = rest.RESTClientObject(configuration) - self.default_headers = {} - if header_name is not None: - self.default_headers[header_name] = header_value - self.cookie = cookie - # Set default User-Agent. - self.user_agent = '{{#if httpUserAgent}}{{{httpUserAgent}}}{{/if}}{{#unless httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/unless}}' - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - def close(self): - if self._pool: - self._pool.close() - self._pool.join() - self._pool = None - if hasattr(atexit, 'unregister'): - atexit.unregister(self.close) - - @property - def pool(self): - """Create thread pool on first request - avoids instantiating unused threadpool for blocking clients. - """ - if self._pool is None: - atexit.register(self.close) - self._pool = ThreadPool(self.pool_threads) - return self._pool - - @property - def user_agent(self): - """User agent for this API client""" - return self.default_headers['User-Agent'] - - @user_agent.setter - def user_agent(self, value): - self.default_headers['User-Agent'] = value - - def set_default_header(self, header_name, header_value): - self.default_headers[header_name] = header_value - - {{#if tornado}} - @tornado.gen.coroutine - {{/if}} - {{#if asyncio}}async {{/if}}def __call_api( - self, - resource_path: str, - method: str, - path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, - query_params: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - headers: typing.Optional[HTTPHeaderDict] = None, - body: typing.Optional[typing.Union[str, bytes]] = None, - fields: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - auth_settings: typing.Optional[typing.List[str]] = None, - stream: bool = False, - timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, - host: typing.Optional[str] = None, - ) -> urllib3.HTTPResponse: - - # header parameters - headers = headers or {} - headers.update(self.default_headers) - if self.cookie: - headers['Cookie'] = self.cookie - - # path parameters - if path_params: - for k, v in path_params.items(): - # specified safe chars, encode everything - resource_path = resource_path.replace( - '{%s}' % k, - quote(str(v), safe=self.configuration.safe_chars_for_path_param) - ) - - # auth setting - self.update_params_for_auth(headers, query_params, - auth_settings, resource_path, method, body) - - # request url - if host is None: - url = self.configuration.host + resource_path - else: - # use server/host defined in path or operation instead - url = host + resource_path - - # perform request and return response - response = {{#if asyncio}}await {{/if}}{{#if tornado}}yield {{/if}}self.request( - method, - url, - query_params=query_params, - headers=headers, - fields=fields, - body=body, - stream=stream, - timeout=timeout, - ) - return response - - def call_api( - self, - resource_path: str, - method: str, - path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, - query_params: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - headers: typing.Optional[HTTPHeaderDict] = None, - body: typing.Optional[typing.Union[str, bytes]] = None, - fields: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - auth_settings: typing.Optional[typing.List[str]] = None, - async_req: typing.Optional[bool] = None, - stream: bool = False, - timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, - host: typing.Optional[str] = None, - ) -> urllib3.HTTPResponse: - """Makes the HTTP request (synchronous) and returns deserialized data. - - To make an async_req request, set the async_req parameter. - - :param resource_path: Path to method endpoint. - :param method: Method to call. - :param path_params: Path parameters in the url. - :param query_params: Query parameters in the url. - :param headers: Header parameters to be - placed in the request header. - :param body: Request body. - :param fields: Request post form parameters, - for `application/x-www-form-urlencoded`, `multipart/form-data`. - :param auth_settings: Auth Settings names for the request. - :param async_req: execute request asynchronously - :type async_req: bool, optional TODO remove, unused - :param stream: if True, the urllib3.HTTPResponse object will - be returned without reading/decoding response - data. Also when True, if the openapi spec describes a file download, - the data will be written to a local filesystme file and the BinarySchema - instance will also inherit from FileSchema and FileIO - Default is False. - :type stream: bool, optional - :param timeout: timeout setting for this request. If one - number provided, it will be total request - timeout. It can also be a pair (tuple) of - (connection, read) timeouts. - :param host: api endpoint host - :return: - If async_req parameter is True, - the request will be called asynchronously. - The method will return the request thread. - If parameter async_req is False or missing, - then the method will return the response directly. - """ - - if not async_req: - return self.__call_api( - resource_path, - method, - path_params, - query_params, - headers, - body, - fields, - auth_settings, - stream, - timeout, - host, - ) - - return self.pool.apply_async( - self.__call_api, - ( - resource_path, - method, - path_params, - query_params, - headers, - body, - json, - fields, - auth_settings, - stream, - timeout, - host, - ) - ) - - def request( - self, - method: str, - url: str, - query_params: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - headers: typing.Optional[HTTPHeaderDict] = None, - fields: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - body: typing.Optional[typing.Union[str, bytes]] = None, - stream: bool = False, - timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, - ) -> urllib3.HTTPResponse: - """Makes the HTTP request using RESTClient.""" - if method == "GET": - return self.rest_client.GET(url, - query_params=query_params, - stream=stream, - timeout=timeout, - headers=headers) - elif method == "HEAD": - return self.rest_client.HEAD(url, - query_params=query_params, - stream=stream, - timeout=timeout, - headers=headers) - elif method == "OPTIONS": - return self.rest_client.OPTIONS(url, - query_params=query_params, - headers=headers, - fields=fields, - stream=stream, - timeout=timeout, - body=body) - elif method == "POST": - return self.rest_client.POST(url, - query_params=query_params, - headers=headers, - fields=fields, - stream=stream, - timeout=timeout, - body=body) - elif method == "PUT": - return self.rest_client.PUT(url, - query_params=query_params, - headers=headers, - fields=fields, - stream=stream, - timeout=timeout, - body=body) - elif method == "PATCH": - return self.rest_client.PATCH(url, - query_params=query_params, - headers=headers, - fields=fields, - stream=stream, - timeout=timeout, - body=body) - elif method == "DELETE": - return self.rest_client.DELETE(url, - query_params=query_params, - headers=headers, - stream=stream, - timeout=timeout, - body=body) - else: - raise ApiValueError( - "http method must be `GET`, `HEAD`, `OPTIONS`," - " `POST`, `PATCH`, `PUT` or `DELETE`." - ) - - def update_params_for_auth(self, headers, querys, auth_settings, - resource_path, method, body): - """Updates header and query params based on authentication setting. - - :param headers: Header parameters dict to be updated. - :param querys: Query parameters tuple list to be updated. - :param auth_settings: Authentication setting identifiers list. - :param resource_path: A string representation of the HTTP request resource path. - :param method: A string representation of the HTTP request method. - :param body: A object representing the body of the HTTP request. - The object type is the return value of _encoder.default(). - """ - if not auth_settings: - return - - for auth in auth_settings: - auth_setting = self.configuration.auth_settings().get(auth) - if auth_setting: - if auth_setting['in'] == 'cookie': - headers.add('Cookie', auth_setting['value']) - elif auth_setting['in'] == 'header': - if auth_setting['type'] != 'http-signature': - headers.add(auth_setting['key'], auth_setting['value']) -{{#if hasHttpSignatureMethods}} - else: - # The HTTP signature scheme requires multiple HTTP headers - # that are calculated dynamically. - signing_info = self.configuration.signing_info - auth_headers = signing_info.get_http_signature_headers( - resource_path, method, headers, body, querys) - for key, value in auth_headers.items(): - headers.add(key, value) -{{/if}} - elif auth_setting['in'] == 'query': - querys.append((auth_setting['key'], auth_setting['value'])) - else: - raise ApiValueError( - 'Authentication token must be in `query` or `header`' - ) - - -class Api: - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - def __init__(self, api_client: typing.Optional[ApiClient] = None): - if api_client is None: - api_client = ApiClient() - self.api_client = api_client - - @staticmethod - def _verify_typed_dict_inputs(cls: typing.Type[typing.TypedDict], data: typing.Dict[str, typing.Any]): - """ - Ensures that: - - required keys are present - - additional properties are not input - - value stored under required keys do not have the value unset - Note: detailed value checking is done in schema classes - """ - missing_required_keys = [] - required_keys_with_unset_values = [] - for required_key in cls.__required_keys__: - if required_key not in data: - missing_required_keys.append(required_key) - continue - value = data[required_key] - if value is unset: - required_keys_with_unset_values.append(required_key) - if missing_required_keys: - raise ApiTypeError( - '{} missing {} required arguments: {}'.format( - cls.__name__, len(missing_required_keys), missing_required_keys - ) - ) - if required_keys_with_unset_values: - raise ApiValueError( - '{} contains invalid unset values for {} required keys: {}'.format( - cls.__name__, len(required_keys_with_unset_values), required_keys_with_unset_values - ) - ) - - disallowed_additional_keys = [] - for key in data: - if key in cls.__required_keys__ or key in cls.__optional_keys__: - continue - disallowed_additional_keys.append(key) - if disallowed_additional_keys: - raise ApiTypeError( - '{} got {} unexpected keyword arguments: {}'.format( - cls.__name__, len(disallowed_additional_keys), disallowed_additional_keys - ) - ) - - def get_host( - self, - operation_id: str, - servers: typing.Tuple[typing.Dict[str, str], ...] = tuple(), - host_index: typing.Optional[int] = None - ) -> typing.Optional[str]: - configuration = self.api_client.configuration - try: - if host_index is None: - index = configuration.server_operation_index.get( - operation_id, configuration.server_index - ) - else: - index = host_index - server_variables = configuration.server_operation_variables.get( - operation_id, configuration.server_variables - ) - host = configuration.get_host_from_settings( - index, variables=server_variables, servers=servers - ) - except IndexError: - if servers: - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" % - len(servers) - ) - host = None - return host - - -class SerializedRequestBody(typing.TypedDict, total=False): - body: typing.Union[str, bytes] - fields: typing.Tuple[typing.Union[RequestField, tuple[str, str]], ...] - - -class RequestBody(StyleFormSerializer): - """ - A request body parameter - content: content_type to MediaType Schema info - """ - __json_encoder = JSONEncoder() - - def __init__( - self, - content: typing.Dict[str, MediaType], - required: bool = False, - ): - self.required = required - if len(content) == 0: - raise ValueError('Invalid value for content, the content dict must have >= 1 entry') - self.content = content - - def __serialize_json( - self, - in_data: typing.Any - ) -> typing.Dict[str, bytes]: - in_data = self.__json_encoder.default(in_data) - json_str = json.dumps(in_data, separators=(",", ":"), ensure_ascii=False).encode( - "utf-8" - ) - return dict(body=json_str) - - @staticmethod - def __serialize_text_plain(in_data: typing.Any) -> typing.Dict[str, str]: - if isinstance(in_data, frozendict): - raise ValueError('Unable to serialize type frozendict to text/plain') - elif isinstance(in_data, tuple): - raise ValueError('Unable to serialize type tuple to text/plain') - elif isinstance(in_data, NoneClass): - raise ValueError('Unable to serialize type NoneClass to text/plain') - elif isinstance(in_data, BoolClass): - raise ValueError('Unable to serialize type BoolClass to text/plain') - return dict(body=str(in_data)) - - def __multipart_json_item(self, key: str, value: Schema) -> RequestField: - json_value = self.__json_encoder.default(value) - return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) - - def __multipart_form_item(self, key: str, value: Schema) -> RequestField: - if isinstance(value, str): - return RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) - elif isinstance(value, bytes): - return RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) - elif isinstance(value, FileIO): - request_field = RequestField( - name=key, - data=value.read(), - filename=os.path.basename(value.name), - headers={'Content-Type': 'application/octet-stream'} - ) - value.close() - return request_field - else: - return self.__multipart_json_item(key=key, value=value) - - def __serialize_multipart_form_data( - self, in_data: Schema - ) -> typing.Dict[str, typing.Tuple[RequestField, ...]]: - if not isinstance(in_data, frozendict): - raise ValueError(f'Unable to serialize {in_data} to multipart/form-data because it is not a dict of data') - """ - In a multipart/form-data request body, each schema property, or each element of a schema array property, - takes a section in the payload with an internal header as defined by RFC7578. The serialization strategy - for each property of a multipart/form-data request body can be specified in an associated Encoding Object. - - When passing in multipart types, boundaries MAY be used to separate sections of the content being - transferred – thus, the following default Content-Types are defined for multipart: - - If the (object) property is a primitive, or an array of primitive values, the default Content-Type is text/plain - If the property is complex, or an array of complex values, the default Content-Type is application/json - Question: how is the array of primitives encoded? - If the property is a type: string with a contentEncoding, the default Content-Type is application/octet-stream - """ - fields = [] - for key, value in in_data.items(): - if isinstance(value, tuple): - if value: - # values use explode = True, so the code makes a RequestField for each item with name=key - for item in value: - request_field = self.__multipart_form_item(key=key, value=item) - fields.append(request_field) - else: - # send an empty array as json because exploding will not send it - request_field = self.__multipart_json_item(key=key, value=value) - fields.append(request_field) - else: - request_field = self.__multipart_form_item(key=key, value=value) - fields.append(request_field) - - return dict(fields=tuple(fields)) - - def __serialize_application_octet_stream(self, in_data: BinarySchema) -> typing.Dict[str, bytes]: - if isinstance(in_data, bytes): - return dict(body=in_data) - # FileIO type - result = dict(body=in_data.read()) - in_data.close() - return result - - def __serialize_application_x_www_form_data( - self, in_data: typing.Any - ) -> typing.Dict[str, tuple[tuple[str, str], ...]]: - if not isinstance(in_data, frozendict): - raise ValueError( - f'Unable to serialize {in_data} to application/x-www-form-urlencoded because it is not a dict of data') - cast_in_data = self.__json_encoder.default(in_data) - fields = self._serialize_form(cast_in_data, explode=True, name='') - if not fields: - return {} - return {'fields': fields} - - def serialize( - self, in_data: typing.Any, content_type: str - ) -> SerializedRequestBody: - """ - If a str is returned then the result will be assigned to data when making the request - If a tuple is returned then the result will be used as fields input in encode_multipart_formdata - Return a tuple of - - The key of the return dict is - - body for application/json - - encode_multipart and fields for multipart/form-data - """ - media_type = self.content[content_type] - if isinstance(in_data, media_type.schema): - cast_in_data = in_data - elif isinstance(in_data, (dict, frozendict)) and in_data: - cast_in_data = media_type.schema(**in_data) - else: - cast_in_data = media_type.schema(in_data) - # TODO check for and use encoding if it exists - # and content_type is multipart or application/x-www-form-urlencoded - if content_type == 'application/json': - return self.__serialize_json(cast_in_data) - elif content_type == 'text/plain': - return self.__serialize_text_plain(cast_in_data) - elif content_type == 'multipart/form-data': - return self.__serialize_multipart_form_data(cast_in_data) - elif content_type == 'application/x-www-form-urlencoded': - return self.__serialize_application_x_www_form_data(cast_in_data) - elif content_type == 'application/octet-stream': - return self.__serialize_application_octet_stream(cast_in_data) - raise NotImplementedError('Serialization has not yet been implemented for {}'.format(content_type)) diff --git a/openapi/python_explerimental_client_template/api_doc.handlebars b/openapi/python_explerimental_client_template/api_doc.handlebars deleted file mode 100644 index 6e74b8ab..00000000 --- a/openapi/python_explerimental_client_template/api_doc.handlebars +++ /dev/null @@ -1,212 +0,0 @@ -# {{packageName}}.{{classname}}{{#if description}} -{{description}}{{/if}} - -All URIs are relative to *{{basePath}}* - -Method | HTTP request | Description -------------- | ------------- | ------------- -{{#with operations}}{{#each operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#if summary}}{{summary}}{{/if}} -{{/each}}{{/with}} - -{{#with operations}} -{{#each operation}} -# **{{{operationId}}}** -> {{#if returnType}}{{{returnType}}} {{/if}}{{{operationId}}}({{#each requiredParams}}{{#unless defaultValue}}{{paramName}}{{#if hasMore}}, {{/if}}{{/unless}}{{/each}}) - -{{{summary}}}{{#if notes}} - -{{{notes}}}{{/if}} - -### Example - -{{#if hasAuthMethods}} -{{#each authMethods}} -{{#if isBasic}} -{{#if isBasicBasic}} -* Basic Authentication ({{name}}): -{{/if}} -{{#if isBasicBearer}} -* Bearer{{#if bearerFormat}} ({{{bearerFormat}}}){{/if}} Authentication ({{name}}): -{{/if}} -{{/if}} -{{#if isApiKey}} -* Api Key Authentication ({{name}}): -{{/if}} -{{#if isOAuth}} -* OAuth Authentication ({{name}}): -{{/if}} -{{/each}} -{{/if}} -{{> api_doc_example }} -### Parameters -{{#if allParams}} - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#with bodyParam}} -{{baseName}} | typing.Union[{{#each content}}{{#unless @first}}, {{/unless}}{{this.schema.baseName}}{{/each}}{{#unless required}}, Unset]{{else}}]{{/unless}} | {{#if required}}required{{else}}optional, default is unset{{/if}} | - {{/with}} - {{#if queryParams}} -query_params | RequestQueryParams | | - {{/if}} - {{#if headerParams}} -header_params | RequestHeaderParams | | - {{/if}} - {{#if pathParams}} -path_params | RequestPathParams | | - {{/if}} - {{#if cookieParams}} -cookie_params | RequestCookieParams | | - {{/if}} - {{#with bodyParam}} - {{#each content}} - {{#if @first}} -content_type | str | optional, default is '{{@key}}' | Selects the schema and serialization of the request body - {{/if}} - {{/each}} - {{/with}} - {{#if produces}} -accept_content_types | typing.Tuple[str] | default is ({{#each produces}}'{{this.mediaType}}', {{/each}}) | Tells the server the content type(s) that are accepted by the client - {{/if}} - {{#if servers}} -host_index | typing.Optional[int] | default is None | Allows one to select a different host - {{/if}} -stream | bool | default is False | if True then the response.content will be streamed and loaded from a file like object. When downloading a file, set this to True to force the code to deserialize the content to a FileSchema file -timeout | typing.Optional[typing.Union[int, typing.Tuple]] | default is None | the timeout used by the rest client -skip_deserialization | bool | default is False | when True, headers and body will be unset and an instance of api_client.ApiResponseWithoutDeserialization will be returned - {{#with bodyParam}} - -### body - {{#each content}} - {{#with this.schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - {{/with}} - {{#if queryParams}} - -### query_params -#### RequestQueryParams - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#each queryParams}} -{{baseName}} | {{#with schema}}{{baseName}}{{/with}} | | {{#unless required}}optional{{/unless}} - {{/each}} - - {{#each queryParams}} - {{#with schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - {{/if}} - {{#if headerParams}} - -### header_params -#### RequestHeaderParams - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#each headerParams}} -{{baseName}} | {{#with schema}}{{baseName}}{{/with}} | | {{#unless required}}optional{{/unless}} - {{/each}} - {{#each headerParams}} - {{#with schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - {{/if}} - {{#if pathParams}} - -### path_params -#### RequestPathParams - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#each pathParams}} -{{baseName}} | {{#with schema}}{{baseName}}{{/with}} | | {{#unless required}}optional{{/unless}} - {{/each}} - {{#each pathParams}} - {{#with schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - {{/if}} - {{#if cookieParams}} - -### cookie_params -#### RequestCookieParams - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#each cookieParams}} -{{baseName}} | {{#with schema}}{{baseName}}{{/with}} | | {{#unless required}}optional{{/unless}} - {{/each}} - {{#each cookieParams}} - {{#with schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - {{/if}} -{{else}} -This endpoint does not need any parameter. -{{/if}} - -### Return Types, Responses - -Code | Class | Description -------------- | ------------- | ------------- -n/a | api_client.ApiResponseWithoutDeserialization | When skip_deserialization is True this response is returned -{{#each responses}} -{{#if isDefault}} -default | ApiResponseForDefault | {{message}} {{description}} -{{else}} -{{code}} | ApiResponseFor{{code}} | {{message}} {{description}} -{{/if}} -{{/each}} -{{#each responses}} -{{#if isDefault}} - -#### ApiResponseForDefault -{{else}} - -#### ApiResponseFor{{code}} -{{/if}} -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -response | urllib3.HTTPResponse | Raw response | -body | {{#unless content}}Unset{{else}}typing.Union[{{#each content}}{{this.schema.baseName}}, {{/each}}]{{/unless}} | {{#unless content}}body was not defined{{/unless}} | -headers | {{#unless responseHeaders}}Unset{{else}}ResponseHeadersFor{{code}}{{/unless}} | {{#unless responseHeaders}}headers were not defined{{/unless}} | -{{#each content}} -{{#with this.schema}} -{{> api_doc_schema_type_hint }} -{{/with}} -{{/each}} -{{#if responseHeaders}} -#### ResponseHeadersFor{{code}} - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - {{#each responseHeaders}} -{{baseName}} | {{#with schema}}{{baseName}}{{/with}} | | {{#unless required}}optional{{/unless}} - {{/each}} - {{#each responseHeaders}} - {{#with schema}} -{{> api_doc_schema_type_hint }} - {{/with}} - {{/each}} - -{{/if}} -{{/each}} - - -{{#if returnType}}{{#if returnTypeIsPrimitive}}**{{{returnType}}}**{{/if}}{{#unless returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/unless}}{{/if}}{{#unless returnType}}void (empty response body){{/unless}} - -### Authorization - -{{#unless authMethods}}No authorization required{{/unless}}{{#each authMethods}}[{{{name}}}](../README.md#{{{name}}}){{#unless @last}}, {{/unless}}{{/each}} - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -{{/each}} -{{/with}} diff --git a/openapi/python_explerimental_client_template/api_doc_example.handlebars b/openapi/python_explerimental_client_template/api_doc_example.handlebars deleted file mode 100644 index e6fa0bd4..00000000 --- a/openapi/python_explerimental_client_template/api_doc_example.handlebars +++ /dev/null @@ -1,163 +0,0 @@ -```python -import {{{packageName}}} -from {{packageName}}.{{apiPackage}} import {{classFilename}} -{{#each imports}} -{{{.}}} -{{/each}} -from pprint import pprint -{{> doc_auth_partial}} -# Enter a context with an instance of the API client -with {{{packageName}}}.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = {{classFilename}}.{{{classname}}}(api_client) -{{#if requiredParams}} - - # example passing only required values which don't have defaults set -{{#if pathParams}} - path_params = { - {{#each pathParams}} - {{#if required}} - '{{baseName}}': {{{example}}}, - {{/if}} - {{/each}} - } -{{/if}} -{{#if queryParams}} - query_params = { - {{#each queryParams}} - {{#if required}} - '{{baseName}}': {{{example}}}, - {{/if}} - {{/each}} - } -{{/if}} -{{#if cookieParams}} - cookie_params = { - {{#each cookieParams}} - {{#if required}} - '{{baseName}}': {{{example}}}, - {{/if}} - {{/each}} - } -{{/if}} -{{#if headerParams}} - header_params = { - {{#each headerParams}} - {{#if required}} - '{{baseName}}': {{{example}}}, - {{/if}} - {{/each}} - } -{{/if}} -{{#with bodyParam}} - {{#if required}} - body = {{{example}}} - {{/if}} -{{/with}} - try: -{{#if summary}} - # {{{summary}}} -{{/if}} - api_response = api_instance.{{{operationId}}}( - {{#if pathParams}} - path_params=path_params, - {{/if}} - {{#if queryParams}} - query_params=query_params, - {{/if}} - {{#if headerParams}} - header_params=header_params, - {{/if}} - {{#if cookieParams}} - cookie_params=cookie_params, - {{/if}} - {{#with bodyParam}} - {{#if required}} - body=body, - {{/if}} - {{/with}} - ) -{{#if returnType}} - pprint(api_response) -{{/if}} - except {{{packageName}}}.ApiException as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -{{/if}} -{{#if optionalParams}} - - # example passing only optional values -{{#if pathParams}} - path_params = { - {{#each pathParams}} - '{{baseName}}': {{{example}}}, - {{/each}} - } -{{/if}} -{{#if queryParams}} - query_params = { - {{#each queryParams}} - '{{baseName}}': {{{example}}}, - {{/each}} - } -{{/if}} -{{#if cookieParams}} - cookie_params = { - {{#each cookieParams}} - '{{baseName}}': {{{example}}}, - {{/each}} - } -{{/if}} -{{#if headerParams}} - header_params = { - {{#each headerParams}} - '{{baseName}}': {{{example}}}, - {{/each}} - } -{{/if}} -{{#with bodyParam}} - body = {{{example}}} -{{/with}} - try: -{{#if summary}} - # {{{summary}}} -{{/if}} - api_response = api_instance.{{{operationId}}}( - {{#if pathParams}} - path_params=path_params, - {{/if}} - {{#if queryParams}} - query_params=query_params, - {{/if}} - {{#if headerParams}} - header_params=header_params, - {{/if}} - {{#if cookieParams}} - cookie_params=cookie_params, - {{/if}} - {{#if bodyParam}} - body=body, - {{/if}} - ) -{{#if returnType}} - pprint(api_response) -{{/if}} - except {{{packageName}}}.ApiException as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -{{/if}} -{{#unless requiredParams}} -{{#unless optionalParams}} - - # example, this endpoint has no required or optional parameters - try: -{{#if summary}} - # {{{summary}}} -{{/if}} - api_response = api_instance.{{{operationId}}}() -{{#if returnType}} - pprint(api_response) -{{/if}} - except {{{packageName}}}.ApiException as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -{{/unless}} -{{/unless}} -``` diff --git a/openapi/python_explerimental_client_template/api_doc_schema_type_hint.handlebars b/openapi/python_explerimental_client_template/api_doc_schema_type_hint.handlebars deleted file mode 100644 index 0698320a..00000000 --- a/openapi/python_explerimental_client_template/api_doc_schema_type_hint.handlebars +++ /dev/null @@ -1,10 +0,0 @@ - -#### {{baseName}} -{{#if complexType}} -Type | Description | Notes -------------- | ------------- | ------------- -[**{{dataType}}**]({{complexType}}.md) | {{description}} | {{#if isReadOnly}}[readonly] {{/if}} - -{{else}} -{{> schema_doc }} -{{/if}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/endpoint.handlebars b/openapi/python_explerimental_client_template/endpoint.handlebars deleted file mode 100644 index f6de978e..00000000 --- a/openapi/python_explerimental_client_template/endpoint.handlebars +++ /dev/null @@ -1,549 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -from dataclasses import dataclass -import re # noqa: F401 -import sys # noqa: F401 -import typing -import urllib3 -{{#with operation}} -{{#or headerParams bodyParam produces}} -from urllib3._collections import HTTPHeaderDict -{{/or}} -{{/with}} - -from {{packageName}} import api_client, exceptions -{{> model_templates/imports_schema_types }} -{{> model_templates/imports_schemas }} - -{{#with operation}} -{{#if queryParams}} -# query params -{{#each queryParams}} -{{#with schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} -RequestRequiredQueryParams = typing.TypedDict( - 'RequestRequiredQueryParams', - { -{{#each queryParams}} -{{#if required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/if}} -{{/each}} - } -) -RequestOptionalQueryParams = typing.TypedDict( - 'RequestOptionalQueryParams', - { -{{#each queryParams}} -{{#unless required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/unless}} -{{/each}} - }, - total=False -) - - -class RequestQueryParams(RequestRequiredQueryParams, RequestOptionalQueryParams): - pass - - -{{#each queryParams}} -{{> endpoint_parameter }} -{{/each}} -{{/if}} -{{#if headerParams}} -# header params -{{#each headerParams}} -{{#with schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} -RequestRequiredHeaderParams = typing.TypedDict( - 'RequestRequiredHeaderParams', - { -{{#each headerParams}} -{{#if required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/if}} -{{/each}} - } -) -RequestOptionalHeaderParams = typing.TypedDict( - 'RequestOptionalHeaderParams', - { -{{#each headerParams}} -{{#unless required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/unless}} -{{/each}} - }, - total=False -) - - -class RequestHeaderParams(RequestRequiredHeaderParams, RequestOptionalHeaderParams): - pass - - -{{#each headerParams}} -{{> endpoint_parameter }} -{{/each}} -{{/if}} -{{#if pathParams}} -# path params -{{#each pathParams}} -{{#with schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} -RequestRequiredPathParams = typing.TypedDict( - 'RequestRequiredPathParams', - { -{{#each pathParams}} -{{#if required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/if}} -{{/each}} - } -) -RequestOptionalPathParams = typing.TypedDict( - 'RequestOptionalPathParams', - { -{{#each pathParams}} -{{#unless required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/unless}} -{{/each}} - }, - total=False -) - - -class RequestPathParams(RequestRequiredPathParams, RequestOptionalPathParams): - pass - - -{{#each pathParams}} -{{> endpoint_parameter }} -{{/each}} -{{/if}} -{{#if cookieParams}} -# cookie params -{{#each cookieParams}} -{{#with schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} -RequestRequiredCookieParams = typing.TypedDict( - 'RequestRequiredCookieParams', - { -{{#each cookieParams}} -{{#if required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/if}} -{{/each}} - } -) -RequestOptionalCookieParams = typing.TypedDict( - 'RequestOptionalCookieParams', - { -{{#each cookieParams}} -{{#unless required}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/unless}} -{{/each}} - }, - total=False -) - - -class RequestCookieParams(RequestRequiredCookieParams, RequestOptionalCookieParams): - pass - - -{{#each cookieParams}} -{{> endpoint_parameter }} -{{/each}} -{{/if}} -{{#with bodyParam}} -# body param -{{#each content}} -{{#with this.schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} - - -request_body_{{paramName}} = api_client.RequestBody( - content={ -{{#each content}} - '{{@key}}': api_client.MediaType( - schema={{this.schema.baseName}}), -{{/each}} - }, -{{#if required}} - required=True, -{{/if}} -) -{{/with}} -_path = '{{{path}}}' -_method = '{{httpMethod}}' -{{#each authMethods}} -{{#if @first}} -_auth = [ -{{/if}} - '{{name}}', -{{#if @last}} -] -{{/if}} -{{/each}} -{{#each servers}} -{{#if @first}} -_servers = ( -{{/if}} - { - 'url': "{{{url}}}", - 'description': "{{{description}}}{{#unless description}}No description provided{{/unless}}", - {{#each variables}} - {{#if @first}} - 'variables': { - {{/if}} - '{{{name}}}': { - 'description': "{{{description}}}{{#unless description}}No description provided{{/unless}}", - 'default_value': "{{{defaultValue}}}", - {{#each enumValues}} - {{#if @first}} - 'enum_values': [ - {{/if}} - "{{{.}}}"{{#unless @last}},{{/unless}} - {{#if @last}} - ] - {{/if}} - {{/each}} - }{{#unless @last}},{{/unless}} - {{#if @last}} - } - {{/if}} - {{/each}} - }, -{{#if @last}} -) -{{/if}} -{{/each}} -{{#each responses}} -{{#each responseHeaders}} -{{#with schema}} -{{> model_templates/schema }} -{{/with}} -{{paramName}}_parameter = api_client.HeaderParameter( - name="{{baseName}}", -{{#if style}} - style=api_client.ParameterStyle.{{style}}, -{{/if}} -{{#if schema}} -{{#with schema}} - schema={{baseName}}, -{{/with}} -{{/if}} -{{#if required}} - required=True, -{{/if}} -{{#if isExplode}} - explode=True, -{{/if}} -) -{{/each}} -{{#each content}} -{{#with this.schema}} -{{> model_templates/schema }} -{{/with}} -{{/each}} -{{#if responseHeaders}} -ResponseHeadersFor{{code}} = typing.TypedDict( - 'ResponseHeadersFor{{code}}', - { -{{#each responseHeaders}} - '{{baseName}}': {{#with schema}}{{baseName}},{{/with}} -{{/each}} - } -) -{{/if}} - - -@dataclass -{{#if isDefault}} -class ApiResponseForDefault(api_client.ApiResponse): -{{else}} -class ApiResponseFor{{code}}(api_client.ApiResponse): -{{/if}} - response: urllib3.HTTPResponse -{{#and responseHeaders content}} - body: typing.Union[ -{{#each content}} - {{this.schema.baseName}}, -{{/each}} - ] - headers: ResponseHeadersFor{{code}} -{{else}} -{{#or responseHeaders content}} -{{#if responseHeaders}} - headers: ResponseHeadersFor{{code}} - body: Unset = unset -{{else}} - body: typing.Union[ -{{#each content}} - {{this.schema.baseName}}, -{{/each}} - ] - headers: Unset = unset -{{/if}} -{{/or}} -{{/and}} -{{#unless responseHeaders}} -{{#unless content}} - body: Unset = unset - headers: Unset = unset -{{/unless}} -{{/unless}} - - -{{#if isDefault}} -_response_for_default = api_client.OpenApiResponse( - response_cls=ApiResponseForDefault, -{{else}} -_response_for_{{code}} = api_client.OpenApiResponse( - response_cls=ApiResponseFor{{code}}, -{{/if}} -{{#each content}} -{{#if @first}} - content={ -{{/if}} - '{{@key}}': api_client.MediaType( - schema={{this.schema.baseName}}), -{{#if @last}} - }, -{{/if}} -{{/each}} -{{#if responseHeaders}} - headers=[ -{{#each responseHeaders}} - {{paramName}}_parameter, -{{/each}} - ] -{{/if}} -) -{{/each}} -_status_code_to_response = { -{{#each responses}} -{{#if isDefault}} - 'default': _response_for_default, -{{else}} - '{{code}}': _response_for_{{code}}, -{{/if}} -{{/each}} -} -{{#each produces}} -{{#if @first}} -_all_accept_content_types = ( -{{/if}} - '{{this.mediaType}}', -{{#if @last}} -) -{{/if}} -{{/each}} - - -class {{operationIdCamelCase}}(api_client.Api): - - def {{operationId}}( - self: api_client.Api, - {{#if bodyParam}} - {{#with bodyParam}} - {{baseName}}: typing.Union[{{#each content}}{{#unless @first}}, {{/unless}}{{this.schema.baseName}}{{/each}}{{#unless required}}, Unset] = unset{{else}}]{{/unless}}, - {{/with}} - {{/if}} - {{#if queryParams}} - query_params: RequestQueryParams = frozendict(), - {{/if}} - {{#if headerParams}} - header_params: RequestHeaderParams = frozendict(), - {{/if}} - {{#if pathParams}} - path_params: RequestPathParams = frozendict(), - {{/if}} - {{#if cookieParams}} - cookie_params: RequestCookieParams = frozendict(), - {{/if}} - {{#with bodyParam}} - {{#each content}} - {{#if @first}} - content_type: str = '{{@key}}', - {{/if}} - {{/each}} - {{/with}} - {{#if produces}} - accept_content_types: typing.Tuple[str] = _all_accept_content_types, - {{/if}} - {{#if servers}} - host_index: typing.Optional[int] = None, - {{/if}} - stream: bool = False, - timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, - skip_deserialization: bool = False, - ) -> typing.Union[ - {{#each responses}} - {{#if isDefault}} - ApiResponseForDefault, - {{else}} - {{#if is2xx}} - ApiResponseFor{{code}}, - {{/if}} - {{/if}} - {{/each}} - api_client.ApiResponseWithoutDeserialization - ]: - """ - {{#if summary}} - {{summary}} - {{/if}} - :param skip_deserialization: If true then api_response.response will be set but - api_response.body and api_response.headers will not be deserialized into schema - class instances - """ - {{#if queryParams}} - self._verify_typed_dict_inputs(RequestQueryParams, query_params) - {{/if}} - {{#if headerParams}} - self._verify_typed_dict_inputs(RequestHeaderParams, header_params) - {{/if}} - {{#if pathParams}} - self._verify_typed_dict_inputs(RequestPathParams, path_params) - {{/if}} - {{#if cookieParams}} - self._verify_typed_dict_inputs(RequestCookieParams, cookie_params) - {{/if}} - {{#if pathParams}} - - _path_params = {} - for parameter in ( - {{#each pathParams}} - request_path_{{paramName}}, - {{/each}} - ): - parameter_data = path_params.get(parameter.name, unset) - if parameter_data is unset: - continue - serialized_data = parameter.serialize(parameter_data) - _path_params.update(serialized_data) - {{/if}} - {{#if queryParams}} - - _query_params = [] - for parameter in ( - {{#each queryParams}} - request_query_{{paramName}}, - {{/each}} - ): - parameter_data = query_params.get(parameter.name, unset) - if parameter_data is unset: - continue - serialized_data = parameter.serialize(parameter_data) - _query_params.extend(serialized_data) - {{/if}} - {{#or headerParams bodyParam produces}} - - _headers = HTTPHeaderDict() - {{else}} - {{/or}} - {{#if headerParams}} - for parameter in ( - {{#each headerParams}} - request_header_{{paramName}}, - {{/each}} - ): - parameter_data = header_params.get(parameter.name, unset) - if parameter_data is unset: - continue - serialized_data = parameter.serialize(parameter_data) - _headers.extend(serialized_data) - {{/if}} - # TODO add cookie handling - {{#if produces}} - if accept_content_types: - for accept_content_type in accept_content_types: - _headers.add('Accept', accept_content_type) - {{/if}} - {{#with bodyParam}} - - {{#if required}} - if body is unset: - raise exceptions.ApiValueError( - 'The required body parameter has an invalid value of: unset. Set a valid value instead') - {{/if}} - _fields = None - _body = None - {{#if required}} - {{> endpoint_body_serialization }} - {{else}} - if body is not unset: - {{> endpoint_body_serialization }} - {{/if}} - {{/with}} - {{#if servers}} - - host = self.get_host('{{operationId}}', _servers, host_index) - {{/if}} - - response = self.api_client.call_api( - resource_path=_path, - method=_method, - {{#if pathParams}} - path_params=_path_params, - {{/if}} - {{#if queryParams}} - query_params=tuple(_query_params), - {{/if}} - {{#or headerParams bodyParam produces}} - headers=_headers, - {{/or}} - {{#if bodyParam}} - fields=_fields, - body=_body, - {{/if}} - {{#if hasAuthMethods}} - auth_settings=_auth, - {{/if}} - {{#if servers}} - host=host, - {{/if}} - stream=stream, - timeout=timeout, - ) - - if skip_deserialization: - api_response = api_client.ApiResponseWithoutDeserialization(response=response) - else: - response_for_status = _status_code_to_response.get(str(response.status)) - if response_for_status: - api_response = response_for_status.deserialize(response, self.api_client.configuration) - else: - {{#if hasDefaultResponse}} - default_response = _status_code_to_response.get('default') - if default_response: - api_response = default_response.deserialize(response, self.api_client.configuration) - else: - api_response = api_client.ApiResponseWithoutDeserialization(response=response) - {{else}} - api_response = api_client.ApiResponseWithoutDeserialization(response=response) - {{/if}} - - if not 200 <= response.status <= 299: - raise exceptions.ApiException(api_response=api_response) - - return api_response -{{/with}} diff --git a/openapi/python_explerimental_client_template/endpoint_body_serialization.handlebars b/openapi/python_explerimental_client_template/endpoint_body_serialization.handlebars deleted file mode 100644 index f00d9f05..00000000 --- a/openapi/python_explerimental_client_template/endpoint_body_serialization.handlebars +++ /dev/null @@ -1,6 +0,0 @@ -serialized_data = request_body_{{paramName}}.serialize(body, content_type) -_headers.add('Content-Type', content_type) -if 'fields' in serialized_data: - _fields = serialized_data['fields'] -elif 'body' in serialized_data: - _body = serialized_data['body'] \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/endpoint_parameter.handlebars b/openapi/python_explerimental_client_template/endpoint_parameter.handlebars deleted file mode 100644 index 4b9d815a..00000000 --- a/openapi/python_explerimental_client_template/endpoint_parameter.handlebars +++ /dev/null @@ -1,17 +0,0 @@ -request_{{#if isQueryParam}}query{{/if}}{{#if isPathParam}}path{{/if}}{{#if isHeaderParam}}header{{/if}}{{#if isCookieParam}}cookie{{/if}}_{{paramName}} = api_client.{{#if isQueryParam}}Query{{/if}}{{#if isPathParam}}Path{{/if}}{{#if isHeaderParam}}Header{{/if}}{{#if isCookieParam}}Cookie{{/if}}Parameter( - name="{{baseName}}", -{{#if style}} - style=api_client.ParameterStyle.{{style}}, -{{/if}} -{{#if schema}} -{{#with schema}} - schema={{baseName}}, -{{/with}} -{{/if}} -{{#if required}} - required=True, -{{/if}} -{{#if isExplode}} - explode=True, -{{/if}} -) diff --git a/openapi/python_explerimental_client_template/model.handlebars b/openapi/python_explerimental_client_template/model.handlebars deleted file mode 100644 index d8c1e63a..00000000 --- a/openapi/python_explerimental_client_template/model.handlebars +++ /dev/null @@ -1,17 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -import re # noqa: F401 -import sys # noqa: F401 -import typing # noqa: F401 - -from frozendict import frozendict # noqa: F401 - -{{#each models}} -{{#with model}} -{{> model_templates/imports_schema_types }} -{{> model_templates/schema }} -{{> model_templates/imports_schemas }} -{{/with}} -{{/each}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_doc.handlebars b/openapi/python_explerimental_client_template/model_doc.handlebars deleted file mode 100644 index 2c4136a1..00000000 --- a/openapi/python_explerimental_client_template/model_doc.handlebars +++ /dev/null @@ -1,9 +0,0 @@ -{{#each models}} -{{#with model}} -# {{classname}} -{{> schema_doc }} -{{/with}} -{{/each}} - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - diff --git a/openapi/python_explerimental_client_template/model_templates/composed_schemas.handlebars b/openapi/python_explerimental_client_template/model_templates/composed_schemas.handlebars deleted file mode 100644 index 6aef3311..00000000 --- a/openapi/python_explerimental_client_template/model_templates/composed_schemas.handlebars +++ /dev/null @@ -1,86 +0,0 @@ -@classmethod -@property -def _composed_schemas(cls): - # we need this here to make our import statements work - # we must store _composed_schemas in here so the code is only run - # when we invoke this method. If we kept this at the class - # level we would get an error because the class level - # code would be run when this module is imported, and these composed - # classes don't exist yet because their module has not finished - # loading -{{#with composedSchemas}} -{{#each allOf}} -{{#unless complexType}} -{{#unless isAnyType}} - {{> model_templates/schema }} -{{/unless}} -{{/unless}} -{{#if isAnyType}} - {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}} = AnyTypeSchema -{{/if}} -{{/each}} -{{#each oneOf}} -{{#unless complexType}} -{{#unless isAnyType}} - {{> model_templates/schema }} -{{/unless}} -{{/unless}} -{{#if isAnyType}} - {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}} = AnyTypeSchema -{{/if}} -{{/each}} -{{#each anyOf}} -{{#unless complexType}} -{{#unless isAnyType}} - {{> model_templates/schema }} -{{/unless}} -{{/unless}} -{{#if isAnyType}} - {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}} = AnyTypeSchema -{{/if}} -{{/each}} -{{/with}} - return { - 'allOf': [ -{{#with composedSchemas}} -{{#each allOf}} -{{#if complexType}} - {{complexType}}, -{{/if}} -{{#unless complexType}} - {{#if nameInSnakeCase}}{{name}}{{/if}}{{#unless nameInSnakeCase}}{{baseName}}{{/unless}}, -{{/unless}} -{{/each}} - ], - 'oneOf': [ -{{#each oneOf}} -{{#if complexType}} - {{complexType}}, -{{/if}} -{{#unless complexType}} -{{#if isAnyType}} - AnyTypeSchema, -{{/if}} -{{#unless isAnyType}} - {{#if nameInSnakeCase}}{{name}}{{/if}}{{#unless nameInSnakeCase}}{{baseName}}{{/unless}}, -{{/unless}} -{{/unless}} -{{/each}} - ], - 'anyOf': [ -{{#each anyOf}} -{{#if complexType}} - {{complexType}}, -{{/if}} -{{#unless complexType}} -{{#if isAnyType}} - AnyTypeSchema, -{{/if}} -{{#unless isAnyType}} - {{#if nameInSnakeCase}}{{name}}{{/if}}{{#unless nameInSnakeCase}}{{baseName}}{{/unless}}, -{{/unless}} -{{/unless}} -{{/each}} -{{/with}} - ], - } diff --git a/openapi/python_explerimental_client_template/model_templates/dict_partial.handlebars b/openapi/python_explerimental_client_template/model_templates/dict_partial.handlebars deleted file mode 100644 index 15338b38..00000000 --- a/openapi/python_explerimental_client_template/model_templates/dict_partial.handlebars +++ /dev/null @@ -1,54 +0,0 @@ -{{#if getHasRequired}} - _required_property_names = set(( - {{#each requiredVars}} - '{{baseName}}', - {{/each}} - )) -{{/if}} -{{#each vars}} -{{#if complexType}} - - @classmethod - @property - def {{baseName}}(cls) -> typing.Type['{{complexType}}']: - return {{complexType}} -{{else}} - {{> model_templates/schema }} -{{/if}} -{{/each}} -{{#if getHasDiscriminatorWithNonEmptyMapping}} -{{#with discriminator}} -{{#each mappedModels}} -{{#if @first}} - - @classmethod - @property - def _discriminator(cls): - return { - '{{{propertyBaseName}}}': { -{{/if}} - '{{mappingName}}': {{{modelName}}}, -{{#if @last}} - } - } -{{/if}} -{{/each}} -{{/with}} -{{/if}} -{{#with additionalProperties}} -{{#if complexType}} - - @classmethod - @property - def _additional_properties(cls) -> typing.Type['{{complexType}}']: - return {{complexType}} -{{/if}} -{{#unless complexType}} -{{#unless isAnyType}} - {{> model_templates/schema }} -{{/unless}} -{{/unless}} -{{/with}} -{{#unless additionalProperties}} - _additional_properties = None -{{/unless}} diff --git a/openapi/python_explerimental_client_template/model_templates/enum_value_to_name.handlebars b/openapi/python_explerimental_client_template/model_templates/enum_value_to_name.handlebars deleted file mode 100644 index ea2cb759..00000000 --- a/openapi/python_explerimental_client_template/model_templates/enum_value_to_name.handlebars +++ /dev/null @@ -1,12 +0,0 @@ -_SchemaEnumMaker( - enum_value_to_name={ -{{#if isNull}} - None: "NONE", -{{/if}} -{{#with allowableValues}} -{{#each enumVars}} - {{{value}}}: "{{name}}", -{{/each}} -{{/with}} - } -), diff --git a/openapi/python_explerimental_client_template/model_templates/enums.handlebars b/openapi/python_explerimental_client_template/model_templates/enums.handlebars deleted file mode 100644 index 5cbc68dc..00000000 --- a/openapi/python_explerimental_client_template/model_templates/enums.handlebars +++ /dev/null @@ -1,16 +0,0 @@ -{{#if isNull}} - -@classmethod -@property -def NONE(cls): - return cls._enum_by_value[None](None) -{{/if}} -{{#with allowableValues}} -{{#each enumVars}} - -@classmethod -@property -def {{name}}(cls): - return cls._enum_by_value[{{{value}}}]({{{value}}}) -{{/each}} -{{/with}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_templates/imports_schema_types.handlebars b/openapi/python_explerimental_client_template/model_templates/imports_schema_types.handlebars deleted file mode 100644 index 38015e35..00000000 --- a/openapi/python_explerimental_client_template/model_templates/imports_schema_types.handlebars +++ /dev/null @@ -1,42 +0,0 @@ -import decimal # noqa: F401 -from datetime import date, datetime # noqa: F401 -from frozendict import frozendict # noqa: F401 - -from {{packageName}}.schemas import ( # noqa: F401 - AnyTypeSchema, - ComposedSchema, - DictSchema, - ListSchema, - StrSchema, - IntSchema, - Int32Schema, - Int64Schema, - Float32Schema, - Float64Schema, - NumberSchema, - DateSchema, - DateTimeSchema, - DecimalSchema, - BoolSchema, - BinarySchema, - NoneSchema, - none_type, - InstantiationMetadata, - Unset, - unset, - ComposedBase, - ListBase, - DictBase, - NoneBase, - StrBase, - IntBase, - NumberBase, - DateBase, - DateTimeBase, - BoolBase, - BinaryBase, - Schema, - _SchemaValidator, - _SchemaTypeChecker, - _SchemaEnumMaker -) diff --git a/openapi/python_explerimental_client_template/model_templates/imports_schemas.handlebars b/openapi/python_explerimental_client_template/model_templates/imports_schemas.handlebars deleted file mode 100644 index b925cf92..00000000 --- a/openapi/python_explerimental_client_template/model_templates/imports_schemas.handlebars +++ /dev/null @@ -1,6 +0,0 @@ -{{#each imports}} -{{#if @first}} - -{{/if}} -{{{.}}} -{{/each}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_templates/new.handlebars b/openapi/python_explerimental_client_template/model_templates/new.handlebars deleted file mode 100644 index e74461e8..00000000 --- a/openapi/python_explerimental_client_template/model_templates/new.handlebars +++ /dev/null @@ -1,53 +0,0 @@ -def __new__( - cls, - *args: typing.Union[{{#if isAnyType}}dict, frozendict, str, date, datetime, int, float, decimal.Decimal, None, list, tuple, bytes{{/if}}{{#if isUnboundedInteger}}int, {{/if}}{{#if isNumber}}float, {{/if}}{{#if isBoolean}}bool, {{/if}}{{#if isArray}}list, tuple, {{/if}}{{#if isMap}}dict, frozendict, {{/if}}{{#if isString}}str, {{/if}}{{#if isNull}}None, {{/if}}], -{{#unless isNull}} -{{#if getHasRequired}} -{{#each requiredVars}} -{{#unless nameInSnakeCase}} - {{baseName}}: {{baseName}}, -{{/unless}} -{{/each}} -{{/if}} -{{/unless}} -{{#each vars}} -{{#unless nameInSnakeCase}} -{{#unless getRequired}} -{{#unless complexType}} - {{baseName}}: typing.Union[{{baseName}}, Unset] = unset, -{{/unless}} -{{#if complexType}} - {{baseName}}: typing.Union['{{complexType}}', Unset] = unset, -{{/if}} -{{/unless}} -{{/unless}} -{{/each}} - _instantiation_metadata: typing.Optional[InstantiationMetadata] = None, -{{#with additionalProperties}} - **kwargs: typing.Type[Schema], -{{/with}} -) -> '{{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}}': - return super().__new__( - cls, - *args, -{{#unless isNull}} -{{#if getHasRequired}} -{{#each requiredVars}} -{{#unless nameInSnakeCase}} - {{baseName}}={{baseName}}, -{{/unless}} -{{/each}} -{{/if}} -{{/unless}} -{{#each vars}} -{{#unless getRequired}} -{{#unless nameInSnakeCase}} - {{baseName}}={{baseName}}, -{{/unless}} -{{/unless}} -{{/each}} - _instantiation_metadata=_instantiation_metadata, -{{#with additionalProperties}} - **kwargs, -{{/with}} - ) diff --git a/openapi/python_explerimental_client_template/model_templates/schema.handlebars b/openapi/python_explerimental_client_template/model_templates/schema.handlebars deleted file mode 100644 index 7523d39f..00000000 --- a/openapi/python_explerimental_client_template/model_templates/schema.handlebars +++ /dev/null @@ -1,46 +0,0 @@ -{{#if composedSchemas}} -{{> model_templates/schema_composed_or_anytype }} -{{/if}} -{{#unless composedSchemas}} - {{#if getHasMultipleTypes}} -{{> model_templates/schema_composed_or_anytype }} - {{else}} - {{#or isMap isArray isAnyType}} - {{#if isMap}} - {{#or hasVars hasValidation hasRequiredVars getHasDiscriminatorWithNonEmptyMapping}} -{{> model_templates/schema_dict }} - {{else}} - {{#if additionalPropertiesIsAnyType}} -{{> model_templates/var_equals_cls }} - {{else}} -{{> model_templates/schema_dict }} - {{/if}} - {{/or}} - {{/if}} - {{#if isArray}} - {{#or hasItems hasValidation}} -{{> model_templates/schema_list }} - {{else}} -{{> model_templates/var_equals_cls }} - {{/or}} - {{/if}} - {{#if isAnyType}} - {{#or isEnum hasVars hasValidation hasRequiredVars getHasDiscriminatorWithNonEmptyMapping items}} -{{> model_templates/schema_composed_or_anytype }} - {{else}} -{{> model_templates/var_equals_cls }} - {{/or}} - {{/if}} - {{else}} - {{#or isEnum hasValidation}} -{{> model_templates/schema_simple }} - {{else}} -{{> model_templates/var_equals_cls }} - {{/or}} - {{/or}} - {{#if nameInSnakeCase}} -locals()['{{baseName}}'] = {{name}} -del locals()['{{name}}'] - {{/if}} - {{/if}} -{{/unless}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_templates/schema_composed_or_anytype.handlebars b/openapi/python_explerimental_client_template/model_templates/schema_composed_or_anytype.handlebars deleted file mode 100644 index 763da96f..00000000 --- a/openapi/python_explerimental_client_template/model_templates/schema_composed_or_anytype.handlebars +++ /dev/null @@ -1,48 +0,0 @@ - - -class {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}}( -{{#if hasValidation}} - {{> model_templates/validations }} -{{/if}} -{{#if getIsAnyType}} - {{#if composedSchemas}} - ComposedSchema - {{else}} - AnyTypeSchema - {{/if}} -{{else}} - {{#if getHasMultipleTypes}} - _SchemaTypeChecker(typing.Union[{{#if isArray}}tuple, {{/if}}{{#if isMap}}frozendict, {{/if}}{{#if isNull}}none_type, {{/if}}{{#if isString}}str, {{/if}}{{#if isByteArray}}str, {{/if}}{{#if isUnboundedInteger}}decimal.Decimal, {{/if}}{{#if isShort}}decimal.Decimal, {{/if}}{{#if isLong}}decimal.Decimal, {{/if}}{{#if isFloat}}decimal.Decimal, {{/if}}{{#if isDouble}}decimal.Decimal, {{/if}}{{#if isNumber}}decimal.Decimal, {{/if}}{{#if isDate}}str, {{/if}}{{#if isDateTime}}str, {{/if}}{{#if isDecimal}}str, {{/if}}{{#if isBoolean}}bool, {{/if}}]), - {{/if}} - {{#if composedSchemas}} - ComposedBase, - {{/if}} - {{#if isEnum}} - {{> model_templates/enum_value_to_name }} - {{/if}} - {{> model_templates/xbase_schema }} -{{/if}} -): -{{#if this.classname}} - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. -{{#if description}} - - {{{unescapedDescription}}} -{{/if}} - """ -{{/if}} -{{#or isMap isAnyType}} -{{> model_templates/dict_partial }} -{{/or}} -{{#if composedSchemas}} - - {{> model_templates/composed_schemas }} -{{/if}} -{{#if isEnum}} - {{> model_templates/enums }} -{{/if}} - - {{> model_templates/new }} diff --git a/openapi/python_explerimental_client_template/model_templates/schema_dict.handlebars b/openapi/python_explerimental_client_template/model_templates/schema_dict.handlebars deleted file mode 100644 index c17e0f57..00000000 --- a/openapi/python_explerimental_client_template/model_templates/schema_dict.handlebars +++ /dev/null @@ -1,23 +0,0 @@ - - -class {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}}( -{{#if hasValidation}} - {{> model_templates/validations }} -{{/if}} - DictSchema -): -{{#if this.classname}} - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. -{{#if description}} - - {{{unescapedDescription}}} -{{/if}} - """ -{{/if}} -{{> model_templates/dict_partial }} - - - {{> model_templates/new }} diff --git a/openapi/python_explerimental_client_template/model_templates/schema_list.handlebars b/openapi/python_explerimental_client_template/model_templates/schema_list.handlebars deleted file mode 100644 index 542b486a..00000000 --- a/openapi/python_explerimental_client_template/model_templates/schema_list.handlebars +++ /dev/null @@ -1,30 +0,0 @@ - - -class {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}}( -{{#if hasValidation}} - {{> model_templates/validations }} -{{/if}} - ListSchema -): -{{#if this.classname}} - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. -{{#if description}} - - {{{unescapedDescription}}} -{{/if}} - """ -{{/if}} -{{#with items}} -{{#if complexType}} - - @classmethod - @property - def _items(cls) -> typing.Type['{{complexType}}']: - return {{complexType}} -{{else}} - {{> model_templates/schema }} -{{/if}} -{{/with}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_templates/schema_simple.handlebars b/openapi/python_explerimental_client_template/model_templates/schema_simple.handlebars deleted file mode 100644 index ea12a00f..00000000 --- a/openapi/python_explerimental_client_template/model_templates/schema_simple.handlebars +++ /dev/null @@ -1,27 +0,0 @@ - - -class {{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}}( -{{#if hasValidation}} - {{> model_templates/validations }} -{{/if}} -{{#if isEnum}} - {{> model_templates/enum_value_to_name }} -{{/if}} - {{> model_templates/xbase_schema }} -): -{{#if this.classname}} - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. -{{#if description}} - - {{{unescapedDescription}}} -{{/if}} - """ -{{/if}} -{{#if isEnum}} - {{> model_templates/enums }} -{{else}} - pass -{{/if}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/model_templates/validations.handlebars b/openapi/python_explerimental_client_template/model_templates/validations.handlebars deleted file mode 100644 index 2384ac06..00000000 --- a/openapi/python_explerimental_client_template/model_templates/validations.handlebars +++ /dev/null @@ -1,50 +0,0 @@ -_SchemaValidator( -{{#if getUniqueItems}} - unique_items=True, -{{/if}} -{{#if maxLength}} - max_length={{maxLength}}, -{{/if}} -{{#if minLength}} - min_length={{minLength}}, -{{/if}} -{{#if maxItems}} - max_items={{maxItems}}, -{{/if}} -{{#if minItems}} - min_items={{minItems}}, -{{/if}} -{{#if maxProperties}} - max_properties={{maxProperties}}, -{{/if}} -{{#if minProperties}} - min_properties={{minProperties}}, -{{/if}} -{{#if maximum}} - {{#if exclusiveMaximum}}exclusive_maximum{{/if}}inclusive_maximum{{#unless exclusiveMaximum}}{{/unless}}={{maximum}}, -{{/if}} -{{#if minimum}} - {{#if exclusiveMinimum}}exclusive_minimum{{/if}}inclusive_minimum{{#unless exclusiveMinimum}}{{/unless}}={{minimum}}, -{{/if}} -{{#if pattern}} - regex=[{ -{{#if vendorExtensions.x-regex}} - 'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501 -{{else}} - 'pattern': r'{{{pattern}}}', # noqa: E501 -{{/if}} -{{#each vendorExtensions.x-modifiers}} -{{#if @first}} - 'flags': ( -{{/if}} - {{#unless @first}}| {{/unless}}re.{{.}} -{{#if @last}} - ) -{{/if}} -{{/each}} - }], -{{/if}} -{{#if multipleOf}} - multiple_of=[{{multipleOf}}], -{{/if}} -), diff --git a/openapi/python_explerimental_client_template/model_templates/var_equals_cls.handlebars b/openapi/python_explerimental_client_template/model_templates/var_equals_cls.handlebars deleted file mode 100644 index 24f52cac..00000000 --- a/openapi/python_explerimental_client_template/model_templates/var_equals_cls.handlebars +++ /dev/null @@ -1 +0,0 @@ -{{#if this.classname}}{{classname}}{{else}}{{#if nameInSnakeCase}}{{name}}{{else}}{{baseName}}{{/if}}{{/if}} = {{#if complexType}}{{complexType}}{{else}}{{#if isNullable}}Nullable{{/if}}{{#if getIsNull}}None{{/if}}{{#if isAnyType}}AnyType{{/if}}{{#if isMap}}Dict{{/if}}{{#if isArray}}List{{/if}}{{#if isString}}Str{{/if}}{{#if isByteArray}}Str{{/if}}{{#if isDate}}Date{{/if}}{{#if isDateTime}}DateTime{{/if}}{{#if isDecimal}}Decimal{{/if}}{{#if isUnboundedInteger}}Int{{/if}}{{#if isShort}}Int32{{/if}}{{#if isLong}}Int64{{/if}}{{#if isFloat}}Float32{{/if}}{{#if isDouble}}Float64{{/if}}{{#if isNumber}}Number{{/if}}{{#if isBoolean}}Bool{{/if}}{{#if isBinary}}Binary{{/if}}Schema{{/if}} diff --git a/openapi/python_explerimental_client_template/model_templates/xbase_schema.handlebars b/openapi/python_explerimental_client_template/model_templates/xbase_schema.handlebars deleted file mode 100644 index 87fe3d0b..00000000 --- a/openapi/python_explerimental_client_template/model_templates/xbase_schema.handlebars +++ /dev/null @@ -1,51 +0,0 @@ -{{#if isArray}} -List{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isMap}} -Dict{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isString}} -Str{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isByteArray}} -Str{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isUnboundedInteger}} -Int{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isNumber}} -Number{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#isShort}} -Int32{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/isShort}} -{{#isLong}} -Int64{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/isLong}} -{{#isFloat}} -Float32{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/isFloat}} -{{#isDouble}} -Float64{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/isDouble}} -{{#if isDate}} -Date{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isDateTime}} -DateTime{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isDecimal}} -Decimal{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isBoolean}} -Bool{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isBinary}} -Binary{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if isNull}} -None{{#if getHasMultipleTypes}}Base,{{else}}Schema{{/if}} -{{/if}} -{{#if getHasMultipleTypes}} -Schema -{{/if}} diff --git a/openapi/python_explerimental_client_template/partial_header.handlebars b/openapi/python_explerimental_client_template/partial_header.handlebars deleted file mode 100644 index 19b633be..00000000 --- a/openapi/python_explerimental_client_template/partial_header.handlebars +++ /dev/null @@ -1,17 +0,0 @@ -""" -{{#if appName}} - {{{appName}}} -{{/if}} - -{{#if appDescription}} - {{{appDescription}}} # noqa: E501 -{{/if}} - - {{#if version}} - The version of the OpenAPI document: {{{version}}} - {{/if}} - {{#if infoEmail}} - Contact: {{{infoEmail}}} - {{/if}} - Generated by: https://openapi-generator.tech -""" diff --git a/openapi/python_explerimental_client_template/requirements.handlebars b/openapi/python_explerimental_client_template/requirements.handlebars deleted file mode 100644 index c9227e58..00000000 --- a/openapi/python_explerimental_client_template/requirements.handlebars +++ /dev/null @@ -1,5 +0,0 @@ -certifi >= 14.05.14 -frozendict >= 2.0.3 -python_dateutil >= 2.5.3 -setuptools >= 21.0.0 -urllib3 >= 1.15.1 diff --git a/openapi/python_explerimental_client_template/rest.handlebars b/openapi/python_explerimental_client_template/rest.handlebars deleted file mode 100644 index e3083193..00000000 --- a/openapi/python_explerimental_client_template/rest.handlebars +++ /dev/null @@ -1,251 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -import logging -import ssl -from urllib.parse import urlencode -import typing - -import certifi -import urllib3 -from urllib3._collections import HTTPHeaderDict - -from {{packageName}}.exceptions import ApiException, ApiValueError - - -logger = logging.getLogger(__name__) - - -class RESTClientObject(object): - - def __init__(self, configuration, pools_size=4, maxsize=None): - # urllib3.PoolManager will pass all kw parameters to connectionpool - # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 - # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 - # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 - # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 - - # cert_reqs - if configuration.verify_ssl: - cert_reqs = ssl.CERT_REQUIRED - else: - cert_reqs = ssl.CERT_NONE - - # ca_certs - if configuration.ssl_ca_cert: - ca_certs = configuration.ssl_ca_cert - else: - # if not set certificate file, use Mozilla's root certificates. - ca_certs = certifi.where() - - addition_pool_args = {} - if configuration.assert_hostname is not None: - addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 - - if configuration.retries is not None: - addition_pool_args['retries'] = configuration.retries - - if configuration.socket_options is not None: - addition_pool_args['socket_options'] = configuration.socket_options - - if maxsize is None: - if configuration.connection_pool_maxsize is not None: - maxsize = configuration.connection_pool_maxsize - else: - maxsize = 4 - - # https pool manager - if configuration.proxy: - self.pool_manager = urllib3.ProxyManager( - num_pools=pools_size, - maxsize=maxsize, - cert_reqs=cert_reqs, - ca_certs=ca_certs, - cert_file=configuration.cert_file, - key_file=configuration.key_file, - proxy_url=configuration.proxy, - proxy_headers=configuration.proxy_headers, - **addition_pool_args - ) - else: - self.pool_manager = urllib3.PoolManager( - num_pools=pools_size, - maxsize=maxsize, - cert_reqs=cert_reqs, - ca_certs=ca_certs, - cert_file=configuration.cert_file, - key_file=configuration.key_file, - **addition_pool_args - ) - - def request( - self, - method: str, - url: str, - query_params: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, - headers: typing.Optional[HTTPHeaderDict] = None, - fields: typing.Optional[typing.Tuple[typing.Tuple[str, typing.Any], ...]] = None, - body: typing.Optional[typing.Union[str, bytes]] = None, - stream: bool = False, - timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, - ) -> urllib3.HTTPResponse: - """Perform requests. - - :param method: http request method - :param url: http request url - :param query_params: query parameters in the url - :param headers: http request headers - :param body: request body, for other types - :param fields: request parameters for - `application/x-www-form-urlencoded` - or `multipart/form-data` - :param stream: if True, the urllib3.HTTPResponse object will - be returned without reading/decoding response - data. Default is False. - :param timeout: timeout setting for this request. If one - number provided, it will be total request - timeout. It can also be a pair (tuple) of - (connection, read) timeouts. - """ - method = method.upper() - assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', - 'PATCH', 'OPTIONS'] - - if fields and body: - raise ApiValueError( - "body parameter cannot be used with fields parameter." - ) - - fields = fields or {} - headers = headers or {} - - if timeout: - if isinstance(timeout, (int, float)): # noqa: E501,F821 - timeout = urllib3.Timeout(total=timeout) - elif (isinstance(timeout, tuple) and - len(timeout) == 2): - timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1]) - - if 'Content-Type' not in headers: - headers['Content-Type'] = 'application/json' - - try: - # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` - if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: - if query_params: - url += '?' + urlencode(query_params) - if headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 - r = self.pool_manager.request( - method, url, - fields=fields, - encode_multipart=False, - preload_content=not stream, - timeout=timeout, - headers=headers) - elif headers['Content-Type'] == 'multipart/form-data': - # must del headers['Content-Type'], or the correct - # Content-Type which generated by urllib3 will be - # overwritten. - del headers['Content-Type'] - r = self.pool_manager.request( - method, url, - fields=fields, - encode_multipart=True, - preload_content=not stream, - timeout=timeout, - headers=headers) - # Pass a `string` parameter directly in the body to support - # other content types than Json when `body` argument is - # provided in serialized form - elif isinstance(body, str) or isinstance(body, bytes): - request_body = body - r = self.pool_manager.request( - method, url, - body=request_body, - preload_content=not stream, - timeout=timeout, - headers=headers) - else: - # Cannot generate the request from given parameters - msg = """Cannot prepare a request message for provided - arguments. Please check that your arguments match - declared content type.""" - raise ApiException(status=0, reason=msg) - # For `GET`, `HEAD` - else: - r = self.pool_manager.request(method, url, - fields=query_params, - preload_content=not stream, - timeout=timeout, - headers=headers) - except urllib3.exceptions.SSLError as e: - msg = "{0}\n{1}".format(type(e).__name__, str(e)) - raise ApiException(status=0, reason=msg) - - if not stream: - # log response body - logger.debug("response body: %s", r.data) - - return r - - def GET(self, url, headers=None, query_params=None, stream=False, - timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("GET", url, - headers=headers, - stream=stream, - timeout=timeout, - query_params=query_params, fields=fields) - - def HEAD(self, url, headers=None, query_params=None, stream=False, - timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("HEAD", url, - headers=headers, - stream=stream, - timeout=timeout, - query_params=query_params, fields=fields) - - def OPTIONS(self, url, headers=None, query_params=None, - body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("OPTIONS", url, - headers=headers, - query_params=query_params, - stream=stream, - timeout=timeout, - body=body, fields=fields) - - def DELETE(self, url, headers=None, query_params=None, body=None, - stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("DELETE", url, - headers=headers, - query_params=query_params, - stream=stream, - timeout=timeout, - body=body, fields=fields) - - def POST(self, url, headers=None, query_params=None, - body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("POST", url, - headers=headers, - query_params=query_params, - stream=stream, - timeout=timeout, - body=body, fields=fields) - - def PUT(self, url, headers=None, query_params=None, - body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("PUT", url, - headers=headers, - query_params=query_params, - stream=stream, - timeout=timeout, - body=body, fields=fields) - - def PATCH(self, url, headers=None, query_params=None, - body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse: - return self.request("PATCH", url, - headers=headers, - query_params=query_params, - stream=stream, - timeout=timeout, - body=body, fields=fields) diff --git a/openapi/python_explerimental_client_template/schema_doc.handlebars b/openapi/python_explerimental_client_template/schema_doc.handlebars deleted file mode 100644 index 556d2cbb..00000000 --- a/openapi/python_explerimental_client_template/schema_doc.handlebars +++ /dev/null @@ -1,32 +0,0 @@ - -{{#if description}} -{{&description}} - -{{/if}} -{{#or vars additionalProperties}} -#### Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - {{#each vars}} -**{{baseName}}** | {{#unless complexType}}**{{dataType}}**{{/unless}}{{#if complexType}}[**{{dataType}}**]({{complexType}}.md){{/if}} | {{description}} | {{#unless required}}[optional] {{/unless}}{{#if isReadOnly}}[readonly] {{/if}}{{#if defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/if}} - {{/each}} - {{#with additionalProperties}} -**any string name** | **{{dataType}}** | any string name can be used but the value must be the correct type | [optional] - {{/with}} -{{else}} -Type | Description | Notes -------------- | ------------- | ------------- - {{#if isAnyType}} -typing.Union[dict, frozendict, str, date, datetime, int, float, bool, Decimal, None, list, tuple, bytes] | | - {{else}} - {{#if hasMultipleTypes}} -typing.Union[{{#if isMap}}dict, frozendict, {{/if}}{{#if isString}}str, {{/if}}{{#if isDate}}date, {{/if}}{{#if isDataTime}}datetime, {{/if}}{{#or isLong isShort isUnboundedInteger}}int, {{/or}}{{#or isFloat isDouble}}float, {{/or}}{{#if isNumber}}Decimal, {{/if}}{{#if isBoolean}}bool, {{/if}}{{#if isNull}}None, {{/if}}{{#if isArray}}list, tuple, {{/if}}{{#if isBinary}}bytes{{/if}}] | | {{#with allowableValues}}{{#if defaultValue}}, {{/if}} must be one of [{{#each enumVars}}{{{value}}}, {{/each}}]{{/with}} - {{else}} - {{#if isArray}} -{{#unless arrayModelType}}**{{dataType}}**{{/unless}}{{#if arrayModelType}}[**{{dataType}}**]({{arrayModelType}}.md){{/if}} | {{description}} | {{#if defaultValue}}{{#if hasRequired}} if omitted the server will use the default value of {{/if}}{{#unless hasRequired}}defaults to {{/unless}}{{{defaultValue}}}{{/if}} - {{else}} -{{#unless arrayModelType}}**{{dataType}}**{{/unless}} | {{description}} | {{#if defaultValue}}{{#if hasRequired}} if omitted the server will use the default value of {{/if}}{{#unless hasRequired}}defaults to {{/unless}}{{{defaultValue}}}{{/if}}{{#with allowableValues}}{{#if defaultValue}}, {{/if}} must be one of [{{#each enumVars}}{{{value}}}, {{/each}}]{{/with}} - {{/if}} - {{/if}} - {{/if}} -{{/or}} diff --git a/openapi/python_explerimental_client_template/schemas.handlebars b/openapi/python_explerimental_client_template/schemas.handlebars deleted file mode 100644 index 69dc3895..00000000 --- a/openapi/python_explerimental_client_template/schemas.handlebars +++ /dev/null @@ -1,2038 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -from collections import defaultdict -from datetime import date, datetime, timedelta # noqa: F401 -from dataclasses import dataclass -import functools -import decimal -import io -import os -import re -import tempfile -import typing - -from dateutil.parser.isoparser import isoparser, _takes_ascii -from frozendict import frozendict - -from {{packageName}}.exceptions import ( - ApiTypeError, - ApiValueError, -) -from {{packageName}}.configuration import ( - Configuration, -) - - -class Unset(object): - """ - An instance of this class is set as the default value for object type(dict) properties that are optional - When a property has an unset value, that property will not be assigned in the dict - """ - pass - -unset = Unset() - -none_type = type(None) -file_type = io.IOBase - - -class FileIO(io.FileIO): - """ - A class for storing files - Note: this class is not immutable - """ - - def __new__(cls, arg: typing.Union[io.FileIO, io.BufferedReader]): - if isinstance(arg, (io.FileIO, io.BufferedReader)): - arg.close() - inst = super(FileIO, cls).__new__(cls, arg.name) - super(FileIO, inst).__init__(arg.name) - return inst - raise ApiValueError('FileIO must be passed arg which contains the open file') - - -def update(d: dict, u: dict): - """ - Adds u to d - Where each dict is defaultdict(set) - """ - for k, v in u.items(): - d[k] = d[k].union(v) - return d - - -class InstantiationMetadata: - """ - A class to store metadata that is needed when instantiating OpenApi Schema subclasses - """ - def __init__( - self, - path_to_item: typing.Tuple[typing.Union[str, int], ...] = tuple(['args[0]']), - from_server: bool = False, - configuration: typing.Optional[Configuration] = None, - base_classes: typing.FrozenSet[typing.Type] = frozenset(), - path_to_schemas: typing.Optional[typing.Dict[str, typing.Set[typing.Type]]] = None, - ): - """ - Args: - path_to_item: the path to the current data being instantiated. - For {'a': [1]} if the code is handling, 1, then the path is ('args[0]', 'a', 0) - from_server: whether or not this data came form the server - True when receiving server data - False when instantiating model with client side data not form the server - configuration: the Configuration instance to use - This is needed because in Configuration: - - one can disable validation checking - base_classes: when deserializing data that matches multiple schemas, this is used to store - the schemas that have been traversed. This is used to stop processing when a cycle is seen. - path_to_schemas: a dict that goes from path to a list of classes at each path location - """ - self.path_to_item = path_to_item - self.from_server = from_server - self.configuration = configuration - self.base_classes = base_classes - if path_to_schemas is None: - path_to_schemas = defaultdict(set) - self.path_to_schemas = path_to_schemas - - def __repr__(self): - return str(self.__dict__) - - def __eq__(self, other): - if not isinstance(other, InstantiationMetadata): - return False - return self.__dict__ == other.__dict__ - - -class ValidatorBase: - @staticmethod - def __is_json_validation_enabled(schema_keyword, configuration=None): - """Returns true if JSON schema validation is enabled for the specified - validation keyword. This can be used to skip JSON schema structural validation - as requested in the configuration. - - Args: - schema_keyword (string): the name of a JSON schema validation keyword. - configuration (Configuration): the configuration class. - """ - - return (configuration is None or - not hasattr(configuration, '_disabled_client_side_validations') or - schema_keyword not in configuration._disabled_client_side_validations) - - @staticmethod - def __raise_validation_error_message(value, constraint_msg, constraint_value, path_to_item, additional_txt=""): - raise ApiValueError( - "Invalid value `{value}`, {constraint_msg} `{constraint_value}`{additional_txt} at {path_to_item}".format( - value=value, - constraint_msg=constraint_msg, - constraint_value=constraint_value, - additional_txt=additional_txt, - path_to_item=path_to_item, - ) - ) - - @classmethod - def __check_str_validations(cls, - validations, input_values, - _instantiation_metadata: InstantiationMetadata): - - if (cls.__is_json_validation_enabled('maxLength', _instantiation_metadata.configuration) and - 'max_length' in validations and - len(input_values) > validations['max_length']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="length must be less than or equal to", - constraint_value=validations['max_length'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('minLength', _instantiation_metadata.configuration) and - 'min_length' in validations and - len(input_values) < validations['min_length']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="length must be greater than or equal to", - constraint_value=validations['min_length'], - path_to_item=_instantiation_metadata.path_to_item - ) - - checked_value = input_values - if (cls.__is_json_validation_enabled('pattern', _instantiation_metadata.configuration) and - 'regex' in validations): - for regex_dict in validations['regex']: - flags = regex_dict.get('flags', 0) - if not re.search(regex_dict['pattern'], checked_value, flags=flags): - if flags != 0: - # Don't print the regex flags if the flags are not - # specified in the OAS document. - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must match regular expression", - constraint_value=regex_dict['pattern'], - path_to_item=_instantiation_metadata.path_to_item, - additional_txt=" with flags=`{}`".format(flags) - ) - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must match regular expression", - constraint_value=regex_dict['pattern'], - path_to_item=_instantiation_metadata.path_to_item - ) - - @classmethod - def __check_tuple_validations( - cls, validations, input_values, - _instantiation_metadata: InstantiationMetadata): - - if (cls.__is_json_validation_enabled('maxItems', _instantiation_metadata.configuration) and - 'max_items' in validations and - len(input_values) > validations['max_items']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="number of items must be less than or equal to", - constraint_value=validations['max_items'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('minItems', _instantiation_metadata.configuration) and - 'min_items' in validations and - len(input_values) < validations['min_items']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="number of items must be greater than or equal to", - constraint_value=validations['min_items'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('uniqueItems', _instantiation_metadata.configuration) and - 'unique_items' in validations and validations['unique_items'] and input_values): - unique_items = [] - # print(validations) - for item in input_values: - if item not in unique_items: - unique_items.append(item) - if len(input_values) > len(unique_items): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="duplicate items were found, and the tuple must not contain duplicates because", - constraint_value='unique_items==True', - path_to_item=_instantiation_metadata.path_to_item - ) - - @classmethod - def __check_dict_validations( - cls, validations, input_values, - _instantiation_metadata: InstantiationMetadata): - - if (cls.__is_json_validation_enabled('maxProperties', _instantiation_metadata.configuration) and - 'max_properties' in validations and - len(input_values) > validations['max_properties']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="number of properties must be less than or equal to", - constraint_value=validations['max_properties'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('minProperties', _instantiation_metadata.configuration) and - 'min_properties' in validations and - len(input_values) < validations['min_properties']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="number of properties must be greater than or equal to", - constraint_value=validations['min_properties'], - path_to_item=_instantiation_metadata.path_to_item - ) - - @classmethod - def __check_numeric_validations( - cls, validations, input_values, - _instantiation_metadata: InstantiationMetadata): - - if cls.__is_json_validation_enabled('multipleOf', - _instantiation_metadata.configuration) and 'multiple_of' in validations: - multiple_of_values = validations['multiple_of'] - for multiple_of_value in multiple_of_values: - if (isinstance(input_values, decimal.Decimal) and - not (float(input_values) / multiple_of_value).is_integer() - ): - # Note 'multipleOf' will be as good as the floating point arithmetic. - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="value must be a multiple of", - constraint_value=multiple_of_value, - path_to_item=_instantiation_metadata.path_to_item - ) - - checking_max_or_min_values = {'exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum', - 'inclusive_minimum'}.isdisjoint(validations) is False - if not checking_max_or_min_values: - return - max_val = input_values - min_val = input_values - - if (cls.__is_json_validation_enabled('exclusiveMaximum', _instantiation_metadata.configuration) and - 'exclusive_maximum' in validations and - max_val >= validations['exclusive_maximum']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must be a value less than", - constraint_value=validations['exclusive_maximum'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('maximum', _instantiation_metadata.configuration) and - 'inclusive_maximum' in validations and - max_val > validations['inclusive_maximum']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must be a value less than or equal to", - constraint_value=validations['inclusive_maximum'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('exclusiveMinimum', _instantiation_metadata.configuration) and - 'exclusive_minimum' in validations and - min_val <= validations['exclusive_minimum']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must be a value greater than", - constraint_value=validations['exclusive_maximum'], - path_to_item=_instantiation_metadata.path_to_item - ) - - if (cls.__is_json_validation_enabled('minimum', _instantiation_metadata.configuration) and - 'inclusive_minimum' in validations and - min_val < validations['inclusive_minimum']): - cls.__raise_validation_error_message( - value=input_values, - constraint_msg="must be a value greater than or equal to", - constraint_value=validations['inclusive_minimum'], - path_to_item=_instantiation_metadata.path_to_item - ) - - @classmethod - def _check_validations_for_types( - cls, - validations, - input_values, - _instantiation_metadata: InstantiationMetadata - ): - if isinstance(input_values, str): - cls.__check_str_validations(validations, input_values, _instantiation_metadata) - elif isinstance(input_values, tuple): - cls.__check_tuple_validations(validations, input_values, _instantiation_metadata) - elif isinstance(input_values, frozendict): - cls.__check_dict_validations(validations, input_values, _instantiation_metadata) - elif isinstance(input_values, decimal.Decimal): - cls.__check_numeric_validations(validations, input_values, _instantiation_metadata) - try: - return super()._validate_validations_pass(input_values, _instantiation_metadata) - except AttributeError: - return True - - -class Validator(typing.Protocol): - def _validate_validations_pass( - cls, - input_values, - _instantiation_metadata: InstantiationMetadata - ): - pass - - -def _SchemaValidator(**validations: typing.Union[str, bool, None, int, float, list[dict[str, typing.Union[str, int, float]]]]) -> Validator: - class SchemaValidator(ValidatorBase): - @classmethod - def _validate_validations_pass( - cls, - input_values, - _instantiation_metadata: InstantiationMetadata - ): - cls._check_validations_for_types(validations, input_values, _instantiation_metadata) - try: - return super()._validate_validations_pass(input_values, _instantiation_metadata) - except AttributeError: - return True - - return SchemaValidator - - -class TypeChecker(typing.Protocol): - @classmethod - def _validate_type( - cls, arg_simple_class: type - ) -> typing.Tuple[type]: - pass - - -def _SchemaTypeChecker(union_type_cls: typing.Union[typing.Any]) -> TypeChecker: - if typing.get_origin(union_type_cls) is typing.Union: - union_classes = typing.get_args(union_type_cls) - else: - # note: when a union of a single class is passed in, the union disappears - union_classes = tuple([union_type_cls]) - """ - I want the type hint... union_type_cls - and to use it as a base class but when I do, I get - TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - """ - class SchemaTypeChecker: - @classmethod - def _validate_type(cls, arg_simple_class: type): - if arg_simple_class not in union_classes: - return union_classes - try: - return super()._validate_type(arg_simple_class) - except AttributeError: - return tuple() - - return SchemaTypeChecker - - -class EnumMakerBase: - @classmethod - @property - def _enum_by_value( - cls - ) -> type: - enum_classes = {} - if not hasattr(cls, "_enum_value_to_name"): - return enum_classes - for enum_value, enum_name in cls._enum_value_to_name.items(): - base_class = type(enum_value) - if base_class is none_type: - enum_classes[enum_value] = get_new_class( - "Dynamic" + cls.__name__, (cls, NoneClass)) - log_cache_usage(get_new_class) - elif base_class is bool: - enum_classes[enum_value] = get_new_class( - "Dynamic" + cls.__name__, (cls, BoolClass)) - log_cache_usage(get_new_class) - else: - enum_classes[enum_value] = get_new_class( - "Dynamic" + cls.__name__, (cls, Singleton, base_class)) - log_cache_usage(get_new_class) - return enum_classes - - -class EnumMakerInterface(typing.Protocol): - @classmethod - @property - def _enum_value_to_name( - cls - ) -> typing.Dict[typing.Union[str, decimal.Decimal, bool, none_type], str]: - pass - - @classmethod - @property - def _enum_by_value( - cls - ) -> type: - pass - - -def _SchemaEnumMaker(enum_value_to_name: typing.Dict[typing.Union[str, decimal.Decimal, bool, none_type], str]) -> EnumMakerInterface: - class SchemaEnumMaker(EnumMakerBase): - @classmethod - @property - def _enum_value_to_name( - cls - ) -> typing.Dict[typing.Union[str, decimal.Decimal, bool, none_type], str]: - pass - try: - super_enum_value_to_name = super()._enum_value_to_name - except AttributeError: - return enum_value_to_name - intersection = dict(enum_value_to_name.items() & super_enum_value_to_name.items()) - return intersection - - return SchemaEnumMaker - - -class Singleton: - """ - Enums and singletons are the same - The same instance is returned for a given key of (cls, arg) - """ - # TODO use bidict to store this so boolean enums can move through it in reverse to get their own arg value? - _instances = {} - - def __new__(cls, *args, **kwargs): - if not args: - raise ValueError('arg must be passed') - arg = args[0] - key = (cls, arg) - if key not in cls._instances: - if arg in {None, True, False}: - inst = super().__new__(cls) - # inst._value = arg - cls._instances[key] = inst - else: - cls._instances[key] = super().__new__(cls, arg) - return cls._instances[key] - - def __repr__(self): - return '({}, {})'.format(self.__class__.__name__, self) - - -class NoneClass(Singleton): - @classmethod - @property - def NONE(cls): - return cls(None) - - def is_none(self) -> bool: - return True - - def __bool__(self) -> bool: - return False - - -class BoolClass(Singleton): - @classmethod - @property - def TRUE(cls): - return cls(True) - - @classmethod - @property - def FALSE(cls): - return cls(False) - - @functools.cache - def __bool__(self) -> bool: - for key, instance in self._instances.items(): - if self is instance: - return key[1] - raise ValueError('Unable to find the boolean value of this instance') - - def is_true(self): - return bool(self) - - def is_false(self): - return bool(self) - - -class BoolBase: - pass - - -class NoneBase: - pass - - -class StrBase: - @property - def as_str(self) -> str: - return self - - @property - def as_date(self) -> date: - raise Exception('not implemented') - - @property - def as_datetime(self) -> datetime: - raise Exception('not implemented') - - @property - def as_decimal(self) -> decimal.Decimal: - raise Exception('not implemented') - - -class CustomIsoparser(isoparser): - - @_takes_ascii - def parse_isodatetime(self, dt_str): - components, pos = self._parse_isodate(dt_str) - if len(dt_str) > pos: - if self._sep is None or dt_str[pos:pos + 1] == self._sep: - components += self._parse_isotime(dt_str[pos + 1:]) - else: - raise ValueError('String contains unknown ISO components') - - if len(components) > 3 and components[3] == 24: - components[3] = 0 - return datetime(*components) + timedelta(days=1) - - if len(components) <= 3: - raise ValueError('Value is not a datetime') - - return datetime(*components) - - @_takes_ascii - def parse_isodate(self, datestr): - components, pos = self._parse_isodate(datestr) - - if len(datestr) > pos: - raise ValueError('String contains invalid time components') - - if len(components) > 3: - raise ValueError('String contains invalid time components') - - return date(*components) - - -DEFAULT_ISOPARSER = CustomIsoparser() - - -class DateBase(StrBase): - @property - @functools.cache - def as_date(self) -> date: - return DEFAULT_ISOPARSER.parse_isodate(self) - - @classmethod - def _validate_format(cls, arg: typing.Optional[str], _instantiation_metadata: InstantiationMetadata): - if isinstance(arg, str): - try: - DEFAULT_ISOPARSER.parse_isodate(arg) - return True - except ValueError: - raise ApiValueError( - "Value does not conform to the required ISO-8601 date format. " - "Invalid value '{}' for type date at {}".format(arg, _instantiation_metadata.path_to_item) - ) - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - DateBase _validate - """ - cls._validate_format(args[0], _instantiation_metadata=_instantiation_metadata) - return super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - - -class DateTimeBase: - @property - @functools.cache - def as_datetime(self) -> datetime: - return DEFAULT_ISOPARSER.parse_isodatetime(self) - - @classmethod - def _validate_format(cls, arg: typing.Optional[str], _instantiation_metadata: InstantiationMetadata): - if isinstance(arg, str): - try: - DEFAULT_ISOPARSER.parse_isodatetime(arg) - return True - except ValueError: - raise ApiValueError( - "Value does not conform to the required ISO-8601 datetime format. " - "Invalid value '{}' for type datetime at {}".format(arg, _instantiation_metadata.path_to_item) - ) - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - DateTimeBase _validate - """ - cls._validate_format(args[0], _instantiation_metadata=_instantiation_metadata) - return super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - - -class DecimalBase(StrBase): - """ - A class for storing decimals that are sent over the wire as strings - These schemas must remain based on StrBase rather than NumberBase - because picking base classes must be deterministic - """ - - @property - @functools.cache - def as_decimal(self) -> decimal.Decimal: - return decimal.Decimal(self) - - @classmethod - def _validate_format(cls, arg: typing.Optional[str], _instantiation_metadata: InstantiationMetadata): - if isinstance(arg, str): - try: - decimal.Decimal(arg) - return True - except decimal.InvalidOperation: - raise ApiValueError( - "Value cannot be converted to a decimal. " - "Invalid value '{}' for type decimal at {}".format(arg, _instantiation_metadata.path_to_item) - ) - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - DecimalBase _validate - """ - cls._validate_format(args[0], _instantiation_metadata=_instantiation_metadata) - return super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - - -class NumberBase: - @property - def as_int(self) -> int: - try: - return self._as_int - except AttributeError: - """ - Note: for some numbers like 9.0 they could be represented as an - integer but our code chooses to store them as - >>> Decimal('9.0').as_tuple() - DecimalTuple(sign=0, digits=(9, 0), exponent=-1) - so we can tell that the value came from a float and convert it back to a float - during later serialization - """ - if self.as_tuple().exponent < 0: - # this could be represented as an integer but should be represented as a float - # because that's what it was serialized from - raise ApiValueError(f'{self} is not an integer') - self._as_int = int(self) - return self._as_int - - @property - def as_float(self) -> float: - try: - return self._as_float - except AttributeError: - if self.as_tuple().exponent >= 0: - raise ApiValueError(f'{self} is not an float') - self._as_float = float(self) - return self._as_float - - -class ListBase: - @classmethod - def _validate_items(cls, list_items, _instantiation_metadata: InstantiationMetadata): - """ - Ensures that: - - values passed in for items are valid - Exceptions will be raised if: - - invalid arguments were passed in - - Args: - list_items: the input list of items - - Raises: - ApiTypeError - for missing required arguments, or for invalid properties - """ - - # if we have definitions for an items schema, use it - # otherwise accept anything - item_cls = getattr(cls, '_items', AnyTypeSchema) - path_to_schemas = defaultdict(set) - for i, value in enumerate(list_items): - if isinstance(value, item_cls): - continue - item_instantiation_metadata = InstantiationMetadata( - from_server=_instantiation_metadata.from_server, - configuration=_instantiation_metadata.configuration, - path_to_item=_instantiation_metadata.path_to_item+(i,) - ) - other_path_to_schemas = item_cls._validate( - value, _instantiation_metadata=item_instantiation_metadata) - update(path_to_schemas, other_path_to_schemas) - return path_to_schemas - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - ListBase _validate - We return dynamic classes of different bases depending upon the inputs - This makes it so: - - the returned instance is always a subclass of our defining schema - - this allows us to check type based on whether an instance is a subclass of a schema - - the returned instance is a serializable type (except for None, True, and False) which are enums - - Returns: - new_cls (type): the new class - - Raises: - ApiValueError: when a string can't be converted into a date or datetime and it must be one of those classes - ApiTypeError: when the input type is not in the list of allowed spec types - """ - arg = args[0] - _path_to_schemas = super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - if not isinstance(arg, tuple): - return _path_to_schemas - if cls in _instantiation_metadata.base_classes: - # we have already moved through this class so stop here - return _path_to_schemas - _instantiation_metadata.base_classes |= frozenset({cls}) - other_path_to_schemas = cls._validate_items(arg, _instantiation_metadata=_instantiation_metadata) - update(_path_to_schemas, other_path_to_schemas) - return _path_to_schemas - - @classmethod - def _get_items(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - ''' - ListBase _get_items - ''' - _instantiation_metadata = InstantiationMetadata() if _instantiation_metadata is None else _instantiation_metadata - - list_items = args[0] - cast_items = [] - # if we have definitions for an items schema, use it - # otherwise accept anything - - cls_item_cls = getattr(cls, '_items', AnyTypeSchema) - for i, value in enumerate(list_items): - item_path_to_item = _instantiation_metadata.path_to_item+(i,) - if item_path_to_item in _instantiation_metadata.path_to_schemas: - item_cls = _instantiation_metadata.path_to_schemas[item_path_to_item] - else: - item_cls = cls_item_cls - - if isinstance(value, item_cls): - cast_items.append(value) - continue - item_instantiation_metadata = InstantiationMetadata( - configuration=_instantiation_metadata.configuration, - from_server=_instantiation_metadata.from_server, - path_to_item=item_path_to_item, - path_to_schemas=_instantiation_metadata.path_to_schemas, - ) - - if _instantiation_metadata.from_server: - new_value = item_cls._from_openapi_data(value, _instantiation_metadata=item_instantiation_metadata) - else: - new_value = item_cls(value, _instantiation_metadata=item_instantiation_metadata) - cast_items.append(new_value) - - return cast_items - - -class Discriminable: - @classmethod - def _ensure_discriminator_value_present(cls, disc_property_name: str, _instantiation_metadata: InstantiationMetadata, *args): - if not args or args and disc_property_name not in args[0]: - # The input data does not contain the discriminator property - raise ApiValueError( - "Cannot deserialize input data due to missing discriminator. " - "The discriminator property '{}' is missing at path: {}".format(disc_property_name, _instantiation_metadata.path_to_item) - ) - - @classmethod - def _get_discriminated_class(cls, disc_property_name: str, disc_payload_value: str): - """ - Used in schemas with discriminators - """ - if not hasattr(cls, '_discriminator'): - return None - disc = cls._discriminator - if disc_property_name not in disc: - return None - discriminated_cls = disc[disc_property_name].get(disc_payload_value) - if discriminated_cls is not None: - return discriminated_cls - elif not hasattr(cls, '_composed_schemas'): - return None - # TODO stop traveling if a cycle is hit - for allof_cls in cls._composed_schemas['allOf']: - discriminated_cls = allof_cls._get_discriminated_class( - disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) - if discriminated_cls is not None: - return discriminated_cls - for oneof_cls in cls._composed_schemas['oneOf']: - discriminated_cls = oneof_cls._get_discriminated_class( - disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) - if discriminated_cls is not None: - return discriminated_cls - for anyof_cls in cls._composed_schemas['anyOf']: - discriminated_cls = anyof_cls._get_discriminated_class( - disc_property_name=disc_property_name, disc_payload_value=disc_payload_value) - if discriminated_cls is not None: - return discriminated_cls - return None - - -class DictBase(Discriminable): - # subclass properties - _required_property_names = set() - - @classmethod - def _validate_arg_presence(cls, arg): - """ - Ensures that: - - all required arguments are passed in - - the input variable names are valid - - present in properties or - - accepted because additionalProperties exists - Exceptions will be raised if: - - invalid arguments were passed in - - a var_name is invalid if additionProperties == None and var_name not in _properties - - required properties were not passed in - - Args: - arg: the input dict - - Raises: - ApiTypeError - for missing required arguments, or for invalid properties - """ - seen_required_properties = set() - invalid_arguments = [] - for property_name in arg: - if property_name in cls._required_property_names: - seen_required_properties.add(property_name) - elif property_name in cls._property_names: - continue - elif cls._additional_properties: - continue - else: - invalid_arguments.append(property_name) - missing_required_arguments = list(cls._required_property_names - seen_required_properties) - if missing_required_arguments: - missing_required_arguments.sort() - raise ApiTypeError( - "{} is missing {} required argument{}: {}".format( - cls.__name__, - len(missing_required_arguments), - "s" if len(missing_required_arguments) > 1 else "", - missing_required_arguments - ) - ) - if invalid_arguments: - invalid_arguments.sort() - raise ApiTypeError( - "{} was passed {} invalid argument{}: {}".format( - cls.__name__, - len(invalid_arguments), - "s" if len(invalid_arguments) > 1 else "", - invalid_arguments - ) - ) - - @classmethod - def _validate_args(cls, arg, _instantiation_metadata: InstantiationMetadata): - """ - Ensures that: - - values passed in for properties are valid - Exceptions will be raised if: - - invalid arguments were passed in - - Args: - arg: the input dict - - Raises: - ApiTypeError - for missing required arguments, or for invalid properties - """ - path_to_schemas = defaultdict(set) - for property_name, value in arg.items(): - if property_name in cls._required_property_names or property_name in cls._property_names: - schema = getattr(cls, property_name) - elif cls._additional_properties: - schema = cls._additional_properties - else: - raise ApiTypeError('Unable to find schema for value={} in class={} at path_to_item={}'.format( - value, cls, _instantiation_metadata.path_to_item+(property_name,) - )) - if isinstance(value, schema): - continue - arg_instantiation_metadata = InstantiationMetadata( - from_server=_instantiation_metadata.from_server, - configuration=_instantiation_metadata.configuration, - path_to_item=_instantiation_metadata.path_to_item+(property_name,) - ) - other_path_to_schemas = schema._validate(value, _instantiation_metadata=arg_instantiation_metadata) - update(path_to_schemas, other_path_to_schemas) - _instantiation_metadata.path_to_schemas.update(arg_instantiation_metadata.path_to_schemas) - return path_to_schemas - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - DictBase _validate - We return dynamic classes of different bases depending upon the inputs - This makes it so: - - the returned instance is always a subclass of our defining schema - - this allows us to check type based on whether an instance is a subclass of a schema - - the returned instance is a serializable type (except for None, True, and False) which are enums - - Returns: - new_cls (type): the new class - - Raises: - ApiValueError: when a string can't be converted into a date or datetime and it must be one of those classes - ApiTypeError: when the input type is not in the list of allowed spec types - """ - if args and isinstance(args[0], cls): - # an instance of the correct type was passed in - return {} - arg = args[0] - _path_to_schemas = super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - if not isinstance(arg, frozendict): - return _path_to_schemas - cls._validate_arg_presence(args[0]) - other_path_to_schemas = cls._validate_args(args[0], _instantiation_metadata=_instantiation_metadata) - update(_path_to_schemas, other_path_to_schemas) - try: - _discriminator = cls._discriminator - except AttributeError: - return _path_to_schemas - # discriminator exists - disc_prop_name = list(_discriminator.keys())[0] - cls._ensure_discriminator_value_present(disc_prop_name, _instantiation_metadata, *args) - discriminated_cls = cls._get_discriminated_class( - disc_property_name=disc_prop_name, disc_payload_value=arg[disc_prop_name]) - if discriminated_cls is None: - raise ApiValueError( - "Invalid discriminator value was passed in to {}.{} Only the values {} are allowed at {}".format( - cls.__name__, - disc_prop_name, - list(_discriminator[disc_prop_name].keys()), - _instantiation_metadata.path_to_item + (disc_prop_name,) - ) - ) - if discriminated_cls in _instantiation_metadata.base_classes: - # we have already moved through this class so stop here - return _path_to_schemas - _instantiation_metadata.base_classes |= frozenset({cls}) - other_path_to_schemas = discriminated_cls._validate(*args, _instantiation_metadata=_instantiation_metadata) - update(_path_to_schemas, other_path_to_schemas) - return _path_to_schemas - - @classmethod - @property - def _additional_properties(cls): - return AnyTypeSchema - - @classmethod - @property - @functools.cache - def _property_names(cls): - property_names = set() - for var_name, var_value in cls.__dict__.items(): - # referenced models are classmethods - is_classmethod = type(var_value) is classmethod - if is_classmethod: - property_names.add(var_name) - continue - is_class = type(var_value) is type - if not is_class: - continue - if not issubclass(var_value, Schema): - continue - if var_name == '_additional_properties': - continue - property_names.add(var_name) - property_names = list(property_names) - property_names.sort() - return tuple(property_names) - - @classmethod - def _get_properties(cls, arg: typing.Dict[str, typing.Any], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - DictBase _get_properties, this is how properties are set - These values already passed validation - """ - dict_items = {} - # if we have definitions for property schemas convert values using it - # otherwise accept anything - - _instantiation_metadata = InstantiationMetadata() if _instantiation_metadata is None else _instantiation_metadata - - for property_name_js, value in arg.items(): - property_cls = getattr(cls, property_name_js, cls._additional_properties) - property_path_to_item = _instantiation_metadata.path_to_item+(property_name_js,) - stored_property_cls = _instantiation_metadata.path_to_schemas.get(property_path_to_item) - if stored_property_cls: - property_cls = stored_property_cls - - if isinstance(value, property_cls): - dict_items[property_name_js] = value - continue - - prop_instantiation_metadata = InstantiationMetadata( - configuration=_instantiation_metadata.configuration, - from_server=_instantiation_metadata.from_server, - path_to_item=property_path_to_item, - path_to_schemas=_instantiation_metadata.path_to_schemas, - ) - if _instantiation_metadata.from_server: - new_value = property_cls._from_openapi_data(value, _instantiation_metadata=prop_instantiation_metadata) - else: - new_value = property_cls(value, _instantiation_metadata=prop_instantiation_metadata) - dict_items[property_name_js] = new_value - return dict_items - - def __setattr__(self, name, value): - if not isinstance(self, FileIO): - raise AttributeError('property setting not supported on immutable instances') - - def __getattr__(self, name): - if isinstance(self, frozendict): - # if an attribute does not exist - try: - return self[name] - except KeyError as ex: - raise AttributeError(str(ex)) - # print(('non-frozendict __getattr__', name)) - return super().__getattr__(self, name) - - def __getattribute__(self, name): - # print(('__getattribute__', name)) - # if an attribute does exist (for example as a class property but not as an instance method) - try: - return self[name] - except (KeyError, TypeError): - return super().__getattribute__(name) - - -inheritable_primitive_types_set = {decimal.Decimal, str, tuple, frozendict, FileIO, bytes} - - -class Schema: - """ - the base class of all swagger/openapi schemas/models - - ensures that: - - payload passes required validations - - payload is of allowed types - - payload value is an allowed enum value - """ - - @staticmethod - def __get_simple_class(input_value): - """Returns an input_value's simple class that we will use for type checking - - Args: - input_value (class/class_instance): the item for which we will return - the simple class - """ - if isinstance(input_value, tuple): - return tuple - elif isinstance(input_value, frozendict): - return frozendict - elif isinstance(input_value, none_type): - return none_type - elif isinstance(input_value, bytes): - return bytes - elif isinstance(input_value, (io.FileIO, io.BufferedReader)): - return FileIO - elif isinstance(input_value, bool): - # this must be higher than the int check because - # isinstance(True, int) == True - return bool - elif isinstance(input_value, int): - return int - elif isinstance(input_value, float): - return float - elif isinstance(input_value, datetime): - # this must be higher than the date check because - # isinstance(datetime_instance, date) == True - return datetime - elif isinstance(input_value, date): - return date - elif isinstance(input_value, str): - return str - return type(input_value) - - @staticmethod - def __get_valid_classes_phrase(input_classes): - """Returns a string phrase describing what types are allowed""" - all_classes = list(input_classes) - all_classes = sorted(all_classes, key=lambda cls: cls.__name__) - all_class_names = [cls.__name__ for cls in all_classes] - if len(all_class_names) == 1: - return "is {0}".format(all_class_names[0]) - return "is one of [{0}]".format(", ".join(all_class_names)) - - @classmethod - def __type_error_message( - cls, var_value=None, var_name=None, valid_classes=None, key_type=None - ): - """ - Keyword Args: - var_value (any): the variable which has the type_error - var_name (str): the name of the variable which has the typ error - valid_classes (tuple): the accepted classes for current_item's - value - key_type (bool): False if our value is a value in a dict - True if it is a key in a dict - False if our item is an item in a tuple - """ - key_or_value = "value" - if key_type: - key_or_value = "key" - valid_classes_phrase = cls.__get_valid_classes_phrase(valid_classes) - msg = "Invalid type. Required {1} type {2} and " "passed type was {3}".format( - var_name, - key_or_value, - valid_classes_phrase, - type(var_value).__name__, - ) - return msg - - @classmethod - def __get_type_error(cls, var_value, path_to_item, valid_classes, key_type=False): - error_msg = cls.__type_error_message( - var_name=path_to_item[-1], - var_value=var_value, - valid_classes=valid_classes, - key_type=key_type, - ) - return ApiTypeError( - error_msg, - path_to_item=path_to_item, - valid_classes=valid_classes, - key_type=key_type, - ) - - @classmethod - def _class_by_base_class(cls, base_cls: type) -> type: - cls_name = "Dynamic"+cls.__name__ - if base_cls is bool: - new_cls = get_new_class(cls_name, (cls, BoolBase, BoolClass)) - elif base_cls is str: - new_cls = get_new_class(cls_name, (cls, StrBase, str)) - elif base_cls is decimal.Decimal: - new_cls = get_new_class(cls_name, (cls, NumberBase, decimal.Decimal)) - elif base_cls is tuple: - new_cls = get_new_class(cls_name, (cls, ListBase, tuple)) - elif base_cls is frozendict: - new_cls = get_new_class(cls_name, (cls, DictBase, frozendict)) - elif base_cls is none_type: - new_cls = get_new_class(cls_name, (cls, NoneBase, NoneClass)) - log_cache_usage(get_new_class) - return new_cls - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - Schema _validate - Runs all schema validation logic and - returns a dynamic class of different bases depending upon the input - This makes it so: - - the returned instance is always a subclass of our defining schema - - this allows us to check type based on whether an instance is a subclass of a schema - - the returned instance is a serializable type (except for None, True, and False) which are enums - - Use cases: - 1. inheritable type: string/decimal.Decimal/frozendict/tuple - 2. enum value cases: 'hi', 1 -> no base_class set because the enum includes the base class - 3. uninheritable type: True/False/None -> no base_class because the base class is not inheritable - _enum_by_value will handle this use case - - Required Steps: - 1. verify type of input is valid vs the allowed _types - 2. check validations that are applicable for this type of input - 3. if enums exist, check that the value exists in the enum - - Returns: - path_to_schemas: a map of path to schemas - - Raises: - ApiValueError: when a string can't be converted into a date or datetime and it must be one of those classes - ApiTypeError: when the input type is not in the list of allowed spec types - """ - arg = args[0] - - base_class = cls.__get_simple_class(arg) - failed_type_check_classes = cls._validate_type(base_class) - if failed_type_check_classes: - raise cls.__get_type_error( - arg, - _instantiation_metadata.path_to_item, - failed_type_check_classes, - key_type=False, - ) - if hasattr(cls, '_validate_validations_pass'): - cls._validate_validations_pass(arg, _instantiation_metadata) - path_to_schemas = defaultdict(set) - path_to_schemas[_instantiation_metadata.path_to_item].add(cls) - - if hasattr(cls, "_enum_by_value"): - cls._validate_enum_value(arg) - return path_to_schemas - - if base_class is none_type or base_class is bool: - return path_to_schemas - - path_to_schemas[_instantiation_metadata.path_to_item].add(base_class) - return path_to_schemas - - @classmethod - def _validate_enum_value(cls, arg): - try: - cls._enum_by_value[arg] - except KeyError: - raise ApiValueError("Invalid value {} passed in to {}, {}".format(arg, cls, cls._enum_value_to_name)) - - @classmethod - def __get_new_cls(cls, arg, _instantiation_metadata: InstantiationMetadata): - """ - PATH 1 - make a new dynamic class and return an instance of that class - We are making an instance of cls, but instead of making cls - make a new class, new_cls - which includes dynamic bases including cls - return an instance of that new class - """ - if ( - _instantiation_metadata.path_to_schemas and - _instantiation_metadata.path_to_item in _instantiation_metadata.path_to_schemas): - chosen_new_cls = _instantiation_metadata.path_to_schemas[_instantiation_metadata.path_to_item] - # print('leaving __get_new_cls early for cls {} because path_to_schemas exists'.format(cls)) - # print(_instantiation_metadata.path_to_item) - # print(chosen_new_cls) - return chosen_new_cls - """ - Dict property + List Item Assignment Use cases: - 1. value is NOT an instance of the required schema class - the value is validated by _validate - _validate returns a key value pair - where the key is the path to the item, and the value will be the required manufactured class - made out of the matching schemas - 2. value is an instance of the the correct schema type - the value is NOT validated by _validate, _validate only checks that the instance is of the correct schema type - for this value, _validate does NOT return an entry for it in _path_to_schemas - and in list/dict _get_items,_get_properties the value will be directly assigned - because value is of the correct type, and validation was run earlier when the instance was created - """ - _path_to_schemas = cls._validate(arg, _instantiation_metadata=_instantiation_metadata) - {{!-- from pprint import pprint --}} - {{!-- pprint(dict(_path_to_schemas)) --}} - # loop through it make a new class for each entry - for path, schema_classes in _path_to_schemas.items(): - enum_schema = any( - hasattr(this_cls, '_enum_by_value') for this_cls in schema_classes) - inheritable_primitive_type = schema_classes.intersection(inheritable_primitive_types_set) - chosen_schema_classes = schema_classes - suffix = tuple() - if inheritable_primitive_type: - chosen_schema_classes = schema_classes - inheritable_primitive_types_set - if not enum_schema: - # include the inheritable_primitive_type - suffix = tuple(inheritable_primitive_type) - - if len(chosen_schema_classes) == 1 and not suffix: - mfg_cls = tuple(chosen_schema_classes)[0] - else: - x_schema = schema_descendents & chosen_schema_classes - if x_schema: - x_schema = x_schema.pop() - if any(c is not x_schema and issubclass(c, x_schema) for c in chosen_schema_classes): - # needed to not have a mro error in get_new_class - chosen_schema_classes.remove(x_schema) - used_classes = tuple(sorted(chosen_schema_classes, key=lambda a_cls: a_cls.__name__)) + suffix - mfg_cls = get_new_class(class_name='DynamicSchema', bases=used_classes) - - if inheritable_primitive_type and not enum_schema: - _instantiation_metadata.path_to_schemas[path] = mfg_cls - continue - - # Use case: value is None, True, False, or an enum value - # print('choosing enum class for path {} in arg {}'.format(path, arg)) - value = arg - for key in path[1:]: - value = value[key] - if hasattr(mfg_cls, '_enum_by_value'): - mfg_cls = mfg_cls._enum_by_value[value] - elif value in {True, False}: - mfg_cls = mfg_cls._class_by_base_class(bool) - elif value is None: - mfg_cls = mfg_cls._class_by_base_class(none_type) - else: - raise ApiValueError('Unhandled case value={} bases={}'.format(value, mfg_cls.__bases__)) - _instantiation_metadata.path_to_schemas[path] = mfg_cls - - return _instantiation_metadata.path_to_schemas[_instantiation_metadata.path_to_item] - - @classmethod - def __get_new_instance_without_conversion(cls, arg, _instantiation_metadata): - # PATH 2 - we have a Dynamic class and we are making an instance of it - if issubclass(cls, tuple): - items = cls._get_items(arg, _instantiation_metadata=_instantiation_metadata) - return super(Schema, cls).__new__(cls, items) - elif issubclass(cls, frozendict): - properties = cls._get_properties(arg, _instantiation_metadata=_instantiation_metadata) - return super(Schema, cls).__new__(cls, properties) - """ - str = openapi str, date, and datetime - decimal.Decimal = openapi int and float - FileIO = openapi binary type and the user inputs a file - bytes = openapi binary type and the user inputs bytes - """ - return super(Schema, cls).__new__(cls, arg) - - @classmethod - def _from_openapi_data( - cls, - arg: typing.Union[ - str, - date, - datetime, - int, - float, - decimal.Decimal, - bool, - None, - 'Schema', - dict, - frozendict, - tuple, - list, - io.FileIO, - io.BufferedReader, - bytes - ], - _instantiation_metadata: typing.Optional[InstantiationMetadata] - ): - arg = cast_to_allowed_types(arg, from_server=True) - _instantiation_metadata = InstantiationMetadata(from_server=True) if _instantiation_metadata is None else _instantiation_metadata - if not _instantiation_metadata.from_server: - raise ApiValueError( - 'from_server must be True in this code path, if you need it to be False, use cls()' - ) - new_cls = cls.__get_new_cls(arg, _instantiation_metadata) - new_inst = new_cls.__get_new_instance_without_conversion(arg, _instantiation_metadata) - return new_inst - - @staticmethod - def __get_input_dict(*args, **kwargs) -> frozendict: - input_dict = {} - if args and isinstance(args[0], (dict, frozendict)): - input_dict.update(args[0]) - if kwargs: - input_dict.update(kwargs) - return frozendict(input_dict) - - @staticmethod - def __remove_unsets(kwargs): - return {key: val for key, val in kwargs.items() if val is not unset} - - def __new__(cls, *args: typing.Union[dict, frozendict, list, tuple, decimal.Decimal, float, int, str, date, datetime, bool, None, 'Schema'], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None, **kwargs: typing.Union[dict, frozendict, list, tuple, decimal.Decimal, float, int, str, date, datetime, bool, None, 'Schema', Unset]): - """ - Schema __new__ - - Args: - args (int/float/decimal.Decimal/str/list/tuple/dict/frozendict/bool/None): the value - kwargs (str, int/float/decimal.Decimal/str/list/tuple/dict/frozendict/bool/None): dict values - _instantiation_metadata: contains the needed from_server, configuration, path_to_item - """ - kwargs = cls.__remove_unsets(kwargs) - if not args and not kwargs: - raise TypeError( - 'No input given. args or kwargs must be given.' - ) - if not kwargs and args and not isinstance(args[0], dict): - arg = args[0] - else: - arg = cls.__get_input_dict(*args, **kwargs) - _instantiation_metadata = InstantiationMetadata() if _instantiation_metadata is None else _instantiation_metadata - if _instantiation_metadata.from_server: - raise ApiValueError( - 'from_server must be False in this code path, if you need it to be True, use cls._from_openapi_data()' - ) - arg = cast_to_allowed_types(arg, from_server=_instantiation_metadata.from_server) - new_cls = cls.__get_new_cls(arg, _instantiation_metadata) - return new_cls.__get_new_instance_without_conversion(arg, _instantiation_metadata) - - def __init__( - self, - *args: typing.Union[ - dict, frozendict, list, tuple, decimal.Decimal, float, int, str, date, datetime, bool, None, 'Schema'], - _instantiation_metadata: typing.Optional[InstantiationMetadata] = None, - **kwargs: typing.Union[ - dict, frozendict, list, tuple, decimal.Decimal, float, int, str, date, datetime, bool, None, 'Schema', Unset - ] - ): - """ - this is needed to fix 'Unexpected argument' warning in pycharm - this code does nothing because all Schema instances are immutable - this means that all input data is passed into and used in new, and after the new instance is made - no new attributes are assigned and init is not used - """ - pass - - -def cast_to_allowed_types(arg: typing.Union[str, date, datetime, decimal.Decimal, int, float, None, dict, frozendict, list, tuple, bytes, Schema], from_server=False) -> typing.Union[str, bytes, decimal.Decimal, None, frozendict, tuple, Schema]: - """ - from_server=False date, datetime -> str - int, float -> Decimal - StrSchema will convert that to bytes and remember the encoding when we pass in str input - """ - if isinstance(arg, (date, datetime)): - if not from_server: - return arg.isoformat() - # ApiTypeError will be thrown later by _validate_type - return arg - elif isinstance(arg, bool): - """ - this check must come before isinstance(arg, (int, float)) - because isinstance(True, int) is True - """ - return arg - elif isinstance(arg, decimal.Decimal): - return arg - elif isinstance(arg, int): - return decimal.Decimal(arg) - elif isinstance(arg, float): - decimal_from_float = decimal.Decimal(arg) - if decimal_from_float.as_integer_ratio()[1] == 1: - # 9.0 -> Decimal('9.0') - # 3.4028234663852886e+38 -> Decimal('340282346638528859811704183484516925440.0') - return decimal.Decimal(str(decimal_from_float)+'.0') - return decimal_from_float - elif isinstance(arg, str): - return arg - elif isinstance(arg, bytes): - return arg - elif isinstance(arg, (io.FileIO, io.BufferedReader)): - if arg.closed: - raise ApiValueError('Invalid file state; file is closed and must be open') - return arg - elif type(arg) is list or type(arg) is tuple: - return tuple([cast_to_allowed_types(item) for item in arg]) - elif type(arg) is dict or type(arg) is frozendict: - return frozendict({key: cast_to_allowed_types(val) for key, val in arg.items() if val is not unset}) - elif arg is None: - return arg - elif isinstance(arg, Schema): - return arg - raise ValueError('Invalid type passed in got input={} type={}'.format(arg, type(arg))) - - -class ComposedBase(Discriminable): - - @classmethod - def __get_allof_classes(cls, *args, _instantiation_metadata: InstantiationMetadata): - path_to_schemas = defaultdict(set) - for allof_cls in cls._composed_schemas['allOf']: - if allof_cls in _instantiation_metadata.base_classes: - continue - other_path_to_schemas = allof_cls._validate(*args, _instantiation_metadata=_instantiation_metadata) - update(path_to_schemas, other_path_to_schemas) - return path_to_schemas - - @classmethod - def __get_oneof_class( - cls, - *args, - discriminated_cls, - _instantiation_metadata: InstantiationMetadata, - path_to_schemas: typing.Dict[typing.Tuple, typing.Set[typing.Type[Schema]]] - ): - oneof_classes = [] - chosen_oneof_cls = None - original_base_classes = _instantiation_metadata.base_classes - new_base_classes = _instantiation_metadata.base_classes - path_to_schemas = defaultdict(set) - for oneof_cls in cls._composed_schemas['oneOf']: - if oneof_cls in path_to_schemas[_instantiation_metadata.path_to_item]: - oneof_classes.append(oneof_cls) - continue - if isinstance(args[0], oneof_cls): - # passed in instance is the correct type - chosen_oneof_cls = oneof_cls - oneof_classes.append(oneof_cls) - continue - _instantiation_metadata.base_classes = original_base_classes - try: - path_to_schemas = oneof_cls._validate(*args, _instantiation_metadata=_instantiation_metadata) - new_base_classes = _instantiation_metadata.base_classes - except (ApiValueError, ApiTypeError) as ex: - if discriminated_cls is not None and oneof_cls is discriminated_cls: - raise ex - continue - chosen_oneof_cls = oneof_cls - oneof_classes.append(oneof_cls) - if not oneof_classes: - raise ApiValueError( - "Invalid inputs given to generate an instance of {}. None " - "of the oneOf schemas matched the input data.".format(cls) - ) - elif len(oneof_classes) > 1: - raise ApiValueError( - "Invalid inputs given to generate an instance of {}. Multiple " - "oneOf schemas {} matched the inputs, but a max of one is allowed.".format(cls, oneof_classes) - ) - _instantiation_metadata.base_classes = new_base_classes - return path_to_schemas - - @classmethod - def __get_anyof_classes( - cls, - *args, - discriminated_cls, - _instantiation_metadata: InstantiationMetadata - ): - anyof_classes = [] - chosen_anyof_cls = None - original_base_classes = _instantiation_metadata.base_classes - path_to_schemas = defaultdict(set) - for anyof_cls in cls._composed_schemas['anyOf']: - if anyof_cls in _instantiation_metadata.base_classes: - continue - if isinstance(args[0], anyof_cls): - # passed in instance is the correct type - chosen_anyof_cls = anyof_cls - anyof_classes.append(anyof_cls) - continue - - _instantiation_metadata.base_classes = original_base_classes - try: - other_path_to_schemas = anyof_cls._validate(*args, _instantiation_metadata=_instantiation_metadata) - except (ApiValueError, ApiTypeError) as ex: - if discriminated_cls is not None and anyof_cls is discriminated_cls: - raise ex - continue - original_base_classes = _instantiation_metadata.base_classes - chosen_anyof_cls = anyof_cls - anyof_classes.append(anyof_cls) - update(path_to_schemas, other_path_to_schemas) - if not anyof_classes: - raise ApiValueError( - "Invalid inputs given to generate an instance of {}. None " - "of the anyOf schemas matched the input data.".format(cls) - ) - return path_to_schemas - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - ComposedBase _validate - We return dynamic classes of different bases depending upon the inputs - This makes it so: - - the returned instance is always a subclass of our defining schema - - this allows us to check type based on whether an instance is a subclass of a schema - - the returned instance is a serializable type (except for None, True, and False) which are enums - - Returns: - new_cls (type): the new class - - Raises: - ApiValueError: when a string can't be converted into a date or datetime and it must be one of those classes - ApiTypeError: when the input type is not in the list of allowed spec types - """ - if args and isinstance(args[0], Schema) and _instantiation_metadata.from_server is False: - if isinstance(args[0], cls): - # an instance of the correct type was passed in - return {} - raise ApiTypeError( - 'Incorrect type passed in, required type was {} and passed type was {} at {}'.format( - cls, - type(args[0]), - _instantiation_metadata.path_to_item - ) - ) - - # validation checking on types, validations, and enums - path_to_schemas = super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - - _instantiation_metadata.base_classes |= frozenset({cls}) - - # process composed schema - _discriminator = getattr(cls, '_discriminator', None) - discriminated_cls = None - if _discriminator and args and isinstance(args[0], frozendict): - disc_property_name = list(_discriminator.keys())[0] - cls._ensure_discriminator_value_present(disc_property_name, _instantiation_metadata, *args) - # get discriminated_cls by looking at the dict in the current class - discriminated_cls = cls._get_discriminated_class( - disc_property_name=disc_property_name, disc_payload_value=args[0][disc_property_name]) - if discriminated_cls is None: - raise ApiValueError( - "Invalid discriminator value '{}' was passed in to {}.{} Only the values {} are allowed at {}".format( - args[0][disc_property_name], - cls.__name__, - disc_property_name, - list(_discriminator[disc_property_name].keys()), - _instantiation_metadata.path_to_item + (disc_property_name,) - ) - ) - - if cls._composed_schemas['allOf']: - other_path_to_schemas = cls.__get_allof_classes(*args, _instantiation_metadata=_instantiation_metadata) - update(path_to_schemas, other_path_to_schemas) - if cls._composed_schemas['oneOf']: - other_path_to_schemas = cls.__get_oneof_class( - *args, - discriminated_cls=discriminated_cls, - _instantiation_metadata=_instantiation_metadata, - path_to_schemas=path_to_schemas - ) - update(path_to_schemas, other_path_to_schemas) - if cls._composed_schemas['anyOf']: - other_path_to_schemas = cls.__get_anyof_classes( - *args, - discriminated_cls=discriminated_cls, - _instantiation_metadata=_instantiation_metadata - ) - update(path_to_schemas, other_path_to_schemas) - - if discriminated_cls is not None: - # TODO use an exception from this package here - assert discriminated_cls in path_to_schemas[_instantiation_metadata.path_to_item] - return path_to_schemas - - -# DictBase, ListBase, NumberBase, StrBase, BoolBase, NoneBase -class ComposedSchema( - _SchemaTypeChecker(typing.Union[none_type, str, decimal.Decimal, bool, tuple, frozendict]), - ComposedBase, - DictBase, - ListBase, - NumberBase, - StrBase, - BoolBase, - NoneBase, - Schema -): - - # subclass properties - _composed_schemas = {} - - @classmethod - def _from_openapi_data(cls, *args: typing.Any, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None, **kwargs): - if not args: - if not kwargs: - raise ApiTypeError('{} is missing required input data in args or kwargs'.format(cls.__name__)) - args = (kwargs, ) - return super()._from_openapi_data(args[0], _instantiation_metadata=_instantiation_metadata) - - -class ListSchema( - _SchemaTypeChecker(typing.Union[tuple]), - ListBase, - Schema -): - - @classmethod - def _from_openapi_data(cls, arg: typing.List[typing.Any], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: typing.Union[list, tuple], **kwargs: InstantiationMetadata): - return super().__new__(cls, arg, **kwargs) - - -class NoneSchema( - _SchemaTypeChecker(typing.Union[none_type]), - NoneBase, - Schema -): - - @classmethod - def _from_openapi_data(cls, arg: None, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: None, **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class NumberSchema( - _SchemaTypeChecker(typing.Union[decimal.Decimal]), - NumberBase, - Schema -): - """ - This is used for type: number with no format - Both integers AND floats are accepted - """ - - @classmethod - def _from_openapi_data(cls, arg: typing.Union[int, float, decimal.Decimal], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: typing.Union[decimal.Decimal, int, float], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class IntBase(NumberBase): - @property - def as_int(self) -> int: - try: - return self._as_int - except AttributeError: - self._as_int = int(self) - return self._as_int - - @classmethod - def _validate_format(cls, arg: typing.Optional[decimal.Decimal], _instantiation_metadata: InstantiationMetadata): - if isinstance(arg, decimal.Decimal): - exponent = arg.as_tuple().exponent - if exponent != 0: - raise ApiValueError( - "Invalid value '{}' for type integer at {}".format(arg, _instantiation_metadata.path_to_item) - ) - - @classmethod - def _validate(cls, *args, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - """ - IntBase _validate - TODO what about types = (int, number) -> IntBase, NumberBase? We could drop int and keep number only - """ - cls._validate_format(args[0], _instantiation_metadata=_instantiation_metadata) - return super()._validate(*args, _instantiation_metadata=_instantiation_metadata) - - -class IntSchema(IntBase, NumberSchema): - - @classmethod - def _from_openapi_data(cls, arg: int, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: typing.Union[decimal.Decimal, int], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class Int32Schema( - _SchemaValidator( - inclusive_minimum=decimal.Decimal(-2147483648), - inclusive_maximum=decimal.Decimal(2147483647) - ), - IntSchema -): - pass - -class Int64Schema( - _SchemaValidator( - inclusive_minimum=decimal.Decimal(-9223372036854775808), - inclusive_maximum=decimal.Decimal(9223372036854775807) - ), - IntSchema -): - pass - - -class Float32Schema( - _SchemaValidator( - inclusive_minimum=decimal.Decimal(-3.4028234663852886e+38), - inclusive_maximum=decimal.Decimal(3.4028234663852886e+38) - ), - NumberSchema -): - - @classmethod - def _from_openapi_data(cls, arg: typing.Union[float, decimal.Decimal], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - # todo check format - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - -class Float64Schema( - _SchemaValidator( - inclusive_minimum=decimal.Decimal(-1.7976931348623157E+308), - inclusive_maximum=decimal.Decimal(1.7976931348623157E+308) - ), - NumberSchema -): - - @classmethod - def _from_openapi_data(cls, arg: typing.Union[float, decimal.Decimal], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - # todo check format - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - -class StrSchema( - _SchemaTypeChecker(typing.Union[str]), - StrBase, - Schema -): - """ - date + datetime string types must inherit from this class - That is because one can validate a str payload as both: - - type: string (format unset) - - type: string, format: date - """ - - @classmethod - def _from_openapi_data(cls, arg: typing.Union[str], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None) -> 'StrSchema': - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: typing.Union[str, date, datetime], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class DateSchema(DateBase, StrSchema): - - def __new__(cls, arg: typing.Union[str, datetime], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class DateTimeSchema(DateTimeBase, StrSchema): - - def __new__(cls, arg: typing.Union[str, datetime], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class DecimalSchema(DecimalBase, StrSchema): - - def __new__(cls, arg: typing.Union[str], **kwargs: typing.Union[InstantiationMetadata]): - """ - Note: Decimals may not be passed in because cast_to_allowed_types is only invoked once for payloads - which can be simple (str) or complex (dicts or lists with nested values) - Because casting is only done once and recursively casts all values prior to validation then for a potential - client side Decimal input if Decimal was accepted as an input in DecimalSchema then one would not know - if one was using it for a StrSchema (where it should be cast to str) or one is using it for NumberSchema - where it should stay as Decimal. - """ - return super().__new__(cls, arg, **kwargs) - - -class BytesSchema( - _SchemaTypeChecker(typing.Union[bytes]), - Schema, -): - """ - this class will subclass bytes and is immutable - """ - def __new__(cls, arg: typing.Union[bytes], **kwargs: typing.Union[InstantiationMetadata]): - return super(Schema, cls).__new__(cls, arg) - - -class FileSchema( - _SchemaTypeChecker(typing.Union[FileIO]), - Schema, -): - """ - This class is NOT immutable - Dynamic classes are built using it for example when AnyType allows in binary data - Al other schema classes ARE immutable - If one wanted to make this immutable one could make this a DictSchema with required properties: - - data = BytesSchema (which would be an immutable bytes based schema) - - file_name = StrSchema - and cast_to_allowed_types would convert bytes and file instances into dicts containing data + file_name - The downside would be that data would be stored in memory which one may not want to do for very large files - - The developer is responsible for closing this file and deleting it - - This class was kept as mutable: - - to allow file reading and writing to disk - - to be able to preserve file name info - """ - - def __new__(cls, arg: typing.Union[io.FileIO, io.BufferedReader], **kwargs: typing.Union[InstantiationMetadata]): - return super(Schema, cls).__new__(cls, arg) - - -class BinaryBase: - pass - - -class BinarySchema( - _SchemaTypeChecker(typing.Union[bytes, FileIO]), - ComposedBase, - BinaryBase, - Schema, -): - - @classmethod - @property - def _composed_schemas(cls): - # we need this here to make our import statements work - # we must store _composed_schemas in here so the code is only run - # when we invoke this method. If we kept this at the class - # level we would get an error because the class level - # code would be run when this module is imported, and these composed - # classes don't exist yet because their module has not finished - # loading - return { - 'allOf': [], - 'oneOf': [ - BytesSchema, - FileSchema, - ], - 'anyOf': [ - ], - } - - def __new__(cls, arg: typing.Union[io.FileIO, io.BufferedReader, bytes], **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg) - - -class BoolSchema( - _SchemaTypeChecker(typing.Union[bool]), - BoolBase, - Schema -): - - @classmethod - def _from_openapi_data(cls, arg: bool, _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, arg: bool, **kwargs: typing.Union[InstantiationMetadata]): - return super().__new__(cls, arg, **kwargs) - - -class AnyTypeSchema( - _SchemaTypeChecker( - typing.Union[frozendict, tuple, decimal.Decimal, str, bool, none_type, bytes, FileIO] - ), - DictBase, - ListBase, - NumberBase, - StrBase, - BoolBase, - NoneBase, - Schema -): - pass - - -class DictSchema( - _SchemaTypeChecker(typing.Union[frozendict]), - DictBase, - Schema -): - - @classmethod - def _from_openapi_data(cls, arg: typing.Dict[str, typing.Any], _instantiation_metadata: typing.Optional[InstantiationMetadata] = None): - return super()._from_openapi_data(arg, _instantiation_metadata=_instantiation_metadata) - - def __new__(cls, *args: typing.Union[dict, frozendict], **kwargs: typing.Union[dict, frozendict, list, tuple, decimal.Decimal, float, int, str, date, datetime, bool, None, bytes, Schema, Unset, InstantiationMetadata]): - return super().__new__(cls, *args, **kwargs) - - -schema_descendents = set([NoneSchema, DictSchema, ListSchema, NumberSchema, StrSchema, BoolSchema]) - - -def deserialize_file(response_data, configuration, content_disposition=None): - """Deserializes body to file - - Saves response body into a file in a temporary folder, - using the filename from the `Content-Disposition` header if provided. - - Args: - param response_data (str): the file data to write - configuration (Configuration): the instance to use to convert files - - Keyword Args: - content_disposition (str): the value of the Content-Disposition - header - - Returns: - (file_type): the deserialized file which is open - The user is responsible for closing and reading the file - """ - fd, path = tempfile.mkstemp(dir=configuration.temp_folder_path) - os.close(fd) - os.remove(path) - - if content_disposition: - filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', - content_disposition).group(1) - path = os.path.join(os.path.dirname(path), filename) - - with open(path, "wb") as f: - if isinstance(response_data, str): - # change str to bytes so we can write it - response_data = response_data.encode('utf-8') - f.write(response_data) - - f = open(path, "rb") - return f - - -@functools.cache -def get_new_class( - class_name: str, - bases: typing.Tuple[typing.Type[typing.Union[Schema, typing.Any]], ...] -) -> typing.Type[Schema]: - """ - Returns a new class that is made with the subclass bases - """ - return type(class_name, bases, {}) - - -LOG_CACHE_USAGE = False - - -def log_cache_usage(cache_fn): - if LOG_CACHE_USAGE: - print(cache_fn.__name__, cache_fn.cache_info()) diff --git a/openapi/python_explerimental_client_template/test-requirements.handlebars b/openapi/python_explerimental_client_template/test-requirements.handlebars deleted file mode 100644 index 3529726b..00000000 --- a/openapi/python_explerimental_client_template/test-requirements.handlebars +++ /dev/null @@ -1,15 +0,0 @@ -{{#if useNose}} -coverage>=4.0.3 -nose>=1.3.7 -pluggy>=0.3.1 -py>=1.4.31 -randomize>=0.13 -{{/if}} -{{#unless useNose}} -pytest~=4.6.7 # needed for python 3.4 -pytest-cov>=2.8.1 -pytest-randomly==1.2.3 # needed for python 3.4 -{{/unless}} -{{#if hasHttpSignatureMethods}} -pycryptodome>=3.9.0 -{{/if}} \ No newline at end of file diff --git a/openapi/python_explerimental_client_template/tox.handlebars b/openapi/python_explerimental_client_template/tox.handlebars deleted file mode 100644 index d1b68916..00000000 --- a/openapi/python_explerimental_client_template/tox.handlebars +++ /dev/null @@ -1,9 +0,0 @@ -[tox] -envlist = py39 - -[testenv] -deps=-r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -commands= - {{#unless useNose}}pytest --cov={{{packageName}}}{{/unless}}{{#if useNose}}nosetests{{/if}}