diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java index 510cf09b44c7..53f82d2f7ba9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java @@ -1318,7 +1318,8 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List params = operation.allParams; for (CodegenParameter cp : params) { - PydanticType pydantic = new PydanticType( + PydanticType pydantic = getPydanticParameterType( + cp, modelImports, exampleImports, postponedModelImports, @@ -1414,6 +1415,23 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List modelImports, + Set exampleImports, + Set postponedModelImports, + Set postponedExampleImports, + PythonImports moduleImports, + String classname) { + return new PydanticType( + modelImports, + exampleImports, + postponedModelImports, + postponedExampleImports, + moduleImports, + classname + ); + } + @Override public void postProcessParameter(CodegenParameter parameter) { @@ -1781,7 +1799,7 @@ public String asTypeValue(PythonImports imports) { * entries will be automatically removed. * * */ - class PythonImports { + protected class PythonImports { private Map> imports; public PythonImports() { @@ -1827,22 +1845,22 @@ public boolean isEmpty() { } } - class PydanticType { + protected class PydanticType { - private static final String LESS_THAN = "lt"; - private static final String GREATER_THAN = "gt"; - private static final String GREATER_OR_EQUAL_TO = "ge"; - private static final String LESS_OR_EQUAL_TO = "le"; - private static final String TYPING = "typing"; + protected static final String LESS_THAN = "lt"; + protected static final String GREATER_THAN = "gt"; + protected static final String GREATER_OR_EQUAL_TO = "ge"; + protected static final String LESS_OR_EQUAL_TO = "le"; + protected static final String TYPING = "typing"; - private static final String DECIMAL = "Decimal"; + protected static final String DECIMAL = "Decimal"; - private Set modelImports; - private Set exampleImports; - private Set postponedModelImports; - private Set postponedExampleImports; - private PythonImports moduleImports; - private String classname; + protected Set modelImports; + protected Set exampleImports; + protected Set postponedModelImports; + protected Set postponedExampleImports; + protected PythonImports moduleImports; + protected String classname; public PydanticType( Set modelImports, @@ -1860,7 +1878,7 @@ public PydanticType( this.classname = classname; } - private PythonType arrayType(IJsonSchemaValidationProperties cp) { + protected PythonType arrayType(IJsonSchemaValidationProperties cp) { PythonType pt = new PythonType(); if (cp.getMaxItems() != null) { pt.constrain("max_length", cp.getMaxItems()); @@ -1887,7 +1905,7 @@ private PythonType arrayType(IJsonSchemaValidationProperties cp) { return pt; } - private PythonType collectionItemType(CodegenProperty itemCp) { + protected PythonType collectionItemType(CodegenProperty itemCp) { PythonType itemPt = getType(itemCp); if (itemCp != null && !itemPt.type.equals("Any") && itemCp.isNullable) { moduleImports.add(TYPING, "Optional"); @@ -1898,7 +1916,7 @@ private PythonType collectionItemType(CodegenProperty itemCp) { return itemPt; } - private PythonType stringType(IJsonSchemaValidationProperties cp) { + protected PythonType stringType(IJsonSchemaValidationProperties cp) { if (cp.getHasValidation()) { PythonType pt = new PythonType("str"); @@ -1929,7 +1947,7 @@ private PythonType stringType(IJsonSchemaValidationProperties cp) { } } - private PythonType mapType(IJsonSchemaValidationProperties cp) { + protected PythonType mapType(IJsonSchemaValidationProperties cp) { moduleImports.add(TYPING, "Dict"); PythonType pt = new PythonType("Dict"); pt.addTypeParam(new PythonType("str")); @@ -1937,7 +1955,7 @@ private PythonType mapType(IJsonSchemaValidationProperties cp) { return pt; } - private PythonType numberType(IJsonSchemaValidationProperties cp) { + protected PythonType numberType(IJsonSchemaValidationProperties cp) { if (cp.getHasValidation()) { PythonType floatt = new PythonType("float"); PythonType intt = new PythonType("int"); @@ -1998,7 +2016,7 @@ private PythonType numberType(IJsonSchemaValidationProperties cp) { } } - private PythonType intType(IJsonSchemaValidationProperties cp) { + protected PythonType intType(IJsonSchemaValidationProperties cp) { if (cp.getHasValidation()) { PythonType pt = new PythonType("int"); // e.g. conint(ge=10, le=100, strict=True) @@ -2011,7 +2029,7 @@ private PythonType intType(IJsonSchemaValidationProperties cp) { } } - private PythonType binaryType(IJsonSchemaValidationProperties cp) { + protected PythonType binaryType(IJsonSchemaValidationProperties cp) { if (cp.getHasValidation()) { PythonType bytest = new PythonType("bytes"); PythonType strt = new PythonType("str"); @@ -2075,12 +2093,12 @@ private PythonType binaryType(IJsonSchemaValidationProperties cp) { } } - private PythonType boolType(IJsonSchemaValidationProperties cp) { + protected PythonType boolType(IJsonSchemaValidationProperties cp) { moduleImports.add(PYDANTIC, "StrictBool"); return new PythonType("StrictBool"); } - private PythonType decimalType(IJsonSchemaValidationProperties cp) { + protected PythonType decimalType(IJsonSchemaValidationProperties cp) { PythonType pt = new PythonType(DECIMAL); moduleImports.add("decimal", DECIMAL); @@ -2093,12 +2111,12 @@ private PythonType decimalType(IJsonSchemaValidationProperties cp) { return pt; } - private PythonType anyType(IJsonSchemaValidationProperties cp) { + protected PythonType anyType(IJsonSchemaValidationProperties cp) { moduleImports.add(TYPING, "Any"); return new PythonType("Any"); } - private PythonType dateType(IJsonSchemaValidationProperties cp) { + protected PythonType dateType(IJsonSchemaValidationProperties cp) { if (cp.getIsDate()) { moduleImports.add("datetime", "date"); } @@ -2109,12 +2127,12 @@ private PythonType dateType(IJsonSchemaValidationProperties cp) { return new PythonType(cp.getDataType()); } - private PythonType uuidType(IJsonSchemaValidationProperties cp) { + protected PythonType uuidType(IJsonSchemaValidationProperties cp) { moduleImports.add("uuid", "UUID"); return new PythonType("UUID"); } - private PythonType modelType(IJsonSchemaValidationProperties cp) { + protected PythonType modelType(IJsonSchemaValidationProperties cp) { // add model prefix hasModelsToImport = true; modelImports.add(cp.getDataType()); @@ -2122,7 +2140,7 @@ private PythonType modelType(IJsonSchemaValidationProperties cp) { return new PythonType(cp.getDataType()); } - private PythonType fromCommon(IJsonSchemaValidationProperties cp) { + protected PythonType fromCommon(IJsonSchemaValidationProperties cp) { if (cp == null) { // if codegen property (e.g. map/dict of undefined type) is null, default to string LOGGER.warn("Codegen property is null (e.g. map/dict of undefined type). Default to typing.Any."); @@ -2170,7 +2188,7 @@ public String generatePythonType(CodegenProperty cp) { return this.finalizeType(cp, pt); } - private PythonType getType(CodegenProperty cp) { + protected PythonType getType(CodegenProperty cp) { PythonType result = fromCommon(cp); /* comment out the following since Literal requires python 3.8 @@ -2276,7 +2294,7 @@ public String generatePythonType(CodegenParameter cp) { return this.finalizeType(cp, pt); } - private PythonType getType(CodegenParameter cp) { + protected PythonType getType(CodegenParameter cp) { // TODO: cleanup PythonType result = fromCommon(cp); @@ -2305,7 +2323,7 @@ private PythonType getType(CodegenParameter cp) { return result; } - private void applyConstraints(PythonType pythonType, IJsonSchemaValidationProperties cp) { + protected void applyConstraints(PythonType pythonType, IJsonSchemaValidationProperties cp) { if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { pythonType.constrain(LESS_THAN, cp.getMaximum(), false); @@ -2341,4 +2359,123 @@ private String finalizeType(CodegenParameter cp, PythonType pt) { return pt.asTypeConstraintWithAnnotations(moduleImports); } } + + /** + * Pydantic type generator for values that arrive over the wire as strings — server-bound request + * parameters in path, query, and header position. These rely on Pydantic's automatic coercion + * (e.g. {@code "3" -> 3}); the strict types emitted by the base {@link PydanticType} + * ({@code StrictInt}/{@code StrictStr}/{@code StrictFloat}, {@code strict=True}) disable that + * coercion and make FastAPI reject otherwise-valid requests with a 422. See issue #21905. + * + *

Request bodies and models are not wire-string values — they carry real JSON types — + * so they keep the strict base behaviour. + */ + protected class PydanticCoercibleType extends PydanticType { + public PydanticCoercibleType( + Set modelImports, + Set exampleImports, + Set postponedModelImports, + Set postponedExampleImports, + PythonImports moduleImports, + String classname + ) { + super(modelImports, exampleImports, postponedModelImports, postponedExampleImports, moduleImports, classname); + } + + @Override + protected PythonType stringType(IJsonSchemaValidationProperties cp) { + if (cp.getHasValidation()) { + PythonType pt = new PythonType("str"); + if (cp.getMaxLength() != null) { + pt.constrain("max_length", cp.getMaxLength()); + } + if (cp.getMinLength() != null) { + pt.constrain("min_length", cp.getMinLength()); + } + if (cp.getPattern() != null) { + moduleImports.add(PYDANTIC, "field_validator"); + } + return pt; + } else if ("password".equals(cp.getFormat())) { // TODO avoid using format, use `is` boolean flag instead + moduleImports.add(PYDANTIC, "SecretStr"); + return new PythonType("SecretStr"); + } + + return new PythonType("str"); + } + + @Override + protected PythonType numberType(IJsonSchemaValidationProperties cp) { + if (cp.getHasValidation()) { + PythonType floatt = new PythonType("float"); + PythonType intt = new PythonType("int"); + + if (cp.getMaximum() != null) { + if (cp.getExclusiveMaximum()) { + floatt.constrain(LESS_THAN, cp.getMaximum(), false); + intt.constrain(LESS_THAN, (int) Math.ceil(Double.valueOf(cp.getMaximum()))); + } else { + floatt.constrain(LESS_OR_EQUAL_TO, cp.getMaximum(), false); + intt.constrain(LESS_OR_EQUAL_TO, (int) Math.floor(Double.valueOf(cp.getMaximum()))); + } + } + if (cp.getMinimum() != null) { + if (cp.getExclusiveMinimum()) { + floatt.constrain(GREATER_THAN, cp.getMinimum(), false); + intt.constrain(GREATER_THAN, (int) Math.floor(Double.valueOf(cp.getMinimum()))); + } else { + floatt.constrain(GREATER_OR_EQUAL_TO, cp.getMinimum(), false); + intt.constrain(GREATER_OR_EQUAL_TO, (int) Math.ceil(Double.valueOf(cp.getMinimum()))); + } + } + if (cp.getMultipleOf() != null) { + floatt.constrain("multiple_of", cp.getMultipleOf()); + } + + if ("Union[StrictFloat, StrictInt]".equals(mapNumberTo)) { + moduleImports.add(TYPING, "Union"); + PythonType pt = new PythonType("Union"); + pt.addTypeParam(floatt); + pt.addTypeParam(intt); + return pt; + } + + return floatt; + } else if ("Union[StrictFloat, StrictInt]".equals(mapNumberTo)) { + moduleImports.add(TYPING, "Union"); + PythonType pt = new PythonType("Union"); + pt.addTypeParam(new PythonType("float")); + pt.addTypeParam(new PythonType("int")); + return pt; + } + + return new PythonType("float"); + } + + @Override + protected PythonType intType(IJsonSchemaValidationProperties cp) { + PythonType pt = new PythonType("int"); + if (cp.getHasValidation()) { + applyConstraints(pt, cp); + } + return pt; + } + + @Override + protected PythonType boolType(IJsonSchemaValidationProperties cp) { + return new PythonType("bool"); + } + + @Override + protected PythonType decimalType(IJsonSchemaValidationProperties cp) { + PythonType pt = new PythonType(DECIMAL); + moduleImports.add("decimal", DECIMAL); + + if (cp.getHasValidation()) { + applyConstraints(pt, cp); + } + + return pt; + } + } } 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 ab6a95fcc3a1..8454c4f1dd9f 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 @@ -237,6 +237,38 @@ public String getTypeDeclaration(Schema p) { return super.getTypeDeclaration(p); } + @Override + protected PydanticType getPydanticParameterType(CodegenParameter parameter, + Set modelImports, + Set exampleImports, + Set postponedModelImports, + Set postponedExampleImports, + PythonImports moduleImports, + String classname) { + // Path/query/header values always arrive as strings on the wire and rely on Pydantic + // coercion, so they must not use strict types. Body params keep the strict default. + if (parameter.isQueryParam || parameter.isPathParam || parameter.isHeaderParam) { + return new PydanticCoercibleType( + modelImports, + exampleImports, + postponedModelImports, + postponedExampleImports, + moduleImports, + classname + ); + } + + return super.getPydanticParameterType( + parameter, + modelImports, + exampleImports, + postponedModelImports, + postponedExampleImports, + moduleImports, + classname + ); + } + @Override public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { super.postProcessOperationsWithModels(objs, allModels); 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 0e3f8d51b319..56b3e6e64381 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 @@ -23,7 +23,7 @@ ) from openapi_server.models.extra_models import TokenModel # noqa: F401 -from pydantic import Field, StrictStr +from pydantic import Field from typing import Any, Optional from typing_extensions import Annotated @@ -46,8 +46,8 @@ response_model_by_alias=True, ) async def fake_query_param_default( - has_default: Annotated[Optional[StrictStr], Field(description="has default value")] = Query('Hello World', description="has default value", alias="hasDefault"), - no_default: Annotated[Optional[StrictStr], Field(description="no default value")] = Query(None, description="no default value", alias="noDefault"), + has_default: Annotated[Optional[str], Field(description="has default value")] = Query('Hello World', description="has default value", alias="hasDefault"), + no_default: Annotated[Optional[str], Field(description="no default value")] = Query(None, description="no default value", alias="noDefault"), ) -> None: """""" if not BaseFakeApi.subclasses: diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api_base.py index 1c71537aa944..e8343c5d1330 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api_base.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/fake_api_base.py @@ -2,7 +2,7 @@ from typing import ClassVar, Dict, List, Tuple # noqa: F401 -from pydantic import Field, StrictStr +from pydantic import Field from typing import Any, Optional from typing_extensions import Annotated @@ -15,8 +15,8 @@ def __init_subclass__(cls, **kwargs): BaseFakeApi.subclasses = BaseFakeApi.subclasses + (cls,) async def fake_query_param_default( self, - has_default: Annotated[Optional[StrictStr], Field(description="has default value")], - no_default: Annotated[Optional[StrictStr], Field(description="no default value")], + has_default: Annotated[Optional[str], Field(description="has default value")], + no_default: Annotated[Optional[str], Field(description="no default value")], ) -> None: """""" ... 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 660a8d99d437..870db9dc5a56 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 @@ -23,7 +23,7 @@ ) from openapi_server.models.extra_models import TokenModel # noqa: F401 -from pydantic import Field, StrictBytes, StrictInt, StrictStr, field_validator +from pydantic import Field, StrictBytes, StrictStr, field_validator from typing import Any, List, Optional, Tuple, Union from typing_extensions import Annotated from openapi_server.models.api_response import ApiResponse @@ -95,7 +95,7 @@ async def add_pet( response_model_by_alias=True, ) async def find_pets_by_status( - status: Annotated[List[StrictStr], Field(description="Status values that need to be considered for filter")] = Query(None, description="Status values that need to be considered for filter", alias="status"), + status: Annotated[List[str], Field(description="Status values that need to be considered for filter")] = Query(None, description="Status values that need to be considered for filter", alias="status"), token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["read:pets"] ), @@ -117,7 +117,7 @@ async def find_pets_by_status( response_model_by_alias=True, ) async def find_pets_by_tags( - tags: Annotated[List[StrictStr], Field(description="Tags to filter by")] = Query(None, description="Tags to filter by", alias="tags"), + tags: Annotated[List[str], Field(description="Tags to filter by")] = Query(None, description="Tags to filter by", alias="tags"), token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["read:pets"] ), @@ -140,7 +140,7 @@ async def find_pets_by_tags( response_model_by_alias=True, ) async def get_pet_by_id( - petId: Annotated[StrictInt, Field(description="ID of pet to return")] = Path(..., description="ID of pet to return"), + petId: Annotated[int, Field(description="ID of pet to return")] = Path(..., description="ID of pet to return"), token_api_key: TokenModel = Security( get_token_api_key ), @@ -161,7 +161,7 @@ async def get_pet_by_id( response_model_by_alias=True, ) async def update_pet_with_form( - petId: Annotated[StrictInt, Field(description="ID of pet that needs to be updated")] = Path(..., description="ID of pet that needs to be updated"), + petId: Annotated[int, Field(description="ID of pet that needs to be updated")] = Path(..., description="ID of pet that needs to be updated"), name: Annotated[Optional[StrictStr], Field(description="Updated name of the pet")] = Form(None, description="Updated name of the pet"), status: Annotated[Optional[StrictStr], Field(description="Updated status of the pet")] = Form(None, description="Updated status of the pet"), token_petstore_auth: TokenModel = Security( @@ -184,8 +184,8 @@ async def update_pet_with_form( response_model_by_alias=True, ) async def delete_pet( - petId: Annotated[StrictInt, Field(description="Pet id to delete")] = Path(..., description="Pet id to delete"), - api_key: Optional[StrictStr] = Header(None, description=""), + petId: Annotated[int, Field(description="Pet id to delete")] = Path(..., description="Pet id to delete"), + api_key: Optional[str] = Header(None, description=""), token_petstore_auth: TokenModel = Security( get_token_petstore_auth, scopes=["write:pets", "read:pets"] ), @@ -206,7 +206,7 @@ async def delete_pet( response_model_by_alias=True, ) async def upload_file( - petId: Annotated[StrictInt, Field(description="ID of pet to update")] = Path(..., description="ID of pet to update"), + petId: Annotated[int, Field(description="ID of pet to update")] = Path(..., description="ID of pet to update"), additional_metadata: Annotated[Optional[StrictStr], Field(description="Additional data to pass to server")] = Form(None, description="Additional data to pass to server"), file: Optional[UploadFile] = File(None, description="file to upload"), token_petstore_auth: TokenModel = Security( diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py index 4f8b292e3eec..dd8780dc2416 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py @@ -2,7 +2,7 @@ from typing import ClassVar, Dict, List, Tuple # noqa: F401 -from pydantic import Field, StrictBytes, StrictInt, StrictStr, field_validator +from pydantic import Field, StrictBytes, StrictStr, field_validator from typing import Any, List, Optional, Tuple, Union from typing_extensions import Annotated from openapi_server.models.api_response import ApiResponse @@ -34,7 +34,7 @@ async def add_pet( async def find_pets_by_status( self, - status: Annotated[List[StrictStr], Field(description="Status values that need to be considered for filter")], + status: Annotated[List[str], Field(description="Status values that need to be considered for filter")], ) -> List[Pet]: """Multiple status values can be provided with comma separated strings""" ... @@ -42,7 +42,7 @@ async def find_pets_by_status( async def find_pets_by_tags( self, - tags: Annotated[List[StrictStr], Field(description="Tags to filter by")], + tags: Annotated[List[str], Field(description="Tags to filter by")], ) -> List[Pet]: """Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.""" ... @@ -50,7 +50,7 @@ async def find_pets_by_tags( async def get_pet_by_id( self, - petId: Annotated[StrictInt, Field(description="ID of pet to return")], + petId: Annotated[int, Field(description="ID of pet to return")], ) -> Pet: """Returns a single pet""" ... @@ -58,7 +58,7 @@ async def get_pet_by_id( async def update_pet_with_form( self, - petId: Annotated[StrictInt, Field(description="ID of pet that needs to be updated")], + petId: Annotated[int, Field(description="ID of pet that needs to be updated")], name: Annotated[Optional[StrictStr], Field(description="Updated name of the pet")], status: Annotated[Optional[StrictStr], Field(description="Updated status of the pet")], ) -> None: @@ -68,8 +68,8 @@ async def update_pet_with_form( async def delete_pet( self, - petId: Annotated[StrictInt, Field(description="Pet id to delete")], - api_key: Optional[StrictStr], + petId: Annotated[int, Field(description="Pet id to delete")], + api_key: Optional[str], ) -> None: """""" ... @@ -77,7 +77,7 @@ async def delete_pet( async def upload_file( self, - petId: Annotated[StrictInt, Field(description="ID of pet to update")], + petId: Annotated[int, Field(description="ID of pet to update")], additional_metadata: Annotated[Optional[StrictStr], Field(description="Additional data to pass to server")], file: Optional[UploadFile], ) -> ApiResponse: 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 3d2744c2e028..fbcf205b9866 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 @@ -23,7 +23,7 @@ ) from openapi_server.models.extra_models import TokenModel # noqa: F401 -from pydantic import Field, StrictInt, StrictStr +from pydantic import Field, StrictInt from typing import Any, Dict from typing_extensions import Annotated from openapi_server.models.order import Order @@ -87,7 +87,7 @@ async def place_order( response_model_by_alias=True, ) async def get_order_by_id( - orderId: Annotated[int, Field(le=5, strict=True, ge=1, description="ID of pet that needs to be fetched")] = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5), + orderId: Annotated[int, Field(le=5, ge=1, description="ID of pet that needs to be fetched")] = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5), ) -> Order: """For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions""" if not BaseStoreApi.subclasses: @@ -106,7 +106,7 @@ async def get_order_by_id( response_model_by_alias=True, ) async def delete_order( - orderId: Annotated[StrictStr, Field(description="ID of the order that needs to be deleted")] = Path(..., description="ID of the order that needs to be deleted"), + orderId: Annotated[str, Field(description="ID of the order that needs to be deleted")] = Path(..., description="ID of the order that needs to be deleted"), ) -> None: """For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors""" if not BaseStoreApi.subclasses: diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py index 84d9b639c1d3..76d310a4997c 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py @@ -2,7 +2,7 @@ from typing import ClassVar, Dict, List, Tuple # noqa: F401 -from pydantic import Field, StrictInt, StrictStr +from pydantic import Field, StrictInt from typing import Any, Dict from typing_extensions import Annotated from openapi_server.models.order import Order @@ -31,7 +31,7 @@ async def place_order( async def get_order_by_id( self, - orderId: Annotated[int, Field(le=5, strict=True, ge=1, description="ID of pet that needs to be fetched")], + orderId: Annotated[int, Field(le=5, ge=1, description="ID of pet that needs to be fetched")], ) -> Order: """For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions""" ... @@ -39,7 +39,7 @@ async def get_order_by_id( async def delete_order( self, - orderId: Annotated[StrictStr, Field(description="ID of the order that needs to be deleted")], + orderId: Annotated[str, Field(description="ID of the order that needs to be deleted")], ) -> None: """For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors""" ... 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 e7d5ea8011b5..942ad817a3c8 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 @@ -110,8 +110,8 @@ async def create_users_with_list_input( response_model_by_alias=True, ) async def login_user( - username: Annotated[str, Field(strict=True, description="The user name for login")] = Query(None, description="The user name for login", alias="username", regex=r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$"), - password: Annotated[StrictStr, Field(description="The password for login in clear text")] = Query(None, description="The password for login in clear text", alias="password"), + username: Annotated[str, Field(description="The user name for login")] = Query(None, description="The user name for login", alias="username", regex=r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$"), + password: Annotated[str, Field(description="The password for login in clear text")] = Query(None, description="The password for login in clear text", alias="password"), ) -> str: """""" if not BaseUserApi.subclasses: @@ -151,7 +151,7 @@ async def logout_user( response_model_by_alias=True, ) async def get_user_by_name( - username: Annotated[StrictStr, Field(description="The name that needs to be fetched. Use user1 for testing.")] = Path(..., description="The name that needs to be fetched. Use user1 for testing."), + username: Annotated[str, Field(description="The name that needs to be fetched. Use user1 for testing.")] = Path(..., description="The name that needs to be fetched. Use user1 for testing."), ) -> User: """""" if not BaseUserApi.subclasses: @@ -170,7 +170,7 @@ async def get_user_by_name( response_model_by_alias=True, ) async def update_user( - username: Annotated[StrictStr, Field(description="name that need to be deleted")] = Path(..., description="name that need to be deleted"), + username: Annotated[str, Field(description="name that need to be deleted")] = Path(..., description="name that need to be deleted"), user: Annotated[User, Field(description="Updated user object")] = Body(None, description="Updated user object"), token_api_key: TokenModel = Security( get_token_api_key @@ -193,7 +193,7 @@ async def update_user( response_model_by_alias=True, ) async def delete_user( - username: Annotated[StrictStr, Field(description="The name that needs to be deleted")] = Path(..., description="The name that needs to be deleted"), + username: Annotated[str, Field(description="The name that needs to be deleted")] = Path(..., description="The name that needs to be deleted"), token_api_key: TokenModel = Security( get_token_api_key ), diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py index 752960411104..9668b337d089 100644 --- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py +++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py @@ -40,8 +40,8 @@ async def create_users_with_list_input( async def login_user( self, - username: Annotated[str, Field(strict=True, description="The user name for login")], - password: Annotated[StrictStr, Field(description="The password for login in clear text")], + username: Annotated[str, Field(description="The user name for login")], + password: Annotated[str, Field(description="The password for login in clear text")], ) -> str: """""" ... @@ -56,7 +56,7 @@ async def logout_user( async def get_user_by_name( self, - username: Annotated[StrictStr, Field(description="The name that needs to be fetched. Use user1 for testing.")], + username: Annotated[str, Field(description="The name that needs to be fetched. Use user1 for testing.")], ) -> User: """""" ... @@ -64,7 +64,7 @@ async def get_user_by_name( async def update_user( self, - username: Annotated[StrictStr, Field(description="name that need to be deleted")], + username: Annotated[str, Field(description="name that need to be deleted")], user: Annotated[User, Field(description="Updated user object")], ) -> None: """This can only be done by the logged in user.""" @@ -73,7 +73,7 @@ async def update_user( async def delete_user( self, - username: Annotated[StrictStr, Field(description="The name that needs to be deleted")], + username: Annotated[str, Field(description="The name that needs to be deleted")], ) -> None: """This can only be done by the logged in user.""" ...