diff --git a/docs/generators/java.md b/docs/generators/java.md index 0ff06a86214..ca9444ebed6 100644 --- a/docs/generators/java.md +++ b/docs/generators/java.md @@ -308,6 +308,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |AdditionalProperties|✗|OAS2,OAS3 |AllOf|✗|OAS2,OAS3 |AnyOf|✗|OAS3 +|Default|✗|OAS2,OAS3 |Discriminator|✓|OAS2,OAS3 |Enum|✓|OAS2,OAS3 |ExclusiveMinimum|✓|OAS2,OAS3 diff --git a/docs/generators/jaxrs-jersey.md b/docs/generators/jaxrs-jersey.md index a76e41c0cdc..bb52767f45d 100644 --- a/docs/generators/jaxrs-jersey.md +++ b/docs/generators/jaxrs-jersey.md @@ -291,6 +291,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |AdditionalProperties|✗|OAS2,OAS3 |AllOf|✗|OAS2,OAS3 |AnyOf|✗|OAS3 +|Default|✗|OAS2,OAS3 |Discriminator|✓|OAS2,OAS3 |Enum|✓|OAS2,OAS3 |ExclusiveMinimum|✓|OAS2,OAS3 diff --git a/docs/generators/jmeter.md b/docs/generators/jmeter.md index b0bc500e5f9..ba4d83d19e4 100644 --- a/docs/generators/jmeter.md +++ b/docs/generators/jmeter.md @@ -150,6 +150,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |AdditionalProperties|✗|OAS2,OAS3 |AllOf|✗|OAS2,OAS3 |AnyOf|✗|OAS3 +|Default|✗|OAS2,OAS3 |Discriminator|✓|OAS2,OAS3 |Enum|✓|OAS2,OAS3 |ExclusiveMinimum|✓|OAS2,OAS3 diff --git a/docs/generators/kotlin.md b/docs/generators/kotlin.md index b8704f58871..5ac6eb59a6c 100644 --- a/docs/generators/kotlin.md +++ b/docs/generators/kotlin.md @@ -260,6 +260,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |AdditionalProperties|✗|OAS2,OAS3 |AllOf|✗|OAS2,OAS3 |AnyOf|✗|OAS3 +|Default|✗|OAS2,OAS3 |Discriminator|✓|OAS2,OAS3 |Enum|✓|OAS2,OAS3 |ExclusiveMinimum|✓|OAS2,OAS3 diff --git a/docs/generators/python.md b/docs/generators/python.md index 2564cdb6004..830ab89d6b4 100644 --- a/docs/generators/python.md +++ b/docs/generators/python.md @@ -219,6 +219,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |AdditionalProperties|✓|OAS2,OAS3 |AllOf|✓|OAS2,OAS3 |AnyOf|✓|OAS3 +|Default|✓|OAS2,OAS3 |Discriminator|✓|OAS2,OAS3 |Enum|✓|OAS2,OAS3 |ExclusiveMinimum|✓|OAS2,OAS3 diff --git a/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/FeatureSet.java b/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/FeatureSet.java index 27014c75dae..e5b33bc1b03 100644 --- a/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/FeatureSet.java +++ b/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/FeatureSet.java @@ -524,7 +524,7 @@ public Builder excludeDocumentationFeatures(DocumentationFeature... documentatio * @param schemaFeatures the {@code schemaSupportFeature} to set * @return a reference to this Builder */ - public Builder schemaSupportFeatures(EnumSet schemaFeatures) { + public Builder schemaFeatures(EnumSet schemaFeatures) { if (schemaFeatures != null) { this.schemaFeatures = schemaFeatures; } else { @@ -540,7 +540,7 @@ public Builder schemaSupportFeatures(EnumSet schemaFeatures) { * * @return a reference to this Builder */ - public Builder includeSchemaSupportFeatures(SchemaFeature... schemaFeature) { + public Builder includeSchemaFeatures(SchemaFeature... schemaFeature) { this.schemaFeatures.addAll(Arrays.stream(schemaFeature).collect(Collectors.toList())); return this; } @@ -552,7 +552,7 @@ public Builder includeSchemaSupportFeatures(SchemaFeature... schemaFeature) { * * @return a reference to this Builder */ - public Builder excludeSchemaSupportFeatures(SchemaFeature... schemaFeature) { + public Builder excludeSchemaFeatures(SchemaFeature... schemaFeature) { this.schemaFeatures.removeAll(Arrays.stream(schemaFeature).collect(Collectors.toList())); return this; } diff --git a/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/features/SchemaFeature.java b/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/features/SchemaFeature.java index e9db253717a..fc149c5574b 100644 --- a/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/features/SchemaFeature.java +++ b/modules/openapi-json-schema-generator-core/src/main/java/org/openapijsonschematools/codegen/meta/features/SchemaFeature.java @@ -40,6 +40,9 @@ public enum SchemaFeature { @OAS3 AnyOf, + @OAS2 @OAS3 + Default, + @OAS2 @OAS3 Discriminator, diff --git a/modules/openapi-json-schema-generator-core/src/test/java/org/openapijsonschematools/codegen/meta/FeatureSetTest.java b/modules/openapi-json-schema-generator-core/src/test/java/org/openapijsonschematools/codegen/meta/FeatureSetTest.java index 94837beec7b..2c80f4e664b 100644 --- a/modules/openapi-json-schema-generator-core/src/test/java/org/openapijsonschematools/codegen/meta/FeatureSetTest.java +++ b/modules/openapi-json-schema-generator-core/src/test/java/org/openapijsonschematools/codegen/meta/FeatureSetTest.java @@ -44,7 +44,7 @@ public void flattenOnMultipleFeatures() { .includeParameterFeatures(ParameterFeature.In_Header, ParameterFeature.In_Query) .includeSecurityFeatures(SecurityFeature.HTTP_Bearer, SecurityFeature.HTTP_Basic, SecurityFeature.OAuth2_Implicit) .includeDocumentationFeatures(DocumentationFeature.ComponentSchemas) - .includeSchemaSupportFeatures(SchemaFeature.OneOf) + .includeSchemaFeatures(SchemaFeature.OneOf) .build(); List flattened = featureSet.flatten(); diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/DefaultCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/DefaultCodegen.java index b649bbbe1ac..13bd49b9cf2 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/DefaultCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/DefaultCodegen.java @@ -142,7 +142,7 @@ public class DefaultCodegen implements CodegenConfig { GlobalFeature.Info, GlobalFeature.Components ) - .includeSchemaSupportFeatures( + .includeSchemaFeatures( SchemaFeature.Discriminator, SchemaFeature.Enum, SchemaFeature.ExclusiveMaximum, SchemaFeature.ExclusiveMinimum, SchemaFeature.Format, SchemaFeature.Items, diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractJavaCodegen.java index 28aaf6183a5..af02b9468b5 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractJavaCodegen.java @@ -135,7 +135,7 @@ public AbstractJavaCodegen() { .securityFeatures(EnumSet.noneOf( SecurityFeature.class )) - .excludeSchemaSupportFeatures( + .excludeSchemaFeatures( SchemaFeature.Not ) .includeClientModificationFeatures( diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JMeterClientCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JMeterClientCodegen.java index eea7db6f2b2..7c9bcc7f092 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JMeterClientCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JMeterClientCodegen.java @@ -84,7 +84,7 @@ public JMeterClientCodegen() { SecurityFeature.ApiKey, SecurityFeature.OAuth2_Implicit )) - .excludeSchemaSupportFeatures( + .excludeSchemaFeatures( SchemaFeature.Not ) .includeParameterFeatures( diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/KotlinClientCodegen.java index 0b5f342f10c..5b7ece1f7d6 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/KotlinClientCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/KotlinClientCodegen.java @@ -147,7 +147,7 @@ public KotlinClientCodegen() { SecurityFeature.OAuth2_ClientCredentials, SecurityFeature.OAuth2_Implicit ) - .excludeSchemaSupportFeatures( + .excludeSchemaFeatures( SchemaFeature.Not ) .excludeParameterFeatures( diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/PythonClientCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/PythonClientCodegen.java index 621c88e3141..46692cbdf20 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/PythonClientCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/PythonClientCodegen.java @@ -143,20 +143,33 @@ public PythonClientCodegen() { DataTypeFeature.Byte, DataTypeFeature.Password ) - .includeSchemaSupportFeatures( + .includeSchemaFeatures( SchemaFeature.AdditionalProperties, - SchemaFeature.AllOf, SchemaFeature.AnyOf, - SchemaFeature.Discriminator, SchemaFeature.Enum, - SchemaFeature.ExclusiveMaximum, SchemaFeature.ExclusiveMinimum, - SchemaFeature.Format, SchemaFeature.Items, - SchemaFeature.MaxItems, SchemaFeature.MaxLength, - SchemaFeature.MaxProperties, SchemaFeature.Maximum, - SchemaFeature.MinItems, SchemaFeature.MinLength, - SchemaFeature.MinProperties, SchemaFeature.Minimum, - SchemaFeature.MultipleOf, SchemaFeature.Not, - SchemaFeature.Nullable, SchemaFeature.OneOf, - SchemaFeature.Pattern, SchemaFeature.Properties, - SchemaFeature.Required, SchemaFeature.Type, + SchemaFeature.AllOf, + SchemaFeature.AnyOf, + SchemaFeature.Default, + SchemaFeature.Discriminator, + SchemaFeature.Enum, + SchemaFeature.ExclusiveMaximum, + SchemaFeature.ExclusiveMinimum, + SchemaFeature.Format, + SchemaFeature.Items, + SchemaFeature.MaxItems, + SchemaFeature.MaxLength, + SchemaFeature.MaxProperties, + SchemaFeature.Maximum, + SchemaFeature.MinItems, + SchemaFeature.MinLength, + SchemaFeature.MinProperties, + SchemaFeature.Minimum, + SchemaFeature.MultipleOf, + SchemaFeature.Not, + SchemaFeature.Nullable, + SchemaFeature.OneOf, + SchemaFeature.Pattern, + SchemaFeature.Properties, + SchemaFeature.Required, + SchemaFeature.Type, SchemaFeature.UniqueItems ) .includeDocumentationFeatures( diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/README.hbs b/modules/openapi-json-schema-generator/src/main/resources/python/README.hbs index 56f80bd9d7d..fd36b419592 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/README.hbs +++ b/modules/openapi-json-schema-generator/src/main/resources/python/README.hbs @@ -15,7 +15,7 @@ This Python package is automatically generated by the [OpenAPI JSON Schema Gener For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) {{/if}} -## Requirements. +## Requirements Python {{generatorLanguageVersion}} @@ -24,7 +24,7 @@ Python {{generatorLanguageVersion}} - [Migration from Other Python Generators](migration_other_python_generators.md) -## Installation & Usage +## Installation ### pip install If the python package is hosted on a repository, you can install directly using: @@ -53,8 +53,130 @@ Then import the package: import {{{packageName}}} ``` +## 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 +- one can use isinstance to check if a instance or property is valid to a schema class + - this means that expensive validation does not need to be run twice + +
+ Reason + +To do that, some changes had to be made. Python bool and NoneType cannot be subclassed, +so to be able to meet the above design goals, I implemented BoolClass and NoneClass classes +to allow schemas to subclass them. + +In python 0 == False and 1 == True. This is a problem for json schema which is language independent. +The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) has +[explicit tests that require that 0 != False and 1 != True](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/type.json#L260-L267) +Using the above described BoolClass and NoneClasses allows those tests to pass. +- Another example of a package using it's own boolean class is [numpy's bool_](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool_) +
+ +If you need to check is True/False/None, instead use instance.is_true_()/.is_false_()/.is_none_() + +Here is the mapping from json schema types to python subclassed types: +| Json Schema Type | Python Base Class | +| ---------------- | ----------------- | +| object | frozendict.frozendict | +| array | tuple | +| string | str | +| number | decimal.Decimal | +| integer | decimal.Decimal | +| boolean | BoolClass | +| null | NoneClass | +| AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | + +### 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. This data could be stored as +1. class properties +2. in a container in the class like in a dict or in a nested class + +This data is stored in a nested class named Schema_. +Storing this data as a nested class ensures that the data is encapsulated, does not collide with +class properties, and allows for deeper complex inline definitions. + +
+ Reason + +If the data were stored at the class property level, then the keywords could collide with +type object property names. To avoid that, one could make the properties semi or fully private with +a single or double underscore prefix, but that it a lot of data to put there. +Better to separate out json schema data from type object properties at the class property level. + +If the data were stored in a container that would segregate the different data which is good. +But json schemas can be inlined to any depth. Those complex deeper schemas would need to be included. +One could define them higher in the class file and then refer to them in the dict. That would required +iterating over schemas and adding all of the inner into a collection, and then generate that collection. + +The schema definitions are already nested from the json schema definition. The easiest solution is +to use a nested json schema definition class which holds that data. That way: +- the data is separated from class properties +- deeper complicated schemas can still be stored + +So a nested class was chosen to store json schema data, this class is named Schema_. +- The [django project uses this same pattern to store model class metadata in a Meta class](https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options) +
+ +### 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, frozendict.frozendict 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 +- type hints are written for accessing values by key literals like instance["hi-there"] +- and there is a method instance.get_item_["hi-there"] which returns an schemas.Unset value if the key was not set +- required properties with valid python names are accessible with instance.SomeRequiredProp + which uses the exact key from the openapi document + - 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 te below accessors for string data: +- type string + format: See .as_date_, .as_datetime_, .as_decimal_, .as_uuid_ + +In json schema, type: number with no format validates both integers and floats, so decimal.Decimal is used to store them. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + +
+ 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, with a date accessor, instance.as_date_ +
+ ## Getting Started -Please follow the [installation procedure](#installation--usage) and then run the following: +Please follow the [installation procedure](#installation) and then run the following: {{> _helper_readme_common }} diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/_helper_readme_common.hbs b/modules/openapi-json-schema-generator/src/main/resources/python/_helper_readme_common.hbs index 1d04c0095e3..5f11c6f33f1 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/_helper_readme_common.hbs +++ b/modules/openapi-json-schema-generator/src/main/resources/python/_helper_readme_common.hbs @@ -133,10 +133,10 @@ RecursionError indicating the maximum recursion limit has been exceeded. In that Solution 1: Use specific imports for apis and models like: -- `from {{{packageName}}}.{{apiPackage}}.default_api import DefaultApi` -- `from {{{packageName}}}.{{apiPackage}}.paths.some_path import SomePath` -- `from {{{packageName}}}.paths.some_path.get import ApiForget` -- `from {{{packageName}}}.{{modelPackage}}.pet import Pet` +- tagged api: `from {{{packageName}}}.{{apiPackage}}.tags.default_api import DefaultApi` +- api for one path: `from {{{packageName}}}.{{apiPackage}}.paths.some_path import SomePath` +- api for one operation (path + verb): `from {{{packageName}}}.paths.some_path.get import ApiForget` +- single model import: `from {{{packageName}}}.{{modelPackage}}.pet import Pet` Solution 2: Before importing the package, adjust the maximum recursion limit as shown below: diff --git a/samples/openapi3/client/3_0_3_unit_test/python/README.md b/samples/openapi3/client/3_0_3_unit_test/python/README.md index 79bff42af43..3554698c011 100644 --- a/samples/openapi3/client/3_0_3_unit_test/python/README.md +++ b/samples/openapi3/client/3_0_3_unit_test/python/README.md @@ -7,7 +7,7 @@ This Python package is automatically generated by the [OpenAPI JSON Schema Gener - Package version: 1.0.0 - Build package: PythonClientCodegen -## Requirements. +## Requirements Python >=3.7 @@ -16,7 +16,7 @@ Python >=3.7 - [Migration from Other Python Generators](migration_other_python_generators.md) -## Installation & Usage +## Installation ### pip install If the python package is hosted on a repository, you can install directly using: @@ -45,9 +45,131 @@ Then import the package: import unit_test_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 +- one can use isinstance to check if a instance or property is valid to a schema class + - this means that expensive validation does not need to be run twice + +
+ Reason + +To do that, some changes had to be made. Python bool and NoneType cannot be subclassed, +so to be able to meet the above design goals, I implemented BoolClass and NoneClass classes +to allow schemas to subclass them. + +In python 0 == False and 1 == True. This is a problem for json schema which is language independent. +The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) has +[explicit tests that require that 0 != False and 1 != True](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/type.json#L260-L267) +Using the above described BoolClass and NoneClasses allows those tests to pass. +- Another example of a package using it's own boolean class is [numpy's bool_](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool_) +
+ +If you need to check is True/False/None, instead use instance.is_true_()/.is_false_()/.is_none_() + +Here is the mapping from json schema types to python subclassed types: +| Json Schema Type | Python Base Class | +| ---------------- | ----------------- | +| object | frozendict.frozendict | +| array | tuple | +| string | str | +| number | decimal.Decimal | +| integer | decimal.Decimal | +| boolean | BoolClass | +| null | NoneClass | +| AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | + +### 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. This data could be stored as +1. class properties +2. in a container in the class like in a dict or in a nested class + +This data is stored in a nested class named Schema_. +Storing this data as a nested class ensures that the data is encapsulated, does not collide with +class properties, and allows for deeper complex inline definitions. + +
+ Reason + +If the data were stored at the class property level, then the keywords could collide with +type object property names. To avoid that, one could make the properties semi or fully private with +a single or double underscore prefix, but that it a lot of data to put there. +Better to separate out json schema data from type object properties at the class property level. + +If the data were stored in a container that would segregate the different data which is good. +But json schemas can be inlined to any depth. Those complex deeper schemas would need to be included. +One could define them higher in the class file and then refer to them in the dict. That would required +iterating over schemas and adding all of the inner into a collection, and then generate that collection. + +The schema definitions are already nested from the json schema definition. The easiest solution is +to use a nested json schema definition class which holds that data. That way: +- the data is separated from class properties +- deeper complicated schemas can still be stored + +So a nested class was chosen to store json schema data, this class is named Schema_. +- The [django project uses this same pattern to store model class metadata in a Meta class](https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options) +
+ +### 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, frozendict.frozendict 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 +- type hints are written for accessing values by key literals like instance["hi-there"] +- and there is a method instance.get_item_["hi-there"] which returns an schemas.Unset value if the key was not set +- required properties with valid python names are accessible with instance.SomeRequiredProp + which uses the exact key from the openapi document + - 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 te below accessors for string data: +- type string + format: See .as_date_, .as_datetime_, .as_decimal_, .as_uuid_ + +In json schema, type: number with no format validates both integers and floats, so decimal.Decimal is used to store them. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + +
+ 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, with a date accessor, instance.as_date_ +
+ ## Getting Started -Please follow the [installation procedure](#installation--usage) and then run the following: +Please follow the [installation procedure](#installation) and then run the following: ```python import unit_test_api @@ -362,10 +484,10 @@ RecursionError indicating the maximum recursion limit has been exceeded. In that Solution 1: Use specific imports for apis and models like: -- `from unit_test_api.apis.default_api import DefaultApi` -- `from unit_test_api.apis.paths.some_path import SomePath` -- `from unit_test_api.paths.some_path.get import ApiForget` -- `from unit_test_api.components.schema.pet import Pet` +- tagged api: `from unit_test_api.apis.tags.default_api import DefaultApi` +- api for one path: `from unit_test_api.apis.paths.some_path import SomePath` +- api for one operation (path + verb): `from unit_test_api.paths.some_path.get import ApiForget` +- single model import: `from unit_test_api.components.schema.pet import Pet` Solution 2: Before importing the package, adjust the maximum recursion limit as shown below: diff --git a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/README.md b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/README.md index a9a69cb447d..b960211aa00 100644 --- a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/README.md +++ b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/README.md @@ -7,7 +7,7 @@ This Python package is automatically generated by the [OpenAPI JSON Schema Gener - Package version: 1.0.0 - Build package: PythonClientCodegen -## Requirements. +## Requirements Python >=3.7 @@ -16,7 +16,7 @@ Python >=3.7 - [Migration from Other Python Generators](migration_other_python_generators.md) -## Installation & Usage +## Installation ### pip install If the python package is hosted on a repository, you can install directly using: @@ -45,9 +45,131 @@ Then import the package: import this_package ``` +## 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 +- one can use isinstance to check if a instance or property is valid to a schema class + - this means that expensive validation does not need to be run twice + +
+ Reason + +To do that, some changes had to be made. Python bool and NoneType cannot be subclassed, +so to be able to meet the above design goals, I implemented BoolClass and NoneClass classes +to allow schemas to subclass them. + +In python 0 == False and 1 == True. This is a problem for json schema which is language independent. +The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) has +[explicit tests that require that 0 != False and 1 != True](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/type.json#L260-L267) +Using the above described BoolClass and NoneClasses allows those tests to pass. +- Another example of a package using it's own boolean class is [numpy's bool_](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool_) +
+ +If you need to check is True/False/None, instead use instance.is_true_()/.is_false_()/.is_none_() + +Here is the mapping from json schema types to python subclassed types: +| Json Schema Type | Python Base Class | +| ---------------- | ----------------- | +| object | frozendict.frozendict | +| array | tuple | +| string | str | +| number | decimal.Decimal | +| integer | decimal.Decimal | +| boolean | BoolClass | +| null | NoneClass | +| AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | + +### 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. This data could be stored as +1. class properties +2. in a container in the class like in a dict or in a nested class + +This data is stored in a nested class named Schema_. +Storing this data as a nested class ensures that the data is encapsulated, does not collide with +class properties, and allows for deeper complex inline definitions. + +
+ Reason + +If the data were stored at the class property level, then the keywords could collide with +type object property names. To avoid that, one could make the properties semi or fully private with +a single or double underscore prefix, but that it a lot of data to put there. +Better to separate out json schema data from type object properties at the class property level. + +If the data were stored in a container that would segregate the different data which is good. +But json schemas can be inlined to any depth. Those complex deeper schemas would need to be included. +One could define them higher in the class file and then refer to them in the dict. That would required +iterating over schemas and adding all of the inner into a collection, and then generate that collection. + +The schema definitions are already nested from the json schema definition. The easiest solution is +to use a nested json schema definition class which holds that data. That way: +- the data is separated from class properties +- deeper complicated schemas can still be stored + +So a nested class was chosen to store json schema data, this class is named Schema_. +- The [django project uses this same pattern to store model class metadata in a Meta class](https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options) +
+ +### 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, frozendict.frozendict 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 +- type hints are written for accessing values by key literals like instance["hi-there"] +- and there is a method instance.get_item_["hi-there"] which returns an schemas.Unset value if the key was not set +- required properties with valid python names are accessible with instance.SomeRequiredProp + which uses the exact key from the openapi document + - 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 te below accessors for string data: +- type string + format: See .as_date_, .as_datetime_, .as_decimal_, .as_uuid_ + +In json schema, type: number with no format validates both integers and floats, so decimal.Decimal is used to store them. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + +
+ 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, with a date accessor, instance.as_date_ +
+ ## Getting Started -Please follow the [installation procedure](#installation--usage) and then run the following: +Please follow the [installation procedure](#installation) and then run the following: ```python import this_package @@ -106,10 +228,10 @@ RecursionError indicating the maximum recursion limit has been exceeded. In that Solution 1: Use specific imports for apis and models like: -- `from this_package.apis.default_api import DefaultApi` -- `from this_package.apis.paths.some_path import SomePath` -- `from this_package.paths.some_path.get import ApiForget` -- `from this_package.components.schema.pet import Pet` +- tagged api: `from this_package.apis.tags.default_api import DefaultApi` +- api for one path: `from this_package.apis.paths.some_path import SomePath` +- api for one operation (path + verb): `from this_package.paths.some_path.get import ApiForget` +- single model import: `from this_package.components.schema.pet import Pet` Solution 2: Before importing the package, adjust the maximum recursion limit as shown below: diff --git a/samples/openapi3/client/features/security/python/README.md b/samples/openapi3/client/features/security/python/README.md index 433c5f8aa33..0fbc6110a82 100644 --- a/samples/openapi3/client/features/security/python/README.md +++ b/samples/openapi3/client/features/security/python/README.md @@ -7,7 +7,7 @@ This Python package is automatically generated by the [OpenAPI JSON Schema Gener - Package version: 1.0.0 - Build package: PythonClientCodegen -## Requirements. +## Requirements Python >=3.7 @@ -16,7 +16,7 @@ Python >=3.7 - [Migration from Other Python Generators](migration_other_python_generators.md) -## Installation & Usage +## Installation ### pip install If the python package is hosted on a repository, you can install directly using: @@ -45,9 +45,131 @@ Then import the package: import this_package ``` +## 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 +- one can use isinstance to check if a instance or property is valid to a schema class + - this means that expensive validation does not need to be run twice + +
+ Reason + +To do that, some changes had to be made. Python bool and NoneType cannot be subclassed, +so to be able to meet the above design goals, I implemented BoolClass and NoneClass classes +to allow schemas to subclass them. + +In python 0 == False and 1 == True. This is a problem for json schema which is language independent. +The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) has +[explicit tests that require that 0 != False and 1 != True](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/type.json#L260-L267) +Using the above described BoolClass and NoneClasses allows those tests to pass. +- Another example of a package using it's own boolean class is [numpy's bool_](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool_) +
+ +If you need to check is True/False/None, instead use instance.is_true_()/.is_false_()/.is_none_() + +Here is the mapping from json schema types to python subclassed types: +| Json Schema Type | Python Base Class | +| ---------------- | ----------------- | +| object | frozendict.frozendict | +| array | tuple | +| string | str | +| number | decimal.Decimal | +| integer | decimal.Decimal | +| boolean | BoolClass | +| null | NoneClass | +| AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | + +### 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. This data could be stored as +1. class properties +2. in a container in the class like in a dict or in a nested class + +This data is stored in a nested class named Schema_. +Storing this data as a nested class ensures that the data is encapsulated, does not collide with +class properties, and allows for deeper complex inline definitions. + +
+ Reason + +If the data were stored at the class property level, then the keywords could collide with +type object property names. To avoid that, one could make the properties semi or fully private with +a single or double underscore prefix, but that it a lot of data to put there. +Better to separate out json schema data from type object properties at the class property level. + +If the data were stored in a container that would segregate the different data which is good. +But json schemas can be inlined to any depth. Those complex deeper schemas would need to be included. +One could define them higher in the class file and then refer to them in the dict. That would required +iterating over schemas and adding all of the inner into a collection, and then generate that collection. + +The schema definitions are already nested from the json schema definition. The easiest solution is +to use a nested json schema definition class which holds that data. That way: +- the data is separated from class properties +- deeper complicated schemas can still be stored + +So a nested class was chosen to store json schema data, this class is named Schema_. +- The [django project uses this same pattern to store model class metadata in a Meta class](https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options) +
+ +### 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, frozendict.frozendict 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 +- type hints are written for accessing values by key literals like instance["hi-there"] +- and there is a method instance.get_item_["hi-there"] which returns an schemas.Unset value if the key was not set +- required properties with valid python names are accessible with instance.SomeRequiredProp + which uses the exact key from the openapi document + - 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 te below accessors for string data: +- type string + format: See .as_date_, .as_datetime_, .as_decimal_, .as_uuid_ + +In json schema, type: number with no format validates both integers and floats, so decimal.Decimal is used to store them. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + +
+ 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, with a date accessor, instance.as_date_ +
+ ## Getting Started -Please follow the [installation procedure](#installation--usage) and then run the following: +Please follow the [installation procedure](#installation) and then run the following: ```python import this_package @@ -117,10 +239,10 @@ RecursionError indicating the maximum recursion limit has been exceeded. In that Solution 1: Use specific imports for apis and models like: -- `from this_package.apis.default_api import DefaultApi` -- `from this_package.apis.paths.some_path import SomePath` -- `from this_package.paths.some_path.get import ApiForget` -- `from this_package.components.schema.pet import Pet` +- tagged api: `from this_package.apis.tags.default_api import DefaultApi` +- api for one path: `from this_package.apis.paths.some_path import SomePath` +- api for one operation (path + verb): `from this_package.paths.some_path.get import ApiForget` +- single model import: `from this_package.components.schema.pet import Pet` Solution 2: Before importing the package, adjust the maximum recursion limit as shown below: diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index 5d6084cdee9..f1a57b64489 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -7,7 +7,7 @@ This Python package is automatically generated by the [OpenAPI JSON Schema Gener - Package version: 1.0.0 - Build package: PythonClientCodegen -## Requirements. +## Requirements Python >=3.7 @@ -16,7 +16,7 @@ Python >=3.7 - [Migration from Other Python Generators](migration_other_python_generators.md) -## Installation & Usage +## Installation ### pip install If the python package is hosted on a repository, you can install directly using: @@ -45,9 +45,131 @@ Then import the package: import petstore_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 +- one can use isinstance to check if a instance or property is valid to a schema class + - this means that expensive validation does not need to be run twice + +
+ Reason + +To do that, some changes had to be made. Python bool and NoneType cannot be subclassed, +so to be able to meet the above design goals, I implemented BoolClass and NoneClass classes +to allow schemas to subclass them. + +In python 0 == False and 1 == True. This is a problem for json schema which is language independent. +The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) has +[explicit tests that require that 0 != False and 1 != True](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/type.json#L260-L267) +Using the above described BoolClass and NoneClasses allows those tests to pass. +- Another example of a package using it's own boolean class is [numpy's bool_](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool_) +
+ +If you need to check is True/False/None, instead use instance.is_true_()/.is_false_()/.is_none_() + +Here is the mapping from json schema types to python subclassed types: +| Json Schema Type | Python Base Class | +| ---------------- | ----------------- | +| object | frozendict.frozendict | +| array | tuple | +| string | str | +| number | decimal.Decimal | +| integer | decimal.Decimal | +| boolean | BoolClass | +| null | NoneClass | +| AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | + +### 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. This data could be stored as +1. class properties +2. in a container in the class like in a dict or in a nested class + +This data is stored in a nested class named Schema_. +Storing this data as a nested class ensures that the data is encapsulated, does not collide with +class properties, and allows for deeper complex inline definitions. + +
+ Reason + +If the data were stored at the class property level, then the keywords could collide with +type object property names. To avoid that, one could make the properties semi or fully private with +a single or double underscore prefix, but that it a lot of data to put there. +Better to separate out json schema data from type object properties at the class property level. + +If the data were stored in a container that would segregate the different data which is good. +But json schemas can be inlined to any depth. Those complex deeper schemas would need to be included. +One could define them higher in the class file and then refer to them in the dict. That would required +iterating over schemas and adding all of the inner into a collection, and then generate that collection. + +The schema definitions are already nested from the json schema definition. The easiest solution is +to use a nested json schema definition class which holds that data. That way: +- the data is separated from class properties +- deeper complicated schemas can still be stored + +So a nested class was chosen to store json schema data, this class is named Schema_. +- The [django project uses this same pattern to store model class metadata in a Meta class](https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options) +
+ +### 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, frozendict.frozendict 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 +- type hints are written for accessing values by key literals like instance["hi-there"] +- and there is a method instance.get_item_["hi-there"] which returns an schemas.Unset value if the key was not set +- required properties with valid python names are accessible with instance.SomeRequiredProp + which uses the exact key from the openapi document + - 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 te below accessors for string data: +- type string + format: See .as_date_, .as_datetime_, .as_decimal_, .as_uuid_ + +In json schema, type: number with no format validates both integers and floats, so decimal.Decimal is used to store them. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + +
+ 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, with a date accessor, instance.as_date_ +
+ ## Getting Started -Please follow the [installation procedure](#installation--usage) and then run the following: +Please follow the [installation procedure](#installation) and then run the following: ```python import petstore_api @@ -346,10 +468,10 @@ RecursionError indicating the maximum recursion limit has been exceeded. In that Solution 1: Use specific imports for apis and models like: -- `from petstore_api.apis.default_api import DefaultApi` -- `from petstore_api.apis.paths.some_path import SomePath` -- `from petstore_api.paths.some_path.get import ApiForget` -- `from petstore_api.components.schema.pet import Pet` +- tagged api: `from petstore_api.apis.tags.default_api import DefaultApi` +- api for one path: `from petstore_api.apis.paths.some_path import SomePath` +- api for one operation (path + verb): `from petstore_api.paths.some_path.get import ApiForget` +- single model import: `from petstore_api.components.schema.pet import Pet` Solution 2: Before importing the package, adjust the maximum recursion limit as shown below: