diff --git a/python/api_client_generator/_private/rest_api_tools/create_client_and_imports.py b/python/api_client_generator/_private/rest_api_tools/create_client_and_imports.py index 7ff4c81a03839c5c34e5152e246e52430a365365..a221e8b14c49e000fb4171eb1a4af24ecfb3aad0 100644 --- a/python/api_client_generator/_private/rest_api_tools/create_client_and_imports.py +++ b/python/api_client_generator/_private/rest_api_tools/create_client_and_imports.py @@ -27,12 +27,13 @@ def create_client_and_imports( # NOQA: PLR0913 api_name: str, server_url: str, endpoints: CreatedEndpoints, - types_module_path: str | Path, base_class: type[BaseApiClass] | str, base_class_source: str | None = None, + types_module_path: str | Path | None = None, endpoint_decorator: str = DEFAULT_ENDPOINT_REST_DECORATOR_NAME, additional_items_to_import: Sequence[Importable] | None = None, already_imported: list[str] | None = None, + types_generated: bool = True, ) -> GeneratedClass: """ Create a client class and resolve the needed imports. @@ -40,13 +41,14 @@ def create_client_and_imports( # NOQA: PLR0913 Args: api_name: The name of the API. server_url: The server URL for the API. - types_module_path: The path to the module containing types. endpoints: The endpoints description for the API. base_class: The base class for the API client. base_class_source: The source of the base class. + types_module_path: The path to the module containing types. endpoint_decorator: The name of the endpoint decorator to be used. additional_items_to_import: Additional items to import. already_imported: A list of already imported items. + types_generated: Whether types have been generated from the Swagger definition. """ already_imported = already_imported or [] @@ -61,17 +63,19 @@ def create_client_and_imports( # NOQA: PLR0913 base_class_name = base_class if isinstance(base_class, str) else base_class.__name__ already_imported.append(base_class_name) - types_module_path = Path(types_module_path) - root = find_package_root(types_module_path) - full_module_name = compute_full_module_name(types_module_path, root) - - needed_imports.append( - ast.ImportFrom( - module=full_module_name, - names=[ast.alias(name="*")], - level=DEFAULT_IMPORT_LEVEL, + if types_generated: + assert types_module_path is not None, "types_module_path must be provided when types are generated" + types_module_path = Path(types_module_path) + root = find_package_root(types_module_path) + full_module_name = compute_full_module_name(types_module_path, root) + + needed_imports.append( + ast.ImportFrom( + module=full_module_name, + names=[ast.alias(name="*")], + level=DEFAULT_IMPORT_LEVEL, + ) ) - ) additional_imports = import_classes(additional_items_to_import or [], already_imported) needed_imports.extend(additional_imports) diff --git a/python/api_client_generator/_private/rest_api_tools/create_endpoints_for_all_url_paths.py b/python/api_client_generator/_private/rest_api_tools/create_endpoints_for_all_url_paths.py index 838c066b5cc1cde61d1fe25df854bd49200c1ab4..67b5081d3caec6217a62dad38350116568ee5e39 100644 --- a/python/api_client_generator/_private/rest_api_tools/create_endpoints_for_all_url_paths.py +++ b/python/api_client_generator/_private/rest_api_tools/create_endpoints_for_all_url_paths.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +import msgspec + from api_client_generator._private.common.converters import snake_to_camel from api_client_generator._private.common.models_aliased import SwaggerReadyForExtraction from api_client_generator._private.common.openapi_to_python_type import convert_openapi_type_to_python_type @@ -64,7 +66,7 @@ def create_endpoints_for_all_url_paths( else: response = convert_openapi_type_to_python_type(response_schema["type"]) - method = RestApiMethod(**path[method_type]) + method = msgspec.convert(path[method_type], RestApiMethod) endpoints.append( create_endpoint(method_name, url_path, method, response, method_type, asynchronous=asynchronous) diff --git a/python/api_client_generator/_private/rest_api_tools/rest_method_model.py b/python/api_client_generator/_private/rest_api_tools/rest_method_model.py index fe59e92e4a28554f9215f9871885c00c88f553f7..63d97b18ac00bd93142ac2cbfb956d1c1937d54f 100644 --- a/python/api_client_generator/_private/rest_api_tools/rest_method_model.py +++ b/python/api_client_generator/_private/rest_api_tools/rest_method_model.py @@ -1,8 +1,8 @@ from __future__ import annotations -from msgspec import Struct +from typing import Any -from api_client_generator._private.common.models_aliased import AnyJson +from msgspec import Struct, field class RestApiMethod(Struct): @@ -10,5 +10,6 @@ class RestApiMethod(Struct): summary: str description: str operationId: str # NOQA: N815 - responses: AnyJson - parameters: list[AnyJson] | None = None + responses: Any + parameters: Any | None = None + x_response_headers: Any | None = field(name="x-response-headers", default=None) diff --git a/python/api_client_generator/json_rpc/generate_api_description.py b/python/api_client_generator/json_rpc/generate_api_description.py index 46167257229a89bf25561cf5160d554ceb749b0f..9fdb85d05cc240a1dad6aa4fd4370fab150d0c85 100644 --- a/python/api_client_generator/json_rpc/generate_api_description.py +++ b/python/api_client_generator/json_rpc/generate_api_description.py @@ -43,6 +43,7 @@ from api_client_generator._private.description_tools import ( get_params_name_for_endpoint, get_result_name_for_endpoint, is_result_array, + get_last_part_of_ref, ) from api_client_generator._private.export_client_module_to_file import export_module_to_file from api_client_generator.generate_types_from_swagger import generate_types_from_swagger @@ -107,7 +108,14 @@ def generate_api_description( endpoint_description["response_array"] = True assert isinstance(endpoint_description["result"], str), "Result must be a string at this point." - endpoint_description["result"] += "Item" # It will be typed as list[ClasNameResponseItem] + + potential_ref = components[result_name].get("items", {}).get("$ref") + if potential_ref: # This means that the elements of the array are represented by a custom class/model/components, which are contained in the components section. + endpoint_description["result"] = snake_to_camel( + get_last_part_of_ref(components[result_name]["items"]["$ref"]) + ) + else: + endpoint_description["result"] += "Item" api_description[api_name][endpoint_name] = endpoint_description diff --git a/python/api_client_generator/rest/generate_api_client_from_swagger.py b/python/api_client_generator/rest/generate_api_client_from_swagger.py index 92fa4de7204f9c4208dd5e9bd74e61986021d3b7..3d26bd9efa157f626b95c8fa936198a7eca25f17 100644 --- a/python/api_client_generator/rest/generate_api_client_from_swagger.py +++ b/python/api_client_generator/rest/generate_api_client_from_swagger.py @@ -51,26 +51,30 @@ def generate_api_client_from_swagger( # NOQA: PLR0913 openapi_file = openapi_api_definition if isinstance(openapi_api_definition, Path) else Path(openapi_api_definition) output_package = output_package if isinstance(output_package, Path) else Path(output_package) - generate_types_from_swagger(openapi_api_definition, output_package) - openapi = json.loads(openapi_file.read_text()) + types_generated = False # Flag to check if types were generated from the Swagger definition + module_path = None + + if openapi.get("components") is not None: # Situation when swagger not contains components is possible + generate_types_from_swagger(openapi_api_definition, output_package) + types_generated = True + types_module_name = get_types_name_from_components(openapi) + module_path = output_package / f"{types_module_name}.py" server_url = get_api_name_from_server_property(openapi) api_name = hyphen_to_snake(server_url) - types_module_name = get_types_name_from_components(openapi) - module_path = output_package / f"{types_module_name}.py" - endpoints = create_endpoints_for_all_url_paths(openapi, asynchronous=asynchronous) class_and_imports = create_client_and_imports( api_name, server_url, endpoints, - module_path, base_class, base_class_source, + module_path, endpoint_decorator, + types_generated=types_generated, ) client_module = ast.Module(body=[*class_and_imports.imports, class_and_imports.class_def], type_ignores=[])