spitch 1.14.0__tar.gz → 1.15.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.
- spitch-1.15.1/.release-please-manifest.json +3 -0
- {spitch-1.14.0 → spitch-1.15.1}/CHANGELOG.md +23 -0
- {spitch-1.14.0 → spitch-1.15.1}/PKG-INFO +7 -11
- {spitch-1.14.0 → spitch-1.15.1}/README.md +4 -6
- {spitch-1.14.0 → spitch-1.15.1}/examples/example.py +1 -1
- {spitch-1.14.0 → spitch-1.15.1}/pyproject.toml +4 -9
- {spitch-1.14.0 → spitch-1.15.1}/requirements-dev.lock +11 -13
- {spitch-1.14.0 → spitch-1.15.1}/requirements.lock +4 -4
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_base_client.py +1 -1
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_compat.py +5 -3
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_models.py +11 -8
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_types.py +4 -2
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/__init__.py +1 -0
- spitch-1.15.1/src/spitch/_utils/_sync.py +71 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_transform.py +12 -2
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_utils.py +17 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_version.py +1 -1
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/speech.py +36 -2
- spitch-1.15.1/src/spitch/types/speech_generate_params.py +36 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/conftest.py +8 -6
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_client.py +59 -4
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_models.py +7 -14
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_transform.py +15 -0
- spitch-1.14.0/.release-please-manifest.json +0 -3
- spitch-1.14.0/src/spitch/_utils/_sync.py +0 -81
- spitch-1.14.0/src/spitch/types/speech_generate_params.py +0 -17
- spitch-1.14.0/src/spitch/types/speech_transcibe_params.py +0 -18
- {spitch-1.14.0 → spitch-1.15.1}/.gitignore +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/CONTRIBUTING.md +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/LICENSE +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/SECURITY.md +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/api.md +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/bin/check-release-environment +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/bin/publish-pypi +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/examples/.keep +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/mypy.ini +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/noxfile.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/release-please-config.json +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/__init__.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_client.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_constants.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_exceptions.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_files.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_qs.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_resource.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_response.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_streaming.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_logs.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_proxy.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_reflection.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_streams.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_typing.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/lib/.keep +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/py.typed +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/__init__.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/text.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/__init__.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/speech_transcribe_params.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/speech_transcribe_response.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_tone_mark_params.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_tone_mark_response.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_translate_params.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_translate_response.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/__init__.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/__init__.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/test_speech.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/test_text.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/sample_file.txt +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_deepcopy.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_extract_files.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_files.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_qs.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_required_args.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_response.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_streaming.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_utils/test_proxy.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/test_utils/test_typing.py +0 -0
- {spitch-1.14.0 → spitch-1.15.1}/tests/utils.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.15.1 (2024-11-18)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v1.15.0...v1.15.1](https://github.com/spi-tch/spitch-python/compare/v1.15.0...v1.15.1)
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
|
|
9
|
+
* rebuild project due to codegen change ([#67](https://github.com/spi-tch/spitch-python/issues/67)) ([4b6aa68](https://github.com/spi-tch/spitch-python/commit/4b6aa6839c2aeba8e4a4fbe20786b69634c3c5bc))
|
|
10
|
+
|
|
11
|
+
## 1.15.0 (2024-11-15)
|
|
12
|
+
|
|
13
|
+
Full Changelog: [v1.14.0...v1.15.0](https://github.com/spi-tch/spitch-python/compare/v1.14.0...v1.15.0)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **api:** update via SDK Studio ([#65](https://github.com/spi-tch/spitch-python/issues/65)) ([75d0335](https://github.com/spi-tch/spitch-python/commit/75d0335fa27259e3c41e2591f0f8a89773f5ff02))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Chores
|
|
21
|
+
|
|
22
|
+
* rebuild project due to codegen change ([#61](https://github.com/spi-tch/spitch-python/issues/61)) ([7041078](https://github.com/spi-tch/spitch-python/commit/70410787cb6255e839e3633b0732295f0d98f2bb))
|
|
23
|
+
* rebuild project due to codegen change ([#63](https://github.com/spi-tch/spitch-python/issues/63)) ([1761ce9](https://github.com/spi-tch/spitch-python/commit/1761ce946924b818037c3d055f422020814119a1))
|
|
24
|
+
* rebuild project due to codegen change ([#64](https://github.com/spi-tch/spitch-python/issues/64)) ([99cc2fc](https://github.com/spi-tch/spitch-python/commit/99cc2fcfe79bbc09242230a5dcdbce085107fed9))
|
|
25
|
+
|
|
3
26
|
## 1.14.0 (2024-10-18)
|
|
4
27
|
|
|
5
28
|
Full Changelog: [v1.13.0...v1.14.0](https://github.com/spi-tch/spitch-python/compare/v1.13.0...v1.14.0)
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: spitch
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.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
|
|
7
7
|
Author-email: Spitch <admin@spi-tch.com>
|
|
8
|
-
License
|
|
9
|
-
License-File: LICENSE
|
|
8
|
+
License: Apache-2.0
|
|
10
9
|
Classifier: Intended Audience :: Developers
|
|
11
10
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
11
|
Classifier: Operating System :: MacOS
|
|
@@ -14,7 +13,6 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Operating System :: POSIX
|
|
16
15
|
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -22,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
22
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.8
|
|
26
24
|
Requires-Dist: anyio<5,>=3.5.0
|
|
27
25
|
Requires-Dist: cached-property; python_version < '3.8'
|
|
28
26
|
Requires-Dist: distro<2,>=1.7.0
|
|
@@ -36,7 +34,7 @@ Description-Content-Type: text/markdown
|
|
|
36
34
|
|
|
37
35
|
[](https://pypi.org/project/spitch/)
|
|
38
36
|
|
|
39
|
-
The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.
|
|
37
|
+
The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.8+
|
|
40
38
|
application. The library includes type definitions for all request params and response fields,
|
|
41
39
|
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
|
|
42
40
|
|
|
@@ -62,8 +60,7 @@ import os
|
|
|
62
60
|
from spitch import Spitch
|
|
63
61
|
|
|
64
62
|
client = Spitch(
|
|
65
|
-
# This is the default and can be omitted
|
|
66
|
-
api_key=os.environ.get("SPITCH_API_KEY"),
|
|
63
|
+
api_key=os.environ.get("SPITCH_API_KEY"), # This is the default and can be omitted
|
|
67
64
|
)
|
|
68
65
|
|
|
69
66
|
response = client.speech.generate(
|
|
@@ -88,8 +85,7 @@ import asyncio
|
|
|
88
85
|
from spitch import AsyncSpitch
|
|
89
86
|
|
|
90
87
|
client = AsyncSpitch(
|
|
91
|
-
# This is the default and can be omitted
|
|
92
|
-
api_key=os.environ.get("SPITCH_API_KEY"),
|
|
88
|
+
api_key=os.environ.get("SPITCH_API_KEY"), # This is the default and can be omitted
|
|
93
89
|
)
|
|
94
90
|
|
|
95
91
|
|
|
@@ -374,7 +370,7 @@ print(spitch.__version__)
|
|
|
374
370
|
|
|
375
371
|
## Requirements
|
|
376
372
|
|
|
377
|
-
Python 3.
|
|
373
|
+
Python 3.8 or higher.
|
|
378
374
|
|
|
379
375
|
## Contributing
|
|
380
376
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/spitch/)
|
|
4
4
|
|
|
5
|
-
The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.
|
|
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,
|
|
7
7
|
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
|
|
8
8
|
|
|
@@ -28,8 +28,7 @@ import os
|
|
|
28
28
|
from spitch import Spitch
|
|
29
29
|
|
|
30
30
|
client = Spitch(
|
|
31
|
-
# This is the default and can be omitted
|
|
32
|
-
api_key=os.environ.get("SPITCH_API_KEY"),
|
|
31
|
+
api_key=os.environ.get("SPITCH_API_KEY"), # This is the default and can be omitted
|
|
33
32
|
)
|
|
34
33
|
|
|
35
34
|
response = client.speech.generate(
|
|
@@ -54,8 +53,7 @@ import asyncio
|
|
|
54
53
|
from spitch import AsyncSpitch
|
|
55
54
|
|
|
56
55
|
client = AsyncSpitch(
|
|
57
|
-
# This is the default and can be omitted
|
|
58
|
-
api_key=os.environ.get("SPITCH_API_KEY"),
|
|
56
|
+
api_key=os.environ.get("SPITCH_API_KEY"), # This is the default and can be omitted
|
|
59
57
|
)
|
|
60
58
|
|
|
61
59
|
|
|
@@ -340,7 +338,7 @@ print(spitch.__version__)
|
|
|
340
338
|
|
|
341
339
|
## Requirements
|
|
342
340
|
|
|
343
|
-
Python 3.
|
|
341
|
+
Python 3.8 or higher.
|
|
344
342
|
|
|
345
343
|
## Contributing
|
|
346
344
|
|
|
@@ -20,7 +20,7 @@ def main() -> None:
|
|
|
20
20
|
speech.stream_to_file(speech_file_path)
|
|
21
21
|
|
|
22
22
|
# transcribe
|
|
23
|
-
response = client.speech.
|
|
23
|
+
response = client.speech.transcribe(language="yo", content=speech_file_path)
|
|
24
24
|
print(type(response))
|
|
25
25
|
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spitch"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.15.1"
|
|
4
4
|
description = "The official Python library for the spitch API"
|
|
5
5
|
dynamic = ["readme"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -16,11 +16,10 @@ dependencies = [
|
|
|
16
16
|
"sniffio",
|
|
17
17
|
"cached-property; python_version < '3.8'",
|
|
18
18
|
]
|
|
19
|
-
requires-python = ">= 3.
|
|
19
|
+
requires-python = ">= 3.8"
|
|
20
20
|
classifiers = [
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
"Intended Audience :: Developers",
|
|
23
|
-
"Programming Language :: Python :: 3.7",
|
|
24
23
|
"Programming Language :: Python :: 3.8",
|
|
25
24
|
"Programming Language :: Python :: 3.9",
|
|
26
25
|
"Programming Language :: Python :: 3.10",
|
|
@@ -56,6 +55,7 @@ dev-dependencies = [
|
|
|
56
55
|
"dirty-equals>=0.6.0",
|
|
57
56
|
"importlib-metadata>=6.7.0",
|
|
58
57
|
"rich>=13.7.1",
|
|
58
|
+
"nest_asyncio==1.6.0"
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
[tool.rye.scripts]
|
|
@@ -66,7 +66,6 @@ format = { chain = [
|
|
|
66
66
|
# run formatting again to fix any inconsistencies when imports are stripped
|
|
67
67
|
"format:ruff",
|
|
68
68
|
]}
|
|
69
|
-
"format:black" = "black ."
|
|
70
69
|
"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
|
|
71
70
|
"format:ruff" = "ruff format"
|
|
72
71
|
|
|
@@ -126,10 +125,6 @@ path = "README.md"
|
|
|
126
125
|
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
|
|
127
126
|
replacement = '[\1](https://github.com/spi-tch/spitch-python/tree/main/\g<2>)'
|
|
128
127
|
|
|
129
|
-
[tool.black]
|
|
130
|
-
line-length = 120
|
|
131
|
-
target-version = ["py37"]
|
|
132
|
-
|
|
133
128
|
[tool.pytest.ini_options]
|
|
134
129
|
testpaths = ["tests"]
|
|
135
130
|
addopts = "--tb=short"
|
|
@@ -144,7 +139,7 @@ filterwarnings = [
|
|
|
144
139
|
# there are a couple of flags that are still disabled by
|
|
145
140
|
# default in strict mode as they are experimental and niche.
|
|
146
141
|
typeCheckingMode = "strict"
|
|
147
|
-
pythonVersion = "3.
|
|
142
|
+
pythonVersion = "3.8"
|
|
148
143
|
|
|
149
144
|
exclude = [
|
|
150
145
|
"_dev",
|
|
@@ -16,8 +16,6 @@ anyio==4.4.0
|
|
|
16
16
|
# via spitch
|
|
17
17
|
argcomplete==3.1.2
|
|
18
18
|
# via nox
|
|
19
|
-
attrs==23.1.0
|
|
20
|
-
# via pytest
|
|
21
19
|
certifi==2023.7.22
|
|
22
20
|
# via httpcore
|
|
23
21
|
# via httpx
|
|
@@ -28,8 +26,9 @@ distlib==0.3.7
|
|
|
28
26
|
# via virtualenv
|
|
29
27
|
distro==1.8.0
|
|
30
28
|
# via spitch
|
|
31
|
-
exceptiongroup==1.
|
|
29
|
+
exceptiongroup==1.2.2
|
|
32
30
|
# via anyio
|
|
31
|
+
# via pytest
|
|
33
32
|
filelock==3.12.4
|
|
34
33
|
# via virtualenv
|
|
35
34
|
h11==0.14.0
|
|
@@ -49,9 +48,10 @@ markdown-it-py==3.0.0
|
|
|
49
48
|
# via rich
|
|
50
49
|
mdurl==0.1.2
|
|
51
50
|
# via markdown-it-py
|
|
52
|
-
mypy==1.
|
|
51
|
+
mypy==1.13.0
|
|
53
52
|
mypy-extensions==1.0.0
|
|
54
53
|
# via mypy
|
|
54
|
+
nest-asyncio==1.6.0
|
|
55
55
|
nodeenv==1.8.0
|
|
56
56
|
# via pyright
|
|
57
57
|
nox==2023.4.22
|
|
@@ -60,20 +60,18 @@ packaging==23.2
|
|
|
60
60
|
# via pytest
|
|
61
61
|
platformdirs==3.11.0
|
|
62
62
|
# via virtualenv
|
|
63
|
-
pluggy==1.
|
|
64
|
-
# via pytest
|
|
65
|
-
py==1.11.0
|
|
63
|
+
pluggy==1.5.0
|
|
66
64
|
# via pytest
|
|
67
|
-
pydantic==2.
|
|
65
|
+
pydantic==2.9.2
|
|
68
66
|
# via spitch
|
|
69
|
-
pydantic-core==2.
|
|
67
|
+
pydantic-core==2.23.4
|
|
70
68
|
# via pydantic
|
|
71
69
|
pygments==2.18.0
|
|
72
70
|
# via rich
|
|
73
71
|
pyright==1.1.380
|
|
74
|
-
pytest==
|
|
72
|
+
pytest==8.3.3
|
|
75
73
|
# via pytest-asyncio
|
|
76
|
-
pytest-asyncio==0.
|
|
74
|
+
pytest-asyncio==0.24.0
|
|
77
75
|
python-dateutil==2.8.2
|
|
78
76
|
# via time-machine
|
|
79
77
|
pytz==2023.3.post1
|
|
@@ -90,10 +88,10 @@ sniffio==1.3.0
|
|
|
90
88
|
# via httpx
|
|
91
89
|
# via spitch
|
|
92
90
|
time-machine==2.9.0
|
|
93
|
-
tomli==2.0.
|
|
91
|
+
tomli==2.0.2
|
|
94
92
|
# via mypy
|
|
95
93
|
# via pytest
|
|
96
|
-
typing-extensions==4.
|
|
94
|
+
typing-extensions==4.12.2
|
|
97
95
|
# via anyio
|
|
98
96
|
# via mypy
|
|
99
97
|
# via pydantic
|
|
@@ -19,7 +19,7 @@ certifi==2023.7.22
|
|
|
19
19
|
# via httpx
|
|
20
20
|
distro==1.8.0
|
|
21
21
|
# via spitch
|
|
22
|
-
exceptiongroup==1.
|
|
22
|
+
exceptiongroup==1.2.2
|
|
23
23
|
# via anyio
|
|
24
24
|
h11==0.14.0
|
|
25
25
|
# via httpcore
|
|
@@ -30,15 +30,15 @@ httpx==0.25.2
|
|
|
30
30
|
idna==3.4
|
|
31
31
|
# via anyio
|
|
32
32
|
# via httpx
|
|
33
|
-
pydantic==2.
|
|
33
|
+
pydantic==2.9.2
|
|
34
34
|
# via spitch
|
|
35
|
-
pydantic-core==2.
|
|
35
|
+
pydantic-core==2.23.4
|
|
36
36
|
# via pydantic
|
|
37
37
|
sniffio==1.3.0
|
|
38
38
|
# via anyio
|
|
39
39
|
# via httpx
|
|
40
40
|
# via spitch
|
|
41
|
-
typing-extensions==4.
|
|
41
|
+
typing-extensions==4.12.2
|
|
42
42
|
# via anyio
|
|
43
43
|
# via pydantic
|
|
44
44
|
# via pydantic-core
|
|
@@ -1575,7 +1575,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
|
1575
1575
|
except Exception as err:
|
|
1576
1576
|
log.debug("Encountered Exception", exc_info=True)
|
|
1577
1577
|
|
|
1578
|
-
if
|
|
1578
|
+
if remaining_retries > 0:
|
|
1579
1579
|
return await self._retry_request(
|
|
1580
1580
|
input_options,
|
|
1581
1581
|
cast_to,
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
|
|
4
4
|
from datetime import date, datetime
|
|
5
|
-
from typing_extensions import Self
|
|
5
|
+
from typing_extensions import Self, Literal
|
|
6
6
|
|
|
7
7
|
import pydantic
|
|
8
8
|
from pydantic.fields import FieldInfo
|
|
@@ -133,13 +133,15 @@ def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
|
|
|
133
133
|
def model_dump(
|
|
134
134
|
model: pydantic.BaseModel,
|
|
135
135
|
*,
|
|
136
|
-
exclude: IncEx = None,
|
|
136
|
+
exclude: IncEx | None = None,
|
|
137
137
|
exclude_unset: bool = False,
|
|
138
138
|
exclude_defaults: bool = False,
|
|
139
139
|
warnings: bool = True,
|
|
140
|
+
mode: Literal["json", "python"] = "python",
|
|
140
141
|
) -> dict[str, Any]:
|
|
141
|
-
if PYDANTIC_V2:
|
|
142
|
+
if PYDANTIC_V2 or hasattr(model, "model_dump"):
|
|
142
143
|
return model.model_dump(
|
|
144
|
+
mode=mode,
|
|
143
145
|
exclude=exclude,
|
|
144
146
|
exclude_unset=exclude_unset,
|
|
145
147
|
exclude_defaults=exclude_defaults,
|
|
@@ -37,6 +37,7 @@ from ._utils import (
|
|
|
37
37
|
PropertyInfo,
|
|
38
38
|
is_list,
|
|
39
39
|
is_given,
|
|
40
|
+
json_safe,
|
|
40
41
|
lru_cache,
|
|
41
42
|
is_mapping,
|
|
42
43
|
parse_date,
|
|
@@ -176,7 +177,7 @@ class BaseModel(pydantic.BaseModel):
|
|
|
176
177
|
# Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
|
|
177
178
|
@classmethod
|
|
178
179
|
@override
|
|
179
|
-
def construct(
|
|
180
|
+
def construct( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
180
181
|
cls: Type[ModelT],
|
|
181
182
|
_fields_set: set[str] | None = None,
|
|
182
183
|
**values: object,
|
|
@@ -248,8 +249,8 @@ class BaseModel(pydantic.BaseModel):
|
|
|
248
249
|
self,
|
|
249
250
|
*,
|
|
250
251
|
mode: Literal["json", "python"] | str = "python",
|
|
251
|
-
include: IncEx = None,
|
|
252
|
-
exclude: IncEx = None,
|
|
252
|
+
include: IncEx | None = None,
|
|
253
|
+
exclude: IncEx | None = None,
|
|
253
254
|
by_alias: bool = False,
|
|
254
255
|
exclude_unset: bool = False,
|
|
255
256
|
exclude_defaults: bool = False,
|
|
@@ -279,8 +280,8 @@ class BaseModel(pydantic.BaseModel):
|
|
|
279
280
|
Returns:
|
|
280
281
|
A dictionary representation of the model.
|
|
281
282
|
"""
|
|
282
|
-
if mode
|
|
283
|
-
raise ValueError("mode
|
|
283
|
+
if mode not in {"json", "python"}:
|
|
284
|
+
raise ValueError("mode must be either 'json' or 'python'")
|
|
284
285
|
if round_trip != False:
|
|
285
286
|
raise ValueError("round_trip is only supported in Pydantic v2")
|
|
286
287
|
if warnings != True:
|
|
@@ -289,7 +290,7 @@ class BaseModel(pydantic.BaseModel):
|
|
|
289
290
|
raise ValueError("context is only supported in Pydantic v2")
|
|
290
291
|
if serialize_as_any != False:
|
|
291
292
|
raise ValueError("serialize_as_any is only supported in Pydantic v2")
|
|
292
|
-
|
|
293
|
+
dumped = super().dict( # pyright: ignore[reportDeprecated]
|
|
293
294
|
include=include,
|
|
294
295
|
exclude=exclude,
|
|
295
296
|
by_alias=by_alias,
|
|
@@ -298,13 +299,15 @@ class BaseModel(pydantic.BaseModel):
|
|
|
298
299
|
exclude_none=exclude_none,
|
|
299
300
|
)
|
|
300
301
|
|
|
302
|
+
return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped
|
|
303
|
+
|
|
301
304
|
@override
|
|
302
305
|
def model_dump_json(
|
|
303
306
|
self,
|
|
304
307
|
*,
|
|
305
308
|
indent: int | None = None,
|
|
306
|
-
include: IncEx = None,
|
|
307
|
-
exclude: IncEx = None,
|
|
309
|
+
include: IncEx | None = None,
|
|
310
|
+
exclude: IncEx | None = None,
|
|
308
311
|
by_alias: bool = False,
|
|
309
312
|
exclude_unset: bool = False,
|
|
310
313
|
exclude_defaults: bool = False,
|
|
@@ -16,7 +16,7 @@ from typing import (
|
|
|
16
16
|
Optional,
|
|
17
17
|
Sequence,
|
|
18
18
|
)
|
|
19
|
-
from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
|
|
19
|
+
from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
|
|
20
20
|
|
|
21
21
|
import httpx
|
|
22
22
|
import pydantic
|
|
@@ -193,7 +193,9 @@ StrBytesIntFloat = Union[str, bytes, int, float]
|
|
|
193
193
|
|
|
194
194
|
# Note: copied from Pydantic
|
|
195
195
|
# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49
|
|
196
|
-
IncEx: TypeAlias =
|
|
196
|
+
IncEx: TypeAlias = Union[
|
|
197
|
+
Set[int], Set[str], Mapping[int, Union["IncEx", Literal[True]]], Mapping[str, Union["IncEx", Literal[True]]]
|
|
198
|
+
]
|
|
197
199
|
|
|
198
200
|
PostParser = Callable[[Any], Any]
|
|
199
201
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import asyncio
|
|
5
|
+
import functools
|
|
6
|
+
import contextvars
|
|
7
|
+
from typing import Any, TypeVar, Callable, Awaitable
|
|
8
|
+
from typing_extensions import ParamSpec
|
|
9
|
+
|
|
10
|
+
T_Retval = TypeVar("T_Retval")
|
|
11
|
+
T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if sys.version_info >= (3, 9):
|
|
15
|
+
to_thread = asyncio.to_thread
|
|
16
|
+
else:
|
|
17
|
+
# backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
|
|
18
|
+
# for Python 3.8 support
|
|
19
|
+
async def to_thread(
|
|
20
|
+
func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
|
|
21
|
+
) -> Any:
|
|
22
|
+
"""Asynchronously run function *func* in a separate thread.
|
|
23
|
+
|
|
24
|
+
Any *args and **kwargs supplied for this function are directly passed
|
|
25
|
+
to *func*. Also, the current :class:`contextvars.Context` is propagated,
|
|
26
|
+
allowing context variables from the main thread to be accessed in the
|
|
27
|
+
separate thread.
|
|
28
|
+
|
|
29
|
+
Returns a coroutine that can be awaited to get the eventual result of *func*.
|
|
30
|
+
"""
|
|
31
|
+
loop = asyncio.events.get_running_loop()
|
|
32
|
+
ctx = contextvars.copy_context()
|
|
33
|
+
func_call = functools.partial(ctx.run, func, *args, **kwargs)
|
|
34
|
+
return await loop.run_in_executor(None, func_call)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# inspired by `asyncer`, https://github.com/tiangolo/asyncer
|
|
38
|
+
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
|
|
39
|
+
"""
|
|
40
|
+
Take a blocking function and create an async one that receives the same
|
|
41
|
+
positional and keyword arguments. For python version 3.9 and above, it uses
|
|
42
|
+
asyncio.to_thread to run the function in a separate thread. For python version
|
|
43
|
+
3.8, it uses locally defined copy of the asyncio.to_thread function which was
|
|
44
|
+
introduced in python 3.9.
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
def blocking_func(arg1, arg2, kwarg1=None):
|
|
50
|
+
# blocking code
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Arguments
|
|
58
|
+
|
|
59
|
+
`function`: a blocking regular callable (e.g. a function)
|
|
60
|
+
|
|
61
|
+
## Return
|
|
62
|
+
|
|
63
|
+
An async function that takes the same positional and keyword arguments as the
|
|
64
|
+
original one, that when called runs the same original function in a thread worker
|
|
65
|
+
and returns the result.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
|
|
69
|
+
return await to_thread(function, *args, **kwargs)
|
|
70
|
+
|
|
71
|
+
return wrapper
|
|
@@ -173,6 +173,11 @@ def _transform_recursive(
|
|
|
173
173
|
# Iterable[T]
|
|
174
174
|
or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
|
|
175
175
|
):
|
|
176
|
+
# dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
|
|
177
|
+
# intended as an iterable, so we don't transform it.
|
|
178
|
+
if isinstance(data, dict):
|
|
179
|
+
return cast(object, data)
|
|
180
|
+
|
|
176
181
|
inner_type = extract_type_arg(stripped_type, 0)
|
|
177
182
|
return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
|
|
178
183
|
|
|
@@ -186,7 +191,7 @@ def _transform_recursive(
|
|
|
186
191
|
return data
|
|
187
192
|
|
|
188
193
|
if isinstance(data, pydantic.BaseModel):
|
|
189
|
-
return model_dump(data, exclude_unset=True)
|
|
194
|
+
return model_dump(data, exclude_unset=True, mode="json")
|
|
190
195
|
|
|
191
196
|
annotated_type = _get_annotated_type(annotation)
|
|
192
197
|
if annotated_type is None:
|
|
@@ -311,6 +316,11 @@ async def _async_transform_recursive(
|
|
|
311
316
|
# Iterable[T]
|
|
312
317
|
or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
|
|
313
318
|
):
|
|
319
|
+
# dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
|
|
320
|
+
# intended as an iterable, so we don't transform it.
|
|
321
|
+
if isinstance(data, dict):
|
|
322
|
+
return cast(object, data)
|
|
323
|
+
|
|
314
324
|
inner_type = extract_type_arg(stripped_type, 0)
|
|
315
325
|
return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
|
|
316
326
|
|
|
@@ -324,7 +334,7 @@ async def _async_transform_recursive(
|
|
|
324
334
|
return data
|
|
325
335
|
|
|
326
336
|
if isinstance(data, pydantic.BaseModel):
|
|
327
|
-
return model_dump(data, exclude_unset=True)
|
|
337
|
+
return model_dump(data, exclude_unset=True, mode="json")
|
|
328
338
|
|
|
329
339
|
annotated_type = _get_annotated_type(annotation)
|
|
330
340
|
if annotated_type is None:
|
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
overload,
|
|
17
17
|
)
|
|
18
18
|
from pathlib import Path
|
|
19
|
+
from datetime import date, datetime
|
|
19
20
|
from typing_extensions import TypeGuard
|
|
20
21
|
|
|
21
22
|
import sniffio
|
|
@@ -395,3 +396,19 @@ def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
|
|
|
395
396
|
maxsize=maxsize,
|
|
396
397
|
)
|
|
397
398
|
return cast(Any, wrapper) # type: ignore[no-any-return]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def json_safe(data: object) -> object:
|
|
402
|
+
"""Translates a mapping / sequence recursively in the same fashion
|
|
403
|
+
as `pydantic` v2's `model_dump(mode="json")`.
|
|
404
|
+
"""
|
|
405
|
+
if is_mapping(data):
|
|
406
|
+
return {json_safe(key): json_safe(value) for key, value in data.items()}
|
|
407
|
+
|
|
408
|
+
if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
|
|
409
|
+
return [json_safe(item) for item in data]
|
|
410
|
+
|
|
411
|
+
if isinstance(data, (datetime, date)):
|
|
412
|
+
return data.isoformat()
|
|
413
|
+
|
|
414
|
+
return data
|
|
@@ -62,7 +62,24 @@ class SpeechResource(SyncAPIResource):
|
|
|
62
62
|
*,
|
|
63
63
|
language: Literal["yo", "en", "ha", "ig"],
|
|
64
64
|
text: str,
|
|
65
|
-
voice: Literal[
|
|
65
|
+
voice: Literal[
|
|
66
|
+
"sade",
|
|
67
|
+
"segun",
|
|
68
|
+
"femi",
|
|
69
|
+
"funmi",
|
|
70
|
+
"amina",
|
|
71
|
+
"aliyu",
|
|
72
|
+
"hasan",
|
|
73
|
+
"zainab",
|
|
74
|
+
"ngozi",
|
|
75
|
+
"amara",
|
|
76
|
+
"ebuka",
|
|
77
|
+
"obinna",
|
|
78
|
+
"lucy",
|
|
79
|
+
"lina",
|
|
80
|
+
"john",
|
|
81
|
+
"jude",
|
|
82
|
+
],
|
|
66
83
|
stream: bool | NotGiven = NOT_GIVEN,
|
|
67
84
|
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
|
68
85
|
# The extra values given here take precedence over values defined on the client or passed to this method.
|
|
@@ -181,7 +198,24 @@ class AsyncSpeechResource(AsyncAPIResource):
|
|
|
181
198
|
*,
|
|
182
199
|
language: Literal["yo", "en", "ha", "ig"],
|
|
183
200
|
text: str,
|
|
184
|
-
voice: Literal[
|
|
201
|
+
voice: Literal[
|
|
202
|
+
"sade",
|
|
203
|
+
"segun",
|
|
204
|
+
"femi",
|
|
205
|
+
"funmi",
|
|
206
|
+
"amina",
|
|
207
|
+
"aliyu",
|
|
208
|
+
"hasan",
|
|
209
|
+
"zainab",
|
|
210
|
+
"ngozi",
|
|
211
|
+
"amara",
|
|
212
|
+
"ebuka",
|
|
213
|
+
"obinna",
|
|
214
|
+
"lucy",
|
|
215
|
+
"lina",
|
|
216
|
+
"john",
|
|
217
|
+
"jude",
|
|
218
|
+
],
|
|
185
219
|
stream: bool | NotGiven = NOT_GIVEN,
|
|
186
220
|
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
|
187
221
|
# The extra values given here take precedence over values defined on the client or passed to this method.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Literal, Required, TypedDict
|
|
6
|
+
|
|
7
|
+
__all__ = ["SpeechGenerateParams"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SpeechGenerateParams(TypedDict, total=False):
|
|
11
|
+
language: Required[Literal["yo", "en", "ha", "ig"]]
|
|
12
|
+
|
|
13
|
+
text: Required[str]
|
|
14
|
+
|
|
15
|
+
voice: Required[
|
|
16
|
+
Literal[
|
|
17
|
+
"sade",
|
|
18
|
+
"segun",
|
|
19
|
+
"femi",
|
|
20
|
+
"funmi",
|
|
21
|
+
"amina",
|
|
22
|
+
"aliyu",
|
|
23
|
+
"hasan",
|
|
24
|
+
"zainab",
|
|
25
|
+
"ngozi",
|
|
26
|
+
"amara",
|
|
27
|
+
"ebuka",
|
|
28
|
+
"obinna",
|
|
29
|
+
"lucy",
|
|
30
|
+
"lina",
|
|
31
|
+
"john",
|
|
32
|
+
"jude",
|
|
33
|
+
]
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
stream: bool
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import asyncio
|
|
5
4
|
import logging
|
|
6
5
|
from typing import TYPE_CHECKING, Iterator, AsyncIterator
|
|
7
6
|
|
|
8
7
|
import pytest
|
|
8
|
+
from pytest_asyncio import is_async_test
|
|
9
9
|
|
|
10
10
|
from spitch import Spitch, AsyncSpitch
|
|
11
11
|
|
|
@@ -17,11 +17,13 @@ pytest.register_assert_rewrite("tests.utils")
|
|
|
17
17
|
logging.getLogger("spitch").setLevel(logging.DEBUG)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
# automatically add `pytest.mark.asyncio()` to all of our async tests
|
|
21
|
+
# so we don't have to add that boilerplate everywhere
|
|
22
|
+
def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
|
|
23
|
+
pytest_asyncio_tests = (item for item in items if is_async_test(item))
|
|
24
|
+
session_scope_marker = pytest.mark.asyncio(loop_scope="session")
|
|
25
|
+
for async_test in pytest_asyncio_tests:
|
|
26
|
+
async_test.add_marker(session_scope_marker, append=False)
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
|
|
@@ -4,12 +4,16 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import gc
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
import json
|
|
8
9
|
import asyncio
|
|
9
10
|
import inspect
|
|
11
|
+
import subprocess
|
|
10
12
|
import tracemalloc
|
|
11
13
|
from typing import Any, Union, cast
|
|
14
|
+
from textwrap import dedent
|
|
12
15
|
from unittest import mock
|
|
16
|
+
from typing_extensions import Literal
|
|
13
17
|
|
|
14
18
|
import httpx
|
|
15
19
|
import pytest
|
|
@@ -685,7 +689,7 @@ class TestSpitch:
|
|
|
685
689
|
[3, "", 0.5],
|
|
686
690
|
[2, "", 0.5 * 2.0],
|
|
687
691
|
[1, "", 0.5 * 4.0],
|
|
688
|
-
[-1100, "",
|
|
692
|
+
[-1100, "", 8], # test large number potentially overflowing
|
|
689
693
|
],
|
|
690
694
|
)
|
|
691
695
|
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
|
|
@@ -730,7 +734,14 @@ class TestSpitch:
|
|
|
730
734
|
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
|
|
731
735
|
@mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
|
|
732
736
|
@pytest.mark.respx(base_url=base_url)
|
|
733
|
-
|
|
737
|
+
@pytest.mark.parametrize("failure_mode", ["status", "exception"])
|
|
738
|
+
def test_retries_taken(
|
|
739
|
+
self,
|
|
740
|
+
client: Spitch,
|
|
741
|
+
failures_before_success: int,
|
|
742
|
+
failure_mode: Literal["status", "exception"],
|
|
743
|
+
respx_mock: MockRouter,
|
|
744
|
+
) -> None:
|
|
734
745
|
client = client.with_options(max_retries=4)
|
|
735
746
|
|
|
736
747
|
nb_retries = 0
|
|
@@ -739,6 +750,8 @@ class TestSpitch:
|
|
|
739
750
|
nonlocal nb_retries
|
|
740
751
|
if nb_retries < failures_before_success:
|
|
741
752
|
nb_retries += 1
|
|
753
|
+
if failure_mode == "exception":
|
|
754
|
+
raise RuntimeError("oops")
|
|
742
755
|
return httpx.Response(500)
|
|
743
756
|
return httpx.Response(200)
|
|
744
757
|
|
|
@@ -1455,7 +1468,7 @@ class TestAsyncSpitch:
|
|
|
1455
1468
|
[3, "", 0.5],
|
|
1456
1469
|
[2, "", 0.5 * 2.0],
|
|
1457
1470
|
[1, "", 0.5 * 4.0],
|
|
1458
|
-
[-1100, "",
|
|
1471
|
+
[-1100, "", 8], # test large number potentially overflowing
|
|
1459
1472
|
],
|
|
1460
1473
|
)
|
|
1461
1474
|
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
|
|
@@ -1502,8 +1515,13 @@ class TestAsyncSpitch:
|
|
|
1502
1515
|
@mock.patch("spitch._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
|
|
1503
1516
|
@pytest.mark.respx(base_url=base_url)
|
|
1504
1517
|
@pytest.mark.asyncio
|
|
1518
|
+
@pytest.mark.parametrize("failure_mode", ["status", "exception"])
|
|
1505
1519
|
async def test_retries_taken(
|
|
1506
|
-
self,
|
|
1520
|
+
self,
|
|
1521
|
+
async_client: AsyncSpitch,
|
|
1522
|
+
failures_before_success: int,
|
|
1523
|
+
failure_mode: Literal["status", "exception"],
|
|
1524
|
+
respx_mock: MockRouter,
|
|
1507
1525
|
) -> None:
|
|
1508
1526
|
client = async_client.with_options(max_retries=4)
|
|
1509
1527
|
|
|
@@ -1513,6 +1531,8 @@ class TestAsyncSpitch:
|
|
|
1513
1531
|
nonlocal nb_retries
|
|
1514
1532
|
if nb_retries < failures_before_success:
|
|
1515
1533
|
nb_retries += 1
|
|
1534
|
+
if failure_mode == "exception":
|
|
1535
|
+
raise RuntimeError("oops")
|
|
1516
1536
|
return httpx.Response(500)
|
|
1517
1537
|
return httpx.Response(200)
|
|
1518
1538
|
|
|
@@ -1574,3 +1594,38 @@ class TestAsyncSpitch:
|
|
|
1574
1594
|
)
|
|
1575
1595
|
|
|
1576
1596
|
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
|
|
1597
|
+
|
|
1598
|
+
def test_get_platform(self) -> None:
|
|
1599
|
+
# A previous implementation of asyncify could leave threads unterminated when
|
|
1600
|
+
# used with nest_asyncio.
|
|
1601
|
+
#
|
|
1602
|
+
# Since nest_asyncio.apply() is global and cannot be un-applied, this
|
|
1603
|
+
# test is run in a separate process to avoid affecting other tests.
|
|
1604
|
+
test_code = dedent("""
|
|
1605
|
+
import asyncio
|
|
1606
|
+
import nest_asyncio
|
|
1607
|
+
import threading
|
|
1608
|
+
|
|
1609
|
+
from spitch._utils import asyncify
|
|
1610
|
+
from spitch._base_client import get_platform
|
|
1611
|
+
|
|
1612
|
+
async def test_main() -> None:
|
|
1613
|
+
result = await asyncify(get_platform)()
|
|
1614
|
+
print(result)
|
|
1615
|
+
for thread in threading.enumerate():
|
|
1616
|
+
print(thread.name)
|
|
1617
|
+
|
|
1618
|
+
nest_asyncio.apply()
|
|
1619
|
+
asyncio.run(test_main())
|
|
1620
|
+
""")
|
|
1621
|
+
with subprocess.Popen(
|
|
1622
|
+
[sys.executable, "-c", test_code],
|
|
1623
|
+
text=True,
|
|
1624
|
+
) as process:
|
|
1625
|
+
try:
|
|
1626
|
+
process.wait(2)
|
|
1627
|
+
if process.returncode:
|
|
1628
|
+
raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
|
|
1629
|
+
except subprocess.TimeoutExpired as e:
|
|
1630
|
+
process.kill()
|
|
1631
|
+
raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e
|
|
@@ -520,19 +520,15 @@ def test_to_dict() -> None:
|
|
|
520
520
|
assert m3.to_dict(exclude_none=True) == {}
|
|
521
521
|
assert m3.to_dict(exclude_defaults=True) == {}
|
|
522
522
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
class Model2(BaseModel):
|
|
526
|
-
created_at: datetime
|
|
523
|
+
class Model2(BaseModel):
|
|
524
|
+
created_at: datetime
|
|
527
525
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
else:
|
|
533
|
-
with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"):
|
|
534
|
-
m.to_dict(mode="json")
|
|
526
|
+
time_str = "2024-03-21T11:39:01.275859"
|
|
527
|
+
m4 = Model2.construct(created_at=time_str)
|
|
528
|
+
assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)}
|
|
529
|
+
assert m4.to_dict(mode="json") == {"created_at": time_str}
|
|
535
530
|
|
|
531
|
+
if not PYDANTIC_V2:
|
|
536
532
|
with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
|
|
537
533
|
m.to_dict(warnings=False)
|
|
538
534
|
|
|
@@ -558,9 +554,6 @@ def test_forwards_compat_model_dump_method() -> None:
|
|
|
558
554
|
assert m3.model_dump(exclude_none=True) == {}
|
|
559
555
|
|
|
560
556
|
if not PYDANTIC_V2:
|
|
561
|
-
with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"):
|
|
562
|
-
m.model_dump(mode="json")
|
|
563
|
-
|
|
564
557
|
with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
|
|
565
558
|
m.model_dump(round_trip=True)
|
|
566
559
|
|
|
@@ -177,17 +177,32 @@ class DateDict(TypedDict, total=False):
|
|
|
177
177
|
foo: Annotated[date, PropertyInfo(format="iso8601")]
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
class DatetimeModel(BaseModel):
|
|
181
|
+
foo: datetime
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DateModel(BaseModel):
|
|
185
|
+
foo: Optional[date]
|
|
186
|
+
|
|
187
|
+
|
|
180
188
|
@parametrize
|
|
181
189
|
@pytest.mark.asyncio
|
|
182
190
|
async def test_iso8601_format(use_async: bool) -> None:
|
|
183
191
|
dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
|
|
192
|
+
tz = "Z" if PYDANTIC_V2 else "+00:00"
|
|
184
193
|
assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap]
|
|
194
|
+
assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap]
|
|
185
195
|
|
|
186
196
|
dt = dt.replace(tzinfo=None)
|
|
187
197
|
assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap]
|
|
198
|
+
assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap]
|
|
188
199
|
|
|
189
200
|
assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap]
|
|
201
|
+
assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore
|
|
190
202
|
assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap]
|
|
203
|
+
assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == {
|
|
204
|
+
"foo": "2023-02-23"
|
|
205
|
+
} # type: ignore[comparison-overlap]
|
|
191
206
|
|
|
192
207
|
|
|
193
208
|
@parametrize
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import functools
|
|
4
|
-
from typing import TypeVar, Callable, Awaitable
|
|
5
|
-
from typing_extensions import ParamSpec
|
|
6
|
-
|
|
7
|
-
import anyio
|
|
8
|
-
import anyio.to_thread
|
|
9
|
-
|
|
10
|
-
from ._reflection import function_has_argument
|
|
11
|
-
|
|
12
|
-
T_Retval = TypeVar("T_Retval")
|
|
13
|
-
T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# copied from `asyncer`, https://github.com/tiangolo/asyncer
|
|
17
|
-
def asyncify(
|
|
18
|
-
function: Callable[T_ParamSpec, T_Retval],
|
|
19
|
-
*,
|
|
20
|
-
cancellable: bool = False,
|
|
21
|
-
limiter: anyio.CapacityLimiter | None = None,
|
|
22
|
-
) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
|
|
23
|
-
"""
|
|
24
|
-
Take a blocking function and create an async one that receives the same
|
|
25
|
-
positional and keyword arguments, and that when called, calls the original function
|
|
26
|
-
in a worker thread using `anyio.to_thread.run_sync()`. Internally,
|
|
27
|
-
`asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports
|
|
28
|
-
keyword arguments additional to positional arguments and it adds better support for
|
|
29
|
-
autocompletion and inline errors for the arguments of the function called and the
|
|
30
|
-
return value.
|
|
31
|
-
|
|
32
|
-
If the `cancellable` option is enabled and the task waiting for its completion is
|
|
33
|
-
cancelled, the thread will still run its course but its return value (or any raised
|
|
34
|
-
exception) will be ignored.
|
|
35
|
-
|
|
36
|
-
Use it like this:
|
|
37
|
-
|
|
38
|
-
```Python
|
|
39
|
-
def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str:
|
|
40
|
-
# Do work
|
|
41
|
-
return "Some result"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b")
|
|
45
|
-
print(result)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Arguments
|
|
49
|
-
|
|
50
|
-
`function`: a blocking regular callable (e.g. a function)
|
|
51
|
-
`cancellable`: `True` to allow cancellation of the operation
|
|
52
|
-
`limiter`: capacity limiter to use to limit the total amount of threads running
|
|
53
|
-
(if omitted, the default limiter is used)
|
|
54
|
-
|
|
55
|
-
## Return
|
|
56
|
-
|
|
57
|
-
An async function that takes the same positional and keyword arguments as the
|
|
58
|
-
original one, that when called runs the same original function in a thread worker
|
|
59
|
-
and returns the result.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
|
|
63
|
-
partial_f = functools.partial(function, *args, **kwargs)
|
|
64
|
-
|
|
65
|
-
# In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old
|
|
66
|
-
# `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid
|
|
67
|
-
# surfacing deprecation warnings.
|
|
68
|
-
if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"):
|
|
69
|
-
return await anyio.to_thread.run_sync(
|
|
70
|
-
partial_f,
|
|
71
|
-
abandon_on_cancel=cancellable,
|
|
72
|
-
limiter=limiter,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return await anyio.to_thread.run_sync(
|
|
76
|
-
partial_f,
|
|
77
|
-
cancellable=cancellable,
|
|
78
|
-
limiter=limiter,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
return wrapper
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing_extensions import Literal, Required, TypedDict
|
|
6
|
-
|
|
7
|
-
__all__ = ["SpeechGenerateParams"]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SpeechGenerateParams(TypedDict, total=False):
|
|
11
|
-
language: Required[Literal["yo", "en", "ha", "ig"]]
|
|
12
|
-
|
|
13
|
-
text: Required[str]
|
|
14
|
-
|
|
15
|
-
voice: Required[Literal["sade", "segun", "femi", "funmi", "amina", "aliyu", "hasan", "zainab"]]
|
|
16
|
-
|
|
17
|
-
stream: bool
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from typing_extensions import Literal, Required, TypedDict
|
|
7
|
-
|
|
8
|
-
from .._types import FileTypes
|
|
9
|
-
|
|
10
|
-
__all__ = ["SpeechTranscibeParams"]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SpeechTranscibeParams(TypedDict, total=False):
|
|
14
|
-
language: Required[Literal["yo", "en", "ha", "ig"]]
|
|
15
|
-
|
|
16
|
-
content: Optional[FileTypes]
|
|
17
|
-
|
|
18
|
-
url: Optional[str]
|
|
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
|
|
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
|