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.

Files changed (78) hide show
  1. spitch-1.15.1/.release-please-manifest.json +3 -0
  2. {spitch-1.14.0 → spitch-1.15.1}/CHANGELOG.md +23 -0
  3. {spitch-1.14.0 → spitch-1.15.1}/PKG-INFO +7 -11
  4. {spitch-1.14.0 → spitch-1.15.1}/README.md +4 -6
  5. {spitch-1.14.0 → spitch-1.15.1}/examples/example.py +1 -1
  6. {spitch-1.14.0 → spitch-1.15.1}/pyproject.toml +4 -9
  7. {spitch-1.14.0 → spitch-1.15.1}/requirements-dev.lock +11 -13
  8. {spitch-1.14.0 → spitch-1.15.1}/requirements.lock +4 -4
  9. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_base_client.py +1 -1
  10. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_compat.py +5 -3
  11. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_models.py +11 -8
  12. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_types.py +4 -2
  13. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/__init__.py +1 -0
  14. spitch-1.15.1/src/spitch/_utils/_sync.py +71 -0
  15. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_transform.py +12 -2
  16. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_utils.py +17 -0
  17. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_version.py +1 -1
  18. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/speech.py +36 -2
  19. spitch-1.15.1/src/spitch/types/speech_generate_params.py +36 -0
  20. {spitch-1.14.0 → spitch-1.15.1}/tests/conftest.py +8 -6
  21. {spitch-1.14.0 → spitch-1.15.1}/tests/test_client.py +59 -4
  22. {spitch-1.14.0 → spitch-1.15.1}/tests/test_models.py +7 -14
  23. {spitch-1.14.0 → spitch-1.15.1}/tests/test_transform.py +15 -0
  24. spitch-1.14.0/.release-please-manifest.json +0 -3
  25. spitch-1.14.0/src/spitch/_utils/_sync.py +0 -81
  26. spitch-1.14.0/src/spitch/types/speech_generate_params.py +0 -17
  27. spitch-1.14.0/src/spitch/types/speech_transcibe_params.py +0 -18
  28. {spitch-1.14.0 → spitch-1.15.1}/.gitignore +0 -0
  29. {spitch-1.14.0 → spitch-1.15.1}/CONTRIBUTING.md +0 -0
  30. {spitch-1.14.0 → spitch-1.15.1}/LICENSE +0 -0
  31. {spitch-1.14.0 → spitch-1.15.1}/SECURITY.md +0 -0
  32. {spitch-1.14.0 → spitch-1.15.1}/api.md +0 -0
  33. {spitch-1.14.0 → spitch-1.15.1}/bin/check-release-environment +0 -0
  34. {spitch-1.14.0 → spitch-1.15.1}/bin/publish-pypi +0 -0
  35. {spitch-1.14.0 → spitch-1.15.1}/examples/.keep +0 -0
  36. {spitch-1.14.0 → spitch-1.15.1}/mypy.ini +0 -0
  37. {spitch-1.14.0 → spitch-1.15.1}/noxfile.py +0 -0
  38. {spitch-1.14.0 → spitch-1.15.1}/release-please-config.json +0 -0
  39. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/__init__.py +0 -0
  40. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_client.py +0 -0
  41. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_constants.py +0 -0
  42. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_exceptions.py +0 -0
  43. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_files.py +0 -0
  44. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_qs.py +0 -0
  45. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_resource.py +0 -0
  46. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_response.py +0 -0
  47. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_streaming.py +0 -0
  48. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_logs.py +0 -0
  49. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_proxy.py +0 -0
  50. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_reflection.py +0 -0
  51. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_streams.py +0 -0
  52. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/_utils/_typing.py +0 -0
  53. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/lib/.keep +0 -0
  54. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/py.typed +0 -0
  55. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/__init__.py +0 -0
  56. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/resources/text.py +0 -0
  57. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/__init__.py +0 -0
  58. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/speech_transcribe_params.py +0 -0
  59. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/speech_transcribe_response.py +0 -0
  60. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_tone_mark_params.py +0 -0
  61. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_tone_mark_response.py +0 -0
  62. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_translate_params.py +0 -0
  63. {spitch-1.14.0 → spitch-1.15.1}/src/spitch/types/text_translate_response.py +0 -0
  64. {spitch-1.14.0 → spitch-1.15.1}/tests/__init__.py +0 -0
  65. {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/__init__.py +0 -0
  66. {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/test_speech.py +0 -0
  67. {spitch-1.14.0 → spitch-1.15.1}/tests/api_resources/test_text.py +0 -0
  68. {spitch-1.14.0 → spitch-1.15.1}/tests/sample_file.txt +0 -0
  69. {spitch-1.14.0 → spitch-1.15.1}/tests/test_deepcopy.py +0 -0
  70. {spitch-1.14.0 → spitch-1.15.1}/tests/test_extract_files.py +0 -0
  71. {spitch-1.14.0 → spitch-1.15.1}/tests/test_files.py +0 -0
  72. {spitch-1.14.0 → spitch-1.15.1}/tests/test_qs.py +0 -0
  73. {spitch-1.14.0 → spitch-1.15.1}/tests/test_required_args.py +0 -0
  74. {spitch-1.14.0 → spitch-1.15.1}/tests/test_response.py +0 -0
  75. {spitch-1.14.0 → spitch-1.15.1}/tests/test_streaming.py +0 -0
  76. {spitch-1.14.0 → spitch-1.15.1}/tests/test_utils/test_proxy.py +0 -0
  77. {spitch-1.14.0 → spitch-1.15.1}/tests/test_utils/test_typing.py +0 -0
  78. {spitch-1.14.0 → spitch-1.15.1}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.15.1"
3
+ }
@@ -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.14.0
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-Expression: Apache-2.0
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.7
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
  [![PyPI version](https://img.shields.io/pypi/v/spitch.svg)](https://pypi.org/project/spitch/)
38
36
 
39
- The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.7+
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.7 or higher.
373
+ Python 3.8 or higher.
378
374
 
379
375
  ## Contributing
380
376
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/spitch.svg)](https://pypi.org/project/spitch/)
4
4
 
5
- The Spitch Python library provides convenient access to the Spitch REST API from any Python 3.7+
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.7 or higher.
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.transcibe(language="yo", content=speech_file_path)
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.14.0"
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.7"
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.7"
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.1.3
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.11.2
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.3.0
64
- # via pytest
65
- py==1.11.0
63
+ pluggy==1.5.0
66
64
  # via pytest
67
- pydantic==2.7.1
65
+ pydantic==2.9.2
68
66
  # via spitch
69
- pydantic-core==2.18.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==7.1.1
72
+ pytest==8.3.3
75
73
  # via pytest-asyncio
76
- pytest-asyncio==0.21.1
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.1
91
+ tomli==2.0.2
94
92
  # via mypy
95
93
  # via pytest
96
- typing-extensions==4.8.0
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.1.3
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.7.1
33
+ pydantic==2.9.2
34
34
  # via spitch
35
- pydantic-core==2.18.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.8.0
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 retries_taken > 0:
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 != "python":
283
- raise ValueError("mode is only supported in Pydantic v2")
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
- return super().dict( # pyright: ignore[reportDeprecated]
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 = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None"
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
 
@@ -6,6 +6,7 @@ from ._utils import (
6
6
  is_list as is_list,
7
7
  is_given as is_given,
8
8
  is_tuple as is_tuple,
9
+ json_safe as json_safe,
9
10
  lru_cache as lru_cache,
10
11
  is_mapping as is_mapping,
11
12
  is_tuple_t as is_tuple_t,
@@ -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
@@ -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.14.0" # x-release-please-version
4
+ __version__ = "1.15.1" # x-release-please-version
@@ -62,7 +62,24 @@ class SpeechResource(SyncAPIResource):
62
62
  *,
63
63
  language: Literal["yo", "en", "ha", "ig"],
64
64
  text: str,
65
- voice: Literal["sade", "segun", "femi", "funmi", "amina", "aliyu", "hasan", "zainab"],
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["sade", "segun", "femi", "funmi", "amina", "aliyu", "hasan", "zainab"],
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
- @pytest.fixture(scope="session")
21
- def event_loop() -> Iterator[asyncio.AbstractEventLoop]:
22
- loop = asyncio.new_event_loop()
23
- yield loop
24
- loop.close()
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, "", 7.8], # test large number potentially overflowing
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
- def test_retries_taken(self, client: Spitch, failures_before_success: int, respx_mock: MockRouter) -> None:
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, "", 7.8], # test large number potentially overflowing
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, async_client: AsyncSpitch, failures_before_success: int, respx_mock: MockRouter
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
- if PYDANTIC_V2:
524
-
525
- class Model2(BaseModel):
526
- created_at: datetime
523
+ class Model2(BaseModel):
524
+ created_at: datetime
527
525
 
528
- time_str = "2024-03-21T11:39:01.275859"
529
- m4 = Model2.construct(created_at=time_str)
530
- assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)}
531
- assert m4.to_dict(mode="json") == {"created_at": time_str}
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,3 +0,0 @@
1
- {
2
- ".": "1.14.0"
3
- }
@@ -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