Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

v2 readme adds usage notes #144

Merged
merged 8 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/generators/jaxrs-jersey.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/generators/jmeter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/generators/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/generators/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SchemaFeature> schemaFeatures) {
public Builder schemaFeatures(EnumSet<SchemaFeature> schemaFeatures) {
if (schemaFeatures != null) {
this.schemaFeatures = schemaFeatures;
} else {
Expand All @@ -540,7 +540,7 @@ public Builder schemaSupportFeatures(EnumSet<SchemaFeature> 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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public enum SchemaFeature {
@OAS3
AnyOf,

@OAS2 @OAS3
Default,

@OAS2 @OAS3
Discriminator,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FeatureSet.FeatureSetFlattened> flattened = featureSet.flatten();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public AbstractJavaCodegen() {
.securityFeatures(EnumSet.noneOf(
SecurityFeature.class
))
.excludeSchemaSupportFeatures(
.excludeSchemaFeatures(
SchemaFeature.Not
)
.includeClientModificationFeatures(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public JMeterClientCodegen() {
SecurityFeature.ApiKey,
SecurityFeature.OAuth2_Implicit
))
.excludeSchemaSupportFeatures(
.excludeSchemaFeatures(
SchemaFeature.Not
)
.includeParameterFeatures(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public KotlinClientCodegen() {
SecurityFeature.OAuth2_ClientCredentials,
SecurityFeature.OAuth2_Implicit
)
.excludeSchemaSupportFeatures(
.excludeSchemaFeatures(
SchemaFeature.Not
)
.excludeParameterFeatures(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

Expand All @@ -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:
Expand Down Expand Up @@ -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

<details>
<summary>Reason</summary>

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_)
</details>

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.

<details>
<summary>Reason</summary>

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)
</details>

### 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.

<details>
<summary>Other Details</summary>

- 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
</details>

### 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_

<details>
<summary>String + Date Example</summary>

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_
</details>

## 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 }}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading