From b57dc83678fb8f51d3dbb847b18c6edab89ce199 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 11 Apr 2025 17:01:46 +0200
Subject: [PATCH] release: 0.3.0 (#77)
* chore(internal): remove extra empty newlines (#76)
* chore(internal): bump rye to 0.44.0 (#78)
* chore(internal): codegen related update (#79)
* fix(types): handle more discriminated union shapes (#80)
* fix(ci): ensure pip is always available (#81)
* fix(ci): remove publishing patch (#82)
* codegen metadata
* chore: fix typos (#83)
* codegen metadata
* codegen metadata
* chore(internal): remove trailing character (#84)
* chore(internal): slight transform perf improvement (#85)
* chore(tests): improve enum examples (#86)
* chore(internal): expand CI branch coverage (#87)
* chore(internal): reduce CI branch coverage
* fix(perf): skip traversing types for NotGiven values
* fix(perf): optimize some hot paths
* feat(api): update via SDK Studio
* feat(api): update via SDK Studio
* release: 0.3.0
---------
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
---
.devcontainer/Dockerfile | 2 +-
.github/workflows/ci.yml | 6 +-
.github/workflows/publish-pypi.yml | 2 +-
.release-please-manifest.json | 2 +-
.stats.yml | 6 +-
CHANGELOG.md | 31 ++
api.md | 21 +-
bin/publish-pypi | 3 -
pyproject.toml | 6 +-
requirements-dev.lock | 1 +
requirements.lock | 1 +
src/prelude_python_sdk/_client.py | 10 +-
src/prelude_python_sdk/_models.py | 9 +-
src/prelude_python_sdk/_utils/_transform.py | 49 ++-
src/prelude_python_sdk/_utils/_typing.py | 2 +
src/prelude_python_sdk/_version.py | 2 +-
src/prelude_python_sdk/resources/__init__.py | 14 +
src/prelude_python_sdk/resources/lookup.py | 199 +++++++++++
.../resources/verification.py | 22 +-
src/prelude_python_sdk/resources/watch.py | 237 ++++++++-----
src/prelude_python_sdk/types/__init__.py | 8 +-
.../types/lookup_lookup_params.py | 17 +
.../types/lookup_lookup_response.py | 116 +++++++
.../types/verification_create_params.py | 19 +-
.../types/watch_feed_back_params.py | 38 ---
.../types/watch_feed_back_response.py | 11 -
.../types/watch_predict_params.py | 49 ++-
.../types/watch_predict_response.py | 26 +-
.../types/watch_send_events_params.py | 32 ++
.../types/watch_send_events_response.py | 18 +
.../types/watch_send_feedbacks_params.py | 86 +++++
.../types/watch_send_feedbacks_response.py | 18 +
tests/api_resources/test_lookup.py | 114 +++++++
tests/api_resources/test_verification.py | 6 +-
tests/api_resources/test_watch.py | 313 +++++++++++++-----
tests/test_client.py | 2 +-
tests/test_models.py | 32 ++
tests/test_transform.py | 21 +-
38 files changed, 1278 insertions(+), 273 deletions(-)
create mode 100644 src/prelude_python_sdk/resources/lookup.py
create mode 100644 src/prelude_python_sdk/types/lookup_lookup_params.py
create mode 100644 src/prelude_python_sdk/types/lookup_lookup_response.py
delete mode 100644 src/prelude_python_sdk/types/watch_feed_back_params.py
delete mode 100644 src/prelude_python_sdk/types/watch_feed_back_response.py
create mode 100644 src/prelude_python_sdk/types/watch_send_events_params.py
create mode 100644 src/prelude_python_sdk/types/watch_send_events_response.py
create mode 100644 src/prelude_python_sdk/types/watch_send_feedbacks_params.py
create mode 100644 src/prelude_python_sdk/types/watch_send_feedbacks_response.py
create mode 100644 tests/api_resources/test_lookup.py
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 55d2025..ff261ba 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
USER vscode
-RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash
ENV PATH=/home/vscode/.rye/shims:$PATH
RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8a8a4f..81f6dc2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,6 @@ jobs:
lint:
name: lint
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
@@ -21,7 +20,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Install dependencies
@@ -33,7 +32,6 @@ jobs:
test:
name: test
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
@@ -42,7 +40,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Bootstrap
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index b1deba8..1ccb8d9 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -21,7 +21,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Publish to PyPI
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 10f3091..6b7b74c 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.2.0"
+ ".": "0.3.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 6028d62..2bb2a0e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,4 @@
-configured_endpoints: 5
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude%2Fprelude-ca3f4103971d8bfdb9ea7c345b6112409a62e183460acd29da40a155192d2213.yml
+configured_endpoints: 7
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/prelude%2Fprelude-d14cd19a9858e9e255cc1e21227462f83b2964beac2022e77203e4d60020ff74.yml
+openapi_spec_hash: ecbc1fb11f938b50db2b7d0a9aa325fa
+config_hash: 3b3da533cbc4a28dd64565b6c3fa3dd0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1961b2f..418704f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## 0.3.0 (2025-04-11)
+
+Full Changelog: [v0.2.0...v0.3.0](https://github.com/prelude-so/python-sdk/compare/v0.2.0...v0.3.0)
+
+### Features
+
+* **api:** update via SDK Studio ([e8db40d](https://github.com/prelude-so/python-sdk/commit/e8db40d0c6bb7ed120d01c7a5133e84611fa2dc5))
+* **api:** update via SDK Studio ([2738f74](https://github.com/prelude-so/python-sdk/commit/2738f749089da145689c78aabdedf810d3329826))
+
+
+### Bug Fixes
+
+* **ci:** ensure pip is always available ([#81](https://github.com/prelude-so/python-sdk/issues/81)) ([3496a08](https://github.com/prelude-so/python-sdk/commit/3496a088c4a51ff9755df7d5537031d2b66224b8))
+* **ci:** remove publishing patch ([#82](https://github.com/prelude-so/python-sdk/issues/82)) ([00fa879](https://github.com/prelude-so/python-sdk/commit/00fa8799dc14bc3d2dae941485f2e3a24bfb2bf3))
+* **perf:** optimize some hot paths ([6203988](https://github.com/prelude-so/python-sdk/commit/6203988ff6273cfe5135ec7d427c620a1094f6a1))
+* **perf:** skip traversing types for NotGiven values ([e5a8fd5](https://github.com/prelude-so/python-sdk/commit/e5a8fd59dd7168e68ff026e9d11d796e3d002241))
+* **types:** handle more discriminated union shapes ([#80](https://github.com/prelude-so/python-sdk/issues/80)) ([716195b](https://github.com/prelude-so/python-sdk/commit/716195b1874b4ec76cd39465810e3500c756eae8))
+
+
+### Chores
+
+* fix typos ([#83](https://github.com/prelude-so/python-sdk/issues/83)) ([ab98ad3](https://github.com/prelude-so/python-sdk/commit/ab98ad32961298cf1a2f47e6b3cc66a9f69cddbc))
+* **internal:** bump rye to 0.44.0 ([#78](https://github.com/prelude-so/python-sdk/issues/78)) ([436ceca](https://github.com/prelude-so/python-sdk/commit/436ceca01c22fd4015010d5bd3852ce319d0ed65))
+* **internal:** codegen related update ([#79](https://github.com/prelude-so/python-sdk/issues/79)) ([e5e9c6d](https://github.com/prelude-so/python-sdk/commit/e5e9c6d643232cad96a285dd7dc662f98684fbdc))
+* **internal:** expand CI branch coverage ([#87](https://github.com/prelude-so/python-sdk/issues/87)) ([3edb1aa](https://github.com/prelude-so/python-sdk/commit/3edb1aab16d2b38705d64969e9ac70fe00951ee6))
+* **internal:** reduce CI branch coverage ([70118ea](https://github.com/prelude-so/python-sdk/commit/70118ea5611c2f4337b21ac7da52a740bc52d7ff))
+* **internal:** remove extra empty newlines ([#76](https://github.com/prelude-so/python-sdk/issues/76)) ([3e52319](https://github.com/prelude-so/python-sdk/commit/3e5231901ad7bcc6e06a4c82aeaa619f759434f7))
+* **internal:** remove trailing character ([#84](https://github.com/prelude-so/python-sdk/issues/84)) ([526b990](https://github.com/prelude-so/python-sdk/commit/526b990f47cf42a44064d85ed2d8f9acbe35a609))
+* **internal:** slight transform perf improvement ([#85](https://github.com/prelude-so/python-sdk/issues/85)) ([b77e93b](https://github.com/prelude-so/python-sdk/commit/b77e93ba797273dd8a145183d9b9c712659163cd))
+* **tests:** improve enum examples ([#86](https://github.com/prelude-so/python-sdk/issues/86)) ([140d696](https://github.com/prelude-so/python-sdk/commit/140d6966a00666b4ade42fef7de7ec701cf697d3))
+
## 0.2.0 (2025-03-11)
Full Changelog: [v0.1.0...v0.2.0](https://github.com/prelude-so/python-sdk/compare/v0.1.0...v0.2.0)
diff --git a/api.md b/api.md
index 041f85b..87b5a5c 100644
--- a/api.md
+++ b/api.md
@@ -1,3 +1,15 @@
+# Lookup
+
+Types:
+
+```python
+from prelude_python_sdk.types import LookupLookupResponse
+```
+
+Methods:
+
+- client.lookup.lookup(phone_number, \*\*params) -> LookupLookupResponse
+
# Transactional
Types:
@@ -28,10 +40,15 @@ Methods:
Types:
```python
-from prelude_python_sdk.types import WatchFeedBackResponse, WatchPredictResponse
+from prelude_python_sdk.types import (
+ WatchPredictResponse,
+ WatchSendEventsResponse,
+ WatchSendFeedbacksResponse,
+)
```
Methods:
-- client.watch.feed_back(\*\*params) -> WatchFeedBackResponse
- client.watch.predict(\*\*params) -> WatchPredictResponse
+- client.watch.send_events(\*\*params) -> WatchSendEventsResponse
+- client.watch.send_feedbacks(\*\*params) -> WatchSendFeedbacksResponse
diff --git a/bin/publish-pypi b/bin/publish-pypi
index 05bfccb..826054e 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -3,7 +3,4 @@
set -eux
mkdir -p dist
rye build --clean
-# Patching importlib-metadata version until upstream library version is updated
-# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
-"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
diff --git a/pyproject.toml b/pyproject.toml
index 619f4b3..b609f29 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "prelude-python-sdk"
-version = "0.2.0"
+version = "0.3.0"
description = "The official Python library for the Prelude API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -38,7 +38,6 @@ Homepage = "https://github.com/prelude-so/python-sdk"
Repository = "https://github.com/prelude-so/python-sdk"
-
[tool.rye]
managed = true
# version pins are in requirements-dev.lock
@@ -87,7 +86,7 @@ typecheck = { chain = [
"typecheck:mypy" = "mypy ."
[build-system]
-requires = ["hatchling", "hatch-fancy-pypi-readme"]
+requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[tool.hatch.build]
@@ -152,7 +151,6 @@ reportImplicitOverride = true
reportImportCycles = false
reportPrivateUsage = false
-
[tool.ruff]
line-length = 120
output-format = "grouped"
diff --git a/requirements-dev.lock b/requirements-dev.lock
index baa8124..a35ea0c 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -7,6 +7,7 @@
# all-features: true
# with-sources: false
# generate-hashes: false
+# universal: false
-e file:.
annotated-types==0.6.0
diff --git a/requirements.lock b/requirements.lock
index 2fe1f85..c5e53b6 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -7,6 +7,7 @@
# all-features: true
# with-sources: false
# generate-hashes: false
+# universal: false
-e file:.
annotated-types==0.6.0
diff --git a/src/prelude_python_sdk/_client.py b/src/prelude_python_sdk/_client.py
index e2ad734..fae0d87 100644
--- a/src/prelude_python_sdk/_client.py
+++ b/src/prelude_python_sdk/_client.py
@@ -24,7 +24,7 @@
get_async_library,
)
from ._version import __version__
-from .resources import watch, verification, transactional
+from .resources import watch, lookup, verification, transactional
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import PreludeError, APIStatusError
from ._base_client import (
@@ -37,6 +37,7 @@
class Prelude(SyncAPIClient):
+ lookup: lookup.LookupResource
transactional: transactional.TransactionalResource
verification: verification.VerificationResource
watch: watch.WatchResource
@@ -97,6 +98,7 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
+ self.lookup = lookup.LookupResource(self)
self.transactional = transactional.TransactionalResource(self)
self.verification = verification.VerificationResource(self)
self.watch = watch.WatchResource(self)
@@ -209,6 +211,7 @@ def _make_status_error(
class AsyncPrelude(AsyncAPIClient):
+ lookup: lookup.AsyncLookupResource
transactional: transactional.AsyncTransactionalResource
verification: verification.AsyncVerificationResource
watch: watch.AsyncWatchResource
@@ -269,6 +272,7 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
+ self.lookup = lookup.AsyncLookupResource(self)
self.transactional = transactional.AsyncTransactionalResource(self)
self.verification = verification.AsyncVerificationResource(self)
self.watch = watch.AsyncWatchResource(self)
@@ -382,6 +386,7 @@ def _make_status_error(
class PreludeWithRawResponse:
def __init__(self, client: Prelude) -> None:
+ self.lookup = lookup.LookupResourceWithRawResponse(client.lookup)
self.transactional = transactional.TransactionalResourceWithRawResponse(client.transactional)
self.verification = verification.VerificationResourceWithRawResponse(client.verification)
self.watch = watch.WatchResourceWithRawResponse(client.watch)
@@ -389,6 +394,7 @@ def __init__(self, client: Prelude) -> None:
class AsyncPreludeWithRawResponse:
def __init__(self, client: AsyncPrelude) -> None:
+ self.lookup = lookup.AsyncLookupResourceWithRawResponse(client.lookup)
self.transactional = transactional.AsyncTransactionalResourceWithRawResponse(client.transactional)
self.verification = verification.AsyncVerificationResourceWithRawResponse(client.verification)
self.watch = watch.AsyncWatchResourceWithRawResponse(client.watch)
@@ -396,6 +402,7 @@ def __init__(self, client: AsyncPrelude) -> None:
class PreludeWithStreamedResponse:
def __init__(self, client: Prelude) -> None:
+ self.lookup = lookup.LookupResourceWithStreamingResponse(client.lookup)
self.transactional = transactional.TransactionalResourceWithStreamingResponse(client.transactional)
self.verification = verification.VerificationResourceWithStreamingResponse(client.verification)
self.watch = watch.WatchResourceWithStreamingResponse(client.watch)
@@ -403,6 +410,7 @@ def __init__(self, client: Prelude) -> None:
class AsyncPreludeWithStreamedResponse:
def __init__(self, client: AsyncPrelude) -> None:
+ self.lookup = lookup.AsyncLookupResourceWithStreamingResponse(client.lookup)
self.transactional = transactional.AsyncTransactionalResourceWithStreamingResponse(client.transactional)
self.verification = verification.AsyncVerificationResourceWithStreamingResponse(client.verification)
self.watch = watch.AsyncWatchResourceWithStreamingResponse(client.watch)
diff --git a/src/prelude_python_sdk/_models.py b/src/prelude_python_sdk/_models.py
index c4401ff..3493571 100644
--- a/src/prelude_python_sdk/_models.py
+++ b/src/prelude_python_sdk/_models.py
@@ -65,7 +65,7 @@
from ._constants import RAW_RESPONSE_HEADER
if TYPE_CHECKING:
- from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema
+ from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
__all__ = ["BaseModel", "GenericModel"]
@@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
schema = model.__pydantic_core_schema__
+ if schema["type"] == "definitions":
+ schema = schema["schema"]
+
if schema["type"] != "model":
return None
+ schema = cast("ModelSchema", schema)
fields_schema = schema["schema"]
if fields_schema["type"] != "model-fields":
return None
fields_schema = cast("ModelFieldsSchema", fields_schema)
-
field = fields_schema["fields"].get(field_name)
if not field:
return None
@@ -678,7 +681,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
setattr(typ, "__pydantic_config__", config) # noqa: B010
-# our use of subclasssing here causes weirdness for type checkers,
+# our use of subclassing here causes weirdness for type checkers,
# so we just pretend that we don't subclass
if TYPE_CHECKING:
GenericModel = BaseModel
diff --git a/src/prelude_python_sdk/_utils/_transform.py b/src/prelude_python_sdk/_utils/_transform.py
index 18afd9d..b0cc20a 100644
--- a/src/prelude_python_sdk/_utils/_transform.py
+++ b/src/prelude_python_sdk/_utils/_transform.py
@@ -5,13 +5,15 @@
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
-from typing_extensions import Literal, get_args, override, get_type_hints
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
import anyio
import pydantic
from ._utils import (
is_list,
+ is_given,
+ lru_cache,
is_mapping,
is_iterable,
)
@@ -108,6 +110,7 @@ class Params(TypedDict, total=False):
return cast(_T, transformed)
+@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
"""If the given type is an `Annotated` type then it is returned, if not `None` is returned.
@@ -126,7 +129,7 @@ def _get_annotated_type(type_: type) -> type | None:
def _maybe_transform_key(key: str, type_: type) -> str:
"""Transform the given `data` based on the annotations provided in `type_`.
- Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata.
+ Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
"""
annotated_type = _get_annotated_type(type_)
if annotated_type is None:
@@ -142,6 +145,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
return key
+def _no_transform_needed(annotation: type) -> bool:
+ return annotation == float or annotation == int
+
+
def _transform_recursive(
data: object,
*,
@@ -184,6 +191,15 @@ def _transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
@@ -245,6 +261,11 @@ def _transform_typeddict(
result: dict[str, object] = {}
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
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
@@ -332,6 +353,15 @@ async def _async_transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
@@ -393,6 +423,11 @@ async def _async_transform_typeddict(
result: dict[str, object] = {}
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
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
@@ -400,3 +435,13 @@ async def _async_transform_typeddict(
else:
result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+ obj: Any,
+ globalns: dict[str, Any] | None = None,
+ localns: Mapping[str, Any] | None = None,
+ include_extras: bool = False,
+) -> dict[str, Any]:
+ return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/prelude_python_sdk/_utils/_typing.py b/src/prelude_python_sdk/_utils/_typing.py
index 278749b..1958820 100644
--- a/src/prelude_python_sdk/_utils/_typing.py
+++ b/src/prelude_python_sdk/_utils/_typing.py
@@ -13,6 +13,7 @@
get_origin,
)
+from ._utils import lru_cache
from .._types import InheritsGeneric
from .._compat import is_union as _is_union
@@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
if is_required_type(typ) or is_annotated_type(typ):
return strip_annotated_type(cast(type, get_args(typ)[0]))
diff --git a/src/prelude_python_sdk/_version.py b/src/prelude_python_sdk/_version.py
index 9122928..134a030 100644
--- a/src/prelude_python_sdk/_version.py
+++ b/src/prelude_python_sdk/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "prelude_python_sdk"
-__version__ = "0.2.0" # x-release-please-version
+__version__ = "0.3.0" # x-release-please-version
diff --git a/src/prelude_python_sdk/resources/__init__.py b/src/prelude_python_sdk/resources/__init__.py
index 5bf14a5..00ae8f9 100644
--- a/src/prelude_python_sdk/resources/__init__.py
+++ b/src/prelude_python_sdk/resources/__init__.py
@@ -8,6 +8,14 @@
WatchResourceWithStreamingResponse,
AsyncWatchResourceWithStreamingResponse,
)
+from .lookup import (
+ LookupResource,
+ AsyncLookupResource,
+ LookupResourceWithRawResponse,
+ AsyncLookupResourceWithRawResponse,
+ LookupResourceWithStreamingResponse,
+ AsyncLookupResourceWithStreamingResponse,
+)
from .verification import (
VerificationResource,
AsyncVerificationResource,
@@ -26,6 +34,12 @@
)
__all__ = [
+ "LookupResource",
+ "AsyncLookupResource",
+ "LookupResourceWithRawResponse",
+ "AsyncLookupResourceWithRawResponse",
+ "LookupResourceWithStreamingResponse",
+ "AsyncLookupResourceWithStreamingResponse",
"TransactionalResource",
"AsyncTransactionalResource",
"TransactionalResourceWithRawResponse",
diff --git a/src/prelude_python_sdk/resources/lookup.py b/src/prelude_python_sdk/resources/lookup.py
new file mode 100644
index 0000000..6ce0cc3
--- /dev/null
+++ b/src/prelude_python_sdk/resources/lookup.py
@@ -0,0 +1,199 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import lookup_lookup_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import (
+ maybe_transform,
+ async_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 .._base_client import make_request_options
+from ..types.lookup_lookup_response import LookupLookupResponse
+
+__all__ = ["LookupResource", "AsyncLookupResource"]
+
+
+class LookupResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> LookupResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return LookupResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LookupResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return LookupResourceWithStreamingResponse(self)
+
+ def lookup(
+ self,
+ phone_number: str,
+ *,
+ type: List[Literal["cnam"]] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> LookupLookupResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line
+ type, and portability status.
+
+ Args:
+ phone_number: An E.164 formatted phone number to look up.
+
+ type:
+ Optional features. Possible values are:
+
+ - `cnam` - Retrieve CNAM (Caller ID Name) along with other information. Contact
+ us if you need to use this functionality.
+
+ 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 phone_number:
+ raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}")
+ return self._get(
+ f"/v2/lookup/{phone_number}",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"type": type}, lookup_lookup_params.LookupLookupParams),
+ ),
+ cast_to=LookupLookupResponse,
+ )
+
+
+class AsyncLookupResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncLookupResourceWithRawResponse:
+ """
+ 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/prelude-so/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLookupResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLookupResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/prelude-so/python-sdk#with_streaming_response
+ """
+ return AsyncLookupResourceWithStreamingResponse(self)
+
+ async def lookup(
+ self,
+ phone_number: str,
+ *,
+ type: List[Literal["cnam"]] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> LookupLookupResponse:
+ """
+ Retrieve detailed information about a phone number including carrier data, line
+ type, and portability status.
+
+ Args:
+ phone_number: An E.164 formatted phone number to look up.
+
+ type:
+ Optional features. Possible values are:
+
+ - `cnam` - Retrieve CNAM (Caller ID Name) along with other information. Contact
+ us if you need to use this functionality.
+
+ 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 phone_number:
+ raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}")
+ return await self._get(
+ f"/v2/lookup/{phone_number}",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"type": type}, lookup_lookup_params.LookupLookupParams),
+ ),
+ cast_to=LookupLookupResponse,
+ )
+
+
+class LookupResourceWithRawResponse:
+ def __init__(self, lookup: LookupResource) -> None:
+ self._lookup = lookup
+
+ self.lookup = to_raw_response_wrapper(
+ lookup.lookup,
+ )
+
+
+class AsyncLookupResourceWithRawResponse:
+ def __init__(self, lookup: AsyncLookupResource) -> None:
+ self._lookup = lookup
+
+ self.lookup = async_to_raw_response_wrapper(
+ lookup.lookup,
+ )
+
+
+class LookupResourceWithStreamingResponse:
+ def __init__(self, lookup: LookupResource) -> None:
+ self._lookup = lookup
+
+ self.lookup = to_streamed_response_wrapper(
+ lookup.lookup,
+ )
+
+
+class AsyncLookupResourceWithStreamingResponse:
+ def __init__(self, lookup: AsyncLookupResource) -> None:
+ self._lookup = lookup
+
+ self.lookup = async_to_streamed_response_wrapper(
+ lookup.lookup,
+ )
diff --git a/src/prelude_python_sdk/resources/verification.py b/src/prelude_python_sdk/resources/verification.py
index 7367e88..550d152 100644
--- a/src/prelude_python_sdk/resources/verification.py
+++ b/src/prelude_python_sdk/resources/verification.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing_extensions import Literal
+
import httpx
from ..types import verification_check_params, verification_create_params
@@ -51,6 +53,7 @@ def create(
target: verification_create_params.Target,
dispatch_id: str | NotGiven = NOT_GIVEN,
metadata: verification_create_params.Metadata | NotGiven = NOT_GIVEN,
+ method: Literal["auto", "voice"] | NotGiven = NOT_GIVEN,
options: verification_create_params.Options | NotGiven = NOT_GIVEN,
signals: verification_create_params.Signals | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -75,10 +78,16 @@ def create(
metadata: The metadata for this verification. This object will be returned with every
response or webhook sent that refers to this verification.
+ method: The method used for verifying this phone number. The 'voice' option provides an
+ accessible alternative for visually impaired users by delivering the
+ verification code through a phone call rather than a text message. It also
+ allows verification of landline numbers that cannot receive SMS messages.
+ **Coming soon.**
+
options: Verification options
signals: The signals used for anti-fraud. For more details, refer to
- [Signals](/guides/prevent-fraud#signals).
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
extra_headers: Send extra headers
@@ -95,6 +104,7 @@ def create(
"target": target,
"dispatch_id": dispatch_id,
"metadata": metadata,
+ "method": method,
"options": options,
"signals": signals,
},
@@ -177,6 +187,7 @@ async def create(
target: verification_create_params.Target,
dispatch_id: str | NotGiven = NOT_GIVEN,
metadata: verification_create_params.Metadata | NotGiven = NOT_GIVEN,
+ method: Literal["auto", "voice"] | NotGiven = NOT_GIVEN,
options: verification_create_params.Options | NotGiven = NOT_GIVEN,
signals: verification_create_params.Signals | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -201,10 +212,16 @@ async def create(
metadata: The metadata for this verification. This object will be returned with every
response or webhook sent that refers to this verification.
+ method: The method used for verifying this phone number. The 'voice' option provides an
+ accessible alternative for visually impaired users by delivering the
+ verification code through a phone call rather than a text message. It also
+ allows verification of landline numbers that cannot receive SMS messages.
+ **Coming soon.**
+
options: Verification options
signals: The signals used for anti-fraud. For more details, refer to
- [Signals](/guides/prevent-fraud#signals).
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
extra_headers: Send extra headers
@@ -221,6 +238,7 @@ async def create(
"target": target,
"dispatch_id": dispatch_id,
"metadata": metadata,
+ "method": method,
"options": options,
"signals": signals,
},
diff --git a/src/prelude_python_sdk/resources/watch.py b/src/prelude_python_sdk/resources/watch.py
index 0907e8b..b1f2e82 100644
--- a/src/prelude_python_sdk/resources/watch.py
+++ b/src/prelude_python_sdk/resources/watch.py
@@ -2,9 +2,11 @@
from __future__ import annotations
+from typing import Iterable
+
import httpx
-from ..types import watch_predict_params, watch_feed_back_params
+from ..types import watch_predict_params, watch_send_events_params, watch_send_feedbacks_params
from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
from .._utils import (
maybe_transform,
@@ -20,7 +22,8 @@
)
from .._base_client import make_request_options
from ..types.watch_predict_response import WatchPredictResponse
-from ..types.watch_feed_back_response import WatchFeedBackResponse
+from ..types.watch_send_events_response import WatchSendEventsResponse
+from ..types.watch_send_feedbacks_response import WatchSendFeedbacksResponse
__all__ = ["WatchResource", "AsyncWatchResource"]
@@ -45,28 +48,32 @@ def with_streaming_response(self) -> WatchResourceWithStreamingResponse:
"""
return WatchResourceWithStreamingResponse(self)
- def feed_back(
+ def predict(
self,
*,
- feedback: watch_feed_back_params.Feedback,
- target: watch_feed_back_params.Target,
+ target: watch_predict_params.Target,
+ dispatch_id: str | NotGiven = NOT_GIVEN,
+ metadata: watch_predict_params.Metadata | NotGiven = NOT_GIVEN,
+ signals: watch_predict_params.Signals | NotGiven = NOT_GIVEN,
# 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,
- ) -> WatchFeedBackResponse:
+ ) -> WatchPredictResponse:
"""
- Once the user with a trustworthy phone number demonstrates authentic behavior,
- call this endpoint to report their authenticity to our systems.
+ Predict the outcome of a verification based on Prelude’s anti-fraud system.
Args:
- feedback: You should send a feedback event back to Watch API when your user demonstrates
- authentic behavior.
+ target: The prediction target. Only supports phone numbers for now.
+
+ dispatch_id: The identifier of the dispatch that came from the front-end SDK.
+
+ metadata: The metadata for this prediction.
- target: The verification target. Either a phone number or an email address. To use the
- email verification feature contact us to discuss your use case.
+ signals: The signals used for anti-fraud. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
extra_headers: Send extra headers
@@ -77,43 +84,39 @@ def feed_back(
timeout: Override the client-level default timeout for this request, in seconds
"""
return self._post(
- "/v2/watch/feedback",
+ "/v2/watch/predict",
body=maybe_transform(
{
- "feedback": feedback,
"target": target,
+ "dispatch_id": dispatch_id,
+ "metadata": metadata,
+ "signals": signals,
},
- watch_feed_back_params.WatchFeedBackParams,
+ watch_predict_params.WatchPredictParams,
),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=WatchFeedBackResponse,
+ cast_to=WatchPredictResponse,
)
- def predict(
+ def send_events(
self,
*,
- target: watch_predict_params.Target,
- signals: watch_predict_params.Signals | NotGiven = NOT_GIVEN,
+ events: Iterable[watch_send_events_params.Event],
# 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,
- ) -> WatchPredictResponse:
+ ) -> WatchSendEventsResponse:
"""
- Identify trustworthy phone numbers to mitigate fake traffic or traffic involved
- in fraud and international revenue share fraud (IRSF) patterns. This endpoint
- must be implemented in conjunction with the `watch/feedback` endpoint.
+ Send real-time event data from end-user interactions within your application.
+ Events will be analyzed for proactive fraud prevention and risk scoring.
Args:
- target: The verification target. Either a phone number or an email address. To use the
- email verification feature contact us to discuss your use case.
-
- signals: It is highly recommended that you provide the signals to increase prediction
- performance.
+ events: A list of events to dispatch.
extra_headers: Send extra headers
@@ -124,18 +127,48 @@ def predict(
timeout: Override the client-level default timeout for this request, in seconds
"""
return self._post(
- "/v2/watch/predict",
- body=maybe_transform(
- {
- "target": target,
- "signals": signals,
- },
- watch_predict_params.WatchPredictParams,
+ "/v2/watch/event",
+ body=maybe_transform({"events": events}, watch_send_events_params.WatchSendEventsParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=WatchSendEventsResponse,
+ )
+
+ def send_feedbacks(
+ self,
+ *,
+ feedbacks: Iterable[watch_send_feedbacks_params.Feedback],
+ # 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,
+ ) -> WatchSendFeedbacksResponse:
+ """Send feedback regarding your end-users verification funnel.
+
+ Events will be
+ analyzed for proactive fraud prevention and risk scoring.
+
+ Args:
+ feedbacks: A list of feedbacks to send.
+
+ 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._post(
+ "/v2/watch/feedback",
+ body=maybe_transform({"feedbacks": feedbacks}, watch_send_feedbacks_params.WatchSendFeedbacksParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=WatchPredictResponse,
+ cast_to=WatchSendFeedbacksResponse,
)
@@ -159,28 +192,32 @@ def with_streaming_response(self) -> AsyncWatchResourceWithStreamingResponse:
"""
return AsyncWatchResourceWithStreamingResponse(self)
- async def feed_back(
+ async def predict(
self,
*,
- feedback: watch_feed_back_params.Feedback,
- target: watch_feed_back_params.Target,
+ target: watch_predict_params.Target,
+ dispatch_id: str | NotGiven = NOT_GIVEN,
+ metadata: watch_predict_params.Metadata | NotGiven = NOT_GIVEN,
+ signals: watch_predict_params.Signals | NotGiven = NOT_GIVEN,
# 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,
- ) -> WatchFeedBackResponse:
+ ) -> WatchPredictResponse:
"""
- Once the user with a trustworthy phone number demonstrates authentic behavior,
- call this endpoint to report their authenticity to our systems.
+ Predict the outcome of a verification based on Prelude’s anti-fraud system.
Args:
- feedback: You should send a feedback event back to Watch API when your user demonstrates
- authentic behavior.
+ target: The prediction target. Only supports phone numbers for now.
+
+ dispatch_id: The identifier of the dispatch that came from the front-end SDK.
- target: The verification target. Either a phone number or an email address. To use the
- email verification feature contact us to discuss your use case.
+ metadata: The metadata for this prediction.
+
+ signals: The signals used for anti-fraud. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
extra_headers: Send extra headers
@@ -191,43 +228,75 @@ async def feed_back(
timeout: Override the client-level default timeout for this request, in seconds
"""
return await self._post(
- "/v2/watch/feedback",
+ "/v2/watch/predict",
body=await async_maybe_transform(
{
- "feedback": feedback,
"target": target,
+ "dispatch_id": dispatch_id,
+ "metadata": metadata,
+ "signals": signals,
},
- watch_feed_back_params.WatchFeedBackParams,
+ watch_predict_params.WatchPredictParams,
),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=WatchFeedBackResponse,
+ cast_to=WatchPredictResponse,
)
- async def predict(
+ async def send_events(
self,
*,
- target: watch_predict_params.Target,
- signals: watch_predict_params.Signals | NotGiven = NOT_GIVEN,
+ events: Iterable[watch_send_events_params.Event],
# 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,
- ) -> WatchPredictResponse:
+ ) -> WatchSendEventsResponse:
"""
- Identify trustworthy phone numbers to mitigate fake traffic or traffic involved
- in fraud and international revenue share fraud (IRSF) patterns. This endpoint
- must be implemented in conjunction with the `watch/feedback` endpoint.
+ Send real-time event data from end-user interactions within your application.
+ Events will be analyzed for proactive fraud prevention and risk scoring.
Args:
- target: The verification target. Either a phone number or an email address. To use the
- email verification feature contact us to discuss your use case.
+ events: A list of events to dispatch.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- signals: It is highly recommended that you provide the signals to increase prediction
- performance.
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v2/watch/event",
+ body=await async_maybe_transform({"events": events}, watch_send_events_params.WatchSendEventsParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WatchSendEventsResponse,
+ )
+
+ async def send_feedbacks(
+ self,
+ *,
+ feedbacks: Iterable[watch_send_feedbacks_params.Feedback],
+ # 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,
+ ) -> WatchSendFeedbacksResponse:
+ """Send feedback regarding your end-users verification funnel.
+
+ Events will be
+ analyzed for proactive fraud prevention and risk scoring.
+
+ Args:
+ feedbacks: A list of feedbacks to send.
extra_headers: Send extra headers
@@ -238,18 +307,14 @@ async def predict(
timeout: Override the client-level default timeout for this request, in seconds
"""
return await self._post(
- "/v2/watch/predict",
+ "/v2/watch/feedback",
body=await async_maybe_transform(
- {
- "target": target,
- "signals": signals,
- },
- watch_predict_params.WatchPredictParams,
+ {"feedbacks": feedbacks}, watch_send_feedbacks_params.WatchSendFeedbacksParams
),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=WatchPredictResponse,
+ cast_to=WatchSendFeedbacksResponse,
)
@@ -257,45 +322,57 @@ class WatchResourceWithRawResponse:
def __init__(self, watch: WatchResource) -> None:
self._watch = watch
- self.feed_back = to_raw_response_wrapper(
- watch.feed_back,
- )
self.predict = to_raw_response_wrapper(
watch.predict,
)
+ self.send_events = to_raw_response_wrapper(
+ watch.send_events,
+ )
+ self.send_feedbacks = to_raw_response_wrapper(
+ watch.send_feedbacks,
+ )
class AsyncWatchResourceWithRawResponse:
def __init__(self, watch: AsyncWatchResource) -> None:
self._watch = watch
- self.feed_back = async_to_raw_response_wrapper(
- watch.feed_back,
- )
self.predict = async_to_raw_response_wrapper(
watch.predict,
)
+ self.send_events = async_to_raw_response_wrapper(
+ watch.send_events,
+ )
+ self.send_feedbacks = async_to_raw_response_wrapper(
+ watch.send_feedbacks,
+ )
class WatchResourceWithStreamingResponse:
def __init__(self, watch: WatchResource) -> None:
self._watch = watch
- self.feed_back = to_streamed_response_wrapper(
- watch.feed_back,
- )
self.predict = to_streamed_response_wrapper(
watch.predict,
)
+ self.send_events = to_streamed_response_wrapper(
+ watch.send_events,
+ )
+ self.send_feedbacks = to_streamed_response_wrapper(
+ watch.send_feedbacks,
+ )
class AsyncWatchResourceWithStreamingResponse:
def __init__(self, watch: AsyncWatchResource) -> None:
self._watch = watch
- self.feed_back = async_to_streamed_response_wrapper(
- watch.feed_back,
- )
self.predict = async_to_streamed_response_wrapper(
watch.predict,
)
+ self.send_events = async_to_streamed_response_wrapper(
+ watch.send_events,
+ )
+ self.send_feedbacks = async_to_streamed_response_wrapper(
+ watch.send_feedbacks,
+ )
diff --git a/src/prelude_python_sdk/types/__init__.py b/src/prelude_python_sdk/types/__init__.py
index 9f98177..8abf857 100644
--- a/src/prelude_python_sdk/types/__init__.py
+++ b/src/prelude_python_sdk/types/__init__.py
@@ -2,13 +2,17 @@
from __future__ import annotations
+from .lookup_lookup_params import LookupLookupParams as LookupLookupParams
from .watch_predict_params import WatchPredictParams as WatchPredictParams
-from .watch_feed_back_params import WatchFeedBackParams as WatchFeedBackParams
+from .lookup_lookup_response import LookupLookupResponse as LookupLookupResponse
from .watch_predict_response import WatchPredictResponse as WatchPredictResponse
-from .watch_feed_back_response import WatchFeedBackResponse as WatchFeedBackResponse
+from .watch_send_events_params import WatchSendEventsParams as WatchSendEventsParams
from .transactional_send_params import TransactionalSendParams as TransactionalSendParams
from .verification_check_params import VerificationCheckParams as VerificationCheckParams
from .verification_create_params import VerificationCreateParams as VerificationCreateParams
+from .watch_send_events_response import WatchSendEventsResponse as WatchSendEventsResponse
from .transactional_send_response import TransactionalSendResponse as TransactionalSendResponse
from .verification_check_response import VerificationCheckResponse as VerificationCheckResponse
+from .watch_send_feedbacks_params import WatchSendFeedbacksParams as WatchSendFeedbacksParams
from .verification_create_response import VerificationCreateResponse as VerificationCreateResponse
+from .watch_send_feedbacks_response import WatchSendFeedbacksResponse as WatchSendFeedbacksResponse
diff --git a/src/prelude_python_sdk/types/lookup_lookup_params.py b/src/prelude_python_sdk/types/lookup_lookup_params.py
new file mode 100644
index 0000000..c5b99b5
--- /dev/null
+++ b/src/prelude_python_sdk/types/lookup_lookup_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["LookupLookupParams"]
+
+
+class LookupLookupParams(TypedDict, total=False):
+ type: List[Literal["cnam"]]
+ """Optional features. Possible values are:
+
+ - `cnam` - Retrieve CNAM (Caller ID Name) along with other information. Contact
+ us if you need to use this functionality.
+ """
diff --git a/src/prelude_python_sdk/types/lookup_lookup_response.py b/src/prelude_python_sdk/types/lookup_lookup_response.py
new file mode 100644
index 0000000..f585823
--- /dev/null
+++ b/src/prelude_python_sdk/types/lookup_lookup_response.py
@@ -0,0 +1,116 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["LookupLookupResponse", "NetworkInfo", "OriginalNetworkInfo"]
+
+
+class NetworkInfo(BaseModel):
+ carrier_name: Optional[str] = None
+ """The name of the carrier."""
+
+ mcc: Optional[str] = None
+ """Mobile Country Code."""
+
+ mnc: Optional[str] = None
+ """Mobile Network Code."""
+
+
+class OriginalNetworkInfo(BaseModel):
+ carrier_name: Optional[str] = None
+ """The name of the original carrier."""
+
+ mcc: Optional[str] = None
+ """Mobile Country Code."""
+
+ mnc: Optional[str] = None
+ """Mobile Network Code."""
+
+
+class LookupLookupResponse(BaseModel):
+ caller_name: Optional[str] = None
+ """The CNAM (Caller ID Name) associated with the phone number.
+
+ Contact us if you need to use this functionality. Once enabled, put `cnam`
+ option to `type` query parameter.
+ """
+
+ country_code: Optional[str] = None
+ """The country code of the phone number."""
+
+ flags: Optional[List[Literal["ported", "temporary"]]] = None
+ """A list of flags associated with the phone number.
+
+ - `ported` - Indicates the phone number has been transferred from one carrier to
+ another.
+ - `temporary` - Indicates the phone number is likely a temporary or virtual
+ number, often used for verification services or burner phones.
+ """
+
+ line_type: Optional[
+ Literal[
+ "calling_cards",
+ "fixed_line",
+ "isp",
+ "local_rate",
+ "mobile",
+ "other",
+ "pager",
+ "payphone",
+ "premium_rate",
+ "satellite",
+ "service",
+ "shared_cost",
+ "short_codes_commercial",
+ "toll_free",
+ "universal_access",
+ "unknown",
+ "vpn",
+ "voice_mail",
+ "voip",
+ ]
+ ] = None
+ """The type of phone line.
+
+ - `calling_cards` - Numbers that are associated with providers of pre-paid
+ domestic and international calling cards.
+ - `fixed_line` - Landline phone numbers.
+ - `isp` - Numbers reserved for Internet Service Providers.
+ - `local_rate` - Numbers that can be assigned non-geographically.
+ - `mobile` - Mobile phone numbers.
+ - `other` - Other types of services.
+ - `pager` - Number ranges specifically allocated to paging devices.
+ - `payphone` - Allocated numbers for payphone kiosks in some countries.
+ - `premium_rate` - Landline numbers where the calling party pays more than
+ standard.
+ - `satellite` - Satellite phone numbers.
+ - `service` - Automated applications.
+ - `shared_cost` - Specific landline ranges where the cost of making the call is
+ shared between the calling and called party.
+ - `short_codes_commercial` - Short codes are memorable, easy-to-use numbers,
+ like the UK's NHS 111, often sold to businesses. Not available in all
+ countries.
+ - `toll_free` - Number where the called party pays for the cost of the call not
+ the calling party.
+ - `universal_access` - Number ranges reserved for Universal Access initiatives.
+ - `unknown` - Unknown phone number type.
+ - `vpn` - Numbers are used exclusively within a private telecommunications
+ network, connecting the operator's terminals internally and not accessible via
+ the public telephone network.
+ - `voice_mail` - A specific category of Interactive Voice Response (IVR)
+ services.
+ - `voip` - Specific ranges for providers of VoIP services to allow incoming
+ calls from the regular telephony network.
+ """
+
+ network_info: Optional[NetworkInfo] = None
+ """The current carrier information."""
+
+ original_network_info: Optional[OriginalNetworkInfo] = None
+ """The original carrier information."""
+
+ phone_number: Optional[str] = None
+ """The phone number."""
diff --git a/src/prelude_python_sdk/types/verification_create_params.py b/src/prelude_python_sdk/types/verification_create_params.py
index 15a83b6..b057312 100644
--- a/src/prelude_python_sdk/types/verification_create_params.py
+++ b/src/prelude_python_sdk/types/verification_create_params.py
@@ -26,13 +26,23 @@ class VerificationCreateParams(TypedDict, total=False):
this verification.
"""
+ method: Literal["auto", "voice"]
+ """The method used for verifying this phone number.
+
+ The 'voice' option provides an accessible alternative for visually impaired
+ users by delivering the verification code through a phone call rather than a
+ text message. It also allows verification of landline numbers that cannot
+ receive SMS messages. **Coming soon.**
+ """
+
options: Options
"""Verification options"""
signals: Signals
"""The signals used for anti-fraud.
- For more details, refer to [Signals](/guides/prevent-fraud#signals).
+ For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
"""
@@ -71,7 +81,7 @@ class Options(TypedDict, total=False):
"""
The URL where webhooks will be sent when verification events occur, including
verification creation, attempt creation, and delivery status changes. For more
- details, refer to [Webhook](/api-reference/v2/verify/webhook).
+ details, refer to [Webhook](/verify/v2/documentation/webhook).
"""
code_size: int
@@ -86,7 +96,7 @@ class Options(TypedDict, total=False):
This feature is only available for compatibility purposes and subject to
Prelude’s approval. Contact us to discuss your use case. For more details, refer
- to [Multi Routing](/concepts/multi-routing).
+ to [Multi Routing](/introduction/concepts/multi-routing).
"""
locale: str
@@ -137,7 +147,8 @@ class Signals(TypedDict, total=False):
is_trusted_user: bool
"""
This signal should provide a higher level of trust, indicating that the user is
- genuine. For more details, refer to [Signals](/guides/prevent-fraud#signals).
+ genuine. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
"""
os_version: str
diff --git a/src/prelude_python_sdk/types/watch_feed_back_params.py b/src/prelude_python_sdk/types/watch_feed_back_params.py
deleted file mode 100644
index c4cc330..0000000
--- a/src/prelude_python_sdk/types/watch_feed_back_params.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Literal, Required, TypedDict
-
-__all__ = ["WatchFeedBackParams", "Feedback", "Target"]
-
-
-class WatchFeedBackParams(TypedDict, total=False):
- feedback: Required[Feedback]
- """
- You should send a feedback event back to Watch API when your user demonstrates
- authentic behavior.
- """
-
- target: Required[Target]
- """The verification target.
-
- Either a phone number or an email address. To use the email verification feature
- contact us to discuss your use case.
- """
-
-
-class Feedback(TypedDict, total=False):
- type: Required[Literal["CONFIRM_TARGET"]]
- """
- `CONFIRM_TARGET` should be sent when you are sure that the user with this target
- (e.g. phone number) is trustworthy.
- """
-
-
-class Target(TypedDict, total=False):
- type: Required[Literal["phone_number", "email_address"]]
- """The type of the target. Either "phone_number" or "email_address"."""
-
- value: Required[str]
- """An E.164 formatted phone number or an email address."""
diff --git a/src/prelude_python_sdk/types/watch_feed_back_response.py b/src/prelude_python_sdk/types/watch_feed_back_response.py
deleted file mode 100644
index c879860..0000000
--- a/src/prelude_python_sdk/types/watch_feed_back_response.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-
-from .._models import BaseModel
-
-__all__ = ["WatchFeedBackResponse"]
-
-
-class WatchFeedBackResponse(BaseModel):
- id: str
- """A unique identifier for your feedback request."""
diff --git a/src/prelude_python_sdk/types/watch_predict_params.py b/src/prelude_python_sdk/types/watch_predict_params.py
index 6ef2093..d0b98b8 100644
--- a/src/prelude_python_sdk/types/watch_predict_params.py
+++ b/src/prelude_python_sdk/types/watch_predict_params.py
@@ -4,21 +4,24 @@
from typing_extensions import Literal, Required, TypedDict
-__all__ = ["WatchPredictParams", "Target", "Signals"]
+__all__ = ["WatchPredictParams", "Target", "Metadata", "Signals"]
class WatchPredictParams(TypedDict, total=False):
target: Required[Target]
- """The verification target.
+ """The prediction target. Only supports phone numbers for now."""
- Either a phone number or an email address. To use the email verification feature
- contact us to discuss your use case.
- """
+ dispatch_id: str
+ """The identifier of the dispatch that came from the front-end SDK."""
+
+ metadata: Metadata
+ """The metadata for this prediction."""
signals: Signals
- """
- It is highly recommended that you provide the signals to increase prediction
- performance.
+ """The signals used for anti-fraud.
+
+ For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
"""
@@ -30,7 +33,15 @@ class Target(TypedDict, total=False):
"""An E.164 formatted phone number or an email address."""
+class Metadata(TypedDict, total=False):
+ correlation_id: str
+ """A user-defined identifier to correlate this prediction with."""
+
+
class Signals(TypedDict, total=False):
+ app_version: str
+ """The version of your application."""
+
device_id: str
"""The unique identifier for the user's device.
@@ -41,8 +52,26 @@ class Signals(TypedDict, total=False):
device_model: str
"""The model of the user's device."""
- device_type: str
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
"""The type of the user's device."""
ip: str
- """The IPv4 address of the user's device"""
+ """The IP address of the user's device."""
+
+ is_trusted_user: bool
+ """
+ This signal should provide a higher level of trust, indicating that the user is
+ genuine. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ os_version: str
+ """The version of the user's device operating system."""
+
+ user_agent: str
+ """The user agent of the user's device.
+
+ If the individual fields (os_version, device_platform, device_model) are
+ provided, we will prioritize those values instead of parsing them from the user
+ agent string.
+ """
diff --git a/src/prelude_python_sdk/types/watch_predict_response.py b/src/prelude_python_sdk/types/watch_predict_response.py
index cb5a871..0bd330c 100644
--- a/src/prelude_python_sdk/types/watch_predict_response.py
+++ b/src/prelude_python_sdk/types/watch_predict_response.py
@@ -1,29 +1,21 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
from typing_extensions import Literal
from .._models import BaseModel
-__all__ = ["WatchPredictResponse", "Reasoning"]
-
-
-class Reasoning(BaseModel):
- cause: Optional[Literal["none", "smart_antifraud", "repeat_number", "invalid_line"]] = None
- """A label explaining why the phone number was classified as not trustworthy"""
-
- score: Optional[float] = None
- """
- Indicates the risk of the phone number being genuine or involved in fraudulent
- patterns. The higher the riskier.
- """
+__all__ = ["WatchPredictResponse"]
class WatchPredictResponse(BaseModel):
id: str
- """A unique identifier for your prediction request."""
+ """The prediction identifier."""
+
+ prediction: Literal["legitimate", "suspicious"]
+ """The prediction outcome."""
- prediction: Literal["allow", "block"]
- """A label indicating the trustworthiness of the phone number."""
+ request_id: str
+ """A string that identifies this specific request.
- reasoning: Reasoning
+ Report it back to us to help us diagnose your issues.
+ """
diff --git a/src/prelude_python_sdk/types/watch_send_events_params.py b/src/prelude_python_sdk/types/watch_send_events_params.py
new file mode 100644
index 0000000..ab5b492
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_send_events_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["WatchSendEventsParams", "Event", "EventTarget"]
+
+
+class WatchSendEventsParams(TypedDict, total=False):
+ events: Required[Iterable[Event]]
+ """A list of events to dispatch."""
+
+
+class EventTarget(TypedDict, total=False):
+ type: Required[Literal["phone_number", "email_address"]]
+ """The type of the target. Either "phone_number" or "email_address"."""
+
+ value: Required[str]
+ """An E.164 formatted phone number or an email address."""
+
+
+class Event(TypedDict, total=False):
+ confidence: Required[Literal["maximum", "high", "neutral", "low", "minimum"]]
+ """A confidence level you want to assign to the event."""
+
+ label: Required[str]
+ """A label to describe what the event refers to."""
+
+ target: Required[EventTarget]
+ """The event target. Only supports phone numbers for now."""
diff --git a/src/prelude_python_sdk/types/watch_send_events_response.py b/src/prelude_python_sdk/types/watch_send_events_response.py
new file mode 100644
index 0000000..dab5729
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_send_events_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["WatchSendEventsResponse"]
+
+
+class WatchSendEventsResponse(BaseModel):
+ request_id: str
+ """A string that identifies this specific request.
+
+ Report it back to us to help us diagnose your issues.
+ """
+
+ status: Literal["success"]
+ """The status of the events dispatch."""
diff --git a/src/prelude_python_sdk/types/watch_send_feedbacks_params.py b/src/prelude_python_sdk/types/watch_send_feedbacks_params.py
new file mode 100644
index 0000000..4320e60
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_send_feedbacks_params.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["WatchSendFeedbacksParams", "Feedback", "FeedbackTarget", "FeedbackMetadata", "FeedbackSignals"]
+
+
+class WatchSendFeedbacksParams(TypedDict, total=False):
+ feedbacks: Required[Iterable[Feedback]]
+ """A list of feedbacks to send."""
+
+
+class FeedbackTarget(TypedDict, total=False):
+ type: Required[Literal["phone_number", "email_address"]]
+ """The type of the target. Either "phone_number" or "email_address"."""
+
+ value: Required[str]
+ """An E.164 formatted phone number or an email address."""
+
+
+class FeedbackMetadata(TypedDict, total=False):
+ correlation_id: str
+ """A user-defined identifier to correlate this feedback with."""
+
+
+class FeedbackSignals(TypedDict, total=False):
+ app_version: str
+ """The version of your application."""
+
+ device_id: str
+ """The unique identifier for the user's device.
+
+ For Android, this corresponds to the `ANDROID_ID` and for iOS, this corresponds
+ to the `identifierForVendor`.
+ """
+
+ device_model: str
+ """The model of the user's device."""
+
+ device_platform: Literal["android", "ios", "ipados", "tvos", "web"]
+ """The type of the user's device."""
+
+ ip: str
+ """The IP address of the user's device."""
+
+ is_trusted_user: bool
+ """
+ This signal should provide a higher level of trust, indicating that the user is
+ genuine. For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
+
+ os_version: str
+ """The version of the user's device operating system."""
+
+ user_agent: str
+ """The user agent of the user's device.
+
+ If the individual fields (os_version, device_platform, device_model) are
+ provided, we will prioritize those values instead of parsing them from the user
+ agent string.
+ """
+
+
+class Feedback(TypedDict, total=False):
+ target: Required[FeedbackTarget]
+ """The feedback target. Only supports phone numbers for now."""
+
+ type: Required[Literal["verification.started", "verification.completed"]]
+ """The type of feedback."""
+
+ dispatch_id: str
+ """The identifier of the dispatch that came from the front-end SDK."""
+
+ metadata: FeedbackMetadata
+ """The metadata for this feedback."""
+
+ signals: FeedbackSignals
+ """The signals used for anti-fraud.
+
+ For more details, refer to
+ [Signals](/verify/v2/documentation/prevent-fraud#signals).
+ """
diff --git a/src/prelude_python_sdk/types/watch_send_feedbacks_response.py b/src/prelude_python_sdk/types/watch_send_feedbacks_response.py
new file mode 100644
index 0000000..ca05636
--- /dev/null
+++ b/src/prelude_python_sdk/types/watch_send_feedbacks_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["WatchSendFeedbacksResponse"]
+
+
+class WatchSendFeedbacksResponse(BaseModel):
+ request_id: str
+ """A string that identifies this specific request.
+
+ Report it back to us to help us diagnose your issues.
+ """
+
+ status: Literal["success"]
+ """The status of the feedbacks sending."""
diff --git a/tests/api_resources/test_lookup.py b/tests/api_resources/test_lookup.py
new file mode 100644
index 0000000..c65a5c2
--- /dev/null
+++ b/tests/api_resources/test_lookup.py
@@ -0,0 +1,114 @@
+# 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 tests.utils import assert_matches_type
+from prelude_python_sdk import Prelude, AsyncPrelude
+from prelude_python_sdk.types import LookupLookupResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLookup:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_lookup(self, client: Prelude) -> None:
+ lookup = client.lookup.lookup(
+ phone_number="+12065550100",
+ )
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ def test_method_lookup_with_all_params(self, client: Prelude) -> None:
+ lookup = client.lookup.lookup(
+ phone_number="+12065550100",
+ type=["cnam"],
+ )
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ def test_raw_response_lookup(self, client: Prelude) -> None:
+ response = client.lookup.with_raw_response.lookup(
+ phone_number="+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ lookup = response.parse()
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ def test_streaming_response_lookup(self, client: Prelude) -> None:
+ with client.lookup.with_streaming_response.lookup(
+ phone_number="+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ lookup = response.parse()
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_lookup(self, client: Prelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"):
+ client.lookup.with_raw_response.lookup(
+ phone_number="",
+ )
+
+
+class TestAsyncLookup:
+ parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ async def test_method_lookup(self, async_client: AsyncPrelude) -> None:
+ lookup = await async_client.lookup.lookup(
+ phone_number="+12065550100",
+ )
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ async def test_method_lookup_with_all_params(self, async_client: AsyncPrelude) -> None:
+ lookup = await async_client.lookup.lookup(
+ phone_number="+12065550100",
+ type=["cnam"],
+ )
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ async def test_raw_response_lookup(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.lookup.with_raw_response.lookup(
+ phone_number="+12065550100",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ lookup = await response.parse()
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_lookup(self, async_client: AsyncPrelude) -> None:
+ async with async_client.lookup.with_streaming_response.lookup(
+ phone_number="+12065550100",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ lookup = await response.parse()
+ assert_matches_type(LookupLookupResponse, lookup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_lookup(self, async_client: AsyncPrelude) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"):
+ await async_client.lookup.with_raw_response.lookup(
+ phone_number="",
+ )
diff --git a/tests/api_resources/test_verification.py b/tests/api_resources/test_verification.py
index d88eca4..015cd08 100644
--- a/tests/api_resources/test_verification.py
+++ b/tests/api_resources/test_verification.py
@@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Prelude) -> None:
},
dispatch_id="dispatch_id",
metadata={"correlation_id": "correlation_id"},
+ method="auto",
options={
"app_realm": {
"platform": "android",
@@ -56,7 +57,7 @@ def test_method_create_with_all_params(self, client: Prelude) -> None:
"app_version": "1.2.34",
"device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
"device_model": "iPhone17,2",
- "device_platform": "android",
+ "device_platform": "ios",
"ip": "192.0.2.1",
"is_trusted_user": False,
"os_version": "18.0.1",
@@ -161,6 +162,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncPrelude) -
},
dispatch_id="dispatch_id",
metadata={"correlation_id": "correlation_id"},
+ method="auto",
options={
"app_realm": {
"platform": "android",
@@ -178,7 +180,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncPrelude) -
"app_version": "1.2.34",
"device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
"device_model": "iPhone17,2",
- "device_platform": "android",
+ "device_platform": "ios",
"ip": "192.0.2.1",
"is_trusted_user": False,
"os_version": "18.0.1",
diff --git a/tests/api_resources/test_watch.py b/tests/api_resources/test_watch.py
index 1e948da..80096e0 100644
--- a/tests/api_resources/test_watch.py
+++ b/tests/api_resources/test_watch.py
@@ -11,7 +11,8 @@
from prelude_python_sdk import Prelude, AsyncPrelude
from prelude_python_sdk.types import (
WatchPredictResponse,
- WatchFeedBackResponse,
+ WatchSendEventsResponse,
+ WatchSendFeedbacksResponse,
)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -20,49 +21,6 @@
class TestWatch:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
- @parametrize
- def test_method_feed_back(self, client: Prelude) -> None:
- watch = client.watch.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
- )
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
-
- @parametrize
- def test_raw_response_feed_back(self, client: Prelude) -> None:
- response = client.watch.with_raw_response.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- watch = response.parse()
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
-
- @parametrize
- def test_streaming_response_feed_back(self, client: Prelude) -> None:
- with client.watch.with_streaming_response.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- watch = response.parse()
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
@parametrize
def test_method_predict(self, client: Prelude) -> None:
watch = client.watch.predict(
@@ -80,11 +38,17 @@ def test_method_predict_with_all_params(self, client: Prelude) -> None:
"type": "phone_number",
"value": "+30123456789",
},
+ dispatch_id="dispatch_id",
+ metadata={"correlation_id": "correlation_id"},
signals={
- "device_id": "device_id",
- "device_model": "device_model",
- "device_type": "device_type",
- "ip": "ip",
+ "app_version": "1.2.34",
+ "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
+ "device_model": "iPhone17,2",
+ "device_platform": "ios",
+ "ip": "192.0.2.1",
+ "is_trusted_user": False,
+ "os_version": "18.0.1",
+ "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
},
)
assert_matches_type(WatchPredictResponse, watch, path=["response"])
@@ -119,53 +83,123 @@ def test_streaming_response_predict(self, client: Prelude) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_send_events(self, client: Prelude) -> None:
+ watch = client.watch.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ )
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
-class TestAsyncWatch:
- parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+ @parametrize
+ def test_raw_response_send_events(self, client: Prelude) -> None:
+ response = client.watch.with_raw_response.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ watch = response.parse()
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
@parametrize
- async def test_method_feed_back(self, async_client: AsyncPrelude) -> None:
- watch = await async_client.watch.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
+ def test_streaming_response_send_events(self, client: Prelude) -> None:
+ with client.watch.with_streaming_response.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ watch = response.parse()
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_send_feedbacks(self, client: Prelude) -> None:
+ watch = client.watch.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
)
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
@parametrize
- async def test_raw_response_feed_back(self, async_client: AsyncPrelude) -> None:
- response = await async_client.watch.with_raw_response.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
+ def test_raw_response_send_feedbacks(self, client: Prelude) -> None:
+ response = client.watch.with_raw_response.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- watch = await response.parse()
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
+ watch = response.parse()
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
@parametrize
- async def test_streaming_response_feed_back(self, async_client: AsyncPrelude) -> None:
- async with async_client.watch.with_streaming_response.feed_back(
- feedback={"type": "CONFIRM_TARGET"},
- target={
- "type": "phone_number",
- "value": "+30123456789",
- },
+ def test_streaming_response_send_feedbacks(self, client: Prelude) -> None:
+ with client.watch.with_streaming_response.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- watch = await response.parse()
- assert_matches_type(WatchFeedBackResponse, watch, path=["response"])
+ watch = response.parse()
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
assert cast(Any, response.is_closed) is True
+
+class TestAsyncWatch:
+ parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
@parametrize
async def test_method_predict(self, async_client: AsyncPrelude) -> None:
watch = await async_client.watch.predict(
@@ -183,11 +217,17 @@ async def test_method_predict_with_all_params(self, async_client: AsyncPrelude)
"type": "phone_number",
"value": "+30123456789",
},
+ dispatch_id="dispatch_id",
+ metadata={"correlation_id": "correlation_id"},
signals={
- "device_id": "device_id",
- "device_model": "device_model",
- "device_type": "device_type",
- "ip": "ip",
+ "app_version": "1.2.34",
+ "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2",
+ "device_model": "iPhone17,2",
+ "device_platform": "ios",
+ "ip": "192.0.2.1",
+ "is_trusted_user": False,
+ "os_version": "18.0.1",
+ "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
},
)
assert_matches_type(WatchPredictResponse, watch, path=["response"])
@@ -221,3 +261,116 @@ async def test_streaming_response_predict(self, async_client: AsyncPrelude) -> N
assert_matches_type(WatchPredictResponse, watch, path=["response"])
assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_send_events(self, async_client: AsyncPrelude) -> None:
+ watch = await async_client.watch.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ )
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_send_events(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.watch.with_raw_response.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ watch = await response.parse()
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_send_events(self, async_client: AsyncPrelude) -> None:
+ async with async_client.watch.with_streaming_response.send_events(
+ events=[
+ {
+ "confidence": "maximum",
+ "label": "onboarding.start",
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ watch = await response.parse()
+ assert_matches_type(WatchSendEventsResponse, watch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_send_feedbacks(self, async_client: AsyncPrelude) -> None:
+ watch = await async_client.watch.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
+ )
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_send_feedbacks(self, async_client: AsyncPrelude) -> None:
+ response = await async_client.watch.with_raw_response.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ watch = await response.parse()
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_send_feedbacks(self, async_client: AsyncPrelude) -> None:
+ async with async_client.watch.with_streaming_response.send_feedbacks(
+ feedbacks=[
+ {
+ "target": {
+ "type": "phone_number",
+ "value": "+30123456789",
+ },
+ "type": "verification.started",
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ watch = await response.parse()
+ assert_matches_type(WatchSendFeedbacksResponse, watch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/test_client.py b/tests/test_client.py
index a9d9e60..6303f15 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1703,7 +1703,7 @@ def test_get_platform(self) -> None:
import threading
from prelude_python_sdk._utils import asyncify
- from prelude_python_sdk._base_client import get_platform
+ from prelude_python_sdk._base_client import get_platform
async def test_main() -> None:
result = await asyncify(get_platform)()
diff --git a/tests/test_models.py b/tests/test_models.py
index 638d33f..2a5225a 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -854,3 +854,35 @@ class Model(BaseModel):
m = construct_type(value={"cls": "foo"}, type_=Model)
assert isinstance(m, Model)
assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: bool
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: List[Union[A, object]]
+
+ class ModelA(BaseModel):
+ type: Literal["modelA"]
+
+ data: int
+
+ class ModelB(BaseModel):
+ type: Literal["modelB"]
+
+ required: str
+
+ data: Union[A, B]
+
+ # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+ m = construct_type(
+ value={"type": "modelB", "data": {"type": "a", "data": True}},
+ type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+ )
+
+ assert isinstance(m, ModelB)
diff --git a/tests/test_transform.py b/tests/test_transform.py
index b48736d..16f6a21 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -8,7 +8,7 @@
import pytest
-from prelude_python_sdk._types import Base64FileInput
+from prelude_python_sdk._types import NOT_GIVEN, Base64FileInput
from prelude_python_sdk._utils import (
PropertyInfo,
transform as _transform,
@@ -432,3 +432,22 @@ async def test_base64_file_input(use_async: bool) -> None:
assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
"foo": "SGVsbG8sIHdvcmxkIQ=="
} # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+ # lists of ints are left as-is
+ data = [1, 2, 3]
+ assert await transform(data, List[int], use_async) is data
+
+ # iterables of ints are converted to a list
+ data = iter([1, 2, 3])
+ assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@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) == {}