spitch 1.26.0__tar.gz → 1.27.1__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.

Potentially problematic release.


This version of spitch might be problematic. Click here for more details.

Files changed (76) hide show
  1. spitch-1.27.1/.release-please-manifest.json +3 -0
  2. {spitch-1.26.0 → spitch-1.27.1}/CHANGELOG.md +48 -0
  3. {spitch-1.26.0 → spitch-1.27.1}/CONTRIBUTING.md +1 -2
  4. {spitch-1.26.0 → spitch-1.27.1}/PKG-INFO +3 -3
  5. {spitch-1.26.0 → spitch-1.27.1}/README.md +2 -2
  6. {spitch-1.26.0 → spitch-1.27.1}/SECURITY.md +2 -2
  7. {spitch-1.26.0 → spitch-1.27.1}/pyproject.toml +3 -2
  8. {spitch-1.26.0 → spitch-1.27.1}/requirements-dev.lock +4 -0
  9. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/__init__.py +5 -0
  10. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_base_client.py +22 -2
  11. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_models.py +2 -0
  12. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_response.py +1 -1
  13. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_types.py +2 -0
  14. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_proxy.py +4 -1
  15. spitch-1.27.1/src/spitch/_utils/_resources_proxy.py +24 -0
  16. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_version.py +1 -1
  17. {spitch-1.26.0 → spitch-1.27.1}/tests/conftest.py +2 -0
  18. {spitch-1.26.0 → spitch-1.27.1}/tests/test_client.py +118 -42
  19. {spitch-1.26.0 → spitch-1.27.1}/tests/test_utils/test_proxy.py +11 -0
  20. spitch-1.26.0/.release-please-manifest.json +0 -3
  21. {spitch-1.26.0 → spitch-1.27.1}/.gitignore +0 -0
  22. {spitch-1.26.0 → spitch-1.27.1}/LICENSE +0 -0
  23. {spitch-1.26.0 → spitch-1.27.1}/api.md +0 -0
  24. {spitch-1.26.0 → spitch-1.27.1}/bin/check-release-environment +0 -0
  25. {spitch-1.26.0 → spitch-1.27.1}/bin/publish-pypi +0 -0
  26. {spitch-1.26.0 → spitch-1.27.1}/examples/.keep +0 -0
  27. {spitch-1.26.0 → spitch-1.27.1}/examples/example.py +0 -0
  28. {spitch-1.26.0 → spitch-1.27.1}/mypy.ini +0 -0
  29. {spitch-1.26.0 → spitch-1.27.1}/noxfile.py +0 -0
  30. {spitch-1.26.0 → spitch-1.27.1}/release-please-config.json +0 -0
  31. {spitch-1.26.0 → spitch-1.27.1}/requirements.lock +0 -0
  32. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_client.py +0 -0
  33. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_compat.py +0 -0
  34. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_constants.py +0 -0
  35. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_exceptions.py +0 -0
  36. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_files.py +0 -0
  37. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_qs.py +0 -0
  38. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_resource.py +0 -0
  39. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_streaming.py +0 -0
  40. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/__init__.py +0 -0
  41. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_logs.py +0 -0
  42. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_reflection.py +0 -0
  43. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_streams.py +0 -0
  44. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_sync.py +0 -0
  45. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_transform.py +0 -0
  46. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_typing.py +0 -0
  47. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/_utils/_utils.py +0 -0
  48. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/lib/.keep +0 -0
  49. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/py.typed +0 -0
  50. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/resources/__init__.py +0 -0
  51. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/resources/speech.py +0 -0
  52. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/resources/text.py +0 -0
  53. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/__init__.py +0 -0
  54. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/speech_generate_params.py +0 -0
  55. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/speech_transcribe_params.py +0 -0
  56. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/speech_transcribe_response.py +0 -0
  57. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/text_tone_mark_params.py +0 -0
  58. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/text_tone_mark_response.py +0 -0
  59. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/text_translate_params.py +0 -0
  60. {spitch-1.26.0 → spitch-1.27.1}/src/spitch/types/text_translate_response.py +0 -0
  61. {spitch-1.26.0 → spitch-1.27.1}/tests/__init__.py +0 -0
  62. {spitch-1.26.0 → spitch-1.27.1}/tests/api_resources/__init__.py +0 -0
  63. {spitch-1.26.0 → spitch-1.27.1}/tests/api_resources/test_speech.py +0 -0
  64. {spitch-1.26.0 → spitch-1.27.1}/tests/api_resources/test_text.py +0 -0
  65. {spitch-1.26.0 → spitch-1.27.1}/tests/sample_file.txt +0 -0
  66. {spitch-1.26.0 → spitch-1.27.1}/tests/test_deepcopy.py +0 -0
  67. {spitch-1.26.0 → spitch-1.27.1}/tests/test_extract_files.py +0 -0
  68. {spitch-1.26.0 → spitch-1.27.1}/tests/test_files.py +0 -0
  69. {spitch-1.26.0 → spitch-1.27.1}/tests/test_models.py +0 -0
  70. {spitch-1.26.0 → spitch-1.27.1}/tests/test_qs.py +0 -0
  71. {spitch-1.26.0 → spitch-1.27.1}/tests/test_required_args.py +0 -0
  72. {spitch-1.26.0 → spitch-1.27.1}/tests/test_response.py +0 -0
  73. {spitch-1.26.0 → spitch-1.27.1}/tests/test_streaming.py +0 -0
  74. {spitch-1.26.0 → spitch-1.27.1}/tests/test_transform.py +0 -0
  75. {spitch-1.26.0 → spitch-1.27.1}/tests/test_utils/test_typing.py +0 -0
  76. {spitch-1.26.0 → spitch-1.27.1}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.27.1"
3
+ }
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.27.1 (2025-06-19)
4
+
5
+ Full Changelog: [v1.27.0...v1.27.1](https://github.com/spi-tch/spitch-python/compare/v1.27.0...v1.27.1)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([e6df2f6](https://github.com/spi-tch/spitch-python/commit/e6df2f6b390f5f3313ff1e20ee2890fd2ac53265))
10
+
11
+
12
+ ### Chores
13
+
14
+ * **readme:** update badges ([d272526](https://github.com/spi-tch/spitch-python/commit/d2725265a638bba5ef65bf8ab468a33af6b5b594))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * **client:** fix httpx.Timeout documentation reference ([d8af609](https://github.com/spi-tch/spitch-python/commit/d8af609deca7a01713250544babf4ced45dc2376))
20
+
21
+ ## 1.27.0 (2025-06-17)
22
+
23
+ Full Changelog: [v1.26.0...v1.27.0](https://github.com/spi-tch/spitch-python/compare/v1.26.0...v1.27.0)
24
+
25
+ ### Features
26
+
27
+ * **client:** add follow_redirects request option ([23f17af](https://github.com/spi-tch/spitch-python/commit/23f17afc3292d6641890115e849134e14412ab7c))
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * **client:** correctly parse binary response | stream ([43997d9](https://github.com/spi-tch/spitch-python/commit/43997d9e1f67269df4f5ed9472f1abb6dfc8afc3))
33
+ * **package:** support direct resource imports ([3b664db](https://github.com/spi-tch/spitch-python/commit/3b664db8de2fe51363215430c04ef0d45a96ef15))
34
+
35
+
36
+ ### Chores
37
+
38
+ * broadly detect json family of content-type headers ([1358fe6](https://github.com/spi-tch/spitch-python/commit/1358fe6da25145ce5d57317a835bf306e9886f5c))
39
+ * **ci:** enable for pull requests ([f21c633](https://github.com/spi-tch/spitch-python/commit/f21c633b3bc746fdfcf95214bda7e664212fce3f))
40
+ * **ci:** fix installation instructions ([c23d223](https://github.com/spi-tch/spitch-python/commit/c23d22393563b50ac77742d75cda7f7b95e8a4a0))
41
+ * **ci:** only use depot for staging repos ([86c82b9](https://github.com/spi-tch/spitch-python/commit/86c82b989e1599bc9d71060f2f2998eaa4b79081))
42
+ * **ci:** upload sdks to package manager ([a40305e](https://github.com/spi-tch/spitch-python/commit/a40305e129dd6795b0e0e1408b863758cfad62d0))
43
+ * **docs:** grammar improvements ([e31d3bb](https://github.com/spi-tch/spitch-python/commit/e31d3bbe8aa390cdbae514e16e31e45b3ee5a4d5))
44
+ * **docs:** remove reference to rye shell ([b7f1703](https://github.com/spi-tch/spitch-python/commit/b7f1703ce312c6733e2986d045568b003ac4bba1))
45
+ * **internal:** avoid errors for isinstance checks on proxies ([aaf12a5](https://github.com/spi-tch/spitch-python/commit/aaf12a5580a673adbf303bea88f958f384b0ebcd))
46
+ * **internal:** codegen related update ([fc70ee0](https://github.com/spi-tch/spitch-python/commit/fc70ee0bf2bb47f12ec36a35fad1b27d257897ce))
47
+ * **internal:** update conftest.py ([1b062cd](https://github.com/spi-tch/spitch-python/commit/1b062cdf9d6200399d24bceb73e31779a80be190))
48
+ * **tests:** add tests for httpx client instantiation & proxies ([a57f4cc](https://github.com/spi-tch/spitch-python/commit/a57f4cc2105706eb53c9483ac74e2ec4995bd757))
49
+ * **tests:** run tests in parallel ([da93455](https://github.com/spi-tch/spitch-python/commit/da93455365f885480cf4553ab138fc71eebd59f9))
50
+
3
51
  ## 1.26.0 (2025-04-23)
4
52
 
5
53
  Full Changelog: [v1.25.0...v1.26.0](https://github.com/spi-tch/spitch-python/compare/v1.25.0...v1.26.0)
@@ -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: spitch
3
- Version: 1.26.0
3
+ Version: 1.27.1
4
4
  Summary: The official Python library for the spitch API
5
5
  Project-URL: Homepage, https://github.com/spi-tch/spitch-python
6
6
  Project-URL: Repository, https://github.com/spi-tch/spitch-python
@@ -32,7 +32,7 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # Spitch Python API library
34
34
 
35
- [![PyPI version](https://img.shields.io/pypi/v/spitch.svg)](https://pypi.org/project/spitch/)
35
+ [![PyPI version](https://github.com/spi-tch/spitch-python/tree/main/<https://img.shields.io/pypi/v/spitch.svg?label=pypi%20(stable)>)](https://pypi.org/project/spitch/)
36
36
 
37
37
  The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.8+
38
38
  application. The library includes type definitions for all request params and response fields,
@@ -202,7 +202,7 @@ client.with_options(max_retries=5).speech.generate(
202
202
  ### Timeouts
203
203
 
204
204
  By default requests time out after 1 minute. You can configure this with a `timeout` option,
205
- which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
205
+ which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
206
206
 
207
207
  ```python
208
208
  from spitch import Spitch
@@ -1,6 +1,6 @@
1
1
  # Spitch Python API library
2
2
 
3
- [![PyPI version](https://img.shields.io/pypi/v/spitch.svg)](https://pypi.org/project/spitch/)
3
+ [![PyPI version](<https://img.shields.io/pypi/v/spitch.svg?label=pypi%20(stable)>)](https://pypi.org/project/spitch/)
4
4
 
5
5
  The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.8+
6
6
  application. The library includes type definitions for all request params and response fields,
@@ -170,7 +170,7 @@ client.with_options(max_retries=5).speech.generate(
170
170
  ### Timeouts
171
171
 
172
172
  By default requests time out after 1 minute. You can configure this with a `timeout` option,
173
- which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
173
+ which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
174
174
 
175
175
  ```python
176
176
  from spitch import Spitch
@@ -16,11 +16,11 @@ before making any information public.
16
16
  ## Reporting Non-SDK Related Security Issues
17
17
 
18
18
  If you encounter security issues that are not directly related to SDKs but pertain to the services
19
- or products provided by Spitch please follow the respective company's security reporting guidelines.
19
+ or products provided by Spitch, please follow the respective company's security reporting guidelines.
20
20
 
21
21
  ### Spitch Terms and Policies
22
22
 
23
- Please contact dev@spitch.app for any questions or concerns regarding security of our services.
23
+ Please contact dev@spitch.app for any questions or concerns regarding the security of our services.
24
24
 
25
25
  ---
26
26
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spitch"
3
- version = "1.26.0"
3
+ version = "1.27.1"
4
4
  description = "The official Python library for the spitch API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -55,6 +55,7 @@ dev-dependencies = [
55
55
  "importlib-metadata>=6.7.0",
56
56
  "rich>=13.7.1",
57
57
  "nest_asyncio==1.6.0",
58
+ "pytest-xdist>=3.6.1",
58
59
  ]
59
60
 
60
61
  [tool.rye.scripts]
@@ -126,7 +127,7 @@ replacement = '[\1](https://github.com/spi-tch/spitch-python/tree/main/\g<2>)'
126
127
 
127
128
  [tool.pytest.ini_options]
128
129
  testpaths = ["tests"]
129
- addopts = "--tb=short"
130
+ addopts = "--tb=short -n auto"
130
131
  xfail_strict = true
131
132
  asyncio_mode = "auto"
132
133
  asyncio_default_fixture_loop_scope = "session"
@@ -30,6 +30,8 @@ distro==1.8.0
30
30
  exceptiongroup==1.2.2
31
31
  # via anyio
32
32
  # via pytest
33
+ execnet==2.1.1
34
+ # via pytest-xdist
33
35
  filelock==3.12.4
34
36
  # via virtualenv
35
37
  h11==0.14.0
@@ -72,7 +74,9 @@ pygments==2.18.0
72
74
  pyright==1.1.399
73
75
  pytest==8.3.3
74
76
  # via pytest-asyncio
77
+ # via pytest-xdist
75
78
  pytest-asyncio==0.24.0
79
+ pytest-xdist==3.7.0
76
80
  python-dateutil==2.8.2
77
81
  # via time-machine
78
82
  pytz==2023.3.post1
@@ -1,5 +1,7 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
+ import typing as _t
4
+
3
5
  from . import types
4
6
  from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes
5
7
  from ._utils import file_from_path
@@ -67,6 +69,9 @@ __all__ = [
67
69
  "DefaultAsyncHttpxClient",
68
70
  ]
69
71
 
72
+ if not _t.TYPE_CHECKING:
73
+ from ._utils._resources_proxy import resources as resources
74
+
70
75
  _setup_logging()
71
76
 
72
77
  # Update the __module__ attribute for exported symbols so that
@@ -960,6 +960,9 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
960
960
  if self.custom_auth is not None:
961
961
  kwargs["auth"] = self.custom_auth
962
962
 
963
+ if options.follow_redirects is not None:
964
+ kwargs["follow_redirects"] = options.follow_redirects
965
+
963
966
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
964
967
 
965
968
  response = None
@@ -1068,7 +1071,14 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1068
1071
  ) -> ResponseT:
1069
1072
  origin = get_origin(cast_to) or cast_to
1070
1073
 
1071
- if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
1074
+ if (
1075
+ inspect.isclass(origin)
1076
+ and issubclass(origin, BaseAPIResponse)
1077
+ # we only want to actually return the custom BaseAPIResponse class if we're
1078
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
1079
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
1080
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
1081
+ ):
1072
1082
  if not issubclass(origin, APIResponse):
1073
1083
  raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
1074
1084
 
@@ -1464,6 +1474,9 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1464
1474
  if self.custom_auth is not None:
1465
1475
  kwargs["auth"] = self.custom_auth
1466
1476
 
1477
+ if options.follow_redirects is not None:
1478
+ kwargs["follow_redirects"] = options.follow_redirects
1479
+
1467
1480
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
1468
1481
 
1469
1482
  response = None
@@ -1572,7 +1585,14 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1572
1585
  ) -> ResponseT:
1573
1586
  origin = get_origin(cast_to) or cast_to
1574
1587
 
1575
- if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse):
1588
+ if (
1589
+ inspect.isclass(origin)
1590
+ and issubclass(origin, BaseAPIResponse)
1591
+ # we only want to actually return the custom BaseAPIResponse class if we're
1592
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
1593
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
1594
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
1595
+ ):
1576
1596
  if not issubclass(origin, AsyncAPIResponse):
1577
1597
  raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
1578
1598
 
@@ -733,6 +733,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
733
733
  idempotency_key: str
734
734
  json_data: Body
735
735
  extra_json: AnyMapping
736
+ follow_redirects: bool
736
737
 
737
738
 
738
739
  @final
@@ -746,6 +747,7 @@ class FinalRequestOptions(pydantic.BaseModel):
746
747
  files: Union[HttpxRequestFiles, None] = None
747
748
  idempotency_key: Union[str, None] = None
748
749
  post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
750
+ follow_redirects: Union[bool, None] = None
749
751
 
750
752
  # It should be noted that we cannot use `json` here as that would override
751
753
  # a BaseModel method in an incompatible fashion.
@@ -227,7 +227,7 @@ class BaseAPIResponse(Generic[R]):
227
227
  # split is required to handle cases where additional information is included
228
228
  # in the response, e.g. application/json; charset=utf-8
229
229
  content_type, *_ = response.headers.get("content-type", "*").split(";")
230
- if content_type != "application/json":
230
+ if not content_type.endswith("json"):
231
231
  if is_basemodel(cast_to):
232
232
  try:
233
233
  data = response.json()
@@ -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
@@ -217,3 +218,4 @@ class _GenericAlias(Protocol):
217
218
 
218
219
  class HttpxSendArgs(TypedDict, total=False):
219
220
  auth: httpx.Auth
221
+ follow_redirects: bool
@@ -46,7 +46,10 @@ class LazyProxy(Generic[T], ABC):
46
46
  @property # type: ignore
47
47
  @override
48
48
  def __class__(self) -> type: # pyright: ignore
49
- proxied = self.__get_proxied__()
49
+ try:
50
+ proxied = self.__get_proxied__()
51
+ except Exception:
52
+ return type(self)
50
53
  if issubclass(type(proxied), LazyProxy):
51
54
  return type(proxied)
52
55
  return proxied.__class__
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ from typing_extensions import override
5
+
6
+ from ._proxy import LazyProxy
7
+
8
+
9
+ class ResourcesProxy(LazyProxy[Any]):
10
+ """A proxy for the `spitch.resources` module.
11
+
12
+ This is used so that we can lazily import `spitch.resources` only when
13
+ needed *and* so that users can just import `spitch` and reference `spitch.resources`
14
+ """
15
+
16
+ @override
17
+ def __load__(self) -> Any:
18
+ import importlib
19
+
20
+ mod = importlib.import_module("spitch.resources")
21
+ return mod
22
+
23
+
24
+ resources = ResourcesProxy().__as_proxied__()
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "spitch"
4
- __version__ = "1.26.0" # x-release-please-version
4
+ __version__ = "1.27.1" # x-release-please-version
@@ -1,3 +1,5 @@
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
@@ -23,12 +23,16 @@ from pydantic import ValidationError
23
23
 
24
24
  from spitch import Spitch, AsyncSpitch, APIResponseValidationError
25
25
  from spitch._types import Omit
26
- from spitch._utils import maybe_transform
27
26
  from spitch._models import BaseModel, FinalRequestOptions
28
- from spitch._constants import RAW_RESPONSE_HEADER
29
27
  from spitch._exceptions import SpitchError, APIStatusError, APITimeoutError, APIResponseValidationError
30
- from spitch._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options
31
- from spitch.types.speech_generate_params import SpeechGenerateParams
28
+ from spitch._base_client import (
29
+ DEFAULT_TIMEOUT,
30
+ HTTPX_DEFAULT_TIMEOUT,
31
+ BaseClient,
32
+ DefaultHttpxClient,
33
+ DefaultAsyncHttpxClient,
34
+ make_request_options,
35
+ )
32
36
 
33
37
  from .utils import update_env
34
38
 
@@ -706,36 +710,21 @@ class TestSpitch:
706
710
 
707
711
  @mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
708
712
  @pytest.mark.respx(base_url=base_url)
709
- def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
713
+ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Spitch) -> None:
710
714
  respx_mock.post("/v1/speech").mock(side_effect=httpx.TimeoutException("Test timeout error"))
711
715
 
712
716
  with pytest.raises(APITimeoutError):
713
- self.client.post(
714
- "/v1/speech",
715
- body=cast(
716
- object, maybe_transform(dict(language="yo", text="text", voice="sade"), SpeechGenerateParams)
717
- ),
718
- cast_to=httpx.Response,
719
- options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
720
- )
717
+ client.speech.with_streaming_response.generate(language="yo", text="text", voice="sade").__enter__()
721
718
 
722
719
  assert _get_open_connections(self.client) == 0
723
720
 
724
721
  @mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
725
722
  @pytest.mark.respx(base_url=base_url)
726
- def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
723
+ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Spitch) -> None:
727
724
  respx_mock.post("/v1/speech").mock(return_value=httpx.Response(500))
728
725
 
729
726
  with pytest.raises(APIStatusError):
730
- self.client.post(
731
- "/v1/speech",
732
- body=cast(
733
- object, maybe_transform(dict(language="yo", text="text", voice="sade"), SpeechGenerateParams)
734
- ),
735
- cast_to=httpx.Response,
736
- options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
737
- )
738
-
727
+ client.speech.with_streaming_response.generate(language="yo", text="text", voice="sade").__enter__()
739
728
  assert _get_open_connections(self.client) == 0
740
729
 
741
730
  @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -819,6 +808,55 @@ class TestSpitch:
819
808
 
820
809
  assert response.http_request.headers.get("x-stainless-retry-count") == "42"
821
810
 
811
+ def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
812
+ # Test that the proxy environment variables are set correctly
813
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
814
+
815
+ client = DefaultHttpxClient()
816
+
817
+ mounts = tuple(client._mounts.items())
818
+ assert len(mounts) == 1
819
+ assert mounts[0][0].pattern == "https://"
820
+
821
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
822
+ def test_default_client_creation(self) -> None:
823
+ # Ensure that the client can be initialized without any exceptions
824
+ DefaultHttpxClient(
825
+ verify=True,
826
+ cert=None,
827
+ trust_env=True,
828
+ http1=True,
829
+ http2=False,
830
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
831
+ )
832
+
833
+ @pytest.mark.respx(base_url=base_url)
834
+ def test_follow_redirects(self, respx_mock: MockRouter) -> None:
835
+ # Test that the default follow_redirects=True allows following redirects
836
+ respx_mock.post("/redirect").mock(
837
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
838
+ )
839
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
840
+
841
+ response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
842
+ assert response.status_code == 200
843
+ assert response.json() == {"status": "ok"}
844
+
845
+ @pytest.mark.respx(base_url=base_url)
846
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
847
+ # Test that follow_redirects=False prevents following redirects
848
+ respx_mock.post("/redirect").mock(
849
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
850
+ )
851
+
852
+ with pytest.raises(APIStatusError) as exc_info:
853
+ self.client.post(
854
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
855
+ )
856
+
857
+ assert exc_info.value.response.status_code == 302
858
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
859
+
822
860
 
823
861
  class TestAsyncSpitch:
824
862
  client = AsyncSpitch(base_url=base_url, api_key=api_key, _strict_response_validation=True)
@@ -1490,36 +1528,25 @@ class TestAsyncSpitch:
1490
1528
 
1491
1529
  @mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1492
1530
  @pytest.mark.respx(base_url=base_url)
1493
- async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
1531
+ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncSpitch) -> None:
1494
1532
  respx_mock.post("/v1/speech").mock(side_effect=httpx.TimeoutException("Test timeout error"))
1495
1533
 
1496
1534
  with pytest.raises(APITimeoutError):
1497
- await self.client.post(
1498
- "/v1/speech",
1499
- body=cast(
1500
- object, maybe_transform(dict(language="yo", text="text", voice="sade"), SpeechGenerateParams)
1501
- ),
1502
- cast_to=httpx.Response,
1503
- options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
1504
- )
1535
+ await async_client.speech.with_streaming_response.generate(
1536
+ language="yo", text="text", voice="sade"
1537
+ ).__aenter__()
1505
1538
 
1506
1539
  assert _get_open_connections(self.client) == 0
1507
1540
 
1508
1541
  @mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1509
1542
  @pytest.mark.respx(base_url=base_url)
1510
- async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None:
1543
+ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncSpitch) -> None:
1511
1544
  respx_mock.post("/v1/speech").mock(return_value=httpx.Response(500))
1512
1545
 
1513
1546
  with pytest.raises(APIStatusError):
1514
- await self.client.post(
1515
- "/v1/speech",
1516
- body=cast(
1517
- object, maybe_transform(dict(language="yo", text="text", voice="sade"), SpeechGenerateParams)
1518
- ),
1519
- cast_to=httpx.Response,
1520
- options={"headers": {RAW_RESPONSE_HEADER: "stream"}},
1521
- )
1522
-
1547
+ await async_client.speech.with_streaming_response.generate(
1548
+ language="yo", text="text", voice="sade"
1549
+ ).__aenter__()
1523
1550
  assert _get_open_connections(self.client) == 0
1524
1551
 
1525
1552
  @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@@ -1650,3 +1677,52 @@ class TestAsyncSpitch:
1650
1677
  raise AssertionError("calling get_platform using asyncify resulted in a hung process")
1651
1678
 
1652
1679
  time.sleep(0.1)
1680
+
1681
+ async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
1682
+ # Test that the proxy environment variables are set correctly
1683
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
1684
+
1685
+ client = DefaultAsyncHttpxClient()
1686
+
1687
+ mounts = tuple(client._mounts.items())
1688
+ assert len(mounts) == 1
1689
+ assert mounts[0][0].pattern == "https://"
1690
+
1691
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
1692
+ async def test_default_client_creation(self) -> None:
1693
+ # Ensure that the client can be initialized without any exceptions
1694
+ DefaultAsyncHttpxClient(
1695
+ verify=True,
1696
+ cert=None,
1697
+ trust_env=True,
1698
+ http1=True,
1699
+ http2=False,
1700
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
1701
+ )
1702
+
1703
+ @pytest.mark.respx(base_url=base_url)
1704
+ async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
1705
+ # Test that the default follow_redirects=True allows following redirects
1706
+ respx_mock.post("/redirect").mock(
1707
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1708
+ )
1709
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
1710
+
1711
+ response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
1712
+ assert response.status_code == 200
1713
+ assert response.json() == {"status": "ok"}
1714
+
1715
+ @pytest.mark.respx(base_url=base_url)
1716
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
1717
+ # Test that follow_redirects=False prevents following redirects
1718
+ respx_mock.post("/redirect").mock(
1719
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1720
+ )
1721
+
1722
+ with pytest.raises(APIStatusError) as exc_info:
1723
+ await self.client.post(
1724
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
1725
+ )
1726
+
1727
+ assert exc_info.value.response.status_code == 302
1728
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
@@ -21,3 +21,14 @@ def test_recursive_proxy() -> None:
21
21
  assert dir(proxy) == []
22
22
  assert type(proxy).__name__ == "RecursiveLazyProxy"
23
23
  assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy"
24
+
25
+
26
+ def test_isinstance_does_not_error() -> None:
27
+ class AlwaysErrorProxy(LazyProxy[Any]):
28
+ @override
29
+ def __load__(self) -> Any:
30
+ raise RuntimeError("Mocking missing dependency")
31
+
32
+ proxy = AlwaysErrorProxy()
33
+ assert not isinstance(proxy, dict)
34
+ assert isinstance(proxy, LazyProxy)
@@ -1,3 +0,0 @@
1
- {
2
- ".": "1.26.0"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes