diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache index 7c87ef7543c..5997912c168 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache @@ -28,7 +28,7 @@ from {{modelPackage}}.extra_models import TokenModel # noqa: F401 {{/imports}} {{#securityImports.0}}from {{packageName}}.security_api import {{#securityImports}}get_token_{{.}}{{^-last}}, {{/-last}}{{/securityImports}}{{/securityImports.0}} -router = APIRouter() +router = APIRouter(prefix="{{basePathWithoutHost}}") ns_pkg = {{fastapiImplementationPackage}} for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."): diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache index c8887fb64f4..d3fb8687042 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/endpoint_argument_definition.mustache @@ -1 +1 @@ -{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}Form{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}({{&defaultValue}}{{^defaultValue}}{{#isPathParam}}...{{/isPathParam}}{{^isPathParam}}None{{/isPathParam}}{{/defaultValue}}, description="{{description}}"{{#isQueryParam}}, alias="{{baseName}}"{{/isQueryParam}}{{#isLong}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isLong}}{{#isInteger}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isInteger}}{{#pattern}}, regex=r"{{.}}"{{/pattern}}{{#minLength}}, min_length={{.}}{{/minLength}}{{#maxLength}}, max_length={{.}}{{/maxLength}}) \ No newline at end of file +{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}Form{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}({{#isEnum}}{{&enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{&defaultValue}}{{/isEnum}}{{^defaultValue}}{{#isPathParam}}...{{/isPathParam}}{{^isPathParam}}None{{/isPathParam}}{{/defaultValue}}, description="{{description}}"{{#isQueryParam}}, alias="{{baseName}}"{{/isQueryParam}}{{#isLong}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isLong}}{{#isInteger}}{{#minimum}}, ge={{.}}{{/minimum}}{{#maximum}}, le={{.}}{{/maximum}}{{/isInteger}}{{#pattern}}, regex=r"{{.}}"{{/pattern}}{{#minLength}}, min_length={{.}}{{/minLength}}{{#maxLength}}, max_length={{.}}{{/maxLength}}) \ 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 index b87c42cf2b9..0f26a1f96a3 100644 --- a/modules/openapi-generator/src/main/resources/python-fastapi/model_oneof.mustache +++ b/modules/openapi-generator/src/main/resources/python-fastapi/model_oneof.mustache @@ -14,198 +14,50 @@ import re # noqa: F401 {{/vendorExtensions.x-py-model-imports}} from typing import Union, Any, List, TYPE_CHECKING, Optional, Dict from typing_extensions import Literal -from pydantic import StrictStr, Field +from pydantic import StrictStr, Field, RootModel try: from typing import Self except ImportError: from typing_extensions import Self -{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS = [{{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}}] +{{#discriminator.mappedModels}}{{#explicitMapping}} +class {{modelName}}Oneof({{modelName}}): + {{discriminatorName}}: Literal['{{mappingName}}'] + pass +{{/explicitMapping}}{{/discriminator.mappedModels}} -class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): +class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}RootModel{{/parent}}): """ {{{description}}}{{^description}}{{{classname}}}{{/description}} """ -{{#composedSchemas.oneOf}} - # data type: {{{dataType}}} - {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} -{{/composedSchemas.oneOf}} - actual_instance: Optional[Union[{{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}]] = None - one_of_schemas: List[str] = Literal[{{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}}] + root: Union[{{#oneOf}}{{{.}}}Oneof{{^-last}}, {{/-last}}{{/oneOf}}] = Field(discriminator="{{discriminatorName}}") model_config = { "validate_assignment": True, "protected_namespaces": (), } -{{#discriminator}} - - discriminator_value_class_map: Dict[str, str] = { -{{#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) -> None: - 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) - - @field_validator('actual_instance') - def actual_instance_must_validate_oneof(cls, v): - {{#isNullable}} - if v is None: - return v - - {{/isNullable}} - instance = {{{classname}}}.model_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) -> Self: - return cls.from_json(json.dumps(obj)) - - @classmethod - def from_json(cls, json_str: str) -> Self: - """Returns the object represented by the json string""" - instance = cls.model_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.model_dump()) + @classmethod + def from_dict(cls, obj: Dict) -> Self: + """Create an instance of {{classname}} from a dictionary""" + return cls(obj) + + def to_dict(self) -> Dict: + """Returns the dict representation of the actual instance""" + if self.root is None: + return None + + to_dict = getattr(self.root, "to_dict", None) + if callable(to_dict): + return self.root.to_dict() + else: + # primitive type + return self.root + {{#vendorExtensions.x-py-postponed-model-imports.size}} {{#vendorExtensions.x-py-postponed-model-imports}} {{{.}}} diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py index 0e3f8d51b31..b79f04870af 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api.py @@ -28,7 +28,7 @@ from typing import Any, Optional from typing_extensions import Annotated -router = APIRouter() +router = APIRouter(prefix="/v2") ns_pkg = openapi_server.impl for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."): diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py index a4aa3a6e71e..856c38f19da 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py @@ -30,7 +30,7 @@ from openapi_server.models.api_response import ApiResponse from openapi_server.models.pet import Pet from openapi_server.security_api import get_token_petstore_auth, get_token_api_key -router = APIRouter() +router = APIRouter(prefix="/v2") ns_pkg = openapi_server.impl for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."): diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py index 21d2aceb380..c1934e416d2 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py @@ -29,7 +29,7 @@ from typing_extensions import Annotated from openapi_server.models.order import Order from openapi_server.security_api import get_token_api_key -router = APIRouter() +router = APIRouter(prefix="/v2") ns_pkg = openapi_server.impl for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."): diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py index 8f848f0131d..7c941a97dab 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py @@ -29,7 +29,7 @@ from typing_extensions import Annotated from openapi_server.models.user import User from openapi_server.security_api import get_token_api_key -router = APIRouter() +router = APIRouter(prefix="/v2") ns_pkg = openapi_server.impl for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):