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

Potentially problematic release.


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

Files changed (76) hide show
  1. spitch-1.27.0/.release-please-manifest.json +3 -0
  2. {spitch-1.26.0 → spitch-1.27.0}/CHANGELOG.md +30 -0
  3. {spitch-1.26.0 → spitch-1.27.0}/CONTRIBUTING.md +1 -2
  4. {spitch-1.26.0 → spitch-1.27.0}/PKG-INFO +1 -1
  5. {spitch-1.26.0 → spitch-1.27.0}/SECURITY.md +2 -2
  6. {spitch-1.26.0 → spitch-1.27.0}/pyproject.toml +3 -2
  7. {spitch-1.26.0 → spitch-1.27.0}/requirements-dev.lock +4 -0
  8. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/__init__.py +5 -0
  9. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_base_client.py +22 -2
  10. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_models.py +2 -0
  11. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_response.py +1 -1
  12. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_types.py +2 -0
  13. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_proxy.py +4 -1
  14. spitch-1.27.0/src/spitch/_utils/_resources_proxy.py +24 -0
  15. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_version.py +1 -1
  16. {spitch-1.26.0 → spitch-1.27.0}/tests/conftest.py +2 -0
  17. {spitch-1.26.0 → spitch-1.27.0}/tests/test_client.py +106 -1
  18. {spitch-1.26.0 → spitch-1.27.0}/tests/test_utils/test_proxy.py +11 -0
  19. spitch-1.26.0/.release-please-manifest.json +0 -3
  20. {spitch-1.26.0 → spitch-1.27.0}/.gitignore +0 -0
  21. {spitch-1.26.0 → spitch-1.27.0}/LICENSE +0 -0
  22. {spitch-1.26.0 → spitch-1.27.0}/README.md +0 -0
  23. {spitch-1.26.0 → spitch-1.27.0}/api.md +0 -0
  24. {spitch-1.26.0 → spitch-1.27.0}/bin/check-release-environment +0 -0
  25. {spitch-1.26.0 → spitch-1.27.0}/bin/publish-pypi +0 -0
  26. {spitch-1.26.0 → spitch-1.27.0}/examples/.keep +0 -0
  27. {spitch-1.26.0 → spitch-1.27.0}/examples/example.py +0 -0
  28. {spitch-1.26.0 → spitch-1.27.0}/mypy.ini +0 -0
  29. {spitch-1.26.0 → spitch-1.27.0}/noxfile.py +0 -0
  30. {spitch-1.26.0 → spitch-1.27.0}/release-please-config.json +0 -0
  31. {spitch-1.26.0 → spitch-1.27.0}/requirements.lock +0 -0
  32. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_client.py +0 -0
  33. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_compat.py +0 -0
  34. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_constants.py +0 -0
  35. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_exceptions.py +0 -0
  36. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_files.py +0 -0
  37. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_qs.py +0 -0
  38. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_resource.py +0 -0
  39. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_streaming.py +0 -0
  40. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/__init__.py +0 -0
  41. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_logs.py +0 -0
  42. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_reflection.py +0 -0
  43. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_streams.py +0 -0
  44. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_sync.py +0 -0
  45. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_transform.py +0 -0
  46. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_typing.py +0 -0
  47. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/_utils/_utils.py +0 -0
  48. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/lib/.keep +0 -0
  49. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/py.typed +0 -0
  50. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/resources/__init__.py +0 -0
  51. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/resources/speech.py +0 -0
  52. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/resources/text.py +0 -0
  53. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/__init__.py +0 -0
  54. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/speech_generate_params.py +0 -0
  55. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/speech_transcribe_params.py +0 -0
  56. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/speech_transcribe_response.py +0 -0
  57. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/text_tone_mark_params.py +0 -0
  58. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/text_tone_mark_response.py +0 -0
  59. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/text_translate_params.py +0 -0
  60. {spitch-1.26.0 → spitch-1.27.0}/src/spitch/types/text_translate_response.py +0 -0
  61. {spitch-1.26.0 → spitch-1.27.0}/tests/__init__.py +0 -0
  62. {spitch-1.26.0 → spitch-1.27.0}/tests/api_resources/__init__.py +0 -0
  63. {spitch-1.26.0 → spitch-1.27.0}/tests/api_resources/test_speech.py +0 -0
  64. {spitch-1.26.0 → spitch-1.27.0}/tests/api_resources/test_text.py +0 -0
  65. {spitch-1.26.0 → spitch-1.27.0}/tests/sample_file.txt +0 -0
  66. {spitch-1.26.0 → spitch-1.27.0}/tests/test_deepcopy.py +0 -0
  67. {spitch-1.26.0 → spitch-1.27.0}/tests/test_extract_files.py +0 -0
  68. {spitch-1.26.0 → spitch-1.27.0}/tests/test_files.py +0 -0
  69. {spitch-1.26.0 → spitch-1.27.0}/tests/test_models.py +0 -0
  70. {spitch-1.26.0 → spitch-1.27.0}/tests/test_qs.py +0 -0
  71. {spitch-1.26.0 → spitch-1.27.0}/tests/test_required_args.py +0 -0
  72. {spitch-1.26.0 → spitch-1.27.0}/tests/test_response.py +0 -0
  73. {spitch-1.26.0 → spitch-1.27.0}/tests/test_streaming.py +0 -0
  74. {spitch-1.26.0 → spitch-1.27.0}/tests/test_transform.py +0 -0
  75. {spitch-1.26.0 → spitch-1.27.0}/tests/test_utils/test_typing.py +0 -0
  76. {spitch-1.26.0 → spitch-1.27.0}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.27.0"
3
+ }
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.27.0 (2025-06-17)
4
+
5
+ Full Changelog: [v1.26.0...v1.27.0](https://github.com/spi-tch/spitch-python/compare/v1.26.0...v1.27.0)
6
+
7
+ ### Features
8
+
9
+ * **client:** add follow_redirects request option ([23f17af](https://github.com/spi-tch/spitch-python/commit/23f17afc3292d6641890115e849134e14412ab7c))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **client:** correctly parse binary response | stream ([43997d9](https://github.com/spi-tch/spitch-python/commit/43997d9e1f67269df4f5ed9472f1abb6dfc8afc3))
15
+ * **package:** support direct resource imports ([3b664db](https://github.com/spi-tch/spitch-python/commit/3b664db8de2fe51363215430c04ef0d45a96ef15))
16
+
17
+
18
+ ### Chores
19
+
20
+ * broadly detect json family of content-type headers ([1358fe6](https://github.com/spi-tch/spitch-python/commit/1358fe6da25145ce5d57317a835bf306e9886f5c))
21
+ * **ci:** enable for pull requests ([f21c633](https://github.com/spi-tch/spitch-python/commit/f21c633b3bc746fdfcf95214bda7e664212fce3f))
22
+ * **ci:** fix installation instructions ([c23d223](https://github.com/spi-tch/spitch-python/commit/c23d22393563b50ac77742d75cda7f7b95e8a4a0))
23
+ * **ci:** only use depot for staging repos ([86c82b9](https://github.com/spi-tch/spitch-python/commit/86c82b989e1599bc9d71060f2f2998eaa4b79081))
24
+ * **ci:** upload sdks to package manager ([a40305e](https://github.com/spi-tch/spitch-python/commit/a40305e129dd6795b0e0e1408b863758cfad62d0))
25
+ * **docs:** grammar improvements ([e31d3bb](https://github.com/spi-tch/spitch-python/commit/e31d3bbe8aa390cdbae514e16e31e45b3ee5a4d5))
26
+ * **docs:** remove reference to rye shell ([b7f1703](https://github.com/spi-tch/spitch-python/commit/b7f1703ce312c6733e2986d045568b003ac4bba1))
27
+ * **internal:** avoid errors for isinstance checks on proxies ([aaf12a5](https://github.com/spi-tch/spitch-python/commit/aaf12a5580a673adbf303bea88f958f384b0ebcd))
28
+ * **internal:** codegen related update ([fc70ee0](https://github.com/spi-tch/spitch-python/commit/fc70ee0bf2bb47f12ec36a35fad1b27d257897ce))
29
+ * **internal:** update conftest.py ([1b062cd](https://github.com/spi-tch/spitch-python/commit/1b062cdf9d6200399d24bceb73e31779a80be190))
30
+ * **tests:** add tests for httpx client instantiation & proxies ([a57f4cc](https://github.com/spi-tch/spitch-python/commit/a57f4cc2105706eb53c9483ac74e2ec4995bd757))
31
+ * **tests:** run tests in parallel ([da93455](https://github.com/spi-tch/spitch-python/commit/da93455365f885480cf4553ab138fc71eebd59f9))
32
+
3
33
  ## 1.26.0 (2025-04-23)
4
34
 
5
35
  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.0
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
@@ -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.0"
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.0" # 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
@@ -27,7 +27,14 @@ from spitch._utils import maybe_transform
27
27
  from spitch._models import BaseModel, FinalRequestOptions
28
28
  from spitch._constants import RAW_RESPONSE_HEADER
29
29
  from spitch._exceptions import SpitchError, APIStatusError, APITimeoutError, APIResponseValidationError
30
- from spitch._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options
30
+ from spitch._base_client import (
31
+ DEFAULT_TIMEOUT,
32
+ HTTPX_DEFAULT_TIMEOUT,
33
+ BaseClient,
34
+ DefaultHttpxClient,
35
+ DefaultAsyncHttpxClient,
36
+ make_request_options,
37
+ )
31
38
  from spitch.types.speech_generate_params import SpeechGenerateParams
32
39
 
33
40
  from .utils import update_env
@@ -819,6 +826,55 @@ class TestSpitch:
819
826
 
820
827
  assert response.http_request.headers.get("x-stainless-retry-count") == "42"
821
828
 
829
+ def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
830
+ # Test that the proxy environment variables are set correctly
831
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
832
+
833
+ client = DefaultHttpxClient()
834
+
835
+ mounts = tuple(client._mounts.items())
836
+ assert len(mounts) == 1
837
+ assert mounts[0][0].pattern == "https://"
838
+
839
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
840
+ def test_default_client_creation(self) -> None:
841
+ # Ensure that the client can be initialized without any exceptions
842
+ DefaultHttpxClient(
843
+ verify=True,
844
+ cert=None,
845
+ trust_env=True,
846
+ http1=True,
847
+ http2=False,
848
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
849
+ )
850
+
851
+ @pytest.mark.respx(base_url=base_url)
852
+ def test_follow_redirects(self, respx_mock: MockRouter) -> None:
853
+ # Test that the default follow_redirects=True allows following redirects
854
+ respx_mock.post("/redirect").mock(
855
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
856
+ )
857
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
858
+
859
+ response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
860
+ assert response.status_code == 200
861
+ assert response.json() == {"status": "ok"}
862
+
863
+ @pytest.mark.respx(base_url=base_url)
864
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
865
+ # Test that follow_redirects=False prevents following redirects
866
+ respx_mock.post("/redirect").mock(
867
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
868
+ )
869
+
870
+ with pytest.raises(APIStatusError) as exc_info:
871
+ self.client.post(
872
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
873
+ )
874
+
875
+ assert exc_info.value.response.status_code == 302
876
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
877
+
822
878
 
823
879
  class TestAsyncSpitch:
824
880
  client = AsyncSpitch(base_url=base_url, api_key=api_key, _strict_response_validation=True)
@@ -1650,3 +1706,52 @@ class TestAsyncSpitch:
1650
1706
  raise AssertionError("calling get_platform using asyncify resulted in a hung process")
1651
1707
 
1652
1708
  time.sleep(0.1)
1709
+
1710
+ async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
1711
+ # Test that the proxy environment variables are set correctly
1712
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
1713
+
1714
+ client = DefaultAsyncHttpxClient()
1715
+
1716
+ mounts = tuple(client._mounts.items())
1717
+ assert len(mounts) == 1
1718
+ assert mounts[0][0].pattern == "https://"
1719
+
1720
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
1721
+ async def test_default_client_creation(self) -> None:
1722
+ # Ensure that the client can be initialized without any exceptions
1723
+ DefaultAsyncHttpxClient(
1724
+ verify=True,
1725
+ cert=None,
1726
+ trust_env=True,
1727
+ http1=True,
1728
+ http2=False,
1729
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
1730
+ )
1731
+
1732
+ @pytest.mark.respx(base_url=base_url)
1733
+ async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
1734
+ # Test that the default follow_redirects=True allows following redirects
1735
+ respx_mock.post("/redirect").mock(
1736
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1737
+ )
1738
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
1739
+
1740
+ response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
1741
+ assert response.status_code == 200
1742
+ assert response.json() == {"status": "ok"}
1743
+
1744
+ @pytest.mark.respx(base_url=base_url)
1745
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
1746
+ # Test that follow_redirects=False prevents following redirects
1747
+ respx_mock.post("/redirect").mock(
1748
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1749
+ )
1750
+
1751
+ with pytest.raises(APIStatusError) as exc_info:
1752
+ await self.client.post(
1753
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
1754
+ )
1755
+
1756
+ assert exc_info.value.response.status_code == 302
1757
+ 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
File without changes