diff --git a/.circleci/parallel.sh b/.circleci/parallel.sh
index b8f42d3a3a1..b7c22ff7298 100755
--- a/.circleci/parallel.sh
+++ b/.circleci/parallel.sh
@@ -23,6 +23,7 @@ elif [ "$JOB_ID" = "testPythonClientSamples" ]; then
(cd samples/client/3_0_3_unit_test/python && make test)
(cd samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python && make test)
(cd samples/client/openapi_features/security/python && make test)
+ (cd samples/client/3_1_0_json_schema/python && make test)
else
echo "Running job $JOB_ID"
diff --git a/bin/generate_samples_configs/python_3_1_0_json_schema.yaml b/bin/generate_samples_configs/python_3_1_0_json_schema.yaml
new file mode 100644
index 00000000000..771332c43bc
--- /dev/null
+++ b/bin/generate_samples_configs/python_3_1_0_json_schema.yaml
@@ -0,0 +1,5 @@
+generatorName: python
+outputDir: samples/client/3_1_0_json_schema/python
+inputSpec: src/test/resources/3_1/json_schema.yaml
+additionalProperties:
+ packageName: json_schema_api
diff --git a/docs/generators/java.md b/docs/generators/java.md
index 4c7ba094af6..39dca02fcbc 100644
--- a/docs/generators/java.md
+++ b/docs/generators/java.md
@@ -316,6 +316,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|AdditionalProperties|✗|OAS2,OAS3
|AllOf|✗|OAS2,OAS3
|AnyOf|✗|OAS3
+|Contains|✗|OAS3
|Default|✗|OAS2,OAS3
|Discriminator|✓|OAS2,OAS3
|Enum|✓|OAS2,OAS3
diff --git a/docs/generators/jaxrs-jersey.md b/docs/generators/jaxrs-jersey.md
index de8258f6184..b2601907a84 100644
--- a/docs/generators/jaxrs-jersey.md
+++ b/docs/generators/jaxrs-jersey.md
@@ -299,6 +299,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|AdditionalProperties|✗|OAS2,OAS3
|AllOf|✗|OAS2,OAS3
|AnyOf|✗|OAS3
+|Contains|✗|OAS3
|Default|✗|OAS2,OAS3
|Discriminator|✓|OAS2,OAS3
|Enum|✓|OAS2,OAS3
diff --git a/docs/generators/jmeter.md b/docs/generators/jmeter.md
index 866f884d6b2..bb01c6cb064 100644
--- a/docs/generators/jmeter.md
+++ b/docs/generators/jmeter.md
@@ -158,6 +158,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|AdditionalProperties|✗|OAS2,OAS3
|AllOf|✗|OAS2,OAS3
|AnyOf|✗|OAS3
+|Contains|✗|OAS3
|Default|✗|OAS2,OAS3
|Discriminator|✓|OAS2,OAS3
|Enum|✓|OAS2,OAS3
diff --git a/docs/generators/kotlin.md b/docs/generators/kotlin.md
index 83b16ec866c..ffbe8dae385 100644
--- a/docs/generators/kotlin.md
+++ b/docs/generators/kotlin.md
@@ -268,6 +268,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|AdditionalProperties|✗|OAS2,OAS3
|AllOf|✗|OAS2,OAS3
|AnyOf|✗|OAS3
+|Contains|✗|OAS3
|Default|✗|OAS2,OAS3
|Discriminator|✓|OAS2,OAS3
|Enum|✓|OAS2,OAS3
diff --git a/docs/generators/python.md b/docs/generators/python.md
index 45179cd5279..a3f3d8356bc 100644
--- a/docs/generators/python.md
+++ b/docs/generators/python.md
@@ -227,6 +227,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|AdditionalProperties|✓|OAS2,OAS3
|AllOf|✓|OAS2,OAS3
|AnyOf|✓|OAS3
+|Contains|✓|OAS3
|Default|✓|OAS2,OAS3
|Discriminator|✓|OAS2,OAS3
|Enum|✓|OAS2,OAS3
diff --git a/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/api_configuration.py b/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/api_configuration.py
index 30deb1c7d10..1abc7dcbc6d 100644
--- a/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/api_configuration.py
+++ b/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/api_configuration.py
@@ -66,7 +66,7 @@ def __init__(
"""Constructor
"""
# Authentication Settings
- self.security_scheme_info = {}
+ self.security_scheme_info: typing.Dict[str, typing.Any] = {}
self.security_index_info = {'security': 0}
# Server Info
self.server_info: ServerInfo = server_info or {
diff --git a/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/schema_configuration.py b/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/schema_configuration.py
index e6a0d5e8b95..6c253e8300f 100644
--- a/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/schema_configuration.py
+++ b/samples/client/3_0_3_unit_test/python/src/unit_test_api/configurations/schema_configuration.py
@@ -16,6 +16,7 @@
'additional_properties': 'additionalProperties',
'all_of': 'allOf',
'any_of': 'anyOf',
+ 'contains': 'contains',
'discriminator': 'discriminator',
# default omitted because it has no validation impact
'enum_value_to_name': 'enum',
diff --git a/samples/client/3_0_3_unit_test/python/src/unit_test_api/schemas/validation.py b/samples/client/3_0_3_unit_test/python/src/unit_test_api/schemas/validation.py
index 913dedfc0dd..5c240b8b7a4 100644
--- a/samples/client/3_0_3_unit_test/python/src/unit_test_api/schemas/validation.py
+++ b/samples/client/3_0_3_unit_test/python/src/unit_test_api/schemas/validation.py
@@ -956,6 +956,41 @@ def validate_discriminator(
return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'types': validate_types,
@@ -982,5 +1017,6 @@ def validate_discriminator(
'any_of': validate_any_of,
'all_of': validate_all_of,
'not_': validate_not,
- 'discriminator': validate_discriminator
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
}
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/.gitignore b/samples/client/3_1_0_json_schema/python/.gitignore
new file mode 100644
index 00000000000..a62e8aba43f
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.gitignore
@@ -0,0 +1,67 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+dev-requirements.txt.log
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+venv/
+.venv/
+.python-version
+.pytest_cache
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#Ipython Notebook
+.ipynb_checkpoints
diff --git a/samples/client/3_1_0_json_schema/python/.gitlab-ci.yml b/samples/client/3_1_0_json_schema/python/.gitlab-ci.yml
new file mode 100644
index 00000000000..6a58a19c436
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.gitlab-ci.yml
@@ -0,0 +1,15 @@
+# ref: https://docs.gitlab.com/ee/ci/README.html
+
+stages:
+ - test
+
+.tests:
+ stage: test
+ script:
+ - pip install -r requirements.txt
+ - pip install -r test-requirements.txt
+ - pytest --cov=json_schema_api
+
+test-3.8:
+ extends: .tests
+ image: python:3.8-alpine
diff --git a/samples/client/3_1_0_json_schema/python/.openapi-generator-ignore b/samples/client/3_1_0_json_schema/python/.openapi-generator-ignore
new file mode 100644
index 00000000000..d24a2da8ae5
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/client/3_1_0_json_schema/python/.openapi-generator/FILES b/samples/client/3_1_0_json_schema/python/.openapi-generator/FILES
new file mode 100644
index 00000000000..7c8a35d4ef9
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.openapi-generator/FILES
@@ -0,0 +1,70 @@
+.gitignore
+.gitlab-ci.yml
+.travis.yml
+README.md
+docs/apis/tags/default_api.md
+docs/components/schema/any_type_contains_value.md
+docs/components/schema/array_contains_value.md
+docs/paths/some_path/get.md
+docs/paths/some_path/get/responses/response_200/content/application_json/schema.md
+docs/servers/server_0.md
+git_push.sh
+migration_2_0_0.md
+migration_3_0_0.md
+migration_other_python_generators.md
+pyproject.toml
+src/json_schema_api/__init__.py
+src/json_schema_api/api_client.py
+src/json_schema_api/api_response.py
+src/json_schema_api/apis/__init__.py
+src/json_schema_api/apis/path_to_api.py
+src/json_schema_api/apis/paths/__init__.py
+src/json_schema_api/apis/paths/some_path.py
+src/json_schema_api/apis/tag_to_api.py
+src/json_schema_api/apis/tags/__init__.py
+src/json_schema_api/apis/tags/default_api.py
+src/json_schema_api/components/__init__.py
+src/json_schema_api/components/schema/__init__.py
+src/json_schema_api/components/schema/any_type_contains_value.py
+src/json_schema_api/components/schema/array_contains_value.py
+src/json_schema_api/components/schemas/__init__.py
+src/json_schema_api/configurations/__init__.py
+src/json_schema_api/configurations/api_configuration.py
+src/json_schema_api/configurations/schema_configuration.py
+src/json_schema_api/exceptions.py
+src/json_schema_api/paths/__init__.py
+src/json_schema_api/paths/some_path/__init__.py
+src/json_schema_api/paths/some_path/get/__init__.py
+src/json_schema_api/paths/some_path/get/operation.py
+src/json_schema_api/paths/some_path/get/responses/__init__.py
+src/json_schema_api/paths/some_path/get/responses/response_200/__init__.py
+src/json_schema_api/paths/some_path/get/responses/response_200/content/__init__.py
+src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/__init__.py
+src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/schema.py
+src/json_schema_api/py.typed
+src/json_schema_api/rest.py
+src/json_schema_api/schemas/__init__.py
+src/json_schema_api/schemas/format.py
+src/json_schema_api/schemas/original_immutabledict.py
+src/json_schema_api/schemas/schema.py
+src/json_schema_api/schemas/schemas.py
+src/json_schema_api/schemas/validation.py
+src/json_schema_api/security_schemes.py
+src/json_schema_api/server.py
+src/json_schema_api/servers/__init__.py
+src/json_schema_api/servers/server_0.py
+src/json_schema_api/shared_imports/__init__.py
+src/json_schema_api/shared_imports/header_imports.py
+src/json_schema_api/shared_imports/operation_imports.py
+src/json_schema_api/shared_imports/response_imports.py
+src/json_schema_api/shared_imports/schema_imports.py
+src/json_schema_api/shared_imports/security_scheme_imports.py
+src/json_schema_api/shared_imports/server_imports.py
+test-requirements.txt
+test/__init__.py
+test/components/__init__.py
+test/components/schema/__init__.py
+test/test_paths/__init__.py
+test/test_paths/test_some_path/__init__.py
+test/test_paths/test_some_path/test_get.py
+tox.ini
diff --git a/samples/client/3_1_0_json_schema/python/.openapi-generator/VERSION b/samples/client/3_1_0_json_schema/python/.openapi-generator/VERSION
new file mode 100644
index 00000000000..717311e32e3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.openapi-generator/VERSION
@@ -0,0 +1 @@
+unset
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/.travis.yml b/samples/client/3_1_0_json_schema/python/.travis.yml
new file mode 100644
index 00000000000..e9d810b7a9c
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/.travis.yml
@@ -0,0 +1,11 @@
+# ref: https://docs.travis-ci.com/user/languages/python
+language: python
+python:
+ - "3.8"
+ - "3.9"
+# command to install dependencies
+install:
+ - "pip install -r requirements.txt"
+ - "pip install -r test-requirements.txt"
+# command to run tests
+script: pytest --cov=json_schema_api
diff --git a/samples/client/3_1_0_json_schema/python/Makefile b/samples/client/3_1_0_json_schema/python/Makefile
new file mode 100644
index 00000000000..b73277d308c
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/Makefile
@@ -0,0 +1,13 @@
+SETUP_OUT=*.egg-info
+VENV=venv
+
+clean:
+ rm -rf $(SETUP_OUT)
+ rm -rf $(VENV)
+ rm -rf .tox
+ rm -rf .coverage
+ find . -name "*.py[oc]" -delete
+ find . -name "__pycache__" -delete
+
+test: clean
+ bash ./test_python.sh
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/README.md b/samples/client/3_1_0_json_schema/python/README.md
new file mode 100644
index 00000000000..997e429b100
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/README.md
@@ -0,0 +1,198 @@
+# json-schema-api
+No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator)
+
+This Python package is automatically generated by the [OpenAPI JSON Schema Generator](https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) project:
+
+- API version: 1.0.0
+- Package version: 1.0.0
+- Build package: PythonClientGenerator
+
+## Requirements
+
+Python >=3.8
+
+## Migration Guides
+- [3.0.0 Migration Guide](migration_3_0_0.md)
+- [2.0.0 Migration Guide](migration_2_0_0.md)
+- [Migration from Other Python Generators](migration_other_python_generators.md)
+
+
+## Installation
+### pip install
+
+If the python package is hosted on a repository, you can install directly using:
+
+```sh
+pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git
+```
+(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git`)
+
+Then import the package:
+```python
+import json_schema_api
+```
+
+### Setuptools
+
+Install via [Setuptools](http://pypi.python.org/pypi/setuptools).
+
+```sh
+python -m pip install . --user
+```
+(or `python -m pip install .` to install the package for all users)
+
+Then import the package:
+```python
+import json_schema_api
+```
+
+## Usage Notes
+### Validation, Immutability, and Data Type
+This python code validates data to schema classes and return back an immutable instance containing the data
+which subclasses all validated schema classes. This ensure that
+- valid data cannot be mutated and become invalid to a set of schemas
+ - the one exception is that files are not immutable, so schema instances storing/sending/receiving files are not immutable
+
+Here is the mapping from json schema types to python subclassed types:
+| Json Schema Type | Python Base Class |
+| ---------------- | ----------------- |
+| object | schemas.immutabledict |
+| array | tuple |
+| string | str |
+| number | float, int |
+| integer | int |
+| boolean | bool |
+| null | None |
+| AnyType (unset) | typing.Union[schemas.immutabledict, tuple, str, float, int, bool, None] |
+
+### Storage of Json Schema Definition in Python Classes
+In openapi v3.0.3 there are ~ 28 json schema keywords. Almost all of them can apply if
+type is unset. I have chosen to separate the storage of json schema definition info and output
+validated classes for payload instantiation.
+
+
+ Reason
+
+This json schema data is stored in each class that is written for a schema, in a component or
+other openapi document location. This class is only responsible for storing schema info.
+Output classes like those that store dict payloads are written separately and are
+returned by the Schema.validate method when that method is passed in dict input.
+This prevents payload property access methods from
+colliding with json schema definition.
+
+
+### Json Schema Type Object
+Most component schemas (models) are probably of type object. Which is a map data structure.
+Json schema allows string keys in this map, which means schema properties can have key names that are
+invalid python variable names. Names like:
+- "hi-there"
+- "1variable"
+- "@now"
+- " "
+- "from"
+
+To allow these use cases to work, schemas.immutabledict is used as the base class of type object schemas.
+This means that one can use normal dict methods on instances of these classes.
+
+
+ Other Details
+
+- optional properties which were not set will not exist in the instance
+- None is only allowed in as a value if type: "null" was included or nullable: true was set
+- preserving the original key names is required to properly validate a payload to multiple json schemas
+
+
+### Json Schema Type + Format, Validated Data Storage
+N schemas can be validated on the same payload.
+To allow multiple schemas to validate, the data must be stored using one base class whether or not
+a json schema format constraint exists in the schema.
+See the below accessors for string data:
+- type string + format: See schemas.as_date, schemas.as_datetime, schemas.as_decimal, schemas.as_uuid
+
+In json schema, type: number with no format validates both integers and floats,
+so int and float values are stored for type number.
+
+
+ String + Date Example
+
+For example the string payload '2023-12-20' is validates to both of these schemas:
+1. string only
+```
+- type: string
+```
+2. string and date format
+```
+- type: string
+ format: date
+```
+Because of use cases like this, a datetime.date is allowed as an input to this schema, but the data
+is stored as a string.
+
+
+## Getting Started
+
+Please follow the [installation procedure](#installation) and then run the following:
+
+```python
+import json_schema_api
+from json_schema_api.configurations import api_configuration
+from json_schema_api.apis.tags import default_api
+from pprint import pprint
+used_configuration = api_configuration.ApiConfiguration(
+)
+# Enter a context with an instance of the API client
+with json_schema_api.ApiClient(used_configuration) as api_client:
+ # Create an instance of the API class
+ api_instance = default_api.DefaultApi(api_client)
+
+ # example, this endpoint has no required or optional parameters
+ try:
+ api_response = api_instance.get_some_path()
+ pprint(api_response)
+ except json_schema_api.ApiException as e:
+ print("Exception when calling DefaultApi->get_some_path: %s\n" % e)
+```
+
+## Servers
+server_index | Class | Description
+------------ | ----- | ------------
+0 | [Server0](docs/servers/server_0.md) |
+
+## Endpoints
+
+All URIs are relative to the selected server
+- The server is selected by passing in server_info and server_index into api_configuration.ApiConfiguration
+- Code samples in endpoints documents show how to do this
+- server_index can also be passed in to endpoint calls, see endpoint documentation
+
+HTTP request | Method | Description
+------------ | ------ | -------------
+/somePath **get** | [DefaultApi](docs/apis/tags/default_api.md).[get_some_path](docs/paths/some_path/get.md) |
+
+## Component Schemas
+
+Class | Description
+----- | ------------
+[AnyTypeContainsValue](docs/components/schema/any_type_contains_value.md) |
+[ArrayContainsValue](docs/components/schema/array_contains_value.md) |
+
+## Notes for Large OpenAPI documents
+If the OpenAPI document is large, imports in json_schema_api.apis.tags.tag_to_api and json_schema_api.components.schemas 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:
+- tagged api: `from json_schema_api.apis.tags.default_api import DefaultApi`
+- api for one path: `from json_schema_api.apis.paths.some_path import SomePath`
+- api for one operation (path + verb): `from json_schema_api.paths.some_path.get import ApiForget`
+- single model import: `from json_schema_api.components.schema.pet import Pet`
+
+Solution 2:
+Before importing the package, adjust the maximum recursion limit as shown below:
+```
+import sys
+sys.setrecursionlimit(1500)
+import json_schema_api
+from json_schema_api.apis.tags.tag_to_api import *
+from json_schema_api.components.schemas import *
+```
diff --git a/samples/client/3_1_0_json_schema/python/docs/apis/tags/default_api.md b/samples/client/3_1_0_json_schema/python/docs/apis/tags/default_api.md
new file mode 100644
index 00000000000..e60d861c4f9
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/apis/tags/default_api.md
@@ -0,0 +1,17 @@
+
+json_schema_api.apis.tags.default_api
+# DefaultApi
+
+## Description
+operations that lack tags are assigned this default tag
+
+All URIs are relative to the selected server
+- The server is selected by passing in server_info and server_index into api_configuration.ApiConfiguration
+- Code samples in endpoints documents show how to do this
+- server_index can also be passed in to endpoint calls, see endpoint documentation
+
+Method | Description
+------ | -------------
+[**get_some_path**](../../paths/some_path/get.md) |
+
+[[Back to top]](#top) [[Back to Endpoints]](../../../README.md#Endpoints) [[Back to README]](../../../README.md)
diff --git a/samples/client/3_1_0_json_schema/python/docs/components/schema/any_type_contains_value.md b/samples/client/3_1_0_json_schema/python/docs/components/schema/any_type_contains_value.md
new file mode 100644
index 00000000000..9df36e2ee52
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/components/schema/any_type_contains_value.md
@@ -0,0 +1,12 @@
+# AnyTypeContainsValue
+json_schema_api.components.schema.any_type_contains_value
+```
+type: schemas.Schema
+```
+
+## validate method
+Input Type | Return Type | Notes
+------------ | ------------- | -------------
+dict, schemas.immutabledict, str, datetime.date, datetime.datetime, uuid.UUID, int, float, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader | schemas.immutabledict, str, float, int, bool, None, tuple, bytes, io.FileIO |
+
+[[Back to top]](#top) [[Back to Component Schemas]](../../../README.md#Component-Schemas) [[Back to README]](../../../README.md)
diff --git a/samples/client/3_1_0_json_schema/python/docs/components/schema/array_contains_value.md b/samples/client/3_1_0_json_schema/python/docs/components/schema/array_contains_value.md
new file mode 100644
index 00000000000..a000d0d384f
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/components/schema/array_contains_value.md
@@ -0,0 +1,12 @@
+# ArrayContainsValue
+json_schema_api.components.schema.array_contains_value
+```
+type: schemas.Schema
+```
+
+## validate method
+Input Type | Return Type | Notes
+------------ | ------------- | -------------
+list, tuple | tuple |
+
+[[Back to top]](#top) [[Back to Component Schemas]](../../../README.md#Component-Schemas) [[Back to README]](../../../README.md)
diff --git a/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get.md b/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get.md
new file mode 100644
index 00000000000..086f162630c
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get.md
@@ -0,0 +1,105 @@
+json_schema_api.paths.some_path.operation
+# Operation Method Name
+
+| Method Name | Api Class | Notes |
+| ----------- | --------- | ----- |
+| get_some_path | [DefaultApi](../../apis/tags/default_api.md) | This api is only for tag=default |
+| get | ApiForGet | This api is only for this endpoint |
+| get | SomePath | This api is only for path=/somePath |
+
+## Table of Contents
+- [General Info](#general-info)
+- [Arguments](#arguments)
+- [Return Types](#return-types)
+- [Servers](#servers)
+- [Code Sample](#code-sample)
+
+## General Info
+| Field | Value |
+| ----- | ----- |
+| Path | "/somePath" |
+| HTTP Method | get |
+
+## Arguments
+
+Name | Type | Description | Notes
+------------- | ------------- | ------------- | -------------
+accept_content_types | typing.Tuple[str] | default is ("application/json", ) | Tells the server the content type(s) that are accepted by the client
+server_index | typing.Optional[int] | default is None | Allows one to select a different [server](#servers). If not None, must be one of [0]
+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_response.ApiResponseWithoutDeserialization will be returned
+
+## Return Types
+
+HTTP Status Code | Class | Description
+------------- | ------------- | -------------
+n/a | api_response.ApiResponseWithoutDeserialization | When skip_deserialization is True this response is returned
+200 | [ResponseFor200.ApiResponse](#responsefor200-apiresponse) | OK
+
+## ResponseFor200
+
+### Description
+OK
+
+### ResponseFor200 ApiResponse
+Name | Type | Description | Notes
+------------- | ------------- | ------------- | -------------
+response | urllib3.HTTPResponse | Raw response |
+[body](#responsefor200-body) | schemas.immutabledict, str, float, int, bool, None, tuple, bytes, io.FileIO | |
+headers | Unset | headers were not defined |
+
+### ResponseFor200 Body
+Content-Type | Schema
+------------ | -------
+"application/json" | [content.application_json.Schema](#responsefor200-content-applicationjson-schema)
+
+### Body Details
+#### ResponseFor200 content ApplicationJson Schema
+json_schema_api.paths.some_path.get.responses.response_200.content.application_json.schema
+```
+type: schemas.Schema
+```
+
+##### validate method
+Input Type | Return Type | Notes
+------------ | ------------- | -------------
+dict, schemas.immutabledict, str, datetime.date, datetime.datetime, uuid.UUID, int, float, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader | schemas.immutabledict, str, float, int, bool, None, tuple, bytes, io.FileIO |
+
+## Servers
+
+Set the available servers by defining your used servers in ApiConfiguration.server_info
+Then select your server by setting a server index in ApiConfiguration.server_index_info or by
+passing server_index in to the endpoint method.
+- these servers are the general api servers
+- defaults to server_index=0, server.url = http://api.example.xyz/v1
+
+server_index | Class | Description
+------------ | ----- | ------------
+0 | [Server0](../../servers/server_0.md) |
+
+## Code Sample
+
+```python
+import json_schema_api
+from json_schema_api.configurations import api_configuration
+from json_schema_api.apis.tags import default_api
+from pprint import pprint
+used_configuration = api_configuration.ApiConfiguration(
+)
+# Enter a context with an instance of the API client
+with json_schema_api.ApiClient(used_configuration) as api_client:
+ # Create an instance of the API class
+ api_instance = default_api.DefaultApi(api_client)
+
+ # example, this endpoint has no required or optional parameters
+ try:
+ api_response = api_instance.get_some_path()
+ pprint(api_response)
+ except json_schema_api.ApiException as e:
+ print("Exception when calling DefaultApi->get_some_path: %s\n" % e)
+```
+
+[[Back to top]](#top)
+[[Back to DefaultApi API]](../../apis/tags/default_api.md)
+[[Back to Endpoints]](../../../README.md#Endpoints) [[Back to README]](../../../README.md)
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get/responses/response_200/content/application_json/schema.md b/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get/responses/response_200/content/application_json/schema.md
new file mode 100644
index 00000000000..c69534cbdd3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/paths/some_path/get/responses/response_200/content/application_json/schema.md
@@ -0,0 +1,10 @@
+# Schema
+json_schema_api.paths.some_path.get.responses.response_200.content.application_json.schema
+```
+type: schemas.Schema
+```
+
+## validate method
+Input Type | Return Type | Notes
+------------ | ------------- | -------------
+dict, schemas.immutabledict, str, datetime.date, datetime.datetime, uuid.UUID, int, float, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader | schemas.immutabledict, str, float, int, bool, None, tuple, bytes, io.FileIO |
diff --git a/samples/client/3_1_0_json_schema/python/docs/servers/server_0.md b/samples/client/3_1_0_json_schema/python/docs/servers/server_0.md
new file mode 100644
index 00000000000..d3076afc978
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/docs/servers/server_0.md
@@ -0,0 +1,7 @@
+json_schema_api.servers.server_0
+# Server Server0
+
+## Url
+http://api.example.xyz/v1
+
+[[Back to top]](#top) [[Back to Servers]](../../README.md#Servers) [[Back to README]](../../README.md)
diff --git a/samples/client/3_1_0_json_schema/python/git_push.sh b/samples/client/3_1_0_json_schema/python/git_push.sh
new file mode 100644
index 00000000000..ced3be2b0c7
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/git_push.sh
@@ -0,0 +1,58 @@
+#!/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"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+ git_host="github.com"
+ echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+ git_user_id="GIT_USER_ID"
+ echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+ git_repo_id="GIT_REPO_ID"
+ echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+ release_note="Minor update"
+ echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new 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
+ fi
+
+fi
+
+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/samples/client/3_1_0_json_schema/python/migration_2_0_0.md b/samples/client/3_1_0_json_schema/python/migration_2_0_0.md
new file mode 100644
index 00000000000..ed19050a503
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/migration_2_0_0.md
@@ -0,0 +1,203 @@
+# Migration v1.X.X to v2.0.0
+
+- [Compatibility note for opeanpi-generator](#compatibility-note-for-opeanpi-generator)
+- [Component Generation](#component-generation)
+- [Packaging Changes](#packaging-changes)
+- [Path Generation](#path-generation)
+- [Configuration Info Refactored](#configuration-info-refactored)
+- [Servers and Security Generation](#servers-and-security-generation)
+- [Java Classes for Openapi Data Refactored](#java-classes-for-openapi-data-refactored)
+- [Api Access by Tags and Paths Updated](#api-access-by-tags-and-paths-updated)
+- [Some Method/Property/Input/Class Names Updated](#some-methodpropertyinputclass-names-updated)
+- [Documentation Updated](#documentation-updated)
+
+## Compatibility note for opeanpi-generator
+The v1.0.4 release is nearly identical to the openapi-generator v6.3.0 release
+
+Below is a summary of big changes when updating you code from v1.X.X to 2.0.0
+
+## Component Generation
+Update:
+Openapi document components in "#/components/x" are now generated in a components/x packages
+Ths applies to:
+- headers
+- parameters
+- request_bodies
+- responses
+- schemas
+- security_schemes
+
+The generator now writes a class for each of those generated components.
+
+### Reason
+A lot of openapi data is $ref references throughout an openapi document.
+With this update, those $ref source locations are generated with file system paths that closely mirror the openapi
+document json schema path to that info. $ref definition is then imported in generated python code.
+This minimizes the amount of code that is generated, imposes well defined encapsulation and allows templates to be
+re-used for many types of data regardless of where the data is in the source openapi document.
+
+### Action
+- Update where you are importing models from, models are now component schemas
+ - File path change: model/pet.py -> components/schema/pet.py
+
+## Packaging changes
+Code has been updated to use .toml packaging. Code is now distributed in a src directory
+
+### Reason
+These updates follow latest python packaging best practices
+
+### Action
+- if you are suppressing generation of any files, you will need to edit those file paths
+ - File Path Change: package_name -> src/package_name
+
+## Path Generation
+If paths contain inline descriptions of parameters, request bodies, responses, security, or servers,
+then those are now generated in separate files. Those files are imported into the endpoint code.
+File locations closely mirror the openapi data json schema path.
+
+### Reason
+Generating those files in paths that closely mirror the json schema paths will allow
+the generator to use $ref to any location in the openapi document in the future, not just components.
+These could include:
+- relative $refs
+- a request body $ref referring to the request body in another endpoint
+
+This also allowed the generation code to work the same way regardless of where the header/requestBody etc
+is in the source spec.
+
+Note:
+Operations are now at
+paths/somePath/get/operation.py
+and not at
+paths/somePath/get/__init__.py
+to minimize the amount of memory needed when importing the python package.
+The configurations.api_configuration.py file imports all servers defined at:
+- openapi document root
+- pathItem
+- pathItem operation
+And if servers were defined in pathItem operations, then every operation that contains a server would be imported
+in api_configuration.py. Which could be many many files.
+
+So instead
+- operation endpoint definition was moved to paths/somePath/get/operation.py
+- paths/somePath/get/__init__.py stays empty and does not need lots of memory when api_configuration.py imports servers
+- server information was kept in paths/somePath/get/servers/server_x.py
+
+### Action
+- if you are importing any endpoint form its file, update your import
+ - File path update: paths/somePath/get.py -> paths/somePath/get/operation.py
+
+## Configuration Info Refactored
+Configuration information was separated into two classes
+- configurations/api_configuration.py ApiConfiguration for:
+ - server info
+ - security (auth) info
+ - logging info
+- configurations/schema_configuration.py SchemaConfiguration for:
+ - disabled openapi/json-schema keywords
+
+### Reason
+Schema validation only relies on SchemaConfiguration data and does not need to know about any ApiConfiguration info
+General api configuration info was poorly structured in the legacy configuration class which had 13 inputs.
+The refactored ApiConfiguration now has 4 inputs which define servers and security info.
+Having these separate classes prevents circular imports when the schemas.py file imports its SchemaConfiguration class.
+
+### Action
+- When you instantiate ApiClient, update your code to pass in an instance of SchemaConfiguration + ApiConfiguration
+
+
+## Servers and Security Generation
+Servers are now generated as separate files with one class per file wherever they were defined in the openapi document
+- servers/server_0.py
+- paths/somePath/servers/server_0.py
+- paths/somePath/get/servers/server_0.py
+Security requirements objects are now generated as separate files with one security requirement object per file
+wherever they were defined in the openapi document
+- security/security_requirement_object_0.py
+- paths/somePath/get/security/security_requirement_object_0.py
+
+### Reason
+Server classes now re-use schema validation code to ensure that inputs to server variables are valid.
+Generating these separate files minimizes generated code and maximizes code re-use.
+
+### Action
+- If endpoints need to use specific servers or security not at index 0, pass security_index_info + server_index_info
+ into ApiConfiguration when instantiating it
+- If you use non-default server variable values, then update your code to pass in server_info into ApiConfiguration
+
+## Java Classes for Openapi Data Refactored
+Almost every java class used to store openapi document data at generation time has been re-written or refactored.
+The new classes are much shorter and contain only what is needed to generate code and documentation.
+This will make it much easier to add new openapi v3.1.0 features like new json schema keywords.
+Generator interface methods were also refactored to simplify Java model instantiation and code generation.
+Almost all properties are now public static final and are:
+- immutable
+- publicly accessible to the templates
+
+### Reason
+These updates make the code much more maintainable.
+The number of properties that have to be maintained is much smaller
+- Component Schema: ~100 properties in CodegenModel.java -> ~50 properties in CodegenSchema.java
+- PathItem Operation: ~75 properties CodegenOperation: ~25 properties
+
+This will reduce bugs like: why can't I access this property in this template
+Because instances are mostly immutable it is very clear where the wrong value is being created/assigned.
+
+### Action
+- if you are customizing the python generator, you will need to update your java code
+
+## Api Access By Tags and Paths Updated
+Previously, keys togo from tags and paths to apis were enums.
+The code was updated to use a TypedDict and uses strings as the keys.
+
+### Reason
+Making this change allows type hinting to work for the TypedDict with string keys
+
+### Action
+- If you use path_to_api.py or tag_to_api.py, update the key that you use to access the api
+
+## Some Method/Property/Input/Class Names Updated
+- is_true_oapg -> is_true_
+- is_false_oapg -> is_false_
+- is_none_oapg -> is_none_
+- as_date_oapg -> as_date_
+- as_datetime_oapg -> as_datetime_
+- as_decimal_oapg -> as_decimal_
+- as_uuid_oapg -> as_uuid_
+- as_float_oapg -> as_float_
+- as_int_oapg -> as_int_
+- get_item_oapg -> get_item_
+- from_openapi_data_oapg -> from_openapi_data_
+- _verify_typed_dict_inputs_oapg -> _verify_typed_dict_inputs
+- _configuration -> configuration_
+- _arg -> arg_
+- _args -> args_
+- MetaOapg -> Schema_
+- JsonSchema -> OpenApiSchema
+
+### Reason
+Classes can have arbitrarily named properties set on them
+Endpoints can have arbitrary operationId method names set
+For those reasons, I use the prefix and suffix _ to greatly reduce the likelihood of collisions
+on protected + public classes/methods.
+
+### Action
+- if you use the above methods/inputs/properties/classes update them to the latest names
+
+## Documentation Updated
+- components now have sections in the readme
+ - models became component schemas
+- one file is now generated for each endpoint
+ - that file now has a table of contents
+ - heading indentation levels now are indented correctly in descending order
+ - endpoint now includes server and security info (if security exists)
+- servers section added to readme if servers were defined in the openapi document root
+- security section added to readme if security was defined in the openapi document root
+
+### Reason
+Endpoint documentation had indentation and linking bugs before, this fixes them.
+When all endpoints docs were written in one file it was difficult to link ot the correct section.
+How to set server and security info was unclear before, the new docs and classes should clarify that.
+
+### Action
+- if you link to specific parts of the documentation, update your links
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/migration_3_0_0.md b/samples/client/3_1_0_json_schema/python/migration_3_0_0.md
new file mode 100644
index 00000000000..a7ee4715667
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/migration_3_0_0.md
@@ -0,0 +1,30 @@
+# Migration v2.0.0 to v3.0.0
+
+- DynamicSchema classes are no longer produced,
+ all schemas are validated, but only one schema class produces output classes for instantiation.
+ Instances no longer subclass all validated schemas.
+ - the schema that is used is the first schema that is checked
+ - so if you depended on .methodName access on a oneOf schema, that will no longer work
+ - instead use instance['methodName'] access
+ - so if you depended on isinstance(instance SomeOneOfSchema) instead check that the instance contains
+ the needed oneOf properties that you are trying to use
+- SomeSchema.__new__ no longer validates payloads
+ - instead use SomeSchema.validate or SomeSchemaDict.__new__
+- SomeSchema.Schema_ inner class has been moved to the class SomeSchema
+ - so if you need to access openapi schema info, use SomeSchema
+ - so if you depended on SomeSchema.SOME_ENUM, update it to SomeSchema.enums.SOME_ENUM
+- instance.get_item_ methods have been removed to reduce amount of generated code
+ - optional properties are now generated as @property methods
+ - so one can use instance.someProp OR instance.get('someProp', schemas.unset)
+- instance methods as_date_, as_datetime_, as_decimal_, as_uuid_ have been changed to functions and moved into the schemas module
+ - now that python primitives are returned from validation for str/int/float/bool/None, custom methods could not be provided for those instances
+ - so instead update your code to use schemas.as_date/as_datetime/as_decimal/as_uuid
+- Output classes are only written for json schema type object (dict) and array (tuple) types
+ - so if you depended on boolean/null/string/number instances being an instance of a Schema class
+ update your code to handle the primitive values instead
+- NoneClass instances no longer returned from schema validation, None returned instead
+ - so update your code to handle None values
+- BoolClass instances no longer returned from schema validation, True/False returned instead
+ - so update you code to handle bool values
+- Decimal instances are no longer returned for type integer or type number schemas, int or float values are returned
+ - so update your code to use the int or float values
diff --git a/samples/client/3_1_0_json_schema/python/migration_other_python_generators.md b/samples/client/3_1_0_json_schema/python/migration_other_python_generators.md
new file mode 100644
index 00000000000..3195e008bcb
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/migration_other_python_generators.md
@@ -0,0 +1,77 @@
+# Migration from Other Python Generators
+
+When switching from other python client generators you will need to make some changes to your code.
+
+1. This generator uses spec case for all (object) property names and parameter names.
+ - So if the spec has a property name like camelCase, it will use camelCase rather than camel_case
+ - So you will need to update how you input and read properties to use spec case
+ - endpoint calls will need to have their input arguments updated
+ - schema instance property usage and instantiation will need to be updated
+2. Endpoint parameters are stored in dictionaries to prevent collisions (explanation below)
+ - So you will need to update how you pass data in to endpoints
+ - update your endpoint calls to pass in parameter data in path_params, query_params, header_params etc dict inputs
+3. Endpoint responses now include the original response, the deserialized response body, and (todo)the deserialized headers
+ - So you will need to update your code to use response.body to access deserialized data
+4. All validated class instances are immutable except for ones based on io.File
+ - This is because if properties were changed after validation, that validation would no longer apply
+ - So no changing values or property values after a class has been instantiated
+5. String + Number types with formats
+ - String type data is stored as a string and if you need to access types based on its format like date,
+ date-time, uuid, number etc then you will need to use schemas accessor functions on the instance
+ - type string + format: See schemas.as_date, schemas.as_datetime, schemas.as_decimal, schemas.as_uuid
+ - this was done because openapi/json-schema defines constraints. string data may be type string with no format
+ keyword in one schema, and include a format constraint in another schema
+ - So if you need to access a string format based type, use as_date/as_datetime/as_decimal/as_uuid
+6. Property access on AnyType(type unset) or object(dict) schemas
+ - Only required keys with valid python names are properties like .someProp and have type hints
+ - All optional keys may not exist, properties are defined for them and if they are unset schemas.unset will be returned
+ - One can also access optional values with dict_instance['optionalProp'] and KeyError will be raised if it does not exist
+7. The location of the api classes has changed
+ - Api classes are located in your_package.apis.tags.some_api
+ - This change was made to eliminate redundant code generation
+ - Legacy generators generated the same endpoint twice if it had > 1 tag on it
+ - This generator defines an endpoint in one class, then inherits that class to generate
+ apis by tags and by paths
+ - This change reduces code and allows quicker run time if you use the path apis
+ - path apis are at your_package.apis.paths.some_path
+ - Those apis will only load their needed models, which is less to load than all of the resources needed in a tag api
+ - So you will need to update your import paths to the api classes
+
+### Why are Leading and Trailing Underscores in some class and method names?
+Classes can have arbitrarily named properties set on them
+Endpoints can have arbitrary operationId method names set
+For those reasons, I use the prefix and suffix _ to greatly reduce the likelihood of collisions
+on protected + public classes/methods.
+
+### Object property spec case
+This was done because when payloads are ingested, they can be validated against N number of schemas.
+If the input signature used a different property name then that has mutated the payload.
+So SchemaA and SchemaB must both see the camelCase spec named variable.
+Also it is possible to send in two properties, named camelCase and camel_case in the same payload.
+That use case should work, so spec case is used.
+
+### Parameter spec case
+Parameters can be included in different locations including:
+- query
+- path
+- header
+- cookie
+
+Any of those parameters could use the same parameter names, so if every parameter
+was included as an endpoint parameter in a function signature, they would collide.
+For that reason, each of those inputs have been separated out into separate typed dictionaries:
+- query_params
+- path_params
+- header_params
+- cookie_params
+
+So when updating your code, you will need to pass endpoint parameters in using those
+dictionaries.
+
+### Endpoint responses
+Endpoint responses have been enriched to now include more information.
+Any response reom an endpoint will now include the following properties:
+response: urllib3.HTTPResponse
+body: typing.Union[schemas.Unset, typing.Union[str, inf, float, None, schemas.immutabledict, tuple, bytes, schemas.FileIO]]
+headers: typing.Union[schemas.Unset, HeaderSchemaDict]
+Note: response header deserialization has not yet been added
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/pyproject.toml b/samples/client/3_1_0_json_schema/python/pyproject.toml
new file mode 100644
index 00000000000..e6fecdc7932
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/pyproject.toml
@@ -0,0 +1,41 @@
+# Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.setuptools.package-data]
+"json_schema_api" = ["py.typed"]
+
+[project]
+name = "json-schema-api"
+version = "1.0.0"
+authors = [
+ { name="OpenAPI JSON Schema Generator community" },
+]
+description = "Example"
+readme = "README.md"
+requires-python = ">=3.8"
+dependencies = [
+ "certifi >= 14.5.14",
+ "immutabledict ~= 3.0.0",
+ "python-dateutil ~= 2.7.0",
+ "setuptools >= 61.0",
+ "types-python-dateutil",
+ "types-urllib3",
+ "typing_extensions ~= 4.5.0",
+ "urllib3 ~= 2.0.a3",
+]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: MIT",
+ "Operating System :: OS Independent",
+ "Topic :: Software Development :: Code Generators"
+]
+
+
+[[tool.mypy.overrides]]
+module = 'json_schema_api'
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/__init__.py
new file mode 100644
index 00000000000..f97c9a6e952
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/__init__.py
@@ -0,0 +1,26 @@
+# coding: utf-8
+
+# flake8: noqa
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+__version__ = "1.0.0"
+
+# import ApiClient
+from json_schema_api.api_client import ApiClient
+
+# import Configuration
+from json_schema_api.configurations.api_configuration import ApiConfiguration
+
+# import exceptions
+from json_schema_api.exceptions import OpenApiException
+from json_schema_api.exceptions import ApiAttributeError
+from json_schema_api.exceptions import ApiTypeError
+from json_schema_api.exceptions import ApiValueError
+from json_schema_api.exceptions import ApiKeyError
+from json_schema_api.exceptions import ApiException
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_client.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_client.py
new file mode 100644
index 00000000000..c1759f7b4ed
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_client.py
@@ -0,0 +1,1398 @@
+# coding: utf-8
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+import abc
+import datetime
+import dataclasses
+import decimal
+import enum
+import email
+import json
+import os
+import io
+import atexit
+from multiprocessing import pool
+import re
+import tempfile
+import typing
+import typing_extensions
+from urllib import parse
+import urllib3
+from urllib3 import _collections, fields
+
+
+from json_schema_api import exceptions, rest, schemas, security_schemes, api_response
+from json_schema_api.configurations import api_configuration, schema_configuration as schema_configuration_
+
+
+class JSONEncoder(json.JSONEncoder):
+ compact_separators = (',', ':')
+
+ def default(self, obj: typing.Any):
+ if isinstance(obj, str):
+ return str(obj)
+ elif isinstance(obj, float):
+ return obj
+ elif isinstance(obj, bool):
+ # must be before int check
+ return obj
+ elif isinstance(obj, int):
+ return obj
+ elif obj is None:
+ return None
+ elif isinstance(obj, (dict, schemas.immutabledict)):
+ 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 exceptions.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'
+
+
+@dataclasses.dataclass
+class PrefixSeparatorIterator:
+ # A class to store prefixes and separators for rfc6570 expansions
+ prefix: str
+ separator: str
+ first: bool = True
+ item_separator: str = dataclasses.field(init=False)
+
+ def __post_init__(self):
+ self.item_separator = self.separator if self.separator in {'.', '|', '%20'} else ','
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.first:
+ self.first = False
+ return self.prefix
+ return self.separator
+
+
+class ParameterSerializerBase:
+ @staticmethod
+ def __ref6570_item_value(in_data: typing.Any, percent_encode: bool):
+ """
+ Get representation if str/float/int/None/items in list/ values in dict
+ None is returned if an item is undefined, use cases are value=
+ - None
+ - []
+ - {}
+ - [None, None None]
+ - {'a': None, 'b': None}
+ """
+ if type(in_data) in {str, float, int}:
+ if percent_encode:
+ return parse.quote(str(in_data))
+ return str(in_data)
+ elif in_data is None:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return None
+ elif isinstance(in_data, list) and not in_data:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return None
+ elif isinstance(in_data, dict) and not in_data:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return None
+ raise exceptions.ApiValueError('Unable to generate a ref6570 item representation of {}'.format(in_data))
+
+ @staticmethod
+ def _to_dict(name: str, value: str):
+ return {name: value}
+
+ @classmethod
+ def __ref6570_str_float_int_expansion(
+ cls,
+ variable_name: str,
+ in_data: typing.Any,
+ explode: bool,
+ percent_encode: bool,
+ prefix_separator_iterator: PrefixSeparatorIterator,
+ var_name_piece: str,
+ named_parameter_expansion: bool
+ ) -> str:
+ item_value = cls.__ref6570_item_value(in_data, percent_encode)
+ if item_value is None or (item_value == '' and prefix_separator_iterator.separator == ';'):
+ return next(prefix_separator_iterator) + var_name_piece
+ value_pair_equals = '=' if named_parameter_expansion else ''
+ return next(prefix_separator_iterator) + var_name_piece + value_pair_equals + item_value
+
+ @classmethod
+ def __ref6570_list_expansion(
+ cls,
+ variable_name: str,
+ in_data: typing.Any,
+ explode: bool,
+ percent_encode: bool,
+ prefix_separator_iterator: PrefixSeparatorIterator,
+ var_name_piece: str,
+ named_parameter_expansion: bool
+ ) -> str:
+ item_values = [cls.__ref6570_item_value(v, percent_encode) for v in in_data]
+ item_values = [v for v in item_values if v is not None]
+ if not item_values:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return ""
+ value_pair_equals = '=' if named_parameter_expansion else ''
+ if not explode:
+ return (
+ next(prefix_separator_iterator) +
+ var_name_piece +
+ value_pair_equals +
+ prefix_separator_iterator.item_separator.join(item_values)
+ )
+ # exploded
+ return next(prefix_separator_iterator) + next(prefix_separator_iterator).join(
+ [var_name_piece + value_pair_equals + val for val in item_values]
+ )
+
+ @classmethod
+ def __ref6570_dict_expansion(
+ cls,
+ variable_name: str,
+ in_data: typing.Any,
+ explode: bool,
+ percent_encode: bool,
+ prefix_separator_iterator: PrefixSeparatorIterator,
+ var_name_piece: str,
+ named_parameter_expansion: bool
+ ) -> str:
+ in_data_transformed = {key: cls.__ref6570_item_value(val, percent_encode) for key, val in in_data.items()}
+ in_data_transformed = {key: val for key, val in in_data_transformed.items() if val is not None}
+ if not in_data_transformed:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return ""
+ value_pair_equals = '=' if named_parameter_expansion else ''
+ if not explode:
+ return (
+ next(prefix_separator_iterator) +
+ var_name_piece + value_pair_equals +
+ prefix_separator_iterator.item_separator.join(
+ prefix_separator_iterator.item_separator.join(
+ item_pair
+ ) for item_pair in in_data_transformed.items()
+ )
+ )
+ # exploded
+ return next(prefix_separator_iterator) + next(prefix_separator_iterator).join(
+ [key + '=' + val for key, val in in_data_transformed.items()]
+ )
+
+ @classmethod
+ def _ref6570_expansion(
+ cls,
+ variable_name: str,
+ in_data: typing.Any,
+ explode: bool,
+ percent_encode: bool,
+ prefix_separator_iterator: PrefixSeparatorIterator
+ ) -> str:
+ """
+ Separator is for separate variables like dict with explode true, not for array item separation
+ """
+ named_parameter_expansion = prefix_separator_iterator.separator in {'&', ';'}
+ var_name_piece = variable_name if named_parameter_expansion else ''
+ if type(in_data) in {str, float, int}:
+ return cls.__ref6570_str_float_int_expansion(
+ variable_name,
+ in_data,
+ explode,
+ percent_encode,
+ prefix_separator_iterator,
+ var_name_piece,
+ named_parameter_expansion
+ )
+ elif in_data is None:
+ # ignored by the expansion process https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.1
+ return ""
+ elif isinstance(in_data, list):
+ return cls.__ref6570_list_expansion(
+ variable_name,
+ in_data,
+ explode,
+ percent_encode,
+ prefix_separator_iterator,
+ var_name_piece,
+ named_parameter_expansion
+ )
+ elif isinstance(in_data, dict):
+ return cls.__ref6570_dict_expansion(
+ variable_name,
+ in_data,
+ explode,
+ percent_encode,
+ prefix_separator_iterator,
+ var_name_piece,
+ named_parameter_expansion
+ )
+ # bool, bytes, etc
+ raise exceptions.ApiValueError('Unable to generate a ref6570 representation of {}'.format(in_data))
+
+
+class StyleFormSerializer(ParameterSerializerBase):
+ @classmethod
+ def _serialize_form(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ name: str,
+ explode: bool,
+ percent_encode: bool,
+ prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator] = None
+ ) -> str:
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = PrefixSeparatorIterator('', '&')
+ return cls._ref6570_expansion(
+ variable_name=name,
+ in_data=in_data,
+ explode=explode,
+ percent_encode=percent_encode,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+
+
+class StyleSimpleSerializer(ParameterSerializerBase):
+
+ @classmethod
+ def _serialize_simple(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ name: str,
+ explode: bool,
+ percent_encode: bool
+ ) -> str:
+ prefix_separator_iterator = PrefixSeparatorIterator('', ',')
+ return cls._ref6570_expansion(
+ variable_name=name,
+ in_data=in_data,
+ explode=explode,
+ percent_encode=percent_encode,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+
+ @classmethod
+ def _deserialize_simple(
+ cls,
+ in_data: str,
+ name: str,
+ explode: bool,
+ percent_encode: bool
+ ) -> typing.Union[str, typing.List[str], typing.Dict[str, str]]:
+ raise NotImplementedError(
+ "Deserialization of style=simple has not yet been added. "
+ "If you need this how about you submit a PR adding it?"
+ )
+
+
+class JSONDetector:
+ """
+ Works for:
+ application/json
+ application/json; charset=UTF-8
+ application/json-patch+json
+ application/geo+json
+ """
+ __json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")
+
+ @classmethod
+ def _content_type_is_json(cls, content_type: str) -> bool:
+ if cls.__json_content_type_pattern.match(content_type):
+ return True
+ return False
+
+
+class Encoding:
+ content_type: str
+ headers: typing.Optional[typing.Dict[str, 'HeaderParameter']] = None
+ style: typing.Optional[ParameterStyle] = None
+ explode: bool = False
+ allow_reserved: bool = False
+
+
+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.
+ """
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ encoding: typing.Optional[typing.Dict[str, Encoding]] = None
+
+
+class ParameterBase(JSONDetector):
+ in_type: ParameterInType
+ required: bool
+ style: typing.Optional[ParameterStyle]
+ explode: typing.Optional[bool]
+ allow_reserved: typing.Optional[bool]
+ schema: typing.Optional[typing.Type[schemas.Schema]]
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]]
+
+ _json_encoder = JSONEncoder()
+
+ def __init_subclass__(cls, **kwargs):
+ if cls.explode is None:
+ if cls.style is ParameterStyle.FORM:
+ cls.explode = True
+ else:
+ cls.explode = False
+
+ @classmethod
+ def _serialize_json(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ eliminate_whitespace: bool = False
+ ) -> str:
+ if eliminate_whitespace:
+ return json.dumps(in_data, separators=cls._json_encoder.compact_separators)
+ return json.dumps(in_data)
+
+_SERIALIZE_TYPES = typing.Union[
+ int,
+ float,
+ str,
+ datetime.date,
+ datetime.datetime,
+ None,
+ bool,
+ list,
+ tuple,
+ dict,
+ schemas.immutabledict
+]
+
+_JSON_TYPES = typing.Union[
+ int,
+ float,
+ str,
+ None,
+ bool,
+ typing.Tuple['_JSON_TYPES', ...],
+ schemas.immutabledict[str, '_JSON_TYPES'],
+]
+
+@dataclasses.dataclass
+class PathParameter(ParameterBase, StyleSimpleSerializer):
+ name: str
+ required: bool = False
+ in_type: ParameterInType = ParameterInType.PATH
+ style: ParameterStyle = ParameterStyle.SIMPLE
+ explode: bool = False
+ allow_reserved: typing.Optional[bool] = None
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+
+ @classmethod
+ def __serialize_label(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list]
+ ) -> typing.Dict[str, str]:
+ prefix_separator_iterator = PrefixSeparatorIterator('.', '.')
+ value = cls._ref6570_expansion(
+ variable_name=cls.name,
+ in_data=in_data,
+ explode=cls.explode,
+ percent_encode=True,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def __serialize_matrix(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list]
+ ) -> typing.Dict[str, str]:
+ prefix_separator_iterator = PrefixSeparatorIterator(';', ';')
+ value = cls._ref6570_expansion(
+ variable_name=cls.name,
+ in_data=in_data,
+ explode=cls.explode,
+ percent_encode=True,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def __serialize_simple(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ ) -> typing.Dict[str, str]:
+ value = cls._serialize_simple(
+ in_data=in_data,
+ name=cls.name,
+ explode=cls.explode,
+ percent_encode=True
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def serialize(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ skip_validation: bool = False
+ ) -> typing.Dict[str, str]:
+ if cls.schema:
+ cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ """
+ simple -> path
+ path:
+ returns path_params: dict
+ label -> path
+ returns path_params
+ matrix -> path
+ returns path_params
+ """
+ if cls.style:
+ if cls.style is ParameterStyle.SIMPLE:
+ return cls.__serialize_simple(cast_in_data)
+ elif cls.style is ParameterStyle.LABEL:
+ return cls.__serialize_label(cast_in_data)
+ elif cls.style is ParameterStyle.MATRIX:
+ return cls.__serialize_matrix(cast_in_data)
+ assert cls.content is not None
+ for content_type, media_type in cls.content.items():
+ assert media_type.schema is not None
+ cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ if cls._content_type_is_json(content_type):
+ value = cls._serialize_json(cast_in_data)
+ return cls._to_dict(cls.name, value)
+ else:
+ raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
+ raise ValueError('Invalid value for content, it was empty and must have 1 key value pair')
+
+
+@dataclasses.dataclass
+class QueryParameter(ParameterBase, StyleFormSerializer):
+ name: str
+ required: bool = False
+ in_type: ParameterInType = ParameterInType.QUERY
+ style: ParameterStyle = ParameterStyle.FORM
+ explode: typing.Optional[bool] = None
+ allow_reserved: typing.Optional[bool] = None
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+
+ @classmethod
+ def __serialize_space_delimited(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator],
+ explode: bool
+ ) -> typing.Dict[str, str]:
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = cls.get_prefix_separator_iterator()
+ value = cls._ref6570_expansion(
+ variable_name=cls.name,
+ in_data=in_data,
+ explode=explode,
+ percent_encode=True,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def __serialize_pipe_delimited(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator],
+ explode: bool
+ ) -> typing.Dict[str, str]:
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = cls.get_prefix_separator_iterator()
+ value = cls._ref6570_expansion(
+ variable_name=cls.name,
+ in_data=in_data,
+ explode=explode,
+ percent_encode=True,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def __serialize_form(
+ cls,
+ in_data: typing.Union[None, int, float, str, bool, dict, list],
+ prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator],
+ explode: bool
+ ) -> typing.Dict[str, str]:
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = cls.get_prefix_separator_iterator()
+ value = cls._serialize_form(
+ in_data,
+ name=cls.name,
+ explode=explode,
+ percent_encode=True,
+ prefix_separator_iterator=prefix_separator_iterator
+ )
+ return cls._to_dict(cls.name, value)
+
+ @classmethod
+ def get_prefix_separator_iterator(cls) -> PrefixSeparatorIterator:
+ if cls.style is ParameterStyle.FORM:
+ return PrefixSeparatorIterator('?', '&')
+ elif cls.style is ParameterStyle.SPACE_DELIMITED:
+ return PrefixSeparatorIterator('', '%20')
+ elif cls.style is ParameterStyle.PIPE_DELIMITED:
+ return PrefixSeparatorIterator('', '|')
+ raise ValueError(f'No iterator possible for style={cls.style}')
+
+ @classmethod
+ def serialize(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ prefix_separator_iterator: typing.Optional[PrefixSeparatorIterator] = None,
+ skip_validation: bool = False
+ ) -> typing.Dict[str, str]:
+ if cls.schema:
+ cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data)
+ cast_in_data = cls._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 cls.style:
+ # TODO update query ones to omit setting values when [] {} or None is input
+ explode = cls.explode if cls.explode is not None else cls.style == ParameterStyle.FORM
+ if cls.style is ParameterStyle.FORM:
+ return cls.__serialize_form(cast_in_data, prefix_separator_iterator, explode)
+ elif cls.style is ParameterStyle.SPACE_DELIMITED:
+ return cls.__serialize_space_delimited(cast_in_data, prefix_separator_iterator, explode)
+ elif cls.style is ParameterStyle.PIPE_DELIMITED:
+ return cls.__serialize_pipe_delimited(cast_in_data, prefix_separator_iterator, explode)
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = cls.get_prefix_separator_iterator()
+ assert cls.content is not None
+ for content_type, media_type in cls.content.items():
+ assert media_type.schema is not None
+ cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ if cls._content_type_is_json(content_type):
+ value = cls._serialize_json(cast_in_data, eliminate_whitespace=True)
+ return cls._to_dict(
+ cls.name,
+ next(prefix_separator_iterator) + cls.name + '=' + parse.quote(value)
+ )
+ else:
+ raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
+ raise ValueError('Invalid value for content, it was empty and must have 1 key value pair')
+
+
+@dataclasses.dataclass
+class CookieParameter(ParameterBase, StyleFormSerializer):
+ name: str
+ required: bool = False
+ style: ParameterStyle = ParameterStyle.FORM
+ in_type: ParameterInType = ParameterInType.COOKIE
+ explode: typing.Optional[bool] = None
+ allow_reserved: typing.Optional[bool] = None
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+
+ @classmethod
+ def serialize(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ skip_validation: bool = False
+ ) -> typing.Dict[str, str]:
+ if cls.schema:
+ cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ """
+ form -> cookie
+ returns fields: tuple
+ """
+ if cls.style:
+ """
+ TODO add escaping of comma, space, equals
+ or turn encoding on
+ """
+ explode = cls.explode if cls.explode is not None else cls.style == ParameterStyle.FORM
+ value = cls._serialize_form(
+ cast_in_data,
+ explode=explode,
+ name=cls.name,
+ percent_encode=False,
+ prefix_separator_iterator=PrefixSeparatorIterator('', '&')
+ )
+ return cls._to_dict(cls.name, value)
+ assert cls.content is not None
+ for content_type, media_type in cls.content.items():
+ assert media_type.schema is not None
+ cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ if cls._content_type_is_json(content_type):
+ value = cls._serialize_json(cast_in_data)
+ return cls._to_dict(cls.name, value)
+ else:
+ raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
+ raise ValueError('Invalid value for content, it was empty and must have 1 key value pair')
+
+
+class __HeaderParameterBase(ParameterBase, StyleSimpleSerializer):
+ style: ParameterStyle = ParameterStyle.SIMPLE
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+ explode: bool = False
+
+ @staticmethod
+ def __to_headers(in_data: typing.Tuple[typing.Tuple[str, str], ...]) -> _collections.HTTPHeaderDict:
+ data = tuple(t for t in in_data if t)
+ headers = _collections.HTTPHeaderDict()
+ if not data:
+ return headers
+ headers.extend(data)
+ return headers
+
+ @classmethod
+ def serialize_with_name(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ name: str,
+ skip_validation: bool = False
+ ) -> _collections.HTTPHeaderDict:
+ if cls.schema:
+ cast_in_data = in_data if skip_validation else cls.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ """
+ simple -> header
+ headers: PoolManager needs a mapping, tuple is close
+ returns headers: dict
+ """
+ if cls.style:
+ value = cls._serialize_simple(cast_in_data, name, cls.explode, False)
+ return cls.__to_headers(((name, value),))
+ assert cls.content is not None
+ for content_type, media_type in cls.content.items():
+ assert media_type.schema is not None
+ cast_in_data = in_data if skip_validation else media_type.schema.validate_base(in_data)
+ cast_in_data = cls._json_encoder.default(cast_in_data)
+ if cls._content_type_is_json(content_type):
+ value = cls._serialize_json(cast_in_data)
+ return cls.__to_headers(((name, value),))
+ else:
+ raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
+ raise ValueError('Invalid value for content, it was empty and must have 1 key value pair')
+
+ @classmethod
+ def deserialize(
+ cls,
+ in_data: str,
+ name: str
+ ):
+ if cls.schema:
+ """
+ simple -> header
+ headers: PoolManager needs a mapping, tuple is close
+ returns headers: dict
+ """
+ if cls.style:
+ extracted_data = cls._deserialize_simple(in_data, name, cls.explode, False)
+ return cls.schema.validate_base(extracted_data)
+ assert cls.content is not None
+ for content_type, media_type in cls.content.items():
+ if cls._content_type_is_json(content_type):
+ cast_in_data: typing.Union[dict, list, None, int, float, str] = json.loads(in_data)
+ assert media_type.schema is not None
+ return media_type.schema.validate_base(cast_in_data)
+ else:
+ raise NotImplementedError('Deserialization of {} has not yet been implemented'.format(content_type))
+ raise ValueError('Invalid value for content, it was empty and must have 1 key value pair')
+
+
+class HeaderParameterWithoutName(__HeaderParameterBase):
+ required: bool = False
+ style: ParameterStyle = ParameterStyle.SIMPLE
+ in_type: ParameterInType = ParameterInType.HEADER
+ explode: bool = False
+ allow_reserved: typing.Optional[bool] = None
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+
+ @classmethod
+ def serialize(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ name: str,
+ skip_validation: bool = False
+ ) -> _collections.HTTPHeaderDict:
+ return cls.serialize_with_name(
+ in_data,
+ name,
+ skip_validation=skip_validation
+ )
+
+
+class HeaderParameter(__HeaderParameterBase):
+ name: str
+ required: bool = False
+ style: ParameterStyle = ParameterStyle.SIMPLE
+ in_type: ParameterInType = ParameterInType.HEADER
+ explode: bool = False
+ allow_reserved: typing.Optional[bool] = None
+ schema: typing.Optional[typing.Type[schemas.Schema]] = None
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+
+ @classmethod
+ def serialize(
+ cls,
+ in_data: _SERIALIZE_TYPES,
+ skip_validation: bool = False
+ ) -> _collections.HTTPHeaderDict:
+ return cls.serialize_with_name(
+ in_data,
+ cls.name,
+ skip_validation=skip_validation
+ )
+
+T = typing.TypeVar("T", bound=api_response.ApiResponse)
+
+
+class OpenApiResponse(typing.Generic[T], JSONDetector, abc.ABC):
+ __filename_content_disposition_pattern = re.compile('filename="(.+?)"')
+ content: typing.Optional[typing.Dict[str, typing.Type[MediaType]]] = None
+ headers: typing.Optional[typing.Dict[str, typing.Type[HeaderParameterWithoutName]]] = None
+ headers_schema: typing.Optional[typing.Type[schemas.Schema]] = None
+
+ @classmethod
+ @abc.abstractmethod
+ def get_response(cls, response, headers, body) -> T: ...
+
+ @staticmethod
+ def __deserialize_json(response: urllib3.HTTPResponse) -> typing.Any:
+ # python must be >= 3.9 so we can pass in bytes into json.loads
+ return json.loads(response.data)
+
+ @staticmethod
+ def __file_name_from_response_url(response_url: typing.Optional[str]) -> typing.Optional[str]:
+ if response_url is None:
+ return None
+ url_path = parse.urlparse(response_url).path
+ if url_path:
+ path_basename = os.path.basename(url_path)
+ if path_basename:
+ _filename, ext = os.path.splitext(path_basename)
+ if ext:
+ return path_basename
+ return None
+
+ @classmethod
+ def __file_name_from_content_disposition(cls, content_disposition: typing.Optional[str]) -> typing.Optional[str]:
+ if content_disposition is None:
+ return None
+ match = cls.__filename_content_disposition_pattern.search(content_disposition)
+ if not match:
+ return None
+ return match.group(1)
+
+ @classmethod
+ def __deserialize_application_octet_stream(
+ cls, 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 = (
+ cls.__file_name_from_content_disposition(response.headers.get('content-disposition'))
+ or cls.__file_name_from_response_url(response.geturl())
+ )
+
+ if file_name is None:
+ _fd, path = tempfile.mkstemp()
+ else:
+ path = os.path.join(tempfile.gettempdir(), file_name)
+
+ with open(path, 'wb') as write_file:
+ chunk_size = 1024
+ while True:
+ data = response.read(chunk_size)
+ if not data:
+ break
+ write_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
+
+ @staticmethod
+ def __deserialize_multipart_form_data(
+ response: urllib3.HTTPResponse
+ ) -> typing.Dict[str, typing.Any]:
+ msg = email.message_from_bytes(response.data)
+ return {
+ part.get_param("name", header="Content-Disposition"): part.get_payload(
+ decode=True
+ ).decode(part.get_content_charset())
+ if part.get_content_charset()
+ else part.get_payload()
+ for part in msg.get_payload()
+ }
+
+ @classmethod
+ def deserialize(cls, response: urllib3.HTTPResponse, configuration: schema_configuration_.SchemaConfiguration) -> T:
+ content_type = response.headers.get('content-type')
+ deserialized_body = schemas.unset
+ streamed = response.supports_chunked_reads()
+
+ deserialized_headers: typing.Union[schemas.Unset, typing.Dict[str, typing.Any]] = schemas.unset
+ if cls.headers is not None and cls.headers_schema is not None:
+ deserialized_headers = {}
+ for header_name, header_param in cls.headers.items():
+ header_value = response.headers.get(header_name)
+ if header_value is None:
+ continue
+ header_value = header_param.deserialize(header_value, header_name)
+ deserialized_headers[header_name] = header_value
+ deserialized_headers = cls.headers_schema.validate_base(deserialized_headers, configuration=configuration)
+
+ if cls.content is not None:
+ if content_type not in cls.content:
+ raise exceptions.ApiValueError(
+ f"Invalid content_type returned. Content_type='{content_type}' was returned "
+ f"when only {str(set(cls.content))} are defined for status_code={str(response.status)}"
+ )
+ body_schema = cls.content[content_type].schema
+ if body_schema is None:
+ # some specs do not define response content media type schemas
+ return cls.get_response(
+ response=response,
+ headers=deserialized_headers,
+ body=schemas.unset
+ )
+
+ if cls._content_type_is_json(content_type):
+ body_data = cls.__deserialize_json(response)
+ elif content_type == 'application/octet-stream':
+ body_data = cls.__deserialize_application_octet_stream(response)
+ elif content_type.startswith('multipart/form-data'):
+ body_data = cls.__deserialize_multipart_form_data(response)
+ content_type = 'multipart/form-data'
+ else:
+ raise NotImplementedError('Deserialization of {} has not yet been implemented'.format(content_type))
+ body_schema = schemas.get_class(body_schema)
+ if body_schema is schemas.BinarySchema:
+ deserialized_body = body_schema.validate_base(body_data)
+ else:
+ deserialized_body = body_schema.validate_base(
+ body_data, configuration=configuration)
+ elif streamed:
+ response.release_conn()
+
+ return cls.get_response(
+ response=response,
+ headers=deserialized_headers,
+ body=deserialized_body
+ )
+
+
+@dataclasses.dataclass
+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 JSON Schema Generator.
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+ Do not edit the class manually.
+
+ :param configuration: api_configuration.ApiConfiguration object for this client
+ :param schema_configuration: schema_configuration_.SchemaConfiguration object for this client
+ :param default_headers: any default headers to include 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.
+ """
+ configuration: api_configuration.ApiConfiguration = dataclasses.field(
+ default_factory=lambda: api_configuration.ApiConfiguration())
+ schema_configuration: schema_configuration_.SchemaConfiguration = dataclasses.field(
+ default_factory=lambda: schema_configuration_.SchemaConfiguration())
+ default_headers: _collections.HTTPHeaderDict = dataclasses.field(
+ default_factory=lambda: _collections.HTTPHeaderDict())
+ pool_threads: int = 1
+ user_agent: str = 'OpenAPI-JSON-Schema-Generator/1.0.0/python'
+ rest_client: rest.RESTClientObject = dataclasses.field(init=False)
+
+ def __post_init__(self):
+ self._pool = None
+ self.rest_client = rest.RESTClientObject(self.configuration)
+
+ 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 = pool.ThreadPool(self.pool_threads)
+ return self._pool
+
+ def set_default_header(self, header_name: str, header_value: str):
+ self.default_headers[header_name] = header_value
+
+ def call_api(
+ self,
+ resource_path: str,
+ method: str,
+ host: str,
+ query_params_suffix: typing.Optional[str] = None,
+ headers: typing.Optional[_collections.HTTPHeaderDict] = None,
+ body: typing.Union[str, bytes, None] = None,
+ fields: typing.Optional[typing.Tuple[rest.RequestField, ...]] = None,
+ security_requirement_object: typing.Optional[security_schemes.SecurityRequirementObject] = None,
+ stream: bool = False,
+ timeout: typing.Union[int, float, typing.Tuple, None] = None,
+ ) -> urllib3.HTTPResponse:
+ """Makes the HTTP request (synchronous) and returns deserialized data.
+
+ :param resource_path: Path to method endpoint.
+ :param method: Method to call.
+ :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 security_requirement_object: The security requirement object, used to apply auth when making the call
+ :param async_req: execute request asynchronously
+ :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 filesystem file and the schemas.BinarySchema
+ instance will also inherit from FileSchema and schemas.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:
+ the method will return the response directly.
+ """
+ # header parameters
+ used_headers = _collections.HTTPHeaderDict(self.default_headers)
+ user_agent_key = 'User-Agent'
+ if user_agent_key not in used_headers and self.user_agent:
+ used_headers[user_agent_key] = self.user_agent
+
+ # auth setting
+ self.update_params_for_auth(
+ used_headers,
+ security_requirement_object,
+ resource_path,
+ method,
+ body,
+ query_params_suffix
+ )
+
+ # must happen after auth setting in case user is overriding those
+ if headers:
+ used_headers.update(headers)
+
+ # request url
+ url = host + resource_path
+ if query_params_suffix:
+ url += query_params_suffix
+
+ # perform request and return response
+ response = self.request(
+ method,
+ url,
+ headers=used_headers,
+ fields=fields,
+ body=body,
+ stream=stream,
+ timeout=timeout,
+ )
+ return response
+
+ def request(
+ self,
+ method: str,
+ url: str,
+ headers: typing.Optional[_collections.HTTPHeaderDict] = None,
+ fields: typing.Optional[typing.Tuple[rest.RequestField, ...]] = None,
+ body: typing.Union[str, bytes, None] = None,
+ stream: bool = False,
+ timeout: typing.Union[int, float, typing.Tuple, None] = None,
+ ) -> urllib3.HTTPResponse:
+ """Makes the HTTP request using RESTClient."""
+ if method == "get":
+ return self.rest_client.get(url,
+ stream=stream,
+ timeout=timeout,
+ headers=headers)
+ elif method == "head":
+ return self.rest_client.head(url,
+ stream=stream,
+ timeout=timeout,
+ headers=headers)
+ elif method == "options":
+ return self.rest_client.options(url,
+ headers=headers,
+ fields=fields,
+ stream=stream,
+ timeout=timeout,
+ body=body)
+ elif method == "post":
+ return self.rest_client.post(url,
+ headers=headers,
+ fields=fields,
+ stream=stream,
+ timeout=timeout,
+ body=body)
+ elif method == "put":
+ return self.rest_client.put(url,
+ headers=headers,
+ fields=fields,
+ stream=stream,
+ timeout=timeout,
+ body=body)
+ elif method == "patch":
+ return self.rest_client.patch(url,
+ headers=headers,
+ fields=fields,
+ stream=stream,
+ timeout=timeout,
+ body=body)
+ elif method == "delete":
+ return self.rest_client.delete(url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body)
+ else:
+ raise exceptions.ApiValueError(
+ "http method must be `GET`, `HEAD`, `OPTIONS`,"
+ " `POST`, `PATCH`, `PUT` or `DELETE`."
+ )
+
+ def update_params_for_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ security_requirement_object: typing.Optional[security_schemes.SecurityRequirementObject],
+ resource_path: str,
+ method: str,
+ body: typing.Union[str, bytes, None] = None,
+ query_params_suffix: typing.Optional[str] = None
+ ):
+ """Updates header and query params based on authentication setting.
+
+ :param headers: Header parameters dict to be updated.
+ :param security_requirement_object: the openapi security requirement object
+ :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().
+ """
+ return
+
+@dataclasses.dataclass
+class Api:
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+ Do not edit the class manually.
+ """
+ api_client: ApiClient = dataclasses.field(default_factory=lambda: ApiClient())
+
+ @staticmethod
+ def _get_used_path(
+ used_path: str,
+ path_parameters: typing.Tuple[typing.Type[PathParameter], ...] = (),
+ path_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None,
+ query_parameters: typing.Tuple[typing.Type[QueryParameter], ...] = (),
+ query_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None,
+ skip_validation: bool = False
+ ) -> typing.Tuple[str, str]:
+ used_path_params = {}
+ if path_params is not None:
+ for path_parameter in path_parameters:
+ parameter_data = path_params.get(path_parameter.name, schemas.unset)
+ if isinstance(parameter_data, schemas.Unset):
+ continue
+ assert not isinstance(parameter_data, (bytes, schemas.FileIO))
+ serialized_data = path_parameter.serialize(parameter_data, skip_validation=skip_validation)
+ used_path_params.update(serialized_data)
+
+ for k, v in used_path_params.items():
+ used_path = used_path.replace('{%s}' % k, v)
+
+ query_params_suffix = ""
+ if query_params is not None:
+ prefix_separator_iterator = None
+ for query_parameter in query_parameters:
+ parameter_data = query_params.get(query_parameter.name, schemas.unset)
+ if isinstance(parameter_data, schemas.Unset):
+ continue
+ if prefix_separator_iterator is None:
+ prefix_separator_iterator = query_parameter.get_prefix_separator_iterator()
+ assert not isinstance(parameter_data, (bytes, schemas.FileIO))
+ serialized_data = query_parameter.serialize(
+ parameter_data,
+ prefix_separator_iterator=prefix_separator_iterator,
+ skip_validation=skip_validation
+ )
+ for serialized_value in serialized_data.values():
+ query_params_suffix += serialized_value
+ return used_path, query_params_suffix
+
+ @staticmethod
+ def _get_headers(
+ header_parameters: typing.Tuple[typing.Type[HeaderParameter], ...] = (),
+ header_params: typing.Optional[typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = None,
+ accept_content_types: typing.Tuple[str, ...] = (),
+ skip_validation: bool = False
+ ) -> _collections.HTTPHeaderDict:
+ headers = _collections.HTTPHeaderDict()
+ if header_params is not None:
+ for parameter in header_parameters:
+ parameter_data = header_params.get(parameter.name, schemas.unset)
+ if isinstance(parameter_data, schemas.Unset):
+ continue
+ assert not isinstance(parameter_data, (bytes, schemas.FileIO))
+ serialized_data = parameter.serialize(parameter_data, skip_validation=skip_validation)
+ headers.extend(serialized_data)
+ if accept_content_types:
+ for accept_content_type in accept_content_types:
+ headers.add('Accept', accept_content_type)
+ return headers
+
+ @staticmethod
+ def _get_fields_and_body(
+ request_body: typing.Type[RequestBody],
+ body: typing.Union[schemas.INPUT_TYPES_ALL, schemas.Unset],
+ content_type: str,
+ headers: _collections.HTTPHeaderDict
+ ):
+ if request_body.required and body is schemas.unset:
+ raise exceptions.ApiValueError(
+ 'The required body parameter has an invalid value of: unset. Set a valid value instead')
+
+ if isinstance(body, schemas.Unset):
+ return None, None
+
+ serialized_fields = None
+ serialized_body = None
+ serialized_data = request_body.serialize(body, content_type)
+ headers.add('Content-Type', content_type)
+ if 'fields' in serialized_data:
+ serialized_fields = serialized_data['fields']
+ elif 'body' in serialized_data:
+ serialized_body = serialized_data['body']
+ return serialized_fields, serialized_body
+
+ @staticmethod
+ def _verify_response_status(response: api_response.ApiResponse):
+ if not 200 <= response.response.status <= 399:
+ raise exceptions.ApiException(
+ status=response.response.status,
+ reason=response.response.reason,
+ api_response=response
+ )
+
+
+class SerializedRequestBody(typing.TypedDict, total=False):
+ body: typing.Union[str, bytes]
+ fields: typing.Tuple[rest.RequestField, ...]
+
+
+class RequestBody(StyleFormSerializer, JSONDetector):
+ """
+ A request body parameter
+ content: content_type to MediaType schemas.Schema info
+ """
+ __json_encoder = JSONEncoder()
+ content: typing.Dict[str, typing.Type[MediaType]]
+ required: bool = False
+
+ @classmethod
+ def __serialize_json(
+ cls,
+ in_data: _JSON_TYPES
+ ) -> SerializedRequestBody:
+ in_data = cls.__json_encoder.default(in_data)
+ json_str = json.dumps(in_data, separators=(",", ":"), ensure_ascii=False).encode(
+ "utf-8"
+ )
+ return {'body': json_str}
+
+ @staticmethod
+ def __serialize_text_plain(in_data: typing.Union[int, float, str]) -> SerializedRequestBody:
+ return {'body': str(in_data)}
+
+ @classmethod
+ def __multipart_json_item(cls, key: str, value: _JSON_TYPES) -> rest.RequestField:
+ json_value = cls.__json_encoder.default(value)
+ request_field = rest.RequestField(name=key, data=json.dumps(json_value))
+ request_field.make_multipart(content_type='application/json')
+ return request_field
+
+ @classmethod
+ def __multipart_form_item(cls, key: str, value: typing.Union[_JSON_TYPES, bytes, schemas.FileIO]) -> rest.RequestField:
+ if isinstance(value, str):
+ request_field = rest.RequestField(name=key, data=str(value))
+ request_field.make_multipart(content_type='text/plain')
+ elif isinstance(value, bytes):
+ request_field = rest.RequestField(name=key, data=value)
+ request_field.make_multipart(content_type='application/octet-stream')
+ elif isinstance(value, schemas.FileIO):
+ # TODO use content.encoding to limit allowed content types if they are present
+ urllib3_request_field = rest.RequestField.from_tuples(key, (os.path.basename(str(value.name)), value.read()))
+ request_field = typing.cast(rest.RequestField, urllib3_request_field)
+ value.close()
+ else:
+ request_field = cls.__multipart_json_item(key=key, value=value)
+ return request_field
+
+ @classmethod
+ def __serialize_multipart_form_data(
+ cls, in_data: schemas.immutabledict[str, typing.Union[_JSON_TYPES, bytes, schemas.FileIO]]
+ ) -> SerializedRequestBody:
+ """
+ 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 rest.RequestField for each item with name=key
+ for item in value:
+ request_field = cls.__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 = cls.__multipart_json_item(key=key, value=value) # type: ignore
+ fields.append(request_field)
+ else:
+ request_field = cls.__multipart_form_item(key=key, value=value)
+ fields.append(request_field)
+
+ return {'fields': tuple(fields)}
+
+ @staticmethod
+ def __serialize_application_octet_stream(in_data: typing.Union[schemas.FileIO, bytes]) -> SerializedRequestBody:
+ if isinstance(in_data, bytes):
+ return {'body': in_data}
+ # schemas.FileIO type
+ used_in_data = in_data.read()
+ in_data.close()
+ return {'body': used_in_data}
+
+ @classmethod
+ def __serialize_application_x_www_form_data(
+ cls, in_data: schemas.immutabledict[str, _JSON_TYPES]
+ ) -> SerializedRequestBody:
+ """
+ POST submission of form data in body
+ """
+ cast_in_data = cls.__json_encoder.default(in_data)
+ value = cls._serialize_form(cast_in_data, name='', explode=True, percent_encode=True)
+ return {'body': value}
+
+ @classmethod
+ def serialize(
+ cls, in_data: schemas.INPUT_TYPES_ALL, 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 = cls.content[content_type]
+ assert media_type.schema is not None
+ schema = schemas.get_class(media_type.schema)
+ cast_in_data = schema.validate_base(in_data)
+ # TODO check for and use encoding if it exists
+ # and content_type is multipart or application/x-www-form-urlencoded
+ if cls._content_type_is_json(content_type):
+ if isinstance(cast_in_data, (schemas.FileIO, bytes)):
+ raise ValueError(f"Invalid input data type. Data must be int/float/str/bool/None/tuple/immutabledict and it was type {type(cast_in_data)}")
+ return cls.__serialize_json(cast_in_data)
+ elif content_type == 'text/plain':
+ if not isinstance(cast_in_data, (int, float, str)):
+ raise ValueError(f"Unable to serialize type {type(cast_in_data)} to text/plain")
+ return cls.__serialize_text_plain(cast_in_data)
+ elif content_type == 'multipart/form-data':
+ if not isinstance(cast_in_data, schemas.immutabledict):
+ raise ValueError(f"Unable to serialize {cast_in_data} to multipart/form-data because it is not a dict of data")
+ return cls.__serialize_multipart_form_data(cast_in_data)
+ elif content_type == 'application/x-www-form-urlencoded':
+ if not isinstance(cast_in_data, schemas.immutabledict):
+ raise ValueError(
+ f"Unable to serialize {cast_in_data} to application/x-www-form-urlencoded because it is not a dict of data")
+ return cls.__serialize_application_x_www_form_data(cast_in_data)
+ elif content_type == 'application/octet-stream':
+ if not isinstance(cast_in_data, (schemas.FileIO, bytes)):
+ raise ValueError(f"Invalid input data type. Data must be bytes or File for content_type={content_type}")
+ return cls.__serialize_application_octet_stream(cast_in_data)
+ raise NotImplementedError('Serialization has not yet been implemented for {}'.format(content_type))
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_response.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_response.py
new file mode 100644
index 00000000000..ec9907ededb
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/api_response.py
@@ -0,0 +1,28 @@
+# coding: utf-8
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import dataclasses
+import typing
+
+import urllib3
+
+from json_schema_api import schemas
+
+
+@dataclasses.dataclass
+class ApiResponse:
+ response: urllib3.HTTPResponse
+ body: typing.Union[schemas.Unset, schemas.OUTPUT_BASE_TYPES] = schemas.unset
+ headers: typing.Union[schemas.Unset, typing.Mapping[str, schemas.OUTPUT_BASE_TYPES]] = schemas.unset
+
+
+@dataclasses.dataclass
+class ApiResponseWithoutDeserialization(ApiResponse):
+ response: urllib3.HTTPResponse
+ body: schemas.Unset = schemas.unset
+ headers: schemas.Unset = schemas.unset
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/__init__.py
new file mode 100644
index 00000000000..7840f7726f6
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/__init__.py
@@ -0,0 +1,3 @@
+# do not import all endpoints into this module because that uses a lot of memory and stack frames
+# if you need the ability to import all endpoints then import them from
+# tags, paths, or path_to_api, or tag_to_api
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/path_to_api.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/path_to_api.py
new file mode 100644
index 00000000000..8c080c5304d
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/path_to_api.py
@@ -0,0 +1,17 @@
+import typing
+import typing_extensions
+
+from json_schema_api.apis.paths.some_path import SomePath
+
+PathToApi = typing.TypedDict(
+ 'PathToApi',
+ {
+ "/somePath": typing.Type[SomePath],
+ }
+)
+
+path_to_api = PathToApi(
+ {
+ "/somePath": SomePath,
+ }
+)
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/__init__.py
new file mode 100644
index 00000000000..298f4273cd3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/__init__.py
@@ -0,0 +1,3 @@
+# do not import all endpoints into this module because that uses a lot of memory and stack frames
+# if you need the ability to import all endpoints from this module, import them with
+# from json_schema_api.apis.path_to_api import path_to_api
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/some_path.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/some_path.py
new file mode 100644
index 00000000000..25597763231
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/paths/some_path.py
@@ -0,0 +1,13 @@
+# coding: utf-8
+
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from json_schema_api.paths.some_path.get.operation import ApiForGet
+
+
+class SomePath(
+ ApiForGet,
+):
+ pass
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tag_to_api.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tag_to_api.py
new file mode 100644
index 00000000000..d97b93a57b2
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tag_to_api.py
@@ -0,0 +1,17 @@
+import typing
+import typing_extensions
+
+from json_schema_api.apis.tags.default_api import DefaultApi
+
+TagToApi = typing.TypedDict(
+ 'TagToApi',
+ {
+ "default": typing.Type[DefaultApi],
+ }
+)
+
+tag_to_api = TagToApi(
+ {
+ "default": DefaultApi,
+ }
+)
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/__init__.py
new file mode 100644
index 00000000000..f4326db6655
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/__init__.py
@@ -0,0 +1,3 @@
+# do not import all endpoints into this module because that uses a lot of memory and stack frames
+# if you need the ability to import all endpoints from this module, import them with
+# from json_schema_api.apis.tag_to_api import tag_to_api
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/default_api.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/default_api.py
new file mode 100644
index 00000000000..a50d9e3a2c2
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/apis/tags/default_api.py
@@ -0,0 +1,18 @@
+# coding: utf-8
+
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from json_schema_api.paths.some_path.get.operation import GetSomePath
+
+
+class DefaultApi(
+ GetSomePath,
+):
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+ Do not edit the class manually.
+ """
+ pass
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/__init__.py
new file mode 100644
index 00000000000..217bb49a46f
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/__init__.py
@@ -0,0 +1,5 @@
+# we can not import model classes here because that would create a circular
+# 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 json_schema_api.components.schemas import ModelA, ModelB
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/any_type_contains_value.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/any_type_contains_value.py
new file mode 100644
index 00000000000..b44942a9707
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/any_type_contains_value.py
@@ -0,0 +1,75 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+from json_schema_api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+
+
+class ContainsEnums:
+
+ @schemas.classproperty
+ def POSITIVE_1(cls) -> typing.Literal[1]:
+ return Contains.validate(1)
+
+
+@dataclasses.dataclass(frozen=True)
+class Contains(
+ schemas.Schema
+):
+ types: typing.FrozenSet[typing.Type] = frozenset({
+ float,
+ int,
+ })
+ enum_value_to_name: typing.Mapping[typing.Union[int, float, str, schemas.Bool, None], str] = dataclasses.field(
+ default_factory=lambda: {
+ 1: "POSITIVE_1",
+ }
+ )
+ enums = ContainsEnums
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[1],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[1]: ...
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: int,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[1,]: ...
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[int, float],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Union[int, float]:
+ validated_arg = super().validate_base(
+ arg,
+ configuration=configuration,
+ )
+ return validated_arg
+
+
+@dataclasses.dataclass(frozen=True)
+class AnyTypeContainsValue(
+ schemas.AnyTypeSchema[schemas.immutabledict[str, schemas.OUTPUT_BASE_TYPES], typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]],
+):
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator.
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+ Do not edit the class manually.
+ """
+ # any type
+ contains: typing.Type[Contains] = dataclasses.field(default_factory=lambda: Contains) # type: ignore
+
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/array_contains_value.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/array_contains_value.py
new file mode 100644
index 00000000000..7cf4593938a
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schema/array_contains_value.py
@@ -0,0 +1,88 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+from json_schema_api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+
+
+class ContainsEnums:
+
+ @schemas.classproperty
+ def POSITIVE_1(cls) -> typing.Literal[1]:
+ return Contains.validate(1)
+
+
+@dataclasses.dataclass(frozen=True)
+class Contains(
+ schemas.Schema
+):
+ types: typing.FrozenSet[typing.Type] = frozenset({
+ float,
+ int,
+ })
+ enum_value_to_name: typing.Mapping[typing.Union[int, float, str, schemas.Bool, None], str] = dataclasses.field(
+ default_factory=lambda: {
+ 1: "POSITIVE_1",
+ }
+ )
+ enums = ContainsEnums
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[1],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[1]: ...
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: int,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[1,]: ...
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[int, float],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Union[int, float]:
+ validated_arg = super().validate_base(
+ arg,
+ configuration=configuration,
+ )
+ return validated_arg
+
+
+@dataclasses.dataclass(frozen=True)
+class ArrayContainsValue(
+ schemas.Schema[schemas.immutabledict, typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]]
+):
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator.
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+
+ Do not edit the class manually.
+ """
+ types: typing.FrozenSet[typing.Type] = frozenset({tuple})
+ contains: typing.Type[Contains] = dataclasses.field(default_factory=lambda: Contains) # type: ignore
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[
+ typing.List[schemas.INPUT_TYPES_ALL],
+ typing.Tuple[schemas.INPUT_TYPES_ALL, ...],
+ ],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]:
+ return super().validate_base(
+ arg,
+ configuration=configuration,
+ )
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schemas/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schemas/__init__.py
new file mode 100644
index 00000000000..f04ff1683c2
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/components/schemas/__init__.py
@@ -0,0 +1,15 @@
+# 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 json_schema_api.components.schema.pet import Pet
+# or import this package, but before doing it, use:
+# import sys
+# sys.setrecursionlimit(n)
+
+from json_schema_api.components.schema.any_type_contains_value import AnyTypeContainsValue
+from json_schema_api.components.schema.array_contains_value import ArrayContainsValue
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/api_configuration.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/api_configuration.py
new file mode 100644
index 00000000000..b15820bc4fb
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/api_configuration.py
@@ -0,0 +1,281 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import copy
+from http import client as http_client
+import logging
+import multiprocessing
+import sys
+import typing
+import typing_extensions
+
+import urllib3
+
+from json_schema_api import exceptions
+from json_schema_api.servers import server_0
+
+# the server to use at each openapi document json path
+ServerInfo = typing.TypedDict(
+ 'ServerInfo',
+ {
+ 'servers/0': server_0.Server0,
+ },
+ total=False
+)
+
+
+class ServerIndexInfoRequired(typing.TypedDict):
+ servers: typing.Literal[0]
+
+ServerIndexInfoOptional = typing.TypedDict(
+ 'ServerIndexInfoOptional',
+ {
+ },
+ total=False
+)
+
+
+class ServerIndexInfo(ServerIndexInfoRequired, ServerIndexInfoOptional):
+ """
+ the default server_index to use at each openapi document json path
+ the fallback value is stored in the 'servers' key
+ """
+
+
+class ApiConfiguration(object):
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator
+
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+ Do not edit the class manually.
+
+ :param server_info: the servers that can be used to make endpoint calls
+ :param server_index_info: index to servers configuration
+ """
+
+ def __init__(
+ self,
+ server_info: typing.Optional[ServerInfo] = None,
+ server_index_info: typing.Optional[ServerIndexInfo] = None,
+ ):
+ """Constructor
+ """
+ # Authentication Settings
+ self.security_scheme_info: typing.Dict[str, typing.Any] = {}
+ self.security_index_info = {'security': 0}
+ # Server Info
+ self.server_info: ServerInfo = server_info or {
+ 'servers/0': server_0.Server0(),
+ }
+ self.server_index_info: ServerIndexInfo = server_index_info or {'servers': 0}
+ self.logger = {}
+ """Logging Settings
+ """
+ self.logger["package_logger"] = logging.getLogger("json_schema_api")
+ self.logger["urllib3_logger"] = logging.getLogger("urllib3")
+ self.logger_format = '%(asctime)s %(levelname)s %(message)s'
+ """Log format
+ """
+ self.logger_stream_handler = None
+ """Log stream handler
+ """
+ self.logger_file_handler = None
+ """Log file handler
+ """
+ self.logger_file = None
+ """Debug file location
+ """
+ self.debug = False
+ """Debug switch
+ """
+
+ self.verify_ssl = True
+ """SSL/TLS verification
+ Set this to false to skip verifying SSL certificate when calling API
+ from https server.
+ """
+ self.ssl_ca_cert = None
+ """Set this to customize the certificate file to verify the peer.
+ """
+ self.cert_file = None
+ """client certificate file
+ """
+ self.key_file = None
+ """client key file
+ """
+ self.assert_hostname = None
+ """Set this to True/False to enable/disable SSL hostname verification.
+ """
+
+ 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
+ not the best value when you are making a lot of possibly parallel
+ requests to the same host, which is often the case here.
+ cpu_count * 5 is used as default value to increase performance.
+ """
+
+ self.proxy = None
+ """Proxy URL
+ """
+ self.proxy_headers = None
+ """Proxy headers
+ """
+ self.safe_chars_for_path_param = ''
+ """Safe chars for path_param
+ """
+ self.retries = None
+ """Adding retries to override urllib3 default value 3
+ """
+ # Enable client side validation
+ self.client_side_validation = True
+
+ # Options to pass down to the underlying urllib3 socket
+ self.socket_options = None
+
+ def __deepcopy__(self, memo):
+ cls = self.__class__
+ result = cls.__new__(cls)
+ memo[id(self)] = result
+ for k, v in self.__dict__.items():
+ if k not in ('logger', 'logger_file_handler'):
+ setattr(result, k, copy.deepcopy(v, memo))
+ # shallow copy of loggers
+ result.logger = copy.copy(self.logger)
+ # use setters to configure loggers
+ result.logger_file = self.logger_file
+ result.debug = self.debug
+ return result
+
+ @property
+ def logger_file(self):
+ """The logger file.
+
+ If the logger_file is None, then add stream handler and remove file
+ handler. Otherwise, add file handler and remove stream handler.
+
+ :param value: The logger_file path.
+ :type: str
+ """
+ return self.__logger_file
+
+ @logger_file.setter
+ def logger_file(self, value):
+ """The logger file.
+
+ If the logger_file is None, then add stream handler and remove file
+ handler. Otherwise, add file handler and remove stream handler.
+
+ :param value: The logger_file path.
+ :type: str
+ """
+ self.__logger_file = value
+ if self.__logger_file:
+ # If set logging file,
+ # then add file handler and remove stream handler.
+ self.logger_file_handler = logging.FileHandler(self.__logger_file)
+ self.logger_file_handler.setFormatter(self.logger_formatter)
+ for _, logger in self.logger.items():
+ logger.addHandler(self.logger_file_handler)
+
+ @property
+ def debug(self):
+ """Debug status
+
+ :param value: The debug status, True or False.
+ :type: bool
+ """
+ return self.__debug
+
+ @debug.setter
+ def debug(self, value):
+ """Debug status
+
+ :param value: The debug status, True or False.
+ :type: bool
+ """
+ self.__debug = value
+ if self.__debug:
+ # if debug status is True, turn on debug logging
+ for _, logger in self.logger.items():
+ logger.setLevel(logging.DEBUG)
+ # turn on http_client debug
+ http_client.HTTPConnection.debuglevel = 1
+ else:
+ # if debug status is False, turn off debug logging,
+ # setting log level to default `logging.WARNING`
+ for _, logger in self.logger.items():
+ logger.setLevel(logging.WARNING)
+ # turn off http_client debug
+ http_client.HTTPConnection.debuglevel = 0
+
+ @property
+ def logger_format(self):
+ """The logger format.
+
+ The logger_formatter will be updated when sets logger_format.
+
+ :param value: The format string.
+ :type: str
+ """
+ return self.__logger_format
+
+ @logger_format.setter
+ def logger_format(self, value):
+ """The logger format.
+
+ The logger_formatter will be updated when sets logger_format.
+
+ :param value: The format string.
+ :type: str
+ """
+ self.__logger_format = value
+ self.logger_formatter = logging.Formatter(self.__logger_format)
+
+ def to_debug_report(self):
+ """Gets the essential information for debugging.
+
+ :return: The report for debugging.
+ """
+ return "Python SDK Debug Report:\n"\
+ "OS: {env}\n"\
+ "Python Version: {pyversion}\n"\
+ "Version of the API: 1.0.0\n"\
+ "SDK Package Version: 1.0.0".\
+ format(env=sys.platform, pyversion=sys.version)
+
+ def get_server_url(
+ self,
+ key_prefix: typing.Literal[
+ "servers",
+ ],
+ index: typing.Optional[int],
+ ) -> str:
+ """Gets host URL based on the index
+ :param index: array index of the host settings
+ :return: URL based on host settings
+ """
+ if index:
+ used_index = index
+ else:
+ try:
+ used_index = self.server_index_info[key_prefix]
+ except KeyError:
+ # fallback and use the default index
+ used_index = self.server_index_info.get("servers", 0)
+ server_info_key = typing.cast(
+ typing.Literal[
+ "servers/0",
+ ],
+ f"{key_prefix}/{used_index}"
+ )
+ try:
+ server = self.server_info[server_info_key]
+ except KeyError as ex:
+ raise ex
+ return server.url
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/schema_configuration.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/schema_configuration.py
new file mode 100644
index 00000000000..b456e9997ac
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/configurations/schema_configuration.py
@@ -0,0 +1,95 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import typing
+
+from json_schema_api import exceptions
+
+
+PYTHON_KEYWORD_TO_JSON_SCHEMA_KEYWORD = {
+ 'additional_properties': 'additionalProperties',
+ 'all_of': 'allOf',
+ 'any_of': 'anyOf',
+ 'contains': 'contains',
+ 'discriminator': 'discriminator',
+ # default omitted because it has no validation impact
+ 'enum_value_to_name': 'enum',
+ 'exclusive_maximum': 'exclusiveMaximum',
+ 'exclusive_minimum': 'exclusiveMinimum',
+ 'format': 'format',
+ 'inclusive_maximum': 'maximum',
+ 'inclusive_minimum': 'minimum',
+ 'items': 'items',
+ 'max_items': 'maxItems',
+ 'max_length': 'maxLength',
+ 'max_properties': 'maxProperties',
+ 'min_items': 'minItems',
+ 'min_length': 'minLength',
+ 'min_properties': 'minProperties',
+ 'multiple_of': 'multipleOf',
+ 'not_': 'not',
+ 'one_of': 'oneOf',
+ 'pattern': 'pattern',
+ 'properties': 'properties',
+ 'required': 'required',
+ 'types': 'type',
+ 'unique_items': 'uniqueItems'
+}
+
+class SchemaConfiguration:
+ """NOTE: This class is auto generated by OpenAPI JSON Schema Generator
+
+ Ref: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+ Do not edit the class manually.
+
+ :param disabled_json_schema_keywords (set): Set of
+ JSON schema validation keywords to disable JSON schema structural validation
+ rules. The following keywords may be specified: multipleOf, maximum,
+ exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
+ maxItems, minItems.
+ By default, the validation is performed for data generated locally by the client
+ and data received from the server, independent of any validation performed by
+ the server side. If the input data does not satisfy the JSON schema validation
+ rules specified in the OpenAPI document, an exception is raised.
+ If disabled_json_schema_keywords is set, structural validation is
+ 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.
+ :param server_index: Index to servers configuration.
+ """
+
+ def __init__(
+ self,
+ disabled_json_schema_keywords = set(),
+ ):
+ """Constructor
+ """
+ self.disabled_json_schema_keywords = disabled_json_schema_keywords
+
+ @property
+ def disabled_json_schema_python_keywords(self) -> typing.Set[str]:
+ return self.__disabled_json_schema_python_keywords
+
+ @property
+ def disabled_json_schema_keywords(self) -> typing.Set[str]:
+ return self.__disabled_json_schema_keywords
+
+ @disabled_json_schema_keywords.setter
+ def disabled_json_schema_keywords(self, json_keywords: typing.Set[str]):
+ disabled_json_schema_keywords = set()
+ disabled_json_schema_python_keywords = set()
+ for k in json_keywords:
+ python_keywords = {key for key, val in PYTHON_KEYWORD_TO_JSON_SCHEMA_KEYWORD.items() if val == k}
+ if not python_keywords:
+ raise exceptions.ApiValueError(
+ "Invalid keyword: '{0}''".format(k))
+ disabled_json_schema_keywords.add(k)
+ disabled_json_schema_python_keywords.update(python_keywords)
+ self.__disabled_json_schema_keywords = disabled_json_schema_keywords
+ self.__disabled_json_schema_python_keywords = disabled_json_schema_python_keywords
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/exceptions.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/exceptions.py
new file mode 100644
index 00000000000..de07c63ae57
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/exceptions.py
@@ -0,0 +1,132 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import dataclasses
+import typing
+
+from json_schema_api import api_response
+
+
+class OpenApiException(Exception):
+ """The base exception class for all OpenAPIExceptions"""
+
+def render_path(path_to_item):
+ """Returns a string representation of a path"""
+ result = ""
+ for pth in path_to_item:
+ if isinstance(pth, int):
+ result += "[{0}]".format(pth)
+ else:
+ result += "['{0}']".format(pth)
+ return result
+
+
+class ApiTypeError(OpenApiException, TypeError):
+ def __init__(self, msg, path_to_item=None, valid_classes=None,
+ key_type=None):
+ """ Raises an exception for TypeErrors
+
+ Args:
+ msg (str): the exception message
+
+ Keyword Args:
+ path_to_item (list): a list of keys an indices to get to the
+ current_item
+ None if unset
+ valid_classes (tuple): the primitive classes that current item
+ should be an instance of
+ None if unset
+ 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
+ None if unset
+ """
+ self.path_to_item = path_to_item
+ self.valid_classes = valid_classes
+ self.key_type = key_type
+ full_msg = msg
+ if path_to_item:
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+ super(ApiTypeError, self).__init__(full_msg)
+
+
+class ApiValueError(OpenApiException, ValueError):
+ def __init__(self, msg, path_to_item=None):
+ """
+ Args:
+ msg (str): the exception message
+
+ Keyword Args:
+ path_to_item (list) the path to the exception in the
+ received_data dict. None if unset
+ """
+
+ self.path_to_item = path_to_item
+ full_msg = msg
+ if path_to_item:
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+ super(ApiValueError, self).__init__(full_msg)
+
+
+class ApiAttributeError(OpenApiException, AttributeError):
+ def __init__(self, msg, path_to_item=None):
+ """
+ Raised when an attribute reference or assignment fails.
+
+ Args:
+ msg (str): the exception message
+
+ Keyword Args:
+ path_to_item (None/list) the path to the exception in the
+ received_data dict
+ """
+ self.path_to_item = path_to_item
+ full_msg = msg
+ if path_to_item:
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+ super(ApiAttributeError, self).__init__(full_msg)
+
+
+class ApiKeyError(OpenApiException, KeyError):
+ def __init__(self, msg, path_to_item=None):
+ """
+ Args:
+ msg (str): the exception message
+
+ Keyword Args:
+ path_to_item (None/list) the path to the exception in the
+ received_data dict
+ """
+ self.path_to_item = path_to_item
+ full_msg = msg
+ if path_to_item:
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
+ super(ApiKeyError, self).__init__(full_msg)
+
+T = typing.TypeVar('T', bound=api_response.ApiResponse)
+
+
+@dataclasses.dataclass
+class ApiException(OpenApiException, typing.Generic[T]):
+ status: int
+ reason: typing.Optional[str] = None
+ api_response: typing.Optional[T] = None
+
+ def __str__(self):
+ """Custom error messages for exception"""
+ error_message = "({0})\n"\
+ "Reason: {1}\n".format(self.status, self.reason)
+ if self.api_response:
+ if self.api_response.response.headers:
+ error_message += "HTTP response headers: {0}\n".format(
+ self.api_response.response.headers)
+ if self.api_response.response.data:
+ error_message += "HTTP response body: {0}\n".format(self.api_response.response.data)
+
+ return error_message
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/__init__.py
new file mode 100644
index 00000000000..269cc181bd4
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/__init__.py
@@ -0,0 +1,3 @@
+# do not import all endpoints into this module because that uses a lot of memory and stack frames
+# if you need the ability to import all endpoints from this module, import them with
+# from json_schema_api.apis import path_to_api
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/__init__.py
new file mode 100644
index 00000000000..837999dee22
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/__init__.py
@@ -0,0 +1,5 @@
+# do not import all endpoints into this module because that uses a lot of memory and stack frames
+# if you need the ability to import all endpoints from this module, import them with
+# from json_schema_api.apis.paths.some_path import SomePath
+
+path = "/somePath"
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/operation.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/operation.py
new file mode 100644
index 00000000000..ced9b15ada3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/operation.py
@@ -0,0 +1,113 @@
+# coding: utf-8
+
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from json_schema_api import api_client
+from json_schema_api.shared_imports.operation_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+from .. import path
+from .responses import response_200
+
+
+__StatusCodeToResponse = typing.TypedDict(
+ '__StatusCodeToResponse',
+ {
+ '200': typing.Type[response_200.ResponseFor200],
+ }
+)
+_status_code_to_response: __StatusCodeToResponse = {
+ '200': response_200.ResponseFor200,
+}
+_non_error_status_codes = frozenset({
+ '200',
+})
+
+_all_accept_content_types = (
+ "application/json",
+)
+
+
+class BaseApi(api_client.Api):
+ @typing.overload
+ def _get_some_path(
+ self,
+ *,
+ skip_deserialization: typing.Literal[False] = False,
+ accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types,
+ server_index: typing.Optional[int] = None,
+ stream: bool = False,
+ timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None,
+ ) -> response_200.ApiResponse: ...
+
+ @typing.overload
+ def _get_some_path(
+ self,
+ *,
+ skip_deserialization: typing.Literal[True],
+ accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types,
+ server_index: typing.Optional[int] = None,
+ stream: bool = False,
+ timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None,
+ ) -> api_response.ApiResponseWithoutDeserialization: ...
+
+ def _get_some_path(
+ self,
+ *,
+ skip_deserialization: bool = False,
+ accept_content_types: typing.Tuple[str, ...] = _all_accept_content_types,
+ server_index: typing.Optional[int] = None,
+ stream: bool = False,
+ timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None,
+ ):
+ """
+ :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
+ """
+ used_path = path
+ headers = self._get_headers(accept_content_types=accept_content_types)
+ # TODO add cookie handling
+ host = self.api_client.configuration.get_server_url(
+ "servers", server_index
+ )
+
+ raw_response = self.api_client.call_api(
+ resource_path=used_path,
+ method='get',
+ host=host,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ )
+
+ if skip_deserialization:
+ skip_deser_response = api_response.ApiResponseWithoutDeserialization(response=raw_response)
+ self._verify_response_status(skip_deser_response)
+ return skip_deser_response
+
+ status = str(raw_response.status)
+ if status in _non_error_status_codes:
+ status_code = typing.cast(
+ typing.Literal[
+ '200',
+ ],
+ status
+ )
+ return _status_code_to_response[status_code].deserialize(
+ raw_response, self.api_client.schema_configuration)
+
+ response = api_response.ApiResponseWithoutDeserialization(response=raw_response)
+ self._verify_response_status(response)
+ return response
+
+
+class GetSomePath(BaseApi):
+ # this class is used by api classes that refer to endpoints with operationId.snakeCase fn names
+ get_some_path = BaseApi._get_some_path
+
+
+class ApiForGet(BaseApi):
+ # this class is used by api classes that refer to endpoints by path and http method names
+ get = BaseApi._get_some_path
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/__init__.py
new file mode 100644
index 00000000000..da45069e001
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/__init__.py
@@ -0,0 +1,29 @@
+# coding: utf-8
+
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from json_schema_api.shared_imports.response_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+from .content.application_json import schema as application_json_schema
+
+
+@dataclasses.dataclass
+class ApiResponse(api_response.ApiResponse):
+ response: urllib3.HTTPResponse
+ body: schemas.OUTPUT_BASE_TYPES
+ headers: schemas.Unset = schemas.unset
+
+
+class ResponseFor200(api_client.OpenApiResponse[ApiResponse]):
+ @classmethod
+ def get_response(cls, response, headers, body) -> ApiResponse:
+ return ApiResponse(response=response, body=body, headers=headers)
+
+
+ class ApplicationJsonMediaType(api_client.MediaType):
+ schema: typing_extensions.TypeAlias = application_json_schema.Schema
+ content = {
+ 'application/json': ApplicationJsonMediaType,
+ }
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/schema.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/schema.py
new file mode 100644
index 00000000000..c5c1b0ea3dd
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/paths/some_path/get/responses/response_200/content/application_json/schema.py
@@ -0,0 +1,13 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+from json_schema_api.shared_imports.schema_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+Schema: typing_extensions.TypeAlias = schemas.AnyTypeSchema
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/py.typed b/samples/client/3_1_0_json_schema/python/src/json_schema_api/py.typed
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/rest.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/rest.py
new file mode 100644
index 00000000000..99c8591a46a
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/rest.py
@@ -0,0 +1,270 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import logging
+import ssl
+from urllib.parse import urlencode
+import typing
+
+import certifi # type: ignore[import]
+import urllib3
+from urllib3 import fields
+from urllib3 import exceptions as urllib3_exceptions
+from urllib3._collections import HTTPHeaderDict
+
+from json_schema_api import exceptions
+
+
+logger = logging.getLogger(__name__)
+_TYPE_FIELD_VALUE = typing.Union[str, bytes]
+
+
+class RequestField(fields.RequestField):
+ def __init__(
+ self,
+ name: str,
+ data: _TYPE_FIELD_VALUE,
+ filename: typing.Optional[str] = None,
+ headers: typing.Optional[typing.Mapping[str, typing.Union[str, None]]] = None,
+ header_formatter: typing.Optional[typing.Callable[[str, _TYPE_FIELD_VALUE], str]] = None,
+ ):
+ super().__init__(name, data, filename, headers, header_formatter) # type: ignore
+
+ def __eq__(self, other):
+ if not isinstance(other, fields.RequestField):
+ return False
+ return self.__dict__ == other.__dict__
+
+
+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,
+ headers: typing.Optional[HTTPHeaderDict] = None,
+ fields: typing.Optional[typing.Tuple[RequestField, ...]] = None,
+ body: typing.Optional[typing.Union[str, bytes]] = None,
+ stream: bool = False,
+ timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None,
+ ) -> urllib3.HTTPResponse:
+ """Perform requests.
+
+ :param method: http request method
+ :param url: http request 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.
+ """
+ assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
+ 'PATCH', 'OPTIONS']
+
+ if fields and body:
+ raise exceptions.ApiValueError(
+ "body parameter cannot be used with fields parameter."
+ )
+
+ headers = headers or HTTPHeaderDict()
+
+ used_timeout: typing.Optional[urllib3.Timeout] = None
+ if timeout:
+ if isinstance(timeout, (int, float)):
+ used_timeout = urllib3.Timeout(total=timeout)
+ elif (isinstance(timeout, tuple) and
+ len(timeout) == 2):
+ used_timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1])
+
+ try:
+ # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
+ if method in {'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE'}:
+ if 'Content-Type' not in headers and body is None:
+ r = self.pool_manager.request(
+ method,
+ url,
+ preload_content=not stream,
+ timeout=used_timeout,
+ headers=headers
+ )
+ elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501
+ r = self.pool_manager.request(
+ method, url,
+ body=body,
+ encode_multipart=False,
+ preload_content=not stream,
+ timeout=used_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=used_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=used_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 exceptions.ApiException(status=0, reason=msg)
+ # For `GET`, `HEAD`
+ else:
+ r = self.pool_manager.request(method, url,
+ preload_content=not stream,
+ timeout=used_timeout,
+ headers=headers)
+ except urllib3_exceptions.SSLError as e:
+ msg = "{0}\n{1}".format(type(e).__name__, str(e))
+ raise exceptions.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, stream=False,
+ timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("GET", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ fields=fields)
+
+ def head(self, url, headers=None, stream=False,
+ timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("HEAD", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ fields=fields)
+
+ def options(self, url, headers=None,
+ body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("OPTIONS", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body, fields=fields)
+
+ def delete(self, url, headers=None, body=None,
+ stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("DELETE", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body, fields=fields)
+
+ def post(self, url, headers=None,
+ body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("POST", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body, fields=fields)
+
+ def put(self, url, headers=None,
+ body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("PUT", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body, fields=fields)
+
+ def patch(self, url, headers=None,
+ body=None, stream=False, timeout=None, fields=None) -> urllib3.HTTPResponse:
+ return self.request("PATCH", url,
+ headers=headers,
+ stream=stream,
+ timeout=timeout,
+ body=body, fields=fields)
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/__init__.py
new file mode 100644
index 00000000000..7f86329469c
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/__init__.py
@@ -0,0 +1,148 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import typing
+
+import typing_extensions
+
+from .schema import (
+ get_class,
+ none_type_,
+ classproperty,
+ Bool,
+ FileIO,
+ Schema,
+ SingletonMeta,
+ AnyTypeSchema,
+ UnsetAnyTypeSchema,
+ INPUT_TYPES_ALL
+)
+
+from .schemas import (
+ ListSchema,
+ NoneSchema,
+ NumberSchema,
+ IntSchema,
+ Int32Schema,
+ Int64Schema,
+ Float32Schema,
+ Float64Schema,
+ StrSchema,
+ UUIDSchema,
+ DateSchema,
+ DateTimeSchema,
+ DecimalSchema,
+ BytesSchema,
+ FileSchema,
+ BinarySchema,
+ BoolSchema,
+ NotAnyTypeSchema,
+ OUTPUT_BASE_TYPES,
+ DictSchema
+)
+from .validation import (
+ PatternInfo,
+ ValidationMetadata,
+ immutabledict
+)
+from .format import (
+ as_date,
+ as_datetime,
+ as_decimal,
+ as_uuid
+)
+
+def typed_dict_to_instance(t_dict: typing_extensions._TypedDictMeta) -> typing.Mapping: # type: ignore
+ res = {}
+ for key, val in t_dict.__annotations__.items():
+ if isinstance(val, typing._GenericAlias): # type: ignore
+ # typing.Type[W] -> W
+ val_cls = typing.get_args(val)[0]
+ res[key] = val_cls
+ return res
+
+X = typing.TypeVar('X', bound=typing.Tuple)
+
+def tuple_to_instance(tup: typing.Type[X]) -> X:
+ res = []
+ for arg in typing.get_args(tup):
+ if isinstance(arg, typing._GenericAlias): # type: ignore
+ # typing.Type[Schema] -> Schema
+ arg_cls = typing.get_args(arg)[0]
+ res.append(arg_cls)
+ return tuple(res) # type: ignore
+
+
+class Unset:
+ """
+ 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 = Unset()
+
+def key_unknown_error_msg(key: str) -> str:
+ return (f"Invalid key. The key {key} is not a known key in this payload. "
+ "If this key is an additional property, use get_additional_property_"
+ )
+
+def raise_if_key_known(
+ key: str,
+ required_keys: typing.FrozenSet[str],
+ optional_keys: typing.FrozenSet[str]
+):
+ if key in required_keys or key in optional_keys:
+ raise ValueError(f"The key {key} is a known property, use get_property to access its value")
+
+__all__ = [
+ 'get_class',
+ 'none_type_',
+ 'classproperty',
+ 'Bool',
+ 'FileIO',
+ 'Schema',
+ 'SingletonMeta',
+ 'AnyTypeSchema',
+ 'UnsetAnyTypeSchema',
+ 'INPUT_TYPES_ALL',
+ 'ListSchema',
+ 'NoneSchema',
+ 'NumberSchema',
+ 'IntSchema',
+ 'Int32Schema',
+ 'Int64Schema',
+ 'Float32Schema',
+ 'Float64Schema',
+ 'StrSchema',
+ 'UUIDSchema',
+ 'DateSchema',
+ 'DateTimeSchema',
+ 'DecimalSchema',
+ 'BytesSchema',
+ 'FileSchema',
+ 'BinarySchema',
+ 'BoolSchema',
+ 'NotAnyTypeSchema',
+ 'OUTPUT_BASE_TYPES',
+ 'DictSchema',
+ 'PatternInfo',
+ 'ValidationMetadata',
+ 'immutabledict',
+ 'as_date',
+ 'as_datetime',
+ 'as_decimal',
+ 'as_uuid',
+ 'typed_dict_to_instance',
+ 'tuple_to_instance',
+ 'Unset',
+ 'unset',
+ 'key_unknown_error_msg',
+ 'raise_if_key_known'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/format.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/format.py
new file mode 100644
index 00000000000..bb614903b82
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/format.py
@@ -0,0 +1,115 @@
+import datetime
+import decimal
+import functools
+import typing
+import uuid
+
+from dateutil import parser, tz
+
+
+class CustomIsoparser(parser.isoparser):
+ def __init__(self, sep: typing.Optional[str] = None):
+ """
+ :param sep:
+ A single character that separates date and time portions. If
+ ``None``, the parser will accept any single character.
+ For strict ISO-8601 adherence, pass ``'T'``.
+ """
+ if sep is not None:
+ if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
+ raise ValueError('Separator must be a single, non-numeric ' +
+ 'ASCII character')
+
+ used_sep = sep.encode('ascii')
+ else:
+ used_sep = None
+
+ self._sep = used_sep
+
+ @staticmethod
+ def __get_ascii_bytes(str_in: str) -> bytes:
+ # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
+ # ASCII is the same in UTF-8
+ try:
+ return str_in.encode('ascii')
+ except UnicodeEncodeError as e:
+ msg = 'ISO-8601 strings should contain only ASCII characters'
+ raise ValueError(msg) from e
+
+ def __parse_isodate(self, dt_str: str) -> typing.Tuple[typing.Tuple[int, int, int], int]:
+ dt_str_ascii = self.__get_ascii_bytes(dt_str)
+ values = self._parse_isodate(dt_str_ascii) # type: ignore
+ values = typing.cast(typing.Tuple[typing.List[int], int], values)
+ components = typing.cast( typing.Tuple[int, int, int], tuple(values[0]))
+ pos = values[1]
+ return components, pos
+
+ def __parse_isotime(self, dt_str: str) -> typing.Tuple[int, int, int, int, typing.Optional[typing.Union[tz.tzutc, tz.tzoffset]]]:
+ dt_str_ascii = self.__get_ascii_bytes(dt_str)
+ values = self._parse_isotime(dt_str_ascii) # type: ignore
+ components: typing.Tuple[int, int, int, int, typing.Optional[typing.Union[tz.tzutc, tz.tzoffset]]] = tuple(values) # type: ignore
+ return components
+
+ def parse_isodatetime(self, dt_str: str) -> datetime.datetime:
+ date_components, pos = self.__parse_isodate(dt_str)
+ if len(dt_str) <= pos:
+ # len(components) <= 3
+ raise ValueError('Value is not a datetime')
+ if self._sep is None or dt_str[pos:pos + 1] == self._sep:
+ hour, minute, second, microsecond, tzinfo = self.__parse_isotime(dt_str[pos + 1:])
+ if hour == 24:
+ hour = 0
+ components = (*date_components, hour, minute, second, microsecond, tzinfo)
+ return datetime.datetime(*components) + datetime.timedelta(days=1)
+ else:
+ components = (*date_components, hour, minute, second, microsecond, tzinfo)
+ else:
+ raise ValueError('String contains unknown ISO components')
+
+ return datetime.datetime(*components)
+
+ def parse_isodate_str(self, datestr: str) -> datetime.date:
+ 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 datetime.date(*components)
+
+DEFAULT_ISOPARSER = CustomIsoparser()
+
+@functools.lru_cache()
+def as_date(arg: str) -> datetime.date:
+ """
+ type = "string"
+ format = "date"
+ """
+ return DEFAULT_ISOPARSER.parse_isodate_str(arg)
+
+@functools.lru_cache()
+def as_datetime(arg: str) -> datetime.datetime:
+ """
+ type = "string"
+ format = "date-time"
+ """
+ return DEFAULT_ISOPARSER.parse_isodatetime(arg)
+
+@functools.lru_cache()
+def as_decimal(arg: str) -> decimal.Decimal:
+ """
+ Applicable when storing decimals that are sent over the wire as strings
+ type = "string"
+ format = "number"
+ """
+ return decimal.Decimal(arg)
+
+@functools.lru_cache()
+def as_uuid(arg: str) -> uuid.UUID:
+ """
+ type = "string"
+ format = "uuid"
+ """
+ return uuid.UUID(arg)
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/original_immutabledict.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/original_immutabledict.py
new file mode 100644
index 00000000000..3897e140a4a
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/original_immutabledict.py
@@ -0,0 +1,97 @@
+"""
+MIT License
+
+Copyright (c) 2020 Corentin Garcia
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+from __future__ import annotations
+import typing
+import typing_extensions
+
+_K = typing.TypeVar("_K")
+_V = typing.TypeVar("_V", covariant=True)
+
+
+class immutabledict(typing.Mapping[_K, _V]):
+ """
+ An immutable wrapper around dictionaries that implements
+ the complete :py:class:`collections.Mapping` interface.
+ It can be used as a drop-in replacement for dictionaries
+ where immutability is desired.
+
+ Note: custom version of this class made to remove __init__
+ """
+
+ dict_cls: typing.Type[typing.Dict[typing.Any, typing.Any]] = dict
+ _dict: typing.Dict[_K, _V]
+ _hash: typing.Optional[int]
+
+ @classmethod
+ def fromkeys(
+ cls, seq: typing.Iterable[_K], value: typing.Optional[_V] = None
+ ) -> "immutabledict[_K, _V]":
+ return cls(dict.fromkeys(seq, value))
+
+ def __new__(cls, *args: typing.Any) -> typing_extensions.Self:
+ inst = super().__new__(cls)
+ setattr(inst, '_dict', cls.dict_cls(*args))
+ setattr(inst, '_hash', None)
+ return inst
+
+ def __getitem__(self, key: _K) -> _V:
+ return self._dict[key]
+
+ def __contains__(self, key: object) -> bool:
+ return key in self._dict
+
+ def __iter__(self) -> typing.Iterator[_K]:
+ return iter(self._dict)
+
+ def __len__(self) -> int:
+ return len(self._dict)
+
+ def __repr__(self) -> str:
+ return "%s(%r)" % (self.__class__.__name__, self._dict)
+
+ def __hash__(self) -> int:
+ if self._hash is None:
+ h = 0
+ for key, value in self.items():
+ h ^= hash((key, value))
+ self._hash = h
+
+ return self._hash
+
+ def __or__(self, other: typing.Any) -> immutabledict[_K, _V]:
+ if not isinstance(other, (dict, self.__class__)):
+ return NotImplemented
+ new = dict(self)
+ new.update(other)
+ return self.__class__(new)
+
+ def __ror__(self, other: typing.Any) -> typing.Dict[typing.Any, typing.Any]:
+ if not isinstance(other, (dict, self.__class__)):
+ return NotImplemented
+ new = dict(other)
+ new.update(self)
+ return new
+
+ def __ior__(self, other: typing.Any) -> immutabledict[_K, _V]:
+ raise TypeError(f"'{self.__class__.__name__}' object is not mutable")
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schema.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schema.py
new file mode 100644
index 00000000000..4e4473f2c56
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schema.py
@@ -0,0 +1,703 @@
+from __future__ import annotations
+import datetime
+import dataclasses
+import io
+import types
+import typing
+import uuid
+
+import functools
+import typing_extensions
+
+from json_schema_api import exceptions
+from json_schema_api.configurations import schema_configuration
+
+from . import validation
+
+none_type_ = type(None)
+T = typing.TypeVar('T', bound=typing.Mapping)
+U = typing.TypeVar('U', bound=typing.Sequence)
+W = typing.TypeVar('W')
+
+
+class SchemaTyped:
+ additional_properties: typing.Type[Schema]
+ all_of: typing.Tuple[typing.Type[Schema], ...]
+ any_of: typing.Tuple[typing.Type[Schema], ...]
+ discriminator: typing.Mapping[str, typing.Mapping[str, typing.Type[Schema]]]
+ default: typing.Union[str, int, float, bool, None]
+ enum_value_to_name: typing.Mapping[typing.Union[int, float, str, Bool, None], str]
+ exclusive_maximum: typing.Union[int, float]
+ exclusive_minimum: typing.Union[int, float]
+ format: str
+ inclusive_maximum: typing.Union[int, float]
+ inclusive_minimum: typing.Union[int, float]
+ items: typing.Type[Schema]
+ max_items: int
+ max_length: int
+ max_properties: int
+ min_items: int
+ min_length: int
+ min_properties: int
+ multiple_of: typing.Union[int, float]
+ not_: typing.Type[Schema]
+ one_of: typing.Tuple[typing.Type[Schema], ...]
+ pattern: validation.PatternInfo
+ properties: typing.Mapping[str, typing.Type[Schema]]
+ required: typing.FrozenSet[str]
+ types: typing.FrozenSet[typing.Type]
+ unique_items: bool
+
+
+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)):
+ if arg.closed:
+ raise exceptions.ApiValueError('Invalid file state; file is closed and must be open')
+ arg.close()
+ inst = super(FileIO, cls).__new__(cls, arg.name) # type: ignore
+ super(FileIO, inst).__init__(arg.name)
+ return inst
+ raise exceptions.ApiValueError('FileIO must be passed arg which contains the open file')
+
+ def __init__(self, arg: typing.Union[io.FileIO, io.BufferedReader]):
+ """
+ Needed for instantiation when passing in arguments of the above type
+ """
+ pass
+
+
+class classproperty(typing.Generic[W]):
+ def __init__(self, method: typing.Callable[..., W]):
+ self.__method = method
+ functools.update_wrapper(self, method) # type: ignore
+
+ def __get__(self, obj, cls=None) -> W:
+ if cls is None:
+ cls = type(obj)
+ return self.__method(cls)
+
+
+class Bool:
+ _instances: typing.Dict[typing.Tuple[type, bool], Bool] = {}
+ """
+ This class is needed to replace bool during validation processing
+ json schema requires that 0 != False and 1 != True
+ python implementation defines 0 == False and 1 == True
+ To meet the json schema requirements, all bool instances are replaced with Bool singletons
+ during validation only, and then bool values are returned from validation
+ """
+
+ def __new__(cls, arg_: bool, **kwargs):
+ """
+ Method that implements singleton
+ cls base classes: BoolClass, NoneClass, str, decimal.Decimal
+ The 3rd key is used in the tuple below for a corner case where an enum contains integer 1
+ However 1.0 can also be ingested into that enum schema because 1.0 == 1 and
+ Decimal('1.0') == Decimal('1')
+ But if we omitted the 3rd value in the key, then Decimal('1.0') would be stored as Decimal('1')
+ and json serializing that instance would be '1' rather than the expected '1.0'
+ Adding the 3rd value, the str of arg_ ensures that 1.0 -> Decimal('1.0') which is serialized as 1.0
+ """
+ key = (cls, arg_)
+ if key not in cls._instances:
+ inst = super().__new__(cls)
+ cls._instances[key] = inst
+ return cls._instances[key]
+
+ def __repr__(self):
+ if bool(self):
+ return f''
+ return f''
+
+ @classproperty
+ def TRUE(cls):
+ return cls(True) # type: ignore
+
+ @classproperty
+ def FALSE(cls):
+ return cls(False) # type: ignore
+
+ @functools.lru_cache()
+ def __bool__(self) -> bool:
+ for key, instance in self._instances.items():
+ if self is instance:
+ return bool(key[1])
+ raise ValueError('Unable to find the boolean value of this instance')
+
+
+def cast_to_allowed_types(
+ arg: typing.Union[
+ dict,
+ validation.immutabledict,
+ list,
+ tuple,
+ float,
+ int,
+ str,
+ datetime.date,
+ datetime.datetime,
+ uuid.UUID,
+ bool,
+ None,
+ bytes,
+ io.FileIO,
+ io.BufferedReader,
+ ],
+ from_server: bool,
+ validated_path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Set[typing.Union[str, int, float, bool, None, validation.immutabledict, tuple]]],
+ path_to_item: typing.Tuple[typing.Union[str, int], ...],
+ path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], type]
+) -> typing.Union[
+ validation.immutabledict,
+ tuple,
+ float,
+ int,
+ str,
+ bytes,
+ Bool,
+ None,
+ FileIO
+]:
+ """
+ Casts the input payload arg into the allowed types
+ The input validated_path_to_schemas is mutated by running this function
+
+ When from_server is False then
+ - date/datetime is cast to str
+ - int/float is cast to Decimal
+
+ If a Schema instance is passed in it is converted back to a primitive instance because
+ One may need to validate that data to the original Schema class AND additional different classes
+ those additional classes will need to be added to the new manufactured class for that payload
+ If the code didn't do this and kept the payload as a Schema instance it would fail to validate to other
+ Schema classes and the code wouldn't be able to mfg a new class that includes all valid schemas
+ TODO: store the validated schema classes in validation_metadata
+
+ Args:
+ arg: the payload
+ from_server: whether this payload came from the server or not
+ validated_path_to_schemas: a dict that stores the validated classes at any path location in the payload
+ """
+ type_error = exceptions.ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}")
+ if isinstance(arg, str):
+ path_to_type[path_to_item] = str
+ return str(arg)
+ elif isinstance(arg, (dict, validation.immutabledict)):
+ path_to_type[path_to_item] = validation.immutabledict
+ return validation.immutabledict(
+ {
+ key: cast_to_allowed_types(
+ val,
+ from_server,
+ validated_path_to_schemas,
+ path_to_item + (key,),
+ path_to_type,
+ )
+ for key, val in arg.items()
+ }
+ )
+ elif isinstance(arg, bool):
+ """
+ this check must come before isinstance(arg, (int, float))
+ because isinstance(True, int) is True
+ """
+ path_to_type[path_to_item] = Bool
+ if arg:
+ return Bool.TRUE
+ return Bool.FALSE
+ elif isinstance(arg, int):
+ path_to_type[path_to_item] = int
+ return arg
+ elif isinstance(arg, float):
+ path_to_type[path_to_item] = float
+ return arg
+ elif isinstance(arg, (tuple, list)):
+ path_to_type[path_to_item] = tuple
+ return tuple(
+ [
+ cast_to_allowed_types(
+ item,
+ from_server,
+ validated_path_to_schemas,
+ path_to_item + (i,),
+ path_to_type,
+ )
+ for i, item in enumerate(arg)
+ ]
+ )
+ elif arg is None:
+ path_to_type[path_to_item] = type(None)
+ return None
+ elif isinstance(arg, (datetime.date, datetime.datetime)):
+ path_to_type[path_to_item] = str
+ if not from_server:
+ return arg.isoformat()
+ raise type_error
+ elif isinstance(arg, uuid.UUID):
+ path_to_type[path_to_item] = str
+ if not from_server:
+ return str(arg)
+ raise type_error
+ elif isinstance(arg, bytes):
+ path_to_type[path_to_item] = bytes
+ return bytes(arg)
+ elif isinstance(arg, (io.FileIO, io.BufferedReader)):
+ path_to_type[path_to_item] = FileIO
+ return FileIO(arg)
+ raise exceptions.ApiTypeError('Invalid type passed in got input={} type={}'.format(arg, type(arg)))
+
+
+class SingletonMeta(type):
+ """
+ A singleton class for schemas
+ Schemas are frozen classes that are never instantiated with init args
+ All args come from defaults
+ """
+ _instances: typing.Dict[type, typing.Any] = {}
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ cls._instances[cls] = super().__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+class Schema(typing.Generic[T, U], validation.SchemaValidator, metaclass=SingletonMeta):
+
+ @classmethod
+ def __get_path_to_schemas(
+ cls,
+ arg,
+ validation_metadata: validation.ValidationMetadata,
+ path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type]
+ ) -> typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]]:
+ """
+ Run all validations in the json schema and return a dict of
+ json schema to tuple of validated schemas
+ """
+ _path_to_schemas: validation.PathToSchemasType = {}
+ if validation_metadata.validation_ran_earlier(cls):
+ validation.add_deeper_validated_schemas(validation_metadata, _path_to_schemas)
+ else:
+ other_path_to_schemas = cls._validate(arg, validation_metadata=validation_metadata)
+ validation.update(_path_to_schemas, other_path_to_schemas)
+ # loop through it make a new class for each entry
+ # do not modify the returned result because it is cached and we would be modifying the cached value
+ path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]] = {}
+ for path, schema_classes in _path_to_schemas.items():
+ schema = typing.cast(typing.Type[Schema], tuple(schema_classes)[-1])
+ path_to_schemas[path] = schema
+ """
+ For locations that validation did not check
+ the code still needs to store type + schema information for instantiation
+ All of those schemas will be UnsetAnyTypeSchema
+ """
+ missing_paths = path_to_type.keys() - path_to_schemas.keys()
+ for missing_path in missing_paths:
+ path_to_schemas[missing_path] = UnsetAnyTypeSchema
+
+ return path_to_schemas
+
+ @staticmethod
+ def __get_items(
+ arg: tuple,
+ path_to_item: typing.Tuple[typing.Union[str, int], ...],
+ path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]]
+ ):
+ '''
+ Schema __get_items
+ '''
+ cast_items = []
+
+ for i, value in enumerate(arg):
+ item_path_to_item = path_to_item + (i,)
+ item_cls = path_to_schemas[item_path_to_item]
+ new_value = item_cls._get_new_instance_without_conversion(
+ value,
+ item_path_to_item,
+ path_to_schemas
+ )
+ cast_items.append(new_value)
+
+ return tuple(cast_items)
+
+ @staticmethod
+ def __get_properties(
+ arg: validation.immutabledict[str, typing.Any],
+ path_to_item: typing.Tuple[typing.Union[str, int], ...],
+ path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]]
+ ):
+ """
+ Schema __get_properties, this is how properties are set
+ These values already passed validation
+ """
+ dict_items = {}
+
+ for property_name_js, value in arg.items():
+ property_path_to_item = path_to_item + (property_name_js,)
+ property_cls = path_to_schemas[property_path_to_item]
+ new_value = property_cls._get_new_instance_without_conversion(
+ value,
+ property_path_to_item,
+ path_to_schemas
+ )
+ dict_items[property_name_js] = new_value
+
+ return validation.immutabledict(dict_items)
+
+ @classmethod
+ def _get_new_instance_without_conversion(
+ cls,
+ arg: typing.Union[int, float, None, Bool, str, validation.immutabledict, tuple, FileIO, bytes],
+ path_to_item: typing.Tuple[typing.Union[str, int], ...],
+ path_to_schemas: typing.Dict[typing.Tuple[typing.Union[str, int], ...], typing.Type[Schema]]
+ ):
+ # We have a Dynamic class and we are making an instance of it
+ if isinstance(arg, validation.immutabledict):
+ used_arg = cls.__get_properties(arg, path_to_item, path_to_schemas)
+ elif isinstance(arg, tuple):
+ used_arg = cls.__get_items(arg, path_to_item, path_to_schemas)
+ elif isinstance(arg, Bool):
+ return bool(arg)
+ else:
+ """
+ str, int, float, FileIO, bytes
+ FileIO = openapi binary type and the user inputs a file
+ bytes = openapi binary type and the user inputs bytes
+ """
+ return arg
+ arg_type = type(arg)
+ type_to_output_cls = cls.__get_type_to_output_cls()
+ if type_to_output_cls is None:
+ return used_arg
+ if arg_type not in type_to_output_cls:
+ return used_arg
+ output_cls = type_to_output_cls[arg_type]
+ if arg_type is tuple:
+ inst = super(output_cls, output_cls).__new__(output_cls, used_arg) # type: ignore
+ inst = typing.cast(U, inst)
+ return inst
+ assert issubclass(output_cls, validation.immutabledict)
+ inst = super(output_cls, output_cls).__new__(output_cls, used_arg) # type: ignore
+ inst = typing.cast(T, inst)
+ return inst
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: None,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> None: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Literal[True],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[True]: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Literal[False],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[False]: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: bool,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> bool: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: int,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> int: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: float,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> float: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Union[datetime.date, datetime.datetime, uuid.UUID],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Sequence[INPUT_TYPES_ALL], # also covers str, tuple, list, bytes
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> U: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: U,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> U: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Mapping[str, object], # object needed as value type for typeddict inputs
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> T: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Union[
+ typing.Mapping[str, INPUT_TYPES_ALL],
+ T
+ ],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> T: ...
+
+ @typing.overload
+ @classmethod
+ def validate_base(
+ cls,
+ arg: typing.Union[io.FileIO, io.BufferedReader],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> FileIO: ...
+
+ @classmethod
+ def validate_base(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None,
+ ):
+ """
+ Schema validate_base
+
+ Args:
+ arg (int/float/str/list/tuple/dict/validation.immutabledict/bool/None): the value
+ configuration: contains the schema_configuration.SchemaConfiguration that enables json schema validation keywords
+ like minItems, minLength etc
+ """
+ if isinstance(arg, (tuple, validation.immutabledict)):
+ type_to_output_cls = cls.__get_type_to_output_cls()
+ if type_to_output_cls is not None:
+ for output_cls in type_to_output_cls.values():
+ if isinstance(arg, output_cls):
+ # U + T use case, don't run validations twice
+ return arg
+
+ from_server = False
+ validated_path_to_schemas: typing.Dict[
+ typing.Tuple[typing.Union[str, int], ...],
+ typing.Set[typing.Union[str, int, float, bool, None, validation.immutabledict, tuple]]
+ ] = {}
+ path_to_type: typing.Dict[typing.Tuple[typing.Union[str, int], ...], type] = {}
+ cast_arg = cast_to_allowed_types(
+ arg, from_server, validated_path_to_schemas, ('args[0]',), path_to_type)
+ validation_metadata = validation.ValidationMetadata(
+ path_to_item=('args[0]',),
+ configuration=configuration or schema_configuration.SchemaConfiguration(),
+ validated_path_to_schemas=validation.immutabledict(validated_path_to_schemas)
+ )
+ path_to_schemas = cls.__get_path_to_schemas(cast_arg, validation_metadata, path_to_type)
+ return cls._get_new_instance_without_conversion(
+ cast_arg,
+ validation_metadata.path_to_item,
+ path_to_schemas,
+ )
+
+ @classmethod
+ def __get_type_to_output_cls(cls) -> typing.Optional[typing.Mapping[type, type]]:
+ type_to_output_cls = getattr(cls(), 'type_to_output_cls', None)
+ type_to_output_cls = typing.cast(typing.Optional[typing.Mapping[type, type]], type_to_output_cls)
+ return type_to_output_cls
+
+
+def get_class(
+ item_cls: typing.Union[types.FunctionType, staticmethod, typing.Type[Schema]],
+ local_namespace: typing.Optional[dict] = None
+) -> typing.Type[Schema]:
+ if isinstance(item_cls, typing._GenericAlias): # type: ignore
+ # petstore_api.schemas.StrSchema[~U] -> petstore_api.schemas.StrSchema
+ origin_cls = typing.get_origin(item_cls)
+ if origin_cls is None:
+ raise ValueError('origin class must not be None')
+ return origin_cls
+ elif isinstance(item_cls, types.FunctionType):
+ # referenced schema
+ return item_cls()
+ elif isinstance(item_cls, staticmethod):
+ # referenced schema
+ return item_cls.__func__()
+ elif isinstance(item_cls, type):
+ return item_cls
+ elif isinstance(item_cls, typing.ForwardRef):
+ return item_cls._evaluate(None, local_namespace)
+ raise ValueError('invalid class value passed in')
+
+
+@dataclasses.dataclass(frozen=True)
+class AnyTypeSchema(Schema[T, U]):
+ # Python representation of a schema defined as true or {}
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: None,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> None: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[True],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[True]: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[False],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[False]: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: bool,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> bool: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: int,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> int: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: float,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> float: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[datetime.date, datetime.datetime, uuid.UUID],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Sequence[INPUT_TYPES_ALL], # also covers str, tuple, list, bytes
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> U: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: U,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> U: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[
+ typing.Mapping[str, INPUT_TYPES_ALL],
+ T
+ ],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> T: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[io.FileIO, io.BufferedReader],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> FileIO: ...
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None,
+ ):
+ return cls.validate_base(
+ arg,
+ configuration=configuration
+ )
+
+class UnsetAnyTypeSchema(AnyTypeSchema[T, U]):
+ # Used when additionalProperties/items was not explicitly defined and a defining schema is needed
+ pass
+
+INPUT_TYPES_NOT_STR_BYTES_FILE = typing.Union[
+ dict,
+ validation.immutabledict,
+ list,
+ tuple,
+ float,
+ int,
+ datetime.date,
+ datetime.datetime,
+ uuid.UUID,
+ bool,
+ None,
+]
+
+INPUT_TYPES_ALL = typing.Union[
+ dict,
+ validation.immutabledict,
+ typing.Mapping[str, object], # for TypedDict
+ list,
+ tuple,
+ float,
+ int,
+ str,
+ datetime.date,
+ datetime.datetime,
+ uuid.UUID,
+ bool,
+ None,
+ bytes,
+ io.FileIO,
+ io.BufferedReader,
+ FileIO
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schemas.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schemas.py
new file mode 100644
index 00000000000..c9eeefa6e83
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/schemas.py
@@ -0,0 +1,375 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+import datetime
+import dataclasses
+import io
+import typing
+import uuid
+
+import typing_extensions
+
+from json_schema_api.configurations import schema_configuration
+
+from . import schema, validation
+
+
+@dataclasses.dataclass(frozen=True)
+class ListSchema(schema.Schema[validation.immutabledict, tuple]):
+ types: typing.FrozenSet[typing.Type] = frozenset({tuple})
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[
+ typing.List[schema.INPUT_TYPES_ALL],
+ schema.U
+ ],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> schema.U: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[
+ typing.Tuple[schema.INPUT_TYPES_ALL, ...],
+ schema.U
+ ],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> schema.U: ...
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ):
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class NoneSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({type(None)})
+
+ @classmethod
+ def validate(
+ cls,
+ arg: None,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> None:
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class NumberSchema(schema.Schema):
+ """
+ This is used for type: number with no format
+ Both integers AND floats are accepted
+ """
+ types: typing.FrozenSet[typing.Type] = frozenset({float, int})
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: int,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> int: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: float,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> float: ...
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ):
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class IntSchema(NumberSchema):
+ types: typing.FrozenSet[typing.Type] = frozenset({int, float})
+ format: str = 'int'
+
+
+@dataclasses.dataclass(frozen=True)
+class Int32Schema(IntSchema):
+ types: typing.FrozenSet[typing.Type] = frozenset({int, float})
+ format: str = 'int32'
+
+
+@dataclasses.dataclass(frozen=True)
+class Int64Schema(IntSchema):
+ types: typing.FrozenSet[typing.Type] = frozenset({int, float})
+ format: str = 'int64'
+
+
+@dataclasses.dataclass(frozen=True)
+class Float32Schema(NumberSchema):
+ types: typing.FrozenSet[typing.Type] = frozenset({float})
+ format: str = 'float'
+
+
+@dataclasses.dataclass(frozen=True)
+class Float64Schema(NumberSchema):
+ types: typing.FrozenSet[typing.Type] = frozenset({float})
+ format: str = 'double'
+
+
+@dataclasses.dataclass(frozen=True)
+class StrSchema(schema.Schema[validation.immutabledict, str]):
+ """
+ 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
+ """
+ types: typing.FrozenSet[typing.Type] = frozenset({str})
+
+ @classmethod
+ def validate(
+ cls,
+ arg: str,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str:
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class UUIDSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({str})
+ format: str = 'uuid'
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[str, uuid.UUID],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str:
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class DateSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({str})
+ format: str = 'date'
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[str, datetime.date],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str:
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class DateTimeSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({str})
+ format: str = 'date-time'
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[str, datetime.datetime],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str:
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class DecimalSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({str})
+ format: str = 'number'
+
+ @classmethod
+ def validate(
+ cls,
+ arg: str,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> str:
+ """
+ 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().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class BytesSchema(schema.Schema[validation.immutabledict, bytes]):
+ """
+ this class will subclass bytes and is immutable
+ """
+ types: typing.FrozenSet[typing.Type] = frozenset({bytes})
+
+ @classmethod
+ def validate(
+ cls,
+ arg: bytes,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> bytes:
+ return cls.validate_base(arg)
+
+
+@dataclasses.dataclass(frozen=True)
+class FileSchema(schema.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
+ """
+ types: typing.FrozenSet[typing.Type] = frozenset({schema.FileIO})
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[io.FileIO, io.BufferedReader],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> schema.FileIO:
+ return cls.validate_base(arg)
+
+
+@dataclasses.dataclass(frozen=True)
+class BinarySchema(schema.Schema[validation.immutabledict, bytes]):
+ types: typing.FrozenSet[typing.Type] = frozenset({schema.FileIO, bytes})
+ format: str = 'binary'
+
+ one_of: typing.Tuple[typing.Type[schema.Schema], ...] = (
+ BytesSchema,
+ FileSchema,
+ )
+
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Union[io.FileIO, io.BufferedReader, bytes],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Union[schema.FileIO, bytes]:
+ return cls.validate_base(arg)
+
+
+@dataclasses.dataclass(frozen=True)
+class BoolSchema(schema.Schema):
+ types: typing.FrozenSet[typing.Type] = frozenset({schema.Bool})
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[True],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[True]: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Literal[False],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> typing.Literal[False]: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: bool,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> bool: ...
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ):
+ return super().validate_base(arg, configuration=configuration)
+
+
+@dataclasses.dataclass(frozen=True)
+class NotAnyTypeSchema(schema.AnyTypeSchema):
+ """
+ Python representation of a schema defined as false or {'not': {}}
+ Does not allow inputs in of AnyType
+ Note: validations on this class are never run because the code knows that no inputs will ever validate
+ """
+ not_: typing.Type[schema.Schema] = schema.AnyTypeSchema
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None,
+ ):
+ return super().validate_base(arg, configuration=configuration)
+
+OUTPUT_BASE_TYPES = typing.Union[
+ validation.immutabledict[str, 'OUTPUT_BASE_TYPES'],
+ str,
+ int,
+ float,
+ bool,
+ schema.none_type_,
+ typing.Tuple['OUTPUT_BASE_TYPES', ...],
+ bytes,
+ schema.FileIO
+]
+
+
+@dataclasses.dataclass(frozen=True)
+class DictSchema(schema.Schema[schema.validation.immutabledict[str, OUTPUT_BASE_TYPES], tuple]):
+ types: typing.FrozenSet[typing.Type] = frozenset({validation.immutabledict})
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: schema.validation.immutabledict[str, OUTPUT_BASE_TYPES],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]: ...
+
+ @typing.overload
+ @classmethod
+ def validate(
+ cls,
+ arg: typing.Mapping[str, schema.INPUT_TYPES_ALL],
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None
+ ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]: ...
+
+ @classmethod
+ def validate(
+ cls,
+ arg,
+ configuration: typing.Optional[schema_configuration.SchemaConfiguration] = None,
+ ) -> schema.validation.immutabledict[str, OUTPUT_BASE_TYPES]:
+ return super().validate_base(arg, configuration=configuration)
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/validation.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/validation.py
new file mode 100644
index 00000000000..84c34b36dfd
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/schemas/validation.py
@@ -0,0 +1,1022 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+import collections
+import dataclasses
+import decimal
+import re
+import sys
+import types
+import typing
+import uuid
+
+import typing_extensions
+
+from json_schema_api import exceptions
+from json_schema_api.configurations import schema_configuration
+
+from . import format, original_immutabledict
+
+_K = typing.TypeVar('_K')
+_V = typing.TypeVar('_V', covariant=True)
+
+
+class immutabledict(typing.Generic[_K, _V], original_immutabledict.immutabledict[_K, _V]):
+ # this class layer needed to not show init signature when making new instances
+ pass
+
+
+@dataclasses.dataclass
+class ValidationMetadata:
+ """
+ A class storing metadata that is needed to validate OpenApi Schema payloads
+ """
+ path_to_item: typing.Tuple[typing.Union[str, int], ...]
+ configuration: schema_configuration.SchemaConfiguration
+ validated_path_to_schemas: typing.Mapping[
+ typing.Tuple[typing.Union[str, int], ...],
+ typing.Mapping[type, None]
+ ] = dataclasses.field(default_factory=dict)
+ seen_classes: typing.FrozenSet[type] = frozenset()
+
+ def validation_ran_earlier(self, cls: type) -> bool:
+ validated_schemas: typing.Union[typing.Mapping[type, None], None] = self.validated_path_to_schemas.get(self.path_to_item)
+ if validated_schemas and cls in validated_schemas:
+ return True
+ if cls in self.seen_classes:
+ return True
+ return False
+
+def _raise_validation_error_message(value, constraint_msg, constraint_value, path_to_item, additional_txt=""):
+ raise exceptions.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,
+ )
+ )
+
+
+class SchemaValidator:
+ __excluded_cls_properties = {
+ '__module__',
+ '__dict__',
+ '__weakref__',
+ '__doc__',
+ '__annotations__',
+ 'default', # excluded because it has no impact on validation
+ 'type_to_output_cls', # used to pluck the output class for instantiation
+ }
+
+ @classmethod
+ def _validate(
+ cls,
+ arg,
+ validation_metadata: ValidationMetadata,
+ ) -> PathToSchemasType:
+ """
+ SchemaValidator validate
+ All keyword validation except for type checking was done in calling stack frames
+ If those validations passed, the validated classes are collected in path_to_schemas
+ """
+ cls_schema = cls()
+ json_schema_data = {
+ k: v
+ for k, v in vars(cls_schema).items()
+ if k not in cls.__excluded_cls_properties
+ and k
+ not in validation_metadata.configuration.disabled_json_schema_python_keywords
+ }
+ path_to_schemas: PathToSchemasType = {}
+ for keyword, val in json_schema_data.items():
+ validator = json_schema_keyword_to_validator[keyword]
+
+ other_path_to_schemas = validator(
+ arg,
+ val,
+ cls,
+ validation_metadata,
+ )
+ if other_path_to_schemas:
+ update(path_to_schemas, other_path_to_schemas)
+
+ base_class = type(arg)
+ if validation_metadata.path_to_item not in path_to_schemas:
+ path_to_schemas[validation_metadata.path_to_item] = dict()
+ path_to_schemas[validation_metadata.path_to_item][base_class] = None
+ path_to_schemas[validation_metadata.path_to_item][cls] = None
+ return path_to_schemas
+
+PathToSchemasType = typing.Dict[
+ typing.Tuple[typing.Union[str, int], ...],
+ typing.Dict[
+ typing.Union[
+ typing.Type[SchemaValidator],
+ typing.Type[str],
+ typing.Type[int],
+ typing.Type[float],
+ typing.Type[bool],
+ typing.Type[None],
+ typing.Type[immutabledict],
+ typing.Type[tuple]
+ ],
+ None
+ ]
+]
+
+def _get_class(
+ item_cls: typing.Union[types.FunctionType, staticmethod, typing.Type[SchemaValidator]],
+ local_namespace: typing.Optional[dict] = None
+) -> typing.Type[SchemaValidator]:
+ if isinstance(item_cls, typing._GenericAlias): # type: ignore
+ # petstore_api.schemas.StrSchema[~U] -> petstore_api.schemas.StrSchema
+ origin_cls = typing.get_origin(item_cls)
+ if origin_cls is None:
+ raise ValueError('origin class must not be None')
+ return origin_cls
+ elif isinstance(item_cls, types.FunctionType):
+ # referenced schema
+ return item_cls()
+ elif isinstance(item_cls, staticmethod):
+ # referenced schema
+ return item_cls.__func__()
+ elif isinstance(item_cls, type):
+ return item_cls
+ elif isinstance(item_cls, typing.ForwardRef):
+ return item_cls._evaluate(None, local_namespace)
+ raise ValueError('invalid class value passed in')
+
+
+def update(d: dict, u: dict):
+ """
+ Adds u to d
+ Where each dict is collections.defaultdict(dict)
+ """
+ if not u:
+ return d
+ for k, v in u.items():
+ if k not in d:
+ d[k] = v
+ else:
+ d[k].update(v)
+
+
+def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict):
+ # this is called if validation_ran_earlier and current and deeper locations need to be added
+ current_path_to_item = validation_metadata.path_to_item
+ other_path_to_schemas = {}
+ for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items():
+ if len(path_to_item) < len(current_path_to_item):
+ continue
+ path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item
+ if path_begins_with_current_path:
+ other_path_to_schemas[path_to_item] = schemas
+ update(path_to_schemas, other_path_to_schemas)
+
+
+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 __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 tuple
+ """
+ key_or_value = "value"
+ if key_type:
+ key_or_value = "key"
+ valid_classes_phrase = __get_valid_classes_phrase(valid_classes)
+ msg = "Invalid type. Required {0} type {1} and " "passed type was {2}".format(
+ key_or_value,
+ valid_classes_phrase,
+ type(var_value).__name__,
+ )
+ return msg
+
+
+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 exceptions.ApiTypeError(
+ error_msg,
+ path_to_item=path_to_item,
+ valid_classes=valid_classes,
+ key_type=key_type,
+ )
+
+
+@dataclasses.dataclass(frozen=True)
+class PatternInfo:
+ pattern: str
+ flags: typing.Optional[re.RegexFlag] = None
+
+
+def validate_types(
+ arg: typing.Any,
+ allowed_types: typing.Set[typing.Type],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if type(arg) not in allowed_types:
+ raise __get_type_error(
+ arg,
+ validation_metadata.path_to_item,
+ allowed_types,
+ key_type=False,
+ )
+ return None
+
+def validate_enum(
+ arg: typing.Any,
+ enum_value_to_name: typing.Dict[typing.Any, str],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if arg not in enum_value_to_name:
+ raise exceptions.ApiValueError("Invalid value {} passed in to {}, allowed_values={}".format(arg, cls, enum_value_to_name.keys()))
+ return None
+
+
+def validate_unique_items(
+ arg: typing.Any,
+ unique_items_value: bool,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not unique_items_value or not isinstance(arg, tuple):
+ return None
+ if len(arg) == len(set(arg)):
+ return None
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="duplicate items were found, and the tuple must not contain duplicates because",
+ constraint_value='unique_items==True',
+ path_to_item=validation_metadata.path_to_item
+ )
+
+
+def validate_min_items(
+ arg: typing.Any,
+ min_items: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, tuple):
+ return None
+ if len(arg) < min_items:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="number of items must be greater than or equal to",
+ constraint_value=min_items,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_max_items(
+ arg: typing.Any,
+ max_items: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, tuple):
+ return None
+ if len(arg) > max_items:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="number of items must be less than or equal to",
+ constraint_value=max_items,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_min_properties(
+ arg: typing.Any,
+ min_properties: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, immutabledict):
+ return None
+ if len(arg) < min_properties:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="number of properties must be greater than or equal to",
+ constraint_value=min_properties,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_max_properties(
+ arg: typing.Any,
+ max_properties: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, immutabledict):
+ return None
+ if len(arg) > max_properties:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="number of properties must be less than or equal to",
+ constraint_value=max_properties,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_min_length(
+ arg: typing.Any,
+ min_length: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, str):
+ return None
+ if len(arg) < min_length:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="length must be greater than or equal to",
+ constraint_value=min_length,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_max_length(
+ arg: typing.Any,
+ max_length: int,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, str):
+ return None
+ if len(arg) > max_length:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="length must be less than or equal to",
+ constraint_value=max_length,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_inclusive_minimum(
+ arg: typing.Any,
+ inclusive_minimum: typing.Union[int, float],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, (int, float)):
+ return None
+ if arg < inclusive_minimum:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must be a value greater than or equal to",
+ constraint_value=inclusive_minimum,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_exclusive_minimum(
+ arg: typing.Any,
+ exclusive_minimum: typing.Union[int, float],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, (int, float)):
+ return None
+ if arg <= exclusive_minimum:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must be a value greater than",
+ constraint_value=exclusive_minimum,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_inclusive_maximum(
+ arg: typing.Any,
+ inclusive_maximum: typing.Union[int, float],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, (int, float)):
+ return None
+ if arg > inclusive_maximum:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must be a value less than or equal to",
+ constraint_value=inclusive_maximum,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_exclusive_maximum(
+ arg: typing.Any,
+ exclusive_maximum: typing.Union[int, float],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, (int, float)):
+ return None
+ if arg >= exclusive_maximum:
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must be a value less than",
+ constraint_value=exclusive_maximum,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+def validate_multiple_of(
+ arg: typing.Any,
+ multiple_of: typing.Union[int, float],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, (int, float)):
+ return None
+ if (not (float(arg) / multiple_of).is_integer()):
+ # Note 'multipleOf' will be as good as the floating point arithmetic.
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="value must be a multiple of",
+ constraint_value=multiple_of,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+def validate_pattern(
+ arg: typing.Any,
+ pattern_info: PatternInfo,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, str):
+ return None
+ flags = pattern_info.flags if pattern_info.flags is not None else 0
+ if not re.search(pattern_info.pattern, arg, flags=flags):
+ if flags != 0:
+ # Don't print the regex flags if the flags are not
+ # specified in the OAS document.
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must match regular expression",
+ constraint_value=pattern_info.pattern,
+ path_to_item=validation_metadata.path_to_item,
+ additional_txt=" with flags=`{}`".format(flags)
+ )
+ _raise_validation_error_message(
+ value=arg,
+ constraint_msg="must match regular expression",
+ constraint_value=pattern_info.pattern,
+ path_to_item=validation_metadata.path_to_item
+ )
+ return None
+
+
+__int32_inclusive_minimum = -2147483648
+__int32_inclusive_maximum = 2147483647
+__int64_inclusive_minimum = -9223372036854775808
+__int64_inclusive_maximum = 9223372036854775807
+__float_inclusive_minimum = -3.4028234663852886e+38
+__float_inclusive_maximum = 3.4028234663852886e+38
+__double_inclusive_minimum = -1.7976931348623157E+308
+__double_inclusive_maximum = 1.7976931348623157E+308
+
+def __validate_numeric_format(
+ arg: typing.Union[int, float],
+ format_value: str,
+ validation_metadata: ValidationMetadata
+) -> None:
+ if format_value[:3] == 'int':
+ # there is a json schema test where 1.0 validates as an integer
+ if arg != int(arg):
+ raise exceptions.ApiValueError(
+ "Invalid non-integer value '{}' for type {} at {}".format(
+ arg, format, validation_metadata.path_to_item
+ )
+ )
+ if format_value == 'int32':
+ if not __int32_inclusive_minimum <= arg <= __int32_inclusive_maximum:
+ raise exceptions.ApiValueError(
+ "Invalid value '{}' for type int32 at {}".format(arg, validation_metadata.path_to_item)
+ )
+ return None
+ elif format_value == 'int64':
+ if not __int64_inclusive_minimum <= arg <= __int64_inclusive_maximum:
+ raise exceptions.ApiValueError(
+ "Invalid value '{}' for type int64 at {}".format(arg, validation_metadata.path_to_item)
+ )
+ return None
+ return None
+ elif format_value in {'float', 'double'}:
+ if format_value == 'float':
+ if not __float_inclusive_minimum <= arg <= __float_inclusive_maximum:
+ raise exceptions.ApiValueError(
+ "Invalid value '{}' for type float at {}".format(arg, validation_metadata.path_to_item)
+ )
+ return None
+ # double
+ if not __double_inclusive_minimum <= arg <= __double_inclusive_maximum:
+ raise exceptions.ApiValueError(
+ "Invalid value '{}' for type double at {}".format(arg, validation_metadata.path_to_item)
+ )
+ return None
+ return None
+
+
+def __validate_string_format(
+ arg: str,
+ format_value: str,
+ validation_metadata: ValidationMetadata
+) -> None:
+ if format_value == 'uuid':
+ try:
+ uuid.UUID(arg)
+ return None
+ except ValueError:
+ raise exceptions.ApiValueError(
+ "Invalid value '{}' for type UUID at {}".format(arg, validation_metadata.path_to_item)
+ )
+ elif format_value == 'number':
+ try:
+ decimal.Decimal(arg)
+ return None
+ except decimal.InvalidOperation:
+ raise exceptions.ApiValueError(
+ "Value cannot be converted to a decimal. "
+ "Invalid value '{}' for type decimal at {}".format(arg, validation_metadata.path_to_item)
+ )
+ elif format_value == 'date':
+ try:
+ format.DEFAULT_ISOPARSER.parse_isodate_str(arg)
+ return None
+ except ValueError:
+ raise exceptions.ApiValueError(
+ "Value does not conform to the required ISO-8601 date format. "
+ "Invalid value '{}' for type date at {}".format(arg, validation_metadata.path_to_item)
+ )
+ elif format_value == 'date-time':
+ try:
+ format.DEFAULT_ISOPARSER.parse_isodatetime(arg)
+ return None
+ except ValueError:
+ raise exceptions.ApiValueError(
+ "Value does not conform to the required ISO-8601 datetime format. "
+ "Invalid value '{}' for type datetime at {}".format(arg, validation_metadata.path_to_item)
+ )
+ return None
+
+
+def validate_format(
+ arg: typing.Union[str, int, float],
+ format_value: str,
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ # formats work for strings + numbers
+ if isinstance(arg, (int, float)):
+ return __validate_numeric_format(
+ arg,
+ format_value,
+ validation_metadata
+ )
+ elif isinstance(arg, str):
+ return __validate_string_format(
+ arg,
+ format_value,
+ validation_metadata
+ )
+ return None
+
+
+def validate_required(
+ arg: typing.Any,
+ required: typing.Set[str],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ if not isinstance(arg, immutabledict):
+ return None
+ missing_req_args = required - arg.keys()
+ if missing_req_args:
+ missing_required_arguments = list(missing_req_args)
+ missing_required_arguments.sort()
+ raise exceptions.ApiTypeError(
+ "{} is missing {} required argument{}: {}".format(
+ cls.__name__,
+ len(missing_required_arguments),
+ "s" if len(missing_required_arguments) > 1 else "",
+ missing_required_arguments
+ )
+ )
+ return None
+
+
+def validate_items(
+ arg: typing.Any,
+ item_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ item_cls = _get_class(item_cls)
+ path_to_schemas: PathToSchemasType = {}
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(item_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ continue
+ other_path_to_schemas = item_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+
+
+def validate_properties(
+ arg: typing.Any,
+ properties: typing.Mapping[str, typing.Type[SchemaValidator]],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, immutabledict):
+ return None
+ path_to_schemas: PathToSchemasType = {}
+ present_properties = {k: v for k, v, in arg.items() if k in properties}
+ module_namespace = vars(sys.modules[cls.__module__])
+ for property_name, value in present_properties.items():
+ path_to_item = validation_metadata.path_to_item + (property_name,)
+ schema = properties[property_name]
+ schema = _get_class(schema, module_namespace)
+ arg_validation_metadata = ValidationMetadata(
+ path_to_item=path_to_item,
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if arg_validation_metadata.validation_ran_earlier(schema):
+ add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas)
+ continue
+ other_path_to_schemas = schema._validate(value, validation_metadata=arg_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+
+
+def validate_additional_properties(
+ arg: typing.Any,
+ additional_properties_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, immutabledict):
+ return None
+ schema = _get_class(additional_properties_cls)
+ path_to_schemas: PathToSchemasType = {}
+ cls_schema = cls()
+ properties = cls_schema.properties if hasattr(cls_schema, 'properties') else {}
+ present_additional_properties = {k: v for k, v, in arg.items() if k not in properties}
+ for property_name, value in present_additional_properties.items():
+ path_to_item = validation_metadata.path_to_item + (property_name,)
+ arg_validation_metadata = ValidationMetadata(
+ path_to_item=path_to_item,
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if arg_validation_metadata.validation_ran_earlier(schema):
+ add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas)
+ continue
+ other_path_to_schemas = schema._validate(value, validation_metadata=arg_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+
+
+def validate_one_of(
+ arg: typing.Any,
+ classes: typing.Tuple[typing.Type[SchemaValidator], ...],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> PathToSchemasType:
+ oneof_classes = []
+ path_to_schemas: PathToSchemasType = collections.defaultdict(dict)
+ for schema in classes:
+ schema = _get_class(schema)
+ if schema in path_to_schemas[validation_metadata.path_to_item]:
+ oneof_classes.append(schema)
+ continue
+ if schema is cls:
+ """
+ optimistically assume that cls schema will pass validation
+ do not invoke _validate on it because that is recursive
+ """
+ oneof_classes.append(schema)
+ continue
+ if validation_metadata.validation_ran_earlier(schema):
+ oneof_classes.append(schema)
+ add_deeper_validated_schemas(validation_metadata, path_to_schemas)
+ continue
+ try:
+ path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata)
+ except (exceptions.ApiValueError, exceptions.ApiTypeError) as ex:
+ # silence exceptions because the code needs to accumulate oneof_classes
+ continue
+ oneof_classes.append(schema)
+ if not oneof_classes:
+ raise exceptions.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 exceptions.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)
+ )
+ # exactly one class matches
+ return path_to_schemas
+
+
+def validate_any_of(
+ arg: typing.Any,
+ classes: typing.Tuple[typing.Type[SchemaValidator], ...],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> PathToSchemasType:
+ anyof_classes = []
+ path_to_schemas: PathToSchemasType = collections.defaultdict(dict)
+ for schema in classes:
+ schema = _get_class(schema)
+ if schema is cls:
+ """
+ optimistically assume that cls schema will pass validation
+ do not invoke _validate on it because that is recursive
+ """
+ anyof_classes.append(schema)
+ continue
+ if validation_metadata.validation_ran_earlier(schema):
+ anyof_classes.append(schema)
+ add_deeper_validated_schemas(validation_metadata, path_to_schemas)
+ continue
+
+ try:
+ other_path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata)
+ except (exceptions.ApiValueError, exceptions.ApiTypeError) as ex:
+ # silence exceptions because the code needs to accumulate anyof_classes
+ continue
+ anyof_classes.append(schema)
+ update(path_to_schemas, other_path_to_schemas)
+ if not anyof_classes:
+ raise exceptions.ApiValueError(
+ "Invalid inputs given to generate an instance of {}. None "
+ "of the anyOf schemas matched the input data.".format(cls)
+ )
+ return path_to_schemas
+
+
+def validate_all_of(
+ arg: typing.Any,
+ classes: typing.Tuple[typing.Type[SchemaValidator], ...],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> PathToSchemasType:
+ path_to_schemas: PathToSchemasType = collections.defaultdict(dict)
+ for schema in classes:
+ schema = _get_class(schema)
+ if schema is cls:
+ """
+ optimistically assume that cls schema will pass validation
+ do not invoke _validate on it because that is recursive
+ """
+ continue
+ if validation_metadata.validation_ran_earlier(schema):
+ add_deeper_validated_schemas(validation_metadata, path_to_schemas)
+ continue
+ other_path_to_schemas = schema._validate(arg, validation_metadata=validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+
+
+def validate_not(
+ arg: typing.Any,
+ not_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> None:
+ not_schema = _get_class(not_cls)
+ other_path_to_schemas = None
+ not_exception = exceptions.ApiValueError(
+ "Invalid value '{}' was passed in to {}. Value is invalid because it is disallowed by {}".format(
+ arg,
+ cls.__name__,
+ not_schema.__name__,
+ )
+ )
+ if validation_metadata.validation_ran_earlier(not_schema):
+ raise not_exception
+
+ try:
+ other_path_to_schemas = not_schema._validate(arg, validation_metadata=validation_metadata)
+ except (exceptions.ApiValueError, exceptions.ApiTypeError):
+ pass
+ if other_path_to_schemas:
+ raise not_exception
+ return None
+
+
+def __ensure_discriminator_value_present(
+ disc_property_name: str,
+ validation_metadata: ValidationMetadata,
+ arg
+):
+ if disc_property_name not in arg:
+ # The input data does not contain the discriminator property
+ raise exceptions.ApiValueError(
+ "Cannot deserialize input data due to missing discriminator. "
+ "The discriminator property '{}' is missing at path: {}".format(disc_property_name, validation_metadata.path_to_item)
+ )
+
+
+def __get_discriminated_class(cls, disc_property_name: str, disc_payload_value: str):
+ """
+ Used in schemas with discriminators
+ """
+ cls_schema = cls()
+ if not hasattr(cls_schema, 'discriminator'):
+ return None
+ disc = cls_schema.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
+ if not (
+ hasattr(cls_schema, 'all_of') or
+ hasattr(cls_schema, 'one_of') or
+ hasattr(cls_schema, 'any_of')
+ ):
+ return None
+ # TODO stop traveling if a cycle is hit
+ if hasattr(cls_schema, 'all_of'):
+ for allof_cls in cls_schema.all_of:
+ discriminated_cls = __get_discriminated_class(
+ allof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value)
+ if discriminated_cls is not None:
+ return discriminated_cls
+ if hasattr(cls_schema, 'one_of'):
+ for oneof_cls in cls_schema.one_of:
+ discriminated_cls = __get_discriminated_class(
+ oneof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value)
+ if discriminated_cls is not None:
+ return discriminated_cls
+ if hasattr(cls_schema, 'any_of'):
+ for anyof_cls in cls_schema.any_of:
+ discriminated_cls = __get_discriminated_class(
+ anyof_cls, disc_property_name=disc_property_name, disc_payload_value=disc_payload_value)
+ if discriminated_cls is not None:
+ return discriminated_cls
+ return None
+
+
+def validate_discriminator(
+ arg: typing.Any,
+ discriminator: typing.Mapping[str, typing.Mapping[str, typing.Type[SchemaValidator]]],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, immutabledict):
+ return None
+ disc_prop_name = list(discriminator.keys())[0]
+ __ensure_discriminator_value_present(disc_prop_name, validation_metadata, arg)
+ discriminated_cls = __get_discriminated_class(
+ cls, disc_property_name=disc_prop_name, disc_payload_value=arg[disc_prop_name]
+ )
+ if discriminated_cls is None:
+ raise exceptions.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()),
+ validation_metadata.path_to_item + (disc_prop_name,)
+ )
+ )
+ if discriminated_cls is cls:
+ """
+ Optimistically assume that cls will pass validation
+ If the code invoked _validate on cls it would infinitely recurse
+ """
+ return None
+ if validation_metadata.validation_ran_earlier(discriminated_cls):
+ path_to_schemas: PathToSchemasType = {}
+ add_deeper_validated_schemas(validation_metadata, path_to_schemas)
+ return path_to_schemas
+ updated_vm = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item,
+ configuration=validation_metadata.configuration,
+ seen_classes=validation_metadata.seen_classes | frozenset({cls}),
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+
+
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
+validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
+json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
+ 'types': validate_types,
+ 'enum_value_to_name': validate_enum,
+ 'unique_items': validate_unique_items,
+ 'min_items': validate_min_items,
+ 'max_items': validate_max_items,
+ 'min_properties': validate_min_properties,
+ 'max_properties': validate_max_properties,
+ 'min_length': validate_min_length,
+ 'max_length': validate_max_length,
+ 'inclusive_minimum': validate_inclusive_minimum,
+ 'exclusive_minimum': validate_exclusive_minimum,
+ 'inclusive_maximum': validate_inclusive_maximum,
+ 'exclusive_maximum': validate_exclusive_maximum,
+ 'multiple_of': validate_multiple_of,
+ 'pattern': validate_pattern,
+ 'format': validate_format,
+ 'required': validate_required,
+ 'items': validate_items,
+ 'properties': validate_properties,
+ 'additional_properties': validate_additional_properties,
+ 'one_of': validate_one_of,
+ 'any_of': validate_any_of,
+ 'all_of': validate_all_of,
+ 'not_': validate_not,
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
+}
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/security_schemes.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/security_schemes.py
new file mode 100644
index 00000000000..f9eb9b9cf36
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/security_schemes.py
@@ -0,0 +1,227 @@
+# coding: utf-8
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import abc
+import base64
+import dataclasses
+import enum
+import typing
+import typing_extensions
+
+from urllib3 import _collections
+
+
+class SecuritySchemeType(enum.Enum):
+ API_KEY = 'apiKey'
+ HTTP = 'http'
+ MUTUAL_TLS = 'mutualTLS'
+ OAUTH_2 = 'oauth2'
+ OPENID_CONNECT = 'openIdConnect'
+
+
+class ApiKeyInLocation(enum.Enum):
+ QUERY = 'query'
+ HEADER = 'header'
+ COOKIE = 'cookie'
+
+
+class __SecuritySchemeBase(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ pass
+
+
+@dataclasses.dataclass
+class ApiKeySecurityScheme(__SecuritySchemeBase, abc.ABC):
+ api_key: str # this must be set by the developer
+ name: str = ''
+ in_location: ApiKeyInLocation = ApiKeyInLocation.QUERY
+ type: SecuritySchemeType = SecuritySchemeType.API_KEY
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ if self.in_location is ApiKeyInLocation.COOKIE:
+ headers.add('Cookie', self.api_key)
+ elif self.in_location is ApiKeyInLocation.HEADER:
+ headers.add(self.name, self.api_key)
+ elif self.in_location is ApiKeyInLocation.QUERY:
+ # todo add query handling
+ raise NotImplementedError("ApiKeySecurityScheme in query not yet implemented")
+ return
+
+
+class HTTPSchemeType(enum.Enum):
+ BASIC = 'basic'
+ BEARER = 'bearer'
+ DIGEST = 'digest'
+ SIGNATURE = 'signature' # https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
+
+
+@dataclasses.dataclass
+class HTTPBasicSecurityScheme(__SecuritySchemeBase):
+ user_id: str # user name
+ password: str
+ scheme: HTTPSchemeType = HTTPSchemeType.BASIC
+ encoding: str = 'utf-8'
+ type: SecuritySchemeType = SecuritySchemeType.HTTP
+ """
+ https://www.rfc-editor.org/rfc/rfc7617.html
+ """
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ user_pass = f"{self.user_id}:{self.password}"
+ b64_user_pass = base64.b64encode(user_pass.encode(encoding=self.encoding))
+ headers.add('Authorization', f"Basic {b64_user_pass.decode()}")
+
+
+@dataclasses.dataclass
+class HTTPBearerSecurityScheme(__SecuritySchemeBase):
+ access_token: str
+ bearer_format: typing.Optional[str] = None
+ scheme: HTTPSchemeType = HTTPSchemeType.BEARER
+ type: SecuritySchemeType = SecuritySchemeType.HTTP
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ headers.add('Authorization', f"Bearer {self.access_token}")
+
+
+@dataclasses.dataclass
+class HTTPDigestSecurityScheme(__SecuritySchemeBase):
+ scheme: HTTPSchemeType = HTTPSchemeType.DIGEST
+ type: SecuritySchemeType = SecuritySchemeType.HTTP
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ raise NotImplementedError("HTTPDigestSecurityScheme not yet implemented")
+
+
+@dataclasses.dataclass
+class MutualTLSSecurityScheme(__SecuritySchemeBase):
+ type: SecuritySchemeType = SecuritySchemeType.MUTUAL_TLS
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ raise NotImplementedError("MutualTLSSecurityScheme not yet implemented")
+
+
+@dataclasses.dataclass
+class ImplicitOAuthFlow:
+ authorization_url: str
+ scopes: typing.Dict[str, str]
+ refresh_url: typing.Optional[str] = None
+
+
+@dataclasses.dataclass
+class TokenUrlOauthFlow:
+ token_url: str
+ scopes: typing.Dict[str, str]
+ refresh_url: typing.Optional[str] = None
+
+
+@dataclasses.dataclass
+class AuthorizationCodeOauthFlow:
+ authorization_url: str
+ token_url: str
+ scopes: typing.Dict[str, str]
+ refresh_url: typing.Optional[str] = None
+
+
+@dataclasses.dataclass
+class OAuthFlows:
+ implicit: typing.Optional[ImplicitOAuthFlow] = None
+ password: typing.Optional[TokenUrlOauthFlow] = None
+ client_credentials: typing.Optional[TokenUrlOauthFlow] = None
+ authorization_code: typing.Optional[AuthorizationCodeOauthFlow] = None
+
+
+class OAuth2SecurityScheme(__SecuritySchemeBase, abc.ABC):
+ flows: OAuthFlows
+ type: SecuritySchemeType = SecuritySchemeType.OAUTH_2
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ raise NotImplementedError("OAuth2SecurityScheme not yet implemented")
+
+
+class OpenIdConnectSecurityScheme(__SecuritySchemeBase, abc.ABC):
+ openid_connect_url: str
+ type: SecuritySchemeType = SecuritySchemeType.OPENID_CONNECT
+
+ def apply_auth(
+ self,
+ headers: _collections.HTTPHeaderDict,
+ resource_path: str,
+ method: str,
+ body: typing.Optional[typing.Union[str, bytes]],
+ query_params_suffix: typing.Optional[str],
+ scope_names: typing.Tuple[str, ...] = (),
+ ) -> None:
+ raise NotImplementedError("OpenIdConnectSecurityScheme not yet implemented")
+
+"""
+Key is the Security scheme class
+Value is the list of scopes
+"""
+SecurityRequirementObject = typing.TypedDict(
+ 'SecurityRequirementObject',
+ {
+ },
+ total=False
+)
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/server.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/server.py
new file mode 100644
index 00000000000..ca8af8b111f
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/server.py
@@ -0,0 +1,34 @@
+# coding: utf-8
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from __future__ import annotations
+import abc
+import dataclasses
+import typing
+
+from json_schema_api.schemas import validation, schema
+
+
+@dataclasses.dataclass
+class ServerWithoutVariables(abc.ABC):
+ url: str
+
+
+@dataclasses.dataclass
+class ServerWithVariables(abc.ABC):
+ _url: str
+ variables: validation.immutabledict[str, str]
+ variables_schema: typing.Type[schema.Schema]
+ url: str = dataclasses.field(init=False)
+
+ def __post_init__(self):
+ url = self._url
+ assert isinstance (self.variables, self.variables_schema().type_to_output_cls[validation.immutabledict])
+ for (key, value) in self.variables.items():
+ url = url.replace("{" + key + "}", value)
+ self.url = url
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/servers/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/servers/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/servers/server_0.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/servers/server_0.py
new file mode 100644
index 00000000000..5c91a6f1da4
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/servers/server_0.py
@@ -0,0 +1,11 @@
+# coding: utf-8
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+from json_schema_api.shared_imports.server_imports import * # pyright: ignore [reportWildcardImportFromLibrary]
+
+
+@dataclasses.dataclass
+class Server0(server.ServerWithoutVariables):
+ url: str = "http://api.example.xyz/v1"
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/__init__.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/header_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/header_imports.py
new file mode 100644
index 00000000000..d06d8732d13
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/header_imports.py
@@ -0,0 +1,15 @@
+import decimal
+import io
+import typing
+import typing_extensions
+
+from json_schema_api import api_client, schemas
+
+__all__ = [
+ 'decimal',
+ 'io',
+ 'typing',
+ 'typing_extensions',
+ 'api_client',
+ 'schemas'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/operation_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/operation_imports.py
new file mode 100644
index 00000000000..67a6810c6ec
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/operation_imports.py
@@ -0,0 +1,18 @@
+import datetime
+import decimal
+import io
+import typing
+import typing_extensions
+import uuid
+
+from json_schema_api import schemas, api_response
+
+__all__ = [
+ 'decimal',
+ 'io',
+ 'typing',
+ 'typing_extensions',
+ 'uuid',
+ 'schemas',
+ 'api_response'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/response_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/response_imports.py
new file mode 100644
index 00000000000..1c6cbdb7147
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/response_imports.py
@@ -0,0 +1,25 @@
+import dataclasses
+import datetime
+import decimal
+import io
+import typing
+import uuid
+
+import typing_extensions
+import urllib3
+
+from json_schema_api import api_client, schemas, api_response
+
+__all__ = [
+ 'dataclasses',
+ 'datetime',
+ 'decimal',
+ 'io',
+ 'typing',
+ 'uuid',
+ 'typing_extensions',
+ 'urllib3',
+ 'api_client',
+ 'schemas',
+ 'api_response'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/schema_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/schema_imports.py
new file mode 100644
index 00000000000..9ce95d517d9
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/schema_imports.py
@@ -0,0 +1,28 @@
+import dataclasses
+import datetime
+import decimal
+import io
+import numbers
+import re
+import typing
+import typing_extensions
+import uuid
+
+from json_schema_api import schemas
+from json_schema_api.configurations import schema_configuration
+
+U = typing.TypeVar('U')
+
+__all__ = [
+ 'dataclasses',
+ 'datetime',
+ 'decimal',
+ 'io',
+ 'numbers',
+ 're',
+ 'typing',
+ 'typing_extensions',
+ 'uuid',
+ 'schemas',
+ 'schema_configuration'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/security_scheme_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/security_scheme_imports.py
new file mode 100644
index 00000000000..71b3f27ddd3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/security_scheme_imports.py
@@ -0,0 +1,12 @@
+import dataclasses
+import typing
+import typing_extensions
+
+from json_schema_api import security_schemes
+
+__all__ = [
+ 'dataclasses',
+ 'typing',
+ 'typing_extensions',
+ 'security_schemes'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/server_imports.py b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/server_imports.py
new file mode 100644
index 00000000000..1c89ddff73b
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/src/json_schema_api/shared_imports/server_imports.py
@@ -0,0 +1,13 @@
+import dataclasses
+import typing
+import typing_extensions
+
+from json_schema_api import server, schemas
+
+__all__ = [
+ 'dataclasses',
+ 'typing',
+ 'typing_extensions',
+ 'server',
+ 'schemas'
+]
\ No newline at end of file
diff --git a/samples/client/3_1_0_json_schema/python/test-requirements.txt b/samples/client/3_1_0_json_schema/python/test-requirements.txt
new file mode 100644
index 00000000000..3043888202a
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test-requirements.txt
@@ -0,0 +1,2 @@
+pytest ~= 7.2.0
+pytest-cov ~= 4.0.0
diff --git a/samples/client/3_1_0_json_schema/python/test/__init__.py b/samples/client/3_1_0_json_schema/python/test/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/test/components/__init__.py b/samples/client/3_1_0_json_schema/python/test/components/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/test/components/schema/__init__.py b/samples/client/3_1_0_json_schema/python/test/components/schema/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/test/components/schema/test_any_type_contains_value.py b/samples/client/3_1_0_json_schema/python/test/components/schema/test_any_type_contains_value.py
new file mode 100644
index 00000000000..d20ef05a495
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test/components/schema/test_any_type_contains_value.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import unittest
+
+import json_schema_api
+from json_schema_api.components.schema.any_type_contains_value import AnyTypeContainsValue
+from json_schema_api.configurations import schema_configuration
+
+
+class TestAnyTypeContainsValue(unittest.TestCase):
+ """AnyTypeContainsValue unit test stubs"""
+ configuration = schema_configuration.SchemaConfiguration()
+
+ def test_contains_success_single(self):
+ inst = AnyTypeContainsValue.validate((1,))
+ assert inst == (1,)
+
+ def test_contains_success_multiple(self):
+ inst = AnyTypeContainsValue.validate((2, 1, 1))
+ assert inst == (2, 1, 1)
+
+ def test_contains_failure(self):
+ with self.assertRaises(json_schema_api.ApiValueError):
+ AnyTypeContainsValue.validate((2,))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/samples/client/3_1_0_json_schema/python/test/components/schema/test_array_contains_value.py b/samples/client/3_1_0_json_schema/python/test/components/schema/test_array_contains_value.py
new file mode 100644
index 00000000000..b128ac08a30
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test/components/schema/test_array_contains_value.py
@@ -0,0 +1,23 @@
+# coding: utf-8
+
+"""
+ Example
+ No description provided (generated by Openapi JSON Schema Generator https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) # noqa: E501
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import unittest
+
+import json_schema_api
+from json_schema_api.components.schema.array_contains_value import ArrayContainsValue
+from json_schema_api.configurations import schema_configuration
+
+
+class TestArrayContainsValue(unittest.TestCase):
+ """ArrayContainsValue unit test stubs"""
+ configuration = schema_configuration.SchemaConfiguration()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/samples/client/3_1_0_json_schema/python/test/test_paths/__init__.py b/samples/client/3_1_0_json_schema/python/test/test_paths/__init__.py
new file mode 100644
index 00000000000..bcc864a1738
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test/test_paths/__init__.py
@@ -0,0 +1,68 @@
+import json
+import typing
+
+import urllib3
+from urllib3._collections import HTTPHeaderDict
+
+
+class ApiTestMixin:
+ json_content_type = 'application/json'
+ user_agent = 'OpenAPI-JSON-Schema-Generator/1.0.0/python'
+
+ @classmethod
+ def assert_pool_manager_request_called_with(
+ cls,
+ mock_request,
+ url: str,
+ method: str = 'POST',
+ body: typing.Optional[bytes] = None,
+ content_type: typing.Optional[str] = None,
+ accept_content_type: typing.Optional[str] = None,
+ stream: bool = False,
+ ):
+ headers = {
+ 'User-Agent': cls.user_agent
+ }
+ if accept_content_type:
+ headers['Accept'] = accept_content_type
+ if content_type:
+ headers['Content-Type'] = content_type
+ kwargs = dict(
+ headers=HTTPHeaderDict(headers),
+ preload_content=not stream,
+ timeout=None,
+ )
+ if content_type and method != 'GET':
+ kwargs['body'] = body
+ mock_request.assert_called_with(
+ method,
+ url,
+ **kwargs
+ )
+
+ @staticmethod
+ def headers_for_content_type(content_type: str) -> typing.Dict[str, str]:
+ return {'content-type': content_type}
+
+ @classmethod
+ def response(
+ cls,
+ body: typing.Union[str, bytes],
+ status: int = 200,
+ content_type: str = json_content_type,
+ headers: typing.Optional[typing.Dict[str, str]] = None,
+ preload_content: bool = True
+ ) -> urllib3.HTTPResponse:
+ if headers is None:
+ headers = {}
+ headers.update(cls.headers_for_content_type(content_type))
+ return urllib3.HTTPResponse(
+ body,
+ headers=headers,
+ status=status,
+ preload_content=preload_content
+ )
+
+ @staticmethod
+ def json_bytes(in_data: typing.Any) -> bytes:
+ return json.dumps(in_data, separators=(",", ":"), ensure_ascii=False).encode('utf-8')
diff --git a/samples/client/3_1_0_json_schema/python/test/test_paths/test_some_path/__init__.py b/samples/client/3_1_0_json_schema/python/test/test_paths/test_some_path/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/samples/client/3_1_0_json_schema/python/test/test_paths/test_some_path/test_get.py b/samples/client/3_1_0_json_schema/python/test/test_paths/test_some_path/test_get.py
new file mode 100644
index 00000000000..76b738bccd3
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test/test_paths/test_some_path/test_get.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+"""
+ Generated by: https://github.com/openapi-json-schema-tools/openapi-json-schema-generator
+"""
+
+import unittest
+from unittest.mock import patch
+
+import urllib3
+import typing_extensions
+
+import json_schema_api
+from json_schema_api.paths.some_path.get import operation as get # noqa: E501
+from json_schema_api import schemas, api_client
+from json_schema_api.configurations import api_configuration, schema_configuration
+
+from .. import ApiTestMixin
+
+
+class TestGet(ApiTestMixin, unittest.TestCase):
+ """
+ Get unit test stubs
+ """
+ api_config = api_configuration.ApiConfiguration()
+ schema_config = schema_configuration.SchemaConfiguration()
+ used_api_client = api_client.ApiClient(configuration=api_config, schema_configuration=schema_config)
+ api = get.ApiForGet(api_client=used_api_client) # noqa: E501
+
+ response_status = 200
+ response_body_schema = get.response_200.ResponseFor200.content["application/json"].schema
+ assert response_body_schema is not None
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/samples/client/3_1_0_json_schema/python/test_python.sh b/samples/client/3_1_0_json_schema/python/test_python.sh
new file mode 100755
index 00000000000..4d9747bfc32
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/test_python.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+SETUP_OUT=*.egg-info
+VENV=venv
+DEACTIVE=false
+
+export LC_ALL=en_US.UTF-8
+export LANG=en_US.UTF-8
+
+# set virtualenv
+if [ -z "$VENVV" ]; then
+ python3 -m venv $VENV
+ source $VENV/bin/activate
+ DEACTIVE=true
+fi
+
+# install dependencies
+pip install tox
+# locally install the package, needed for pycharm problem checking
+python -m pip install .
+
+# run tests
+tox || exit 1
+pip install mypy
+# run mypy, static type checking
+mypy src/json_schema_api
+
+# static analysis of code
+#flake8 --show-source petstore_api/
+
+# deactivate virtualenv
+#if [ $DEACTIVE == true ]; then
+# deactivate
+#fi
diff --git a/samples/client/3_1_0_json_schema/python/tox.ini b/samples/client/3_1_0_json_schema/python/tox.ini
new file mode 100644
index 00000000000..283695fea66
--- /dev/null
+++ b/samples/client/3_1_0_json_schema/python/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+envlist = py38
+isolated_build = True
+
+[testenv]
+passenv = PYTHON_VERSION
+deps=-r{toxinidir}/test-requirements.txt
+
+commands=
+ pytest --cov=json_schema_api
diff --git a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/api_configuration.py b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/api_configuration.py
index c4f1ac7931d..ee66a00c502 100644
--- a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/api_configuration.py
+++ b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/api_configuration.py
@@ -66,7 +66,7 @@ def __init__(
"""Constructor
"""
# Authentication Settings
- self.security_scheme_info = {}
+ self.security_scheme_info: typing.Dict[str, typing.Any] = {}
self.security_index_info = {'security': 0}
# Server Info
self.server_info: ServerInfo = server_info or {
diff --git a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/schema_configuration.py b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/schema_configuration.py
index b1db504bacf..6316b7cae74 100644
--- a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/schema_configuration.py
+++ b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/configurations/schema_configuration.py
@@ -16,6 +16,7 @@
'additional_properties': 'additionalProperties',
'all_of': 'allOf',
'any_of': 'anyOf',
+ 'contains': 'contains',
'discriminator': 'discriminator',
# default omitted because it has no validation impact
'enum_value_to_name': 'enum',
diff --git a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/schemas/validation.py b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/schemas/validation.py
index 8a7be08eb69..3b602f776f8 100644
--- a/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/schemas/validation.py
+++ b/samples/client/openapi_features/nonCompliantUseDiscriminatorIfCompositionFails/python/src/this_package/schemas/validation.py
@@ -1033,6 +1033,42 @@ def validate_discriminator(
return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+ **kwargs
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'types': validate_types,
@@ -1059,5 +1095,6 @@ def validate_discriminator(
'any_of': validate_any_of,
'all_of': validate_all_of,
'not_': validate_not,
- 'discriminator': validate_discriminator
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
}
\ No newline at end of file
diff --git a/samples/client/openapi_features/security/python/src/this_package/configurations/schema_configuration.py b/samples/client/openapi_features/security/python/src/this_package/configurations/schema_configuration.py
index 2349b5701e0..22ff15cd614 100644
--- a/samples/client/openapi_features/security/python/src/this_package/configurations/schema_configuration.py
+++ b/samples/client/openapi_features/security/python/src/this_package/configurations/schema_configuration.py
@@ -16,6 +16,7 @@
'additional_properties': 'additionalProperties',
'all_of': 'allOf',
'any_of': 'anyOf',
+ 'contains': 'contains',
'discriminator': 'discriminator',
# default omitted because it has no validation impact
'enum_value_to_name': 'enum',
diff --git a/samples/client/openapi_features/security/python/src/this_package/schemas/validation.py b/samples/client/openapi_features/security/python/src/this_package/schemas/validation.py
index 4f057e65811..93fe5cbb27c 100644
--- a/samples/client/openapi_features/security/python/src/this_package/schemas/validation.py
+++ b/samples/client/openapi_features/security/python/src/this_package/schemas/validation.py
@@ -956,6 +956,41 @@ def validate_discriminator(
return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'types': validate_types,
@@ -982,5 +1017,6 @@ def validate_discriminator(
'any_of': validate_any_of,
'all_of': validate_all_of,
'not_': validate_not,
- 'discriminator': validate_discriminator
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
}
\ No newline at end of file
diff --git a/samples/client/petstore/python/src/petstore_api/configurations/schema_configuration.py b/samples/client/petstore/python/src/petstore_api/configurations/schema_configuration.py
index 337118ec54a..3ff81d2cf56 100644
--- a/samples/client/petstore/python/src/petstore_api/configurations/schema_configuration.py
+++ b/samples/client/petstore/python/src/petstore_api/configurations/schema_configuration.py
@@ -16,6 +16,7 @@
'additional_properties': 'additionalProperties',
'all_of': 'allOf',
'any_of': 'anyOf',
+ 'contains': 'contains',
'discriminator': 'discriminator',
# default omitted because it has no validation impact
'enum_value_to_name': 'enum',
diff --git a/samples/client/petstore/python/src/petstore_api/schemas/validation.py b/samples/client/petstore/python/src/petstore_api/schemas/validation.py
index ea1ff8ec02a..5cca1c42d35 100644
--- a/samples/client/petstore/python/src/petstore_api/schemas/validation.py
+++ b/samples/client/petstore/python/src/petstore_api/schemas/validation.py
@@ -956,6 +956,41 @@ def validate_discriminator(
return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'types': validate_types,
@@ -982,5 +1017,6 @@ def validate_discriminator(
'any_of': validate_any_of,
'all_of': validate_all_of,
'not_': validate_not,
- 'discriminator': validate_discriminator
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
}
\ No newline at end of file
diff --git a/src/main/java/org/openapijsonschematools/codegen/common/CodegenConstants.java b/src/main/java/org/openapijsonschematools/codegen/common/CodegenConstants.java
index 1ead49da6aa..080afef56e4 100644
--- a/src/main/java/org/openapijsonschematools/codegen/common/CodegenConstants.java
+++ b/src/main/java/org/openapijsonschematools/codegen/common/CodegenConstants.java
@@ -407,8 +407,8 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case,
"If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.";
public static final String UNSUPPORTED_V310_SPEC_MSG =
"Generation using 3.1.0 specs is in development and is not officially supported yet. " +
- "If you would like to expedite development, please consider woking on the open issues in the 3.1.0 project: https://github.com/orgs/OpenAPITools/projects/4/views/1 " +
- "and reach out to our team on Slack at https://join.slack.com/t/openapi-generator/shared_invite/zt-12jxxd7p2-XUeQM~4pzsU9x~eGLQqX2g";
+ "If you would like to expedite development, please consider woking on the open issues in the 3.1.0 project: https://github.com/orgs/openapi-json-schema-tools/projects/4 " +
+ "and reach out to our team on Discord at https://discord.gg/mHB8WEQuYQ";
public static final String ENUM_UNKNOWN_DEFAULT_CASE = "enumUnknownDefaultCase";
public static final String ENUM_UNKNOWN_DEFAULT_CASE_DESC =
"If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response." +
diff --git a/src/main/java/org/openapijsonschematools/codegen/generators/DefaultGenerator.java b/src/main/java/org/openapijsonschematools/codegen/generators/DefaultGenerator.java
index 63910aa0b06..9d53a8674a3 100644
--- a/src/main/java/org/openapijsonschematools/codegen/generators/DefaultGenerator.java
+++ b/src/main/java/org/openapijsonschematools/codegen/generators/DefaultGenerator.java
@@ -28,6 +28,7 @@
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import org.apache.commons.text.StringEscapeUtils;
@@ -445,7 +446,7 @@ public void processOpts() {
this.setEnumUnknownDefaultCase(Boolean.parseBoolean(additionalProperties
.get(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE).toString()));
}
- requiredAddPropUnsetSchema = fromSchema(new Schema(), null, null);
+ requiredAddPropUnsetSchema = fromSchema(new JsonSchema(), null, null);
}
@@ -2186,26 +2187,26 @@ protected LinkedHashSet getTypes(Schema schema) {
}
if (schema.getTypes() != null) {
// NoneFrozenDictTupleStrDecimalBoolFileBytes
- if (types.contains("null")) {
- types.add("null");
- }
- if (types.contains("object")) {
- types.add("object");
- }
- if (types.contains("array")) {
- types.add("array");
- }
- if (types.contains("string")) {
- types.add("string");
- }
- if (types.contains("number")) {
- types.add("number");
- }
- if (types.contains("integer")) {
- types.add("integer");
- }
- if (types.contains("boolean")) {
- types.add("boolean");
+ for (Object typeObj: schema.getTypes()) {
+ String type = typeObj.toString();
+ switch (type) {
+ case "null": types.add("null");
+ break;
+ case "object": types.add("object");
+ break;
+ case "array": types.add("array");
+ break;
+ case "string": types.add("string");
+ break;
+ case "number": types.add("number");
+ break;
+ case "integer": types.add("integer");
+ break;
+ case "boolean": types.add("boolean");
+ break;
+ default:
+ break;
+ }
}
// the above order used so mixins will stay the same
}
@@ -2266,8 +2267,10 @@ public CodegenSchema fromSchema(Schema p, String sourceJsonPath, String currentJ
assert generatorMetadata != null;
addImports(property.imports, getImports(property, generatorMetadata.getFeatureSet()));
}
- // TODO with 3.1.0 schemas continue processing
- return property;
+ if (p.getSpecVersion().compareTo(SpecVersion.V31) < 0) {
+ // stop processing if version is less than 3.1.0
+ return property;
+ }
}
if (p.equals(trueSchema)) {
@@ -2293,6 +2296,7 @@ public CodegenSchema fromSchema(Schema p, String sourceJsonPath, String currentJ
oneOf
not
items
+ contains
anyOf
allOf
additionalProperties
@@ -2315,6 +2319,9 @@ public CodegenSchema fromSchema(Schema p, String sourceJsonPath, String currentJ
p.getItems(), sourceJsonPath, currentJsonPath + "/items");
}
property.enumInfo = getEnumInfo(p, currentJsonPath, sourceJsonPath, property.types);
+ if (p.getContains() != null) {
+ property.contains = fromSchema(p.getContains(), sourceJsonPath, currentJsonPath + "/contains");
+ }
List anyOfs = ((Schema>) p).getAnyOf();
if (anyOfs != null && !anyOfs.isEmpty()) {
property.anyOf = getComposedProperties(anyOfs, "anyOf", sourceJsonPath, currentJsonPath);
diff --git a/src/main/java/org/openapijsonschematools/codegen/generators/PythonClientGenerator.java b/src/main/java/org/openapijsonschematools/codegen/generators/PythonClientGenerator.java
index d6676782f94..5ef3a3f8a5f 100644
--- a/src/main/java/org/openapijsonschematools/codegen/generators/PythonClientGenerator.java
+++ b/src/main/java/org/openapijsonschematools/codegen/generators/PythonClientGenerator.java
@@ -139,6 +139,7 @@ public PythonClientGenerator() {
SchemaFeature.AdditionalProperties,
SchemaFeature.AllOf,
SchemaFeature.AnyOf,
+ SchemaFeature.Contains,
SchemaFeature.Default,
SchemaFeature.Discriminator,
SchemaFeature.Enum,
diff --git a/src/main/java/org/openapijsonschematools/codegen/generators/generatormetadata/features/SchemaFeature.java b/src/main/java/org/openapijsonschematools/codegen/generators/generatormetadata/features/SchemaFeature.java
index 59857e55c76..3705e95f165 100644
--- a/src/main/java/org/openapijsonschematools/codegen/generators/generatormetadata/features/SchemaFeature.java
+++ b/src/main/java/org/openapijsonschematools/codegen/generators/generatormetadata/features/SchemaFeature.java
@@ -16,8 +16,8 @@
package org.openapijsonschematools.codegen.generators.generatormetadata.features;
-import org.openapijsonschematools.codegen.generators.generatormetadata.features.annotations.OAS3;
import org.openapijsonschematools.codegen.generators.generatormetadata.features.annotations.OAS2;
+import org.openapijsonschematools.codegen.generators.generatormetadata.features.annotations.OAS3;
/**
* Defines special circumstances handled by the generator.
@@ -40,6 +40,9 @@ public enum SchemaFeature {
@OAS3
AnyOf,
+ @OAS3
+ Contains,
+
@OAS2 @OAS3
Default,
diff --git a/src/main/java/org/openapijsonschematools/codegen/generators/openapimodels/CodegenSchema.java b/src/main/java/org/openapijsonschematools/codegen/generators/openapimodels/CodegenSchema.java
index 01e126ab7fd..6701ae86e91 100644
--- a/src/main/java/org/openapijsonschematools/codegen/generators/openapimodels/CodegenSchema.java
+++ b/src/main/java/org/openapijsonschematools/codegen/generators/openapimodels/CodegenSchema.java
@@ -238,6 +238,7 @@ private void getAllSchemas(ArrayList schemasBeforeImports, ArrayL
additionalProperties
allOf
anyOf
+ contains
items
not
oneOf
@@ -282,6 +283,9 @@ private void getAllSchemas(ArrayList schemasBeforeImports, ArrayL
schemasAfterImports.add(extraSchema);
}
}
+ if (contains != null) {
+ contains.getAllSchemas(schemasBeforeImports, schemasAfterImports, level + 1);
+ }
if (enumInfo != null) {
// write the class as a separate entity so enum values do not collide with
// json schema keywords
diff --git a/src/main/resources/python/components/schemas/schema_cls/_contains_partial.hbs b/src/main/resources/python/components/schemas/schema_cls/_contains_partial.hbs
new file mode 100644
index 00000000000..29c3235ffc2
--- /dev/null
+++ b/src/main/resources/python/components/schemas/schema_cls/_contains_partial.hbs
@@ -0,0 +1,3 @@
+{{#with contains}}
+contains: typing.Type[{{#if refInfo.refClass}}{{#if refInfo.refModule}}{{refInfo.refModule}}.{{/if}}{{refInfo.refClass}}{{else}}{{jsonPathPiece.camelCase}}{{/if}}] = dataclasses.field(default_factory=lambda: {{#if refInfo.refClass}}{{#if refInfo.refModule}}{{refInfo.refModule}}.{{/if}}{{refInfo.refClass}}{{else}}{{jsonPathPiece.camelCase}}{{/if}}) # type: ignore
+{{/with}}
diff --git a/src/main/resources/python/components/schemas/schema_cls/_schema_composed_or_anytype.hbs b/src/main/resources/python/components/schemas/schema_cls/_schema_composed_or_anytype.hbs
index 83d55bff1d9..79ea187006d 100644
--- a/src/main/resources/python/components/schemas/schema_cls/_schema_composed_or_anytype.hbs
+++ b/src/main/resources/python/components/schemas/schema_cls/_schema_composed_or_anytype.hbs
@@ -37,6 +37,9 @@ class {{jsonPathPiece.camelCase}}(
{{#if items}}
{{> components/schemas/schema_cls/_list_partial }}
{{/if}}
+{{#if contains}}
+ {{> components/schemas/schema_cls/_contains_partial }}
+{{/if}}
{{#or additionalProperties requiredProperties hasDiscriminatorWithNonEmptyMapping properties}}
{{> components/schemas/schema_cls/_dict_partial }}
{{/or}}
diff --git a/src/main/resources/python/components/schemas/schema_cls/_schema_list.hbs b/src/main/resources/python/components/schemas/schema_cls/_schema_list.hbs
index 36cd1abab5f..f06c2cbf7b8 100644
--- a/src/main/resources/python/components/schemas/schema_cls/_schema_list.hbs
+++ b/src/main/resources/python/components/schemas/schema_cls/_schema_list.hbs
@@ -15,12 +15,13 @@ class {{jsonPathPiece.camelCase}}(
{{/if}}
"""
{{/if}}
-{{#or items hasValidation}}
+{{#or items hasValidation contains}}
types: typing.FrozenSet[typing.Type] = frozenset({tuple})
{{#if hasValidation}}
{{> components/schemas/schema_cls/_validations }}
{{/if}}
{{> components/schemas/schema_cls/_list_partial }}
+ {{> components/schemas/schema_cls/_contains_partial }}
{{#if arrayOutputJsonPathPiece}}
type_to_output_cls: typing.Mapping[
typing.Type,
diff --git a/src/main/resources/python/components/schemas/schema_cls/schema_cls.hbs b/src/main/resources/python/components/schemas/schema_cls/schema_cls.hbs
index a2243db020e..0777b24c5c2 100644
--- a/src/main/resources/python/components/schemas/schema_cls/schema_cls.hbs
+++ b/src/main/resources/python/components/schemas/schema_cls/schema_cls.hbs
@@ -5,7 +5,7 @@
{{> components/schemas/schema_cls/_schema_composed_or_anytype }}
{{else}}
{{#eq types null }}
- {{#or enumInfo hasValidation items properties requiredProperties hasDiscriminatorWithNonEmptyMapping additionalProperties format}}
+ {{#or enumInfo hasValidation items properties requiredProperties hasDiscriminatorWithNonEmptyMapping additionalProperties format contains}}
{{> components/schemas/schema_cls/_schema_composed_or_anytype }}
{{else}}
{{> components/schemas/schema_cls/_schema_var_equals_cls }}
@@ -23,7 +23,7 @@
{{/or}}
{{else}}
{{#eq this "array"}}
- {{#or items hasValidation}}
+ {{#or items hasValidation contains}}
{{> components/schemas/schema_cls/_schema_list }}
{{else}}
{{> components/schemas/schema_cls/_schema_var_equals_cls }}
diff --git a/src/main/resources/python/components/schemas/schema_cls/validate/validate.hbs b/src/main/resources/python/components/schemas/schema_cls/validate/validate.hbs
index f677697e84f..4d0d14cac8a 100644
--- a/src/main/resources/python/components/schemas/schema_cls/validate/validate.hbs
+++ b/src/main/resources/python/components/schemas/schema_cls/validate/validate.hbs
@@ -40,7 +40,7 @@ def validate(
@classmethod
def validate(
{{> components/schemas/schema_cls/validate/_validate_args }}
-) -> {{#if ../arrayOutputJsonPathPiece}}{{../arrayOutputJsonPathPiece.camelCase}}{{else}}typing.Tuple[schemas.OUTPUT_BASE_TYPES]{{/if}}: ...
+) -> {{#if ../arrayOutputJsonPathPiece}}{{../arrayOutputJsonPathPiece.camelCase}}{{else}}typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]{{/if}}: ...
{{else}}
{{#eq this "object"}}
@typing.overload
@@ -139,7 +139,7 @@ def validate(
{{#if arrayOutputJsonPathPiece}}
) -> {{arrayOutputJsonPathPiece.camelCase}}:
{{else}}
-) -> typing.Tuple[schemas.OUTPUT_BASE_TYPES]:
+) -> typing.Tuple[schemas.OUTPUT_BASE_TYPES, ...]:
{{/if}}
{{/eq}}
{{/eq}}
diff --git a/src/main/resources/python/configurations/api_configuration.hbs b/src/main/resources/python/configurations/api_configuration.hbs
index 13b2d61ad38..7f7117093e8 100644
--- a/src/main/resources/python/configurations/api_configuration.hbs
+++ b/src/main/resources/python/configurations/api_configuration.hbs
@@ -190,7 +190,7 @@ class ApiConfiguration(object):
self.security_scheme_info: SecuritySchemeInfo = security_scheme_info or SecuritySchemeInfo()
self.security_index_info: SecurityIndexInfo = security_index_info or {'security': 0}
{{else}}
- self.security_scheme_info = {}
+ self.security_scheme_info: typing.Dict[str, typing.Any] = {}
self.security_index_info = {'security': 0}
{{/if}}
# Server Info
diff --git a/src/main/resources/python/configurations/schema_configuration.hbs b/src/main/resources/python/configurations/schema_configuration.hbs
index 578c12a8dab..cb152d0f145 100644
--- a/src/main/resources/python/configurations/schema_configuration.hbs
+++ b/src/main/resources/python/configurations/schema_configuration.hbs
@@ -11,6 +11,7 @@ PYTHON_KEYWORD_TO_JSON_SCHEMA_KEYWORD = {
'additional_properties': 'additionalProperties',
'all_of': 'allOf',
'any_of': 'anyOf',
+ 'contains': 'contains',
'discriminator': 'discriminator',
# default omitted because it has no validation impact
'enum_value_to_name': 'enum',
diff --git a/src/main/resources/python/schemas/validation.hbs b/src/main/resources/python/schemas/validation.hbs
index f2a45cdaddd..287f8e72f9c 100644
--- a/src/main/resources/python/schemas/validation.hbs
+++ b/src/main/resources/python/schemas/validation.hbs
@@ -1097,6 +1097,44 @@ def validate_discriminator(
return discriminated_cls._validate(arg, validation_metadata=updated_vm)
+def validate_contains(
+ arg: typing.Any,
+ contains_cls: typing.Type[SchemaValidator],
+ cls: typing.Type,
+ validation_metadata: ValidationMetadata,
+{{#if nonCompliantUseDiscriminatorIfCompositionFails}}
+ **kwargs
+{{/if}}
+) -> typing.Optional[PathToSchemasType]:
+ if not isinstance(arg, tuple):
+ return None
+ contains_cls = _get_class(contains_cls)
+ path_to_schemas: PathToSchemasType = {}
+ array_contains_item = False
+ for i, value in enumerate(arg):
+ item_validation_metadata = ValidationMetadata(
+ path_to_item=validation_metadata.path_to_item+(i,),
+ configuration=validation_metadata.configuration,
+ validated_path_to_schemas=validation_metadata.validated_path_to_schemas
+ )
+ if item_validation_metadata.validation_ran_earlier(contains_cls):
+ add_deeper_validated_schemas(item_validation_metadata, path_to_schemas)
+ return path_to_schemas
+ try:
+ other_path_to_schemas = contains_cls._validate(
+ value, validation_metadata=item_validation_metadata)
+ update(path_to_schemas, other_path_to_schemas)
+ return path_to_schemas
+ except exceptions.OpenApiException:
+ pass
+ if not array_contains_item:
+ raise exceptions.ApiValueError(
+ "Validation failed for contains keyword in class={} at path_to_item={}. No "
+ "items validated to the contains schema.".format(cls, validation_metadata.path_to_item)
+ )
+ return path_to_schemas
+
+
validator_type = typing.Callable[[typing.Any, typing.Any, type, ValidationMetadata], typing.Optional[PathToSchemasType]]
json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'types': validate_types,
@@ -1123,5 +1161,6 @@ json_schema_keyword_to_validator: typing.Mapping[str, validator_type] = {
'any_of': validate_any_of,
'all_of': validate_all_of,
'not_': validate_not,
- 'discriminator': validate_discriminator
+ 'discriminator': validate_discriminator,
+ 'contains': validate_contains
}
\ No newline at end of file
diff --git a/src/test/resources/3_1/json_schema.yaml b/src/test/resources/3_1/json_schema.yaml
new file mode 100644
index 00000000000..98f912e0a1e
--- /dev/null
+++ b/src/test/resources/3_1/json_schema.yaml
@@ -0,0 +1,33 @@
+# OAS document that uses 3.1 features:
+# 'null' type
+# type array
+openapi: 3.1.0
+info:
+ version: 1.0.0
+ title: Example
+ license:
+ name: MIT
+ identifier: MIT
+servers:
+ - url: http://api.example.xyz/v1
+paths:
+ /somePath:
+ get:
+ operationId: getSomePath
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema: {}
+components:
+ schemas:
+ ArrayContainsValue:
+ type: array
+ contains:
+ enum:
+ - 1
+ AnyTypeContainsValue:
+ contains:
+ enum:
+ - 1