From 8474e0d3ffa28be0bc49411780b568e9cc99a92c Mon Sep 17 00:00:00 2001 From: Justin Black Date: Wed, 5 Apr 2023 23:36:52 -0700 Subject: [PATCH 1/8] Adds usage section --- .../src/main/resources/python/README.hbs | 61 ++++++++++++++++++- .../python/.openapi-generator/VERSION | 2 +- .../openapi3/client/petstore/python/README.md | 61 ++++++++++++++++++- 3 files changed, 119 insertions(+), 5 deletions(-) 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..df991b77e7b 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,6 +53,63 @@ Then import the package: import {{{packageName}}} ``` +## Usage Notes + +### Validation, Immutability, bool + None +This generator seeks to validate data nd 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 + +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] | + +### Validation + Formatting +N schemas can be validated on the same payload. +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_ +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. +To allow storage of both data type, decimal.Decimal is used to store type: number + type: integer data. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + + ## Getting Started Please follow the [installation procedure](#installation--usage) and then run the following: diff --git a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION index 359a5b952d4..717311e32e3 100644 --- a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION +++ b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION @@ -1 +1 @@ -2.0.0 \ No newline at end of file +unset \ No newline at end of file diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index 5d6084cdee9..daaf9dedf4d 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,6 +45,63 @@ Then import the package: import petstore_api ``` +## Usage Notes + +### Validation, Immutability, bool + None +This generator seeks to validate data nd 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 + +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] | + +### Validation + Formatting +N schemas can be validated on the same payload. +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_ +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. +To allow storage of both data type, decimal.Decimal is used to store type: number + type: integer data. +See te below accessors for number data: +- type number + format: See .as_float_, .as_int_ + + ## Getting Started Please follow the [installation procedure](#installation--usage) and then run the following: From 2551aa02b0a6c1b64208296a239c7366cad29d9b Mon Sep 17 00:00:00 2001 From: Justin Black Date: Thu, 6 Apr 2023 00:00:40 -0700 Subject: [PATCH 2/8] Adds Json Schema Type Object section --- .../src/main/resources/python/README.hbs | 22 +++++++++++++++++-- .../openapi3/client/petstore/python/README.md | 22 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) 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 df991b77e7b..35d1104fd4b 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 @@ -55,7 +55,7 @@ import {{{packageName}}} ## Usage Notes -### Validation, Immutability, bool + None +### Validation, Immutability, and Data Type This generator seeks to validate data nd 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 @@ -87,7 +87,25 @@ Here is the mapping from json schema types to python subclassed types: | null | NoneClass | | AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | -### Validation + Formatting +### 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" +- " " +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. +- optional properties which were not set will not exist in the instance +- None is only allowed in as a variable 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 Dats Storage N schemas can be validated on the same payload. For example the string payload '2023-12-20' is validates to both of these schemas: 1. string only diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index daaf9dedf4d..130c51a3bbd 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -47,7 +47,7 @@ import petstore_api ## Usage Notes -### Validation, Immutability, bool + None +### Validation, Immutability, and Data Type This generator seeks to validate data nd 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 @@ -79,7 +79,25 @@ Here is the mapping from json schema types to python subclassed types: | null | NoneClass | | AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | -### Validation + Formatting +### 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" +- " " +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. +- optional properties which were not set will not exist in the instance +- None is only allowed in as a variable 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 Dats Storage N schemas can be validated on the same payload. For example the string payload '2023-12-20' is validates to both of these schemas: 1. string only From 4adf360151eedbb07cf54e662c44c2a5206227ff Mon Sep 17 00:00:00 2001 From: Justin Black Date: Thu, 6 Apr 2023 00:08:17 -0700 Subject: [PATCH 3/8] Adds todos --- .../src/main/resources/python/README.hbs | 4 ++++ 1 file changed, 4 insertions(+) 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 35d1104fd4b..628460a35f8 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 @@ -87,6 +87,10 @@ Here is the mapping from json schema types to python subclassed types: | null | NoneClass | | AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | +TODO add binary + file types and mention why + +TODO add section on Schema_ storage and mention why + ### 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 From 1048b03206e36255a3bae79e853e054c1ff82ba5 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Thu, 6 Apr 2023 00:13:31 -0700 Subject: [PATCH 4/8] Adds another invalid variable --- .../src/main/resources/python/README.hbs | 1 + 1 file changed, 1 insertion(+) 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 628460a35f8..7df7c54bfc2 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 @@ -99,6 +99,7 @@ invalid python variable names. Names like: - "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. - optional properties which were not set will not exist in the instance From b11788137db5ea591c32e9bebca0c5d28b6d078e Mon Sep 17 00:00:00 2001 From: Justin Black Date: Thu, 6 Apr 2023 23:33:01 -0700 Subject: [PATCH 5/8] Adds details sections to readme --- .../codegen/meta/FeatureSet.java | 6 +- .../codegen/meta/features/SchemaFeature.java | 3 + .../codegen/meta/FeatureSetTest.java | 2 +- .../codegen/DefaultCodegen.java | 2 +- .../languages/AbstractJavaCodegen.java | 2 +- .../languages/JMeterClientCodegen.java | 2 +- .../languages/KotlinClientCodegen.java | 2 +- .../languages/PythonClientCodegen.java | 39 +++++++---- .../src/main/resources/python/README.hbs | 64 ++++++++++++++---- .../openapi3/client/petstore/python/README.md | 65 +++++++++++++++---- 10 files changed, 142 insertions(+), 45 deletions(-) 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 7df7c54bfc2..67244434bec 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 @@ -54,15 +54,17 @@ import {{{packageName}}} ``` ## Usage Notes - ### Validation, Immutability, and Data Type -This generator seeks to validate data nd return back an immutable instance containing the data +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. @@ -72,6 +74,7 @@ The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test [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_() @@ -87,9 +90,36 @@ Here is the mapping from json schema types to python subclassed types: | null | NoneClass | | AnyType (unset) | typing.Union[frozendict.frozendict, tuple, str, decimal.Decimal, BoolClass, NoneClass] | -TODO add binary + file types and mention why +### 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 + +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. -TODO add section on Schema_ storage and mention why +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. @@ -102,6 +132,10 @@ invalid python variable names. Names like: - "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 variable if type: "null" was included or nullable: true was set - type hints are written for accessing values by key literals like instance["hi-there"] @@ -109,9 +143,20 @@ This means that one can use normal dict methods on instances of these classes. - 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 Dats Storage +### 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 immutable class. +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 ``` @@ -124,14 +169,7 @@ For example the string payload '2023-12-20' is validates to both of these schema ``` 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_ -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. -To allow storage of both data type, decimal.Decimal is used to store type: number + type: integer data. -See te below accessors for number data: -- type number + format: See .as_float_, .as_int_ - +
## Getting Started diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index 130c51a3bbd..728eb7e3264 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -46,15 +46,17 @@ import petstore_api ``` ## Usage Notes - ### Validation, Immutability, and Data Type -This generator seeks to validate data nd return back an immutable instance containing the data +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. @@ -64,6 +66,7 @@ The [json schema test suite](https://github.com/json-schema-org/JSON-Schema-Test [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_() @@ -79,6 +82,37 @@ Here is the mapping from json schema types to python subclassed types: | 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 + +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 @@ -87,8 +121,13 @@ invalid python variable names. Names like: - "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 variable if type: "null" was included or nullable: true was set - type hints are written for accessing values by key literals like instance["hi-there"] @@ -96,9 +135,20 @@ This means that one can use normal dict methods on instances of these classes. - 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 Dats Storage +### 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 immutable class. +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 ``` @@ -111,14 +161,7 @@ For example the string payload '2023-12-20' is validates to both of these schema ``` 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_ -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. -To allow storage of both data type, decimal.Decimal is used to store type: number + type: integer data. -See te below accessors for number data: -- type number + format: See .as_float_, .as_int_ - +
## Getting Started From e2519412411dfbb1d960b7097bc188e9a91e3a85 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Thu, 6 Apr 2023 23:40:36 -0700 Subject: [PATCH 6/8] Small readme improvements --- .../src/main/resources/python/README.hbs | 3 +++ samples/openapi3/client/petstore/python/README.md | 3 +++ 2 files changed, 6 insertions(+) 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 67244434bec..0ba0c32ace3 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 @@ -96,6 +96,7 @@ 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. @@ -130,6 +131,7 @@ invalid python variable names. Names like: - "@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. @@ -157,6 +159,7 @@ See te below accessors for number data:
String + Date Example + For example the string payload '2023-12-20' is validates to both of these schemas: 1. string only ``` diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index 728eb7e3264..208b34e8d58 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -88,6 +88,7 @@ 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. @@ -122,6 +123,7 @@ invalid python variable names. Names like: - "@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. @@ -149,6 +151,7 @@ See te below accessors for number data:
String + Date Example + For example the string payload '2023-12-20' is validates to both of these schemas: 1. string only ``` From 1767ffa95e91c8656eea6242d53e263f77c9adf4 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 7 Apr 2023 09:21:07 -0700 Subject: [PATCH 7/8] Samples updated --- .../src/main/resources/python/README.hbs | 7 +- .../python/_helper_readme_common.hbs | 8 +- .../client/3_0_3_unit_test/python/README.md | 136 +++++++++++++++++- .../python/README.md | 136 +++++++++++++++++- .../client/features/security/python/README.md | 136 +++++++++++++++++- .../python/.openapi-generator/VERSION | 2 +- .../openapi3/client/petstore/python/README.md | 15 +- 7 files changed, 404 insertions(+), 36 deletions(-) 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 0ba0c32ace3..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 @@ -139,7 +139,7 @@ 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 variable if type: "null" was included or nullable: true was set +- 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 @@ -149,7 +149,8 @@ This means that one can use normal dict methods on instances of these classes. ### 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 immutable class. +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_ @@ -176,6 +177,6 @@ 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/.openapi-generator/VERSION b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION index 717311e32e3..359a5b952d4 100644 --- a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION +++ b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION @@ -1 +1 @@ -unset \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index 208b34e8d58..f1a57b64489 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -131,7 +131,7 @@ 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 variable if type: "null" was included or nullable: true was set +- 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 @@ -141,7 +141,8 @@ This means that one can use normal dict methods on instances of these classes. ### 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 immutable class. +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_ @@ -168,7 +169,7 @@ 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 @@ -467,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: From 9ee1a6d800693c00c7ae38586c128e74a915c70a Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 7 Apr 2023 09:25:15 -0700 Subject: [PATCH 8/8] Adds Schema default feature --- docs/generators/java.md | 1 + docs/generators/jaxrs-jersey.md | 1 + docs/generators/jmeter.md | 1 + docs/generators/kotlin.md | 1 + docs/generators/python.md | 1 + 5 files changed, 5 insertions(+) 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