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.
Files changed (267) hide show
  1. {clientele-2.2.2 → clientele-2.3.0}/CHANGELOG.md +7 -0
  2. {clientele-2.2.2 → clientele-2.3.0}/PKG-INFO +1 -1
  3. {clientele-2.2.2 → clientele-2.3.0}/SECURITY.md +2 -2
  4. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/__init__.py +2 -1
  5. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/client.py +12 -10
  6. clientele-2.3.0/clientele/api/params.py +37 -0
  7. clientele-2.2.2/clientele/api/requests.py → clientele-2.3.0/clientele/api/request_context.py +20 -0
  8. clientele-2.3.0/clientele/generators/api/__init__.py +1 -0
  9. clientele-2.2.2/clientele/generators/base_clients.py → clientele-2.3.0/clientele/generators/api/clients.py +78 -72
  10. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/generator.py +6 -8
  11. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/generator.py +2 -1
  12. {clientele-2.2.2/clientele/generators/shared/generators → clientele-2.3.0/clientele/generators/shared}/schemas.py +39 -4
  13. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/shared/utils.py +38 -23
  14. {clientele-2.2.2 → clientele-2.3.0}/clientele/graphql/client.py +5 -5
  15. clientele-2.3.0/clientele/settings.py +5 -0
  16. {clientele-2.2.2 → clientele-2.3.0}/docs/CHANGELOG.md +7 -0
  17. {clientele-2.2.2 → clientele-2.3.0}/docs/api-examples.md +24 -0
  18. {clientele-2.2.2 → clientele-2.3.0}/docs/install.md +1 -1
  19. {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/best.json +9 -0
  20. {clientele-2.2.2 → clientele-2.3.0}/pyproject.toml +1 -1
  21. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/MANIFEST.md +1 -1
  22. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/MANIFEST.md +1 -1
  23. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/MANIFEST.md +1 -1
  24. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client.py +54 -1
  25. clientele-2.2.2/tests/api/test_requests.py → clientele-2.3.0/tests/api/test_request_context.py +35 -33
  26. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_stream.py +4 -2
  27. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/MANIFEST.md +1 -1
  28. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/client.py +4 -2
  29. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/MANIFEST.md +1 -1
  30. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/MANIFEST.md +1 -1
  31. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/client.py +4 -2
  32. clientele-2.3.0/tests/cache/__init__.py +0 -0
  33. clientele-2.3.0/tests/fixtures/openapi_examples/link-example.json +0 -0
  34. clientele-2.3.0/tests/generators/__init__.py +0 -0
  35. clientele-2.3.0/tests/generators/api/__init__.py +0 -0
  36. clientele-2.3.0/tests/generators/api/test_clients.py +118 -0
  37. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_fixture_schemas.py +36 -4
  38. clientele-2.3.0/tests/generators/api/test_generator.py +332 -0
  39. clientele-2.3.0/tests/generators/basic/__init__.py +0 -0
  40. clientele-2.3.0/tests/generators/basic/test_generator.py +42 -0
  41. clientele-2.3.0/tests/generators/shared/__init__.py +0 -0
  42. clientele-2.2.2/tests/generators/test_schemas_coverage.py → clientele-2.3.0/tests/generators/shared/test_schemas.py +41 -2
  43. clientele-2.3.0/tests/generators/shared/test_utils.py +253 -0
  44. {clientele-2.2.2 → clientele-2.3.0}/tests/generators/test_base.py +1 -1
  45. {clientele-2.2.2 → clientele-2.3.0}/tests/generators/test_cicerone_compat.py +160 -1
  46. clientele-2.3.0/tests/graphql/__init__.py +0 -0
  47. clientele-2.3.0/tests/http/__init__.py +0 -0
  48. clientele-2.3.0/tests/http/backends/__init__.py +0 -0
  49. clientele-2.3.0/tests/mypy/__init__.py +0 -0
  50. {clientele-2.2.2 → clientele-2.3.0}/tests/test_cli.py +87 -20
  51. clientele-2.3.0/tests/test_settings.py +24 -0
  52. {clientele-2.2.2 → clientele-2.3.0}/uv.lock +2 -2
  53. clientele-2.2.2/clientele/generators/api/__init__.py +0 -1
  54. clientele-2.2.2/clientele/generators/api/generators/__init__.py +0 -1
  55. clientele-2.2.2/clientele/generators/api/generators/clients.py +0 -146
  56. clientele-2.2.2/clientele/generators/api/generators/schemas.py +0 -3
  57. clientele-2.2.2/clientele/generators/schema_utils.py +0 -42
  58. clientele-2.2.2/clientele/settings.py +0 -10
  59. clientele-2.2.2/clientele/utils.py +0 -38
  60. clientele-2.2.2/tests/generators/framework/test_framework_generator_coverage.py +0 -111
  61. clientele-2.2.2/tests/generators/framework/test_framework_generator_integration.py +0 -170
  62. clientele-2.2.2/tests/generators/shared/test_utils.py +0 -112
  63. clientele-2.2.2/tests/generators/shared/test_utils_coverage.py +0 -163
  64. clientele-2.2.2/tests/generators/test_base_and_basic_coverage.py +0 -157
  65. clientele-2.2.2/tests/test_additional_coverage.py +0 -72
  66. clientele-2.2.2/tests/test_cli_coverage.py +0 -86
  67. clientele-2.2.2/tests/test_cli_full_coverage.py +0 -124
  68. clientele-2.2.2/tests/test_edge_case_coverage.py +0 -65
  69. clientele-2.2.2/tests/test_generator_coverage.py +0 -333
  70. clientele-2.2.2/tests/test_settings.py +0 -32
  71. clientele-2.2.2/tests/test_utils.py +0 -29
  72. {clientele-2.2.2 → clientele-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  73. {clientele-2.2.2 → clientele-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  74. {clientele-2.2.2 → clientele-2.3.0}/.github/compatibility.json +0 -0
  75. {clientele-2.2.2 → clientele-2.3.0}/.github/dependabot.yml +0 -0
  76. {clientele-2.2.2 → clientele-2.3.0}/.github/pull_request_template.md +0 -0
  77. {clientele-2.2.2 → clientele-2.3.0}/.github/workflows/ci.yml +0 -0
  78. {clientele-2.2.2 → clientele-2.3.0}/.gitignore +0 -0
  79. {clientele-2.2.2 → clientele-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  80. {clientele-2.2.2 → clientele-2.3.0}/CONTRIBUTING.md +0 -0
  81. {clientele-2.2.2 → clientele-2.3.0}/CONTRIBUTORS.md +0 -0
  82. {clientele-2.2.2 → clientele-2.3.0}/LICENSE +0 -0
  83. {clientele-2.2.2 → clientele-2.3.0}/Makefile +0 -0
  84. {clientele-2.2.2 → clientele-2.3.0}/PUBLISHING.md +0 -0
  85. {clientele-2.2.2 → clientele-2.3.0}/README.md +0 -0
  86. {clientele-2.2.2 → clientele-2.3.0}/clientele/__init__.py +0 -0
  87. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/config.py +0 -0
  88. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/exceptions.py +0 -0
  89. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/stream/__init__.py +0 -0
  90. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/stream/parser.py +0 -0
  91. {clientele-2.2.2 → clientele-2.3.0}/clientele/api/type_utils.py +0 -0
  92. {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/__init__.py +0 -0
  93. {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/backends.py +0 -0
  94. {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/decorator.py +0 -0
  95. {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/key_generator.py +0 -0
  96. {clientele-2.2.2 → clientele-2.3.0}/clientele/cache/types.py +0 -0
  97. {clientele-2.2.2 → clientele-2.3.0}/clientele/cli.py +0 -0
  98. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/README.md +0 -0
  99. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/__init__.py +0 -0
  100. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/api_get_method.jinja2 +0 -0
  101. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/api_post_method.jinja2 +0 -0
  102. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/client_py.jinja2 +0 -0
  103. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/config_py.jinja2 +0 -0
  104. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/manifest.jinja2 +0 -0
  105. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/pyproject.toml.jinja2 +0 -0
  106. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_class.jinja2 +0 -0
  107. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_helpers.jinja2 +0 -0
  108. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_root_model.jinja2 +0 -0
  109. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schema_type_alias.jinja2 +0 -0
  110. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/templates/schemas_py.jinja2 +0 -0
  111. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/api/writer.py +0 -0
  112. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/base.py +0 -0
  113. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/__init__.py +0 -0
  114. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/client_py.jinja2 +0 -0
  115. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/config_py.jinja2 +0 -0
  116. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/manifest.jinja2 +0 -0
  117. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/templates/schemas_py.jinja2 +0 -0
  118. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/basic/writer.py +0 -0
  119. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/cicerone_compat.py +0 -0
  120. {clientele-2.2.2 → clientele-2.3.0}/clientele/generators/shared/__init__.py +0 -0
  121. {clientele-2.2.2 → clientele-2.3.0}/clientele/graphql/__init__.py +0 -0
  122. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/__init__.py +0 -0
  123. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/aiohttp_backend.py +0 -0
  124. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/backends.py +0 -0
  125. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/fake_backend.py +0 -0
  126. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/httpx_backend.py +0 -0
  127. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/niquests_backend.py +0 -0
  128. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/requests_backend.py +0 -0
  129. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/response.py +0 -0
  130. {clientele-2.2.2 → clientele-2.3.0}/clientele/http/status_codes.py +0 -0
  131. {clientele-2.2.2 → clientele-2.3.0}/clientele/mypy.py +0 -0
  132. {clientele-2.2.2 → clientele-2.3.0}/clientele/py.typed +0 -0
  133. {clientele-2.2.2 → clientele-2.3.0}/clientele/retries/__init__.py +0 -0
  134. {clientele-2.2.2 → clientele-2.3.0}/clientele/retries/decorators.py +0 -0
  135. {clientele-2.2.2 → clientele-2.3.0}/clientele/schemas.py +0 -0
  136. {clientele-2.2.2 → clientele-2.3.0}/clientele/testing.py +0 -0
  137. {clientele-2.2.2 → clientele-2.3.0}/docs/CNAME +0 -0
  138. {clientele-2.2.2 → clientele-2.3.0}/docs/api-async.md +0 -0
  139. {clientele-2.2.2 → clientele-2.3.0}/docs/api-authentication.md +0 -0
  140. {clientele-2.2.2 → clientele-2.3.0}/docs/api-cache.md +0 -0
  141. {clientele-2.2.2 → clientele-2.3.0}/docs/api-configuration.md +0 -0
  142. {clientele-2.2.2 → clientele-2.3.0}/docs/api-direct-requests.md +0 -0
  143. {clientele-2.2.2 → clientele-2.3.0}/docs/api-exceptions.md +0 -0
  144. {clientele-2.2.2 → clientele-2.3.0}/docs/api-graphql.md +0 -0
  145. {clientele-2.2.2 → clientele-2.3.0}/docs/api-http-backends.md +0 -0
  146. {clientele-2.2.2 → clientele-2.3.0}/docs/api-logging.md +0 -0
  147. {clientele-2.2.2 → clientele-2.3.0}/docs/api-overview.md +0 -0
  148. {clientele-2.2.2 → clientele-2.3.0}/docs/api-retries.md +0 -0
  149. {clientele-2.2.2 → clientele-2.3.0}/docs/api-stream.md +0 -0
  150. {clientele-2.2.2 → clientele-2.3.0}/docs/api-testing.md +0 -0
  151. {clientele-2.2.2 → clientele-2.3.0}/docs/clientele_generate.gif +0 -0
  152. {clientele-2.2.2 → clientele-2.3.0}/docs/clientele_header.png +0 -0
  153. {clientele-2.2.2 → clientele-2.3.0}/docs/favicon.png +0 -0
  154. {clientele-2.2.2 → clientele-2.3.0}/docs/index.md +0 -0
  155. {clientele-2.2.2 → clientele-2.3.0}/docs/injected-parameters.md +0 -0
  156. {clientele-2.2.2 → clientele-2.3.0}/docs/openapi-cli.md +0 -0
  157. {clientele-2.2.2 → clientele-2.3.0}/docs/server-django-ninja.md +0 -0
  158. {clientele-2.2.2 → clientele-2.3.0}/docs/server-drf.md +0 -0
  159. {clientele-2.2.2 → clientele-2.3.0}/docs/server-fastapi.md +0 -0
  160. {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/complex_schemas.json +0 -0
  161. {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/issue_248.json +0 -0
  162. {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/simple.json +0 -0
  163. {clientele-2.2.2 → clientele-2.3.0}/example_openapi_specs/test_303.yaml +0 -0
  164. {clientele-2.2.2 → clientele-2.3.0}/mkdocs.yml +0 -0
  165. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/README.md +0 -0
  166. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/api.py +0 -0
  167. {clientele-2.2.2/clientele/generators/shared/generators → clientele-2.3.0/server_examples/django_ninja/client}/__init__.py +0 -0
  168. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/client.py +0 -0
  169. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/config.py +0 -0
  170. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/pyproject.toml +0 -0
  171. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/client/schemas.py +0 -0
  172. {clientele-2.2.2/server_examples/django_ninja/client → clientele-2.3.0/server_examples/django_ninja/example_project}/__init__.py +0 -0
  173. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/example_project/settings.py +0 -0
  174. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/example_project/urls.py +0 -0
  175. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/manage.py +0 -0
  176. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_ninja/openapi.json +0 -0
  177. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/README.md +0 -0
  178. {clientele-2.2.2/server_examples/django_ninja/example_project → clientele-2.3.0/server_examples/django_rest_framework/client}/__init__.py +0 -0
  179. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/client.py +0 -0
  180. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/config.py +0 -0
  181. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/pyproject.toml +0 -0
  182. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/client/schemas.py +0 -0
  183. {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
  184. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/example_project/settings.py +0 -0
  185. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/example_project/urls.py +0 -0
  186. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/manage.py +0 -0
  187. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/openapi.yaml +0 -0
  188. {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
  189. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/apps.py +0 -0
  190. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/migrations/0001_initial.py +0 -0
  191. {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
  192. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/models.py +0 -0
  193. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/serializers.py +0 -0
  194. {clientele-2.2.2 → clientele-2.3.0}/server_examples/django_rest_framework/users/views.py +0 -0
  195. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/README.md +0 -0
  196. {clientele-2.2.2/server_examples/django_rest_framework/users/migrations → clientele-2.3.0/server_examples/fastapi/client}/__init__.py +0 -0
  197. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/client.py +0 -0
  198. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/config.py +0 -0
  199. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/pyproject.toml +0 -0
  200. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/client/schemas.py +0 -0
  201. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/main.py +0 -0
  202. {clientele-2.2.2 → clientele-2.3.0}/server_examples/fastapi/openapi.json +0 -0
  203. {clientele-2.2.2/server_examples/fastapi/client → clientele-2.3.0/tests}/__init__.py +0 -0
  204. {clientele-2.2.2/tests → clientele-2.3.0/tests/api}/__init__.py +0 -0
  205. {clientele-2.2.2/tests/api_clients/async_test_client → clientele-2.3.0/tests/api/retries}/__init__.py +0 -0
  206. {clientele-2.2.2 → clientele-2.3.0}/tests/api/retries/test_retry.py +0 -0
  207. {clientele-2.2.2/tests/api_clients/test_basic_client → clientele-2.3.0/tests/api/stream}/__init__.py +0 -0
  208. {clientele-2.2.2 → clientele-2.3.0}/tests/api/stream/test_parser.py +0 -0
  209. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client_logging.py +0 -0
  210. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_client_singleton.py +0 -0
  211. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_response_parser.py +0 -0
  212. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_api_typed_dict.py +0 -0
  213. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_direct_requests.py +0 -0
  214. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_stream_httpx.py +0 -0
  215. {clientele-2.2.2 → clientele-2.3.0}/tests/api/test_type_utils.py +0 -0
  216. {clientele-2.2.2/tests/api_clients/test_client → clientele-2.3.0/tests/api_clients/async_test_client}/__init__.py +0 -0
  217. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/config.py +0 -0
  218. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/pyproject.toml +0 -0
  219. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/async_test_client/schemas.py +0 -0
  220. {clientele-2.2.2/tests/generators/shared → clientele-2.3.0/tests/api_clients/test_basic_client}/__init__.py +0 -0
  221. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/client.py +0 -0
  222. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/config.py +0 -0
  223. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_basic_client/schemas.py +0 -0
  224. /clientele-2.2.2/tests/fixtures/openapi_examples/link-example.json → /clientele-2.3.0/tests/api_clients/test_client/__init__.py +0 -0
  225. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/config.py +0 -0
  226. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/pyproject.toml +0 -0
  227. {clientele-2.2.2 → clientele-2.3.0}/tests/api_clients/test_client/schemas.py +0 -0
  228. {clientele-2.2.2 → clientele-2.3.0}/tests/cache/fixtures.py +0 -0
  229. {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_backends.py +0 -0
  230. {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_decorator.py +0 -0
  231. {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_integration.py +0 -0
  232. {clientele-2.2.2 → clientele-2.3.0}/tests/cache/test_key_generator.py +0 -0
  233. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/callback_example.yaml +0 -0
  234. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/complex_api.yaml +0 -0
  235. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/README.md +0 -0
  236. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/api-with-examples.json +0 -0
  237. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/callback-example.json +0 -0
  238. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/non-oauth-scopes.json +0 -0
  239. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/petstore-expanded.json +0 -0
  240. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/petstore.json +0 -0
  241. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/tictactoe.json +0 -0
  242. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/uspto.json +0 -0
  243. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/openapi_examples/webhook-example.json +0 -0
  244. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/petstore_openapi3.yaml +0 -0
  245. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/1password.yaml +0 -0
  246. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/ably.yaml +0 -0
  247. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/google.yaml +0 -0
  248. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/medium.yaml +0 -0
  249. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/spacetraders.yaml +0 -0
  250. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/realworld/twilio.yaml +0 -0
  251. {clientele-2.2.2 → clientele-2.3.0}/tests/fixtures/regression/dep_query_alias.json +0 -0
  252. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_complex_schemas.py +0 -0
  253. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_issue_248.py +0 -0
  254. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_nullable_fields.py +0 -0
  255. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_openapi_31_null_type.py +0 -0
  256. {clientele-2.2.2/tests → clientele-2.3.0/tests/generators/api}/test_ref_handling.py +0 -0
  257. {clientele-2.2.2 → clientele-2.3.0}/tests/generators/integration_utils.py +0 -0
  258. {clientele-2.2.2 → clientele-2.3.0}/tests/graphql/test_graphql_client.py +0 -0
  259. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_aiohttp_backend.py +0 -0
  260. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_backend_config.py +0 -0
  261. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_fake_backend.py +0 -0
  262. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_httpx_backend.py +0 -0
  263. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_niquests_backend.py +0 -0
  264. {clientele-2.2.2 → clientele-2.3.0}/tests/http/backends/test_requests_backend.py +0 -0
  265. {clientele-2.2.2 → clientele-2.3.0}/tests/mypy/mypy_plugin_test_config.ini +0 -0
  266. {clientele-2.2.2 → clientele-2.3.0}/tests/mypy/test_mypy_plugin.py +0 -0
  267. /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,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clientele
3
- Version: 2.2.2
3
+ Version: 2.3.0
4
4
  Summary: Clientele is a different way to build Python API Clients
5
5
  Project-URL: Homepage, https://clientele.dev
6
6
  Project-URL: Changelog, https://docs.clientele.dev/CHANGELOG/
@@ -4,8 +4,8 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | --------------| ------------------ |
7
- | 2.2.2 | :white_check_mark: |
8
- | 2.2.1 or less | :x: |
7
+ | 2.3.0 | :white_check_mark: |
8
+ | 2.2.2 or less | :x: |
9
9
 
10
10
  ## Reporting a Vulnerability
11
11
 
@@ -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 requests, type_utils
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 = requests.build_request_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: requests.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
323
- ) -> requests.PreparedCall:
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 requests.PreparedCall(
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: requests.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
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: requests.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
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: requests.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
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: requests.RequestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
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: requests.PreparedCall,
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))
@@ -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
- """Base class for client generators shared by api and basic generators."""
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.shared import utils
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
- def get_path_args_as_string(self) -> str:
27
- # Get all the path arguments, and the query arguments and make a big string out of them.
28
- args = list(self.path_args.items()) + list(self.query_args.items())
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 without Optional wrapper)."""
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 with Optional wrapper)."""
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 BaseClientsGenerator:
53
+ class ClientsGenerator:
73
54
  """
74
- Base class for generating client code from OpenAPI specifications.
75
- Provides common functionality for client generators.
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: typing.Any # Type will be specific in subclasses
83
- http_generator: typing.Any # Type will be specific in subclasses
84
- writer: typing.Any # Must be set by subclass
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: typing.Any,
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.method_template_map = dict(
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
- query_args[clean_key] = param_type
149
- else:
150
- param_type = utils.strip_none_from_type(param_type)
151
- query_args[clean_key] = f"typing.Optional[{param_type}]"
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.http_generator.add_status_codes_to_bundle(func_name=func_name, status_code_map=status_code_map)
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
- if query_args := function_arguments.query_args:
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 = self.writer.templates.get_template(self.method_template_map[method])
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, # Pass the object, not the string
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
- self.writer.write_to_client(content=content, output_dir=self.output_dir)
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 self.method_template_map.keys():
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, utils
11
- from clientele.generators.api import writer
12
- from clientele.generators.api.generators import clients, schemas
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 framework Clientele generator.
19
+ The API Clientele generator.
20
20
 
21
- Produces a decorator-based Python HTTP Client library using the clientele framework.
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
- # Framework generator only needs client, config, and schemas (no http.py)
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, utils
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, schema_utils
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 = schema_utils.build_union_type_string(schema_options, discriminator=discriminator)
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)