diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java index bd6892a4d144..85a258a09b65 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java @@ -139,7 +139,18 @@ public PythonFastAPIServerCodegen() { .defaultValue(DEFAULT_SOURCE_FOLDER)); cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).") .defaultValue(DEFAULT_PACKAGE_NAME.concat(".impl"))); - + // option to change how we process + set the data in the 'additionalProperties' keyword. + CliOption disallowAdditionalPropertiesIfNotPresentOpt = CliOption.newBoolean( + CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, + CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT_DESC).defaultValue(Boolean.TRUE.toString()); + Map disallowAdditionalPropertiesIfNotPresentOpts = new HashMap<>(); + disallowAdditionalPropertiesIfNotPresentOpts.put("false", + "The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications."); + disallowAdditionalPropertiesIfNotPresentOpts.put("true", + "Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default."); + disallowAdditionalPropertiesIfNotPresentOpt.setEnum(disallowAdditionalPropertiesIfNotPresentOpts); + cliOptions.add(disallowAdditionalPropertiesIfNotPresentOpt); + this.setDisallowAdditionalPropertiesIfNotPresent(true); } @Override @@ -322,5 +333,7 @@ public void postProcess() { } @Override - public String generatorLanguageVersion() { return "3.7"; }; + public String generatorLanguageVersion() { + return "3.7"; + } } diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model.mustache index 3be022b17e32..84792dde1957 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/model.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model.mustache @@ -1,76 +1,14 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 - -import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 - -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 -{{#models}} -{{#model}} -{{#pyImports}} -{{import}} -{{/pyImports}} -{{/model}} -{{/models}} - +{{>partial_header}} {{#models}} {{#model}} -class {{classname}}(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - - Do not edit the class manually. - - {{classname}} - a model defined in OpenAPI - - {{#vars}} - {{name}}: The {{name}} of this {{classname}}{{^required}} [Optional]{{/required}}. - {{/vars}} - """ - -{{#vars}} - {{name}}: {{#required}}{{>model_field_type}}{{/required}}{{^required}}Optional[{{>model_field_type}}]{{/required}} = Field(alias="{{baseName}}"{{^required}}, default=None{{/required}}) -{{/vars}} -{{#vars}} -{{#maximum}} - - @validator("{{name}}") - def {{name}}_max(cls, value): - assert value <= {{maximum}} - return value -{{/maximum}} -{{#minimum}} - - @validator("{{name}}") - def {{name}}_min(cls, value): - assert value >= {{minimum}} - return value -{{/minimum}} -{{#minLength}} - - @validator("{{name}}") - def {{name}}_min_length(cls, value): - assert len(value) >= {{minLength}} - return value -{{/minLength}} -{{#maxLength}} - - @validator("{{name}}") - def {{name}}_max_length(cls, value): - assert len(value) <= {{maxLength}} - return value -{{/maxLength}} -{{#pattern}} - - @validator("{{name}}") - def {{name}}_pattern(cls, value): - assert value is not None and re.match(r"{{pattern}}", value) - return value -{{/pattern}} -{{/vars}} +{{#isEnum}} +{{>model_enum}} +{{/isEnum}} +{{^isEnum}} +{{#oneOf}}{{#-first}}{{>model_oneof}}{{/-first}}{{/oneOf}}{{^oneOf}}{{#anyOf}}{{#-first}}{{>model_anyof}}{{/-first}}{{/anyOf}}{{^anyOf}}{{>model_generic}}{{/anyOf}}{{/oneOf}} +{{/isEnum}} {{/model}} -{{/models}} - -{{classname}}.update_forward_refs() +{{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_anyof.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_anyof.mustache new file mode 100644 index 000000000000..74a007fbc192 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_anyof.mustache @@ -0,0 +1,180 @@ +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} +{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} +{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} +{{#vendorExtensions.x-py-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-model-imports}} +from typing import Union, Any, List, TYPE_CHECKING +from pydantic import StrictStr, Field + +{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS = [{{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}}] + +class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): + """ + {{{description}}}{{^description}}{{{classname}}}{{/description}} + """ + +{{#composedSchemas.anyOf}} + # data type: {{{dataType}}} + {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} +{{/composedSchemas.anyOf}} + if TYPE_CHECKING: + actual_instance: Union[{{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}] + else: + actual_instance: Any + any_of_schemas: List[str] = Field({{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS, const=True) + + class Config: + validate_assignment = True +{{#discriminator}} + + discriminator_value_class_map = { +{{#children}} + '{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}},{{/-last}} +{{/children}} + } +{{/discriminator}} + + def __init__(self, *args, **kwargs): + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @validator('actual_instance') + def actual_instance_must_validate_anyof(cls, v): + {{#isNullable}} + if v is None: + return v + + {{/isNullable}} + instance = {{{classname}}}.construct() + error_messages = [] + {{#composedSchemas.anyOf}} + # validate data type: {{{dataType}}} + {{#isContainer}} + try: + instance.{{vendorExtensions.x-py-name}} = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isContainer}} + {{^isContainer}} + {{#isPrimitiveType}} + try: + instance.{{vendorExtensions.x-py-name}} = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{^isPrimitiveType}} + if not isinstance(v, {{{dataType}}}): + error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`") + else: + return v + + {{/isPrimitiveType}} + {{/isContainer}} + {{/composedSchemas.anyOf}} + if error_messages: + # no match + raise ValueError("No match found when setting the actual_instance in {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: dict) -> {{{classname}}}: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> {{{classname}}}: + """Returns the object represented by the json string""" + instance = {{{classname}}}.construct() + {{#isNullable}} + if json_str is None: + return instance + + {{/isNullable}} + error_messages = [] + {{#composedSchemas.anyOf}} + {{#isContainer}} + # deserialize data into {{{dataType}}} + try: + # validation + instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{vendorExtensions.x-py-name}} + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isContainer}} + {{^isContainer}} + {{#isPrimitiveType}} + # deserialize data into {{{dataType}}} + try: + # validation + instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{vendorExtensions.x-py-name}} + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{^isPrimitiveType}} + # {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} + try: + instance.actual_instance = {{{dataType}}}.from_json(json_str) + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{/isContainer}} + {{/composedSchemas.anyOf}} + + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> dict: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_dict() + else: + return json.dumps(self.actual_instance) + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.dict()) + +{{#vendorExtensions.x-py-postponed-model-imports.size}} +{{#vendorExtensions.x-py-postponed-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-postponed-model-imports}} +{{classname}}.update_forward_refs() +{{/vendorExtensions.x-py-postponed-model-imports.size}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_doc.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_doc.mustache new file mode 100644 index 000000000000..dd54470b3be5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_doc.mustache @@ -0,0 +1,33 @@ +{{#models}}{{#model}}# {{classname}} + +{{#description}}{{&description}} +{{/description}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} + +{{^isEnum}} +## Example + +```python +from {{modelPackage}}.{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}} import {{classname}} + +# TODO update the JSON string below +json = "{}" +# create an instance of {{classname}} from a JSON string +{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.from_json(json) +# print the JSON string representation of the object +print {{classname}}.to_json() + +# convert the object into a dict +{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.to_dict() +# create an instance of {{classname}} from a dict +{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_form_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}.from_dict({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict) +``` +{{/isEnum}} +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_enum.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_enum.mustache new file mode 100644 index 000000000000..83f0552c7812 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_enum.mustache @@ -0,0 +1,36 @@ +import json +import pprint +import re # noqa: F401 +from aenum import Enum, no_arg +{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} +{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} +{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} + + +class {{classname}}({{vendorExtensions.x-py-enum-type}}, Enum): + """ + {{{description}}}{{^description}}{{{classname}}}{{/description}} + """ + + """ + allowed enum values + """ +{{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{value}}} + {{/enumVars}} + + @classmethod + def from_json(cls, json_str: str) -> {{{classname}}}: + """Create an instance of {{classname}} from a JSON string""" + return {{classname}}(json.loads(json_str)) + + {{#defaultValue}} + + # + @classmethod + def _missing_value_(cls, value): + if value is no_arg: + return cls.{{{.}}} + {{/defaultValue}} +{{/allowableValues}} diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_field_type.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_field_type.mustache deleted file mode 100644 index a27dc7e87fe6..000000000000 --- a/modules/openapi-generator/src/main/resources/python-fastapi/model_field_type.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#isEmail}}EmailStr{{/isEmail}}{{#isUri}}AnyUrl{{/isUri}}{{#isFreeFormObject}}Dict[str, Any]{{/isFreeFormObject}}{{^isEmail}}{{^isUri}}{{^isFreeFormObject}}{{dataType}}{{/isFreeFormObject}}{{/isUri}}{{/isEmail}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_generic.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_generic.mustache new file mode 100644 index 000000000000..1355ddbf20bb --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_generic.mustache @@ -0,0 +1,361 @@ +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} +{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} +{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} +{{#vendorExtensions.x-py-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-model-imports}} + +class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): + """ + {{{description}}}{{^description}}{{{classname}}}{{/description}} + """ +{{#vars}} + {{name}}: {{{vendorExtensions.x-py-typing}}} +{{/vars}} +{{#isAdditionalPropertiesTrue}} + additional_properties: Dict[str, Any] = {} +{{/isAdditionalPropertiesTrue}} + __properties = [{{#allVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/allVars}}] +{{#vars}} + {{#vendorExtensions.x-regex}} + + @validator('{{{name}}}') + def {{{name}}}_validate_regular_expression(cls, value): + """Validates the regular expression""" + {{^required}} + if value is None: + return value + + {{/required}} + {{#required}} + {{#isNullable}} + if value is None: + return value + + {{/isNullable}} + {{/required}} + if not re.match(r"{{{.}}}", value{{#vendorExtensions.x-modifiers}} ,re.{{{.}}}{{/vendorExtensions.x-modifiers}}): + raise ValueError(r"must validate the regular expression {{{vendorExtensions.x-pattern}}}") + return value + {{/vendorExtensions.x-regex}} + {{#isEnum}} + + @validator('{{{name}}}') + def {{{name}}}_validate_enum(cls, value): + """Validates the enum""" + {{^required}} + if value is None: + return value + + {{/required}} + {{#required}} + {{#isNullable}} + if value is None: + return value + + {{/isNullable}} + {{/required}} + {{#isArray}} + for i in value: + if i not in ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}): + raise ValueError("each list item must be one of ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") + {{/isArray}} + {{^isArray}} + if value not in ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}): + raise ValueError("must be one of enum values ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") + {{/isArray}} + return value + {{/isEnum}} +{{/vars}} + + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True + +{{#hasChildren}} +{{#discriminator}} + # JSON field name that stores the object type + __discriminator_property_name = '{{discriminator.propertyBaseName}}' + + {{#mappedModels}} + {{#-first}} + # discriminator mappings + __discriminator_value_class_map = { + {{/-first}} + '{{{mappingName}}}': '{{{modelName}}}'{{^-last}},{{/-last}} + {{#-last}} + } + + @classmethod + def get_discriminator_value(cls, obj: dict) -> str: + """Returns the discriminator value (object type) of the data""" + discriminator_value = obj[cls.__discriminator_property_name] + if discriminator_value: + return cls.__discriminator_value_class_map.get(discriminator_value) + else: + return None + {{/-last}} + {{/mappedModels}} + +{{/discriminator}} +{{/hasChildren}} + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> {{^hasChildren}}{{{classname}}}{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union({{#children}}{{{classname}}}{{^-last}}, {{/-last}}{{/children}}){{/discriminator}}{{^discriminator}}{{{classname}}}{{/discriminator}}{{/hasChildren}}: + """Create an instance of {{{classname}}} from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + {{#vendorExtensions.x-py-readonly}} + "{{{.}}}", + {{/vendorExtensions.x-py-readonly}} + {{#isAdditionalPropertiesTrue}} + "additional_properties" + {{/isAdditionalPropertiesTrue}} + }, + exclude_none=True) + {{#allVars}} + {{#isContainer}} + {{#isArray}} + {{#items.isArray}} + {{^items.items.isPrimitiveType}} + # override the default output from pydantic by calling `to_dict()` of each item in {{{name}}} (list of list) + _items = [] + if self.{{{name}}}: + for _item in self.{{{name}}}: + if _item: + _items.append( + [_inner_item.to_dict() for _inner_item in _item if _inner_item is not None] + ) + _dict['{{{baseName}}}'] = _items + {{/items.items.isPrimitiveType}} + {{/items.isArray}} + {{^items.isArray}} + {{^items.isPrimitiveType}} + {{^items.isEnumOrRef}} + # override the default output from pydantic by calling `to_dict()` of each item in {{{name}}} (list) + _items = [] + if self.{{{name}}}: + for _item in self.{{{name}}}: + if _item: + _items.append(_item.to_dict()) + _dict['{{{baseName}}}'] = _items + {{/items.isEnumOrRef}} + {{/items.isPrimitiveType}} + {{/items.isArray}} + {{/isArray}} + {{#isMap}} + {{#items.isArray}} + # override the default output from pydantic by calling `to_dict()` of each value in {{{name}}} (dict of array) + _field_dict_of_array = {} + if self.{{{name}}}: + for _key in self.{{{name}}}: + if self.{{{name}}}[_key]: + _field_dict_of_array[_key] = [ + _item.to_dict() for _item in self.{{{name}}}[_key] + ] + _dict['{{{baseName}}}'] = _field_dict_of_array + {{/items.isArray}} + {{^items.isArray}} + {{^items.isPrimitiveType}} + {{^items.isEnumOrRef}} + # override the default output from pydantic by calling `to_dict()` of each value in {{{name}}} (dict) + _field_dict = {} + if self.{{{name}}}: + for _key in self.{{{name}}}: + if self.{{{name}}}[_key]: + _field_dict[_key] = self.{{{name}}}[_key].to_dict() + _dict['{{{baseName}}}'] = _field_dict + {{/items.isEnumOrRef}} + {{/items.isPrimitiveType}} + {{/items.isArray}} + {{/isMap}} + {{/isContainer}} + {{^isContainer}} + {{^isPrimitiveType}} + {{^isEnumOrRef}} + # override the default output from pydantic by calling `to_dict()` of {{{name}}} + if self.{{{name}}}: + _dict['{{{baseName}}}'] = self.{{{name}}}.to_dict() + {{/isEnumOrRef}} + {{/isPrimitiveType}} + {{/isContainer}} + {{/allVars}} + {{#isAdditionalPropertiesTrue}} + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + {{/isAdditionalPropertiesTrue}} + {{#allVars}} + {{#isNullable}} + # set to None if {{{name}}} (nullable) is None + # and __fields_set__ contains the field + if self.{{name}} is None and "{{{name}}}" in self.__fields_set__: + _dict['{{{baseName}}}'] = None + + {{/isNullable}} + {{/allVars}} + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> {{^hasChildren}}{{{classname}}}{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union({{#children}}{{{classname}}}{{^-last}}, {{/-last}}{{/children}}){{/discriminator}}{{^discriminator}}{{{classname}}}{{/discriminator}}{{/hasChildren}}: + """Create an instance of {{{classname}}} from a dict""" + {{#hasChildren}} + {{#discriminator}} + # look up the object type based on discriminator mapping + object_type = cls.get_discriminator_value(obj) + if object_type: + klass = globals()[object_type] + return klass.from_dict(obj) + else: + raise ValueError("{{{classname}}} failed to lookup discriminator value from " + + json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + + ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) + {{/discriminator}} + {{/hasChildren}} + {{^hasChildren}} + if obj is None: + return None + + if not isinstance(obj, dict): + return {{{classname}}}.parse_obj(obj) + + {{#disallowAdditionalPropertiesIfNotPresent}} + {{^isAdditionalPropertiesTrue}} + # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in {{classname}}) in the input: " + obj) + + {{/isAdditionalPropertiesTrue}} + {{/disallowAdditionalPropertiesIfNotPresent}} + _obj = {{{classname}}}.parse_obj({ + {{#allVars}} + {{#isContainer}} + {{#isArray}} + {{#items.isArray}} + {{#items.items.isPrimitiveType}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/items.items.isPrimitiveType}} + {{^items.items.isPrimitiveType}} + "{{{name}}}": [ + [{{{items.items.dataType}}}.from_dict(_inner_item) for _inner_item in _item] + for _item in obj.get("{{{baseName}}}") + ] if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}} + {{/items.items.isPrimitiveType}} + {{/items.isArray}} + {{^items.isArray}} + {{^items.isPrimitiveType}} + {{#items.isEnumOrRef}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/items.isEnumOrRef}} + {{^items.isEnumOrRef}} + "{{{name}}}": [{{{items.dataType}}}.from_dict(_item) for _item in obj.get("{{{baseName}}}")] if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}} + {{/items.isEnumOrRef}} + {{/items.isPrimitiveType}} + {{#items.isPrimitiveType}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/items.isPrimitiveType}} + {{/items.isArray}} + {{/isArray}} + {{#isMap}} + {{^items.isPrimitiveType}} + {{^items.isEnumOrRef}} + {{#items.isContainer}} + {{#items.isMap}} + "{{{name}}}": dict( + (_k, dict( + (_ik, {{{items.items.dataType}}}.from_dict(_iv)) + for _ik, _iv in _v.items() + ) + if _v is not None + else None + ) + for _k, _v in obj.get("{{{baseName}}}").items() + ) + if obj.get("{{{baseName}}}") is not None + else None{{^-last}},{{/-last}} + {{/items.isMap}} + {{#items.isArray}} + "{{{name}}}": dict( + (_k, + [{{{items.items.dataType}}}.from_dict(_item) for _item in _v] + if _v is not None + else None + ) + for _k, _v in obj.get("{{{baseName}}}").items() + ){{^-last}},{{/-last}} + {{/items.isArray}} + {{/items.isContainer}} + {{^items.isContainer}} + "{{{name}}}": dict( + (_k, {{{items.dataType}}}.from_dict(_v)) + for _k, _v in obj.get("{{{baseName}}}").items() + ) + if obj.get("{{{baseName}}}") is not None + else None{{^-last}},{{/-last}} + {{/items.isContainer}} + {{/items.isEnumOrRef}} + {{#items.isEnumOrRef}} + "{{{name}}}": dict((_k, _v) for _k, _v in obj.get("{{{baseName}}}").items()){{^-last}},{{/-last}} + {{/items.isEnumOrRef}} + {{/items.isPrimitiveType}} + {{#items.isPrimitiveType}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/items.isPrimitiveType}} + {{/isMap}} + {{/isContainer}} + {{^isContainer}} + {{^isPrimitiveType}} + {{^isEnumOrRef}} + "{{{name}}}": {{{dataType}}}.from_dict(obj.get("{{{baseName}}}")) if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}} + {{/isEnumOrRef}} + {{#isEnumOrRef}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/isEnumOrRef}} + {{/isPrimitiveType}} + {{#isPrimitiveType}} + {{#defaultValue}} + "{{{name}}}": obj.get("{{{baseName}}}") if obj.get("{{{baseName}}}") is not None else {{{defaultValue}}}{{^-last}},{{/-last}} + {{/defaultValue}} + {{^defaultValue}} + "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} + {{/defaultValue}} + {{/isPrimitiveType}} + {{/isContainer}} + {{/allVars}} + }) + {{#isAdditionalPropertiesTrue}} + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + {{/isAdditionalPropertiesTrue}} + return _obj + {{/hasChildren}} + +{{#vendorExtensions.x-py-postponed-model-imports.size}} +{{#vendorExtensions.x-py-postponed-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-postponed-model-imports}} +{{classname}}.update_forward_refs() +{{/vendorExtensions.x-py-postponed-model-imports.size}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_oneof.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_oneof.mustache new file mode 100644 index 000000000000..c7afad4d172b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_oneof.mustache @@ -0,0 +1,206 @@ +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} +{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} +{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} +{{#vendorExtensions.x-py-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-model-imports}} +from typing import Union, Any, List, TYPE_CHECKING +from pydantic import StrictStr, Field + +{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS = [{{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}}] + +class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): + """ + {{{description}}}{{^description}}{{{classname}}}{{/description}} + """ +{{#composedSchemas.oneOf}} + # data type: {{{dataType}}} + {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} +{{/composedSchemas.oneOf}} + if TYPE_CHECKING: + actual_instance: Union[{{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}] + else: + actual_instance: Any + one_of_schemas: List[str] = Field({{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS, const=True) + + class Config: + validate_assignment = True +{{#discriminator}} + + discriminator_value_class_map = { +{{#children}} + '{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}},{{/-last}} +{{/children}} + } +{{/discriminator}} + + def __init__(self, *args, **kwargs): + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @validator('actual_instance') + def actual_instance_must_validate_oneof(cls, v): + {{#isNullable}} + if v is None: + return v + + {{/isNullable}} + instance = {{{classname}}}.construct() + error_messages = [] + match = 0 + {{#composedSchemas.oneOf}} + # validate data type: {{{dataType}}} + {{#isContainer}} + try: + instance.{{vendorExtensions.x-py-name}} = v + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isContainer}} + {{^isContainer}} + {{#isPrimitiveType}} + try: + instance.{{vendorExtensions.x-py-name}} = v + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{^isPrimitiveType}} + if not isinstance(v, {{{dataType}}}): + error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`") + else: + match += 1 + {{/isPrimitiveType}} + {{/isContainer}} + {{/composedSchemas.oneOf}} + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when setting `actual_instance` in {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when setting `actual_instance` in {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: dict) -> {{{classname}}}: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> {{{classname}}}: + """Returns the object represented by the json string""" + instance = {{{classname}}}.construct() + {{#isNullable}} + if json_str is None: + return instance + + {{/isNullable}} + error_messages = [] + match = 0 + + {{#useOneOfDiscriminatorLookup}} + {{#discriminator}} + {{#mappedModels}} + {{#-first}} + # use oneOf discriminator to lookup the data type + _data_type = json.loads(json_str).get("{{{propertyBaseName}}}") + if not _data_type: + raise ValueError("Failed to lookup data type from the field `{{{propertyBaseName}}}` in the input.") + + {{/-first}} + # check if data type is `{{{modelName}}}` + if _data_type == "{{{mappingName}}}": + instance.actual_instance = {{{modelName}}}.from_json(json_str) + return instance + + {{/mappedModels}} + {{/discriminator}} + {{/useOneOfDiscriminatorLookup}} + {{#composedSchemas.oneOf}} + {{#isContainer}} + # deserialize data into {{{dataType}}} + try: + # validation + instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{vendorExtensions.x-py-name}} + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isContainer}} + {{^isContainer}} + {{#isPrimitiveType}} + # deserialize data into {{{dataType}}} + try: + # validation + instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{vendorExtensions.x-py-name}} + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{^isPrimitiveType}} + # deserialize data into {{{dataType}}} + try: + instance.actual_instance = {{{dataType}}}.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {{/isPrimitiveType}} + {{/isContainer}} + {{/composedSchemas.oneOf}} + + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> dict: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + to_dict = getattr(self.actual_instance, "to_dict", None) + if callable(to_dict): + return self.actual_instance.to_dict() + else: + # primitive type + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.dict()) + +{{#vendorExtensions.x-py-postponed-model-imports.size}} +{{#vendorExtensions.x-py-postponed-model-imports}} +{{{.}}} +{{/vendorExtensions.x-py-postponed-model-imports}} +{{classname}}.update_forward_refs() +{{/vendorExtensions.x-py-postponed-model-imports.size}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/model_test.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/model_test.mustache new file mode 100644 index 000000000000..93ebf99b14fb --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_test.mustache @@ -0,0 +1,62 @@ +# coding: utf-8 + +{{>partial_header}} + +import unittest +import datetime + +{{#models}} +{{#model}} +import {{packageName}} +from {{modelPackage}}.{{classFilename}} import {{classname}} # noqa: E501 +from {{packageName}}.rest import ApiException + +class Test{{classname}}(unittest.TestCase): + """{{classname}} unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass +{{^isEnum}} + + def make_instance(self, include_optional): + """Test {{classname}} + include_option is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `{{{classname}}}` + """ + model = {{packageName}}.models.{{classFilename}}.{{classname}}() # noqa: E501 + if include_optional : + return {{classname}}( + {{#vars}} + {{name}} = {{{example}}}{{^example}}None{{/example}}{{^-last}}, {{/-last}} + {{/vars}} + ) + else : + return {{classname}}( + {{#vars}} + {{#required}} + {{name}} = {{{example}}}{{^example}}None{{/example}}, + {{/required}} + {{/vars}} + ) + """ +{{/isEnum}} + + def test{{classname}}(self): + """Test {{classname}}""" +{{^isEnum}} + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) +{{/isEnum}} +{{#isEnum}} + # inst = {{{classname}}}() +{{/isEnum}} +{{/model}} +{{/models}} + +if __name__ == '__main__': + unittest.main() diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/partial_header.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/partial_header.mustache index add2ee4158ba..50aa81171f6d 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/partial_header.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/partial_header.mustache @@ -1,17 +1,19 @@ """ {{#appName}} {{{.}}} -{{/appName}} +{{/appName}} {{#appDescription}} {{{.}}} -{{/appDescription}} +{{/appDescription}} {{#version}} The version of the OpenAPI document: {{{.}}} {{/version}} {{#infoEmail}} Contact: {{{.}}} {{/infoEmail}} - Generated by: https://openapi-generator.tech -""" + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/requirements.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/requirements.mustache index e5a1b375c4c3..1fd737c41a19 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/requirements.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/requirements.mustache @@ -2,7 +2,7 @@ aiofiles==23.1.0 aniso8601==7.0.0 async-exit-stack==1.0.1 async-generator==1.10 -certifi==2022.12.7 +certifi==2023.7.22 chardet==4.0.0 click==7.1.2 dnspython==2.1.0 @@ -20,7 +20,7 @@ Jinja2==2.11.3 MarkupSafe==2.0.1 orjson==3.5.2 promise==2.3 -pydantic==1.8.2 +pydantic==1.10.12 python-dotenv==0.17.1 python-multipart==0.0.5 PyYAML==5.4.1 diff --git a/samples/server/petstore/python-fastapi/requirements.txt b/samples/server/petstore/python-fastapi/requirements.txt index e5a1b375c4c3..1fd737c41a19 100644 --- a/samples/server/petstore/python-fastapi/requirements.txt +++ b/samples/server/petstore/python-fastapi/requirements.txt @@ -2,7 +2,7 @@ aiofiles==23.1.0 aniso8601==7.0.0 async-exit-stack==1.0.1 async-generator==1.10 -certifi==2022.12.7 +certifi==2023.7.22 chardet==4.0.0 click==7.1.2 dnspython==2.1.0 @@ -20,7 +20,7 @@ Jinja2==2.11.3 MarkupSafe==2.0.1 orjson==3.5.2 promise==2.3 -pydantic==1.8.2 +pydantic==1.10.12 python-dotenv==0.17.1 python-multipart==0.0.5 PyYAML==5.4.1 diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/main.py b/samples/server/petstore/python-fastapi/src/openapi_server/main.py index 0d7f1b526bfb..d31bbdbcd589 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/main.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/main.py @@ -6,8 +6,10 @@ This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. The version of the OpenAPI document: 1.0.0 - Generated by: https://openapi-generator.tech -""" + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 from fastapi import FastAPI diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/api_response.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/api_response.py index 7840e693d734..6ace1a409464 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/api_response.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/api_response.py @@ -1,28 +1,75 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 +import json -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 +from typing import Optional +from pydantic import BaseModel, StrictInt, StrictStr class ApiResponse(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + """ + Describes the result of uploading an image resource + """ + code: Optional[StrictInt] = None + type: Optional[StrictStr] = None + message: Optional[StrictStr] = None + __properties = ["code", "type", "message"] - Do not edit the class manually. + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True - ApiResponse - a model defined in OpenAPI + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) - code: The code of this ApiResponse [Optional]. - type: The type of this ApiResponse [Optional]. - message: The message of this ApiResponse [Optional]. - """ + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> ApiResponse: + """Create an instance of ApiResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> ApiResponse: + """Create an instance of ApiResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return ApiResponse.parse_obj(obj) + + _obj = ApiResponse.parse_obj({ + "code": obj.get("code"), + "type": obj.get("type"), + "message": obj.get("message") + }) + return _obj - code: Optional[int] = Field(alias="code", default=None) - type: Optional[str] = Field(alias="type", default=None) - message: Optional[str] = Field(alias="message", default=None) -ApiResponse.update_forward_refs() diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/category.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/category.py index 843b4a05dffa..e552e6ee371c 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/category.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/category.py @@ -1,31 +1,83 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore -import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + Do not edit the class manually. +""" # noqa: E501 -class Category(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - Do not edit the class manually. +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + - Category - a model defined in OpenAPI +from typing import Optional +from pydantic import BaseModel, StrictInt, constr, validator - id: The id of this Category [Optional]. - name: The name of this Category [Optional]. +class Category(BaseModel): + """ + A category for a pet """ + id: Optional[StrictInt] = None + name: Optional[constr(strict=True)] = None + __properties = ["id", "name"] - id: Optional[int] = Field(alias="id", default=None) - name: Optional[str] = Field(alias="name", default=None) + @validator('name') + def name_validate_regular_expression(cls, value): + """Validates the regular expression""" + if value is None: + return value - @validator("name") - def name_pattern(cls, value): - assert value is not None and re.match(r"/^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$/", value) + if not re.match(r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$", value): + raise ValueError(r"must validate the regular expression /^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$/") return value -Category.update_forward_refs() + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Category: + """Create an instance of Category from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> Category: + """Create an instance of Category from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return Category.parse_obj(obj) + + _obj = Category.parse_obj({ + "id": obj.get("id"), + "name": obj.get("name") + }) + return _obj + + diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/order.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/order.py index 95d532c51560..b041f999542a 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/order.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/order.py @@ -1,34 +1,91 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore -import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + Do not edit the class manually. +""" # noqa: E501 -class Order(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - Do not edit the class manually. +from __future__ import annotations +import pprint +import re # noqa: F401 +import json - Order - a model defined in OpenAPI +from datetime import datetime +from typing import Optional +from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, validator - id: The id of this Order [Optional]. - pet_id: The pet_id of this Order [Optional]. - quantity: The quantity of this Order [Optional]. - ship_date: The ship_date of this Order [Optional]. - status: The status of this Order [Optional]. - complete: The complete of this Order [Optional]. +class Order(BaseModel): """ + An order for a pets from the pet store + """ + id: Optional[StrictInt] = None + pet_id: Optional[StrictInt] = Field(None, alias="petId") + quantity: Optional[StrictInt] = None + ship_date: Optional[datetime] = Field(None, alias="shipDate") + status: Optional[StrictStr] = Field(None, description="Order Status") + complete: Optional[StrictBool] = False + __properties = ["id", "petId", "quantity", "shipDate", "status", "complete"] + + @validator('status') + def status_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in ('placed', 'approved', 'delivered'): + raise ValueError("must be one of enum values ('placed', 'approved', 'delivered')") + return value + + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Order: + """Create an instance of Order from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> Order: + """Create an instance of Order from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return Order.parse_obj(obj) + + _obj = Order.parse_obj({ + "id": obj.get("id"), + "pet_id": obj.get("petId"), + "quantity": obj.get("quantity"), + "ship_date": obj.get("shipDate"), + "status": obj.get("status"), + "complete": obj.get("complete") if obj.get("complete") is not None else False + }) + return _obj - id: Optional[int] = Field(alias="id", default=None) - pet_id: Optional[int] = Field(alias="petId", default=None) - quantity: Optional[int] = Field(alias="quantity", default=None) - ship_date: Optional[datetime] = Field(alias="shipDate", default=None) - status: Optional[str] = Field(alias="status", default=None) - complete: Optional[bool] = Field(alias="complete", default=None) -Order.update_forward_refs() diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/pet.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/pet.py index d814c3ce2a0c..7372730a67d2 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/pet.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/pet.py @@ -1,36 +1,103 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 +import json + -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 +from typing import List, Optional +from pydantic import BaseModel, Field, StrictInt, StrictStr, conlist, validator from openapi_server.models.category import Category from openapi_server.models.tag import Tag - class Pet(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + """ + A pet for sale in the pet store + """ + id: Optional[StrictInt] = None + category: Optional[Category] = None + name: StrictStr = Field(...) + photo_urls: conlist(StrictStr) = Field(..., alias="photoUrls") + tags: Optional[conlist(Tag)] = None + status: Optional[StrictStr] = Field(None, description="pet status in the store") + __properties = ["id", "category", "name", "photoUrls", "tags", "status"] - Do not edit the class manually. + @validator('status') + def status_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value - Pet - a model defined in OpenAPI + if value not in ('available', 'pending', 'sold'): + raise ValueError("must be one of enum values ('available', 'pending', 'sold')") + return value - id: The id of this Pet [Optional]. - category: The category of this Pet [Optional]. - name: The name of this Pet. - photo_urls: The photo_urls of this Pet. - tags: The tags of this Pet [Optional]. - status: The status of this Pet [Optional]. - """ + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Pet: + """Create an instance of Pet from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + # override the default output from pydantic by calling `to_dict()` of category + if self.category: + _dict['category'] = self.category.to_dict() + # override the default output from pydantic by calling `to_dict()` of each item in tags (list) + _items = [] + if self.tags: + for _item in self.tags: + if _item: + _items.append(_item.to_dict()) + _dict['tags'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> Pet: + """Create an instance of Pet from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return Pet.parse_obj(obj) + + _obj = Pet.parse_obj({ + "id": obj.get("id"), + "category": Category.from_dict(obj.get("category")) if obj.get("category") is not None else None, + "name": obj.get("name"), + "photo_urls": obj.get("photoUrls"), + "tags": [Tag.from_dict(_item) for _item in obj.get("tags")] if obj.get("tags") is not None else None, + "status": obj.get("status") + }) + return _obj - id: Optional[int] = Field(alias="id", default=None) - category: Optional[Category] = Field(alias="category", default=None) - name: str = Field(alias="name") - photo_urls: List[str] = Field(alias="photoUrls") - tags: Optional[List[Tag]] = Field(alias="tags", default=None) - status: Optional[str] = Field(alias="status", default=None) -Pet.update_forward_refs() diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/tag.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/tag.py index b12929adea72..7cd5a23c9e1f 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/tag.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/tag.py @@ -1,26 +1,73 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 +import json -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 +from typing import Optional +from pydantic import BaseModel, StrictInt, StrictStr class Tag(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + """ + A tag for a pet + """ + id: Optional[StrictInt] = None + name: Optional[StrictStr] = None + __properties = ["id", "name"] - Do not edit the class manually. + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True - Tag - a model defined in OpenAPI + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) - id: The id of this Tag [Optional]. - name: The name of this Tag [Optional]. - """ + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Tag: + """Create an instance of Tag from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> Tag: + """Create an instance of Tag from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return Tag.parse_obj(obj) + + _obj = Tag.parse_obj({ + "id": obj.get("id"), + "name": obj.get("name") + }) + return _obj - id: Optional[int] = Field(alias="id", default=None) - name: Optional[str] = Field(alias="name", default=None) -Tag.update_forward_refs() diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/models/user.py b/samples/server/petstore/python-fastapi/src/openapi_server/models/user.py index 3cb036759f27..6113bef4645e 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/models/user.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/models/user.py @@ -1,38 +1,85 @@ # coding: utf-8 -from __future__ import annotations -from datetime import date, datetime # noqa: F401 +""" + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations +import pprint import re # noqa: F401 -from typing import Any, Dict, List, Optional # noqa: F401 +import json -from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 +from typing import Optional +from pydantic import BaseModel, Field, StrictInt, StrictStr class User(BaseModel): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + """ + A User who is purchasing from the pet store + """ + id: Optional[StrictInt] = None + username: Optional[StrictStr] = None + first_name: Optional[StrictStr] = Field(None, alias="firstName") + last_name: Optional[StrictStr] = Field(None, alias="lastName") + email: Optional[StrictStr] = None + password: Optional[StrictStr] = None + phone: Optional[StrictStr] = None + user_status: Optional[StrictInt] = Field(None, alias="userStatus", description="User Status") + __properties = ["id", "username", "firstName", "lastName", "email", "password", "phone", "userStatus"] - Do not edit the class manually. + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True - User - a model defined in OpenAPI + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) - id: The id of this User [Optional]. - username: The username of this User [Optional]. - first_name: The first_name of this User [Optional]. - last_name: The last_name of this User [Optional]. - email: The email of this User [Optional]. - password: The password of this User [Optional]. - phone: The phone of this User [Optional]. - user_status: The user_status of this User [Optional]. - """ + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> User: + """Create an instance of User from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ + }, + exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> User: + """Create an instance of User from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return User.parse_obj(obj) + + _obj = User.parse_obj({ + "id": obj.get("id"), + "username": obj.get("username"), + "first_name": obj.get("firstName"), + "last_name": obj.get("lastName"), + "email": obj.get("email"), + "password": obj.get("password"), + "phone": obj.get("phone"), + "user_status": obj.get("userStatus") + }) + return _obj - id: Optional[int] = Field(alias="id", default=None) - username: Optional[str] = Field(alias="username", default=None) - first_name: Optional[str] = Field(alias="firstName", default=None) - last_name: Optional[str] = Field(alias="lastName", default=None) - email: Optional[str] = Field(alias="email", default=None) - password: Optional[str] = Field(alias="password", default=None) - phone: Optional[str] = Field(alias="phone", default=None) - user_status: Optional[int] = Field(alias="userStatus", default=None) -User.update_forward_refs()