From 2eaa7dbb883d8ae3ecf90da45dce26252a996f62 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:22:24 +0000
Subject: [PATCH 01/17] fix: ensure streams are always closed
---
src/spitch/_streaming.py | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/spitch/_streaming.py b/src/spitch/_streaming.py
index feccb04..976c511 100644
--- a/src/spitch/_streaming.py
+++ b/src/spitch/_streaming.py
@@ -54,11 +54,12 @@ def __stream__(self) -> Iterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- response.close()
+ try:
+ for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ response.close()
def __enter__(self) -> Self:
return self
@@ -117,11 +118,12 @@ async def __stream__(self) -> AsyncIterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- async for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- await response.aclose()
+ try:
+ async for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ await response.aclose()
async def __aenter__(self) -> Self:
return self
From ebbc73f41c762dc2adcbb3013ea695fdb3c50e2d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:23:37 +0000
Subject: [PATCH 02/17] chore(deps): mypy 1.18.1 has a regression, pin to 1.17
---
pyproject.toml | 2 +-
requirements-dev.lock | 4 +++-
requirements.lock | 8 ++++----
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index cb8bcd7..a986f76 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,7 +47,7 @@ managed = true
# version pins are in requirements-dev.lock
dev-dependencies = [
"pyright==1.1.399",
- "mypy",
+ "mypy==1.17",
"respx",
"pytest",
"pytest-asyncio",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index fd8463b..00481a0 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -72,7 +72,7 @@ mdurl==0.1.2
multidict==6.5.0
# via aiohttp
# via yarl
-mypy==1.14.1
+mypy==1.17.0
mypy-extensions==1.0.0
# via mypy
nodeenv==1.8.0
@@ -81,6 +81,8 @@ nox==2023.4.22
packaging==23.2
# via nox
# via pytest
+pathspec==0.12.1
+ # via mypy
platformdirs==3.11.0
# via virtualenv
pluggy==1.5.0
diff --git a/requirements.lock b/requirements.lock
index e137eed..5d9c3fc 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -55,22 +55,22 @@ multidict==6.5.0
propcache==0.3.2
# via aiohttp
# via yarl
-pydantic==2.11.9
+pydantic==2.12.5
# via spitch
-pydantic-core==2.33.2
+pydantic-core==2.41.5
# via pydantic
sniffio==1.3.0
# via anyio
# via httpx
# via spitch
-typing-extensions==4.12.2
+typing-extensions==4.15.0
# via anyio
# via multidict
# via pydantic
# via pydantic-core
# via spitch
# via typing-inspection
-typing-inspection==0.4.1
+typing-inspection==0.4.2
# via pydantic
yarl==1.20.0
# via aiohttp
From 442785c97d9cb3ccee5403666a4cdbbf00338cea Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 3 Dec 2025 05:11:42 +0000
Subject: [PATCH 03/17] chore: update lockfile
---
pyproject.toml | 12 +++--
requirements-dev.lock | 109 +++++++++++++++++++++++-------------------
requirements.lock | 32 ++++++-------
3 files changed, 82 insertions(+), 71 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index a986f76..392d40e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,15 +7,17 @@ license = "Apache-2.0"
authors = [
{ name = "Spitch", email = "developer@spitch.app" },
]
+
dependencies = [
- "httpx>=0.23.0, <1",
- "pydantic>=1.9.0, <3",
+ "httpx>=0.23.0, <1",
+ "pydantic>=1.9.0, <3",
"typing-extensions>=4.7, <5",
- "anyio>=3.5.0, <5",
- "distro>=1.7.0, <2",
- "sniffio",
+ "anyio>=3.5.0, <5",
+ "distro>=1.7.0, <2",
+ "sniffio",
"cached-property; python_version < '3.8'",
]
+
requires-python = ">= 3.9"
classifiers = [
"Typing :: Typed",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 00481a0..9badb5b 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -12,40 +12,45 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.12.13
+aiohttp==3.13.2
# via httpx-aiohttp
# via spitch
-aiosignal==1.3.2
+aiosignal==1.4.0
# via aiohttp
-annotated-types==0.6.0
+annotated-types==0.7.0
# via pydantic
-anyio==4.4.0
+anyio==4.12.0
# via httpx
# via spitch
-argcomplete==3.1.2
+argcomplete==3.6.3
# via nox
async-timeout==5.0.1
# via aiohttp
-attrs==25.3.0
+attrs==25.4.0
# via aiohttp
-certifi==2023.7.22
+ # via nox
+backports-asyncio-runner==1.2.0
+ # via pytest-asyncio
+certifi==2025.11.12
# via httpcore
# via httpx
-colorlog==6.7.0
+colorlog==6.10.1
+ # via nox
+dependency-groups==1.3.1
# via nox
-dirty-equals==0.6.0
-distlib==0.3.7
+dirty-equals==0.11
+distlib==0.4.0
# via virtualenv
-distro==1.8.0
+distro==1.9.0
# via spitch
-exceptiongroup==1.2.2
+exceptiongroup==1.3.1
# via anyio
# via pytest
-execnet==2.1.1
+execnet==2.1.2
# via pytest-xdist
-filelock==3.12.4
+filelock==3.19.1
# via virtualenv
-frozenlist==1.7.0
+frozenlist==1.8.0
# via aiohttp
# via aiosignal
h11==0.16.0
@@ -58,83 +63,87 @@ httpx==0.27.2
# via spitch
httpx-aiohttp==0.1.9
# via spitch
-idna==3.4
+humanize==4.13.0
+ # via nox
+idna==3.11
# via anyio
# via httpx
# via yarl
-importlib-metadata==7.0.0
-iniconfig==2.0.0
+importlib-metadata==8.7.0
+iniconfig==2.1.0
# via pytest
markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
-multidict==6.5.0
+multidict==6.7.0
# via aiohttp
# via yarl
mypy==1.17.0
-mypy-extensions==1.0.0
+mypy-extensions==1.1.0
# via mypy
-nodeenv==1.8.0
+nodeenv==1.9.1
# via pyright
-nox==2023.4.22
-packaging==23.2
+nox==2025.11.12
+packaging==25.0
+ # via dependency-groups
# via nox
# via pytest
pathspec==0.12.1
# via mypy
-platformdirs==3.11.0
+platformdirs==4.4.0
# via virtualenv
-pluggy==1.5.0
+pluggy==1.6.0
# via pytest
-propcache==0.3.2
+propcache==0.4.1
# via aiohttp
# via yarl
-pydantic==2.11.9
+pydantic==2.12.5
# via spitch
-pydantic-core==2.33.2
+pydantic-core==2.41.5
# via pydantic
-pygments==2.18.0
+pygments==2.19.2
+ # via pytest
# via rich
pyright==1.1.399
-pytest==8.3.3
+pytest==8.4.2
# via pytest-asyncio
# via pytest-xdist
-pytest-asyncio==0.24.0
-pytest-xdist==3.7.0
-python-dateutil==2.8.2
+pytest-asyncio==1.2.0
+pytest-xdist==3.8.0
+python-dateutil==2.9.0.post0
# via time-machine
-pytz==2023.3.post1
- # via dirty-equals
respx==0.22.0
-rich==13.7.1
-ruff==0.9.4
-setuptools==68.2.2
- # via nodeenv
-six==1.16.0
+rich==14.2.0
+ruff==0.14.7
+six==1.17.0
# via python-dateutil
-sniffio==1.3.0
- # via anyio
- # via httpx
+sniffio==1.3.1
# via spitch
-time-machine==2.9.0
-tomli==2.0.2
+time-machine==2.19.0
+tomli==2.3.0
+ # via dependency-groups
# via mypy
+ # via nox
# via pytest
-typing-extensions==4.12.2
+typing-extensions==4.15.0
+ # via aiosignal
# via anyio
+ # via exceptiongroup
# via multidict
# via mypy
# via pydantic
# via pydantic-core
# via pyright
+ # via pytest-asyncio
# via spitch
# via typing-inspection
-typing-inspection==0.4.1
+ # via virtualenv
+typing-inspection==0.4.2
# via pydantic
-virtualenv==20.24.5
+virtualenv==20.35.4
# via nox
-yarl==1.20.1
+yarl==1.22.0
# via aiohttp
-zipp==3.17.0
+zipp==3.23.0
# via importlib-metadata
diff --git a/requirements.lock b/requirements.lock
index 5d9c3fc..7d1ca08 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -12,28 +12,28 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.12.13
+aiohttp==3.13.2
# via httpx-aiohttp
# via spitch
-aiosignal==1.3.2
+aiosignal==1.4.0
# via aiohttp
-annotated-types==0.6.0
+annotated-types==0.7.0
# via pydantic
-anyio==4.4.0
+anyio==4.12.0
# via httpx
# via spitch
async-timeout==5.0.1
# via aiohttp
-attrs==25.3.0
+attrs==25.4.0
# via aiohttp
-certifi==2023.7.22
+certifi==2025.11.12
# via httpcore
# via httpx
-distro==1.8.0
+distro==1.9.0
# via spitch
-exceptiongroup==1.2.2
+exceptiongroup==1.3.1
# via anyio
-frozenlist==1.7.0
+frozenlist==1.8.0
# via aiohttp
# via aiosignal
h11==0.16.0
@@ -45,26 +45,26 @@ httpx==0.27.2
# via spitch
httpx-aiohttp==0.1.9
# via spitch
-idna==3.4
+idna==3.11
# via anyio
# via httpx
# via yarl
-multidict==6.5.0
+multidict==6.7.0
# via aiohttp
# via yarl
-propcache==0.3.2
+propcache==0.4.1
# via aiohttp
# via yarl
pydantic==2.12.5
# via spitch
pydantic-core==2.41.5
# via pydantic
-sniffio==1.3.0
- # via anyio
- # via httpx
+sniffio==1.3.1
# via spitch
typing-extensions==4.15.0
+ # via aiosignal
# via anyio
+ # via exceptiongroup
# via multidict
# via pydantic
# via pydantic-core
@@ -72,5 +72,5 @@ typing-extensions==4.15.0
# via typing-inspection
typing-inspection==0.4.2
# via pydantic
-yarl==1.20.0
+yarl==1.22.0
# via aiohttp
From e74350e037dc2136577aa758e493e0261ce70166 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 3 Dec 2025 05:20:01 +0000
Subject: [PATCH 04/17] chore(docs): use environment variables for
authentication in code snippets
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 45e7ef0..944ff82 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,7 @@ pip install spitch[aiohttp]
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
```python
+import os
import asyncio
from spitch import DefaultAioHttpClient
from spitch import AsyncSpitch
@@ -92,7 +93,7 @@ from spitch import AsyncSpitch
async def main() -> None:
async with AsyncSpitch(
- api_key="My API Key",
+ api_key=os.environ.get("SPITCH_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
response = await client.speech.generate(
From 49b39447feb57724b18dbabd08da07ff6fe10164 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 04:25:03 +0000
Subject: [PATCH 05/17] fix(types): allow pyright to infer TypedDict types
within SequenceNotStr
---
src/spitch/_types.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/spitch/_types.py b/src/spitch/_types.py
index c3dccf9..fcc62ee 100644
--- a/src/spitch/_types.py
+++ b/src/spitch/_types.py
@@ -245,6 +245,9 @@ class HttpxSendArgs(TypedDict, total=False):
if TYPE_CHECKING:
# This works because str.__contains__ does not accept object (either in typeshed or at runtime)
# https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
+ #
+ # Note: index() and count() methods are intentionally omitted to allow pyright to properly
+ # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
class SequenceNotStr(Protocol[_T_co]):
@overload
def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
@@ -253,8 +256,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
def __contains__(self, value: object, /) -> bool: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_T_co]: ...
- def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ...
- def count(self, value: Any, /) -> int: ...
def __reversed__(self) -> Iterator[_T_co]: ...
else:
# just point this to a normal `Sequence` at runtime to avoid having to special case
From 5706a112946f0c294388455c32cf5c1e1bad270a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 04:27:14 +0000
Subject: [PATCH 06/17] chore: add missing docstrings
---
src/spitch/types/diacritics.py | 9 +++++++++
src/spitch/types/file.py | 8 ++++++++
src/spitch/types/file_download_response.py | 8 ++++++++
src/spitch/types/file_usage.py | 10 ++++++++++
src/spitch/types/files.py | 7 +++++++
src/spitch/types/job.py | 2 ++
src/spitch/types/jobs.py | 7 +++++++
src/spitch/types/translation.py | 8 ++++++++
8 files changed, 59 insertions(+)
diff --git a/src/spitch/types/diacritics.py b/src/spitch/types/diacritics.py
index e9b1e07..0f3a044 100644
--- a/src/spitch/types/diacritics.py
+++ b/src/spitch/types/diacritics.py
@@ -6,6 +6,15 @@
class Diacritics(BaseModel):
+ """
+ Response model for text diacritization requests.
+
+ Attributes:
+ request_id: Unique identifier for the diacritization request
+ text: Text with added diacritical marks for proper pronunciation
+ language: Language code for the diacritized text
+ """
+
request_id: str
text: str
diff --git a/src/spitch/types/file.py b/src/spitch/types/file.py
index 86a1ab2..39e03e5 100644
--- a/src/spitch/types/file.py
+++ b/src/spitch/types/file.py
@@ -10,6 +10,14 @@
class File(BaseModel):
+ """
+ description of a file.
+ Attributes:
+ file_id: unique identifier for the file.
+ status: status of the file, `processing` or `ready`
+ original_name: original name of the file. If the file was uploaded via API
+ """
+
category: Optional[str] = None
content_type: Optional[str] = None
diff --git a/src/spitch/types/file_download_response.py b/src/spitch/types/file_download_response.py
index 734ab58..c9e58d0 100644
--- a/src/spitch/types/file_download_response.py
+++ b/src/spitch/types/file_download_response.py
@@ -8,6 +8,14 @@
class FileDownloadResponse(BaseModel):
+ """Response model for file download URLs.
+
+ Attributes:
+ file_id: Unique identifier for the file
+ url: Pre-signed download URL
+ expires_at: When the download URL expires
+ """
+
expires_at: datetime
file_id: str
diff --git a/src/spitch/types/file_usage.py b/src/spitch/types/file_usage.py
index 588b779..93cb866 100644
--- a/src/spitch/types/file_usage.py
+++ b/src/spitch/types/file_usage.py
@@ -6,6 +6,16 @@
class FileUsage(BaseModel):
+ """Storage usage information for an organization.
+
+ Attributes:
+ total: Human-readable total storage limit
+ used: Human-readable used storage amount
+ total_bytes: Total storage limit in bytes
+ used_bytes: Used storage amount in bytes
+ num_files: Number of files stored
+ """
+
num_files: int
total: str
diff --git a/src/spitch/types/files.py b/src/spitch/types/files.py
index 1a27afd..6328733 100644
--- a/src/spitch/types/files.py
+++ b/src/spitch/types/files.py
@@ -9,6 +9,13 @@
class Files(BaseModel):
+ """Response model for listing files.
+
+ Attributes:
+ items: List of file metadata responses
+ next_cursor: Optional cursor for pagination to get next page of results
+ """
+
items: List[File]
next_cursor: Optional[str] = None
diff --git a/src/spitch/types/job.py b/src/spitch/types/job.py
index 6b7ff69..da92372 100644
--- a/src/spitch/types/job.py
+++ b/src/spitch/types/job.py
@@ -8,6 +8,8 @@
class Job(BaseModel):
+ """Metadata model for job metadata."""
+
created_by: str
due_date: datetime
diff --git a/src/spitch/types/jobs.py b/src/spitch/types/jobs.py
index 83f5731..7df3100 100644
--- a/src/spitch/types/jobs.py
+++ b/src/spitch/types/jobs.py
@@ -9,6 +9,13 @@
class Jobs(BaseModel):
+ """Response model for listing jobs.
+
+ Attributes:
+ items: list of job metadata responses
+ next_cursor: Optional cursor for pagination to get next page of results
+ """
+
items: List[Job]
next_cursor: Optional[str] = None
diff --git a/src/spitch/types/translation.py b/src/spitch/types/translation.py
index 47809c8..f5588ac 100644
--- a/src/spitch/types/translation.py
+++ b/src/spitch/types/translation.py
@@ -9,6 +9,14 @@
class Translation(BaseModel):
+ """Translation result model.
+
+ Attributes:
+ request_id (UUID): Unique ID for this request.
+ text: translated text.
+ due_date: used when model is `human`. the date you can expect the translation to be delivered
+ """
+
request_id: str
text: str
From d52ec358ed429f302ff0c035d1b8b94cbe24e3e0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 16 Dec 2025 04:17:43 +0000
Subject: [PATCH 07/17] chore(internal): add missing files argument to base
client
---
src/spitch/_base_client.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/spitch/_base_client.py b/src/spitch/_base_client.py
index 09bf821..c110dc0 100644
--- a/src/spitch/_base_client.py
+++ b/src/spitch/_base_client.py
@@ -1247,9 +1247,12 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ )
return self.request(cast_to, opts)
def put(
@@ -1771,9 +1774,12 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ )
return await self.request(cast_to, opts)
async def put(
From 8b385dffc6d34d7cbda6ba197d08637c98a6af37 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 17 Dec 2025 05:47:14 +0000
Subject: [PATCH 08/17] chore: speedup initial import
---
src/spitch/_client.py | 229 ++++++++++++++++++++++++++++++++++--------
1 file changed, 185 insertions(+), 44 deletions(-)
diff --git a/src/spitch/_client.py b/src/spitch/_client.py
index e8fa1dc..4e5d00d 100644
--- a/src/spitch/_client.py
+++ b/src/spitch/_client.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import os
-from typing import Any, Mapping
+from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, override
import httpx
@@ -20,8 +20,8 @@
not_given,
)
from ._utils import is_given, get_async_library
+from ._compat import cached_property
from ._version import __version__
-from .resources import jobs, text, files, speech
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import SpitchError, APIStatusError
from ._base_client import (
@@ -30,6 +30,13 @@
AsyncAPIClient,
)
+if TYPE_CHECKING:
+ from .resources import jobs, text, files, speech
+ from .resources.jobs import JobsResource, AsyncJobsResource
+ from .resources.text import TextResource, AsyncTextResource
+ from .resources.files import FilesResource, AsyncFilesResource
+ from .resources.speech import SpeechResource, AsyncSpeechResource
+
__all__ = [
"Timeout",
"Transport",
@@ -44,13 +51,6 @@
class Spitch(SyncAPIClient):
- speech: speech.SpeechResource
- text: text.TextResource
- files: files.FilesResource
- jobs: jobs.JobsResource
- with_raw_response: SpitchWithRawResponse
- with_streaming_response: SpitchWithStreamedResponse
-
# client options
api_key: str
@@ -105,12 +105,38 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.speech = speech.SpeechResource(self)
- self.text = text.TextResource(self)
- self.files = files.FilesResource(self)
- self.jobs = jobs.JobsResource(self)
- self.with_raw_response = SpitchWithRawResponse(self)
- self.with_streaming_response = SpitchWithStreamedResponse(self)
+ @cached_property
+ def speech(self) -> SpeechResource:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import SpeechResource
+
+ return SpeechResource(self)
+
+ @cached_property
+ def text(self) -> TextResource:
+ from .resources.text import TextResource
+
+ return TextResource(self)
+
+ @cached_property
+ def files(self) -> FilesResource:
+ from .resources.files import FilesResource
+
+ return FilesResource(self)
+
+ @cached_property
+ def jobs(self) -> JobsResource:
+ from .resources.jobs import JobsResource
+
+ return JobsResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> SpitchWithRawResponse:
+ return SpitchWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SpitchWithStreamedResponse:
+ return SpitchWithStreamedResponse(self)
@property
@override
@@ -218,13 +244,6 @@ def _make_status_error(
class AsyncSpitch(AsyncAPIClient):
- speech: speech.AsyncSpeechResource
- text: text.AsyncTextResource
- files: files.AsyncFilesResource
- jobs: jobs.AsyncJobsResource
- with_raw_response: AsyncSpitchWithRawResponse
- with_streaming_response: AsyncSpitchWithStreamedResponse
-
# client options
api_key: str
@@ -279,12 +298,38 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.speech = speech.AsyncSpeechResource(self)
- self.text = text.AsyncTextResource(self)
- self.files = files.AsyncFilesResource(self)
- self.jobs = jobs.AsyncJobsResource(self)
- self.with_raw_response = AsyncSpitchWithRawResponse(self)
- self.with_streaming_response = AsyncSpitchWithStreamedResponse(self)
+ @cached_property
+ def speech(self) -> AsyncSpeechResource:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import AsyncSpeechResource
+
+ return AsyncSpeechResource(self)
+
+ @cached_property
+ def text(self) -> AsyncTextResource:
+ from .resources.text import AsyncTextResource
+
+ return AsyncTextResource(self)
+
+ @cached_property
+ def files(self) -> AsyncFilesResource:
+ from .resources.files import AsyncFilesResource
+
+ return AsyncFilesResource(self)
+
+ @cached_property
+ def jobs(self) -> AsyncJobsResource:
+ from .resources.jobs import AsyncJobsResource
+
+ return AsyncJobsResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSpitchWithRawResponse:
+ return AsyncSpitchWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSpitchWithStreamedResponse:
+ return AsyncSpitchWithStreamedResponse(self)
@property
@override
@@ -392,35 +437,131 @@ def _make_status_error(
class SpitchWithRawResponse:
+ _client: Spitch
+
def __init__(self, client: Spitch) -> None:
- self.speech = speech.SpeechResourceWithRawResponse(client.speech)
- self.text = text.TextResourceWithRawResponse(client.text)
- self.files = files.FilesResourceWithRawResponse(client.files)
- self.jobs = jobs.JobsResourceWithRawResponse(client.jobs)
+ self._client = client
+
+ @cached_property
+ def speech(self) -> speech.SpeechResourceWithRawResponse:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import SpeechResourceWithRawResponse
+
+ return SpeechResourceWithRawResponse(self._client.speech)
+
+ @cached_property
+ def text(self) -> text.TextResourceWithRawResponse:
+ from .resources.text import TextResourceWithRawResponse
+
+ return TextResourceWithRawResponse(self._client.text)
+
+ @cached_property
+ def files(self) -> files.FilesResourceWithRawResponse:
+ from .resources.files import FilesResourceWithRawResponse
+
+ return FilesResourceWithRawResponse(self._client.files)
+
+ @cached_property
+ def jobs(self) -> jobs.JobsResourceWithRawResponse:
+ from .resources.jobs import JobsResourceWithRawResponse
+
+ return JobsResourceWithRawResponse(self._client.jobs)
class AsyncSpitchWithRawResponse:
+ _client: AsyncSpitch
+
def __init__(self, client: AsyncSpitch) -> None:
- self.speech = speech.AsyncSpeechResourceWithRawResponse(client.speech)
- self.text = text.AsyncTextResourceWithRawResponse(client.text)
- self.files = files.AsyncFilesResourceWithRawResponse(client.files)
- self.jobs = jobs.AsyncJobsResourceWithRawResponse(client.jobs)
+ self._client = client
+
+ @cached_property
+ def speech(self) -> speech.AsyncSpeechResourceWithRawResponse:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import AsyncSpeechResourceWithRawResponse
+
+ return AsyncSpeechResourceWithRawResponse(self._client.speech)
+
+ @cached_property
+ def text(self) -> text.AsyncTextResourceWithRawResponse:
+ from .resources.text import AsyncTextResourceWithRawResponse
+
+ return AsyncTextResourceWithRawResponse(self._client.text)
+
+ @cached_property
+ def files(self) -> files.AsyncFilesResourceWithRawResponse:
+ from .resources.files import AsyncFilesResourceWithRawResponse
+
+ return AsyncFilesResourceWithRawResponse(self._client.files)
+
+ @cached_property
+ def jobs(self) -> jobs.AsyncJobsResourceWithRawResponse:
+ from .resources.jobs import AsyncJobsResourceWithRawResponse
+
+ return AsyncJobsResourceWithRawResponse(self._client.jobs)
class SpitchWithStreamedResponse:
+ _client: Spitch
+
def __init__(self, client: Spitch) -> None:
- self.speech = speech.SpeechResourceWithStreamingResponse(client.speech)
- self.text = text.TextResourceWithStreamingResponse(client.text)
- self.files = files.FilesResourceWithStreamingResponse(client.files)
- self.jobs = jobs.JobsResourceWithStreamingResponse(client.jobs)
+ self._client = client
+
+ @cached_property
+ def speech(self) -> speech.SpeechResourceWithStreamingResponse:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import SpeechResourceWithStreamingResponse
+
+ return SpeechResourceWithStreamingResponse(self._client.speech)
+
+ @cached_property
+ def text(self) -> text.TextResourceWithStreamingResponse:
+ from .resources.text import TextResourceWithStreamingResponse
+
+ return TextResourceWithStreamingResponse(self._client.text)
+
+ @cached_property
+ def files(self) -> files.FilesResourceWithStreamingResponse:
+ from .resources.files import FilesResourceWithStreamingResponse
+
+ return FilesResourceWithStreamingResponse(self._client.files)
+
+ @cached_property
+ def jobs(self) -> jobs.JobsResourceWithStreamingResponse:
+ from .resources.jobs import JobsResourceWithStreamingResponse
+
+ return JobsResourceWithStreamingResponse(self._client.jobs)
class AsyncSpitchWithStreamedResponse:
+ _client: AsyncSpitch
+
def __init__(self, client: AsyncSpitch) -> None:
- self.speech = speech.AsyncSpeechResourceWithStreamingResponse(client.speech)
- self.text = text.AsyncTextResourceWithStreamingResponse(client.text)
- self.files = files.AsyncFilesResourceWithStreamingResponse(client.files)
- self.jobs = jobs.AsyncJobsResourceWithStreamingResponse(client.jobs)
+ self._client = client
+
+ @cached_property
+ def speech(self) -> speech.AsyncSpeechResourceWithStreamingResponse:
+ """All speech-focused APIs (TTS and STT)"""
+ from .resources.speech import AsyncSpeechResourceWithStreamingResponse
+
+ return AsyncSpeechResourceWithStreamingResponse(self._client.speech)
+
+ @cached_property
+ def text(self) -> text.AsyncTextResourceWithStreamingResponse:
+ from .resources.text import AsyncTextResourceWithStreamingResponse
+
+ return AsyncTextResourceWithStreamingResponse(self._client.text)
+
+ @cached_property
+ def files(self) -> files.AsyncFilesResourceWithStreamingResponse:
+ from .resources.files import AsyncFilesResourceWithStreamingResponse
+
+ return AsyncFilesResourceWithStreamingResponse(self._client.files)
+
+ @cached_property
+ def jobs(self) -> jobs.AsyncJobsResourceWithStreamingResponse:
+ from .resources.jobs import AsyncJobsResourceWithStreamingResponse
+
+ return AsyncJobsResourceWithStreamingResponse(self._client.jobs)
Client = Spitch
From 7c7152096acff1ab2777e88ea6edf8e74762a366 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 17 Dec 2025 05:49:05 +0000
Subject: [PATCH 09/17] feat(files): add support for string alternative to file
upload type
---
src/spitch/resources/speech.py | 6 +++---
src/spitch/types/speech_transcribe_params.py | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/spitch/resources/speech.py b/src/spitch/resources/speech.py
index 970a876..d16308e 100644
--- a/src/spitch/resources/speech.py
+++ b/src/spitch/resources/speech.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Mapping, Optional, cast
+from typing import Union, Mapping, Optional, cast
from typing_extensions import Literal
import httpx
@@ -127,7 +127,7 @@ def transcribe(
self,
*,
language: Literal["yo", "en", "ha", "ig", "am"],
- content: Optional[FileTypes] | Omit = omit,
+ content: Union[FileTypes, str, None] | Omit = omit,
model: Optional[Literal["mansa_v1", "legacy", "human"]] | Omit = omit,
special_words: Optional[str] | Omit = omit,
timestamp: Optional[Literal["sentence", "word", "none"]] | Omit = omit,
@@ -272,7 +272,7 @@ async def transcribe(
self,
*,
language: Literal["yo", "en", "ha", "ig", "am"],
- content: Optional[FileTypes] | Omit = omit,
+ content: Union[FileTypes, str, None] | Omit = omit,
model: Optional[Literal["mansa_v1", "legacy", "human"]] | Omit = omit,
special_words: Optional[str] | Omit = omit,
timestamp: Optional[Literal["sentence", "word", "none"]] | Omit = omit,
diff --git a/src/spitch/types/speech_transcribe_params.py b/src/spitch/types/speech_transcribe_params.py
index 4476699..8909a68 100644
--- a/src/spitch/types/speech_transcribe_params.py
+++ b/src/spitch/types/speech_transcribe_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Union, Optional
from typing_extensions import Literal, Required, TypedDict
from .._types import FileTypes
@@ -13,7 +13,7 @@
class SpeechTranscribeParams(TypedDict, total=False):
language: Required[Literal["yo", "en", "ha", "ig", "am"]]
- content: Optional[FileTypes]
+ content: Union[FileTypes, str, None]
model: Optional[Literal["mansa_v1", "legacy", "human"]]
From d6681dc12cb59df8843b6adbe09095efde66548b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 18 Dec 2025 06:29:44 +0000
Subject: [PATCH 10/17] fix: use async_to_httpx_files in patch method
---
src/spitch/_base_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/spitch/_base_client.py b/src/spitch/_base_client.py
index c110dc0..3cd7c65 100644
--- a/src/spitch/_base_client.py
+++ b/src/spitch/_base_client.py
@@ -1778,7 +1778,7 @@ async def patch(
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
From 697bfd03e8bc2c855e36fbfcc23f6ecf7d740652 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 19 Dec 2025 05:42:27 +0000
Subject: [PATCH 11/17] chore(internal): add `--fix` argument to lint script
---
scripts/lint | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/scripts/lint b/scripts/lint
index 06ef5e3..b0778e2 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -4,8 +4,13 @@ set -e
cd "$(dirname "$0")/.."
-echo "==> Running lints"
-rye run lint
+if [ "$1" = "--fix" ]; then
+ echo "==> Running lints with --fix"
+ rye run fix:ruff
+else
+ echo "==> Running lints"
+ rye run lint
+fi
echo "==> Making sure it imports"
rye run python -c 'import spitch'
From d11c2988b89fde8ef890fe072869caf1692fe56a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 6 Jan 2026 05:14:30 +0000
Subject: [PATCH 12/17] chore(internal): codegen related update
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/LICENSE b/LICENSE
index b0faa66..ff140ee 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2025 Spitch
+ Copyright 2026 Spitch
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
From 0c96451d56e5897221e1a5e628c557f8d47df911 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 14 Jan 2026 07:21:58 +0000
Subject: [PATCH 13/17] feat(client): add support for binary request streaming
---
src/spitch/_base_client.py | 145 +++++++++++++++++++++++++---
src/spitch/_models.py | 17 +++-
src/spitch/_types.py | 9 ++
tests/test_client.py | 187 ++++++++++++++++++++++++++++++++++++-
4 files changed, 344 insertions(+), 14 deletions(-)
diff --git a/src/spitch/_base_client.py b/src/spitch/_base_client.py
index 3cd7c65..d4d25b9 100644
--- a/src/spitch/_base_client.py
+++ b/src/spitch/_base_client.py
@@ -9,6 +9,7 @@
import inspect
import logging
import platform
+import warnings
import email.utils
from types import TracebackType
from random import random
@@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
+ BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
+ AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
@@ -477,8 +480,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
- log.debug("Request options: %s", model_dump(options, exclude_unset=True))
-
+ log.debug(
+ "Request options: %s",
+ model_dump(
+ options,
+ exclude_unset=True,
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ exclude={
+ "content",
+ }
+ if PYDANTIC_V1
+ else {},
+ ),
+ )
kwargs: dict[str, Any] = {}
json_data = options.json_data
@@ -532,7 +546,13 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
- if isinstance(json_data, bytes):
+ if options.content is not None and json_data is not None:
+ raise TypeError("Passing both `content` and `json_data` is not supported")
+ if options.content is not None and files is not None:
+ raise TypeError("Passing both `content` and `files` is not supported")
+ if options.content is not None:
+ kwargs["content"] = options.content
+ elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
@@ -1194,6 +1214,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
@@ -1206,6 +1227,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
@@ -1219,6 +1241,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
@@ -1231,13 +1254,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
@@ -1247,11 +1282,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1261,11 +1308,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1275,9 +1334,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)
def get_api_list(
@@ -1721,6 +1790,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
@@ -1733,6 +1803,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
@@ -1746,6 +1817,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
@@ -1758,13 +1830,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
@@ -1774,11 +1858,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="patch",
+ url=path,
+ json_data=body,
+ content=content,
+ files=await async_to_httpx_files(files),
+ **options,
)
return await self.request(cast_to, opts)
@@ -1788,11 +1889,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
@@ -1802,9 +1915,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)
def get_api_list(
diff --git a/src/spitch/_models.py b/src/spitch/_models.py
index 84e531c..99e58f2 100644
--- a/src/spitch/_models.py
+++ b/src/spitch/_models.py
@@ -3,7 +3,20 @@
import os
import inspect
import weakref
-from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Type,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterable,
+ Optional,
+ AsyncIterable,
+ cast,
+)
from datetime import date, datetime
from typing_extensions import (
List,
@@ -783,6 +796,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
@@ -801,6 +815,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
diff --git a/src/spitch/_types.py b/src/spitch/_types.py
index fcc62ee..74716c3 100644
--- a/src/spitch/_types.py
+++ b/src/spitch/_types.py
@@ -13,9 +13,11 @@
Mapping,
TypeVar,
Callable,
+ Iterable,
Iterator,
Optional,
Sequence,
+ AsyncIterable,
)
from typing_extensions import (
Set,
@@ -56,6 +58,13 @@
else:
Base64FileInput = Union[IO[bytes], PathLike]
FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
+
+
+# Used for sending raw binary data / streaming data in request bodies
+# e.g. for file uploads without multipart encoding
+BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
+AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
+
FileTypes = Union[
# file (or bytes)
FileContent,
diff --git a/tests/test_client.py b/tests/test_client.py
index df13bc3..8b9b263 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -8,10 +8,11 @@
import json
import asyncio
import inspect
+import dataclasses
import tracemalloc
-from typing import Any, Union, cast
+from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast
from unittest import mock
-from typing_extensions import Literal
+from typing_extensions import Literal, AsyncIterator, override
import httpx
import pytest
@@ -36,6 +37,7 @@
from .utils import update_env
+T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "My API Key"
@@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
return 0.1
+def mirror_request_content(request: httpx.Request) -> httpx.Response:
+ return httpx.Response(200, content=request.content)
+
+
+# note: we can't use the httpx.MockTransport class as it consumes the request
+# body itself, which means we can't test that the body is read lazily
+class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport):
+ def __init__(
+ self,
+ handler: Callable[[httpx.Request], httpx.Response]
+ | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]],
+ ) -> None:
+ self.handler = handler
+
+ @override
+ def handle_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function"
+ assert inspect.isfunction(self.handler), "handler must be a function"
+ return self.handler(request)
+
+ @override
+ async def handle_async_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function"
+ return await self.handler(request)
+
+
+@dataclasses.dataclass
+class Counter:
+ value: int = 0
+
+
+def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
+async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
def _get_open_connections(client: Spitch | AsyncSpitch) -> int:
transport = client._client._transport
assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
@@ -500,6 +553,70 @@ def test_multipart_repeating_array(self, client: Spitch) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload(self, respx_mock: MockRouter, client: Spitch) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ def test_binary_content_upload_with_iterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_sync_iterator([file_content], counter=counter)
+
+ def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=request.read())
+
+ with Spitch(
+ base_url=base_url,
+ api_key=api_key,
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Spitch) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
def test_basic_union_response(self, respx_mock: MockRouter, client: Spitch) -> None:
class Model1(BaseModel):
@@ -1323,6 +1440,72 @@ def test_multipart_repeating_array(self, async_client: AsyncSpitch) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncSpitch) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = await async_client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ async def test_binary_content_upload_with_asynciterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_async_iterator([file_content], counter=counter)
+
+ async def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=await request.aread())
+
+ async with AsyncSpitch(
+ base_url=base_url,
+ api_key=api_key,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = await client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload_with_body_is_deprecated(
+ self, respx_mock: MockRouter, async_client: AsyncSpitch
+ ) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = await async_client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncSpitch) -> None:
class Model1(BaseModel):
From afdc7e653e6f39b32558d16ab286f83d5eda385b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 17 Jan 2026 02:23:09 +0000
Subject: [PATCH 14/17] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index d0ce107..7741699 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 12
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/babs-technologies%2Fspitch-b384c3db1a21cd96c342e397f623e66793d5da64a726644609f7803ef6b5886a.yml
openapi_spec_hash: eab054bdfec2c83b963ea3f120bf4109
-config_hash: 5945e27bb7f451aa23c0fe256740d398
+config_hash: c4f2f3efcf1cb1208827064ebdb2bde0
From 9dfcb509c7bed5382e2de4663838fc0583774bc2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 17 Jan 2026 05:47:06 +0000
Subject: [PATCH 15/17] chore(internal): update `actions/checkout` version
---
.github/workflows/ci.yml | 6 +++---
.github/workflows/publish-pypi.yml | 2 +-
.github/workflows/release-doctor.yml | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 54509c1..b7887bc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/spitch-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -44,7 +44,7 @@ jobs:
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/spitch-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -81,7 +81,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/spitch-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index cf9aa94..a87cf3f 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 3405315..b12aede 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'spi-tch/spitch-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Check release environment
run: |
From 12f75eff7f034196f0f453dfb4b4c800234d9198 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 21 Jan 2026 16:59:10 +0000
Subject: [PATCH 16/17] feat(api): manual updates
---
.stats.yml | 8 +-
api.md | 23 +-
src/spitch/_client.py | 39 +--
src/spitch/resources/__init__.py | 14 -
src/spitch/resources/files.py | 145 ++--------
src/spitch/resources/jobs.py | 271 ------------------
src/spitch/resources/speech.py | 104 +++++--
src/spitch/resources/text.py | 47 ++-
src/spitch/types/__init__.py | 8 +-
src/spitch/types/diacritics.py | 9 -
src/spitch/types/file.py | 13 +-
src/spitch/types/file_delete_response.py | 11 +
src/spitch/types/file_download_response.py | 23 --
src/spitch/types/file_list_params.py | 4 +-
src/spitch/types/file_usage.py | 27 +-
src/spitch/types/files.py | 7 +-
src/spitch/types/job.py | 21 --
src/spitch/types/job_list_params.py | 16 --
src/spitch/types/jobs.py | 21 --
src/spitch/types/segment.py | 21 ++
src/spitch/types/speech_generate_params.py | 10 +-
src/spitch/types/speech_transcribe_params.py | 9 +-
.../types/speech_transcribe_response.py | 23 ++
src/spitch/types/text_tone_mark_params.py | 2 +-
src/spitch/types/text_translate_params.py | 13 +-
src/spitch/types/transcription.py | 23 --
src/spitch/types/translation.py | 13 -
tests/api_resources/test_files.py | 36 +--
tests/api_resources/test_jobs.py | 169 -----------
tests/api_resources/test_speech.py | 28 +-
tests/api_resources/test_text.py | 26 +-
31 files changed, 250 insertions(+), 934 deletions(-)
delete mode 100644 src/spitch/resources/jobs.py
create mode 100644 src/spitch/types/file_delete_response.py
delete mode 100644 src/spitch/types/file_download_response.py
delete mode 100644 src/spitch/types/job.py
delete mode 100644 src/spitch/types/job_list_params.py
delete mode 100644 src/spitch/types/jobs.py
create mode 100644 src/spitch/types/segment.py
create mode 100644 src/spitch/types/speech_transcribe_response.py
delete mode 100644 src/spitch/types/transcription.py
delete mode 100644 tests/api_resources/test_jobs.py
diff --git a/.stats.yml b/.stats.yml
index 7741699..03042bb 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 12
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/babs-technologies%2Fspitch-b384c3db1a21cd96c342e397f623e66793d5da64a726644609f7803ef6b5886a.yml
-openapi_spec_hash: eab054bdfec2c83b963ea3f120bf4109
-config_hash: c4f2f3efcf1cb1208827064ebdb2bde0
+configured_endpoints: 10
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/babs-technologies%2Fspitch-9caec45089fb23c139a076c7f6a62fdf1397b3384d3d450e5fcefdc840e7eafc.yml
+openapi_spec_hash: 9b85c4aaecd7073df4e7e94a1887475b
+config_hash: f3bd776604dfca42c23f55d275b73bd0
diff --git a/api.md b/api.md
index 70dfb2c..7cb72b3 100644
--- a/api.md
+++ b/api.md
@@ -3,13 +3,13 @@
Types:
```python
-from spitch.types import Transcription
+from spitch.types import Segment, SpeechTranscribeResponse
```
Methods:
- client.speech.generate(\*\*params) -> BinaryAPIResponse
-- client.speech.transcribe(\*\*params) -> Transcription
+- client.speech.transcribe(\*\*params) -> SpeechTranscribeResponse
# Text
@@ -29,27 +29,14 @@ Methods:
Types:
```python
-from spitch.types import File, FileUsage, Files, FileDownloadResponse
+from spitch.types import File, FileUsage, Files, FileDeleteResponse
```
Methods:
- client.files.list(\*\*params) -> SyncFilesCursor[File]
-- client.files.delete(file_id) -> object
-- client.files.download(file_id, \*\*params) -> FileDownloadResponse
+- client.files.delete(file_id) -> FileDeleteResponse
+- client.files.download(file_id, \*\*params) -> object
- client.files.get(file_id) -> File
- client.files.upload(\*\*params) -> File
- client.files.usage() -> FileUsage
-
-# Jobs
-
-Types:
-
-```python
-from spitch.types import Job, Jobs
-```
-
-Methods:
-
-- client.jobs.list(\*\*params) -> SyncFilesCursor[Job]
-- client.jobs.get(job_id) -> Job
diff --git a/src/spitch/_client.py b/src/spitch/_client.py
index 4e5d00d..14c6822 100644
--- a/src/spitch/_client.py
+++ b/src/spitch/_client.py
@@ -31,8 +31,7 @@
)
if TYPE_CHECKING:
- from .resources import jobs, text, files, speech
- from .resources.jobs import JobsResource, AsyncJobsResource
+ from .resources import text, files, speech
from .resources.text import TextResource, AsyncTextResource
from .resources.files import FilesResource, AsyncFilesResource
from .resources.speech import SpeechResource, AsyncSpeechResource
@@ -124,12 +123,6 @@ def files(self) -> FilesResource:
return FilesResource(self)
- @cached_property
- def jobs(self) -> JobsResource:
- from .resources.jobs import JobsResource
-
- return JobsResource(self)
-
@cached_property
def with_raw_response(self) -> SpitchWithRawResponse:
return SpitchWithRawResponse(self)
@@ -317,12 +310,6 @@ def files(self) -> AsyncFilesResource:
return AsyncFilesResource(self)
- @cached_property
- def jobs(self) -> AsyncJobsResource:
- from .resources.jobs import AsyncJobsResource
-
- return AsyncJobsResource(self)
-
@cached_property
def with_raw_response(self) -> AsyncSpitchWithRawResponse:
return AsyncSpitchWithRawResponse(self)
@@ -461,12 +448,6 @@ def files(self) -> files.FilesResourceWithRawResponse:
return FilesResourceWithRawResponse(self._client.files)
- @cached_property
- def jobs(self) -> jobs.JobsResourceWithRawResponse:
- from .resources.jobs import JobsResourceWithRawResponse
-
- return JobsResourceWithRawResponse(self._client.jobs)
-
class AsyncSpitchWithRawResponse:
_client: AsyncSpitch
@@ -493,12 +474,6 @@ def files(self) -> files.AsyncFilesResourceWithRawResponse:
return AsyncFilesResourceWithRawResponse(self._client.files)
- @cached_property
- def jobs(self) -> jobs.AsyncJobsResourceWithRawResponse:
- from .resources.jobs import AsyncJobsResourceWithRawResponse
-
- return AsyncJobsResourceWithRawResponse(self._client.jobs)
-
class SpitchWithStreamedResponse:
_client: Spitch
@@ -525,12 +500,6 @@ def files(self) -> files.FilesResourceWithStreamingResponse:
return FilesResourceWithStreamingResponse(self._client.files)
- @cached_property
- def jobs(self) -> jobs.JobsResourceWithStreamingResponse:
- from .resources.jobs import JobsResourceWithStreamingResponse
-
- return JobsResourceWithStreamingResponse(self._client.jobs)
-
class AsyncSpitchWithStreamedResponse:
_client: AsyncSpitch
@@ -557,12 +526,6 @@ def files(self) -> files.AsyncFilesResourceWithStreamingResponse:
return AsyncFilesResourceWithStreamingResponse(self._client.files)
- @cached_property
- def jobs(self) -> jobs.AsyncJobsResourceWithStreamingResponse:
- from .resources.jobs import AsyncJobsResourceWithStreamingResponse
-
- return AsyncJobsResourceWithStreamingResponse(self._client.jobs)
-
Client = Spitch
diff --git a/src/spitch/resources/__init__.py b/src/spitch/resources/__init__.py
index 95209fa..ecbcde6 100644
--- a/src/spitch/resources/__init__.py
+++ b/src/spitch/resources/__init__.py
@@ -1,13 +1,5 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from .jobs import (
- JobsResource,
- AsyncJobsResource,
- JobsResourceWithRawResponse,
- AsyncJobsResourceWithRawResponse,
- JobsResourceWithStreamingResponse,
- AsyncJobsResourceWithStreamingResponse,
-)
from .text import (
TextResource,
AsyncTextResource,
@@ -52,10 +44,4 @@
"AsyncFilesResourceWithRawResponse",
"FilesResourceWithStreamingResponse",
"AsyncFilesResourceWithStreamingResponse",
- "JobsResource",
- "AsyncJobsResource",
- "JobsResourceWithRawResponse",
- "AsyncJobsResourceWithRawResponse",
- "JobsResourceWithStreamingResponse",
- "AsyncJobsResourceWithStreamingResponse",
]
diff --git a/src/spitch/resources/files.py b/src/spitch/resources/files.py
index 5a02bfe..1bf894b 100644
--- a/src/spitch/resources/files.py
+++ b/src/spitch/resources/files.py
@@ -3,7 +3,6 @@
from __future__ import annotations
from typing import Mapping, Optional, cast
-from typing_extensions import Literal
import httpx
@@ -22,7 +21,7 @@
from ..types.file import File
from .._base_client import AsyncPaginator, make_request_options
from ..types.file_usage import FileUsage
-from ..types.file_download_response import FileDownloadResponse
+from ..types.file_delete_response import FileDeleteResponse
__all__ = ["FilesResource", "AsyncFilesResource"]
@@ -52,7 +51,6 @@ def list(
*,
cursor: Optional[str] | Omit = omit,
limit: int | Omit = omit,
- status: Optional[Literal["uploading", "ready"]] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -61,16 +59,7 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SyncFilesCursor[File]:
"""
- Get a paginated list of files for the authenticated organization.
-
- Args: identity: Authentication identity containing org_id and user_id limit:
- Maximum number of files to return (max 100) status: Optional filter by file
- status (processing, ready, etc.) cursor: Optional pagination cursor for getting
- next page db: Database session
-
- Returns: ListFilesResponse: Paginated list of files with metadata
-
- Raises: HTTPException: 400 if cursor is invalid
+ Get Files
Args:
extra_headers: Send extra headers
@@ -93,7 +82,6 @@ def list(
{
"cursor": cursor,
"limit": limit,
- "status": status,
},
file_list_params.FileListParams,
),
@@ -111,16 +99,9 @@ def delete(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
+ ) -> FileDeleteResponse:
"""
- Delete a file and its associated S3 object.
-
- Args: file_id: UUID of the file to delete identity: Authentication identity
- containing org_id db: Database session s3: S3 session for deleting objects
-
- Returns: dict: Success confirmation
-
- Raises: HTTPException: 404 if file not found or doesn't belong to org
+ Delete File
Args:
extra_headers: Send extra headers
@@ -138,7 +119,7 @@ def delete(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=object,
+ cast_to=FileDeleteResponse,
)
def download(
@@ -152,19 +133,9 @@ def download(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FileDownloadResponse:
+ ) -> object:
"""
- Generate a pre-signed download URL for a file.
-
- Args: file_id: UUID of the file to download identity: Authentication identity
- containing org_id ttl: Time-to-live for the download URL in seconds (60-3600)
- db: Database session s3: S3 session for generating pre-signed URLs
-
- Returns: FileDownloadResponse: Contains file_id, download URL, and expiration
- time
-
- Raises: HTTPException: 404 if file not found, doesn't belong to org, or not
- ready
+ Download File
Args:
extra_headers: Send extra headers
@@ -186,7 +157,7 @@ def download(
timeout=timeout,
query=maybe_transform({"ttl": ttl}, file_download_params.FileDownloadParams),
),
- cast_to=FileDownloadResponse,
+ cast_to=object,
)
def get(
@@ -201,15 +172,7 @@ def get(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> File:
"""
- Get metadata for a specific file.
-
- Args: file_id: UUID of the file to retrieve identity: Authentication identity
- containing org_id db: Database session
-
- Returns: FileMetaResponse: File metadata including upload information
-
- Raises: HTTPException: 404 if file not found or doesn't belong to org
- HTTPException: 500 if uploader information is corrupted
+ Get File
Args:
extra_headers: Send extra headers
@@ -242,14 +205,7 @@ def upload(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> File:
"""
- Upload a file to the Spitch server.
-
- Args: file: The file to upload from the request identity: Authentication
- identity containing org_id and user_id db: Database session
-
- Returns: FileMetaResponse: Metadata for the uploaded file
-
- Raises: HTTPException: 500 if upload fails
+ Upload a file to your storage.
Args:
extra_headers: Send extra headers
@@ -286,15 +242,7 @@ def usage(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileUsage:
- """
- Get storage usage statistics for the authenticated organization.
-
- Args: identity: Authentication identity containing org_id db: Database session
-
- Returns: FileUsage: Usage statistics including total/used bytes and file count
-
- Raises: HTTPException: 500 if unable to calculate usage
- """
+ """Get Usage"""
return self._get(
"/v1/files:usage",
options=make_request_options(
@@ -329,7 +277,6 @@ def list(
*,
cursor: Optional[str] | Omit = omit,
limit: int | Omit = omit,
- status: Optional[Literal["uploading", "ready"]] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -338,16 +285,7 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncPaginator[File, AsyncFilesCursor[File]]:
"""
- Get a paginated list of files for the authenticated organization.
-
- Args: identity: Authentication identity containing org_id and user_id limit:
- Maximum number of files to return (max 100) status: Optional filter by file
- status (processing, ready, etc.) cursor: Optional pagination cursor for getting
- next page db: Database session
-
- Returns: ListFilesResponse: Paginated list of files with metadata
-
- Raises: HTTPException: 400 if cursor is invalid
+ Get Files
Args:
extra_headers: Send extra headers
@@ -370,7 +308,6 @@ def list(
{
"cursor": cursor,
"limit": limit,
- "status": status,
},
file_list_params.FileListParams,
),
@@ -388,16 +325,9 @@ async def delete(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
+ ) -> FileDeleteResponse:
"""
- Delete a file and its associated S3 object.
-
- Args: file_id: UUID of the file to delete identity: Authentication identity
- containing org_id db: Database session s3: S3 session for deleting objects
-
- Returns: dict: Success confirmation
-
- Raises: HTTPException: 404 if file not found or doesn't belong to org
+ Delete File
Args:
extra_headers: Send extra headers
@@ -415,7 +345,7 @@ async def delete(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=object,
+ cast_to=FileDeleteResponse,
)
async def download(
@@ -429,19 +359,9 @@ async def download(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FileDownloadResponse:
+ ) -> object:
"""
- Generate a pre-signed download URL for a file.
-
- Args: file_id: UUID of the file to download identity: Authentication identity
- containing org_id ttl: Time-to-live for the download URL in seconds (60-3600)
- db: Database session s3: S3 session for generating pre-signed URLs
-
- Returns: FileDownloadResponse: Contains file_id, download URL, and expiration
- time
-
- Raises: HTTPException: 404 if file not found, doesn't belong to org, or not
- ready
+ Download File
Args:
extra_headers: Send extra headers
@@ -463,7 +383,7 @@ async def download(
timeout=timeout,
query=await async_maybe_transform({"ttl": ttl}, file_download_params.FileDownloadParams),
),
- cast_to=FileDownloadResponse,
+ cast_to=object,
)
async def get(
@@ -478,15 +398,7 @@ async def get(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> File:
"""
- Get metadata for a specific file.
-
- Args: file_id: UUID of the file to retrieve identity: Authentication identity
- containing org_id db: Database session
-
- Returns: FileMetaResponse: File metadata including upload information
-
- Raises: HTTPException: 404 if file not found or doesn't belong to org
- HTTPException: 500 if uploader information is corrupted
+ Get File
Args:
extra_headers: Send extra headers
@@ -519,14 +431,7 @@ async def upload(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> File:
"""
- Upload a file to the Spitch server.
-
- Args: file: The file to upload from the request identity: Authentication
- identity containing org_id and user_id db: Database session
-
- Returns: FileMetaResponse: Metadata for the uploaded file
-
- Raises: HTTPException: 500 if upload fails
+ Upload a file to your storage.
Args:
extra_headers: Send extra headers
@@ -563,15 +468,7 @@ async def usage(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileUsage:
- """
- Get storage usage statistics for the authenticated organization.
-
- Args: identity: Authentication identity containing org_id db: Database session
-
- Returns: FileUsage: Usage statistics including total/used bytes and file count
-
- Raises: HTTPException: 500 if unable to calculate usage
- """
+ """Get Usage"""
return await self._get(
"/v1/files:usage",
options=make_request_options(
diff --git a/src/spitch/resources/jobs.py b/src/spitch/resources/jobs.py
deleted file mode 100644
index 6e28035..0000000
--- a/src/spitch/resources/jobs.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Literal
-
-import httpx
-
-from ..types import job_list_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from .._utils import maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..types.job import Job
-from ..pagination import SyncFilesCursor, AsyncFilesCursor
-from .._base_client import AsyncPaginator, make_request_options
-
-__all__ = ["JobsResource", "AsyncJobsResource"]
-
-
-class JobsResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> JobsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/spi-tch/spitch-python#accessing-raw-response-data-eg-headers
- """
- return JobsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> JobsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/spi-tch/spitch-python#with_streaming_response
- """
- return JobsResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- cursor: Optional[str] | Omit = omit,
- limit: int | Omit = omit,
- status: Optional[Literal["queued", "in_progress", "finished"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SyncFilesCursor[Job]:
- """
- Get Jobs
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/jobs",
- page=SyncFilesCursor[Job],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "cursor": cursor,
- "limit": limit,
- "status": status,
- },
- job_list_params.JobListParams,
- ),
- ),
- model=Job,
- )
-
- def get(
- self,
- job_id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Job:
- """
- Get Job
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not job_id:
- raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}")
- return self._get(
- f"/v1/jobs/{job_id}",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Job,
- )
-
-
-class AsyncJobsResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncJobsResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/spi-tch/spitch-python#accessing-raw-response-data-eg-headers
- """
- return AsyncJobsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncJobsResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/spi-tch/spitch-python#with_streaming_response
- """
- return AsyncJobsResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- cursor: Optional[str] | Omit = omit,
- limit: int | Omit = omit,
- status: Optional[Literal["queued", "in_progress", "finished"]] | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncPaginator[Job, AsyncFilesCursor[Job]]:
- """
- Get Jobs
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get_api_list(
- "/v1/jobs",
- page=AsyncFilesCursor[Job],
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "cursor": cursor,
- "limit": limit,
- "status": status,
- },
- job_list_params.JobListParams,
- ),
- ),
- model=Job,
- )
-
- async def get(
- self,
- job_id: str,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Job:
- """
- Get Job
-
- Args:
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not job_id:
- raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}")
- return await self._get(
- f"/v1/jobs/{job_id}",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Job,
- )
-
-
-class JobsResourceWithRawResponse:
- def __init__(self, jobs: JobsResource) -> None:
- self._jobs = jobs
-
- self.list = to_raw_response_wrapper(
- jobs.list,
- )
- self.get = to_raw_response_wrapper(
- jobs.get,
- )
-
-
-class AsyncJobsResourceWithRawResponse:
- def __init__(self, jobs: AsyncJobsResource) -> None:
- self._jobs = jobs
-
- self.list = async_to_raw_response_wrapper(
- jobs.list,
- )
- self.get = async_to_raw_response_wrapper(
- jobs.get,
- )
-
-
-class JobsResourceWithStreamingResponse:
- def __init__(self, jobs: JobsResource) -> None:
- self._jobs = jobs
-
- self.list = to_streamed_response_wrapper(
- jobs.list,
- )
- self.get = to_streamed_response_wrapper(
- jobs.get,
- )
-
-
-class AsyncJobsResourceWithStreamingResponse:
- def __init__(self, jobs: AsyncJobsResource) -> None:
- self._jobs = jobs
-
- self.list = async_to_streamed_response_wrapper(
- jobs.list,
- )
- self.get = async_to_streamed_response_wrapper(
- jobs.get,
- )
diff --git a/src/spitch/resources/speech.py b/src/spitch/resources/speech.py
index d16308e..479ff79 100644
--- a/src/spitch/resources/speech.py
+++ b/src/spitch/resources/speech.py
@@ -9,7 +9,7 @@
from ..types import speech_generate_params, speech_transcribe_params
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
-from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
+from .._utils import is_given, extract_files, maybe_transform, strip_not_given, deepcopy_minimal, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
@@ -27,7 +27,7 @@
async_to_custom_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.transcription import Transcription
+from ..types.speech_transcribe_response import SpeechTranscribeResponse
__all__ = ["SpeechResource", "AsyncSpeechResource"]
@@ -57,7 +57,7 @@ def with_streaming_response(self) -> SpeechResourceWithStreamingResponse:
def generate(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
text: str,
voice: Literal[
"sade",
@@ -83,8 +83,8 @@ def generate(
"tena",
"tesfaye",
],
- format: Literal["wav", "mp3", "ogg_opus", "webm_opus", "flac", "pcm_s16le", "mulaw", "alaw"] | Omit = omit,
- model: Optional[Literal["legacy"]] | Omit = omit,
+ model: Optional[str] | Omit = omit,
+ spitch_x_data_retention: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -92,8 +92,10 @@ def generate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BinaryAPIResponse:
- """
- Synthesize
+ """Convert text to speech.
+
+ Select a voice and use that to generate audio in any
+ format. Audio is retured in chunks.
Args:
extra_headers: Send extra headers
@@ -104,7 +106,17 @@ def generate(
timeout: Override the client-level default timeout for this request, in seconds
"""
- extra_headers = {"Accept": "audio/*", **(extra_headers or {})}
+ extra_headers = {"Accept": "audio/wav", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Spitch-X-Data-Retention": ("true" if spitch_x_data_retention else "false")
+ if is_given(spitch_x_data_retention)
+ else not_given
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/v1/speech",
body=maybe_transform(
@@ -112,7 +124,6 @@ def generate(
"language": language,
"text": text,
"voice": voice,
- "format": format,
"model": model,
},
speech_generate_params.SpeechGenerateParams,
@@ -126,21 +137,24 @@ def generate(
def transcribe(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
content: Union[FileTypes, str, None] | Omit = omit,
- model: Optional[Literal["mansa_v1", "legacy", "human"]] | Omit = omit,
+ model: Optional[Literal["mansa_v1", "legacy"]] | Omit = omit,
special_words: Optional[str] | Omit = omit,
timestamp: Optional[Literal["sentence", "word", "none"]] | Omit = omit,
url: Optional[str] | Omit = omit,
+ spitch_x_data_retention: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Transcription:
- """
- Transcribe
+ ) -> SpeechTranscribeResponse:
+ """Convert speech to text.
+
+ Upload audio file containing speech and get back text
+ that represents the content of the audio file.
Args:
extra_headers: Send extra headers
@@ -151,6 +165,16 @@ def transcribe(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Spitch-X-Data-Retention": ("true" if spitch_x_data_retention else "false")
+ if is_given(spitch_x_data_retention)
+ else not_given
+ }
+ ),
+ **(extra_headers or {}),
+ }
body = deepcopy_minimal(
{
"language": language,
@@ -173,7 +197,7 @@ def transcribe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=Transcription,
+ cast_to=SpeechTranscribeResponse,
)
@@ -202,7 +226,7 @@ def with_streaming_response(self) -> AsyncSpeechResourceWithStreamingResponse:
async def generate(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
text: str,
voice: Literal[
"sade",
@@ -228,8 +252,8 @@ async def generate(
"tena",
"tesfaye",
],
- format: Literal["wav", "mp3", "ogg_opus", "webm_opus", "flac", "pcm_s16le", "mulaw", "alaw"] | Omit = omit,
- model: Optional[Literal["legacy"]] | Omit = omit,
+ model: Optional[str] | Omit = omit,
+ spitch_x_data_retention: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -237,8 +261,10 @@ async def generate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncBinaryAPIResponse:
- """
- Synthesize
+ """Convert text to speech.
+
+ Select a voice and use that to generate audio in any
+ format. Audio is retured in chunks.
Args:
extra_headers: Send extra headers
@@ -249,7 +275,17 @@ async def generate(
timeout: Override the client-level default timeout for this request, in seconds
"""
- extra_headers = {"Accept": "audio/*", **(extra_headers or {})}
+ extra_headers = {"Accept": "audio/wav", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Spitch-X-Data-Retention": ("true" if spitch_x_data_retention else "false")
+ if is_given(spitch_x_data_retention)
+ else not_given
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/v1/speech",
body=await async_maybe_transform(
@@ -257,7 +293,6 @@ async def generate(
"language": language,
"text": text,
"voice": voice,
- "format": format,
"model": model,
},
speech_generate_params.SpeechGenerateParams,
@@ -271,21 +306,24 @@ async def generate(
async def transcribe(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
content: Union[FileTypes, str, None] | Omit = omit,
- model: Optional[Literal["mansa_v1", "legacy", "human"]] | Omit = omit,
+ model: Optional[Literal["mansa_v1", "legacy"]] | Omit = omit,
special_words: Optional[str] | Omit = omit,
timestamp: Optional[Literal["sentence", "word", "none"]] | Omit = omit,
url: Optional[str] | Omit = omit,
+ spitch_x_data_retention: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Transcription:
- """
- Transcribe
+ ) -> SpeechTranscribeResponse:
+ """Convert speech to text.
+
+ Upload audio file containing speech and get back text
+ that represents the content of the audio file.
Args:
extra_headers: Send extra headers
@@ -296,6 +334,16 @@ async def transcribe(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Spitch-X-Data-Retention": ("true" if spitch_x_data_retention else "false")
+ if is_given(spitch_x_data_retention)
+ else not_given
+ }
+ ),
+ **(extra_headers or {}),
+ }
body = deepcopy_minimal(
{
"language": language,
@@ -318,7 +366,7 @@ async def transcribe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=Transcription,
+ cast_to=SpeechTranscribeResponse,
)
diff --git a/src/spitch/resources/text.py b/src/spitch/resources/text.py
index 2c19f1b..8d97ecc 100644
--- a/src/spitch/resources/text.py
+++ b/src/spitch/resources/text.py
@@ -2,13 +2,12 @@
from __future__ import annotations
-from typing import Optional
from typing_extensions import Literal
import httpx
from ..types import text_tone_mark_params, text_translate_params
-from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._types import Body, Query, Headers, NotGiven, not_given
from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
@@ -48,7 +47,7 @@ def with_streaming_response(self) -> TextResourceWithStreamingResponse:
def tone_mark(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -58,7 +57,7 @@ def tone_mark(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Diacritics:
"""
- Tone Mark
+ Add appopriate tone marks to text.
Args:
extra_headers: Send extra headers
@@ -87,12 +86,9 @@ def tone_mark(
def translate(
self,
*,
- source: Literal["yo", "en", "ha", "ig", "am"],
- target: Literal["yo", "en", "ha", "ig", "am"],
- file_id: Optional[str] | Omit = omit,
- instructions: Optional[str] | Omit = omit,
- model: Literal["human", "auto"] | Omit = omit,
- text: Optional[str] | Omit = omit,
+ source: Literal["yo", "en", "ha", "ig", "am", "pcm"],
+ target: Literal["yo", "en", "ha", "ig", "am", "pcm"],
+ text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -100,8 +96,10 @@ def translate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Translation:
- """
- Translate
+ """Translate text from one language to another.
+
+ Select the source and target
+ languages, and get text in new language.
Args:
extra_headers: Send extra headers
@@ -118,9 +116,6 @@ def translate(
{
"source": source,
"target": target,
- "file_id": file_id,
- "instructions": instructions,
- "model": model,
"text": text,
},
text_translate_params.TextTranslateParams,
@@ -155,7 +150,7 @@ def with_streaming_response(self) -> AsyncTextResourceWithStreamingResponse:
async def tone_mark(
self,
*,
- language: Literal["yo", "en", "ha", "ig", "am"],
+ language: Literal["yo", "en", "ha", "ig", "am", "pcm"],
text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -165,7 +160,7 @@ async def tone_mark(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Diacritics:
"""
- Tone Mark
+ Add appopriate tone marks to text.
Args:
extra_headers: Send extra headers
@@ -194,12 +189,9 @@ async def tone_mark(
async def translate(
self,
*,
- source: Literal["yo", "en", "ha", "ig", "am"],
- target: Literal["yo", "en", "ha", "ig", "am"],
- file_id: Optional[str] | Omit = omit,
- instructions: Optional[str] | Omit = omit,
- model: Literal["human", "auto"] | Omit = omit,
- text: Optional[str] | Omit = omit,
+ source: Literal["yo", "en", "ha", "ig", "am", "pcm"],
+ target: Literal["yo", "en", "ha", "ig", "am", "pcm"],
+ text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -207,8 +199,10 @@ async def translate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Translation:
- """
- Translate
+ """Translate text from one language to another.
+
+ Select the source and target
+ languages, and get text in new language.
Args:
extra_headers: Send extra headers
@@ -225,9 +219,6 @@ async def translate(
{
"source": source,
"target": target,
- "file_id": file_id,
- "instructions": instructions,
- "model": model,
"text": text,
},
text_translate_params.TextTranslateParams,
diff --git a/src/spitch/types/__init__.py b/src/spitch/types/__init__.py
index a27cec6..b4de01c 100644
--- a/src/spitch/types/__init__.py
+++ b/src/spitch/types/__init__.py
@@ -2,20 +2,18 @@
from __future__ import annotations
-from .job import Job as Job
from .file import File as File
-from .jobs import Jobs as Jobs
from .files import Files as Files
+from .segment import Segment as Segment
from .diacritics import Diacritics as Diacritics
from .file_usage import FileUsage as FileUsage
from .translation import Translation as Translation
-from .transcription import Transcription as Transcription
-from .job_list_params import JobListParams as JobListParams
from .file_list_params import FileListParams as FileListParams
from .file_upload_params import FileUploadParams as FileUploadParams
+from .file_delete_response import FileDeleteResponse as FileDeleteResponse
from .file_download_params import FileDownloadParams as FileDownloadParams
from .text_tone_mark_params import TextToneMarkParams as TextToneMarkParams
from .text_translate_params import TextTranslateParams as TextTranslateParams
-from .file_download_response import FileDownloadResponse as FileDownloadResponse
from .speech_generate_params import SpeechGenerateParams as SpeechGenerateParams
from .speech_transcribe_params import SpeechTranscribeParams as SpeechTranscribeParams
+from .speech_transcribe_response import SpeechTranscribeResponse as SpeechTranscribeResponse
diff --git a/src/spitch/types/diacritics.py b/src/spitch/types/diacritics.py
index 0f3a044..e9b1e07 100644
--- a/src/spitch/types/diacritics.py
+++ b/src/spitch/types/diacritics.py
@@ -6,15 +6,6 @@
class Diacritics(BaseModel):
- """
- Response model for text diacritization requests.
-
- Attributes:
- request_id: Unique identifier for the diacritization request
- text: Text with added diacritical marks for proper pronunciation
- language: Language code for the diacritized text
- """
-
request_id: str
text: str
diff --git a/src/spitch/types/file.py b/src/spitch/types/file.py
index 39e03e5..5961517 100644
--- a/src/spitch/types/file.py
+++ b/src/spitch/types/file.py
@@ -2,7 +2,6 @@
from typing import Optional
from datetime import datetime
-from typing_extensions import Literal
from .._models import BaseModel
@@ -10,15 +9,7 @@
class File(BaseModel):
- """
- description of a file.
- Attributes:
- file_id: unique identifier for the file.
- status: status of the file, `processing` or `ready`
- original_name: original name of the file. If the file was uploaded via API
- """
-
- category: Optional[str] = None
+ """Metadata info for this file."""
content_type: Optional[str] = None
@@ -30,6 +21,6 @@ class File(BaseModel):
size_bytes: Optional[int] = None
- status: Literal["uploading", "ready"]
+ status: str
uploaded_by: Optional[str] = None
diff --git a/src/spitch/types/file_delete_response.py b/src/spitch/types/file_delete_response.py
new file mode 100644
index 0000000..dd819ca
--- /dev/null
+++ b/src/spitch/types/file_delete_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["FileDeleteResponse"]
+
+
+class FileDeleteResponse(BaseModel):
+ status: Optional[bool] = None
diff --git a/src/spitch/types/file_download_response.py b/src/spitch/types/file_download_response.py
deleted file mode 100644
index c9e58d0..0000000
--- a/src/spitch/types/file_download_response.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from datetime import datetime
-
-from .._models import BaseModel
-
-__all__ = ["FileDownloadResponse"]
-
-
-class FileDownloadResponse(BaseModel):
- """Response model for file download URLs.
-
- Attributes:
- file_id: Unique identifier for the file
- url: Pre-signed download URL
- expires_at: When the download URL expires
- """
-
- expires_at: datetime
-
- file_id: str
-
- url: str
diff --git a/src/spitch/types/file_list_params.py b/src/spitch/types/file_list_params.py
index 7625e59..1ce6011 100644
--- a/src/spitch/types/file_list_params.py
+++ b/src/spitch/types/file_list_params.py
@@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import Literal, TypedDict
+from typing_extensions import TypedDict
__all__ = ["FileListParams"]
@@ -12,5 +12,3 @@ class FileListParams(TypedDict, total=False):
cursor: Optional[str]
limit: int
-
- status: Optional[Literal["uploading", "ready"]]
diff --git a/src/spitch/types/file_usage.py b/src/spitch/types/file_usage.py
index 93cb866..d866f2e 100644
--- a/src/spitch/types/file_usage.py
+++ b/src/spitch/types/file_usage.py
@@ -1,27 +1,24 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
+
from .._models import BaseModel
__all__ = ["FileUsage"]
class FileUsage(BaseModel):
- """Storage usage information for an organization.
-
- Attributes:
- total: Human-readable total storage limit
- used: Human-readable used storage amount
- total_bytes: Total storage limit in bytes
- used_bytes: Used storage amount in bytes
- num_files: Number of files stored
- """
-
- num_files: int
+ num_files: Optional[int] = None
+ """number of files available."""
- total: str
+ total: Optional[str] = None
+ """total storage available in human-readable format"""
- total_bytes: int
+ total_bytes: Optional[int] = None
+ """storage used in bytes"""
- used: str
+ used: Optional[str] = None
+ """storage used in human-readable format"""
- used_bytes: int
+ used_bytes: Optional[int] = None
+ """total storage available in bytes"""
diff --git a/src/spitch/types/files.py b/src/spitch/types/files.py
index 6328733..c29ba0d 100644
--- a/src/spitch/types/files.py
+++ b/src/spitch/types/files.py
@@ -9,12 +9,7 @@
class Files(BaseModel):
- """Response model for listing files.
-
- Attributes:
- items: List of file metadata responses
- next_cursor: Optional cursor for pagination to get next page of results
- """
+ """an array of file information."""
items: List[File]
diff --git a/src/spitch/types/job.py b/src/spitch/types/job.py
deleted file mode 100644
index da92372..0000000
--- a/src/spitch/types/job.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from datetime import datetime
-
-from .._models import BaseModel
-
-__all__ = ["Job"]
-
-
-class Job(BaseModel):
- """Metadata model for job metadata."""
-
- created_by: str
-
- due_date: datetime
-
- job_id: str
-
- org_id: str
-
- status: str
diff --git a/src/spitch/types/job_list_params.py b/src/spitch/types/job_list_params.py
deleted file mode 100644
index 2ef3b95..0000000
--- a/src/spitch/types/job_list_params.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import Literal, TypedDict
-
-__all__ = ["JobListParams"]
-
-
-class JobListParams(TypedDict, total=False):
- cursor: Optional[str]
-
- limit: int
-
- status: Optional[Literal["queued", "in_progress", "finished"]]
diff --git a/src/spitch/types/jobs.py b/src/spitch/types/jobs.py
deleted file mode 100644
index 7df3100..0000000
--- a/src/spitch/types/jobs.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .job import Job
-from .._models import BaseModel
-
-__all__ = ["Jobs"]
-
-
-class Jobs(BaseModel):
- """Response model for listing jobs.
-
- Attributes:
- items: list of job metadata responses
- next_cursor: Optional cursor for pagination to get next page of results
- """
-
- items: List[Job]
-
- next_cursor: Optional[str] = None
diff --git a/src/spitch/types/segment.py b/src/spitch/types/segment.py
new file mode 100644
index 0000000..3243c3e
--- /dev/null
+++ b/src/spitch/types/segment.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["Segment"]
+
+
+class Segment(BaseModel):
+ """a segment (sentence or word-level) of the transcript.
+
+ It contains a start and end time.
+ """
+
+ end: float
+ """the exact time when this segment ended."""
+
+ start: float
+ """the exact time when this segment started."""
+
+ text: str
+ """the text that belongs in this timeframe."""
diff --git a/src/spitch/types/speech_generate_params.py b/src/spitch/types/speech_generate_params.py
index a3ac63e..375af2b 100644
--- a/src/spitch/types/speech_generate_params.py
+++ b/src/spitch/types/speech_generate_params.py
@@ -3,13 +3,15 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["SpeechGenerateParams"]
class SpeechGenerateParams(TypedDict, total=False):
- language: Required[Literal["yo", "en", "ha", "ig", "am"]]
+ language: Required[Literal["yo", "en", "ha", "ig", "am", "pcm"]]
text: Required[str]
@@ -40,6 +42,6 @@ class SpeechGenerateParams(TypedDict, total=False):
]
]
- format: Literal["wav", "mp3", "ogg_opus", "webm_opus", "flac", "pcm_s16le", "mulaw", "alaw"]
+ model: Optional[str]
- model: Optional[Literal["legacy"]]
+ spitch_x_data_retention: Annotated[bool, PropertyInfo(alias="Spitch-X-Data-Retention")]
diff --git a/src/spitch/types/speech_transcribe_params.py b/src/spitch/types/speech_transcribe_params.py
index 8909a68..6555c21 100644
--- a/src/spitch/types/speech_transcribe_params.py
+++ b/src/spitch/types/speech_transcribe_params.py
@@ -3,22 +3,25 @@
from __future__ import annotations
from typing import Union, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypedDict
from .._types import FileTypes
+from .._utils import PropertyInfo
__all__ = ["SpeechTranscribeParams"]
class SpeechTranscribeParams(TypedDict, total=False):
- language: Required[Literal["yo", "en", "ha", "ig", "am"]]
+ language: Required[Literal["yo", "en", "ha", "ig", "am", "pcm"]]
content: Union[FileTypes, str, None]
- model: Optional[Literal["mansa_v1", "legacy", "human"]]
+ model: Optional[Literal["mansa_v1", "legacy"]]
special_words: Optional[str]
timestamp: Optional[Literal["sentence", "word", "none"]]
url: Optional[str]
+
+ spitch_x_data_retention: Annotated[bool, PropertyInfo(alias="Spitch-X-Data-Retention")]
diff --git a/src/spitch/types/speech_transcribe_response.py b/src/spitch/types/speech_transcribe_response.py
new file mode 100644
index 0000000..96e3e4a
--- /dev/null
+++ b/src/spitch/types/speech_transcribe_response.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .segment import Segment
+from .._models import BaseModel
+
+__all__ = ["SpeechTranscribeResponse"]
+
+
+class SpeechTranscribeResponse(BaseModel):
+ """Response from speech-to-text."""
+
+ request_id: str
+ """for audit purposes."""
+
+ text: str
+
+ segments: Optional[List[Segment]] = None
+ """sentence-level or word-level groupings of your transcript.
+
+ Each sentence (or word) will fall within a time range.
+ """
diff --git a/src/spitch/types/text_tone_mark_params.py b/src/spitch/types/text_tone_mark_params.py
index c79ca88..228c8f0 100644
--- a/src/spitch/types/text_tone_mark_params.py
+++ b/src/spitch/types/text_tone_mark_params.py
@@ -8,6 +8,6 @@
class TextToneMarkParams(TypedDict, total=False):
- language: Required[Literal["yo", "en", "ha", "ig", "am"]]
+ language: Required[Literal["yo", "en", "ha", "ig", "am", "pcm"]]
text: Required[str]
diff --git a/src/spitch/types/text_translate_params.py b/src/spitch/types/text_translate_params.py
index 073c81d..05fce7a 100644
--- a/src/spitch/types/text_translate_params.py
+++ b/src/spitch/types/text_translate_params.py
@@ -2,21 +2,14 @@
from __future__ import annotations
-from typing import Optional
from typing_extensions import Literal, Required, TypedDict
__all__ = ["TextTranslateParams"]
class TextTranslateParams(TypedDict, total=False):
- source: Required[Literal["yo", "en", "ha", "ig", "am"]]
+ source: Required[Literal["yo", "en", "ha", "ig", "am", "pcm"]]
- target: Required[Literal["yo", "en", "ha", "ig", "am"]]
+ target: Required[Literal["yo", "en", "ha", "ig", "am", "pcm"]]
- file_id: Optional[str]
-
- instructions: Optional[str]
-
- model: Literal["human", "auto"]
-
- text: Optional[str]
+ text: Required[str]
diff --git a/src/spitch/types/transcription.py b/src/spitch/types/transcription.py
deleted file mode 100644
index 36ecc1f..0000000
--- a/src/spitch/types/transcription.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from .._models import BaseModel
-
-__all__ = ["Transcription", "Timestamp"]
-
-
-class Timestamp(BaseModel):
- end: float
-
- start: float
-
- text: str
-
-
-class Transcription(BaseModel):
- request_id: str
-
- text: str
-
- timestamps: Optional[List[Timestamp]] = None
diff --git a/src/spitch/types/translation.py b/src/spitch/types/translation.py
index f5588ac..8d0ef60 100644
--- a/src/spitch/types/translation.py
+++ b/src/spitch/types/translation.py
@@ -1,24 +1,11 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
-from datetime import datetime
-
from .._models import BaseModel
__all__ = ["Translation"]
class Translation(BaseModel):
- """Translation result model.
-
- Attributes:
- request_id (UUID): Unique ID for this request.
- text: translated text.
- due_date: used when model is `human`. the date you can expect the translation to be delivered
- """
-
request_id: str
text: str
-
- due_date: Optional[datetime] = None
diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py
index b3dbbd9..7f37b8b 100644
--- a/tests/api_resources/test_files.py
+++ b/tests/api_resources/test_files.py
@@ -9,11 +9,7 @@
from spitch import Spitch, AsyncSpitch
from tests.utils import assert_matches_type
-from spitch.types import (
- File,
- FileUsage,
- FileDownloadResponse,
-)
+from spitch.types import File, FileUsage, FileDeleteResponse
from spitch.pagination import SyncFilesCursor, AsyncFilesCursor
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -32,7 +28,6 @@ def test_method_list_with_all_params(self, client: Spitch) -> None:
file = client.files.list(
cursor="cursor",
limit=99,
- status="uploading",
)
assert_matches_type(SyncFilesCursor[File], file, path=["response"])
@@ -61,7 +56,7 @@ def test_method_delete(self, client: Spitch) -> None:
file = client.files.delete(
"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
@parametrize
def test_raw_response_delete(self, client: Spitch) -> None:
@@ -72,7 +67,7 @@ def test_raw_response_delete(self, client: Spitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = response.parse()
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
@parametrize
def test_streaming_response_delete(self, client: Spitch) -> None:
@@ -83,7 +78,7 @@ def test_streaming_response_delete(self, client: Spitch) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = response.parse()
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -99,7 +94,7 @@ def test_method_download(self, client: Spitch) -> None:
file = client.files.download(
file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
def test_method_download_with_all_params(self, client: Spitch) -> None:
@@ -107,7 +102,7 @@ def test_method_download_with_all_params(self, client: Spitch) -> None:
file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
ttl=60,
)
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
def test_raw_response_download(self, client: Spitch) -> None:
@@ -118,7 +113,7 @@ def test_raw_response_download(self, client: Spitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = response.parse()
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
def test_streaming_response_download(self, client: Spitch) -> None:
@@ -129,7 +124,7 @@ def test_streaming_response_download(self, client: Spitch) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = response.parse()
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -250,7 +245,6 @@ async def test_method_list_with_all_params(self, async_client: AsyncSpitch) -> N
file = await async_client.files.list(
cursor="cursor",
limit=99,
- status="uploading",
)
assert_matches_type(AsyncFilesCursor[File], file, path=["response"])
@@ -279,7 +273,7 @@ async def test_method_delete(self, async_client: AsyncSpitch) -> None:
file = await async_client.files.delete(
"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
@parametrize
async def test_raw_response_delete(self, async_client: AsyncSpitch) -> None:
@@ -290,7 +284,7 @@ async def test_raw_response_delete(self, async_client: AsyncSpitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = await response.parse()
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
@parametrize
async def test_streaming_response_delete(self, async_client: AsyncSpitch) -> None:
@@ -301,7 +295,7 @@ async def test_streaming_response_delete(self, async_client: AsyncSpitch) -> Non
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = await response.parse()
- assert_matches_type(object, file, path=["response"])
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -317,7 +311,7 @@ async def test_method_download(self, async_client: AsyncSpitch) -> None:
file = await async_client.files.download(
file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
async def test_method_download_with_all_params(self, async_client: AsyncSpitch) -> None:
@@ -325,7 +319,7 @@ async def test_method_download_with_all_params(self, async_client: AsyncSpitch)
file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
ttl=60,
)
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
async def test_raw_response_download(self, async_client: AsyncSpitch) -> None:
@@ -336,7 +330,7 @@ async def test_raw_response_download(self, async_client: AsyncSpitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = await response.parse()
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
@parametrize
async def test_streaming_response_download(self, async_client: AsyncSpitch) -> None:
@@ -347,7 +341,7 @@ async def test_streaming_response_download(self, async_client: AsyncSpitch) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
file = await response.parse()
- assert_matches_type(FileDownloadResponse, file, path=["response"])
+ assert_matches_type(object, file, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_jobs.py b/tests/api_resources/test_jobs.py
deleted file mode 100644
index 829ebee..0000000
--- a/tests/api_resources/test_jobs.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from spitch import Spitch, AsyncSpitch
-from tests.utils import assert_matches_type
-from spitch.types import Job
-from spitch.pagination import SyncFilesCursor, AsyncFilesCursor
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestJobs:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_list(self, client: Spitch) -> None:
- job = client.jobs.list()
- assert_matches_type(SyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Spitch) -> None:
- job = client.jobs.list(
- cursor="cursor",
- limit=99,
- status="queued",
- )
- assert_matches_type(SyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Spitch) -> None:
- response = client.jobs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- job = response.parse()
- assert_matches_type(SyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Spitch) -> None:
- with client.jobs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- job = response.parse()
- assert_matches_type(SyncFilesCursor[Job], job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_method_get(self, client: Spitch) -> None:
- job = client.jobs.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- )
- assert_matches_type(Job, job, path=["response"])
-
- @parametrize
- def test_raw_response_get(self, client: Spitch) -> None:
- response = client.jobs.with_raw_response.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- job = response.parse()
- assert_matches_type(Job, job, path=["response"])
-
- @parametrize
- def test_streaming_response_get(self, client: Spitch) -> None:
- with client.jobs.with_streaming_response.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- job = response.parse()
- assert_matches_type(Job, job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- def test_path_params_get(self, client: Spitch) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"):
- client.jobs.with_raw_response.get(
- "",
- )
-
-
-class TestAsyncJobs:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncSpitch) -> None:
- job = await async_client.jobs.list()
- assert_matches_type(AsyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncSpitch) -> None:
- job = await async_client.jobs.list(
- cursor="cursor",
- limit=99,
- status="queued",
- )
- assert_matches_type(AsyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncSpitch) -> None:
- response = await async_client.jobs.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- job = await response.parse()
- assert_matches_type(AsyncFilesCursor[Job], job, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncSpitch) -> None:
- async with async_client.jobs.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- job = await response.parse()
- assert_matches_type(AsyncFilesCursor[Job], job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_method_get(self, async_client: AsyncSpitch) -> None:
- job = await async_client.jobs.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- )
- assert_matches_type(Job, job, path=["response"])
-
- @parametrize
- async def test_raw_response_get(self, async_client: AsyncSpitch) -> None:
- response = await async_client.jobs.with_raw_response.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- job = await response.parse()
- assert_matches_type(Job, job, path=["response"])
-
- @parametrize
- async def test_streaming_response_get(self, async_client: AsyncSpitch) -> None:
- async with async_client.jobs.with_streaming_response.get(
- "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- job = await response.parse()
- assert_matches_type(Job, job, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
- @parametrize
- async def test_path_params_get(self, async_client: AsyncSpitch) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"):
- await async_client.jobs.with_raw_response.get(
- "",
- )
diff --git a/tests/api_resources/test_speech.py b/tests/api_resources/test_speech.py
index 62290bd..1dcb99f 100644
--- a/tests/api_resources/test_speech.py
+++ b/tests/api_resources/test_speech.py
@@ -11,7 +11,7 @@
from spitch import Spitch, AsyncSpitch
from tests.utils import assert_matches_type
-from spitch.types import Transcription
+from spitch.types import SpeechTranscribeResponse
from spitch._response import (
BinaryAPIResponse,
AsyncBinaryAPIResponse,
@@ -47,8 +47,8 @@ def test_method_generate_with_all_params(self, client: Spitch, respx_mock: MockR
language="yo",
text="text",
voice="sade",
- format="wav",
- model="legacy",
+ model="model",
+ spitch_x_data_retention=True,
)
assert speech.is_closed
assert speech.json() == {"foo": "bar"}
@@ -94,7 +94,7 @@ def test_method_transcribe(self, client: Spitch) -> None:
speech = client.speech.transcribe(
language="yo",
)
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
def test_method_transcribe_with_all_params(self, client: Spitch) -> None:
@@ -105,8 +105,9 @@ def test_method_transcribe_with_all_params(self, client: Spitch) -> None:
special_words="special_words",
timestamp="sentence",
url="url",
+ spitch_x_data_retention=True,
)
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
def test_raw_response_transcribe(self, client: Spitch) -> None:
@@ -117,7 +118,7 @@ def test_raw_response_transcribe(self, client: Spitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
speech = response.parse()
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
def test_streaming_response_transcribe(self, client: Spitch) -> None:
@@ -128,7 +129,7 @@ def test_streaming_response_transcribe(self, client: Spitch) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
speech = response.parse()
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -160,8 +161,8 @@ async def test_method_generate_with_all_params(self, async_client: AsyncSpitch,
language="yo",
text="text",
voice="sade",
- format="wav",
- model="legacy",
+ model="model",
+ spitch_x_data_retention=True,
)
assert speech.is_closed
assert await speech.json() == {"foo": "bar"}
@@ -207,7 +208,7 @@ async def test_method_transcribe(self, async_client: AsyncSpitch) -> None:
speech = await async_client.speech.transcribe(
language="yo",
)
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
async def test_method_transcribe_with_all_params(self, async_client: AsyncSpitch) -> None:
@@ -218,8 +219,9 @@ async def test_method_transcribe_with_all_params(self, async_client: AsyncSpitch
special_words="special_words",
timestamp="sentence",
url="url",
+ spitch_x_data_retention=True,
)
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
async def test_raw_response_transcribe(self, async_client: AsyncSpitch) -> None:
@@ -230,7 +232,7 @@ async def test_raw_response_transcribe(self, async_client: AsyncSpitch) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
speech = await response.parse()
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
@parametrize
async def test_streaming_response_transcribe(self, async_client: AsyncSpitch) -> None:
@@ -241,6 +243,6 @@ async def test_streaming_response_transcribe(self, async_client: AsyncSpitch) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
speech = await response.parse()
- assert_matches_type(Transcription, speech, path=["response"])
+ assert_matches_type(SpeechTranscribeResponse, speech, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_text.py b/tests/api_resources/test_text.py
index cd78689..9630198 100644
--- a/tests/api_resources/test_text.py
+++ b/tests/api_resources/test_text.py
@@ -56,17 +56,6 @@ def test_method_translate(self, client: Spitch) -> None:
text = client.text.translate(
source="yo",
target="yo",
- )
- assert_matches_type(Translation, text, path=["response"])
-
- @parametrize
- def test_method_translate_with_all_params(self, client: Spitch) -> None:
- text = client.text.translate(
- source="yo",
- target="yo",
- file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- instructions="instructions",
- model="human",
text="text",
)
assert_matches_type(Translation, text, path=["response"])
@@ -76,6 +65,7 @@ def test_raw_response_translate(self, client: Spitch) -> None:
response = client.text.with_raw_response.translate(
source="yo",
target="yo",
+ text="text",
)
assert response.is_closed is True
@@ -88,6 +78,7 @@ def test_streaming_response_translate(self, client: Spitch) -> None:
with client.text.with_streaming_response.translate(
source="yo",
target="yo",
+ text="text",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -142,17 +133,6 @@ async def test_method_translate(self, async_client: AsyncSpitch) -> None:
text = await async_client.text.translate(
source="yo",
target="yo",
- )
- assert_matches_type(Translation, text, path=["response"])
-
- @parametrize
- async def test_method_translate_with_all_params(self, async_client: AsyncSpitch) -> None:
- text = await async_client.text.translate(
- source="yo",
- target="yo",
- file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- instructions="instructions",
- model="human",
text="text",
)
assert_matches_type(Translation, text, path=["response"])
@@ -162,6 +142,7 @@ async def test_raw_response_translate(self, async_client: AsyncSpitch) -> None:
response = await async_client.text.with_raw_response.translate(
source="yo",
target="yo",
+ text="text",
)
assert response.is_closed is True
@@ -174,6 +155,7 @@ async def test_streaming_response_translate(self, async_client: AsyncSpitch) ->
async with async_client.text.with_streaming_response.translate(
source="yo",
target="yo",
+ text="text",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
From 0da0a3e5ed565d2d0c62526c466f6a3db25f798b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:05:34 +0000
Subject: [PATCH 17/17] release: 1.42.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 30 ++++++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/spitch/_version.py | 2 +-
4 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b34aa0b..507912c 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.41.2"
+ ".": "1.42.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29b1836..f34ecde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## 1.42.0 (2026-01-21)
+
+Full Changelog: [v1.41.2...v1.42.0](https://github.com/spi-tch/spitch-python/compare/v1.41.2...v1.42.0)
+
+### Features
+
+* **api:** manual updates ([12f75ef](https://github.com/spi-tch/spitch-python/commit/12f75eff7f034196f0f453dfb4b4c800234d9198))
+* **client:** add support for binary request streaming ([0c96451](https://github.com/spi-tch/spitch-python/commit/0c96451d56e5897221e1a5e628c557f8d47df911))
+* **files:** add support for string alternative to file upload type ([7c71520](https://github.com/spi-tch/spitch-python/commit/7c7152096acff1ab2777e88ea6edf8e74762a366))
+
+
+### Bug Fixes
+
+* ensure streams are always closed ([2eaa7db](https://github.com/spi-tch/spitch-python/commit/2eaa7dbb883d8ae3ecf90da45dce26252a996f62))
+* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([49b3944](https://github.com/spi-tch/spitch-python/commit/49b39447feb57724b18dbabd08da07ff6fe10164))
+* use async_to_httpx_files in patch method ([d6681dc](https://github.com/spi-tch/spitch-python/commit/d6681dc12cb59df8843b6adbe09095efde66548b))
+
+
+### Chores
+
+* add missing docstrings ([5706a11](https://github.com/spi-tch/spitch-python/commit/5706a112946f0c294388455c32cf5c1e1bad270a))
+* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([ebbc73f](https://github.com/spi-tch/spitch-python/commit/ebbc73f41c762dc2adcbb3013ea695fdb3c50e2d))
+* **docs:** use environment variables for authentication in code snippets ([e74350e](https://github.com/spi-tch/spitch-python/commit/e74350e037dc2136577aa758e493e0261ce70166))
+* **internal:** add `--fix` argument to lint script ([697bfd0](https://github.com/spi-tch/spitch-python/commit/697bfd03e8bc2c855e36fbfcc23f6ecf7d740652))
+* **internal:** add missing files argument to base client ([d52ec35](https://github.com/spi-tch/spitch-python/commit/d52ec358ed429f302ff0c035d1b8b94cbe24e3e0))
+* **internal:** codegen related update ([d11c298](https://github.com/spi-tch/spitch-python/commit/d11c2988b89fde8ef890fe072869caf1692fe56a))
+* **internal:** update `actions/checkout` version ([9dfcb50](https://github.com/spi-tch/spitch-python/commit/9dfcb509c7bed5382e2de4663838fc0583774bc2))
+* speedup initial import ([8b385df](https://github.com/spi-tch/spitch-python/commit/8b385dffc6d34d7cbda6ba197d08637c98a6af37))
+* update lockfile ([442785c](https://github.com/spi-tch/spitch-python/commit/442785c97d9cb3ccee5403666a4cdbbf00338cea))
+
## 1.41.2 (2025-11-25)
Full Changelog: [v1.41.1...v1.41.2](https://github.com/spi-tch/spitch-python/compare/v1.41.1...v1.41.2)
diff --git a/pyproject.toml b/pyproject.toml
index 392d40e..fac1536 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "spitch"
-version = "1.41.2"
+version = "1.42.0"
description = "The official Python library for the spitch API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/spitch/_version.py b/src/spitch/_version.py
index 0e8814f..0c0bb79 100644
--- a/src/spitch/_version.py
+++ b/src/spitch/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "spitch"
-__version__ = "1.41.2" # x-release-please-version
+__version__ = "1.42.0" # x-release-please-version