diff --git a/fastapi_code_generator/parser.py b/fastapi_code_generator/parser.py index 38f9156..a0b26b6 100644 --- a/fastapi_code_generator/parser.py +++ b/fastapi_code_generator/parser.py @@ -330,7 +330,7 @@ def get_argument_list(self, snake_case: bool, path: List[str]) -> List[Argument] for argument in arguments: if positional_argument and argument.required and argument.default is None: argument.default = UsefulStr('...') - positional_argument = argument.required + positional_argument = argument.required or argument.default is not None return arguments @@ -379,6 +379,17 @@ def parse_request_body( Import.from_full_path('starlette.requests.Request') ) elif media_type == 'application/octet-stream': + arguments.append( + Argument( + name='request', # type: ignore + type_hint='Request', # type: ignore + required=True, + ) + ) + self.imports_for_fastapi.append( + Import.from_full_path("fastapi.Request") + ) + elif media_type == 'multipart/form-data': arguments.append( Argument( name='file', # type: ignore diff --git a/tests/data/expected/openapi/default_template/body_and_parameters/main.py b/tests/data/expected/openapi/default_template/body_and_parameters/main.py index 9789cce..47bd8d9 100644 --- a/tests/data/expected/openapi/default_template/body_and_parameters/main.py +++ b/tests/data/expected/openapi/default_template/body_and_parameters/main.py @@ -6,7 +6,7 @@ from typing import List, Optional, Union -from fastapi import FastAPI, Path, Query +from fastapi import FastAPI, Path, Query, UploadFile from starlette.requests import Request from .models import ( @@ -36,6 +36,11 @@ def post_bar(request: Request) -> None: pass +@app.post('/convert', response_model=bytes) +def convert(format: Optional[str] = 'pdf', file: UploadFile = ...) -> bytes: + pass + + @app.get('/foo', response_model=str, tags=['foo']) def get_foo(foo: Optional[str] = None) -> str: pass diff --git a/tests/data/expected/openapi/default_template/upload/main.py b/tests/data/expected/openapi/default_template/upload/main.py new file mode 100644 index 0000000..c7697eb --- /dev/null +++ b/tests/data/expected/openapi/default_template/upload/main.py @@ -0,0 +1,49 @@ +# generated by fastapi-codegen: +# filename: upload.yaml +# timestamp: 2020-06-19T00:00:00+00:00 + +from __future__ import annotations + +from typing import Union + +from fastapi import FastAPI, Request, UploadFile + +from .models import Error + +app = FastAPI( + version='1.0.0', + title='Swagger Petstore', + license={'name': 'MIT'}, + description='API definiton for testing file upload', + servers=[{'url': 'http://petstore.swagger.io/v1'}], +) + + +@app.post( + '/pets/{id}/image/form-data', + response_model=None, + responses={'default': {'model': Error}}, + tags=['pets'], +) +def upload_pet_image_with_form_data( + id: str, file: UploadFile = ... +) -> Union[None, Error]: + """ + Upload image with Form-Data for a pet + """ + pass + + +@app.post( + '/pets/{id}/image/octet-stream', + response_model=None, + responses={'default': {'model': Error}}, + tags=['pets'], +) +def upload_pet_image_with_octet_stream( + id: str, request: Request = ... +) -> Union[None, Error]: + """ + Upload image with octet-stream for a pet + """ + pass diff --git a/tests/data/expected/openapi/default_template/upload/models.py b/tests/data/expected/openapi/default_template/upload/models.py new file mode 100644 index 0000000..25baeae --- /dev/null +++ b/tests/data/expected/openapi/default_template/upload/models.py @@ -0,0 +1,12 @@ +# generated by fastapi-codegen: +# filename: upload.yaml +# timestamp: 2020-06-19T00:00:00+00:00 + +from __future__ import annotations + +from pydantic import BaseModel + + +class Error(BaseModel): + code: int + message: str diff --git a/tests/data/expected/openapi/remote_ref/body_and_parameters/main.py b/tests/data/expected/openapi/remote_ref/body_and_parameters/main.py index 4b436c5..49ef84a 100644 --- a/tests/data/expected/openapi/remote_ref/body_and_parameters/main.py +++ b/tests/data/expected/openapi/remote_ref/body_and_parameters/main.py @@ -6,7 +6,7 @@ from typing import List, Optional, Union -from fastapi import FastAPI, Path, Query, UploadFile +from fastapi import FastAPI, Path, Query, Request from .models import Error, Pet, PetForm @@ -106,7 +106,7 @@ def put_pets_pet_id( tags=['pets'], ) def upload_pet_image( - pet_id: str = Path(..., alias='petId'), file: UploadFile = ... + pet_id: str = Path(..., alias='petId'), request: Request = ... ) -> Union[None, str]: """ Upload image for a pet diff --git a/tests/data/openapi/default_template/body_and_parameters.yaml b/tests/data/openapi/default_template/body_and_parameters.yaml index 340fbf3..a373049 100644 --- a/tests/data/openapi/default_template/body_and_parameters.yaml +++ b/tests/data/openapi/default_template/body_and_parameters.yaml @@ -337,6 +337,29 @@ paths: schema: $ref: '#/components/schemas/Pet' required: true + /convert: + post: + operationId: convert + parameters: + - in: query + name: format + schema: + type: string + default: pdf + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + content: + application/octet-stream: + schema: + type: string + format: binary components: parameters: MyParam: diff --git a/tests/data/openapi/default_template/upload.yaml b/tests/data/openapi/default_template/upload.yaml new file mode 100644 index 0000000..6e591af --- /dev/null +++ b/tests/data/openapi/default_template/upload.yaml @@ -0,0 +1,79 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + description: API definiton for testing file upload + +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets/{id}/image/octet-stream: + post: + summary: Upload image with octet-stream for a pet + operationId: uploadPetImageWithOctetStream + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '201': + description: empty response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{id}/image/form-data: + post: + summary: Upload image with Form-Data for a pet + operationId: uploadPetImageWithFormData + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet + schema: + type: string + requestBody: + content: + multipart/form-data: + schema: + type: string + format: binary + responses: + '201': + description: empty response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/tests/test_generate.py b/tests/test_generate.py index 095cf1c..b6278e3 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -1,3 +1,4 @@ +import shutil from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import call @@ -152,28 +153,35 @@ def test_generate_using_routers(oas_file): ) @freeze_time("2023-04-11") def test_generate_modify_specific_routers(oas_file): - test_dir = EXPECTED_DIR / 'openapi/modify_specific_routers/modified' - output_dir = Path(test_dir) / oas_file.stem - Path(output_dir / "routers").mkdir(parents=True, exist_ok=True) - generate_code( - input_name=oas_file.name, - input_text=oas_file.read_text(), - output_dir=output_dir, - template_dir=BUILTIN_MODULAR_TEMPLATE_DIR, - generate_routers=True, - specify_tags=SPECIFIC_TAGS, - ) - expected_dir = ( - EXPECTED_DIR / 'openapi/modify_specific_routers/expected' / oas_file.stem - ) - output_files = sorted(list(output_dir.glob('*'))) - expected_files = sorted(list(expected_dir.glob('*'))) - assert [f.name for f in output_files] == [f.name for f in expected_files] - for output_file, expected_file in zip(output_files, expected_files): - if output_file.is_dir() and expected_file.is_dir(): - output_inners = sorted(list((output_dir / output_file).glob('*'))) - expected_inners = sorted(list((expected_dir / expected_file).glob('*'))) - for output_inner, expected_inner in zip(output_inners, expected_inners): - assert output_inner.read_text() == expected_inner.read_text() - else: - assert output_file.read_text() == expected_file.read_text() + with TemporaryDirectory() as tmp_dir: + output_dir = Path(tmp_dir) / (oas_file.stem + '_modify_specific_routers') + # modified contains generated source files. Some of them will be regenerated in this test. + modified_dir = ( + EXPECTED_DIR + / 'openapi/modify_specific_routers/modified/using_routers_example' + ) + shutil.copytree(modified_dir, output_dir) + + Path(output_dir / "routers").mkdir(parents=True, exist_ok=True) + generate_code( + input_name=oas_file.name, + input_text=oas_file.read_text(), + output_dir=output_dir, + template_dir=BUILTIN_MODULAR_TEMPLATE_DIR, + generate_routers=True, + specify_tags=SPECIFIC_TAGS, + ) + expected_dir = ( + EXPECTED_DIR / 'openapi/modify_specific_routers/expected' / oas_file.stem + ) + output_files = sorted(list(output_dir.glob('*'))) + expected_files = sorted(list(expected_dir.glob('*'))) + assert [f.name for f in output_files] == [f.name for f in expected_files] + for output_file, expected_file in zip(output_files, expected_files): + if output_file.is_dir() and expected_file.is_dir(): + output_inners = sorted(list((output_dir / output_file).glob('*'))) + expected_inners = sorted(list((expected_dir / expected_file).glob('*'))) + for output_inner, expected_inner in zip(output_inners, expected_inners): + assert output_inner.read_text() == expected_inner.read_text() + else: + assert output_file.read_text() == expected_file.read_text()