clientele 2.0.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 (253) hide show
  1. {clientele-2.0.0 → clientele-2.2.0}/.claude/settings.local.json +6 -1
  2. {clientele-2.0.0 → clientele-2.2.0}/.github/workflows/ci.yml +6 -2
  3. {clientele-2.0.0 → clientele-2.2.0}/CHANGELOG.md +20 -0
  4. {clientele-2.0.0 → clientele-2.2.0}/PKG-INFO +1 -1
  5. {clientele-2.0.0 → clientele-2.2.0}/SECURITY.md +2 -2
  6. {clientele-2.0.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.0.0 → clientele-2.2.0}/clientele/generators/api/templates/schemas_py.jinja2 +2 -0
  9. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/base_clients.py +8 -4
  10. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/cicerone_compat.py +8 -0
  11. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/shared/generators/schemas.py +17 -5
  12. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/shared/utils.py +20 -4
  13. clientele-2.2.0/clientele/http/aiohttp_backend.py +165 -0
  14. clientele-2.2.0/clientele/http/niquests_backend.py +216 -0
  15. clientele-2.2.0/clientele/http/requests_backend.py +160 -0
  16. clientele-2.2.0/clientele/schemas.py +20 -0
  17. {clientele-2.0.0 → clientele-2.2.0}/clientele/settings.py +1 -1
  18. {clientele-2.0.0 → clientele-2.2.0}/docs/CHANGELOG.md +20 -0
  19. clientele-2.2.0/docs/api-http-backends.md +340 -0
  20. {clientele-2.0.0 → clientele-2.2.0}/docs/api-overview.md +3 -25
  21. clientele-2.2.0/docs/injected-parameters.md +135 -0
  22. {clientele-2.0.0 → clientele-2.2.0}/docs/install.md +1 -1
  23. clientele-2.2.0/example_openapi_specs/issue_248.json +85 -0
  24. {clientele-2.0.0 → clientele-2.2.0}/mkdocs.yml +1 -1
  25. {clientele-2.0.0 → clientele-2.2.0}/pyproject.toml +6 -1
  26. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/MANIFEST.md +1 -1
  27. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/schemas.py +4 -1
  28. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/MANIFEST.md +1 -1
  29. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/schemas.py +4 -1
  30. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/MANIFEST.md +1 -1
  31. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/schemas.py +4 -1
  32. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/MANIFEST.md +1 -1
  33. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/schemas.py +2 -0
  34. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/MANIFEST.md +1 -1
  35. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/MANIFEST.md +1 -1
  36. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/schemas.py +2 -0
  37. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/shared/test_utils_coverage.py +22 -0
  38. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/test_base_and_basic_coverage.py +42 -0
  39. clientele-2.2.0/tests/generators/test_schemas_coverage.py +233 -0
  40. clientele-2.2.0/tests/http/backends/test_aiohttp_backend.py +222 -0
  41. {clientele-2.0.0 → clientele-2.2.0}/tests/http/backends/test_httpx_backend.py +15 -0
  42. clientele-2.2.0/tests/http/backends/test_niquests_backend.py +235 -0
  43. clientele-2.2.0/tests/http/backends/test_requests_backend.py +192 -0
  44. {clientele-2.0.0 → clientele-2.2.0}/tests/test_complex_schemas.py +10 -9
  45. clientele-2.2.0/tests/test_issue_248.py +126 -0
  46. {clientele-2.0.0 → clientele-2.2.0}/uv.lock +892 -1
  47. clientele-2.0.0/docs/api-http-backends.md +0 -214
  48. clientele-2.0.0/docs/mypy.md +0 -63
  49. clientele-2.0.0/tests/generators/test_schemas_coverage.py +0 -96
  50. {clientele-2.0.0 → clientele-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  51. {clientele-2.0.0 → clientele-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  52. {clientele-2.0.0 → clientele-2.2.0}/.github/compatibility.json +0 -0
  53. {clientele-2.0.0 → clientele-2.2.0}/.github/dependabot.yml +0 -0
  54. {clientele-2.0.0 → clientele-2.2.0}/.github/pull_request_template.md +0 -0
  55. {clientele-2.0.0 → clientele-2.2.0}/.gitignore +0 -0
  56. {clientele-2.0.0 → clientele-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  57. {clientele-2.0.0 → clientele-2.2.0}/CONTRIBUTING.md +0 -0
  58. {clientele-2.0.0 → clientele-2.2.0}/CONTRIBUTORS.md +0 -0
  59. {clientele-2.0.0 → clientele-2.2.0}/LICENSE +0 -0
  60. {clientele-2.0.0 → clientele-2.2.0}/Makefile +0 -0
  61. {clientele-2.0.0 → clientele-2.2.0}/PUBLISHING.md +0 -0
  62. {clientele-2.0.0 → clientele-2.2.0}/README.md +0 -0
  63. {clientele-2.0.0 → clientele-2.2.0}/clientele/__init__.py +0 -0
  64. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/__init__.py +0 -0
  65. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/client.py +0 -0
  66. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/config.py +0 -0
  67. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/exceptions.py +0 -0
  68. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/requests.py +0 -0
  69. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/stream/__init__.py +0 -0
  70. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/stream/parser.py +0 -0
  71. {clientele-2.0.0 → clientele-2.2.0}/clientele/api/type_utils.py +0 -0
  72. {clientele-2.0.0 → clientele-2.2.0}/clientele/cache/__init__.py +0 -0
  73. {clientele-2.0.0 → clientele-2.2.0}/clientele/cache/backends.py +0 -0
  74. {clientele-2.0.0 → clientele-2.2.0}/clientele/cache/decorator.py +0 -0
  75. {clientele-2.0.0 → clientele-2.2.0}/clientele/cache/key_generator.py +0 -0
  76. {clientele-2.0.0 → clientele-2.2.0}/clientele/cache/types.py +0 -0
  77. {clientele-2.0.0 → clientele-2.2.0}/clientele/cli.py +0 -0
  78. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/README.md +0 -0
  79. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/__init__.py +0 -0
  80. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/__init__.py +0 -0
  81. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/generator.py +0 -0
  82. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/generators/__init__.py +0 -0
  83. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/generators/clients.py +0 -0
  84. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/generators/schemas.py +0 -0
  85. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/api_get_method.jinja2 +0 -0
  86. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/api_post_method.jinja2 +0 -0
  87. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/client_py.jinja2 +0 -0
  88. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/manifest.jinja2 +0 -0
  89. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/pyproject.toml.jinja2 +0 -0
  90. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_class.jinja2 +0 -0
  91. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_helpers.jinja2 +0 -0
  92. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/templates/schema_type_alias.jinja2 +0 -0
  93. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/api/writer.py +0 -0
  94. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/base.py +0 -0
  95. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/__init__.py +0 -0
  96. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/generator.py +0 -0
  97. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/templates/client_py.jinja2 +0 -0
  98. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/templates/config_py.jinja2 +0 -0
  99. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/templates/manifest.jinja2 +0 -0
  100. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/templates/schemas_py.jinja2 +0 -0
  101. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/basic/writer.py +0 -0
  102. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/schema_utils.py +0 -0
  103. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/shared/__init__.py +0 -0
  104. {clientele-2.0.0 → clientele-2.2.0}/clientele/generators/shared/generators/__init__.py +0 -0
  105. {clientele-2.0.0 → clientele-2.2.0}/clientele/graphql/__init__.py +0 -0
  106. {clientele-2.0.0 → clientele-2.2.0}/clientele/graphql/client.py +0 -0
  107. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/__init__.py +0 -0
  108. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/backends.py +0 -0
  109. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/fake_backend.py +0 -0
  110. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/httpx_backend.py +0 -0
  111. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/response.py +0 -0
  112. {clientele-2.0.0 → clientele-2.2.0}/clientele/http/status_codes.py +0 -0
  113. {clientele-2.0.0 → clientele-2.2.0}/clientele/mypy.py +0 -0
  114. {clientele-2.0.0 → clientele-2.2.0}/clientele/py.typed +0 -0
  115. {clientele-2.0.0 → clientele-2.2.0}/clientele/retries/__init__.py +0 -0
  116. {clientele-2.0.0 → clientele-2.2.0}/clientele/retries/decorators.py +0 -0
  117. {clientele-2.0.0 → clientele-2.2.0}/clientele/testing.py +0 -0
  118. {clientele-2.0.0 → clientele-2.2.0}/clientele/utils.py +0 -0
  119. {clientele-2.0.0 → clientele-2.2.0}/docs/CNAME +0 -0
  120. {clientele-2.0.0 → clientele-2.2.0}/docs/api-async.md +0 -0
  121. {clientele-2.0.0 → clientele-2.2.0}/docs/api-authentication.md +0 -0
  122. {clientele-2.0.0 → clientele-2.2.0}/docs/api-cache.md +0 -0
  123. {clientele-2.0.0 → clientele-2.2.0}/docs/api-configuration.md +0 -0
  124. {clientele-2.0.0 → clientele-2.2.0}/docs/api-direct-requests.md +0 -0
  125. {clientele-2.0.0 → clientele-2.2.0}/docs/api-examples.md +0 -0
  126. {clientele-2.0.0 → clientele-2.2.0}/docs/api-exceptions.md +0 -0
  127. {clientele-2.0.0 → clientele-2.2.0}/docs/api-graphql.md +0 -0
  128. {clientele-2.0.0 → clientele-2.2.0}/docs/api-logging.md +0 -0
  129. {clientele-2.0.0 → clientele-2.2.0}/docs/api-retries.md +0 -0
  130. {clientele-2.0.0 → clientele-2.2.0}/docs/api-stream.md +0 -0
  131. {clientele-2.0.0 → clientele-2.2.0}/docs/api-testing.md +0 -0
  132. {clientele-2.0.0 → clientele-2.2.0}/docs/clientele_generate.gif +0 -0
  133. {clientele-2.0.0 → clientele-2.2.0}/docs/clientele_header.png +0 -0
  134. {clientele-2.0.0 → clientele-2.2.0}/docs/favicon.png +0 -0
  135. {clientele-2.0.0 → clientele-2.2.0}/docs/index.md +0 -0
  136. {clientele-2.0.0 → clientele-2.2.0}/docs/openapi-cli.md +0 -0
  137. {clientele-2.0.0 → clientele-2.2.0}/docs/server-django-ninja.md +0 -0
  138. {clientele-2.0.0 → clientele-2.2.0}/docs/server-drf.md +0 -0
  139. {clientele-2.0.0 → clientele-2.2.0}/docs/server-fastapi.md +0 -0
  140. {clientele-2.0.0 → clientele-2.2.0}/example_openapi_specs/best.json +0 -0
  141. {clientele-2.0.0 → clientele-2.2.0}/example_openapi_specs/complex_schemas.json +0 -0
  142. {clientele-2.0.0 → clientele-2.2.0}/example_openapi_specs/simple.json +0 -0
  143. {clientele-2.0.0 → clientele-2.2.0}/example_openapi_specs/test_303.yaml +0 -0
  144. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/README.md +0 -0
  145. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/api.py +0 -0
  146. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/__init__.py +0 -0
  147. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/client.py +0 -0
  148. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/config.py +0 -0
  149. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/client/pyproject.toml +0 -0
  150. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/__init__.py +0 -0
  151. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/settings.py +0 -0
  152. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/example_project/urls.py +0 -0
  153. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/manage.py +0 -0
  154. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_ninja/openapi.json +0 -0
  155. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/README.md +0 -0
  156. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/__init__.py +0 -0
  157. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/client.py +0 -0
  158. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/config.py +0 -0
  159. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/client/pyproject.toml +0 -0
  160. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/__init__.py +0 -0
  161. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/settings.py +0 -0
  162. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/example_project/urls.py +0 -0
  163. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/manage.py +0 -0
  164. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/openapi.yaml +0 -0
  165. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/__init__.py +0 -0
  166. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/apps.py +0 -0
  167. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/migrations/0001_initial.py +0 -0
  168. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/migrations/__init__.py +0 -0
  169. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/models.py +0 -0
  170. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/serializers.py +0 -0
  171. {clientele-2.0.0 → clientele-2.2.0}/server_examples/django_rest_framework/users/views.py +0 -0
  172. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/README.md +0 -0
  173. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/__init__.py +0 -0
  174. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/client.py +0 -0
  175. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/config.py +0 -0
  176. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/client/pyproject.toml +0 -0
  177. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/main.py +0 -0
  178. {clientele-2.0.0 → clientele-2.2.0}/server_examples/fastapi/openapi.json +0 -0
  179. {clientele-2.0.0 → clientele-2.2.0}/tests/__init__.py +0 -0
  180. {clientele-2.0.0 → clientele-2.2.0}/tests/api/retries/test_retry.py +0 -0
  181. {clientele-2.0.0 → clientele-2.2.0}/tests/api/stream/test_parser.py +0 -0
  182. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_api_client.py +0 -0
  183. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_api_client_logging.py +0 -0
  184. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_api_client_singleton.py +0 -0
  185. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_api_response_parser.py +0 -0
  186. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_api_typed_dict.py +0 -0
  187. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_direct_requests.py +0 -0
  188. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_requests.py +0 -0
  189. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_stream.py +0 -0
  190. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_stream_httpx.py +0 -0
  191. {clientele-2.0.0 → clientele-2.2.0}/tests/api/test_type_utils.py +0 -0
  192. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/__init__.py +0 -0
  193. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/client.py +0 -0
  194. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/config.py +0 -0
  195. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/async_test_client/pyproject.toml +0 -0
  196. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/__init__.py +0 -0
  197. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/client.py +0 -0
  198. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/config.py +0 -0
  199. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_basic_client/schemas.py +0 -0
  200. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/__init__.py +0 -0
  201. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/client.py +0 -0
  202. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/config.py +0 -0
  203. {clientele-2.0.0 → clientele-2.2.0}/tests/api_clients/test_client/pyproject.toml +0 -0
  204. {clientele-2.0.0 → clientele-2.2.0}/tests/cache/fixtures.py +0 -0
  205. {clientele-2.0.0 → clientele-2.2.0}/tests/cache/test_backends.py +0 -0
  206. {clientele-2.0.0 → clientele-2.2.0}/tests/cache/test_decorator.py +0 -0
  207. {clientele-2.0.0 → clientele-2.2.0}/tests/cache/test_integration.py +0 -0
  208. {clientele-2.0.0 → clientele-2.2.0}/tests/cache/test_key_generator.py +0 -0
  209. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/callback_example.yaml +0 -0
  210. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/complex_api.yaml +0 -0
  211. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/README.md +0 -0
  212. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/api-with-examples.json +0 -0
  213. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/callback-example.json +0 -0
  214. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/link-example.json +0 -0
  215. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/non-oauth-scopes.json +0 -0
  216. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/petstore-expanded.json +0 -0
  217. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/petstore.json +0 -0
  218. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/tictactoe.json +0 -0
  219. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/uspto.json +0 -0
  220. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/openapi_examples/webhook-example.json +0 -0
  221. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/petstore_openapi3.yaml +0 -0
  222. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/1password.yaml +0 -0
  223. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/ably.yaml +0 -0
  224. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/google.yaml +0 -0
  225. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/medium.yaml +0 -0
  226. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/spacetraders.yaml +0 -0
  227. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/realworld/twilio.yaml +0 -0
  228. {clientele-2.0.0 → clientele-2.2.0}/tests/fixtures/regression/dep_query_alias.json +0 -0
  229. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/framework/test_framework_generator_coverage.py +0 -0
  230. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/framework/test_framework_generator_integration.py +0 -0
  231. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/integration_utils.py +0 -0
  232. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/shared/__init__.py +0 -0
  233. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/shared/test_utils.py +0 -0
  234. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/test_base.py +0 -0
  235. {clientele-2.0.0 → clientele-2.2.0}/tests/generators/test_cicerone_compat.py +0 -0
  236. {clientele-2.0.0 → clientele-2.2.0}/tests/graphql/test_graphql_client.py +0 -0
  237. {clientele-2.0.0 → clientele-2.2.0}/tests/http/backends/test_backend_config.py +0 -0
  238. {clientele-2.0.0 → clientele-2.2.0}/tests/http/backends/test_fake_backend.py +0 -0
  239. {clientele-2.0.0 → clientele-2.2.0}/tests/mypy/mypy_plugin_test_config.ini +0 -0
  240. {clientele-2.0.0 → clientele-2.2.0}/tests/mypy/test_mypy_plugin.py +0 -0
  241. {clientele-2.0.0 → clientele-2.2.0}/tests/test_additional_coverage.py +0 -0
  242. {clientele-2.0.0 → clientele-2.2.0}/tests/test_cli.py +0 -0
  243. {clientele-2.0.0 → clientele-2.2.0}/tests/test_cli_coverage.py +0 -0
  244. {clientele-2.0.0 → clientele-2.2.0}/tests/test_cli_full_coverage.py +0 -0
  245. {clientele-2.0.0 → clientele-2.2.0}/tests/test_edge_case_coverage.py +0 -0
  246. {clientele-2.0.0 → clientele-2.2.0}/tests/test_fixture_schemas.py +0 -0
  247. {clientele-2.0.0 → clientele-2.2.0}/tests/test_generator_coverage.py +0 -0
  248. {clientele-2.0.0 → clientele-2.2.0}/tests/test_nullable_fields.py +0 -0
  249. {clientele-2.0.0 → clientele-2.2.0}/tests/test_openapi_31_null_type.py +0 -0
  250. {clientele-2.0.0 → clientele-2.2.0}/tests/test_ref_handling.py +0 -0
  251. {clientele-2.0.0 → clientele-2.2.0}/tests/test_settings.py +0 -0
  252. {clientele-2.0.0 → clientele-2.2.0}/tests/test_testing_utilities.py +0 -0
  253. {clientele-2.0.0 → clientele-2.2.0}/tests/test_utils.py +0 -0
@@ -20,7 +20,12 @@
20
20
  "Bash(gh api *)",
21
21
  "Bash(python3 *)",
22
22
  "Bash(gh issue *)",
23
- "WebSearch"
23
+ "WebSearch",
24
+ "Bash(uv pip *)",
25
+ "Bash(.venv/bin/pytest *)",
26
+ "Bash(.venv/bin/python *)",
27
+ "Bash(pip list *)",
28
+ "Bash(git *)"
24
29
  ]
25
30
  }
26
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,5 +1,25 @@
1
1
  # Change log
2
2
 
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
12
+
13
+ - Introduce `RequestsHTTPBackend` for making HTTP requests using the `requests` library.
14
+ - This backend implements synchronous HTTP requests only.
15
+ - The library `requests` is not installed by default. See the documentation for installation instructions.
16
+ - Introduce `AIOHTTPBackend` for making HTTP requests using the `aiohttp` library.
17
+ - This backend implements asynchronous HTTP requests only.
18
+ - The library `aiohttp` is not installed by default. See the documentation for installation instructions.
19
+ - Introduce `NiquestsHTTPBackend` for making HTTP requests using the `niquests` library.
20
+ - This backend implements both synchronous and asynchronous HTTP requests.
21
+ - The library `niquests` is not installed by default. See the documentation for installation instructions.
22
+
3
23
  ## 2.0.0
4
24
 
5
25
  This release includes many breaking changes, improved type support, and dependency changes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clientele
3
- Version: 2.0.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.0.0 | :white_check_mark: |
8
- | 1.11.1 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,165 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ try:
6
+ import aiohttp as aiohttp_lib
7
+ except ImportError as exc:
8
+ raise ImportError(
9
+ "The AiohttpHTTPBackend requires the 'aiohttp' library. Install it with: pip install aiohttp"
10
+ ) from exc
11
+
12
+ from clientele.api import exceptions as api_exceptions
13
+ from clientele.api.stream import hydrate_content
14
+ from clientele.http import backends, response
15
+
16
+
17
+ class AiohttpHTTPBackend(backends.HTTPBackend):
18
+ """Asynchronous-only HTTP backend using the aiohttp library.
19
+
20
+ Sync methods (build_client, send_sync_request, handle_sync_stream) raise
21
+ NotImplementedError. Use HttpxHTTPBackend for sync support.
22
+
23
+ Args:
24
+ base_url: Base URL prepended to all relative request paths.
25
+ headers: Default headers applied to every request.
26
+ timeout: Request timeout in seconds. None means no timeout.
27
+ follow_redirects: Whether to follow HTTP redirects.
28
+ verify: SSL certificate verification. False to disable; str CA bundle paths
29
+ are not supported (use an ssl.SSLContext via a custom connector instead).
30
+ """
31
+
32
+ _async_client: aiohttp_lib.ClientSession | None = None
33
+
34
+ def __init__(
35
+ self,
36
+ base_url: str = "",
37
+ headers: dict[str, str] | None = None,
38
+ timeout: float | None = 5.0,
39
+ follow_redirects: bool = False,
40
+ verify: bool | str = True,
41
+ ) -> None:
42
+ self.base_url = base_url.rstrip("/")
43
+ self.headers = headers or {}
44
+ self.aiohttp_timeout = (
45
+ aiohttp_lib.ClientTimeout(total=timeout) if timeout is not None else aiohttp_lib.ClientTimeout(total=None)
46
+ )
47
+ self.follow_redirects = follow_redirects
48
+ # aiohttp uses ssl=False to disable; str CA paths are not supported here
49
+ self._ssl: bool | None = None if verify else False
50
+
51
+ @classmethod
52
+ def from_config(cls, config: typing.Any) -> "AiohttpHTTPBackend":
53
+ return cls(
54
+ base_url=config.base_url,
55
+ headers=config.headers,
56
+ timeout=config.timeout,
57
+ follow_redirects=config.follow_redirects,
58
+ verify=config.verify,
59
+ )
60
+
61
+ def _full_url(self, url: str) -> str:
62
+ if url.startswith("http://") or url.startswith("https://"):
63
+ return url
64
+ return self.base_url + url
65
+
66
+ def build_client(self) -> typing.Any:
67
+ raise NotImplementedError("AiohttpHTTPBackend does not support sync requests. Use HttpxHTTPBackend instead.")
68
+
69
+ def build_async_client(self) -> aiohttp_lib.ClientSession:
70
+ if not self._async_client or self._async_client.closed:
71
+ self._async_client = aiohttp_lib.ClientSession(headers=self.headers)
72
+ return self._async_client
73
+
74
+ @staticmethod
75
+ def convert_to_response(native_response: typing.Any) -> response.Response:
76
+ # Body must be pre-read via await resp.read() before calling this
77
+ content: bytes = native_response._body or b""
78
+ return response.Response(
79
+ status_code=native_response.status,
80
+ headers=dict(native_response.headers),
81
+ content=content,
82
+ request_method=native_response.method.upper(),
83
+ request_url=str(native_response.url),
84
+ )
85
+
86
+ def send_sync_request(
87
+ self,
88
+ method: str,
89
+ url: str,
90
+ **kwargs: typing.Any,
91
+ ) -> response.Response:
92
+ raise NotImplementedError("AiohttpHTTPBackend does not support sync requests. Use HttpxHTTPBackend instead.")
93
+
94
+ async def send_async_request(
95
+ self,
96
+ method: str,
97
+ url: str,
98
+ **kwargs: typing.Any,
99
+ ) -> response.Response:
100
+ session = self.build_async_client()
101
+ async with session.request(
102
+ method,
103
+ self._full_url(url),
104
+ timeout=self.aiohttp_timeout,
105
+ allow_redirects=self.follow_redirects,
106
+ ssl=self._ssl,
107
+ **kwargs,
108
+ ) as resp:
109
+ await resp.read()
110
+ return self.convert_to_response(resp)
111
+
112
+ def handle_sync_stream(
113
+ self,
114
+ method: str,
115
+ url: str,
116
+ inner_type: typing.Any,
117
+ response_parser: typing.Callable[[str], typing.Any] | None = None,
118
+ **kwargs: typing.Any,
119
+ ) -> typing.Generator[typing.Any, None, None]:
120
+ raise NotImplementedError("AiohttpHTTPBackend does not support sync requests. Use HttpxHTTPBackend instead.")
121
+ yield # type: ignore[misc]
122
+
123
+ async def handle_async_stream(
124
+ self,
125
+ method: str,
126
+ url: str,
127
+ inner_type: typing.Any,
128
+ response_parser: typing.Callable[[str], typing.Any] | None = None,
129
+ **kwargs: typing.Any,
130
+ ) -> typing.AsyncGenerator[typing.Any, None]:
131
+ session = self.build_async_client()
132
+ async with session.request(
133
+ method,
134
+ self._full_url(url),
135
+ timeout=self.aiohttp_timeout,
136
+ allow_redirects=self.follow_redirects,
137
+ ssl=self._ssl,
138
+ **kwargs,
139
+ ) as resp:
140
+ if 400 <= resp.status < 600:
141
+ await resp.read()
142
+ generic_response = self.convert_to_response(resp)
143
+ raise api_exceptions.HTTPStatusError(
144
+ response=generic_response,
145
+ reason=f"HTTP {resp.status}",
146
+ )
147
+ while True:
148
+ line = await resp.content.readline()
149
+ if not line:
150
+ break
151
+ decoded = line.decode("utf-8").rstrip("\r\n")
152
+ if not decoded:
153
+ continue
154
+ if response_parser is not None:
155
+ yield response_parser(decoded)
156
+ else:
157
+ yield hydrate_content(decoded, inner_type)
158
+
159
+ def close(self) -> None:
160
+ pass
161
+
162
+ async def aclose(self) -> None:
163
+ if self._async_client and not self._async_client.closed:
164
+ await self._async_client.close()
165
+ self._async_client = None
@@ -0,0 +1,216 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ try:
6
+ import niquests as niquests_lib
7
+ except ImportError as exc:
8
+ raise ImportError(
9
+ "The NiquestsHTTPBackend requires the 'niquests' library. Install it with: pip install niquests"
10
+ ) from exc
11
+
12
+ from clientele.api import exceptions as api_exceptions
13
+ from clientele.api.stream import hydrate_content
14
+ from clientele.http import backends, response
15
+
16
+
17
+ class NiquestsHTTPBackend(backends.HTTPBackend):
18
+ """HTTP backend using the niquests library, supporting both sync and async.
19
+
20
+ This backend supports both synchronous and asynchronous requests in a single instance.
21
+
22
+ Args:
23
+ base_url: Base URL prepended to all relative request paths.
24
+ headers: Default headers applied to every request.
25
+ timeout: Request timeout in seconds. None means no timeout.
26
+ follow_redirects: Whether to follow HTTP redirects.
27
+ verify: SSL certificate verification. True to enable, False to disable.
28
+ """
29
+
30
+ _sync_client: niquests_lib.Session | None = None
31
+ _async_client: niquests_lib.AsyncSession | None = None
32
+
33
+ def __init__(
34
+ self,
35
+ base_url: str = "",
36
+ headers: dict[str, str] | None = None,
37
+ timeout: float | None = 5.0,
38
+ follow_redirects: bool = False,
39
+ verify: bool = True,
40
+ ) -> None:
41
+ self.base_url = base_url.rstrip("/")
42
+ self.headers = headers or {}
43
+ self.timeout = timeout
44
+ self.follow_redirects = follow_redirects
45
+ self.verify = verify
46
+
47
+ @classmethod
48
+ def from_config(cls, config: typing.Any) -> NiquestsHTTPBackend:
49
+ return cls(
50
+ base_url=config.base_url,
51
+ headers=config.headers,
52
+ timeout=config.timeout,
53
+ follow_redirects=config.follow_redirects,
54
+ verify=bool(config.verify),
55
+ )
56
+
57
+ def _full_url(self, url: str) -> str:
58
+ if url.startswith("http://") or url.startswith("https://"):
59
+ return url
60
+ return self.base_url + url
61
+
62
+ def build_client(self) -> niquests_lib.Session:
63
+ if not self._sync_client:
64
+ session = niquests_lib.Session(headers=self.headers, timeout=self.timeout)
65
+ session.verify = self.verify
66
+ self._sync_client = session
67
+ return self._sync_client
68
+
69
+ def build_async_client(self) -> niquests_lib.AsyncSession:
70
+ if not self._async_client:
71
+ session = niquests_lib.AsyncSession(headers=self.headers, timeout=self.timeout)
72
+ session.verify = self.verify
73
+ self._async_client = session
74
+ return self._async_client
75
+
76
+ @staticmethod
77
+ def convert_to_response(native_response: typing.Any) -> response.Response:
78
+ # Accepts both sync Response and non-streaming async Response (same type)
79
+ return response.Response(
80
+ status_code=native_response.status_code,
81
+ headers=dict(native_response.headers),
82
+ content=native_response.content or b"",
83
+ text=native_response.text or "",
84
+ request_method=native_response.request.method,
85
+ request_url=native_response.request.url or "",
86
+ )
87
+
88
+ def send_sync_request(
89
+ self,
90
+ method: str,
91
+ url: str,
92
+ **kwargs: typing.Any,
93
+ ) -> response.Response:
94
+ session = self.build_client()
95
+ resp = session.request(
96
+ method,
97
+ self._full_url(url),
98
+ timeout=self.timeout,
99
+ allow_redirects=self.follow_redirects,
100
+ **kwargs,
101
+ )
102
+ return self.convert_to_response(resp)
103
+
104
+ async def send_async_request(
105
+ self,
106
+ method: str,
107
+ url: str,
108
+ **kwargs: typing.Any,
109
+ ) -> response.Response:
110
+ session = self.build_async_client()
111
+ # Non-streaming async returns a regular Response (sync content/text)
112
+ resp = await session.request(
113
+ method,
114
+ self._full_url(url),
115
+ timeout=self.timeout,
116
+ allow_redirects=self.follow_redirects,
117
+ **kwargs,
118
+ )
119
+ return self.convert_to_response(resp)
120
+
121
+ def handle_sync_stream(
122
+ self,
123
+ method: str,
124
+ url: str,
125
+ inner_type: typing.Any,
126
+ response_parser: typing.Callable[[str], typing.Any] | None = None,
127
+ **kwargs: typing.Any,
128
+ ) -> typing.Generator[typing.Any, None, None]:
129
+ session = self.build_client()
130
+ resp = session.request(
131
+ method,
132
+ self._full_url(url),
133
+ stream=True,
134
+ timeout=self.timeout,
135
+ allow_redirects=self.follow_redirects,
136
+ **kwargs,
137
+ )
138
+ try:
139
+ if resp.status_code is not None and 400 <= resp.status_code < 600:
140
+ generic_response = self.convert_to_response(resp)
141
+ raise api_exceptions.HTTPStatusError(
142
+ response=generic_response,
143
+ reason=f"HTTP {resp.status_code}",
144
+ )
145
+ for line in resp.iter_lines(decode_unicode=True):
146
+ if not line:
147
+ continue
148
+ if response_parser is not None:
149
+ yield response_parser(line)
150
+ else:
151
+ yield hydrate_content(line, inner_type)
152
+ finally:
153
+ resp.close()
154
+
155
+ async def handle_async_stream(
156
+ self,
157
+ method: str,
158
+ url: str,
159
+ inner_type: typing.Any,
160
+ response_parser: typing.Callable[[str], typing.Any] | None = None,
161
+ **kwargs: typing.Any,
162
+ ) -> typing.AsyncGenerator[typing.Any, None]:
163
+ session = self.build_async_client()
164
+ # stream=True returns AsyncResponse with async content/text/close
165
+ resp = await session.request(
166
+ method,
167
+ self._full_url(url),
168
+ stream=True,
169
+ timeout=self.timeout,
170
+ allow_redirects=self.follow_redirects,
171
+ **kwargs,
172
+ )
173
+ try:
174
+ if resp.status_code is not None and 400 <= resp.status_code < 600:
175
+ content = await resp.content or b""
176
+ text = await resp.text or ""
177
+ headers: dict[str, str] = {
178
+ k: v if isinstance(v, str) else v.decode("latin-1") for k, v in resp.headers.items()
179
+ }
180
+ request_method = (resp.request.method or "GET") if resp.request is not None else "GET"
181
+ request_url = (resp.request.url or "") if resp.request is not None else ""
182
+ generic_response = response.Response(
183
+ status_code=resp.status_code,
184
+ headers=headers,
185
+ content=content,
186
+ text=text,
187
+ request_method=request_method,
188
+ request_url=request_url,
189
+ )
190
+ raise api_exceptions.HTTPStatusError(
191
+ response=generic_response,
192
+ reason=f"HTTP {resp.status_code}",
193
+ )
194
+ # AsyncResponse.iter_lines is annotated as a coroutine returning AsyncGenerator,
195
+ # but niquests implements it as an async generator function (runtime discrepancy).
196
+ # Assigning to Any lets us iterate correctly at runtime without misrepresenting types.
197
+ resp_any: typing.Any = resp
198
+ async for line in resp_any.iter_lines(decode_unicode=True):
199
+ if not line:
200
+ continue
201
+ if response_parser is not None:
202
+ yield response_parser(line)
203
+ else:
204
+ yield hydrate_content(line, inner_type)
205
+ finally:
206
+ await resp.close()
207
+
208
+ def close(self) -> None:
209
+ if self._sync_client is not None:
210
+ self._sync_client.close()
211
+ self._sync_client = None
212
+
213
+ async def aclose(self) -> None:
214
+ if self._async_client is not None:
215
+ await self._async_client.close()
216
+ self._async_client = None