diff --git a/.stats.yml b/.stats.yml index 163ba63..69b0709 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 9 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lmnt-kaikato-aryp6r%2Flmnt-com-e9b6dfacea7ad119a76705441211d3c25bbcc7c900f74685cd679a3bd84dac72.yml -openapi_spec_hash: 3b34a0865020e8a6dcbc02e2bfc6c638 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lmnt-kaikato-aryp6r%2Flmnt-com-65c2c6c535ed9613a2ec066fcb37e028c45e1a6cf67639d3f3b7d76b067cd3cf.yml +openapi_spec_hash: 53f47fb39d900e20ae936b3ea9bafa40 config_hash: ad76a808facacf5f53e58d591653bac6 diff --git a/.sync_state b/.sync_state index a966d36..09deafc 100644 --- a/.sync_state +++ b/.sync_state @@ -1 +1 @@ -9d6fd10cc3b0950f089efff59f40acfbd04378f6 +eb784943f5074ca7dbee7bd98865a25b164ac332 diff --git a/CHANGELOG.md b/CHANGELOG.md index fe07cfd..b1aa4c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v2.4.0 (2025-09-30) + +Full Changelog: [v2.3.0...v2.4.0](https://github.com/lmnt-com/lmnt-python/compare/v2.3.0...v2.4.0) + +- feat(api): api update ([6e0a4dd](https://github.com/lmnt-com/lmnt-python/commit/6e0a4dda62360a40d20a591ec744f2111a53a6e8)) +- feat(api): api update ([cc7f909](https://github.com/lmnt-com/lmnt-python/commit/cc7f909d59c7bca2cedfc204704cd31dbb32916c)) +- chore(internal): update pydantic dependency ([a4c5af5](https://github.com/lmnt-com/lmnt-python/commit/a4c5af517ea5fe4de75612fb596cb7e643115edb)) +- chore(types): change optional parameter type from NotGiven to Omit ([7c03fb8](https://github.com/lmnt-com/lmnt-python/commit/7c03fb8645d067dd9992b98da473df9f02b901fc)) +- chore: do not install brew dependencies in ./scripts/bootstrap by default ([75d2397](https://github.com/lmnt-com/lmnt-python/commit/75d23979700a889a7ceae787f8449ce66cc53733)) +- chore: improve example values ([5c0919c](https://github.com/lmnt-com/lmnt-python/commit/5c0919cd356a9624a9a1b5d0e4a802d7b107226f)) + + ## v2.3.0 (2025-09-03) Full Changelog: [v2.2.1...v2.3.0](https://github.com/lmnt-com/lmnt-python/compare/v2.2.1...v2.3.0) diff --git a/pyproject.toml b/pyproject.toml index 7ec1c93..9377043 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lmnt" -version = "2.3.0" +version = "2.4.0" description = "The official Python library for the LMNT API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/requirements-dev.lock b/requirements-dev.lock index 8460e60..f459af0 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -88,9 +88,9 @@ pluggy==1.5.0 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via lmnt -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic pygments==2.18.0 # via rich @@ -126,6 +126,9 @@ typing-extensions==4.12.2 # via pydantic # via pydantic-core # via pyright + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic virtualenv==20.24.5 # via nox websockets==15.0.1 diff --git a/requirements.lock b/requirements.lock index 36a1dbe..0ca15f4 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,9 +55,9 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via lmnt -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic sniffio==1.3.0 # via anyio @@ -68,6 +68,9 @@ typing-extensions==4.12.2 # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic websockets==15.0.1 # via lmnt yarl==1.20.0 diff --git a/scripts/bootstrap b/scripts/bootstrap index e84fe62..b430fee 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/src/lmnt/__init__.py b/src/lmnt/__init__.py index a6fc5c6..b4eecab 100644 --- a/src/lmnt/__init__.py +++ b/src/lmnt/__init__.py @@ -3,7 +3,7 @@ import typing as _t from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import Lmnt, Client, Stream, Timeout, AsyncLmnt, Transport, AsyncClient, AsyncStream, RequestOptions from ._models import BaseModel @@ -38,7 +38,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "LmntError", "APIError", "APIStatusError", diff --git a/src/lmnt/_base_client.py b/src/lmnt/_base_client.py index 94c1b4d..0fd976a 100644 --- a/src/lmnt/_base_client.py +++ b/src/lmnt/_base_client.py @@ -42,7 +42,6 @@ from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -57,6 +56,7 @@ RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump @@ -145,9 +145,9 @@ def __init__( def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - json: Body | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url self.json = json @@ -595,7 +595,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -825,7 +825,7 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1356,7 +1356,7 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1818,8 +1818,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/lmnt/_client.py b/src/lmnt/_client.py index 6920690..512e7c1 100644 --- a/src/lmnt/_client.py +++ b/src/lmnt/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping +from typing import Any, Mapping from typing_extensions import Self, override import httpx @@ -11,13 +11,13 @@ from . import _exceptions from ._qs import Querystring from ._types import ( - NOT_GIVEN, Omit, Timeout, NotGiven, Transport, ProxiesTypes, RequestOptions, + not_given, ) from ._utils import is_given, get_async_library from ._version import __version__ @@ -48,7 +48,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -125,9 +125,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -220,7 +220,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -297,9 +297,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, diff --git a/src/lmnt/_models.py b/src/lmnt/_models.py index 3a6017e..6a3cd1d 100644 --- a/src/lmnt/_models.py +++ b/src/lmnt/_models.py @@ -256,7 +256,7 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, @@ -264,6 +264,7 @@ def model_dump( warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, serialize_as_any: bool = False, + fallback: Callable[[Any], Any] | None = None, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -295,10 +296,12 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -313,13 +316,14 @@ def model_dump_json( indent: int | None = None, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -348,11 +352,13 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, diff --git a/src/lmnt/_qs.py b/src/lmnt/_qs.py index 274320c..ada6fd3 100644 --- a/src/lmnt/_qs.py +++ b/src/lmnt/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/lmnt/_types.py b/src/lmnt/_types.py index 8ca055d..6d9327d 100644 --- a/src/lmnt/_types.py +++ b/src/lmnt/_types.py @@ -117,18 +117,21 @@ class RequestOptions(TypedDict, total=False): # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -140,13 +143,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -156,8 +160,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -165,6 +169,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod diff --git a/src/lmnt/_utils/_transform.py b/src/lmnt/_utils/_transform.py index c19124f..5207549 100644 --- a/src/lmnt/_utils/_transform.py +++ b/src/lmnt/_utils/_transform.py @@ -268,7 +268,7 @@ def _transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue @@ -434,7 +434,7 @@ async def _async_transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue diff --git a/src/lmnt/_utils/_utils.py b/src/lmnt/_utils/_utils.py index f081859..50d5926 100644 --- a/src/lmnt/_utils/_utils.py +++ b/src/lmnt/_utils/_utils.py @@ -21,7 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -63,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -126,8 +126,8 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. diff --git a/src/lmnt/resources/accounts.py b/src/lmnt/resources/accounts.py index 657ba95..b3f4b66 100644 --- a/src/lmnt/resources/accounts.py +++ b/src/lmnt/resources/accounts.py @@ -4,7 +4,7 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -47,7 +47,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountRetrieveResponse: """Returns details about your account.""" return self._get( @@ -87,7 +87,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountRetrieveResponse: """Returns details about your account.""" return await self._get( diff --git a/src/lmnt/resources/speech.py b/src/lmnt/resources/speech.py index 5105a44..362bbc6 100644 --- a/src/lmnt/resources/speech.py +++ b/src/lmnt/resources/speech.py @@ -8,7 +8,7 @@ import httpx from ..types import speech_convert_params, speech_generate_params, speech_generate_detailed_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes +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 .._compat import cached_property from .sessions import AsyncSessionsResource @@ -58,7 +58,7 @@ def convert( *, audio: FileTypes, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -83,14 +83,14 @@ def convert( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, + | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BinaryAPIResponse: """ Converts speech from one voice to another. @@ -163,7 +163,8 @@ def generate( *, text: str, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + debug: bool | Omit = omit, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -188,18 +189,18 @@ def generate( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - model: Literal["blizzard"] | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, - seed: int | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - top_p: float | NotGiven = NOT_GIVEN, + | Omit = omit, + model: Literal["blizzard"] | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | Omit = omit, + seed: int | Omit = omit, + temperature: float | Omit = omit, + top_p: float | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BinaryAPIResponse: """ Generates speech from text and streams the audio as binary data chunks in @@ -215,6 +216,9 @@ def generate( voice: The voice id of the voice to use; voice ids can be retrieved by calls to `List voices` or `Voice info`. + debug: When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + format: The desired output format of the audio. If you are using a streaming endpoint, you'll generate audio faster by selecting a streamable format since chunks are encoded and returned as they're generated. For non-streamable formats, the @@ -234,7 +238,7 @@ def generate( - `wav`: 16-bit PCM audio in WAV container. language: The desired language. Two letter ISO 639-1 code. Defaults to auto language - detection. + detection, but specifying the language is recommended for faster generation. model: The model to use for synthesis. Learn more about models [here](https://docs.lmnt.com/guides/models). @@ -268,6 +272,7 @@ def generate( { "text": text, "voice": voice, + "debug": debug, "format": format, "language": language, "model": model, @@ -289,7 +294,8 @@ def generate_detailed( *, text: str, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + debug: bool | Omit = omit, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -314,19 +320,19 @@ def generate_detailed( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - model: Literal["blizzard"] | NotGiven = NOT_GIVEN, - return_durations: bool | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, - seed: int | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - top_p: float | NotGiven = NOT_GIVEN, + | Omit = omit, + model: Literal["blizzard"] | Omit = omit, + return_durations: bool | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | Omit = omit, + seed: int | Omit = omit, + temperature: float | Omit = omit, + top_p: float | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SpeechGenerateDetailedResponse: """ Generates speech from text and returns a JSON object that contains a @@ -340,6 +346,9 @@ def generate_detailed( voice: The voice id of the voice to use; voice ids can be retrieved by calls to `List voices` or `Voice info`. + debug: When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + format: The desired output format of the audio. If you are using a streaming endpoint, you'll generate audio faster by selecting a streamable format since chunks are encoded and returned as they're generated. For non-streamable formats, the @@ -359,7 +368,7 @@ def generate_detailed( - `wav`: 16-bit PCM audio in WAV container. language: The desired language. Two letter ISO 639-1 code. Defaults to auto language - detection. + detection, but specifying the language is recommended for faster generation. model: The model to use for synthesis. Learn more about models [here](https://docs.lmnt.com/guides/models). @@ -394,6 +403,7 @@ def generate_detailed( { "text": text, "voice": voice, + "debug": debug, "format": format, "language": language, "model": model, @@ -441,7 +451,7 @@ async def convert( *, audio: FileTypes, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -466,14 +476,14 @@ async def convert( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, + | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncBinaryAPIResponse: """ Converts speech from one voice to another. @@ -546,7 +556,8 @@ async def generate( *, text: str, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + debug: bool | Omit = omit, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -571,18 +582,18 @@ async def generate( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - model: Literal["blizzard"] | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, - seed: int | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - top_p: float | NotGiven = NOT_GIVEN, + | Omit = omit, + model: Literal["blizzard"] | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | Omit = omit, + seed: int | Omit = omit, + temperature: float | Omit = omit, + top_p: float | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncBinaryAPIResponse: """ Generates speech from text and streams the audio as binary data chunks in @@ -598,6 +609,9 @@ async def generate( voice: The voice id of the voice to use; voice ids can be retrieved by calls to `List voices` or `Voice info`. + debug: When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + format: The desired output format of the audio. If you are using a streaming endpoint, you'll generate audio faster by selecting a streamable format since chunks are encoded and returned as they're generated. For non-streamable formats, the @@ -617,7 +631,7 @@ async def generate( - `wav`: 16-bit PCM audio in WAV container. language: The desired language. Two letter ISO 639-1 code. Defaults to auto language - detection. + detection, but specifying the language is recommended for faster generation. model: The model to use for synthesis. Learn more about models [here](https://docs.lmnt.com/guides/models). @@ -651,6 +665,7 @@ async def generate( { "text": text, "voice": voice, + "debug": debug, "format": format, "language": language, "model": model, @@ -672,7 +687,8 @@ async def generate_detailed( *, text: str, voice: str, - format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | NotGiven = NOT_GIVEN, + debug: bool | Omit = omit, + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] | Omit = omit, language: Literal[ "auto", "ar", @@ -697,19 +713,19 @@ async def generate_detailed( "vi", "zh", ] - | NotGiven = NOT_GIVEN, - model: Literal["blizzard"] | NotGiven = NOT_GIVEN, - return_durations: bool | NotGiven = NOT_GIVEN, - sample_rate: Literal[8000, 16000, 24000] | NotGiven = NOT_GIVEN, - seed: int | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - top_p: float | NotGiven = NOT_GIVEN, + | Omit = omit, + model: Literal["blizzard"] | Omit = omit, + return_durations: bool | Omit = omit, + sample_rate: Literal[8000, 16000, 24000] | Omit = omit, + seed: int | Omit = omit, + temperature: float | Omit = omit, + top_p: float | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SpeechGenerateDetailedResponse: """ Generates speech from text and returns a JSON object that contains a @@ -723,6 +739,9 @@ async def generate_detailed( voice: The voice id of the voice to use; voice ids can be retrieved by calls to `List voices` or `Voice info`. + debug: When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + format: The desired output format of the audio. If you are using a streaming endpoint, you'll generate audio faster by selecting a streamable format since chunks are encoded and returned as they're generated. For non-streamable formats, the @@ -742,7 +761,7 @@ async def generate_detailed( - `wav`: 16-bit PCM audio in WAV container. language: The desired language. Two letter ISO 639-1 code. Defaults to auto language - detection. + detection, but specifying the language is recommended for faster generation. model: The model to use for synthesis. Learn more about models [here](https://docs.lmnt.com/guides/models). @@ -777,6 +796,7 @@ async def generate_detailed( { "text": text, "voice": voice, + "debug": debug, "format": format, "language": language, "model": model, diff --git a/src/lmnt/resources/voices.py b/src/lmnt/resources/voices.py index 0eb0f45..d3371a7 100644 --- a/src/lmnt/resources/voices.py +++ b/src/lmnt/resources/voices.py @@ -7,7 +7,7 @@ import httpx from ..types import voice_list_params, voice_create_params, voice_update_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes, SequenceNotStr +from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, SequenceNotStr, omit, not_given from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -52,14 +52,14 @@ def create( enhance: bool, files: SequenceNotStr[FileTypes], name: str, - description: str | NotGiven = NOT_GIVEN, - gender: str | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + gender: str | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Voice: """ Submits a request to create a voice with a supplied voice configuration and a @@ -125,7 +125,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Voice: """ Returns details of a specific voice. @@ -153,16 +153,16 @@ def update( self, id: str, *, - description: str | NotGiven = NOT_GIVEN, - gender: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - starred: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + gender: str | Omit = omit, + name: str | Omit = omit, + starred: 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceUpdateResponse: """Updates metadata for a specific voice. @@ -207,14 +207,14 @@ def update( def list( self, *, - owner: str | NotGiven = NOT_GIVEN, - starred: str | NotGiven = NOT_GIVEN, + owner: str | Omit = omit, + starred: str | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceListResponse: """ Returns a list of voices available to you. @@ -259,7 +259,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceDeleteResponse: """Deletes a voice and cancels any pending operations on it. @@ -311,14 +311,14 @@ async def create( enhance: bool, files: SequenceNotStr[FileTypes], name: str, - description: str | NotGiven = NOT_GIVEN, - gender: str | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + gender: str | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Voice: """ Submits a request to create a voice with a supplied voice configuration and a @@ -384,7 +384,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Voice: """ Returns details of a specific voice. @@ -412,16 +412,16 @@ async def update( self, id: str, *, - description: str | NotGiven = NOT_GIVEN, - gender: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - starred: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + gender: str | Omit = omit, + name: str | Omit = omit, + starred: 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceUpdateResponse: """Updates metadata for a specific voice. @@ -466,14 +466,14 @@ async def update( async def list( self, *, - owner: str | NotGiven = NOT_GIVEN, - starred: str | NotGiven = NOT_GIVEN, + owner: str | Omit = omit, + starred: str | 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceListResponse: """ Returns a list of voices available to you. @@ -518,7 +518,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VoiceDeleteResponse: """Deletes a voice and cancels any pending operations on it. diff --git a/src/lmnt/types/speech_generate_detailed_params.py b/src/lmnt/types/speech_generate_detailed_params.py index f7420b5..2cea390 100644 --- a/src/lmnt/types/speech_generate_detailed_params.py +++ b/src/lmnt/types/speech_generate_detailed_params.py @@ -17,6 +17,12 @@ class SpeechGenerateDetailedParams(TypedDict, total=False): `List voices` or `Voice info`. """ + debug: bool + """ + When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + """ + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] """The desired output format of the audio. @@ -65,7 +71,8 @@ class SpeechGenerateDetailedParams(TypedDict, total=False): ] """The desired language. - Two letter ISO 639-1 code. Defaults to auto language detection. + Two letter ISO 639-1 code. Defaults to auto language detection, but specifying + the language is recommended for faster generation. """ model: Literal["blizzard"] diff --git a/src/lmnt/types/speech_generate_params.py b/src/lmnt/types/speech_generate_params.py index ef3cdbd..775f3fd 100644 --- a/src/lmnt/types/speech_generate_params.py +++ b/src/lmnt/types/speech_generate_params.py @@ -17,6 +17,12 @@ class SpeechGenerateParams(TypedDict, total=False): `List voices` or `Voice info`. """ + debug: bool + """ + When set to true, the generated speech will also be saved to your + [clip library](https://app.lmnt.com/clips) in the LMNT playground. + """ + format: Literal["aac", "mp3", "ulaw", "wav", "webm", "pcm_s16le", "pcm_f32le"] """The desired output format of the audio. @@ -65,7 +71,8 @@ class SpeechGenerateParams(TypedDict, total=False): ] """The desired language. - Two letter ISO 639-1 code. Defaults to auto language detection. + Two letter ISO 639-1 code. Defaults to auto language detection, but specifying + the language is recommended for faster generation. """ model: Literal["blizzard"] diff --git a/tests/api_resources/test_speech.py b/tests/api_resources/test_speech.py index a92eaba..95d5e51 100644 --- a/tests/api_resources/test_speech.py +++ b/tests/api_resources/test_speech.py @@ -108,6 +108,7 @@ def test_method_generate_with_all_params(self, client: Lmnt, respx_mock: MockRou speech = client.speech.generate( text="hello world.", voice="leah", + debug=True, format="aac", language="auto", model="blizzard", @@ -166,6 +167,7 @@ def test_method_generate_detailed_with_all_params(self, client: Lmnt) -> None: speech = client.speech.generate_detailed( text="hello world.", voice="leah", + debug=True, format="aac", language="auto", model="blizzard", @@ -290,6 +292,7 @@ async def test_method_generate_with_all_params(self, async_client: AsyncLmnt, re speech = await async_client.speech.generate( text="hello world.", voice="leah", + debug=True, format="aac", language="auto", model="blizzard", @@ -348,6 +351,7 @@ async def test_method_generate_detailed_with_all_params(self, async_client: Asyn speech = await async_client.speech.generate_detailed( text="hello world.", voice="leah", + debug=True, format="aac", language="auto", model="blizzard", diff --git a/tests/api_resources/test_voices.py b/tests/api_resources/test_voices.py index d8306a9..59c286b 100644 --- a/tests/api_resources/test_voices.py +++ b/tests/api_resources/test_voices.py @@ -77,14 +77,14 @@ def test_streaming_response_create(self, client: Lmnt) -> None: @parametrize def test_method_retrieve(self, client: Lmnt) -> None: voice = client.voices.retrieve( - "id", + "123", ) assert_matches_type(Voice, voice, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Lmnt) -> None: response = client.voices.with_raw_response.retrieve( - "id", + "123", ) assert response.is_closed is True @@ -95,7 +95,7 @@ def test_raw_response_retrieve(self, client: Lmnt) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lmnt) -> None: with client.voices.with_streaming_response.retrieve( - "id", + "123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -197,14 +197,14 @@ def test_streaming_response_list(self, client: Lmnt) -> None: @parametrize def test_method_delete(self, client: Lmnt) -> None: voice = client.voices.delete( - "id", + "123", ) assert_matches_type(VoiceDeleteResponse, voice, path=["response"]) @parametrize def test_raw_response_delete(self, client: Lmnt) -> None: response = client.voices.with_raw_response.delete( - "id", + "123", ) assert response.is_closed is True @@ -215,7 +215,7 @@ def test_raw_response_delete(self, client: Lmnt) -> None: @parametrize def test_streaming_response_delete(self, client: Lmnt) -> None: with client.voices.with_streaming_response.delete( - "id", + "123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -293,14 +293,14 @@ async def test_streaming_response_create(self, async_client: AsyncLmnt) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncLmnt) -> None: voice = await async_client.voices.retrieve( - "id", + "123", ) assert_matches_type(Voice, voice, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLmnt) -> None: response = await async_client.voices.with_raw_response.retrieve( - "id", + "123", ) assert response.is_closed is True @@ -311,7 +311,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLmnt) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLmnt) -> None: async with async_client.voices.with_streaming_response.retrieve( - "id", + "123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -413,14 +413,14 @@ async def test_streaming_response_list(self, async_client: AsyncLmnt) -> None: @parametrize async def test_method_delete(self, async_client: AsyncLmnt) -> None: voice = await async_client.voices.delete( - "id", + "123", ) assert_matches_type(VoiceDeleteResponse, voice, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncLmnt) -> None: response = await async_client.voices.with_raw_response.delete( - "id", + "123", ) assert response.is_closed is True @@ -431,7 +431,7 @@ async def test_raw_response_delete(self, async_client: AsyncLmnt) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncLmnt) -> None: async with async_client.voices.with_streaming_response.delete( - "id", + "123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/test_transform.py b/tests/test_transform.py index 16b92c7..941a7e5 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from lmnt._types import NOT_GIVEN, Base64FileInput +from lmnt._types import Base64FileInput, omit, not_given from lmnt._utils import ( PropertyInfo, transform as _transform, @@ -450,4 +450,11 @@ async def test_transform_skipping(use_async: bool) -> None: @pytest.mark.asyncio async def test_strips_notgiven(use_async: bool) -> None: assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {}