clientele 2.1.0__tar.gz → 2.2.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 (252) hide show
  1. {clientele-2.1.0 → clientele-2.2.0}/.claude/settings.local.json +2 -1
  2. {clientele-2.1.0 → clientele-2.2.0}/.github/workflows/ci.yml +6 -2
  3. {clientele-2.1.0 → clientele-2.2.0}/CHANGELOG.md +9 -1
  4. {clientele-2.1.0 → clientele-2.2.0}/PKG-INFO +1 -1
  5. {clientele-2.1.0 → clientele-2.2.0}/SECURITY.md +2 -2
  6. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/config_py.jinja2 +2 -4
  7. clientele-2.2.0/clientele/generators/api/templates/schema_root_model.jinja2 +4 -0
  8. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/schemas_py.jinja2 +2 -0
  9. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/base_clients.py +8 -4
  10. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/cicerone_compat.py +8 -0
  11. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/shared/generators/schemas.py +17 -5
  12. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/shared/utils.py +20 -4
  13. clientele-2.2.0/clientele/schemas.py +20 -0
  14. {clientele-2.1.0 → clientele-2.2.0}/clientele/settings.py +1 -1
  15. {clientele-2.1.0 → clientele-2.2.0}/docs/CHANGELOG.md +9 -1
  16. {clientele-2.1.0 → clientele-2.2.0}/docs/api-overview.md +3 -25
  17. clientele-2.2.0/docs/injected-parameters.md +135 -0
  18. {clientele-2.1.0 → clientele-2.2.0}/docs/install.md +1 -1
  19. clientele-2.2.0/example_openapi_specs/issue_248.json +85 -0
  20. {clientele-2.1.0 → clientele-2.2.0}/mkdocs.yml +1 -1
  21. {clientele-2.1.0 → clientele-2.2.0}/pyproject.toml +1 -1
  22. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/MANIFEST.md +1 -1
  23. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/schemas.py +4 -1
  24. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/MANIFEST.md +1 -1
  25. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/schemas.py +4 -1
  26. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/MANIFEST.md +1 -1
  27. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/schemas.py +4 -1
  28. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/MANIFEST.md +1 -1
  29. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/schemas.py +2 -0
  30. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/MANIFEST.md +1 -1
  31. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/MANIFEST.md +1 -1
  32. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/schemas.py +2 -0
  33. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/shared/test_utils_coverage.py +22 -0
  34. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/test_base_and_basic_coverage.py +42 -0
  35. clientele-2.2.0/tests/generators/test_schemas_coverage.py +233 -0
  36. {clientele-2.1.0 → clientele-2.2.0}/tests/test_complex_schemas.py +10 -9
  37. clientele-2.2.0/tests/test_issue_248.py +126 -0
  38. {clientele-2.1.0 → clientele-2.2.0}/uv.lock +1 -1
  39. clientele-2.1.0/docs/mypy.md +0 -63
  40. clientele-2.1.0/tests/generators/test_schemas_coverage.py +0 -96
  41. {clientele-2.1.0 → clientele-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {clientele-2.1.0 → clientele-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  43. {clientele-2.1.0 → clientele-2.2.0}/.github/compatibility.json +0 -0
  44. {clientele-2.1.0 → clientele-2.2.0}/.github/dependabot.yml +0 -0
  45. {clientele-2.1.0 → clientele-2.2.0}/.github/pull_request_template.md +0 -0
  46. {clientele-2.1.0 → clientele-2.2.0}/.gitignore +0 -0
  47. {clientele-2.1.0 → clientele-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  48. {clientele-2.1.0 → clientele-2.2.0}/CONTRIBUTING.md +0 -0
  49. {clientele-2.1.0 → clientele-2.2.0}/CONTRIBUTORS.md +0 -0
  50. {clientele-2.1.0 → clientele-2.2.0}/LICENSE +0 -0
  51. {clientele-2.1.0 → clientele-2.2.0}/Makefile +0 -0
  52. {clientele-2.1.0 → clientele-2.2.0}/PUBLISHING.md +0 -0
  53. {clientele-2.1.0 → clientele-2.2.0}/README.md +0 -0
  54. {clientele-2.1.0 → clientele-2.2.0}/clientele/__init__.py +0 -0
  55. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/__init__.py +0 -0
  56. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/client.py +0 -0
  57. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/config.py +0 -0
  58. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/exceptions.py +0 -0
  59. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/requests.py +0 -0
  60. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/stream/__init__.py +0 -0
  61. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/stream/parser.py +0 -0
  62. {clientele-2.1.0 → clientele-2.2.0}/clientele/api/type_utils.py +0 -0
  63. {clientele-2.1.0 → clientele-2.2.0}/clientele/cache/__init__.py +0 -0
  64. {clientele-2.1.0 → clientele-2.2.0}/clientele/cache/backends.py +0 -0
  65. {clientele-2.1.0 → clientele-2.2.0}/clientele/cache/decorator.py +0 -0
  66. {clientele-2.1.0 → clientele-2.2.0}/clientele/cache/key_generator.py +0 -0
  67. {clientele-2.1.0 → clientele-2.2.0}/clientele/cache/types.py +0 -0
  68. {clientele-2.1.0 → clientele-2.2.0}/clientele/cli.py +0 -0
  69. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/README.md +0 -0
  70. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/__init__.py +0 -0
  71. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/__init__.py +0 -0
  72. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/generator.py +0 -0
  73. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/generators/__init__.py +0 -0
  74. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/generators/clients.py +0 -0
  75. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/generators/schemas.py +0 -0
  76. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/api_get_method.jinja2 +0 -0
  77. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/api_post_method.jinja2 +0 -0
  78. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/client_py.jinja2 +0 -0
  79. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/manifest.jinja2 +0 -0
  80. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/pyproject.toml.jinja2 +0 -0
  81. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_class.jinja2 +0 -0
  82. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_helpers.jinja2 +0 -0
  83. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_type_alias.jinja2 +0 -0
  84. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/api/writer.py +0 -0
  85. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/base.py +0 -0
  86. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/__init__.py +0 -0
  87. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/generator.py +0 -0
  88. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/templates/client_py.jinja2 +0 -0
  89. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/templates/config_py.jinja2 +0 -0
  90. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/templates/manifest.jinja2 +0 -0
  91. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/templates/schemas_py.jinja2 +0 -0
  92. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/basic/writer.py +0 -0
  93. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/schema_utils.py +0 -0
  94. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/shared/__init__.py +0 -0
  95. {clientele-2.1.0 → clientele-2.2.0}/clientele/generators/shared/generators/__init__.py +0 -0
  96. {clientele-2.1.0 → clientele-2.2.0}/clientele/graphql/__init__.py +0 -0
  97. {clientele-2.1.0 → clientele-2.2.0}/clientele/graphql/client.py +0 -0
  98. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/__init__.py +0 -0
  99. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/aiohttp_backend.py +0 -0
  100. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/backends.py +0 -0
  101. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/fake_backend.py +0 -0
  102. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/httpx_backend.py +0 -0
  103. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/niquests_backend.py +0 -0
  104. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/requests_backend.py +0 -0
  105. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/response.py +0 -0
  106. {clientele-2.1.0 → clientele-2.2.0}/clientele/http/status_codes.py +0 -0
  107. {clientele-2.1.0 → clientele-2.2.0}/clientele/mypy.py +0 -0
  108. {clientele-2.1.0 → clientele-2.2.0}/clientele/py.typed +0 -0
  109. {clientele-2.1.0 → clientele-2.2.0}/clientele/retries/__init__.py +0 -0
  110. {clientele-2.1.0 → clientele-2.2.0}/clientele/retries/decorators.py +0 -0
  111. {clientele-2.1.0 → clientele-2.2.0}/clientele/testing.py +0 -0
  112. {clientele-2.1.0 → clientele-2.2.0}/clientele/utils.py +0 -0
  113. {clientele-2.1.0 → clientele-2.2.0}/docs/CNAME +0 -0
  114. {clientele-2.1.0 → clientele-2.2.0}/docs/api-async.md +0 -0
  115. {clientele-2.1.0 → clientele-2.2.0}/docs/api-authentication.md +0 -0
  116. {clientele-2.1.0 → clientele-2.2.0}/docs/api-cache.md +0 -0
  117. {clientele-2.1.0 → clientele-2.2.0}/docs/api-configuration.md +0 -0
  118. {clientele-2.1.0 → clientele-2.2.0}/docs/api-direct-requests.md +0 -0
  119. {clientele-2.1.0 → clientele-2.2.0}/docs/api-examples.md +0 -0
  120. {clientele-2.1.0 → clientele-2.2.0}/docs/api-exceptions.md +0 -0
  121. {clientele-2.1.0 → clientele-2.2.0}/docs/api-graphql.md +0 -0
  122. {clientele-2.1.0 → clientele-2.2.0}/docs/api-http-backends.md +0 -0
  123. {clientele-2.1.0 → clientele-2.2.0}/docs/api-logging.md +0 -0
  124. {clientele-2.1.0 → clientele-2.2.0}/docs/api-retries.md +0 -0
  125. {clientele-2.1.0 → clientele-2.2.0}/docs/api-stream.md +0 -0
  126. {clientele-2.1.0 → clientele-2.2.0}/docs/api-testing.md +0 -0
  127. {clientele-2.1.0 → clientele-2.2.0}/docs/clientele_generate.gif +0 -0
  128. {clientele-2.1.0 → clientele-2.2.0}/docs/clientele_header.png +0 -0
  129. {clientele-2.1.0 → clientele-2.2.0}/docs/favicon.png +0 -0
  130. {clientele-2.1.0 → clientele-2.2.0}/docs/index.md +0 -0
  131. {clientele-2.1.0 → clientele-2.2.0}/docs/openapi-cli.md +0 -0
  132. {clientele-2.1.0 → clientele-2.2.0}/docs/server-django-ninja.md +0 -0
  133. {clientele-2.1.0 → clientele-2.2.0}/docs/server-drf.md +0 -0
  134. {clientele-2.1.0 → clientele-2.2.0}/docs/server-fastapi.md +0 -0
  135. {clientele-2.1.0 → clientele-2.2.0}/example_openapi_specs/best.json +0 -0
  136. {clientele-2.1.0 → clientele-2.2.0}/example_openapi_specs/complex_schemas.json +0 -0
  137. {clientele-2.1.0 → clientele-2.2.0}/example_openapi_specs/simple.json +0 -0
  138. {clientele-2.1.0 → clientele-2.2.0}/example_openapi_specs/test_303.yaml +0 -0
  139. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/README.md +0 -0
  140. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/api.py +0 -0
  141. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/__init__.py +0 -0
  142. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/client.py +0 -0
  143. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/config.py +0 -0
  144. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/client/pyproject.toml +0 -0
  145. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/__init__.py +0 -0
  146. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/settings.py +0 -0
  147. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/urls.py +0 -0
  148. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/manage.py +0 -0
  149. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_ninja/openapi.json +0 -0
  150. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/README.md +0 -0
  151. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/__init__.py +0 -0
  152. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/client.py +0 -0
  153. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/config.py +0 -0
  154. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/pyproject.toml +0 -0
  155. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/__init__.py +0 -0
  156. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/settings.py +0 -0
  157. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/urls.py +0 -0
  158. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/manage.py +0 -0
  159. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/openapi.yaml +0 -0
  160. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/__init__.py +0 -0
  161. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/apps.py +0 -0
  162. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/migrations/0001_initial.py +0 -0
  163. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/migrations/__init__.py +0 -0
  164. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/models.py +0 -0
  165. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/serializers.py +0 -0
  166. {clientele-2.1.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/views.py +0 -0
  167. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/README.md +0 -0
  168. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/__init__.py +0 -0
  169. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/client.py +0 -0
  170. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/config.py +0 -0
  171. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/client/pyproject.toml +0 -0
  172. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/main.py +0 -0
  173. {clientele-2.1.0 → clientele-2.2.0}/server_examples/fastapi/openapi.json +0 -0
  174. {clientele-2.1.0 → clientele-2.2.0}/tests/__init__.py +0 -0
  175. {clientele-2.1.0 → clientele-2.2.0}/tests/api/retries/test_retry.py +0 -0
  176. {clientele-2.1.0 → clientele-2.2.0}/tests/api/stream/test_parser.py +0 -0
  177. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_api_client.py +0 -0
  178. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_api_client_logging.py +0 -0
  179. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_api_client_singleton.py +0 -0
  180. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_api_response_parser.py +0 -0
  181. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_api_typed_dict.py +0 -0
  182. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_direct_requests.py +0 -0
  183. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_requests.py +0 -0
  184. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_stream.py +0 -0
  185. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_stream_httpx.py +0 -0
  186. {clientele-2.1.0 → clientele-2.2.0}/tests/api/test_type_utils.py +0 -0
  187. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/__init__.py +0 -0
  188. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/client.py +0 -0
  189. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/config.py +0 -0
  190. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/async_test_client/pyproject.toml +0 -0
  191. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/__init__.py +0 -0
  192. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/client.py +0 -0
  193. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/config.py +0 -0
  194. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/schemas.py +0 -0
  195. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/__init__.py +0 -0
  196. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/client.py +0 -0
  197. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/config.py +0 -0
  198. {clientele-2.1.0 → clientele-2.2.0}/tests/api_clients/test_client/pyproject.toml +0 -0
  199. {clientele-2.1.0 → clientele-2.2.0}/tests/cache/fixtures.py +0 -0
  200. {clientele-2.1.0 → clientele-2.2.0}/tests/cache/test_backends.py +0 -0
  201. {clientele-2.1.0 → clientele-2.2.0}/tests/cache/test_decorator.py +0 -0
  202. {clientele-2.1.0 → clientele-2.2.0}/tests/cache/test_integration.py +0 -0
  203. {clientele-2.1.0 → clientele-2.2.0}/tests/cache/test_key_generator.py +0 -0
  204. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/callback_example.yaml +0 -0
  205. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/complex_api.yaml +0 -0
  206. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/README.md +0 -0
  207. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/api-with-examples.json +0 -0
  208. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/callback-example.json +0 -0
  209. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/link-example.json +0 -0
  210. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/non-oauth-scopes.json +0 -0
  211. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/petstore-expanded.json +0 -0
  212. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/petstore.json +0 -0
  213. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/tictactoe.json +0 -0
  214. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/uspto.json +0 -0
  215. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/webhook-example.json +0 -0
  216. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/petstore_openapi3.yaml +0 -0
  217. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/1password.yaml +0 -0
  218. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/ably.yaml +0 -0
  219. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/google.yaml +0 -0
  220. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/medium.yaml +0 -0
  221. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/spacetraders.yaml +0 -0
  222. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/realworld/twilio.yaml +0 -0
  223. {clientele-2.1.0 → clientele-2.2.0}/tests/fixtures/regression/dep_query_alias.json +0 -0
  224. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/framework/test_framework_generator_coverage.py +0 -0
  225. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/framework/test_framework_generator_integration.py +0 -0
  226. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/integration_utils.py +0 -0
  227. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/shared/__init__.py +0 -0
  228. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/shared/test_utils.py +0 -0
  229. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/test_base.py +0 -0
  230. {clientele-2.1.0 → clientele-2.2.0}/tests/generators/test_cicerone_compat.py +0 -0
  231. {clientele-2.1.0 → clientele-2.2.0}/tests/graphql/test_graphql_client.py +0 -0
  232. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_aiohttp_backend.py +0 -0
  233. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_backend_config.py +0 -0
  234. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_fake_backend.py +0 -0
  235. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_httpx_backend.py +0 -0
  236. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_niquests_backend.py +0 -0
  237. {clientele-2.1.0 → clientele-2.2.0}/tests/http/backends/test_requests_backend.py +0 -0
  238. {clientele-2.1.0 → clientele-2.2.0}/tests/mypy/mypy_plugin_test_config.ini +0 -0
  239. {clientele-2.1.0 → clientele-2.2.0}/tests/mypy/test_mypy_plugin.py +0 -0
  240. {clientele-2.1.0 → clientele-2.2.0}/tests/test_additional_coverage.py +0 -0
  241. {clientele-2.1.0 → clientele-2.2.0}/tests/test_cli.py +0 -0
  242. {clientele-2.1.0 → clientele-2.2.0}/tests/test_cli_coverage.py +0 -0
  243. {clientele-2.1.0 → clientele-2.2.0}/tests/test_cli_full_coverage.py +0 -0
  244. {clientele-2.1.0 → clientele-2.2.0}/tests/test_edge_case_coverage.py +0 -0
  245. {clientele-2.1.0 → clientele-2.2.0}/tests/test_fixture_schemas.py +0 -0
  246. {clientele-2.1.0 → clientele-2.2.0}/tests/test_generator_coverage.py +0 -0
  247. {clientele-2.1.0 → clientele-2.2.0}/tests/test_nullable_fields.py +0 -0
  248. {clientele-2.1.0 → clientele-2.2.0}/tests/test_openapi_31_null_type.py +0 -0
  249. {clientele-2.1.0 → clientele-2.2.0}/tests/test_ref_handling.py +0 -0
  250. {clientele-2.1.0 → clientele-2.2.0}/tests/test_settings.py +0 -0
  251. {clientele-2.1.0 → clientele-2.2.0}/tests/test_testing_utilities.py +0 -0
  252. {clientele-2.1.0 → clientele-2.2.0}/tests/test_utils.py +0 -0
@@ -24,7 +24,8 @@
24
24
  "Bash(uv pip *)",
25
25
  "Bash(.venv/bin/pytest *)",
26
26
  "Bash(.venv/bin/python *)",
27
- "Bash(pip list *)"
27
+ "Bash(pip list *)",
28
+ "Bash(git *)"
28
29
  ]
29
30
  }
30
31
  }
@@ -1,13 +1,17 @@
1
1
  name: CI - Lint and Test
2
2
 
3
- on: [push]
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
4
8
 
5
9
  jobs:
6
10
  build:
7
11
  runs-on: ubuntu-latest
8
12
  strategy:
9
13
  matrix:
10
- python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
14
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev"]
11
15
  steps:
12
16
  #----------------------------------------------
13
17
  # check-out repo and set-up python
@@ -1,6 +1,14 @@
1
1
  # Change log
2
2
 
3
- ## 2.1.0 UNRELEASED
3
+ ## 2.2.0
4
+
5
+ - Support OpenAPI `default` and `const` in schema generation ([#247](https://github.com/phalt/clientele/pull/247)).
6
+ - Fix `$ref` parameter types and double-nullable handling in client generation ([#247](https://github.com/phalt/clientele/pull/247)).
7
+ - Fix array response schemas being emitted as plain type aliases (`list[...]`) which pydantic rejects in `response_map` — they are now emitted as `clientele.schemas.ListResponse` subclasses, which support `len()`, indexing, and iteration directly ([#248](https://github.com/phalt/clientele/issues/248)).
8
+ - Fix generated `config.py` documenting `API_BASE_URL` as the environment variable name when pydantic-settings reads `BASE_URL` — the example now uses the correct name ([#248](https://github.com/phalt/clientele/issues/248)).
9
+ - Initial CI support for Python 3.15
10
+
11
+ ## 2.1.0
4
12
 
5
13
  - Introduce `RequestsHTTPBackend` for making HTTP requests using the `requests` library.
6
14
  - This backend implements synchronous HTTP requests only.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clientele
3
- Version: 2.1.0
3
+ Version: 2.2.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.1.0 | :white_check_mark: |
8
- | 2.0.0 or less | :x: |
7
+ | 2.2.0 | :white_check_mark: |
8
+ | 2.1.0 or less | :x: |
9
9
 
10
10
  ## Reporting a Vulnerability
11
11
 
@@ -19,14 +19,12 @@ class Config(clientele_api.BaseConfig):
19
19
 
20
20
  Example:
21
21
  # From environment variables
22
- export API_BASE_URL="https://api.example.com"
23
- export BEARER_TOKEN="my-secret-token"
22
+ export BASE_URL="https://api.example.com"
24
23
  config = Config()
25
24
 
26
25
  # Direct instantiation
27
26
  config = Config(
28
- api_base_url="https://api.example.com",
29
- bearer_token="my-token",
27
+ base_url="https://api.example.com",
30
28
  timeout=10.0
31
29
  )
32
30
  """
@@ -0,0 +1,4 @@
1
+
2
+
3
+ class {{class_name}}(ListResponse[{{inner_type}}]):
4
+ pass
@@ -6,3 +6,5 @@ import decimal
6
6
  import enum
7
7
 
8
8
  import pydantic
9
+
10
+ from clientele.schemas import ListResponse # noqa
@@ -143,16 +143,20 @@ class BaseClientsGenerator:
143
143
  required = param.get("required", False) or in_ != "query"
144
144
  if in_ == "query":
145
145
  # URL query string values
146
+ param_type = utils.resolve_forward_refs_for_client(utils.get_type(param["schema"]))
146
147
  if required:
147
- query_args[clean_key] = utils.get_type(param["schema"])
148
+ query_args[clean_key] = param_type
148
149
  else:
149
- query_args[clean_key] = f"typing.Optional[{utils.get_type(param['schema'])}]"
150
+ param_type = utils.strip_none_from_type(param_type)
151
+ query_args[clean_key] = f"typing.Optional[{param_type}]"
150
152
  elif in_ == "path":
151
153
  # Function arguments
154
+ param_type = utils.resolve_forward_refs_for_client(utils.get_type(param["schema"]))
152
155
  if required:
153
- path_args[clean_key] = utils.get_type(param["schema"])
156
+ path_args[clean_key] = param_type
154
157
  else:
155
- path_args[clean_key] = f"typing.Optional[{utils.get_type(param['schema'])}]"
158
+ param_type = utils.strip_none_from_type(param_type)
159
+ path_args[clean_key] = f"typing.Optional[{param_type}]"
156
160
  elif in_ == "header":
157
161
  # Header object arguments
158
162
  headers_args[param["name"]] = utils.get_type(param["schema"])
@@ -191,6 +191,14 @@ def schema_to_dict(schema) -> dict:
191
191
  if hasattr(schema, "__pydantic_extra__") and schema.__pydantic_extra__ and "enum" in schema.__pydantic_extra__:
192
192
  result["enum"] = schema.__pydantic_extra__["enum"]
193
193
 
194
+ # Handle default - it's in the extra fields; use "in" check since defaults can be falsy
195
+ if hasattr(schema, "__pydantic_extra__") and schema.__pydantic_extra__ and "default" in schema.__pydantic_extra__:
196
+ result["default"] = schema.__pydantic_extra__["default"]
197
+
198
+ # Handle const - it's in the extra fields
199
+ if hasattr(schema, "__pydantic_extra__") and schema.__pydantic_extra__ and "const" in schema.__pydantic_extra__:
200
+ result["const"] = schema.__pydantic_extra__["const"]
201
+
194
202
  # Handle properties
195
203
  if hasattr(schema, "properties") and schema.properties:
196
204
  result["properties"] = {k: schema_to_dict(v) for k, v in schema.properties.items()}
@@ -54,12 +54,19 @@ class SchemasGenerator:
54
54
  sanitized_arg = utils.snake_case_prop(arg)
55
55
  arg_type = utils.get_type(arg_details)
56
56
  is_optional = required and arg not in required
57
+ has_default = "default" in arg_details
57
58
 
58
59
  needs_alias = sanitized_arg != arg
59
60
  if needs_alias:
60
61
  has_aliases = True
61
62
 
62
- if is_optional and not arg_type.startswith("typing.Optional["):
63
+ if has_default:
64
+ py_default = repr(arg_details["default"])
65
+ if needs_alias:
66
+ type_string = f'{arg_type} = pydantic.Field(default={py_default}, alias="{arg}")'
67
+ else:
68
+ type_string = f"{arg_type} = {py_default}"
69
+ elif is_optional and not arg_type.startswith("typing.Optional["):
63
70
  if needs_alias:
64
71
  type_string = f'typing.Optional[{arg_type}] = pydantic.Field(default=None, alias="{arg}")'
65
72
  else:
@@ -128,10 +135,15 @@ class SchemasGenerator:
128
135
  return
129
136
 
130
137
  if schema.get("type") == "array":
131
- array_type = utils.get_type(schema)
132
- array_type = utils.remove_forward_ref_quotes(array_type)
133
- template = self.writer.templates.get_template("schema_type_alias.jinja2")
134
- content = template.render(class_name=schema_key, union_type=array_type)
138
+ # Emit a pydantic.RootModel subclass rather than a plain type alias.
139
+ # list[...] is a GenericAlias, not a type, so pydantic rejects it
140
+ # when it appears in response_map: dict[int, type[Any]]. A RootModel
141
+ # subclass is a proper class and satisfies that constraint.
142
+ items_schema = schema.get("items") or {}
143
+ item_type = utils.get_type(items_schema) if items_schema else "typing.Any"
144
+ item_type = utils.remove_forward_ref_quotes(item_type)
145
+ template = self.writer.templates.get_template("schema_root_model.jinja2")
146
+ content = template.render(class_name=schema_key, inner_type=item_type)
135
147
  self.writer.write_to_schemas(content, output_dir=self.output_dir)
136
148
  self.schemas[schema_key] = ""
137
149
  return
@@ -97,6 +97,9 @@ def get_func_name(operation: dict, path: str) -> str:
97
97
 
98
98
 
99
99
  def get_type(t):
100
+ if "const" in t:
101
+ return f"typing.Literal[{repr(t['const'])}]"
102
+
100
103
  t_type = t.get("type")
101
104
  t_format = t.get("format")
102
105
  t_nullable = t.get("nullable", False)
@@ -277,8 +280,21 @@ def remove_forward_ref_quotes(type_string: str) -> str:
277
280
  This is used for type aliases where forward references are not needed
278
281
  because all types are defined in the same module and model_rebuild() is called.
279
282
  """
280
- import re
281
-
282
- # Replace quoted strings within type annotations
283
- # Pattern: matches quoted strings that are type names (alphanumeric + underscore)
284
283
  return re.sub(r'"([A-Za-z_][A-Za-z0-9_]*)"', r"\1", type_string)
284
+
285
+
286
+ def resolve_forward_refs_for_client(type_string: str) -> str:
287
+ """Replace forward references with schemas module references for client.py context."""
288
+ return re.sub(r'"([A-Za-z_][A-Za-z0-9_]*)"', r"schemas.\1", type_string)
289
+
290
+
291
+ def strip_none_from_type(type_string: str) -> str:
292
+ """Strip None/Optional wrapping so the caller can re-wrap without duplication."""
293
+ if type_string.startswith("typing.Optional[") and type_string.endswith("]"):
294
+ return type_string[len("typing.Optional[") : -1]
295
+ m = re.match(r"^typing\.Union\[(.+),\s*None\]$", type_string)
296
+ if m:
297
+ return m.group(1)
298
+ if type_string.endswith(" | None"):
299
+ return type_string[: -len(" | None")]
300
+ return type_string
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+
7
+ _Item = typing.TypeVar("_Item")
8
+
9
+
10
+ class ListResponse(pydantic.RootModel[list[_Item]]):
11
+ """Base class for array response schemas. Provides list-like access to the root list."""
12
+
13
+ def __len__(self) -> int:
14
+ return len(self.root)
15
+
16
+ def __getitem__(self, index: int) -> _Item:
17
+ return self.root[index]
18
+
19
+ def __iter__(self) -> typing.Iterator[_Item]: # ty: ignore[invalid-method-override]
20
+ return iter(self.root)
@@ -1,6 +1,6 @@
1
1
  import platform
2
2
 
3
- VERSION = "2.1.0"
3
+ VERSION = "2.2.0"
4
4
 
5
5
 
6
6
  def split_ver():
@@ -1,6 +1,14 @@
1
1
  # Change log
2
2
 
3
- ## 2.1.0 UNRELEASED
3
+ ## 2.2.0
4
+
5
+ - Support OpenAPI `default` and `const` in schema generation ([#247](https://github.com/phalt/clientele/pull/247)).
6
+ - Fix `$ref` parameter types and double-nullable handling in client generation ([#247](https://github.com/phalt/clientele/pull/247)).
7
+ - Fix array response schemas being emitted as plain type aliases (`list[...]`) which pydantic rejects in `response_map` — they are now emitted as `clientele.schemas.ListResponse` subclasses, which support `len()`, indexing, and iteration directly ([#248](https://github.com/phalt/clientele/issues/248)).
8
+ - Fix generated `config.py` documenting `API_BASE_URL` as the environment variable name when pydantic-settings reads `BASE_URL` — the example now uses the correct name ([#248](https://github.com/phalt/clientele/issues/248)).
9
+ - Initial CI support for Python 3.15
10
+
11
+ ## 2.1.0
4
12
 
5
13
  - Introduce `RequestsHTTPBackend` for making HTTP requests using the `requests` library.
6
14
  - This backend implements synchronous HTTP requests only.
@@ -26,7 +26,7 @@ How Clientele works:
26
26
  - Path parameters inside `{}` are filled from the function arguments (e.g. `user_id`).
27
27
  - Any remaining keyword arguments (like `include_details` above) become query parameters, but you can also provide a dict function parameter `query={...}` instead.
28
28
  - The **`result` parameter is mandatory** and its type annotation (`User`) drives response parsing.
29
- - Your function is injected with the `result` parameter - this is the response payload hydrated into your `result` parameter's type.
29
+ - Your function is injected with the `result` parameter - this is the response payload hydrated into your `result` parameter's type. Callers never supply it; Clientele injects it after the HTTP response arrives.
30
30
  - The function's return value is independent - you can return the result directly, transform it, or return something completely different.
31
31
 
32
32
  ## HTTP POST example
@@ -147,31 +147,9 @@ Clientele will inject the following parameters into your function once an http r
147
147
  - `result`: an instance of the type specified in the `result` parameter annotation. This parameter is **mandatory** and its type annotation determines how the response is parsed. Can be a Pydantic model or a TypedDict.
148
148
  - `response`: the `httpx.Response` - useful for logging, debugging etc. (optional)
149
149
 
150
- **Both `result` and `response` must be declared first in your function signature**, before any caller-supplied parameters (path params, query params, `data`). This is required for type checker support (see below).
150
+ **Both `result` and `response` must be declared first in your function signature**, before any caller-supplied parameters (path params, query params, `data`).
151
151
 
152
- ## Typing support for injected parameters
153
-
154
- Clientele does some Python typing magic to give type checkers (mypy, pyright, ty) a correct view of your decorated functions.
155
-
156
- The decorator type signature says: *"I accept a function whose first parameter(s) are injected values, and I return a new callable with those parameters removed."* This means type checkers see the public API — for example `get_user` is typed as `(user_id: int) -> User`, with `result` invisible to callers.
157
-
158
- For this to work, **`result` and `response` must appear first** in the parameter list:
159
-
160
- ```python
161
- # Correct — injected params first, type checker sees: (user_id: int) -> User
162
- @client.get("/users/{user_id}")
163
- def get_user(result: User, user_id: int) -> User:
164
- return result
165
-
166
- # Incorrect — result is not first, typing will not work correctly
167
- @client.get("/users/{user_id}")
168
- def get_user(user_id: int, result: User) -> User:
169
- return result
170
- ```
171
-
172
- At runtime, Clientele also patches the function's signature to hide `result` and `response` from introspection tools (IDEs, `inspect.signature()`, the cache key generator). This uses the same ordering rule: injected params are identified by name, stripped from the public signature, and injected automatically when the HTTP response arrives.
173
-
174
- If you use the [mypy plugin](mypy.md), mypy will also strip injected parameters by name regardless of position — but for other type checkers (pyright, ty) the position-first rule is required.
152
+ For a full explanation of why this constraint exists, how Clientele hides these parameters from callers and type checkers, and how to configure mypy support, see [Injected parameters & typing](injected-parameters.md).
175
153
 
176
154
  ## Response parsing rules
177
155
 
@@ -0,0 +1,135 @@
1
+ # Injected parameters & typing
2
+
3
+ ## The problem they solve
4
+
5
+ When you decorate a function with `@client.get(...)`, Clientele needs to hand back the parsed HTTP response to your function body. The natural way to do that is through a parameter, but that creates an awkward situation:
6
+
7
+ ```python
8
+ @client.get("/users/{user_id}")
9
+ def get_user(result: User, user_id: int) -> User:
10
+ return result
11
+ ```
12
+
13
+ From *inside* the function, `result` is essential because it holds the `User` parsed from the response. But from the *caller's* perspective, `result` makes no sense: callers supply `user_id`, not a `User` they already have. If Clientele exposed `result` in the public signature, every call site would look broken:
14
+
15
+ ```python
16
+ # Without injection stripping the callers would need to provide result themselves:
17
+ get_user(result=???, user_id=42)
18
+ ```
19
+
20
+ Injected parameters are how Clientele resolves this: `result` (and the optional `response`) live in the function definition for *your* use, but Clientele **strips them from the public signature** so callers never see them.
21
+
22
+ ---
23
+
24
+ ## What callers and IDEs see
25
+
26
+ Clientele uses Python's `typing.Concatenate` and `typing.ParamSpec` to express this contract to type checkers at the decorator level. The decorator signature says: *"I accept a function whose first N parameters are injected values, and I return a new callable with those parameters removed."*
27
+
28
+ The result is that your IDE and type checker see the **public API**, not the implementation detail:
29
+
30
+ ```
31
+ # What you write:
32
+ def get_user(result: User, user_id: int) -> User
33
+
34
+ # What your IDE and type checker see after decoration:
35
+ get_user(user_id: int) -> User
36
+ ```
37
+
38
+ So autocomplete shows `user_id`, not `result`. Passing `user_id=42` produces no type error. Passing `result=...` produces an error, because `result` isn't in the public signature at all.
39
+
40
+ ```python
41
+ get_user(user_id=42) # ✅ correct
42
+ get_user(result=my_user, user_id=42) # ❌ type error: unknown argument
43
+ ```
44
+
45
+ At runtime, Clientele also patches the function's `__signature__` so that `inspect.signature()`, IDEs using runtime introspection, and Clientele's own cache key generator all agree with what the type checker sees.
46
+
47
+ ---
48
+
49
+ ## Injected parameters
50
+
51
+ Clientele injects the following parameters:
52
+
53
+ | Parameter | Type | Required | Description |
54
+ |------------|----------------------------|----------|---------------------------------------------------------------------|
55
+ | `result` | Your annotated type | Yes | The HTTP response body, parsed and validated into the declared type |
56
+ | `response` | `http.Response` | No | The raw `http.Response`, useful for headers, status, debugging |
57
+
58
+ ```python
59
+ from pydantic import BaseModel
60
+ from clientele import api as clientele_api
61
+
62
+ client = clientele_api.APIClient(base_url="https://api.example.com")
63
+
64
+ class User(BaseModel):
65
+ id: int
66
+ name: str
67
+
68
+ # result only — the common case
69
+ @client.get("/users/{user_id}")
70
+ def get_user(result: User, user_id: int) -> User:
71
+ return result
72
+
73
+ # result + response — when you need headers or status
74
+ @client.get("/users/{user_id}")
75
+ def get_user_with_meta(result: User, response: httpx.Response, user_id: int) -> tuple[User, int]:
76
+ return result, response.status_code
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Ordering requirement
82
+
83
+ **`result` and `response` must appear first in the parameter list**, before any caller-supplied parameters (path params, query params, `data`).
84
+
85
+ This is required because the `Concatenate` trick works by stripping parameters from the *front* of the signature. If injected params appear anywhere else, the type math breaks and type checkers will see the wrong signature.
86
+
87
+ ```python
88
+ # ✅ Correct: injected params first
89
+ # Type checker sees: get_user(user_id: int) -> User
90
+ @client.get("/users/{user_id}")
91
+ def get_user(result: User, user_id: int) -> User:
92
+ return result
93
+
94
+ # ❌ Incorrect: result is not first
95
+ # Type checker sees: get_user(user_id: int, result: User) -> User
96
+ @client.get("/users/{user_id}")
97
+ def get_user(user_id: int, result: User) -> User:
98
+ return result
99
+ ```
100
+
101
+ The runtime injection works regardless of position (Clientele finds injected params by name), but **correct typing requires them first**.
102
+
103
+ ---
104
+
105
+ ## Mypy plugin
106
+
107
+ Mypy does not natively support the `Concatenate`-based approach for stripping positional parameters. Clientele ships a mypy plugin that teaches mypy the same rule explicitly:
108
+
109
+ - Removes `result` and `response` from decorated function signatures during type checking
110
+ - Allows `dict` types to be passed where Pydantic models are expected (TypedDict ergonomics)
111
+
112
+ ### Setup
113
+
114
+ The plugin is installed automatically with Clientele. Add it to your mypy configuration:
115
+
116
+ === "mypy.ini / setup.cfg"
117
+
118
+ ```ini
119
+ [mypy]
120
+ plugins = clientele.mypy
121
+ ```
122
+
123
+ === "pyproject.toml"
124
+
125
+ ```toml
126
+ [tool.mypy]
127
+ plugins = ["clientele.mypy"]
128
+ ```
129
+
130
+ With the plugin active, mypy behaves the same as pyright and ty: `result` and `response` are invisible to callers.
131
+
132
+ !!! note
133
+ If you use **pyright** or **ty**, no plugin is needed — the `Concatenate`-based decorator signature is supported natively. Just ensure injected params appear first (see above).
134
+
135
+ ---
@@ -47,7 +47,7 @@ Once installed, you can run `clientele version` to confirm:
47
47
 
48
48
  ```sh
49
49
  > clientele version
50
- clientele 2.1.0
50
+ clientele 2.2.0
51
51
  ```
52
52
 
53
53
  ## Next steps
@@ -0,0 +1,85 @@
1
+ {
2
+ "openapi": "3.0.2",
3
+ "info": {
4
+ "title": "Issue 248 Reproduction",
5
+ "description": "Reproduces Bug 2 (array RootModel) and Bug 3 (config env var name) from issue #248",
6
+ "version": "1.0.0"
7
+ },
8
+ "servers": [
9
+ {"url": "https://api.example.com"}
10
+ ],
11
+ "paths": {
12
+ "/faces": {
13
+ "get": {
14
+ "summary": "Get Faces",
15
+ "operationId": "get_faces",
16
+ "parameters": [
17
+ {
18
+ "name": "faces_type",
19
+ "in": "query",
20
+ "required": false,
21
+ "schema": {
22
+ "type": "array",
23
+ "items": {"$ref": "#/components/schemas/FaceTypeEnum"}
24
+ }
25
+ }
26
+ ],
27
+ "responses": {
28
+ "200": {
29
+ "description": "Successful Response",
30
+ "content": {
31
+ "application/json": {
32
+ "schema": {"$ref": "#/components/schemas/FaceItem"}
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ },
39
+ "/upload-faces": {
40
+ "post": {
41
+ "summary": "Upload Faces",
42
+ "operationId": "upload_faces",
43
+ "requestBody": {
44
+ "required": true,
45
+ "content": {
46
+ "application/json": {
47
+ "schema": {"$ref": "#/components/schemas/FaceItem"}
48
+ }
49
+ }
50
+ },
51
+ "responses": {
52
+ "200": {
53
+ "description": "Successful Response",
54
+ "content": {
55
+ "application/json": {
56
+ "schema": {
57
+ "type": "array",
58
+ "items": {"$ref": "#/components/schemas/FaceItem"}
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ },
67
+ "components": {
68
+ "schemas": {
69
+ "FaceTypeEnum": {
70
+ "type": "string",
71
+ "enum": ["FRONT", "SIDE", "BACK"],
72
+ "title": "FaceTypeEnum"
73
+ },
74
+ "FaceItem": {
75
+ "type": "object",
76
+ "title": "FaceItem",
77
+ "properties": {
78
+ "id": {"type": "integer"},
79
+ "name": {"type": "string"}
80
+ },
81
+ "required": ["id", "name"]
82
+ }
83
+ }
84
+ }
85
+ }
@@ -49,7 +49,7 @@ nav:
49
49
  - 🪵 Logging: api-logging.md
50
50
  - 🧪 Testing: api-testing.md
51
51
  - 🎯 Direct requests: api-direct-requests.md
52
- - 🥧 Mypy support: mypy.md
52
+ - 💉 Injected parameters & typing: injected-parameters.md
53
53
  - 📟 Command line tools:
54
54
  - 🏗️ Client creator: openapi-cli.md
55
55
  - ⚡️ API Server Integrations:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "clientele"
3
- version = "2.1.0"
3
+ version = "2.2.0"
4
4
  description = "Clientele is a different way to build Python API Clients"
5
5
  authors = [{ name = "Paul Hallett", email = "paulandrewhallett@gmail.com" }]
6
6
  license = { text = "MIT" }
@@ -16,7 +16,7 @@ uv add clientele
16
16
 
17
17
  API VERSION: 1.0.0
18
18
  OPENAPI VERSION: 3.1.0
19
- CLIENTELE VERSION: 2.1.0
19
+ CLIENTELE VERSION: 2.2.0
20
20
 
21
21
  Regenerate using this command:
22
22
 
@@ -5,6 +5,8 @@ import typing
5
5
 
6
6
  import pydantic
7
7
 
8
+ from clientele.schemas import ListResponse # noqa
9
+
8
10
 
9
11
  class UserOut(pydantic.BaseModel):
10
12
  id: int
@@ -19,7 +21,8 @@ class UserIn(pydantic.BaseModel):
19
21
  age: int
20
22
 
21
23
 
22
- Response = list[UserOut]
24
+ class Response(ListResponse[UserOut]):
25
+ pass
23
26
 
24
27
 
25
28
  def get_subclasses_from_same_file() -> list[typing.Type[pydantic.BaseModel]]:
@@ -16,7 +16,7 @@ uv add clientele
16
16
 
17
17
  API VERSION: 1.0.0
18
18
  OPENAPI VERSION: 3.0.3
19
- CLIENTELE VERSION: 2.1.0
19
+ CLIENTELE VERSION: 2.2.0
20
20
 
21
21
  Regenerate using this command:
22
22
 
@@ -5,6 +5,8 @@ import typing
5
5
 
6
6
  import pydantic
7
7
 
8
+ from clientele.schemas import ListResponse # noqa
9
+
8
10
 
9
11
  class PatchedUser(pydantic.BaseModel):
10
12
  id: int
@@ -25,7 +27,8 @@ class UserRequest(pydantic.BaseModel):
25
27
  email: str
26
28
 
27
29
 
28
- ListUsers200Response = list[User]
30
+ class ListUsers200Response(ListResponse[User]):
31
+ pass
29
32
 
30
33
 
31
34
  def get_subclasses_from_same_file() -> list[typing.Type[pydantic.BaseModel]]:
@@ -16,7 +16,7 @@ uv add clientele
16
16
 
17
17
  API VERSION: 1.0.0
18
18
  OPENAPI VERSION: 3.1.0
19
- CLIENTELE VERSION: 2.1.0
19
+ CLIENTELE VERSION: 2.2.0
20
20
 
21
21
  Regenerate using this command:
22
22
 
@@ -5,6 +5,8 @@ import typing
5
5
 
6
6
  import pydantic
7
7
 
8
+ from clientele.schemas import ListResponse # noqa
9
+
8
10
 
9
11
  class CreateUserRequest(pydantic.BaseModel):
10
12
  name: str
@@ -29,7 +31,8 @@ class ValidationError(pydantic.BaseModel):
29
31
  model_config = pydantic.ConfigDict(populate_by_name=True)
30
32
 
31
33
 
32
- ResponseListUsers = list[UserResponse]
34
+ class ResponseListUsers(ListResponse[UserResponse]):
35
+ pass
33
36
 
34
37
 
35
38
  def get_subclasses_from_same_file() -> list[typing.Type[pydantic.BaseModel]]: