clientele 2.2.2__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {clientele-2.2.2 → clientele-2.3.0}/CHANGELOG.md +7 -0
- {clientele-2.2.2 → clientele-2.3.0}/PKG-INFO +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/SECURITY.md +2 -2
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/__init__.py +2 -1
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/client.py +12 -10
- clientele-2.3.0/clientele/api/params.py +37 -0
- clientele-2.2.2/clientele/api/requests.py → clientele-2.3.0/clientele/api/request_context.py +20 -0
- clientele-2.3.0/clientele/generators/api/__init__.py +1 -0
- clientele-2.2.2/clientele/generators/base_clients.py → clientele-2.3.0/clientele/generators/api/clients.py +78 -72
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/generator.py +6 -8
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/generator.py +2 -1
- {clientele-2.2.2/clientele/generators/shared/generators → clientele-2.3.0/clientele/generators/shared}/schemas.py +39 -4
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/shared/utils.py +38 -23
- {clientele-2.2.2 → clientele-2.3.0}/clientele/graphql/client.py +5 -5
- clientele-2.3.0/clientele/settings.py +5 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/CHANGELOG.md +7 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-examples.md +24 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/install.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/best.json +9 -0
- {clientele-2.2.2 → clientele-2.3.0}/pyproject.toml +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client.py +54 -1
- clientele-2.2.2/tests/api/test_requests.py → clientele-2.3.0/tests/api/test_request_context.py +35 -33
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_stream.py +4 -2
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/client.py +4 -2
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/MANIFEST.md +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/client.py +4 -2
- clientele-2.3.0/tests/cache/__init__.py +0 -0
- clientele-2.3.0/tests/fixtures/openapi_examples/link-example.json +0 -0
- clientele-2.3.0/tests/generators/__init__.py +0 -0
- clientele-2.3.0/tests/generators/api/__init__.py +0 -0
- clientele-2.3.0/tests/generators/api/test_clients.py +118 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_fixture_schemas.py +36 -4
- clientele-2.3.0/tests/generators/api/test_generator.py +332 -0
- clientele-2.3.0/tests/generators/basic/__init__.py +0 -0
- clientele-2.3.0/tests/generators/basic/test_generator.py +42 -0
- clientele-2.3.0/tests/generators/shared/__init__.py +0 -0
- clientele-2.2.2/tests/generators/test_schemas_coverage.py → clientele-2.3.0/tests/generators/shared/test_schemas.py +41 -2
- clientele-2.3.0/tests/generators/shared/test_utils.py +253 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/generators/test_base.py +1 -1
- {clientele-2.2.2 → clientele-2.3.0}/tests/generators/test_cicerone_compat.py +160 -1
- clientele-2.3.0/tests/graphql/__init__.py +0 -0
- clientele-2.3.0/tests/http/__init__.py +0 -0
- clientele-2.3.0/tests/http/backends/__init__.py +0 -0
- clientele-2.3.0/tests/mypy/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/test_cli.py +87 -20
- clientele-2.3.0/tests/test_settings.py +24 -0
- {clientele-2.2.2 → clientele-2.3.0}/uv.lock +2 -2
- clientele-2.2.2/clientele/generators/api/__init__.py +0 -1
- clientele-2.2.2/clientele/generators/api/generators/__init__.py +0 -1
- clientele-2.2.2/clientele/generators/api/generators/clients.py +0 -146
- clientele-2.2.2/clientele/generators/api/generators/schemas.py +0 -3
- clientele-2.2.2/clientele/generators/schema_utils.py +0 -42
- clientele-2.2.2/clientele/settings.py +0 -10
- clientele-2.2.2/clientele/utils.py +0 -38
- clientele-2.2.2/tests/generators/framework/test_framework_generator_coverage.py +0 -111
- clientele-2.2.2/tests/generators/framework/test_framework_generator_integration.py +0 -170
- clientele-2.2.2/tests/generators/shared/test_utils.py +0 -112
- clientele-2.2.2/tests/generators/shared/test_utils_coverage.py +0 -163
- clientele-2.2.2/tests/generators/test_base_and_basic_coverage.py +0 -157
- clientele-2.2.2/tests/test_additional_coverage.py +0 -72
- clientele-2.2.2/tests/test_cli_coverage.py +0 -86
- clientele-2.2.2/tests/test_cli_full_coverage.py +0 -124
- clientele-2.2.2/tests/test_edge_case_coverage.py +0 -65
- clientele-2.2.2/tests/test_generator_coverage.py +0 -333
- clientele-2.2.2/tests/test_settings.py +0 -32
- clientele-2.2.2/tests/test_utils.py +0 -29
- {clientele-2.2.2 → clientele-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.github/compatibility.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.github/dependabot.yml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.github/pull_request_template.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.github/workflows/ci.yml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/.gitignore +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/CODE_OF_CONDUCT.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/CONTRIBUTING.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/CONTRIBUTORS.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/LICENSE +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/Makefile +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/PUBLISHING.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/README.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/exceptions.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/stream/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/stream/parser.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/api/type_utils.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/backends.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/decorator.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/key_generator.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/types.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/cli.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/README.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/api_get_method.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/api_post_method.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/client_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/config_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/manifest.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/pyproject.toml.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_class.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_helpers.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_root_model.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_type_alias.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schemas_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/writer.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/base.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/client_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/config_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/manifest.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/schemas_py.jinja2 +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/writer.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/cicerone_compat.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/shared/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/graphql/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/aiohttp_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/backends.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/fake_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/httpx_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/niquests_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/requests_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/response.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/http/status_codes.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/mypy.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/py.typed +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/retries/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/retries/decorators.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/schemas.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/clientele/testing.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/CNAME +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-async.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-authentication.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-cache.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-configuration.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-direct-requests.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-exceptions.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-graphql.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-http-backends.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-logging.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-overview.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-retries.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-stream.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/api-testing.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/clientele_generate.gif +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/clientele_header.png +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/favicon.png +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/index.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/injected-parameters.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/openapi-cli.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/server-django-ninja.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/server-drf.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/docs/server-fastapi.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/complex_schemas.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/issue_248.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/simple.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/test_303.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/mkdocs.yml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/README.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/api.py +0 -0
- {clientele-2.2.2/clientele/generators/shared/generators → clientele-2.3.0/server_examples/django_ninja/client}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/client.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/pyproject.toml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/schemas.py +0 -0
- {clientele-2.2.2/server_examples/django_ninja/client → clientele-2.3.0/server_examples/django_ninja/example_project}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/example_project/settings.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/example_project/urls.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/manage.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/openapi.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/README.md +0 -0
- {clientele-2.2.2/server_examples/django_ninja/example_project → clientele-2.3.0/server_examples/django_rest_framework/client}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/client.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/pyproject.toml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/schemas.py +0 -0
- {clientele-2.2.2/server_examples/django_rest_framework/client → clientele-2.3.0/server_examples/django_rest_framework/example_project}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/example_project/settings.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/example_project/urls.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/manage.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/openapi.yaml +0 -0
- {clientele-2.2.2/server_examples/django_rest_framework/example_project → clientele-2.3.0/server_examples/django_rest_framework/users}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/apps.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/migrations/0001_initial.py +0 -0
- {clientele-2.2.2/server_examples/django_rest_framework/users → clientele-2.3.0/server_examples/django_rest_framework/users/migrations}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/models.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/serializers.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/views.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/README.md +0 -0
- {clientele-2.2.2/server_examples/django_rest_framework/users/migrations → clientele-2.3.0/server_examples/fastapi/client}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/client.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/pyproject.toml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/schemas.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/main.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/openapi.json +0 -0
- {clientele-2.2.2/server_examples/fastapi/client → clientele-2.3.0/tests}/__init__.py +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/api}/__init__.py +0 -0
- {clientele-2.2.2/tests/api_clients/async_test_client → clientele-2.3.0/tests/api/retries}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/retries/test_retry.py +0 -0
- {clientele-2.2.2/tests/api_clients/test_basic_client → clientele-2.3.0/tests/api/stream}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/stream/test_parser.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client_logging.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client_singleton.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_response_parser.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_typed_dict.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_direct_requests.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_stream_httpx.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_type_utils.py +0 -0
- {clientele-2.2.2/tests/api_clients/test_client → clientele-2.3.0/tests/api_clients/async_test_client}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/pyproject.toml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/schemas.py +0 -0
- {clientele-2.2.2/tests/generators/shared → clientele-2.3.0/tests/api_clients/test_basic_client}/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/client.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/schemas.py +0 -0
- /clientele-2.2.2/tests/fixtures/openapi_examples/link-example.json → /clientele-2.3.0/tests/api_clients/test_client/__init__.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/pyproject.toml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/schemas.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/cache/fixtures.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_backends.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_decorator.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_integration.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_key_generator.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/callback_example.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/complex_api.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/README.md +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/api-with-examples.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/callback-example.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/non-oauth-scopes.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/petstore-expanded.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/petstore.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/tictactoe.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/uspto.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/webhook-example.json +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/petstore_openapi3.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/1password.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/ably.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/google.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/medium.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/spacetraders.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/twilio.yaml +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/regression/dep_query_alias.json +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_complex_schemas.py +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_issue_248.py +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_nullable_fields.py +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_openapi_31_null_type.py +0 -0
- {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_ref_handling.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/generators/integration_utils.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/graphql/test_graphql_client.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_aiohttp_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_backend_config.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_fake_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_httpx_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_niquests_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_requests_backend.py +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/mypy/mypy_plugin_test_config.ini +0 -0
- {clientele-2.2.2 → clientele-2.3.0}/tests/mypy/test_mypy_plugin.py +0 -0
- /clientele-2.2.2/tests/test_testing_utilities.py → /clientele-2.3.0/tests/test_testing.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
## 2.3.0
|
|
4
|
+
|
|
5
|
+
- Add `clientele.api.Query(alias=...)` for annotating a query parameter with its wire-format name via `typing.Annotated`, so a Python-friendly (snake_case) parameter name can be used without breaking the actual HTTP request. Clients generated from an OpenAPI spec now apply this automatically whenever a query parameter's name isn't already valid snake_case (e.g. `orderBy` → `order_by`).
|
|
6
|
+
- Fix generated/decorated functions sending query parameters under their sanitized Python name instead of the original API name whenever that name required sanitization (e.g. a spec's `yourInput` query parameter was sent as `?your_input=...` instead of `?yourInput=...`).
|
|
7
|
+
- Major refactoring of the project structure including removing of redundant code paths (mostly left over from the old generator logic).
|
|
8
|
+
- Reorganisation of the tests and their files to mirror the codebase more.
|
|
9
|
+
|
|
3
10
|
## 2.2.2
|
|
4
11
|
|
|
5
12
|
- Fix `HttpxHTTPBackend.close()`/`aclose()` not resetting the client reference — a closed-but-non-None client was returned on subsequent requests, causing `RuntimeError: Cannot send a request, as the client has been closed`.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from clientele.api.client import APIClient
|
|
2
2
|
from clientele.api.config import BaseConfig, get_default_config
|
|
3
3
|
from clientele.api.exceptions import APIException
|
|
4
|
+
from clientele.api.params import Query
|
|
4
5
|
from clientele.http.status_codes import codes
|
|
5
6
|
|
|
6
|
-
__all__ = ["APIException", "APIClient", "BaseConfig", "codes", "get_default_config"]
|
|
7
|
+
__all__ = ["APIException", "APIClient", "BaseConfig", "Query", "codes", "get_default_config"]
|
|
@@ -11,7 +11,7 @@ import pydantic
|
|
|
11
11
|
|
|
12
12
|
from clientele.api import config as api_config
|
|
13
13
|
from clientele.api import exceptions as api_exceptions
|
|
14
|
-
from clientele.api import
|
|
14
|
+
from clientele.api import request_context, type_utils
|
|
15
15
|
from clientele.http import httpx_backend
|
|
16
16
|
from clientele.http import response as http_response
|
|
17
17
|
|
|
@@ -281,7 +281,7 @@ class APIClient:
|
|
|
281
281
|
streaming_response: bool = False,
|
|
282
282
|
) -> typing.Callable[[typing.Any], typing.Any]:
|
|
283
283
|
def decorator(func: typing.Any) -> typing.Any:
|
|
284
|
-
context =
|
|
284
|
+
context = request_context.build_request_context(
|
|
285
285
|
method,
|
|
286
286
|
path,
|
|
287
287
|
func,
|
|
@@ -319,8 +319,8 @@ class APIClient:
|
|
|
319
319
|
return decorator
|
|
320
320
|
|
|
321
321
|
def _prepare_call(
|
|
322
|
-
self, context:
|
|
323
|
-
) ->
|
|
322
|
+
self, context: request_context.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
323
|
+
) -> request_context.PreparedCall:
|
|
324
324
|
"""
|
|
325
325
|
Parse function arguments into an HTTP request specification.
|
|
326
326
|
|
|
@@ -373,6 +373,8 @@ class APIClient:
|
|
|
373
373
|
else:
|
|
374
374
|
query_params = {k: v for k, v in request_arguments.items() if k != "data"}
|
|
375
375
|
query_params.update(extra_kwargs)
|
|
376
|
+
if context.query_alias_map:
|
|
377
|
+
query_params = {context.query_alias_map.get(k, k): v for k, v in query_params.items()}
|
|
376
378
|
|
|
377
379
|
# Filter out None values from query params to avoid adding empty parameters to the URL
|
|
378
380
|
if query_params:
|
|
@@ -400,7 +402,7 @@ class APIClient:
|
|
|
400
402
|
url_path = self._substitute_path(context.path_template, path_params)
|
|
401
403
|
result_annotation = context.type_hints.get("result", inspect._empty)
|
|
402
404
|
|
|
403
|
-
return
|
|
405
|
+
return request_context.PreparedCall(
|
|
404
406
|
context=context,
|
|
405
407
|
bound_arguments=bound_arguments,
|
|
406
408
|
call_arguments=call_arguments,
|
|
@@ -412,7 +414,7 @@ class APIClient:
|
|
|
412
414
|
)
|
|
413
415
|
|
|
414
416
|
def _execute_sync(
|
|
415
|
-
self, context:
|
|
417
|
+
self, context: request_context.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
416
418
|
) -> typing.Any:
|
|
417
419
|
prepared = self._prepare_call(context, args, kwargs)
|
|
418
420
|
response = self._send_request(
|
|
@@ -427,7 +429,7 @@ class APIClient:
|
|
|
427
429
|
return result
|
|
428
430
|
|
|
429
431
|
async def _execute_async(
|
|
430
|
-
self, context:
|
|
432
|
+
self, context: request_context.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
431
433
|
) -> typing.Any:
|
|
432
434
|
prepared = self._prepare_call(context, args, kwargs)
|
|
433
435
|
response = await self._send_request_async(
|
|
@@ -442,7 +444,7 @@ class APIClient:
|
|
|
442
444
|
return await result
|
|
443
445
|
|
|
444
446
|
async def _execute_async_stream(
|
|
445
|
-
self, context:
|
|
447
|
+
self, context: request_context.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
446
448
|
) -> typing.Any:
|
|
447
449
|
"""
|
|
448
450
|
Execute an async streaming request.
|
|
@@ -495,7 +497,7 @@ class APIClient:
|
|
|
495
497
|
return result
|
|
496
498
|
|
|
497
499
|
def _execute_sync_stream(
|
|
498
|
-
self, context:
|
|
500
|
+
self, context: request_context.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
499
501
|
) -> typing.Any:
|
|
500
502
|
"""
|
|
501
503
|
Execute a sync streaming request.
|
|
@@ -658,7 +660,7 @@ class APIClient:
|
|
|
658
660
|
|
|
659
661
|
def _finalise_call(
|
|
660
662
|
self,
|
|
661
|
-
prepared:
|
|
663
|
+
prepared: request_context.PreparedCall,
|
|
662
664
|
response: http_response.Response,
|
|
663
665
|
) -> typing.Any:
|
|
664
666
|
parsed_result = self._parse_response(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Query:
|
|
5
|
+
"""Marks a decorated function parameter as an HTTP query parameter with a wire-format alias.
|
|
6
|
+
|
|
7
|
+
Use via ``typing.Annotated`` when the Python parameter name (e.g. to satisfy naming
|
|
8
|
+
conventions such as ruff's N803) differs from the query parameter name expected by the API.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
```python
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
from clientele import api
|
|
15
|
+
|
|
16
|
+
client = api.APIClient(base_url="https://pokeapi.co/api/v2/")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@client.get("/pokemon/")
|
|
20
|
+
def get_pokemon_page(result: dict, order_by: Annotated[str, api.Query(alias="orderBy")]) -> dict:
|
|
21
|
+
return result
|
|
22
|
+
```
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__slots__ = ("alias",)
|
|
26
|
+
|
|
27
|
+
def __init__(self, *, alias: str) -> None:
|
|
28
|
+
self.alias = alias
|
|
29
|
+
|
|
30
|
+
def __repr__(self) -> str:
|
|
31
|
+
return f"Query(alias={self.alias!r})"
|
|
32
|
+
|
|
33
|
+
def __eq__(self, other: object) -> bool:
|
|
34
|
+
return isinstance(other, Query) and self.alias == other.alias
|
|
35
|
+
|
|
36
|
+
def __hash__(self) -> int:
|
|
37
|
+
return hash(("clientele.api.Query", self.alias))
|
clientele-2.2.2/clientele/api/requests.py → clientele-2.3.0/clientele/api/request_context.py
RENAMED
|
@@ -7,6 +7,7 @@ import typing
|
|
|
7
7
|
import pydantic
|
|
8
8
|
|
|
9
9
|
from clientele.api import type_utils
|
|
10
|
+
from clientele.api.params import Query
|
|
10
11
|
from clientele.http import response as http_response
|
|
11
12
|
from clientele.http import status_codes
|
|
12
13
|
|
|
@@ -52,6 +53,7 @@ class RequestContext(pydantic.BaseModel):
|
|
|
52
53
|
typing.Callable[[http_response.Response], typing.Any] | typing.Callable[[str], typing.Any] | None
|
|
53
54
|
) = None
|
|
54
55
|
streaming: bool = False
|
|
56
|
+
query_alias_map: dict[str, str] = {}
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def validate_result_parameter(
|
|
@@ -124,6 +126,23 @@ def validate_result_parameter(
|
|
|
124
126
|
)
|
|
125
127
|
|
|
126
128
|
|
|
129
|
+
def _extract_query_alias_map(type_hints: dict[str, typing.Any]) -> dict[str, str]:
|
|
130
|
+
"""
|
|
131
|
+
Scans resolved type hints for `Annotated[X, Query(alias=...)]` metadata.
|
|
132
|
+
|
|
133
|
+
Returns a mapping from the Python parameter name to the wire-format query
|
|
134
|
+
parameter name declared via `Query(alias=...)`.
|
|
135
|
+
"""
|
|
136
|
+
alias_map: dict[str, str] = {}
|
|
137
|
+
for name, annotation in type_hints.items():
|
|
138
|
+
if typing.get_origin(annotation) is not typing.Annotated:
|
|
139
|
+
continue
|
|
140
|
+
for meta in getattr(annotation, "__metadata__", ()):
|
|
141
|
+
if isinstance(meta, Query):
|
|
142
|
+
alias_map[name] = meta.alias
|
|
143
|
+
return alias_map
|
|
144
|
+
|
|
145
|
+
|
|
127
146
|
def build_request_context(
|
|
128
147
|
method: str,
|
|
129
148
|
path: str,
|
|
@@ -175,6 +194,7 @@ def build_request_context(
|
|
|
175
194
|
response_map=response_map,
|
|
176
195
|
response_parser=response_parser,
|
|
177
196
|
streaming=streaming,
|
|
197
|
+
query_alias_map=_extract_query_alias_map(type_hints),
|
|
178
198
|
)
|
|
179
199
|
|
|
180
200
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# API generator for decorator-based clients
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Generates the client.py file for the api generator."""
|
|
2
2
|
|
|
3
3
|
import collections
|
|
4
4
|
import typing
|
|
@@ -8,10 +8,20 @@ from cicerone.spec import openapi_spec as cicerone_openapi_spec
|
|
|
8
8
|
from rich import console as rich_console
|
|
9
9
|
|
|
10
10
|
from clientele.generators import cicerone_compat
|
|
11
|
-
from clientele.generators.
|
|
11
|
+
from clientele.generators.api import writer
|
|
12
|
+
from clientele.generators.shared import schemas, utils
|
|
12
13
|
|
|
13
14
|
console = rich_console.Console()
|
|
14
15
|
|
|
16
|
+
# Template used for each HTTP method when rendering client functions
|
|
17
|
+
METHOD_TEMPLATE_MAP = {
|
|
18
|
+
"get": "api_get_method.jinja2",
|
|
19
|
+
"delete": "api_get_method.jinja2",
|
|
20
|
+
"post": "api_post_method.jinja2",
|
|
21
|
+
"put": "api_post_method.jinja2",
|
|
22
|
+
"patch": "api_post_method.jinja2",
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
|
|
16
26
|
class ParametersResponse(pydantic.BaseModel):
|
|
17
27
|
# Parameters that need to be passed in the URL query
|
|
@@ -22,88 +32,50 @@ class ParametersResponse(pydantic.BaseModel):
|
|
|
22
32
|
headers_args: dict[str, str]
|
|
23
33
|
# Mapping from sanitized Python name to original API parameter name
|
|
24
34
|
param_name_map: dict[str, str] = {}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# Separate required and optional parameters
|
|
30
|
-
# Python requires all required parameters (no default) before optional ones (with default)
|
|
31
|
-
required_args = []
|
|
32
|
-
optional_args = []
|
|
33
|
-
for k, v in args:
|
|
34
|
-
if v.startswith("typing.Optional["):
|
|
35
|
-
optional_args.append(f"{k}: {v} = None")
|
|
36
|
-
else:
|
|
37
|
-
required_args.append(f"{k}: {v}")
|
|
38
|
-
# Return required parameters first, then optional ones
|
|
39
|
-
return ", ".join(required_args + optional_args)
|
|
35
|
+
# Keys (from path_args/query_args) that are optional. Tracked explicitly rather than
|
|
36
|
+
# sniffed from the type string, since aliased query args wrap their type in
|
|
37
|
+
# `typing.Annotated[...]`, which would otherwise hide a `typing.Optional[` prefix.
|
|
38
|
+
optional_keys: set[str] = set()
|
|
40
39
|
|
|
41
40
|
def get_required_args_as_string(self) -> str:
|
|
42
|
-
"""Get only required parameters (those
|
|
41
|
+
"""Get only required parameters (those not in optional_keys)."""
|
|
43
42
|
args = list(self.path_args.items()) + list(self.query_args.items())
|
|
44
|
-
required_args = []
|
|
45
|
-
for k, v in args:
|
|
46
|
-
if not v.startswith("typing.Optional["):
|
|
47
|
-
required_args.append(f"{k}: {v}")
|
|
43
|
+
required_args = [f"{k}: {v}" for k, v in args if k not in self.optional_keys]
|
|
48
44
|
return ", ".join(required_args) if required_args else ""
|
|
49
45
|
|
|
50
46
|
def get_optional_args_as_string(self) -> str:
|
|
51
|
-
"""Get only optional parameters (those
|
|
47
|
+
"""Get only optional parameters (those in optional_keys)."""
|
|
52
48
|
args = list(self.path_args.items()) + list(self.query_args.items())
|
|
53
|
-
optional_args = []
|
|
54
|
-
for k, v in args:
|
|
55
|
-
if v.startswith("typing.Optional["):
|
|
56
|
-
optional_args.append(f"{k}: {v} = None")
|
|
49
|
+
optional_args = [f"{k}: {v} = None" for k, v in args if k in self.optional_keys]
|
|
57
50
|
return ", ".join(optional_args) if optional_args else ""
|
|
58
51
|
|
|
59
|
-
def get_required_path_args_as_string(self) -> str:
|
|
60
|
-
"""Get only required path parameters (for clientele-api generator)."""
|
|
61
|
-
required_args = []
|
|
62
|
-
for k, v in self.path_args.items():
|
|
63
|
-
if not v.startswith("typing.Optional["):
|
|
64
|
-
required_args.append(f"{k}: {v}")
|
|
65
|
-
return ", ".join(required_args) if required_args else ""
|
|
66
|
-
|
|
67
|
-
def has_query_args(self) -> bool:
|
|
68
|
-
"""Check if there are any query parameters."""
|
|
69
|
-
return len(self.query_args) > 0
|
|
70
|
-
|
|
71
52
|
|
|
72
|
-
class
|
|
53
|
+
class ClientsGenerator:
|
|
73
54
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
Handles all the content generated in the client.py file for the api generator.
|
|
56
|
+
This generates decorator-based client functions instead of traditional functions.
|
|
76
57
|
"""
|
|
77
58
|
|
|
78
|
-
method_template_map: dict[str, str]
|
|
79
59
|
results: dict[str, int]
|
|
80
60
|
spec: cicerone_openapi_spec.OpenAPISpec
|
|
81
61
|
output_dir: str
|
|
82
|
-
schemas_generator:
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
schemas_generator: schemas.SchemasGenerator
|
|
63
|
+
# Status codes per generated function, used to build response_map arguments
|
|
64
|
+
function_and_status_codes_bundle: dict[str, dict[str, str]]
|
|
85
65
|
|
|
86
66
|
def __init__(
|
|
87
67
|
self,
|
|
88
68
|
spec: cicerone_openapi_spec.OpenAPISpec,
|
|
89
69
|
output_dir: str,
|
|
90
|
-
schemas_generator:
|
|
91
|
-
http_generator: typing.Any,
|
|
70
|
+
schemas_generator: schemas.SchemasGenerator,
|
|
92
71
|
asyncio: bool,
|
|
93
72
|
) -> None:
|
|
94
73
|
self.spec = spec
|
|
95
74
|
self.output_dir = output_dir
|
|
96
75
|
self.results = collections.defaultdict(int)
|
|
97
76
|
self.schemas_generator = schemas_generator
|
|
98
|
-
self.http_generator = http_generator
|
|
99
77
|
self.asyncio = asyncio
|
|
100
|
-
self.
|
|
101
|
-
get="get_method.jinja2",
|
|
102
|
-
delete="get_method.jinja2",
|
|
103
|
-
post="post_method.jinja2",
|
|
104
|
-
put="post_method.jinja2",
|
|
105
|
-
patch="post_method.jinja2",
|
|
106
|
-
)
|
|
78
|
+
self.function_and_status_codes_bundle = {}
|
|
107
79
|
|
|
108
80
|
def generate_paths(self) -> None:
|
|
109
81
|
# Check if the spec has paths
|
|
@@ -127,6 +99,7 @@ class BaseClientsGenerator:
|
|
|
127
99
|
path_args = {}
|
|
128
100
|
headers_args = {}
|
|
129
101
|
param_name_map = {} # Maps sanitized name to original name
|
|
102
|
+
optional_keys: set[str] = set()
|
|
130
103
|
all_parameters = parameters + additional_parameters
|
|
131
104
|
for param in all_parameters:
|
|
132
105
|
if param.get("$ref"):
|
|
@@ -144,11 +117,15 @@ class BaseClientsGenerator:
|
|
|
144
117
|
if in_ == "query":
|
|
145
118
|
# URL query string values
|
|
146
119
|
param_type = utils.resolve_forward_refs_for_client(utils.get_type(param["schema"]))
|
|
147
|
-
if required:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
120
|
+
if not required:
|
|
121
|
+
param_type = f"typing.Optional[{utils.strip_none_from_type(param_type)}]"
|
|
122
|
+
optional_keys.add(clean_key)
|
|
123
|
+
if clean_key != original_name:
|
|
124
|
+
# The sanitized Python name differs from the name the API expects, so
|
|
125
|
+
# record it via Query(alias=...) to keep the request sent with the correct
|
|
126
|
+
# wire-format name (see clientele.api.request_context.query_alias_map).
|
|
127
|
+
param_type = f'typing.Annotated[{param_type}, clientele_api.Query(alias="{original_name}")]'
|
|
128
|
+
query_args[clean_key] = param_type
|
|
152
129
|
elif in_ == "path":
|
|
153
130
|
# Function arguments
|
|
154
131
|
param_type = utils.resolve_forward_refs_for_client(utils.get_type(param["schema"]))
|
|
@@ -157,6 +134,7 @@ class BaseClientsGenerator:
|
|
|
157
134
|
else:
|
|
158
135
|
param_type = utils.strip_none_from_type(param_type)
|
|
159
136
|
path_args[clean_key] = f"typing.Optional[{param_type}]"
|
|
137
|
+
optional_keys.add(clean_key)
|
|
160
138
|
elif in_ == "header":
|
|
161
139
|
# Header object arguments
|
|
162
140
|
headers_args[param["name"]] = utils.get_type(param["schema"])
|
|
@@ -166,6 +144,7 @@ class BaseClientsGenerator:
|
|
|
166
144
|
path_args=path_args,
|
|
167
145
|
headers_args=headers_args,
|
|
168
146
|
param_name_map=param_name_map,
|
|
147
|
+
optional_keys=optional_keys,
|
|
169
148
|
)
|
|
170
149
|
|
|
171
150
|
def get_response_class_names(self, responses: dict, func_name: str) -> list[str]:
|
|
@@ -221,7 +200,7 @@ class BaseClientsGenerator:
|
|
|
221
200
|
)
|
|
222
201
|
status_code_map[status_code] = class_name
|
|
223
202
|
response_classes.append(class_name)
|
|
224
|
-
self.
|
|
203
|
+
self.function_and_status_codes_bundle[func_name] = status_code_map
|
|
225
204
|
# Use set to deduplicate, then sorted for consistent ordering
|
|
226
205
|
return sorted(set(response_classes))
|
|
227
206
|
|
|
@@ -267,6 +246,31 @@ class BaseClientsGenerator:
|
|
|
267
246
|
else:
|
|
268
247
|
return f"schemas.{input_class_names[0]}"
|
|
269
248
|
|
|
249
|
+
def get_response_map(self, func_name: str) -> typing.Optional[str]:
|
|
250
|
+
"""
|
|
251
|
+
Generate response_map for decorator if there are multiple response types.
|
|
252
|
+
Returns a string representation of the response_map dict or None.
|
|
253
|
+
"""
|
|
254
|
+
status_codes = self.function_and_status_codes_bundle.get(func_name, {})
|
|
255
|
+
|
|
256
|
+
# If there's only one response code, we don't need response_map
|
|
257
|
+
if len(status_codes) <= 1:
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
# Filter out None responses (no content responses)
|
|
261
|
+
valid_responses = {code: schema for code, schema in status_codes.items() if schema != "None"}
|
|
262
|
+
|
|
263
|
+
if len(valid_responses) <= 1:
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
# Build response_map dict with schemas. prefix
|
|
267
|
+
response_map_parts = []
|
|
268
|
+
for status_code, schema_name in sorted(valid_responses.items()):
|
|
269
|
+
# Add schemas. prefix to match the response types
|
|
270
|
+
response_map_parts.append(f"{status_code}: schemas.{schema_name}")
|
|
271
|
+
|
|
272
|
+
return "{" + ", ".join(response_map_parts) + "}"
|
|
273
|
+
|
|
270
274
|
def generate_function(
|
|
271
275
|
self,
|
|
272
276
|
operation: dict,
|
|
@@ -289,13 +293,10 @@ class BaseClientsGenerator:
|
|
|
289
293
|
parameters=operation.get("parameters", []),
|
|
290
294
|
additional_parameters=additional_parameters,
|
|
291
295
|
)
|
|
292
|
-
# Replace path parameters in URL with sanitized names
|
|
296
|
+
# Replace path parameters in URL with sanitized names but DON'T add query args
|
|
297
|
+
# In the clientele api, query args are passed as function parameters, not in the URL
|
|
293
298
|
api_url = utils.replace_path_parameters(url, function_arguments.param_name_map)
|
|
294
|
-
|
|
295
|
-
# Use original parameter names in URL, but sanitized names for Python variables
|
|
296
|
-
api_url = api_url + utils.create_query_args_with_mapping(
|
|
297
|
-
list(query_args.keys()), function_arguments.param_name_map
|
|
298
|
-
)
|
|
299
|
+
|
|
299
300
|
if method in ["post", "put", "patch"] and not operation.get("requestBody"):
|
|
300
301
|
data_class_name = None
|
|
301
302
|
elif method in ["post", "put", "patch"]:
|
|
@@ -303,7 +304,7 @@ class BaseClientsGenerator:
|
|
|
303
304
|
else:
|
|
304
305
|
data_class_name = None
|
|
305
306
|
self.results[method] += 1
|
|
306
|
-
template =
|
|
307
|
+
template = writer.templates.get_template(METHOD_TEMPLATE_MAP[method])
|
|
307
308
|
if headers := function_arguments.headers_args:
|
|
308
309
|
header_class_name = self.schemas_generator.generate_headers_class(
|
|
309
310
|
properties=headers,
|
|
@@ -311,10 +312,14 @@ class BaseClientsGenerator:
|
|
|
311
312
|
)
|
|
312
313
|
else:
|
|
313
314
|
header_class_name = None
|
|
315
|
+
|
|
316
|
+
# Get response_map for clientele api decorator
|
|
317
|
+
response_map = self.get_response_map(func_name)
|
|
318
|
+
|
|
314
319
|
content = template.render(
|
|
315
320
|
asyncio=self.asyncio,
|
|
316
321
|
func_name=func_name,
|
|
317
|
-
function_arguments=function_arguments,
|
|
322
|
+
function_arguments=function_arguments,
|
|
318
323
|
response_types=response_types,
|
|
319
324
|
data_class_name=data_class_name,
|
|
320
325
|
header_class_name=header_class_name,
|
|
@@ -323,13 +328,14 @@ class BaseClientsGenerator:
|
|
|
323
328
|
summary=operation.get("summary", summary),
|
|
324
329
|
description=operation.get("description"),
|
|
325
330
|
deprecated=operation.get("deprecated", False),
|
|
331
|
+
response_map=response_map,
|
|
326
332
|
)
|
|
327
|
-
|
|
333
|
+
writer.write_to_client(content=content, output_dir=self.output_dir)
|
|
328
334
|
|
|
329
335
|
def write_path_to_client(self, path: tuple[str, dict]) -> None:
|
|
330
336
|
url, operations = path
|
|
331
337
|
for method, operation in operations.items():
|
|
332
|
-
if method.lower() in
|
|
338
|
+
if method.lower() in METHOD_TEMPLATE_MAP:
|
|
333
339
|
self.generate_function(
|
|
334
340
|
operation=operation,
|
|
335
341
|
method=method,
|
|
@@ -7,18 +7,18 @@ from os import path
|
|
|
7
7
|
from cicerone.spec import openapi_spec as cicerone_openapi_spec
|
|
8
8
|
from rich import console as rich_console
|
|
9
9
|
|
|
10
|
-
from clientele import generators, settings
|
|
11
|
-
from clientele.generators.api import writer
|
|
12
|
-
from clientele.generators.
|
|
10
|
+
from clientele import generators, settings
|
|
11
|
+
from clientele.generators.api import clients, writer
|
|
12
|
+
from clientele.generators.shared import schemas, utils
|
|
13
13
|
|
|
14
14
|
console = rich_console.Console()
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class APIGenerator(generators.Generator):
|
|
18
18
|
"""
|
|
19
|
-
The
|
|
19
|
+
The API Clientele generator.
|
|
20
20
|
|
|
21
|
-
Produces a decorator-based Python HTTP Client library using the clientele
|
|
21
|
+
Produces a decorator-based Python HTTP Client library using the clientele runtime.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
spec: cicerone_openapi_spec.OpenAPISpec
|
|
@@ -52,7 +52,7 @@ class APIGenerator(generators.Generator):
|
|
|
52
52
|
self.output_dir = output_dir
|
|
53
53
|
self.file = file
|
|
54
54
|
self.url = url
|
|
55
|
-
#
|
|
55
|
+
# The api generator only needs client, config, and schemas (no http.py)
|
|
56
56
|
self.file_name_writer_tuple = (
|
|
57
57
|
("config.py", "config_py.jinja2", writer.write_to_config),
|
|
58
58
|
("client.py", "client_py.jinja2", writer.write_to_client),
|
|
@@ -60,7 +60,6 @@ class APIGenerator(generators.Generator):
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
def generate_templates_files(self) -> None:
|
|
63
|
-
new_unions = settings.PY_VERSION[1] > 10
|
|
64
63
|
client_project_directory_path = utils.get_client_project_directory_path(output_dir=self.output_dir)
|
|
65
64
|
|
|
66
65
|
# Extract base_url from OpenAPI spec servers
|
|
@@ -83,7 +82,6 @@ class APIGenerator(generators.Generator):
|
|
|
83
82
|
template = writer.templates.get_template(client_template_file)
|
|
84
83
|
content = template.render(
|
|
85
84
|
client_project_directory_path=client_project_directory_path,
|
|
86
|
-
new_unions=new_unions,
|
|
87
85
|
base_url=base_url,
|
|
88
86
|
)
|
|
89
87
|
write_func(content, output_dir=self.output_dir)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import pathlib
|
|
3
3
|
|
|
4
|
-
from clientele import generators, settings
|
|
4
|
+
from clientele import generators, settings
|
|
5
5
|
from clientele.generators.basic import writer
|
|
6
|
+
from clientele.generators.shared import utils
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class BasicGenerator(generators.Generator):
|
|
@@ -3,12 +3,49 @@ import typing
|
|
|
3
3
|
from cicerone.spec import openapi_spec as cicerone_openapi_spec
|
|
4
4
|
from rich import console as rich_console
|
|
5
5
|
|
|
6
|
-
from clientele.generators import cicerone_compat
|
|
6
|
+
from clientele.generators import cicerone_compat
|
|
7
7
|
from clientele.generators.shared import utils
|
|
8
8
|
|
|
9
9
|
console = rich_console.Console()
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def build_union_type_string(
|
|
13
|
+
schema_options: list[typing.Dict[str, typing.Any]],
|
|
14
|
+
discriminator: typing.Optional[str] = None,
|
|
15
|
+
) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Build a union type string from a list of schema options (for oneOf or anyOf).
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
schema_options: List of schema option dictionaries from oneOf or anyOf
|
|
21
|
+
discriminator: Optional discriminator property name for discriminated unions
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
A union type string (e.g., "Cat | Dog" or "typing.Union[Cat, Dog]")
|
|
25
|
+
When discriminator is provided, returns:
|
|
26
|
+
"typing.Annotated[Cat | Dog, pydantic.Field(discriminator='type')]"
|
|
27
|
+
"""
|
|
28
|
+
union_types = []
|
|
29
|
+
for schema_option in schema_options:
|
|
30
|
+
if ref := schema_option.get("$ref"):
|
|
31
|
+
ref_name = utils.class_name_titled(utils.schema_ref(ref))
|
|
32
|
+
# Use direct reference without quotes for type aliases
|
|
33
|
+
union_types.append(ref_name)
|
|
34
|
+
else:
|
|
35
|
+
# Inline schema - convert to type and remove forward ref quotes
|
|
36
|
+
type_str = utils.get_type(schema_option)
|
|
37
|
+
type_str = utils.remove_forward_ref_quotes(type_str)
|
|
38
|
+
union_types.append(type_str)
|
|
39
|
+
|
|
40
|
+
union_str = utils.union_for_py_ver(union_types)
|
|
41
|
+
|
|
42
|
+
# Wrap with Annotated + Field(discriminator=...) if discriminator is present
|
|
43
|
+
if discriminator:
|
|
44
|
+
return f'typing.Annotated[{union_str}, pydantic.Field(discriminator="{discriminator}")]'
|
|
45
|
+
|
|
46
|
+
return union_str
|
|
47
|
+
|
|
48
|
+
|
|
12
49
|
class SchemasGenerator:
|
|
13
50
|
"""
|
|
14
51
|
Handles all the content generated in the schemas.py file.
|
|
@@ -26,8 +63,6 @@ class SchemasGenerator:
|
|
|
26
63
|
self.output_dir = output_dir
|
|
27
64
|
self.writer = writer
|
|
28
65
|
|
|
29
|
-
generated_response_class_names: list[str] = []
|
|
30
|
-
|
|
31
66
|
def generate_enum_properties(self, properties: dict) -> str:
|
|
32
67
|
lines = [
|
|
33
68
|
f" {utils.snake_case_prop(arg.upper())} = {utils.get_type(arg_details)}\n"
|
|
@@ -112,7 +147,7 @@ class SchemasGenerator:
|
|
|
112
147
|
schema_options: list[dict],
|
|
113
148
|
discriminator: typing.Optional[str] = None,
|
|
114
149
|
) -> None:
|
|
115
|
-
union_type =
|
|
150
|
+
union_type = build_union_type_string(schema_options, discriminator=discriminator)
|
|
116
151
|
template = self.writer.templates.get_template("schema_type_alias.jinja2")
|
|
117
152
|
content = template.render(class_name=schema_key, union_type=union_type)
|
|
118
153
|
self.writer.write_to_schemas(content, output_dir=self.output_dir)
|