diff --git a/README.md b/README.md index 4a886d299..dcab6a9da 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ _Be forewarned, this is a beta-level feature in the sense that the API exposed i For a full example you can look at the `end_to_end_tests` directory which has `baseline_openapi_3.0.json` and `baseline_openapi_3.1.yaml` files. The "golden-record" in that same directory is the generated client from either of those OpenAPI documents. +Endpoint argument docstrings use the parameter's description when it is non-empty, falling back to the schema's description. +Reusable model docstrings retain the schema's description. + ## Configuration You can pass a YAML (or JSON) file to openapi-python-client with the `--config` option in order to change some behavior. diff --git a/end_to_end_tests/functional_tests/generated_code_execution/test_docstrings.py b/end_to_end_tests/functional_tests/generated_code_execution/test_docstrings.py index 68e4cbd3e..eccd53a90 100644 --- a/end_to_end_tests/functional_tests/generated_code_execution/test_docstrings.py +++ b/end_to_end_tests/functional_tests/generated_code_execution/test_docstrings.py @@ -1,5 +1,7 @@ from typing import Any +import pytest + from end_to_end_tests.functional_tests.helpers import ( with_generated_client_fixture, with_generated_code_import, @@ -161,3 +163,114 @@ def test_params(self, get_attribute_by_index_sync): "index (int):", "fries (bool | Unset): Do you want fries with that?", ] + + +@with_generated_client_fixture( + """ +paths: + /tasks/{id}: + parameters: + - name: id + in: path + required: true + description: Task to export. + schema: + type: string + description: A task identifier. + get: + operationId: exportTasks + parameters: + - name: since + in: query + description: Only include tasks changed after this timestamp. + schema: + type: integer + description: Unix timestamp in milliseconds. + - name: empty + in: query + description: "" + schema: + type: string + description: Schema fallback. + - name: undescribed + in: query + schema: + type: string + - name: trace + in: header + description: Trace this export. + schema: + type: string + - name: session + in: cookie + description: Session for this export. + schema: + type: string + - $ref: "#/components/parameters/State" + - name: filters + in: query + description: Filter the exported tasks. + schema: + $ref: "#/components/schemas/Filters" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Filters" + /other: + get: + operationId: getOther + parameters: + - name: filters + in: query + schema: + $ref: "#/components/schemas/Filters" + responses: + "200": + description: OK +components: + parameters: + State: + name: state + in: query + description: Select tasks in this state. + schema: + $ref: "#/components/schemas/State" + schemas: + State: + type: string + enum: [open, closed] + description: A task state. + Filters: + type: object + description: Reusable task filters. + properties: + label: + type: string + description: A task label. +""" +) +class TestParameterDocstrings: + @pytest.mark.parametrize("function_name", ["sync", "sync_detailed", "asyncio", "asyncio_detailed"]) + def test_parameter_descriptions(self, generated_client, function_name): + function = generated_client.import_symbol(".api.default.export_tasks", function_name) + assert set(DocstringParser(function).get_section("Args:")) == { + "id (str): Task to export.", + "since (int | Unset): Only include tasks changed after this timestamp.", + "empty (str | Unset): Schema fallback.", + "undescribed (str | Unset):", + "trace (str | Unset): Trace this export.", + "session (str | Unset): Session for this export.", + "state (State | Unset): Select tasks in this state.", + "filters (Filters | Unset): Filter the exported tasks.", + } + + def test_shared_schema_descriptions(self, generated_client): + filters = generated_client.import_symbol(".models.filters", "Filters") + assert DocstringParser(filters).lines[0] == "Reusable task filters." + assert DocstringParser(filters).get_section("Attributes:") == ["label (str | Unset): A task label."] + + function = generated_client.import_symbol(".api.default.get_other", "sync_detailed") + assert DocstringParser(function).get_section("Args:") == ["filters (Filters | Unset): Reusable task filters."] diff --git a/end_to_end_tests/golden-records/escapes-client/escapes_client/api/printtag_escape/with_braces_path.py b/end_to_end_tests/golden-records/escapes-client/escapes_client/api/printtag_escape/with_braces_path.py index cbbbc3541..c486e021e 100644 --- a/end_to_end_tests/golden-records/escapes-client/escapes_client/api/printtag_escape/with_braces_path.py +++ b/end_to_end_tests/golden-records/escapes-client/escapes_client/api/printtag_escape/with_braces_path.py @@ -71,7 +71,7 @@ def sync_detailed( Attempting to escape docstring \"\"\" print('uh oh') Args: - param (str): + param (str): " print('escape param description') printescape_param_name (str | Unset): body (WithBracesPathBody): @@ -108,7 +108,7 @@ async def asyncio_detailed( Attempting to escape docstring \"\"\" print('uh oh') Args: - param (str): + param (str): " print('escape param description') printescape_param_name (str | Unset): body (WithBracesPathBody): diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index c16f03f2f..c0a328b97 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from typing import Any, Protocol +from attrs import evolve from pydantic import ValidationError from .. import schema as oai @@ -302,6 +303,9 @@ def add_parameters( schemas = new_schemas + if param.description: + prop = evolve(prop, description=param.description) + location_error = prop.validate_location(param.param_in) if location_error is not None: location_error.data = param diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 49e278437..a09f0082a 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -177,6 +177,7 @@ def parameter_from_data( new_param = Parameter( name=name.get_untrusted_value(), + description=data.description.get_untrusted_value() if data.description is not None else None, required=data.required, explode=data.explode, style=data.style.get_untrusted_value() if data.style is not None else None,