hubmap-search-sdk 1.0.0a12__tar.gz → 1.0.0a14__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 (97) hide show
  1. hubmap_search_sdk-1.0.0a14/.release-please-manifest.json +3 -0
  2. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/CHANGELOG.md +44 -0
  3. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/CONTRIBUTING.md +1 -2
  4. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/PKG-INFO +37 -3
  5. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/README.md +33 -2
  6. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/bin/check-release-environment +1 -1
  7. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/pyproject.toml +5 -2
  8. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/requirements-dev.lock +31 -0
  9. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/requirements.lock +27 -0
  10. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/__init__.py +2 -1
  11. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_base_client.py +44 -2
  12. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_models.py +2 -0
  13. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_types.py +2 -0
  14. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_version.py +1 -1
  15. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_add.py +3 -1
  16. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_clear_docs.py +3 -1
  17. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_indices.py +3 -1
  18. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_mapping.py +3 -1
  19. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_mget.py +3 -1
  20. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_param_search.py +3 -1
  21. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_reindex.py +3 -1
  22. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_scroll_search.py +3 -1
  23. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_search.py +3 -1
  24. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/test_update.py +3 -1
  25. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/conftest.py +36 -5
  26. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_client.py +114 -15
  27. hubmap_search_sdk-1.0.0a12/.release-please-manifest.json +0 -3
  28. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/.gitignore +0 -0
  29. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/LICENSE +0 -0
  30. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/SECURITY.md +0 -0
  31. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/api.md +0 -0
  32. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/bin/publish-pypi +0 -0
  33. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/examples/.keep +0 -0
  34. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/mypy.ini +0 -0
  35. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/noxfile.py +0 -0
  36. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/release-please-config.json +0 -0
  37. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_client.py +0 -0
  38. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_compat.py +0 -0
  39. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_constants.py +0 -0
  40. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_exceptions.py +0 -0
  41. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_files.py +0 -0
  42. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_qs.py +0 -0
  43. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_resource.py +0 -0
  44. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_response.py +0 -0
  45. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_streaming.py +0 -0
  46. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/__init__.py +0 -0
  47. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_logs.py +0 -0
  48. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_proxy.py +0 -0
  49. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_reflection.py +0 -0
  50. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_resources_proxy.py +0 -0
  51. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_streams.py +0 -0
  52. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_sync.py +0 -0
  53. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_transform.py +0 -0
  54. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_typing.py +0 -0
  55. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/_utils/_utils.py +0 -0
  56. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/lib/.keep +0 -0
  57. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/py.typed +0 -0
  58. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/__init__.py +0 -0
  59. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/add.py +0 -0
  60. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/clear_docs.py +0 -0
  61. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/indices.py +0 -0
  62. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/mapping.py +0 -0
  63. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/mget.py +0 -0
  64. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/param_search.py +0 -0
  65. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/reindex.py +0 -0
  66. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/scroll_search.py +0 -0
  67. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/search.py +0 -0
  68. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/resources/update.py +0 -0
  69. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/__init__.py +0 -0
  70. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/add_create_document_params.py +0 -0
  71. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/add_create_document_with_index_params.py +0 -0
  72. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/add_update_document_with_scope_params.py +0 -0
  73. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/index_list_response.py +0 -0
  74. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/mget_retrieve_multiple_by_index_params.py +0 -0
  75. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/mget_retrieve_multiple_params.py +0 -0
  76. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/param_search_execute_params.py +0 -0
  77. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/scroll_search_create_params.py +0 -0
  78. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/search_execute_index_query_params.py +0 -0
  79. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/search_execute_query_params.py +0 -0
  80. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/update_update_document_at_index_params.py +0 -0
  81. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/update_update_document_params.py +0 -0
  82. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/src/hubmap_search_sdk/types/update_update_document_with_scope_params.py +0 -0
  83. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/__init__.py +0 -0
  84. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/api_resources/__init__.py +0 -0
  85. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/sample_file.txt +0 -0
  86. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_deepcopy.py +0 -0
  87. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_extract_files.py +0 -0
  88. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_files.py +0 -0
  89. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_models.py +0 -0
  90. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_qs.py +0 -0
  91. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_required_args.py +0 -0
  92. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_response.py +0 -0
  93. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_streaming.py +0 -0
  94. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_transform.py +0 -0
  95. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_utils/test_proxy.py +0 -0
  96. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/test_utils/test_typing.py +0 -0
  97. {hubmap_search_sdk-1.0.0a12 → hubmap_search_sdk-1.0.0a14}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.0-alpha.14"
3
+ }
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-alpha.14 (2025-06-28)
4
+
5
+ Full Changelog: [v1.0.0-alpha.13...v1.0.0-alpha.14](https://github.com/hubmapconsortium/search-python-sdk/compare/v1.0.0-alpha.13...v1.0.0-alpha.14)
6
+
7
+ ### Features
8
+
9
+ * **client:** add support for aiohttp ([2e47165](https://github.com/hubmapconsortium/search-python-sdk/commit/2e47165ede72e1dbd891097afa5fe5cc23790b79))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **ci:** release-doctor — report correct token name ([33bf6f7](https://github.com/hubmapconsortium/search-python-sdk/commit/33bf6f7dbf29f9b4d2747e09bf9ef848b0411fea))
15
+ * **client:** correctly parse binary response | stream ([e9dc865](https://github.com/hubmapconsortium/search-python-sdk/commit/e9dc8654daf52a63214e8575307e25b9c748506e))
16
+ * **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([854e415](https://github.com/hubmapconsortium/search-python-sdk/commit/854e41578698efee91b595e702721c1570ecebd5))
17
+
18
+
19
+ ### Chores
20
+
21
+ * **ci:** enable for pull requests ([e7be8b2](https://github.com/hubmapconsortium/search-python-sdk/commit/e7be8b27cda54e12afc5e2936e1238c8dd3f5657))
22
+ * **ci:** only run for pushes and fork pull requests ([1d726a4](https://github.com/hubmapconsortium/search-python-sdk/commit/1d726a486601f809f7ea796b3e26b288f04422b6))
23
+ * **internal:** update conftest.py ([4ea44f2](https://github.com/hubmapconsortium/search-python-sdk/commit/4ea44f2d19f71d20b997cf49fc659819a80f7786))
24
+ * **readme:** update badges ([82fcb23](https://github.com/hubmapconsortium/search-python-sdk/commit/82fcb23a979d8dd8620ea9a6733f9aa661b6fae5))
25
+ * **tests:** add tests for httpx client instantiation & proxies ([2e2d7ac](https://github.com/hubmapconsortium/search-python-sdk/commit/2e2d7ace3a416e7f6805d75c0ca8878508be9434))
26
+ * **tests:** run tests in parallel ([ada43cc](https://github.com/hubmapconsortium/search-python-sdk/commit/ada43cc98977f550f9c8a8f2a27bd53dcb0a0697))
27
+ * **tests:** skip some failing tests on the latest python versions ([e8a90b8](https://github.com/hubmapconsortium/search-python-sdk/commit/e8a90b8b85442856af633c0c6b663d000c7fc8f8))
28
+
29
+
30
+ ### Documentation
31
+
32
+ * **client:** fix httpx.Timeout documentation reference ([5e38f78](https://github.com/hubmapconsortium/search-python-sdk/commit/5e38f78a35147577cbc911fc19804fe430d2cd6f))
33
+
34
+ ## 1.0.0-alpha.13 (2025-06-03)
35
+
36
+ Full Changelog: [v1.0.0-alpha.12...v1.0.0-alpha.13](https://github.com/hubmapconsortium/search-python-sdk/compare/v1.0.0-alpha.12...v1.0.0-alpha.13)
37
+
38
+ ### Features
39
+
40
+ * **client:** add follow_redirects request option ([55b10c1](https://github.com/hubmapconsortium/search-python-sdk/commit/55b10c18cfea334e5ea9558f70ded70c2b21e34c))
41
+
42
+
43
+ ### Chores
44
+
45
+ * **docs:** remove reference to rye shell ([ef26230](https://github.com/hubmapconsortium/search-python-sdk/commit/ef262304d809361ee6d56932bdb9328157e8aef6))
46
+
3
47
  ## 1.0.0-alpha.12 (2025-05-28)
4
48
 
5
49
  Full Changelog: [v1.0.0-alpha.11...v1.0.0-alpha.12](https://github.com/hubmapconsortium/search-python-sdk/compare/v1.0.0-alpha.11...v1.0.0-alpha.12)
@@ -17,8 +17,7 @@ $ rye sync --all-features
17
17
  You can then run scripts using `rye run python script.py` or by activating the virtual environment:
18
18
 
19
19
  ```sh
20
- $ rye shell
21
- # or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
20
+ # Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
22
21
  $ source .venv/bin/activate
23
22
 
24
23
  # now you can omit the `rye run` prefix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hubmap_search_sdk
3
- Version: 1.0.0a12
3
+ Version: 1.0.0a14
4
4
  Summary: The official Python library for the hubmap-search-sdk API
5
5
  Project-URL: Homepage, https://github.com/hubmapconsortium/search-python-sdk
6
6
  Project-URL: Repository, https://github.com/hubmapconsortium/search-python-sdk
@@ -27,11 +27,14 @@ Requires-Dist: httpx<1,>=0.23.0
27
27
  Requires-Dist: pydantic<3,>=1.9.0
28
28
  Requires-Dist: sniffio
29
29
  Requires-Dist: typing-extensions<5,>=4.10
30
+ Provides-Extra: aiohttp
31
+ Requires-Dist: aiohttp; extra == 'aiohttp'
32
+ Requires-Dist: httpx-aiohttp>=0.1.6; extra == 'aiohttp'
30
33
  Description-Content-Type: text/markdown
31
34
 
32
35
  # HuBMAP Search SDK Python API Library
33
36
 
34
- [![PyPI version](https://img.shields.io/pypi/v/hubmap_search_sdk.svg)](https://pypi.org/project/hubmap_search_sdk/)
37
+ [![PyPI version](https://github.com/hubmapconsortium/search-python-sdk/tree/main/<https://img.shields.io/pypi/v/hubmap_search_sdk.svg?label=pypi%20(stable)>)](https://pypi.org/project/hubmap_search_sdk/)
35
38
 
36
39
  The HuBMAP Search SDK Python library provides convenient access to the HuBMAP Search REST API from any Python 3.8+
37
40
  application. The library includes type definitions for all request params and response fields,
@@ -88,6 +91,37 @@ asyncio.run(main())
88
91
 
89
92
  Functionality between the synchronous and asynchronous clients is otherwise identical.
90
93
 
94
+ ### With aiohttp
95
+
96
+ By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
97
+
98
+ You can enable this by installing `aiohttp`:
99
+
100
+ ```sh
101
+ # install from PyPI
102
+ pip install --pre hubmap_search_sdk[aiohttp]
103
+ ```
104
+
105
+ Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
106
+
107
+ ```python
108
+ import asyncio
109
+ from hubmap_search_sdk import DefaultAioHttpClient
110
+ from hubmap_search_sdk import AsyncHubmapSearchSDK
111
+
112
+
113
+ async def main() -> None:
114
+ async with AsyncHubmapSearchSDK(
115
+ bearer_token="My Bearer Token",
116
+ http_client=DefaultAioHttpClient(),
117
+ ) as client:
118
+ indices = await client.indices.list()
119
+ print(indices.indices)
120
+
121
+
122
+ asyncio.run(main())
123
+ ```
124
+
91
125
  ## Using types
92
126
 
93
127
  Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
@@ -162,7 +196,7 @@ client.with_options(max_retries=5).indices.list()
162
196
  ### Timeouts
163
197
 
164
198
  By default requests time out after 1 minute. You can configure this with a `timeout` option,
165
- which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
199
+ which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
166
200
 
167
201
  ```python
168
202
  from hubmap_search_sdk import HubmapSearchSDK
@@ -1,6 +1,6 @@
1
1
  # HuBMAP Search SDK Python API Library
2
2
 
3
- [![PyPI version](https://img.shields.io/pypi/v/hubmap_search_sdk.svg)](https://pypi.org/project/hubmap_search_sdk/)
3
+ [![PyPI version](<https://img.shields.io/pypi/v/hubmap_search_sdk.svg?label=pypi%20(stable)>)](https://pypi.org/project/hubmap_search_sdk/)
4
4
 
5
5
  The HuBMAP Search SDK Python library provides convenient access to the HuBMAP Search REST API from any Python 3.8+
6
6
  application. The library includes type definitions for all request params and response fields,
@@ -57,6 +57,37 @@ asyncio.run(main())
57
57
 
58
58
  Functionality between the synchronous and asynchronous clients is otherwise identical.
59
59
 
60
+ ### With aiohttp
61
+
62
+ By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
63
+
64
+ You can enable this by installing `aiohttp`:
65
+
66
+ ```sh
67
+ # install from PyPI
68
+ pip install --pre hubmap_search_sdk[aiohttp]
69
+ ```
70
+
71
+ Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
72
+
73
+ ```python
74
+ import asyncio
75
+ from hubmap_search_sdk import DefaultAioHttpClient
76
+ from hubmap_search_sdk import AsyncHubmapSearchSDK
77
+
78
+
79
+ async def main() -> None:
80
+ async with AsyncHubmapSearchSDK(
81
+ bearer_token="My Bearer Token",
82
+ http_client=DefaultAioHttpClient(),
83
+ ) as client:
84
+ indices = await client.indices.list()
85
+ print(indices.indices)
86
+
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
60
91
  ## Using types
61
92
 
62
93
  Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
@@ -131,7 +162,7 @@ client.with_options(max_retries=5).indices.list()
131
162
  ### Timeouts
132
163
 
133
164
  By default requests time out after 1 minute. You can configure this with a `timeout` option,
134
- which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
165
+ which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
135
166
 
136
167
  ```python
137
168
  from hubmap_search_sdk import HubmapSearchSDK
@@ -3,7 +3,7 @@
3
3
  errors=()
4
4
 
5
5
  if [ -z "${PYPI_TOKEN}" ]; then
6
- errors+=("The HUBMAP_SEARCH_SDK_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
6
+ errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
7
7
  fi
8
8
 
9
9
  lenErrors=${#errors[@]}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hubmap_search_sdk"
3
- version = "1.0.0-alpha.12"
3
+ version = "1.0.0-alpha.14"
4
4
  description = "The official Python library for the hubmap-search-sdk API"
5
5
  dynamic = ["readme"]
6
6
  license = "MIT"
@@ -37,6 +37,8 @@ classifiers = [
37
37
  Homepage = "https://github.com/hubmapconsortium/search-python-sdk"
38
38
  Repository = "https://github.com/hubmapconsortium/search-python-sdk"
39
39
 
40
+ [project.optional-dependencies]
41
+ aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
40
42
 
41
43
  [tool.rye]
42
44
  managed = true
@@ -54,6 +56,7 @@ dev-dependencies = [
54
56
  "importlib-metadata>=6.7.0",
55
57
  "rich>=13.7.1",
56
58
  "nest_asyncio==1.6.0",
59
+ "pytest-xdist>=3.6.1",
57
60
  ]
58
61
 
59
62
  [tool.rye.scripts]
@@ -125,7 +128,7 @@ replacement = '[\1](https://github.com/hubmapconsortium/search-python-sdk/tree/m
125
128
 
126
129
  [tool.pytest.ini_options]
127
130
  testpaths = ["tests"]
128
- addopts = "--tb=short"
131
+ addopts = "--tb=short -n auto"
129
132
  xfail_strict = true
130
133
  asyncio_mode = "auto"
131
134
  asyncio_default_fixture_loop_scope = "session"
@@ -10,6 +10,13 @@
10
10
  # universal: false
11
11
 
12
12
  -e file:.
13
+ aiohappyeyeballs==2.6.1
14
+ # via aiohttp
15
+ aiohttp==3.12.8
16
+ # via httpx-aiohttp
17
+ # via hubmap-search-sdk
18
+ aiosignal==1.3.2
19
+ # via aiohttp
13
20
  annotated-types==0.6.0
14
21
  # via pydantic
15
22
  anyio==4.4.0
@@ -17,6 +24,10 @@ anyio==4.4.0
17
24
  # via hubmap-search-sdk
18
25
  argcomplete==3.1.2
19
26
  # via nox
27
+ async-timeout==5.0.1
28
+ # via aiohttp
29
+ attrs==25.3.0
30
+ # via aiohttp
20
31
  certifi==2023.7.22
21
32
  # via httpcore
22
33
  # via httpx
@@ -30,18 +41,27 @@ distro==1.8.0
30
41
  exceptiongroup==1.2.2
31
42
  # via anyio
32
43
  # via pytest
44
+ execnet==2.1.1
45
+ # via pytest-xdist
33
46
  filelock==3.12.4
34
47
  # via virtualenv
48
+ frozenlist==1.6.2
49
+ # via aiohttp
50
+ # via aiosignal
35
51
  h11==0.14.0
36
52
  # via httpcore
37
53
  httpcore==1.0.2
38
54
  # via httpx
39
55
  httpx==0.28.1
56
+ # via httpx-aiohttp
40
57
  # via hubmap-search-sdk
41
58
  # via respx
59
+ httpx-aiohttp==0.1.6
60
+ # via hubmap-search-sdk
42
61
  idna==3.4
43
62
  # via anyio
44
63
  # via httpx
64
+ # via yarl
45
65
  importlib-metadata==7.0.0
46
66
  iniconfig==2.0.0
47
67
  # via pytest
@@ -49,6 +69,9 @@ markdown-it-py==3.0.0
49
69
  # via rich
50
70
  mdurl==0.1.2
51
71
  # via markdown-it-py
72
+ multidict==6.4.4
73
+ # via aiohttp
74
+ # via yarl
52
75
  mypy==1.14.1
53
76
  mypy-extensions==1.0.0
54
77
  # via mypy
@@ -63,6 +86,9 @@ platformdirs==3.11.0
63
86
  # via virtualenv
64
87
  pluggy==1.5.0
65
88
  # via pytest
89
+ propcache==0.3.1
90
+ # via aiohttp
91
+ # via yarl
66
92
  pydantic==2.10.3
67
93
  # via hubmap-search-sdk
68
94
  pydantic-core==2.27.1
@@ -72,7 +98,9 @@ pygments==2.18.0
72
98
  pyright==1.1.399
73
99
  pytest==8.3.3
74
100
  # via pytest-asyncio
101
+ # via pytest-xdist
75
102
  pytest-asyncio==0.24.0
103
+ pytest-xdist==3.7.0
76
104
  python-dateutil==2.8.2
77
105
  # via time-machine
78
106
  pytz==2023.3.post1
@@ -94,11 +122,14 @@ tomli==2.0.2
94
122
  typing-extensions==4.12.2
95
123
  # via anyio
96
124
  # via hubmap-search-sdk
125
+ # via multidict
97
126
  # via mypy
98
127
  # via pydantic
99
128
  # via pydantic-core
100
129
  # via pyright
101
130
  virtualenv==20.24.5
102
131
  # via nox
132
+ yarl==1.20.0
133
+ # via aiohttp
103
134
  zipp==3.17.0
104
135
  # via importlib-metadata
@@ -10,11 +10,22 @@
10
10
  # universal: false
11
11
 
12
12
  -e file:.
13
+ aiohappyeyeballs==2.6.1
14
+ # via aiohttp
15
+ aiohttp==3.12.8
16
+ # via httpx-aiohttp
17
+ # via hubmap-search-sdk
18
+ aiosignal==1.3.2
19
+ # via aiohttp
13
20
  annotated-types==0.6.0
14
21
  # via pydantic
15
22
  anyio==4.4.0
16
23
  # via httpx
17
24
  # via hubmap-search-sdk
25
+ async-timeout==5.0.1
26
+ # via aiohttp
27
+ attrs==25.3.0
28
+ # via aiohttp
18
29
  certifi==2023.7.22
19
30
  # via httpcore
20
31
  # via httpx
@@ -22,15 +33,28 @@ distro==1.8.0
22
33
  # via hubmap-search-sdk
23
34
  exceptiongroup==1.2.2
24
35
  # via anyio
36
+ frozenlist==1.6.2
37
+ # via aiohttp
38
+ # via aiosignal
25
39
  h11==0.14.0
26
40
  # via httpcore
27
41
  httpcore==1.0.2
28
42
  # via httpx
29
43
  httpx==0.28.1
44
+ # via httpx-aiohttp
45
+ # via hubmap-search-sdk
46
+ httpx-aiohttp==0.1.6
30
47
  # via hubmap-search-sdk
31
48
  idna==3.4
32
49
  # via anyio
33
50
  # via httpx
51
+ # via yarl
52
+ multidict==6.4.4
53
+ # via aiohttp
54
+ # via yarl
55
+ propcache==0.3.1
56
+ # via aiohttp
57
+ # via yarl
34
58
  pydantic==2.10.3
35
59
  # via hubmap-search-sdk
36
60
  pydantic-core==2.27.1
@@ -41,5 +65,8 @@ sniffio==1.3.0
41
65
  typing-extensions==4.12.2
42
66
  # via anyio
43
67
  # via hubmap-search-sdk
68
+ # via multidict
44
69
  # via pydantic
45
70
  # via pydantic-core
71
+ yarl==1.20.0
72
+ # via aiohttp
@@ -36,7 +36,7 @@ from ._exceptions import (
36
36
  UnprocessableEntityError,
37
37
  APIResponseValidationError,
38
38
  )
39
- from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
39
+ from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
40
40
  from ._utils._logs import setup_logging as _setup_logging
41
41
 
42
42
  __all__ = [
@@ -78,6 +78,7 @@ __all__ = [
78
78
  "DEFAULT_CONNECTION_LIMITS",
79
79
  "DefaultHttpxClient",
80
80
  "DefaultAsyncHttpxClient",
81
+ "DefaultAioHttpClient",
81
82
  ]
82
83
 
83
84
  if not _t.TYPE_CHECKING:
@@ -963,6 +963,9 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
963
963
  if self.custom_auth is not None:
964
964
  kwargs["auth"] = self.custom_auth
965
965
 
966
+ if options.follow_redirects is not None:
967
+ kwargs["follow_redirects"] = options.follow_redirects
968
+
966
969
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
967
970
 
968
971
  response = None
@@ -1080,7 +1083,14 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1080
1083
  ) -> ResponseT:
1081
1084
  origin = get_origin(cast_to) or cast_to
1082
1085
 
1083
- if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
1086
+ if (
1087
+ inspect.isclass(origin)
1088
+ and issubclass(origin, BaseAPIResponse)
1089
+ # we only want to actually return the custom BaseAPIResponse class if we're
1090
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
1091
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
1092
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
1093
+ ):
1084
1094
  if not issubclass(origin, APIResponse):
1085
1095
  raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
1086
1096
 
@@ -1291,6 +1301,24 @@ class _DefaultAsyncHttpxClient(httpx.AsyncClient):
1291
1301
  super().__init__(**kwargs)
1292
1302
 
1293
1303
 
1304
+ try:
1305
+ import httpx_aiohttp
1306
+ except ImportError:
1307
+
1308
+ class _DefaultAioHttpClient(httpx.AsyncClient):
1309
+ def __init__(self, **_kwargs: Any) -> None:
1310
+ raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
1311
+ else:
1312
+
1313
+ class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
1314
+ def __init__(self, **kwargs: Any) -> None:
1315
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
1316
+ kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
1317
+ kwargs.setdefault("follow_redirects", True)
1318
+
1319
+ super().__init__(**kwargs)
1320
+
1321
+
1294
1322
  if TYPE_CHECKING:
1295
1323
  DefaultAsyncHttpxClient = httpx.AsyncClient
1296
1324
  """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
@@ -1299,8 +1327,12 @@ if TYPE_CHECKING:
1299
1327
  This is useful because overriding the `http_client` with your own instance of
1300
1328
  `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
1301
1329
  """
1330
+
1331
+ DefaultAioHttpClient = httpx.AsyncClient
1332
+ """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
1302
1333
  else:
1303
1334
  DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
1335
+ DefaultAioHttpClient = _DefaultAioHttpClient
1304
1336
 
1305
1337
 
1306
1338
  class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
@@ -1472,6 +1504,9 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1472
1504
  if self.custom_auth is not None:
1473
1505
  kwargs["auth"] = self.custom_auth
1474
1506
 
1507
+ if options.follow_redirects is not None:
1508
+ kwargs["follow_redirects"] = options.follow_redirects
1509
+
1475
1510
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
1476
1511
 
1477
1512
  response = None
@@ -1580,7 +1615,14 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1580
1615
  ) -> ResponseT:
1581
1616
  origin = get_origin(cast_to) or cast_to
1582
1617
 
1583
- if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
1618
+ if (
1619
+ inspect.isclass(origin)
1620
+ and issubclass(origin, BaseAPIResponse)
1621
+ # we only want to actually return the custom BaseAPIResponse class if we're
1622
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
1623
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
1624
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
1625
+ ):
1584
1626
  if not issubclass(origin, AsyncAPIResponse):
1585
1627
  raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
1586
1628
 
@@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
737
737
  idempotency_key: str
738
738
  json_data: Body
739
739
  extra_json: AnyMapping
740
+ follow_redirects: bool
740
741
 
741
742
 
742
743
  @final
@@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel):
750
751
  files: Union[HttpxRequestFiles, None] = None
751
752
  idempotency_key: Union[str, None] = None
752
753
  post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
754
+ follow_redirects: Union[bool, None] = None
753
755
 
754
756
  # It should be noted that we cannot use `json` here as that would override
755
757
  # a BaseModel method in an incompatible fashion.
@@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False):
100
100
  params: Query
101
101
  extra_json: AnyMapping
102
102
  idempotency_key: str
103
+ follow_redirects: bool
103
104
 
104
105
 
105
106
  # Sentinel class used until PEP 0661 is accepted
@@ -215,3 +216,4 @@ class _GenericAlias(Protocol):
215
216
 
216
217
  class HttpxSendArgs(TypedDict, total=False):
217
218
  auth: httpx.Auth
219
+ follow_redirects: bool
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "hubmap_search_sdk"
4
- __version__ = "1.0.0-alpha.12" # x-release-please-version
4
+ __version__ = "1.0.0-alpha.14" # x-release-please-version
@@ -880,7 +880,9 @@ class TestAdd:
880
880
 
881
881
 
882
882
  class TestAsyncAdd:
883
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
883
+ parametrize = pytest.mark.parametrize(
884
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
885
+ )
884
886
 
885
887
  @pytest.mark.skip()
886
888
  @parametrize
@@ -175,7 +175,9 @@ class TestClearDocs:
175
175
 
176
176
 
177
177
  class TestAsyncClearDocs:
178
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
178
+ parametrize = pytest.mark.parametrize(
179
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
180
+ )
179
181
 
180
182
  @pytest.mark.skip()
181
183
  @parametrize
@@ -47,7 +47,9 @@ class TestIndices:
47
47
 
48
48
 
49
49
  class TestAsyncIndices:
50
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
50
+ parametrize = pytest.mark.parametrize(
51
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
52
+ )
51
53
 
52
54
  @pytest.mark.skip()
53
55
  @parametrize
@@ -88,7 +88,9 @@ class TestMapping:
88
88
 
89
89
 
90
90
  class TestAsyncMapping:
91
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
91
+ parametrize = pytest.mark.parametrize(
92
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
93
+ )
92
94
 
93
95
  @pytest.mark.skip()
94
96
  @parametrize
@@ -100,7 +100,9 @@ class TestMget:
100
100
 
101
101
 
102
102
  class TestAsyncMget:
103
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
103
+ parametrize = pytest.mark.parametrize(
104
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
105
+ )
104
106
 
105
107
  @pytest.mark.skip()
106
108
  @parametrize
@@ -69,7 +69,9 @@ class TestParamSearch:
69
69
 
70
70
 
71
71
  class TestAsyncParamSearch:
72
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
72
+ parametrize = pytest.mark.parametrize(
73
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
74
+ )
73
75
 
74
76
  @pytest.mark.skip()
75
77
  @parametrize
@@ -59,7 +59,9 @@ class TestReindex:
59
59
 
60
60
 
61
61
  class TestAsyncReindex:
62
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
62
+ parametrize = pytest.mark.parametrize(
63
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
64
+ )
63
65
 
64
66
  @pytest.mark.skip()
65
67
  @parametrize
@@ -75,7 +75,9 @@ class TestScrollSearch:
75
75
 
76
76
 
77
77
  class TestAsyncScrollSearch:
78
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
78
+ parametrize = pytest.mark.parametrize(
79
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
80
+ )
79
81
 
80
82
  @pytest.mark.skip()
81
83
  @parametrize
@@ -180,7 +180,9 @@ class TestSearch:
180
180
 
181
181
 
182
182
  class TestAsyncSearch:
183
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
183
+ parametrize = pytest.mark.parametrize(
184
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
185
+ )
184
186
 
185
187
  @pytest.mark.skip()
186
188
  @parametrize
@@ -880,7 +880,9 @@ class TestUpdate:
880
880
 
881
881
 
882
882
  class TestAsyncUpdate:
883
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
883
+ parametrize = pytest.mark.parametrize(
884
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
885
+ )
884
886
 
885
887
  @pytest.mark.skip()
886
888
  @parametrize
@@ -1,13 +1,17 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import os
4
6
  import logging
5
7
  from typing import TYPE_CHECKING, Iterator, AsyncIterator
6
8
 
9
+ import httpx
7
10
  import pytest
8
11
  from pytest_asyncio import is_async_test
9
12
 
10
- from hubmap_search_sdk import HubmapSearchSDK, AsyncHubmapSearchSDK
13
+ from hubmap_search_sdk import HubmapSearchSDK, AsyncHubmapSearchSDK, DefaultAioHttpClient
14
+ from hubmap_search_sdk._utils import is_dict
11
15
 
12
16
  if TYPE_CHECKING:
13
17
  from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
@@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
25
29
  for async_test in pytest_asyncio_tests:
26
30
  async_test.add_marker(session_scope_marker, append=False)
27
31
 
32
+ # We skip tests that use both the aiohttp client and respx_mock as respx_mock
33
+ # doesn't support custom transports.
34
+ for item in items:
35
+ if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames:
36
+ continue
37
+
38
+ if not hasattr(item, "callspec"):
39
+ continue
40
+
41
+ async_client_param = item.callspec.params.get("async_client")
42
+ if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp":
43
+ item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock"))
44
+
28
45
 
29
46
  base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
30
47
 
@@ -43,11 +60,25 @@ def client(request: FixtureRequest) -> Iterator[HubmapSearchSDK]:
43
60
 
44
61
  @pytest.fixture(scope="session")
45
62
  async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncHubmapSearchSDK]:
46
- strict = getattr(request, "param", True)
47
- if not isinstance(strict, bool):
48
- raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
63
+ param = getattr(request, "param", True)
64
+
65
+ # defaults
66
+ strict = True
67
+ http_client: None | httpx.AsyncClient = None
68
+
69
+ if isinstance(param, bool):
70
+ strict = param
71
+ elif is_dict(param):
72
+ strict = param.get("strict", True)
73
+ assert isinstance(strict, bool)
74
+
75
+ http_client_type = param.get("http_client", "httpx")
76
+ if http_client_type == "aiohttp":
77
+ http_client = DefaultAioHttpClient()
78
+ else:
79
+ raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
49
80
 
50
81
  async with AsyncHubmapSearchSDK(
51
- base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict
82
+ base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict, http_client=http_client
52
83
  ) as client:
53
84
  yield client
@@ -24,12 +24,13 @@ from pydantic import ValidationError
24
24
  from hubmap_search_sdk import HubmapSearchSDK, AsyncHubmapSearchSDK, APIResponseValidationError
25
25
  from hubmap_search_sdk._types import Omit
26
26
  from hubmap_search_sdk._models import BaseModel, FinalRequestOptions
27
- from hubmap_search_sdk._constants import RAW_RESPONSE_HEADER
28
27
  from hubmap_search_sdk._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
29
28
  from hubmap_search_sdk._base_client import (
30
29
  DEFAULT_TIMEOUT,
31
30
  HTTPX_DEFAULT_TIMEOUT,
32
31
  BaseClient,
32
+ DefaultHttpxClient,
33
+ DefaultAsyncHttpxClient,
33
34
  make_request_options,
34
35
  )
35
36
 
@@ -193,6 +194,7 @@ class TestHubmapSearchSDK:
193
194
  copy_param = copy_signature.parameters.get(name)
194
195
  assert copy_param is not None, f"copy() signature is missing the {name} param"
195
196
 
197
+ @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
196
198
  def test_copy_build_request(self) -> None:
197
199
  options = FinalRequestOptions(method="get", url="/foo")
198
200
 
@@ -738,22 +740,21 @@ class TestHubmapSearchSDK:
738
740
 
739
741
  @mock.patch("hubmap_search_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
740
742
  @pytest.mark.respx(base_url=base_url)
741
- def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
743
+ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: HubmapSearchSDK) -> None:
742
744
  respx_mock.get("/indices").mock(side_effect=httpx.TimeoutException("Test timeout error"))
743
745
 
744
746
  with pytest.raises(APITimeoutError):
745
- self.client.get("/indices", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
747
+ client.indices.with_streaming_response.list().__enter__()
746
748
 
747
749
  assert _get_open_connections(self.client) == 0
748
750
 
749
751
  @mock.patch("hubmap_search_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
750
752
  @pytest.mark.respx(base_url=base_url)
751
- def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
753
+ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: HubmapSearchSDK) -> None:
752
754
  respx_mock.get("/indices").mock(return_value=httpx.Response(500))
753
755
 
754
756
  with pytest.raises(APIStatusError):
755
- self.client.get("/indices", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}})
756
-
757
+ client.indices.with_streaming_response.list().__enter__()
757
758
  assert _get_open_connections(self.client) == 0
758
759
 
759
760
  @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -833,6 +834,55 @@ class TestHubmapSearchSDK:
833
834
 
834
835
  assert response.http_request.headers.get("x-stainless-retry-count") == "42"
835
836
 
837
+ def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
838
+ # Test that the proxy environment variables are set correctly
839
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
840
+
841
+ client = DefaultHttpxClient()
842
+
843
+ mounts = tuple(client._mounts.items())
844
+ assert len(mounts) == 1
845
+ assert mounts[0][0].pattern == "https://"
846
+
847
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
848
+ def test_default_client_creation(self) -> None:
849
+ # Ensure that the client can be initialized without any exceptions
850
+ DefaultHttpxClient(
851
+ verify=True,
852
+ cert=None,
853
+ trust_env=True,
854
+ http1=True,
855
+ http2=False,
856
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
857
+ )
858
+
859
+ @pytest.mark.respx(base_url=base_url)
860
+ def test_follow_redirects(self, respx_mock: MockRouter) -> None:
861
+ # Test that the default follow_redirects=True allows following redirects
862
+ respx_mock.post("/redirect").mock(
863
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
864
+ )
865
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
866
+
867
+ response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
868
+ assert response.status_code == 200
869
+ assert response.json() == {"status": "ok"}
870
+
871
+ @pytest.mark.respx(base_url=base_url)
872
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
873
+ # Test that follow_redirects=False prevents following redirects
874
+ respx_mock.post("/redirect").mock(
875
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
876
+ )
877
+
878
+ with pytest.raises(APIStatusError) as exc_info:
879
+ self.client.post(
880
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
881
+ )
882
+
883
+ assert exc_info.value.response.status_code == 302
884
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
885
+
836
886
 
837
887
  class TestAsyncHubmapSearchSDK:
838
888
  client = AsyncHubmapSearchSDK(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
@@ -972,6 +1022,7 @@ class TestAsyncHubmapSearchSDK:
972
1022
  copy_param = copy_signature.parameters.get(name)
973
1023
  assert copy_param is not None, f"copy() signature is missing the {name} param"
974
1024
 
1025
+ @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
975
1026
  def test_copy_build_request(self) -> None:
976
1027
  options = FinalRequestOptions(method="get", url="/foo")
977
1028
 
@@ -1523,26 +1574,25 @@ class TestAsyncHubmapSearchSDK:
1523
1574
 
1524
1575
  @mock.patch("hubmap_search_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1525
1576
  @pytest.mark.respx(base_url=base_url)
1526
- async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
1577
+ async def test_retrying_timeout_errors_doesnt_leak(
1578
+ self, respx_mock: MockRouter, async_client: AsyncHubmapSearchSDK
1579
+ ) -> None:
1527
1580
  respx_mock.get("/indices").mock(side_effect=httpx.TimeoutException("Test timeout error"))
1528
1581
 
1529
1582
  with pytest.raises(APITimeoutError):
1530
- await self.client.get(
1531
- "/indices", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}
1532
- )
1583
+ await async_client.indices.with_streaming_response.list().__aenter__()
1533
1584
 
1534
1585
  assert _get_open_connections(self.client) == 0
1535
1586
 
1536
1587
  @mock.patch("hubmap_search_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1537
1588
  @pytest.mark.respx(base_url=base_url)
1538
- async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
1589
+ async def test_retrying_status_errors_doesnt_leak(
1590
+ self, respx_mock: MockRouter, async_client: AsyncHubmapSearchSDK
1591
+ ) -> None:
1539
1592
  respx_mock.get("/indices").mock(return_value=httpx.Response(500))
1540
1593
 
1541
1594
  with pytest.raises(APIStatusError):
1542
- await self.client.get(
1543
- "/indices", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}
1544
- )
1545
-
1595
+ await async_client.indices.with_streaming_response.list().__aenter__()
1546
1596
  assert _get_open_connections(self.client) == 0
1547
1597
 
1548
1598
  @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -1669,3 +1719,52 @@ class TestAsyncHubmapSearchSDK:
1669
1719
  raise AssertionError("calling get_platform using asyncify resulted in a hung process")
1670
1720
 
1671
1721
  time.sleep(0.1)
1722
+
1723
+ async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
1724
+ # Test that the proxy environment variables are set correctly
1725
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
1726
+
1727
+ client = DefaultAsyncHttpxClient()
1728
+
1729
+ mounts = tuple(client._mounts.items())
1730
+ assert len(mounts) == 1
1731
+ assert mounts[0][0].pattern == "https://"
1732
+
1733
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
1734
+ async def test_default_client_creation(self) -> None:
1735
+ # Ensure that the client can be initialized without any exceptions
1736
+ DefaultAsyncHttpxClient(
1737
+ verify=True,
1738
+ cert=None,
1739
+ trust_env=True,
1740
+ http1=True,
1741
+ http2=False,
1742
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
1743
+ )
1744
+
1745
+ @pytest.mark.respx(base_url=base_url)
1746
+ async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
1747
+ # Test that the default follow_redirects=True allows following redirects
1748
+ respx_mock.post("/redirect").mock(
1749
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1750
+ )
1751
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
1752
+
1753
+ response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
1754
+ assert response.status_code == 200
1755
+ assert response.json() == {"status": "ok"}
1756
+
1757
+ @pytest.mark.respx(base_url=base_url)
1758
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
1759
+ # Test that follow_redirects=False prevents following redirects
1760
+ respx_mock.post("/redirect").mock(
1761
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1762
+ )
1763
+
1764
+ with pytest.raises(APIStatusError) as exc_info:
1765
+ await self.client.post(
1766
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
1767
+ )
1768
+
1769
+ assert exc_info.value.response.status_code == 302
1770
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
@@ -1,3 +0,0 @@
1
- {
2
- ".": "1.0.0-alpha.12"
3
- }