nimble_python 0.16.0__tar.gz → 0.17.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 (146) hide show
  1. nimble_python-0.17.0/.release-please-manifest.json +3 -0
  2. {nimble_python-0.16.0 → nimble_python-0.17.0}/CHANGELOG.md +36 -0
  3. {nimble_python-0.16.0 → nimble_python-0.17.0}/PKG-INFO +1 -1
  4. {nimble_python-0.16.0 → nimble_python-0.17.0}/pyproject.toml +2 -2
  5. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_base_client.py +4 -0
  6. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_client.py +75 -10
  7. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_files.py +53 -3
  8. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_qs.py +2 -6
  9. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_types.py +3 -0
  10. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/__init__.py +0 -1
  11. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_utils.py +37 -25
  12. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_version.py +1 -1
  13. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/resources/agent.py +35 -24
  14. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_generate_params.py +19 -4
  15. nimble_python-0.17.0/src/nimble_python/types/agent_generate_response.py +68 -0
  16. nimble_python-0.17.0/src/nimble_python/types/agent_get_generation_response.py +68 -0
  17. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_get_response.py +5 -3
  18. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_async_params.py +1 -1
  19. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_batch_params.py +2 -2
  20. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_params.py +1 -1
  21. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_response.py +3 -0
  22. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/client_extract_async_params.py +9 -2
  23. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/client_extract_batch_params.py +18 -4
  24. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/client_extract_params.py +9 -2
  25. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_run_params.py +9 -2
  26. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/extract_response.py +3 -0
  27. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/test_agent.py +66 -52
  28. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/test_client.py +24 -152
  29. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/test_crawl.py +6 -38
  30. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_client.py +48 -0
  31. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_extract_files.py +28 -1
  32. nimble_python-0.17.0/tests/test_files.py +148 -0
  33. nimble_python-0.16.0/.release-please-manifest.json +0 -3
  34. nimble_python-0.16.0/src/nimble_python/types/agent_generate_response.py +0 -32
  35. nimble_python-0.16.0/src/nimble_python/types/agent_get_generation_response.py +0 -32
  36. nimble_python-0.16.0/tests/test_deepcopy.py +0 -58
  37. nimble_python-0.16.0/tests/test_files.py +0 -51
  38. {nimble_python-0.16.0 → nimble_python-0.17.0}/.gitignore +0 -0
  39. {nimble_python-0.16.0 → nimble_python-0.17.0}/CONTRIBUTING.md +0 -0
  40. {nimble_python-0.16.0 → nimble_python-0.17.0}/LICENSE +0 -0
  41. {nimble_python-0.16.0 → nimble_python-0.17.0}/README.md +0 -0
  42. {nimble_python-0.16.0 → nimble_python-0.17.0}/SECURITY.md +0 -0
  43. {nimble_python-0.16.0 → nimble_python-0.17.0}/api.md +0 -0
  44. {nimble_python-0.16.0 → nimble_python-0.17.0}/bin/check-release-environment +0 -0
  45. {nimble_python-0.16.0 → nimble_python-0.17.0}/bin/publish-pypi +0 -0
  46. {nimble_python-0.16.0 → nimble_python-0.17.0}/examples/.keep +0 -0
  47. {nimble_python-0.16.0 → nimble_python-0.17.0}/release-please-config.json +0 -0
  48. {nimble_python-0.16.0 → nimble_python-0.17.0}/requirements-dev.lock +0 -0
  49. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/__init__.py +0 -0
  50. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_compat.py +0 -0
  51. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_constants.py +0 -0
  52. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_exceptions.py +0 -0
  53. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_models.py +0 -0
  54. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_resource.py +0 -0
  55. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_response.py +0 -0
  56. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_streaming.py +0 -0
  57. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_compat.py +0 -0
  58. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_datetime_parse.py +0 -0
  59. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_json.py +0 -0
  60. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_logs.py +0 -0
  61. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_path.py +0 -0
  62. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_proxy.py +0 -0
  63. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_reflection.py +0 -0
  64. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_resources_proxy.py +0 -0
  65. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_streams.py +0 -0
  66. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_sync.py +0 -0
  67. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_transform.py +0 -0
  68. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/_utils/_typing.py +0 -0
  69. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/lib/.keep +0 -0
  70. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/py.typed +0 -0
  71. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/resources/__init__.py +0 -0
  72. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/resources/batches.py +0 -0
  73. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/resources/crawl.py +0 -0
  74. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/resources/tasks.py +0 -0
  75. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/__init__.py +0 -0
  76. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_list_params.py +0 -0
  77. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_list_response.py +0 -0
  78. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_publish_params.py +0 -0
  79. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_publish_response.py +0 -0
  80. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_async_response.py +0 -0
  81. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/agent_run_batch_response.py +0 -0
  82. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/batch_get_response.py +0 -0
  83. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/batch_progress_response.py +0 -0
  84. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/client_map_params.py +0 -0
  85. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/client_search_params.py +0 -0
  86. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_list_params.py +0 -0
  87. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_list_response.py +0 -0
  88. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_run_response.py +0 -0
  89. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_status_response.py +0 -0
  90. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/crawl_terminate_response.py +0 -0
  91. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/extract_async_response.py +0 -0
  92. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/extract_batch_response.py +0 -0
  93. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/map_response.py +0 -0
  94. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/search_response.py +0 -0
  95. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/__init__.py +0 -0
  96. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/auto_scroll_action.py +0 -0
  97. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/click_action.py +0 -0
  98. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/eval_action.py +0 -0
  99. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/fetch_action.py +0 -0
  100. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/fill_action.py +0 -0
  101. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/get_cookies_action.py +0 -0
  102. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/goto_action.py +0 -0
  103. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/press_action.py +0 -0
  104. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/screenshot_action.py +0 -0
  105. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/scroll_action.py +0 -0
  106. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/wait_action.py +0 -0
  107. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/wait_for_element_action.py +0 -0
  108. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared/wait_for_navigation_action.py +0 -0
  109. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/__init__.py +0 -0
  110. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/auto_scroll_action.py +0 -0
  111. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/click_action.py +0 -0
  112. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/eval_action.py +0 -0
  113. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/fetch_action.py +0 -0
  114. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/fill_action.py +0 -0
  115. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/get_cookies_action.py +0 -0
  116. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/goto_action.py +0 -0
  117. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/press_action.py +0 -0
  118. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/screenshot_action.py +0 -0
  119. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/scroll_action.py +0 -0
  120. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/wait_action.py +0 -0
  121. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/wait_for_element_action.py +0 -0
  122. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/shared_params/wait_for_navigation_action.py +0 -0
  123. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/task_get_response.py +0 -0
  124. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/task_list_params.py +0 -0
  125. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/task_list_response.py +0 -0
  126. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimble_python/types/task_results_response.py +0 -0
  127. {nimble_python-0.16.0 → nimble_python-0.17.0}/src/nimbleway/lib/.keep +0 -0
  128. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/__init__.py +0 -0
  129. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/__init__.py +0 -0
  130. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/test_batches.py +0 -0
  131. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/api_resources/test_tasks.py +0 -0
  132. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/conftest.py +0 -0
  133. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/sample_file.txt +0 -0
  134. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_models.py +0 -0
  135. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_qs.py +0 -0
  136. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_required_args.py +0 -0
  137. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_response.py +0 -0
  138. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_streaming.py +0 -0
  139. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_transform.py +0 -0
  140. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_utils/test_datetime_parse.py +0 -0
  141. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_utils/test_json.py +0 -0
  142. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_utils/test_path.py +0 -0
  143. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_utils/test_proxy.py +0 -0
  144. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/test_utils/test_typing.py +0 -0
  145. {nimble_python-0.16.0 → nimble_python-0.17.0}/tests/utils.py +0 -0
  146. {nimble_python-0.16.0 → nimble_python-0.17.0}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.17.0"
3
+ }
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 (2026-05-06)
4
+
5
+ Full Changelog: [v0.16.0...v0.17.0](https://github.com/Nimbleway/nimble-python/compare/v0.16.0...v0.17.0)
6
+
7
+ ### Features
8
+
9
+ * **api:** api update ([d859e62](https://github.com/Nimbleway/nimble-python/commit/d859e62068214303e9430d093a78028b25b854a2))
10
+ * **api:** api update ([da41c63](https://github.com/Nimbleway/nimble-python/commit/da41c63037e82fde7af3a3e0da8cb3782a175bb8))
11
+ * **api:** api update ([3460f38](https://github.com/Nimbleway/nimble-python/commit/3460f38ecdf277bbebf2fdc7104015d4a7fee810))
12
+ * **api:** api update ([0809fc4](https://github.com/Nimbleway/nimble-python/commit/0809fc473463e746e5b1af30cfcadbfc87db7a5e))
13
+ * **client:** add client_source parameter, update press_action types ([346cfe7](https://github.com/Nimbleway/nimble-python/commit/346cfe70ff7a237c9f0bd52ae699d2012cf5cec7))
14
+ * support setting headers via env ([74b98e0](https://github.com/Nimbleway/nimble-python/commit/74b98e0af4a71cedc34d53f4b33efd6126b91439))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **client:** preserve hardcoded query params when merging with user params ([c07dd33](https://github.com/Nimbleway/nimble-python/commit/c07dd33a98b0dc7a365153ebe13aa8e4f41035fc))
20
+ * ensure file data are only sent as 1 parameter ([58d7d07](https://github.com/Nimbleway/nimble-python/commit/58d7d07475836f0d4ce1c4f68bba7e3631e68d34))
21
+ * use correct field name format for multipart file arrays ([d33d6b7](https://github.com/Nimbleway/nimble-python/commit/d33d6b7599d29d0508fa14ba03a8e0eb7c756aad))
22
+
23
+
24
+ ### Performance Improvements
25
+
26
+ * **client:** optimize file structure copying in multipart requests ([77a1d14](https://github.com/Nimbleway/nimble-python/commit/77a1d14ebc143548bcca089d281babc094e01a7e))
27
+
28
+
29
+ ### Chores
30
+
31
+ * **internal:** more robust bootstrap script ([ea52394](https://github.com/Nimbleway/nimble-python/commit/ea52394a5adbf1ef83a2d0a5b378d7feb0e0a5f8))
32
+ * **internal:** reformat pyproject.toml ([7d5cec6](https://github.com/Nimbleway/nimble-python/commit/7d5cec6274e237ab69a2b32e79f72a9baad78fff))
33
+
34
+
35
+ ### Documentation
36
+
37
+ * improve examples ([a86f0ef](https://github.com/Nimbleway/nimble-python/commit/a86f0ef01987c3063ab465d4ab61bd04229a4911))
38
+
3
39
  ## 0.16.0 (2026-03-30)
4
40
 
5
41
  Full Changelog: [v0.15.0...v0.16.0](https://github.com/Nimbleway/nimble-python/compare/v0.15.0...v0.16.0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nimble_python
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: The official Python library for the nimble API
5
5
  Project-URL: Homepage, https://github.com/Nimbleway/nimble-python
6
6
  Project-URL: Repository, https://github.com/Nimbleway/nimble-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nimble_python"
3
- version = "0.16.0"
3
+ version = "0.17.0"
4
4
  description = "The official Python library for the nimble API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -154,7 +154,7 @@ show_error_codes = true
154
154
  #
155
155
  # We also exclude our `tests` as mypy doesn't always infer
156
156
  # types correctly and Pyright will still catch any type errors.
157
- exclude = ['src/nimble_python/_files.py', '_dev/.*.py', 'tests/.*']
157
+ exclude = ["src/nimble_python/_files.py", "_dev/.*.py", "tests/.*"]
158
158
 
159
159
  strict_equality = true
160
160
  implicit_reexport = true
@@ -540,6 +540,10 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
540
540
  files = cast(HttpxRequestFiles, ForceMultipartDict())
541
541
 
542
542
  prepared_url = self._prepare_url(options.url)
543
+ # preserve hard-coded query params from the url
544
+ if params and prepared_url.query:
545
+ params = {**dict(prepared_url.params.items()), **params}
546
+ prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0])
543
547
  if "_" in prepared_url.host:
544
548
  # work around https://github.com/encode/httpx/discussions/2880
545
549
  kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
@@ -33,6 +33,7 @@ from ._types import (
33
33
  )
34
34
  from ._utils import (
35
35
  is_given,
36
+ is_mapping_t,
36
37
  maybe_transform,
37
38
  get_async_library,
38
39
  async_maybe_transform,
@@ -72,11 +73,13 @@ __all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Nimble", "
72
73
  class Nimble(SyncAPIClient):
73
74
  # client options
74
75
  api_key: str | None
76
+ client_source: str | None
75
77
 
76
78
  def __init__(
77
79
  self,
78
80
  *,
79
81
  api_key: str | None = None,
82
+ client_source: str | None = None,
80
83
  base_url: str | httpx.URL | None = None,
81
84
  timeout: float | Timeout | None | NotGiven = not_given,
82
85
  max_retries: int = DEFAULT_MAX_RETRIES,
@@ -98,17 +101,32 @@ class Nimble(SyncAPIClient):
98
101
  ) -> None:
99
102
  """Construct a new synchronous Nimble client instance.
100
103
 
101
- This automatically infers the `api_key` argument from the `NIMBLE_API_KEY` environment variable if it is not provided.
104
+ This automatically infers the following arguments from their corresponding environment variables if they are not provided:
105
+ - `api_key` from `NIMBLE_API_KEY`
106
+ - `client_source` from `CLIENT_SOURCE`
102
107
  """
103
108
  if api_key is None:
104
109
  api_key = os.environ.get("NIMBLE_API_KEY")
105
110
  self.api_key = api_key
106
111
 
112
+ if client_source is None:
113
+ client_source = os.environ.get("CLIENT_SOURCE") or "sdk"
114
+ self.client_source = client_source
115
+
107
116
  if base_url is None:
108
117
  base_url = os.environ.get("NIMBLE_BASE_URL")
109
118
  if base_url is None:
110
119
  base_url = f"https://sdk.nimbleway.com"
111
120
 
121
+ custom_headers_env = os.environ.get("NIMBLE_CUSTOM_HEADERS")
122
+ if custom_headers_env is not None:
123
+ parsed: dict[str, str] = {}
124
+ for line in custom_headers_env.split("\n"):
125
+ colon = line.find(":")
126
+ if colon >= 0:
127
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
128
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
129
+
112
130
  super().__init__(
113
131
  version=__version__,
114
132
  base_url=base_url,
@@ -171,6 +189,7 @@ class Nimble(SyncAPIClient):
171
189
  return {
172
190
  **super().default_headers,
173
191
  "X-Stainless-Async": "false",
192
+ "X-Client-Source": self.client_source if self.client_source is not None else Omit(),
174
193
  **self._custom_headers,
175
194
  }
176
195
 
@@ -187,6 +206,7 @@ class Nimble(SyncAPIClient):
187
206
  self,
188
207
  *,
189
208
  api_key: str | None = None,
209
+ client_source: str | None = None,
190
210
  base_url: str | httpx.URL | None = None,
191
211
  timeout: float | Timeout | None | NotGiven = not_given,
192
212
  http_client: httpx.Client | None = None,
@@ -221,6 +241,7 @@ class Nimble(SyncAPIClient):
221
241
  http_client = http_client or self._client
222
242
  return self.__class__(
223
243
  api_key=api_key or self.api_key,
244
+ client_source=client_source or self.client_source,
224
245
  base_url=base_url or self.base_url,
225
246
  timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
226
247
  http_client=http_client,
@@ -498,9 +519,9 @@ class Nimble(SyncAPIClient):
498
519
  ]
499
520
  | Omit = omit,
500
521
  device: Literal["desktop", "mobile", "tablet"] | Omit = omit,
501
- driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro"] | Omit = omit,
522
+ driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro", "media-vx6"] | Omit = omit,
502
523
  expected_status_codes: Iterable[int] | Omit = omit,
503
- formats: List[Literal["html", "markdown", "screenshot", "headers"]] | Omit = omit,
524
+ formats: List[Literal["html", "markdown", "screenshot", "headers", "links"]] | Omit = omit,
504
525
  headers: Dict[str, Union[str, SequenceNotStr[str], None]] | Omit = omit,
505
526
  http2: bool | Omit = omit,
506
527
  is_xhr: bool | Omit = omit,
@@ -1040,6 +1061,7 @@ class Nimble(SyncAPIClient):
1040
1061
  "auto",
1041
1062
  ]
1042
1063
  | Omit = omit,
1064
+ markdown_backend: Literal["full_page", "main_content"] | Omit = omit,
1043
1065
  method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] | Omit = omit,
1044
1066
  network_capture: Iterable[client_extract_params.NetworkCapture] | Omit = omit,
1045
1067
  os: Literal["windows", "mac os", "linux", "android", "ios"] | Omit = omit,
@@ -1154,6 +1176,10 @@ class Nimble(SyncAPIClient):
1154
1176
 
1155
1177
  locale: Locale for browser language and region settings
1156
1178
 
1179
+ markdown_backend: Selects which markdown conversion strategy to use. "full_page" converts the
1180
+ entire HTML page. "main_content" uses Mozilla Readability to extract the main
1181
+ article content before converting.
1182
+
1157
1183
  method: HTTP method for the request
1158
1184
 
1159
1185
  network_capture: Filters for capturing network traffic
@@ -1203,6 +1229,7 @@ class Nimble(SyncAPIClient):
1203
1229
  "http2": http2,
1204
1230
  "is_xhr": is_xhr,
1205
1231
  "locale": locale,
1232
+ "markdown_backend": markdown_backend,
1206
1233
  "method": method,
1207
1234
  "network_capture": network_capture,
1208
1235
  "os": os,
@@ -1489,9 +1516,9 @@ class Nimble(SyncAPIClient):
1489
1516
  ]
1490
1517
  | Omit = omit,
1491
1518
  device: Literal["desktop", "mobile", "tablet"] | Omit = omit,
1492
- driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro"] | Omit = omit,
1519
+ driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro", "media-vx6"] | Omit = omit,
1493
1520
  expected_status_codes: Iterable[int] | Omit = omit,
1494
- formats: List[Literal["html", "markdown", "screenshot", "headers"]] | Omit = omit,
1521
+ formats: List[Literal["html", "markdown", "screenshot", "headers", "links"]] | Omit = omit,
1495
1522
  headers: Dict[str, Union[str, SequenceNotStr[str], None]] | Omit = omit,
1496
1523
  http2: bool | Omit = omit,
1497
1524
  is_xhr: bool | Omit = omit,
@@ -2031,6 +2058,7 @@ class Nimble(SyncAPIClient):
2031
2058
  "auto",
2032
2059
  ]
2033
2060
  | Omit = omit,
2061
+ markdown_backend: Literal["full_page", "main_content"] | Omit = omit,
2034
2062
  method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] | Omit = omit,
2035
2063
  network_capture: Iterable[client_extract_async_params.NetworkCapture] | Omit = omit,
2036
2064
  os: Literal["windows", "mac os", "linux", "android", "ios"] | Omit = omit,
@@ -2151,6 +2179,10 @@ class Nimble(SyncAPIClient):
2151
2179
 
2152
2180
  locale: Locale for browser language and region settings
2153
2181
 
2182
+ markdown_backend: Selects which markdown conversion strategy to use. "full_page" converts the
2183
+ entire HTML page. "main_content" uses Mozilla Readability to extract the main
2184
+ article content before converting.
2185
+
2154
2186
  method: HTTP method for the request
2155
2187
 
2156
2188
  network_capture: Filters for capturing network traffic
@@ -2209,6 +2241,7 @@ class Nimble(SyncAPIClient):
2209
2241
  "http2": http2,
2210
2242
  "is_xhr": is_xhr,
2211
2243
  "locale": locale,
2244
+ "markdown_backend": markdown_backend,
2212
2245
  "method": method,
2213
2246
  "network_capture": network_capture,
2214
2247
  "os": os,
@@ -3272,11 +3305,13 @@ class Nimble(SyncAPIClient):
3272
3305
  class AsyncNimble(AsyncAPIClient):
3273
3306
  # client options
3274
3307
  api_key: str | None
3308
+ client_source: str | None
3275
3309
 
3276
3310
  def __init__(
3277
3311
  self,
3278
3312
  *,
3279
3313
  api_key: str | None = None,
3314
+ client_source: str | None = None,
3280
3315
  base_url: str | httpx.URL | None = None,
3281
3316
  timeout: float | Timeout | None | NotGiven = not_given,
3282
3317
  max_retries: int = DEFAULT_MAX_RETRIES,
@@ -3298,17 +3333,32 @@ class AsyncNimble(AsyncAPIClient):
3298
3333
  ) -> None:
3299
3334
  """Construct a new async AsyncNimble client instance.
3300
3335
 
3301
- This automatically infers the `api_key` argument from the `NIMBLE_API_KEY` environment variable if it is not provided.
3336
+ This automatically infers the following arguments from their corresponding environment variables if they are not provided:
3337
+ - `api_key` from `NIMBLE_API_KEY`
3338
+ - `client_source` from `CLIENT_SOURCE`
3302
3339
  """
3303
3340
  if api_key is None:
3304
3341
  api_key = os.environ.get("NIMBLE_API_KEY")
3305
3342
  self.api_key = api_key
3306
3343
 
3344
+ if client_source is None:
3345
+ client_source = os.environ.get("CLIENT_SOURCE") or "sdk"
3346
+ self.client_source = client_source
3347
+
3307
3348
  if base_url is None:
3308
3349
  base_url = os.environ.get("NIMBLE_BASE_URL")
3309
3350
  if base_url is None:
3310
3351
  base_url = f"https://sdk.nimbleway.com"
3311
3352
 
3353
+ custom_headers_env = os.environ.get("NIMBLE_CUSTOM_HEADERS")
3354
+ if custom_headers_env is not None:
3355
+ parsed: dict[str, str] = {}
3356
+ for line in custom_headers_env.split("\n"):
3357
+ colon = line.find(":")
3358
+ if colon >= 0:
3359
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
3360
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
3361
+
3312
3362
  super().__init__(
3313
3363
  version=__version__,
3314
3364
  base_url=base_url,
@@ -3371,6 +3421,7 @@ class AsyncNimble(AsyncAPIClient):
3371
3421
  return {
3372
3422
  **super().default_headers,
3373
3423
  "X-Stainless-Async": f"async:{get_async_library()}",
3424
+ "X-Client-Source": self.client_source if self.client_source is not None else Omit(),
3374
3425
  **self._custom_headers,
3375
3426
  }
3376
3427
 
@@ -3387,6 +3438,7 @@ class AsyncNimble(AsyncAPIClient):
3387
3438
  self,
3388
3439
  *,
3389
3440
  api_key: str | None = None,
3441
+ client_source: str | None = None,
3390
3442
  base_url: str | httpx.URL | None = None,
3391
3443
  timeout: float | Timeout | None | NotGiven = not_given,
3392
3444
  http_client: httpx.AsyncClient | None = None,
@@ -3421,6 +3473,7 @@ class AsyncNimble(AsyncAPIClient):
3421
3473
  http_client = http_client or self._client
3422
3474
  return self.__class__(
3423
3475
  api_key=api_key or self.api_key,
3476
+ client_source=client_source or self.client_source,
3424
3477
  base_url=base_url or self.base_url,
3425
3478
  timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
3426
3479
  http_client=http_client,
@@ -3698,9 +3751,9 @@ class AsyncNimble(AsyncAPIClient):
3698
3751
  ]
3699
3752
  | Omit = omit,
3700
3753
  device: Literal["desktop", "mobile", "tablet"] | Omit = omit,
3701
- driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro"] | Omit = omit,
3754
+ driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro", "media-vx6"] | Omit = omit,
3702
3755
  expected_status_codes: Iterable[int] | Omit = omit,
3703
- formats: List[Literal["html", "markdown", "screenshot", "headers"]] | Omit = omit,
3756
+ formats: List[Literal["html", "markdown", "screenshot", "headers", "links"]] | Omit = omit,
3704
3757
  headers: Dict[str, Union[str, SequenceNotStr[str], None]] | Omit = omit,
3705
3758
  http2: bool | Omit = omit,
3706
3759
  is_xhr: bool | Omit = omit,
@@ -4240,6 +4293,7 @@ class AsyncNimble(AsyncAPIClient):
4240
4293
  "auto",
4241
4294
  ]
4242
4295
  | Omit = omit,
4296
+ markdown_backend: Literal["full_page", "main_content"] | Omit = omit,
4243
4297
  method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] | Omit = omit,
4244
4298
  network_capture: Iterable[client_extract_params.NetworkCapture] | Omit = omit,
4245
4299
  os: Literal["windows", "mac os", "linux", "android", "ios"] | Omit = omit,
@@ -4354,6 +4408,10 @@ class AsyncNimble(AsyncAPIClient):
4354
4408
 
4355
4409
  locale: Locale for browser language and region settings
4356
4410
 
4411
+ markdown_backend: Selects which markdown conversion strategy to use. "full_page" converts the
4412
+ entire HTML page. "main_content" uses Mozilla Readability to extract the main
4413
+ article content before converting.
4414
+
4357
4415
  method: HTTP method for the request
4358
4416
 
4359
4417
  network_capture: Filters for capturing network traffic
@@ -4403,6 +4461,7 @@ class AsyncNimble(AsyncAPIClient):
4403
4461
  "http2": http2,
4404
4462
  "is_xhr": is_xhr,
4405
4463
  "locale": locale,
4464
+ "markdown_backend": markdown_backend,
4406
4465
  "method": method,
4407
4466
  "network_capture": network_capture,
4408
4467
  "os": os,
@@ -4689,9 +4748,9 @@ class AsyncNimble(AsyncAPIClient):
4689
4748
  ]
4690
4749
  | Omit = omit,
4691
4750
  device: Literal["desktop", "mobile", "tablet"] | Omit = omit,
4692
- driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro"] | Omit = omit,
4751
+ driver: Literal["vx6", "vx8", "vx8-pro", "vx10", "vx10-pro", "vx12", "vx12-pro", "media-vx6"] | Omit = omit,
4693
4752
  expected_status_codes: Iterable[int] | Omit = omit,
4694
- formats: List[Literal["html", "markdown", "screenshot", "headers"]] | Omit = omit,
4753
+ formats: List[Literal["html", "markdown", "screenshot", "headers", "links"]] | Omit = omit,
4695
4754
  headers: Dict[str, Union[str, SequenceNotStr[str], None]] | Omit = omit,
4696
4755
  http2: bool | Omit = omit,
4697
4756
  is_xhr: bool | Omit = omit,
@@ -5231,6 +5290,7 @@ class AsyncNimble(AsyncAPIClient):
5231
5290
  "auto",
5232
5291
  ]
5233
5292
  | Omit = omit,
5293
+ markdown_backend: Literal["full_page", "main_content"] | Omit = omit,
5234
5294
  method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] | Omit = omit,
5235
5295
  network_capture: Iterable[client_extract_async_params.NetworkCapture] | Omit = omit,
5236
5296
  os: Literal["windows", "mac os", "linux", "android", "ios"] | Omit = omit,
@@ -5351,6 +5411,10 @@ class AsyncNimble(AsyncAPIClient):
5351
5411
 
5352
5412
  locale: Locale for browser language and region settings
5353
5413
 
5414
+ markdown_backend: Selects which markdown conversion strategy to use. "full_page" converts the
5415
+ entire HTML page. "main_content" uses Mozilla Readability to extract the main
5416
+ article content before converting.
5417
+
5354
5418
  method: HTTP method for the request
5355
5419
 
5356
5420
  network_capture: Filters for capturing network traffic
@@ -5409,6 +5473,7 @@ class AsyncNimble(AsyncAPIClient):
5409
5473
  "http2": http2,
5410
5474
  "is_xhr": is_xhr,
5411
5475
  "locale": locale,
5476
+ "markdown_backend": markdown_backend,
5412
5477
  "method": method,
5413
5478
  "network_capture": network_capture,
5414
5479
  "os": os,
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import io
4
4
  import os
5
5
  import pathlib
6
- from typing import overload
7
- from typing_extensions import TypeGuard
6
+ from typing import Sequence, cast, overload
7
+ from typing_extensions import TypeVar, TypeGuard
8
8
 
9
9
  import anyio
10
10
 
@@ -17,7 +17,9 @@ from ._types import (
17
17
  HttpxFileContent,
18
18
  HttpxRequestFiles,
19
19
  )
20
- from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
20
+ from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
21
+
22
+ _T = TypeVar("_T")
21
23
 
22
24
 
23
25
  def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
121
123
  return await anyio.Path(file).read_bytes()
122
124
 
123
125
  return file
126
+
127
+
128
+ def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
129
+ """Copy only the containers along the given paths.
130
+
131
+ Used to guard against mutation by extract_files without copying the entire structure.
132
+ Only dicts and lists that lie on a path are copied; everything else
133
+ is returned by reference.
134
+
135
+ For example, given paths=[["foo", "files", "file"]] and the structure:
136
+ {
137
+ "foo": {
138
+ "bar": {"baz": {}},
139
+ "files": {"file": <content>}
140
+ }
141
+ }
142
+ The root dict, "foo", and "files" are copied (they lie on the path).
143
+ "bar" and "baz" are returned by reference (off the path).
144
+ """
145
+ return _deepcopy_with_paths(item, paths, 0)
146
+
147
+
148
+ def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
149
+ if not paths:
150
+ return item
151
+ if is_mapping(item):
152
+ key_to_paths: dict[str, list[Sequence[str]]] = {}
153
+ for path in paths:
154
+ if index < len(path):
155
+ key_to_paths.setdefault(path[index], []).append(path)
156
+
157
+ # if no path continues through this mapping, it won't be mutated and copying it is redundant
158
+ if not key_to_paths:
159
+ return item
160
+
161
+ result = dict(item)
162
+ for key, subpaths in key_to_paths.items():
163
+ if key in result:
164
+ result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
165
+ return cast(_T, result)
166
+ if is_list(item):
167
+ array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]
168
+
169
+ # if no path expects a list here, nothing will be mutated inside it - return by reference
170
+ if not array_paths:
171
+ return cast(_T, item)
172
+ return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
173
+ return item
@@ -2,17 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, List, Tuple, Union, Mapping, TypeVar
4
4
  from urllib.parse import parse_qs, urlencode
5
- from typing_extensions import Literal, get_args
5
+ from typing_extensions import get_args
6
6
 
7
- from ._types import NotGiven, not_given
7
+ from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
8
8
  from ._utils import flatten
9
9
 
10
10
  _T = TypeVar("_T")
11
11
 
12
-
13
- ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
14
- NestedFormat = Literal["dots", "brackets"]
15
-
16
12
  PrimitiveData = Union[str, int, float, bool, None]
17
13
  # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
18
14
  # https://github.com/microsoft/pyright/issues/3555
@@ -47,6 +47,9 @@ AnyMapping = Mapping[str, object]
47
47
  ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
48
48
  _T = TypeVar("_T")
49
49
 
50
+ ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
51
+ NestedFormat = Literal["dots", "brackets"]
52
+
50
53
 
51
54
  # Approximates httpx internal ProxiesTypes and RequestFiles types
52
55
  # while adding support for `PathLike` instances
@@ -24,7 +24,6 @@ from ._utils import (
24
24
  coerce_integer as coerce_integer,
25
25
  file_from_path as file_from_path,
26
26
  strip_not_given as strip_not_given,
27
- deepcopy_minimal as deepcopy_minimal,
28
27
  get_async_library as get_async_library,
29
28
  maybe_coerce_float as maybe_coerce_float,
30
29
  get_required_header as get_required_header,
@@ -17,11 +17,11 @@ from typing import (
17
17
  )
18
18
  from pathlib import Path
19
19
  from datetime import date, datetime
20
- from typing_extensions import TypeGuard
20
+ from typing_extensions import TypeGuard, get_args
21
21
 
22
22
  import sniffio
23
23
 
24
- from .._types import Omit, NotGiven, FileTypes, HeadersLike
24
+ from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike
25
25
 
26
26
  _T = TypeVar("_T")
27
27
  _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
@@ -40,25 +40,45 @@ def extract_files(
40
40
  query: Mapping[str, object],
41
41
  *,
42
42
  paths: Sequence[Sequence[str]],
43
+ array_format: ArrayFormat = "brackets",
43
44
  ) -> list[tuple[str, FileTypes]]:
44
45
  """Recursively extract files from the given dictionary based on specified paths.
45
46
 
46
47
  A path may look like this ['foo', 'files', '<array>', 'data'].
47
48
 
49
+ ``array_format`` controls how ``<array>`` segments contribute to the emitted
50
+ field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
51
+ ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
52
+
48
53
  Note: this mutates the given dictionary.
49
54
  """
50
55
  files: list[tuple[str, FileTypes]] = []
51
56
  for path in paths:
52
- files.extend(_extract_items(query, path, index=0, flattened_key=None))
57
+ files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
53
58
  return files
54
59
 
55
60
 
61
+ def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
62
+ if array_format == "brackets":
63
+ return "[]"
64
+ if array_format == "indices":
65
+ return f"[{array_index}]"
66
+ if array_format == "repeat" or array_format == "comma":
67
+ # Both repeat the bare field name for each file part; there is no
68
+ # meaningful way to comma-join binary parts.
69
+ return ""
70
+ raise NotImplementedError(
71
+ f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
72
+ )
73
+
74
+
56
75
  def _extract_items(
57
76
  obj: object,
58
77
  path: Sequence[str],
59
78
  *,
60
79
  index: int,
61
80
  flattened_key: str | None,
81
+ array_format: ArrayFormat,
62
82
  ) -> list[tuple[str, FileTypes]]:
63
83
  try:
64
84
  key = path[index]
@@ -75,9 +95,11 @@ def _extract_items(
75
95
 
76
96
  if is_list(obj):
77
97
  files: list[tuple[str, FileTypes]] = []
78
- for entry in obj:
79
- assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
80
- files.append((flattened_key + "[]", cast(FileTypes, entry)))
98
+ for array_index, entry in enumerate(obj):
99
+ suffix = _array_suffix(array_format, array_index)
100
+ emitted_key = (flattened_key + suffix) if flattened_key else suffix
101
+ assert_is_file_content(entry, key=emitted_key)
102
+ files.append((emitted_key, cast(FileTypes, entry)))
81
103
  return files
82
104
 
83
105
  assert_is_file_content(obj, key=flattened_key)
@@ -86,8 +108,9 @@ def _extract_items(
86
108
  index += 1
87
109
  if is_dict(obj):
88
110
  try:
89
- # We are at the last entry in the path so we must remove the field
90
- if (len(path)) == index:
111
+ # Remove the field if there are no more dict keys in the path,
112
+ # only "<array>" traversal markers or end.
113
+ if all(p == "<array>" for p in path[index:]):
91
114
  item = obj.pop(key)
92
115
  else:
93
116
  item = obj[key]
@@ -105,6 +128,7 @@ def _extract_items(
105
128
  path,
106
129
  index=index,
107
130
  flattened_key=flattened_key,
131
+ array_format=array_format,
108
132
  )
109
133
  elif is_list(obj):
110
134
  if key != "<array>":
@@ -116,9 +140,12 @@ def _extract_items(
116
140
  item,
117
141
  path,
118
142
  index=index,
119
- flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
143
+ flattened_key=(
144
+ (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
145
+ ),
146
+ array_format=array_format,
120
147
  )
121
- for item in obj
148
+ for array_index, item in enumerate(obj)
122
149
  ]
123
150
  )
124
151
 
@@ -176,21 +203,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
176
203
  return isinstance(obj, Iterable)
177
204
 
178
205
 
179
- def deepcopy_minimal(item: _T) -> _T:
180
- """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
181
-
182
- - mappings, e.g. `dict`
183
- - list
184
-
185
- This is done for performance reasons.
186
- """
187
- if is_mapping(item):
188
- return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
189
- if is_list(item):
190
- return cast(_T, [deepcopy_minimal(entry) for entry in item])
191
- return item
192
-
193
-
194
206
  # copied from https://github.com/Rapptz/RoboDanny
195
207
  def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
196
208
  size = len(seq)
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "nimble_python"
4
- __version__ = "0.16.0" # x-release-please-version
4
+ __version__ = "0.17.0" # x-release-please-version