diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5115900..5ef00cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/brand.dev-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | @@ -46,7 +46,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/brand.dev-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | @@ -67,7 +67,7 @@ jobs: github.repository == 'stainless-sdks/brand.dev-python' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: core.setOutput('github_token', await core.getIDToken()); @@ -87,7 +87,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/brand.dev-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 535e7fd..d2f2691 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rye run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 7778fb4..d21c7a4 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'context-dot-dev/deprecated-brand-python-sdk' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check release environment run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b5fcdb9..ba2c585 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.43.0" + ".": "1.44.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index bee881e..1891309 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 20 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-05a30711e18b0023520a660352d75595a050d1299bf0e3ee4a8cf55ded36aea2.yml -openapi_spec_hash: 8d0e1115a7d864f27c55cec3255d1e77 +configured_endpoints: 16 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev/brand.dev-1bc9121ee0f64a15a503b5ec2a7836edfb1e2bf561d22f598ff3cf8f40381a9c.yml +openapi_spec_hash: e4980adb7bd8a6a37a1dbb7f35585b79 config_hash: 91cf2dcefb99c39eb9cd3e98e15d6011 diff --git a/CHANGELOG.md b/CHANGELOG.md index d135655..12357fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,59 @@ # Changelog +## 1.44.0 (2026-05-19) + +Full Changelog: [v1.43.0...v1.44.0](https://github.com/context-dot-dev/deprecated-brand-python-sdk/compare/v1.43.0...v1.44.0) + +### Features + +* **api:** api update ([90b98b6](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/90b98b60f940e7aa127cb2b52fbdf0e467cd45c1)) +* **api:** api update ([44865c2](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/44865c2f5a63e7d0e2e4ccd0c4d2c06206044fe8)) +* **api:** api update ([291415a](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/291415a60a6ee87cc6b27364e6b71e039ccb3590)) +* **api:** api update ([ce906ee](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/ce906ee12160bff29e64d8c140decf63c02fa104)) +* **api:** api update ([00262df](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/00262dff8700b717cdacde8d05c040c2e240b94b)) +* **api:** api update ([fcd2959](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/fcd2959f013e42957386b17c540c3b69fde4b08e)) +* **api:** api update ([34da1c0](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/34da1c09d1e5883a6134fba8bef1a2622a841a3d)) +* **api:** api update ([5573ee7](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/5573ee7e42f5018a95e85e66460bfccc3d695a2a)) +* **api:** api update ([e4f1307](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/e4f1307a639a0c14467bd4c6af34eafe96b86033)) +* **api:** api update ([3e783d9](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/3e783d9ad471999f34ed2cbc92a58099f15ce9cb)) +* **api:** api update ([f9047c7](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/f9047c77841d0b1707cdeab18880d93dd2e6a115)) +* **api:** api update ([390e85c](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/390e85cad5c6199e1a3d03de72e77d54f0108234)) +* **api:** api update ([74f22a5](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/74f22a55a3d82b5adcc63d5f12218a162180ae95)) +* **api:** api update ([a04e938](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/a04e9389794725781408dc53d635226406d89cb2)) +* **api:** api update ([b4672ab](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/b4672ab67f11664113177977497c75e6c5ce82f8)) +* **api:** api update ([b494e66](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/b494e66bdee0f1c447ee587836aa74c7955da0aa)) +* **api:** api update ([af4c2fa](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/af4c2fa317c4a5cd2ef86e6562fe41ff7436daed)) +* **api:** api update ([9d4a64e](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/9d4a64e28b1321eda8463d8c86ec8a130c78c318)) +* **api:** api update ([f13cc2b](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/f13cc2be3ce7c3355168ba62bb3bd5ec5aaf4105)) +* **api:** api update ([93a9472](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/93a9472c495a1d34afa82ff7a6889e32ff706e3f)) +* **api:** api update ([bf48ceb](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/bf48ceb8693e734ab3da2b89e80f394d351215e6)) +* **api:** api update ([b5e8905](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/b5e8905b3e8554890bda8c7eb9e4255c363f1a81)) +* **api:** api update ([04fafa1](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/04fafa1b94a4047cc9fb3825ab84e6f517baefc4)) +* **api:** api update ([bd0f93f](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/bd0f93f4df85dc1e822ca6b59fb7ce594976ad49)) +* **api:** api update ([a388d39](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/a388d39c93b50ec075a553957081a45f64b73d39)) +* **api:** api update ([9213463](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/9213463afaf5d76d3b706a5aef49082c72409189)) +* **internal/types:** support eagerly validating pydantic iterators ([06b88f4](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/06b88f47143a8e1c9182ceab6bf3c1f2eca6cfdc)) +* support setting headers via env ([0ce56bb](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/0ce56bbd77bbb7170a90b838c9dff09616dea488)) + + +### Bug Fixes + +* **client:** add missing f-string prefix in file type error message ([af1c0ca](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/af1c0cab6151a11859136bd4cbbd8a0a200acdd5)) +* **client:** preserve hardcoded query params when merging with user params ([a92d7b7](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/a92d7b7c66f52b259cbf31d938c8a70565190b5c)) +* ensure file data are only sent as 1 parameter ([9b72a51](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/9b72a51afe0d9992f3c52ee727c1ffc1ec76312f)) +* use correct field name format for multipart file arrays ([c74c369](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/c74c3699de55fd58f298fa9a9d378db7f677a344)) + + +### Performance Improvements + +* **client:** optimize file structure copying in multipart requests ([d7e4a18](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/d7e4a181dd0e9146f98bb51bb977300eac439c6e)) + + +### Chores + +* **internal:** more robust bootstrap script ([28007d9](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/28007d91dbe45b20fc33670466006d9934ac7c5a)) +* **internal:** reformat pyproject.toml ([3e37dca](https://github.com/context-dot-dev/deprecated-brand-python-sdk/commit/3e37dcaafaafaa677893794e66cec86afedb65ca)) + ## 1.43.0 (2026-04-03) Full Changelog: [v1.42.0...v1.43.0](https://github.com/context-dot-dev/deprecated-brand-python-sdk/compare/v1.42.0...v1.43.0) diff --git a/api.md b/api.md index 8b5d705..7f3b993 100644 --- a/api.md +++ b/api.md @@ -8,7 +8,6 @@ from brand.dev.types import ( BrandAIProductResponse, BrandAIProductsResponse, BrandAIQueryResponse, - BrandFontsResponse, BrandIdentifyFromTransactionResponse, BrandPrefetchResponse, BrandPrefetchByEmailResponse, @@ -16,10 +15,7 @@ from brand.dev.types import ( BrandRetrieveByIsinResponse, BrandRetrieveByNameResponse, BrandRetrieveByTickerResponse, - BrandRetrieveNaicsResponse, BrandRetrieveSimplifiedResponse, - BrandScreenshotResponse, - BrandStyleguideResponse, BrandWebScrapeHTMLResponse, BrandWebScrapeImagesResponse, BrandWebScrapeMdResponse, @@ -33,7 +29,6 @@ Methods: - client.brand.ai_product(\*\*params) -> BrandAIProductResponse - client.brand.ai_products(\*\*params) -> BrandAIProductsResponse - client.brand.ai_query(\*\*params) -> BrandAIQueryResponse -- client.brand.fonts(\*\*params) -> BrandFontsResponse - client.brand.identify_from_transaction(\*\*params) -> BrandIdentifyFromTransactionResponse - client.brand.prefetch(\*\*params) -> BrandPrefetchResponse - client.brand.prefetch_by_email(\*\*params) -> BrandPrefetchByEmailResponse @@ -41,10 +36,7 @@ Methods: - client.brand.retrieve_by_isin(\*\*params) -> BrandRetrieveByIsinResponse - client.brand.retrieve_by_name(\*\*params) -> BrandRetrieveByNameResponse - client.brand.retrieve_by_ticker(\*\*params) -> BrandRetrieveByTickerResponse -- client.brand.retrieve_naics(\*\*params) -> BrandRetrieveNaicsResponse - client.brand.retrieve_simplified(\*\*params) -> BrandRetrieveSimplifiedResponse -- client.brand.screenshot(\*\*params) -> BrandScreenshotResponse -- client.brand.styleguide(\*\*params) -> BrandStyleguideResponse - client.brand.web_scrape_html(\*\*params) -> BrandWebScrapeHTMLResponse - client.brand.web_scrape_images(\*\*params) -> BrandWebScrapeImagesResponse - client.brand.web_scrape_md(\*\*params) -> BrandWebScrapeMdResponse diff --git a/pyproject.toml b/pyproject.toml index 1ac8a24..8f59f08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "brand.dev" -version = "1.43.0" +version = "1.44.0" description = "The official Python library for the brand.dev API" dynamic = ["readme"] license = "Apache-2.0" @@ -168,7 +168,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/brand/dev/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/brand/dev/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee..fe8451e 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/src/brand/dev/_base_client.py b/src/brand/dev/_base_client.py index dc4af6b..ce73b2f 100644 --- a/src/brand/dev/_base_client.py +++ b/src/brand/dev/_base_client.py @@ -540,6 +540,10 @@ def _build_request( files = cast(HttpxRequestFiles, ForceMultipartDict()) prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) if "_" in prepared_url.host: # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} diff --git a/src/brand/dev/_client.py b/src/brand/dev/_client.py index 5b46246..fa051a5 100644 --- a/src/brand/dev/_client.py +++ b/src/brand/dev/_client.py @@ -19,7 +19,11 @@ RequestOptions, not_given, ) -from ._utils import is_given, get_async_library +from ._utils import ( + is_given, + is_mapping_t, + get_async_library, +) from ._compat import cached_property from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream @@ -90,6 +94,15 @@ def __init__( if base_url is None: base_url = f"https://api.brand.dev/v1" + custom_headers_env = os.environ.get("BRAND_DEV_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -264,6 +277,15 @@ def __init__( if base_url is None: base_url = f"https://api.brand.dev/v1" + custom_headers_env = os.environ.get("BRAND_DEV_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, diff --git a/src/brand/dev/_files.py b/src/brand/dev/_files.py index cc14c14..76da9e0 100644 --- a/src/brand/dev/_files.py +++ b/src/brand/dev/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles elif is_sequence_t(files): files = [(key, await _async_transform_file(file)) for key, file in files] else: - raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence") + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") return files @@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent: return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/brand/dev/_models.py b/src/brand/dev/_models.py index 29070e0..8c5ab26 100644 --- a/src/brand/dev/_models.py +++ b/src/brand/dev/_models.py @@ -25,7 +25,9 @@ ClassVar, Protocol, Required, + Annotated, ParamSpec, + TypeAlias, TypedDict, TypeGuard, final, @@ -79,7 +81,15 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler + from pydantic_core import CoreSchema, core_schema from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema +else: + try: + from pydantic_core import CoreSchema, core_schema + except ImportError: + CoreSchema = None + core_schema = None __all__ = ["BaseModel", "GenericModel"] @@ -396,6 +406,76 @@ def model_dump_json( ) +class _EagerIterable(list[_T], Generic[_T]): + """ + Accepts any Iterable[T] input (including generators), consumes it + eagerly, and validates all items upfront. + + Validation preserves the original container type where possible + (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) + always emits a list — round-tripping through model_dump() will not + restore the original container type. + """ + + @classmethod + def __get_pydantic_core_schema__( + cls, + source_type: Any, + handler: GetCoreSchemaHandler, + ) -> CoreSchema: + (item_type,) = get_args(source_type) or (Any,) + item_schema: CoreSchema = handler.generate_schema(item_type) + list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) + + return core_schema.no_info_wrap_validator_function( + cls._validate, + list_of_items_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + ), + ) + + @staticmethod + def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: + original_type: type[Any] = type(v) + + # Normalize to list so list_schema can validate each item + if isinstance(v, list): + items: list[_T] = v + else: + try: + items = list(v) + except TypeError as e: + raise TypeError("Value is not iterable") from e + + # Validate items against the inner schema + validated: list[_T] = handler(items) + + # Reconstruct original container type + if original_type is list: + return validated + # str(list) produces the list's repr, not a string built from items, + # so skip reconstruction for str and its subclasses. + if issubclass(original_type, str): + return validated + try: + return original_type(validated) + except (TypeError, ValueError): + # If the type cannot be reconstructed, just return the validated list + return validated + + @staticmethod + def _serialize(v: Iterable[_T]) -> list[_T]: + """Always serialize as a list so Pydantic's JSON encoder is happy.""" + if isinstance(v, list): + return v + return list(v) + + +EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] + + def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) diff --git a/src/brand/dev/_qs.py b/src/brand/dev/_qs.py index de8c99b..4127c19 100644 --- a/src/brand/dev/_qs.py +++ b/src/brand/dev/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NotGiven, not_given +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 diff --git a/src/brand/dev/_types.py b/src/brand/dev/_types.py index fce564e..eff6aae 100644 --- a/src/brand/dev/_types.py +++ b/src/brand/dev/_types.py @@ -47,6 +47,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances diff --git a/src/brand/dev/_utils/__init__.py b/src/brand/dev/_utils/__init__.py index 10cb66d..1c090e5 100644 --- a/src/brand/dev/_utils/__init__.py +++ b/src/brand/dev/_utils/__init__.py @@ -24,7 +24,6 @@ coerce_integer as coerce_integer, file_from_path as file_from_path, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, diff --git a/src/brand/dev/_utils/_utils.py b/src/brand/dev/_utils/_utils.py index eec7f4a..199cd23 100644 --- a/src/brand/dev/_utils/_utils.py +++ b/src/brand/dev/_utils/_utils.py @@ -17,11 +17,11 @@ ) from pathlib import Path from datetime import date, datetime -from typing_extensions import TypeGuard +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import Omit, NotGiven, FileTypes, HeadersLike +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -40,25 +40,45 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] @@ -75,9 +95,11 @@ def _extract_items( if is_list(obj): files: list[tuple[str, FileTypes]] = [] - for entry in obj: - assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") - files.append((flattened_key + "[]", cast(FileTypes, entry))) + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) return files assert_is_file_content(obj, key=flattened_key) @@ -86,8 +108,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] @@ -105,6 +128,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -116,9 +140,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) @@ -176,21 +203,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) diff --git a/src/brand/dev/_version.py b/src/brand/dev/_version.py index 6c398c0..236a189 100644 --- a/src/brand/dev/_version.py +++ b/src/brand/dev/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "brand.dev" -__version__ = "1.43.0" # x-release-please-version +__version__ = "1.44.0" # x-release-please-version diff --git a/src/brand/dev/resources/brand.py b/src/brand/dev/resources/brand.py index 856a490..560f054 100644 --- a/src/brand/dev/resources/brand.py +++ b/src/brand/dev/resources/brand.py @@ -8,16 +8,12 @@ import httpx from ..types import ( - brand_fonts_params, brand_ai_query_params, brand_prefetch_params, brand_retrieve_params, brand_ai_product_params, - brand_screenshot_params, - brand_styleguide_params, brand_ai_products_params, brand_web_scrape_md_params, - brand_retrieve_naics_params, brand_web_scrape_html_params, brand_retrieve_by_isin_params, brand_retrieve_by_name_params, @@ -40,16 +36,12 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.brand_fonts_response import BrandFontsResponse from ..types.brand_ai_query_response import BrandAIQueryResponse from ..types.brand_prefetch_response import BrandPrefetchResponse from ..types.brand_retrieve_response import BrandRetrieveResponse from ..types.brand_ai_product_response import BrandAIProductResponse -from ..types.brand_screenshot_response import BrandScreenshotResponse -from ..types.brand_styleguide_response import BrandStyleguideResponse from ..types.brand_ai_products_response import BrandAIProductsResponse from ..types.brand_web_scrape_md_response import BrandWebScrapeMdResponse -from ..types.brand_retrieve_naics_response import BrandRetrieveNaicsResponse from ..types.brand_web_scrape_html_response import BrandWebScrapeHTMLResponse from ..types.brand_retrieve_by_isin_response import BrandRetrieveByIsinResponse from ..types.brand_retrieve_by_name_response import BrandRetrieveByNameResponse @@ -89,63 +81,129 @@ def retrieve( *, domain: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -163,8 +221,12 @@ def retrieve( domain: Domain name to retrieve brand data for (e.g., 'example.com', 'google.com'). Cannot be used with name or ticker parameters. - force_language: Optional parameter to force the language of the retrieved brand data. Works with - all three lookup methods. + force_language: Optional parameter to force the language of the retrieved brand data. + + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of @@ -193,6 +255,7 @@ def retrieve( { "domain": domain, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -206,6 +269,7 @@ def ai_product( self, *, url: str, + max_age_ms: int | Omit = omit, timeout_ms: int | 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. @@ -215,15 +279,19 @@ def ai_product( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductResponse: """ - Beta feature: Given a single URL, determines if it is a product detail page, - classifies the platform/product type, and extracts the product information. - Supports Amazon, TikTok Shop, Etsy, and generic ecommerce sites. + Given a single URL, determines if it is a product page and extracts the product + information. Args: url: The product page URL to extract product data from. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -238,6 +306,7 @@ def ai_product( body=maybe_transform( { "url": url, + "max_age_ms": max_age_ms, "timeout_ms": timeout_ms, }, brand_ai_product_params.BrandAIProductParams, @@ -253,6 +322,7 @@ def ai_products( self, *, domain: str, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -262,19 +332,24 @@ def ai_products( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductsResponse: - """Beta feature: Extract product information from a brand's website. + """Extract product information from a brand's website. - We will - analyze the website and return a list of products with details such as name, - description, image, pricing, features, and more. + We will analyze the website + and return a list of products with details such as name, description, image, + pricing, features, and more. Args: domain: The domain name to analyze. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + max_products: Maximum number of products to extract. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -291,6 +366,7 @@ def ai_products( self, *, direct_url: str, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -300,20 +376,25 @@ def ai_products( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductsResponse: - """Beta feature: Extract product information from a brand's website. + """Extract product information from a brand's website. - We will - analyze the website and return a list of products with details such as name, - description, image, pricing, features, and more. + We will analyze the website + and return a list of products with details such as name, description, image, + pricing, features, and more. Args: direct_url: A specific URL to use directly as the starting point for extraction without domain resolution. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + max_products: Maximum number of products to extract. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -330,6 +411,7 @@ def ai_products( self, *, domain: str | Omit = omit, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, direct_url: str | Omit = omit, @@ -345,6 +427,7 @@ def ai_products( body=maybe_transform( { "domain": domain, + "max_age_ms": max_age_ms, "max_products": max_products, "timeout_ms": timeout_ms, "direct_url": direct_url, @@ -413,56 +496,6 @@ def ai_query( cast_to=BrandAIQueryResponse, ) - def fonts( - self, - *, - domain: str, - timeout_ms: int | 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, - ) -> BrandFontsResponse: - """ - Extract font information from a brand's website including font families, usage - statistics, fallbacks, and element/word counts. - - Args: - domain: Domain name to extract fonts from (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. - - timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer - than this value, it will be aborted with a 408 status code. Maximum allowed - value is 300000ms (5 minutes). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/brand/fonts", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "domain": domain, - "timeout_ms": timeout_ms, - }, - brand_fonts_params.BrandFontsParams, - ), - ), - cast_to=BrandFontsResponse, - ) - def identify_from_transaction( self, *, @@ -711,61 +744,126 @@ def identify_from_transaction( ] | Omit = omit, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, high_confidence_only: bool | Omit = omit, @@ -796,7 +894,6 @@ def identify_from_transaction( high_confidence_only: When set to true, the API will perform an additional verification steps to ensure the identified brand matches the transaction with high confidence. - Defaults to false. max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of @@ -858,9 +955,7 @@ def prefetch( ) -> BrandPrefetchResponse: """ Signal that you may fetch brand data for a particular domain soon to improve - latency. This endpoint does not charge credits and is available for paid - customers to optimize future requests. [You must be on a paid plan to use this - endpoint] + latency. Args: domain: Domain name to prefetch brand data for @@ -908,9 +1003,7 @@ def prefetch_by_email( Signal that you may fetch brand data for a particular domain soon to improve latency. This endpoint accepts an email address, extracts the domain from it, validates that it's not a disposable or free email provider, and queues the - domain for prefetching. This endpoint does not charge credits and is available - for paid customers to optimize future requests. [You must be on a paid plan to - use this endpoint] + domain for prefetching. Args: email: Email address to prefetch brand data for. The domain will be extracted from the @@ -949,63 +1042,129 @@ def retrieve_by_email( *, email: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1017,9 +1176,8 @@ def retrieve_by_email( ) -> BrandRetrieveByEmailResponse: """ Retrieve brand information using an email address while detecting disposable and - free email addresses. This endpoint extracts the domain from the email address - and returns brand data for that domain. Disposable and free email addresses - (like gmail.com, yahoo.com) will throw a 422 error. + free email addresses. Disposable and free email addresses (like gmail.com, + yahoo.com) will throw a 422 error. Args: email: Email address to retrieve brand data for (e.g., 'contact@example.com'). The @@ -1028,6 +1186,11 @@ def retrieve_by_email( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -1055,6 +1218,7 @@ def retrieve_by_email( { "email": email, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -1069,63 +1233,129 @@ def retrieve_by_isin( *, isin: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1137,8 +1367,7 @@ def retrieve_by_isin( ) -> BrandRetrieveByIsinResponse: """ Retrieve brand information using an ISIN (International Securities - Identification Number). This endpoint looks up the company associated with the - ISIN and returns its brand data. + Identification Number). Args: isin: ISIN (International Securities Identification Number) to retrieve brand data for @@ -1147,6 +1376,11 @@ def retrieve_by_isin( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -1174,6 +1408,7 @@ def retrieve_by_isin( { "isin": isin, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -1430,63 +1665,129 @@ def retrieve_by_name( ] | Omit = omit, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1496,20 +1797,23 @@ def retrieve_by_name( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandRetrieveByNameResponse: - """Retrieve brand information using a company name. - - This endpoint searches for the - company by name and returns its brand data. + """ + Retrieve brand information using a company name. Args: name: Company name to retrieve brand data for (e.g., 'Apple Inc', 'Microsoft Corporation'). Must be 3-30 characters. - country_gl: Optional country code (GL parameter) to specify the country. This affects the - geographic location used for search queries. + country_gl: Optional country code hint (GL parameter) to specify the country for the company + name. force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -1538,6 +1842,7 @@ def retrieve_by_name( "name": name, "country_gl": country_gl, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -1552,63 +1857,129 @@ def retrieve_by_ticker( *, ticker: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, ticker_exchange: Literal[ "AMEX", @@ -1693,10 +2064,8 @@ def retrieve_by_ticker( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandRetrieveByTickerResponse: - """Retrieve brand information using a stock ticker symbol. - - This endpoint looks up - the company associated with the ticker and returns its brand data. + """ + Retrieve brand information using a stock ticker symbol. Args: ticker: Stock ticker symbol to retrieve brand data for (e.g., 'AAPL', 'GOOGL', 'BRK.A'). @@ -1704,6 +2073,11 @@ def retrieve_by_ticker( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -1733,6 +2107,7 @@ def retrieve_by_ticker( { "ticker": ticker, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "ticker_exchange": ticker_exchange, "timeout_ms": timeout_ms, @@ -1743,69 +2118,11 @@ def retrieve_by_ticker( cast_to=BrandRetrieveByTickerResponse, ) - def retrieve_naics( - self, - *, - input: str, - max_results: int | Omit = omit, - min_results: int | Omit = omit, - timeout_ms: int | 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, - ) -> BrandRetrieveNaicsResponse: - """ - Endpoint to classify any brand into a 2022 NAICS code. - - Args: - input: Brand domain or title to retrieve NAICS code for. If a valid domain is provided - in `input`, it will be used for classification, otherwise, we will search for - the brand using the provided title. - - max_results: Maximum number of NAICS codes to return. Must be between 1 and 10. Defaults - to 5. - - min_results: Minimum number of NAICS codes to return. Must be at least 1. Defaults to 1. - - timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer - than this value, it will be aborted with a 408 status code. Maximum allowed - value is 300000ms (5 minutes). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/brand/naics", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "input": input, - "max_results": max_results, - "min_results": min_results, - "timeout_ms": timeout_ms, - }, - brand_retrieve_naics_params.BrandRetrieveNaicsParams, - ), - ), - cast_to=BrandRetrieveNaicsResponse, - ) - def retrieve_simplified( self, *, domain: str, + max_age_ms: int | Omit = omit, timeout_ms: int | 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. @@ -1816,12 +2133,17 @@ def retrieve_simplified( ) -> BrandRetrieveSimplifiedResponse: """ Returns a simplified version of brand data containing only essential - information: domain, title, colors, logos, and backdrops. This endpoint is - optimized for faster responses and reduced data transfer. + information: domain, title, colors, logos, and backdrops. Optimized for faster + responses and reduced data transfer. Args: domain: Domain name to retrieve simplified brand data for + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes). @@ -1844,6 +2166,7 @@ def retrieve_simplified( query=maybe_transform( { "domain": domain, + "max_age_ms": max_age_ms, "timeout_ms": timeout_ms, }, brand_retrieve_simplified_params.BrandRetrieveSimplifiedParams, @@ -1852,102 +2175,45 @@ def retrieve_simplified( cast_to=BrandRetrieveSimplifiedResponse, ) - def screenshot( - self, - *, - domain: str, - full_screenshot: Literal["true", "false"] | Omit = omit, - page: Literal["login", "signup", "blog", "careers", "pricing", "terms", "privacy", "contact"] | Omit = omit, - prioritize: Literal["speed", "quality"] | 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, - ) -> BrandScreenshotResponse: - """Capture a screenshot of a website. - - Supports both viewport (standard browser - view) and full-page screenshots. Can also screenshot specific page types (login, - pricing, etc.) by using heuristics to find the appropriate URL. Returns a URL to - the uploaded screenshot image hosted on our CDN. - - Args: - domain: Domain name to take screenshot of (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. - - full_screenshot: Optional parameter to determine screenshot type. If 'true', takes a full page - screenshot capturing all content. If 'false' or not provided, takes a viewport - screenshot (standard browser view). - - page: Optional parameter to specify which page type to screenshot. If provided, the - system will scrape the domain's links and use heuristics to find the most - appropriate URL for the specified page type (30 supported languages). If not - provided, screenshots the main domain landing page. - - prioritize: Optional parameter to prioritize screenshot capture. If 'speed', optimizes for - faster capture with basic quality. If 'quality', optimizes for higher quality - with longer wait times. Defaults to 'quality' if not provided. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/brand/screenshot", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "domain": domain, - "full_screenshot": full_screenshot, - "page": page, - "prioritize": prioritize, - }, - brand_screenshot_params.BrandScreenshotParams, - ), - ), - cast_to=BrandScreenshotResponse, - ) - - def styleguide( + def web_scrape_html( self, *, - direct_url: str | Omit = omit, - domain: str | Omit = omit, + url: str, + include_frames: bool | Omit = omit, + max_age_ms: int | Omit = omit, + pdf: brand_web_scrape_html_params.Pdf | Omit = omit, timeout_ms: int | Omit = omit, + wait_for_ms: int | 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, - ) -> BrandStyleguideResponse: + ) -> BrandWebScrapeHTMLResponse: """ - Automatically extract comprehensive design system information from a brand's - website including colors, typography, spacing, shadows, and UI components. - Either 'domain' or 'directUrl' must be provided as a query parameter, but not - both. + Scrapes the given URL and returns the raw HTML content of the page. Args: - direct_url: A specific URL to fetch the styleguide from directly, bypassing domain - resolution (e.g., 'https://example.com/design-system'). + url: Full URL to scrape (must include http:// or https:// protocol) + + include_frames: When true, iframes are rendered inline into the returned HTML. - domain: Domain name to extract styleguide from (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + pdf: PDF parsing controls. Use start/end to limit text extraction and OCR to an + inclusive 1-based page range. timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes). + wait_for_ms: + Optional browser wait time in milliseconds after initial page load. Min: 0. Max: + 30000 (30 seconds). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1957,7 +2223,7 @@ def styleguide( timeout: Override the client-level default timeout for this request, in seconds """ return self._get( - "/brand/styleguide", + "/web/scrape/html", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1965,72 +2231,55 @@ def styleguide( timeout=timeout, query=maybe_transform( { - "direct_url": direct_url, - "domain": domain, + "url": url, + "include_frames": include_frames, + "max_age_ms": max_age_ms, + "pdf": pdf, "timeout_ms": timeout_ms, + "wait_for_ms": wait_for_ms, }, - brand_styleguide_params.BrandStyleguideParams, + brand_web_scrape_html_params.BrandWebScrapeHTMLParams, ), ), - cast_to=BrandStyleguideResponse, + cast_to=BrandWebScrapeHTMLResponse, ) - def web_scrape_html( + def web_scrape_images( self, *, url: str, + enrichment: brand_web_scrape_images_params.Enrichment | Omit = omit, + max_age_ms: int | Omit = omit, + timeout_ms: int | Omit = omit, + wait_for_ms: int | 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, - ) -> BrandWebScrapeHTMLResponse: + ) -> BrandWebScrapeImagesResponse: """ - Scrapes the given URL and returns the raw HTML content of the page. + Extract image assets from a web page, including standard URLs, inline SVGs, data + URIs, responsive image sources, metadata, CSS backgrounds, video posters, and + embeds. The base request costs 1 credit. When enrichment is enabled, the entire + call costs 5 credits. Args: - url: Full URL to scrape (must include http:// or https:// protocol) - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request + url: Page URL to inspect. Must include http:// or https://. - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/web/scrape/html", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"url": url}, brand_web_scrape_html_params.BrandWebScrapeHTMLParams), - ), - cast_to=BrandWebScrapeHTMLResponse, - ) + enrichment: Optional per-image processing, sent as deep-object query params such as + enrichment[resolution]=true. - def web_scrape_images( - self, - *, - url: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> BrandWebScrapeImagesResponse: - """Scrapes all images from the given URL. + max_age_ms: Reuse a cached result this many milliseconds old or newer. Default: 86400000 (1 + day). Set to 0 to bypass cache. Maximum: 2592000000 (30 days). - Extracts images from img, svg, - picture/source, link, and video elements including inline SVGs, base64 data - URIs, and standard URLs. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). - Args: - url: Full URL to scrape images from (must include http:// or https:// protocol) + wait_for_ms: Optional browser wait time in milliseconds after initial page load before + collecting images. Min: 0. Max: 30000 (30 seconds). extra_headers: Send extra headers @@ -2047,7 +2296,16 @@ def web_scrape_images( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"url": url}, brand_web_scrape_images_params.BrandWebScrapeImagesParams), + query=maybe_transform( + { + "url": url, + "enrichment": enrichment, + "max_age_ms": max_age_ms, + "timeout_ms": timeout_ms, + "wait_for_ms": wait_for_ms, + }, + brand_web_scrape_images_params.BrandWebScrapeImagesParams, + ), ), cast_to=BrandWebScrapeImagesResponse, ) @@ -2056,10 +2314,15 @@ def web_scrape_md( self, *, url: str, + include_frames: bool | Omit = omit, include_images: bool | Omit = omit, include_links: bool | Omit = omit, + max_age_ms: int | Omit = omit, + pdf: brand_web_scrape_md_params.Pdf | Omit = omit, shorten_base64_images: bool | Omit = omit, + timeout_ms: int | Omit = omit, use_main_content_only: bool | Omit = omit, + wait_for_ms: int | 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, @@ -2068,22 +2331,37 @@ def web_scrape_md( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandWebScrapeMdResponse: """ - Scrapes the given URL, converts the HTML content to Markdown, and returns the - result. + Scrapes the given URL into LLM usable Markdown. Args: - url: Full URL to scrape and convert to markdown (must include http:// or https:// + url: Full URL to scrape into LLM usable Markdown (must include http:// or https:// protocol) + include_frames: When true, the contents of iframes are rendered to Markdown. + include_images: Include image references in Markdown output include_links: Preserve hyperlinks in Markdown output + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + pdf: PDF parsing controls. Use start/end to limit text extraction and OCR to an + inclusive 1-based page range. + shorten_base64_images: Shorten base64-encoded image data in the Markdown output + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + use_main_content_only: Extract only the main content of the page, excluding headers, footers, sidebars, and navigation + wait_for_ms: Optional browser wait time in milliseconds after initial page load before + converting the page to Markdown. Min: 0. Max: 30000 (30 seconds). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -2102,10 +2380,15 @@ def web_scrape_md( query=maybe_transform( { "url": url, + "include_frames": include_frames, "include_images": include_images, "include_links": include_links, + "max_age_ms": max_age_ms, + "pdf": pdf, "shorten_base64_images": shorten_base64_images, + "timeout_ms": timeout_ms, "use_main_content_only": use_main_content_only, + "wait_for_ms": wait_for_ms, }, brand_web_scrape_md_params.BrandWebScrapeMdParams, ), @@ -2118,6 +2401,8 @@ def web_scrape_sitemap( *, domain: str, max_links: int | Omit = omit, + timeout_ms: int | Omit = omit, + url_regex: 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, @@ -2126,17 +2411,21 @@ def web_scrape_sitemap( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandWebScrapeSitemapResponse: """ - Crawls the sitemap of the given domain and returns all discovered page URLs. - Supports sitemap index files (recursive), parallel fetching with concurrency - control, deduplication, and filters out non-page resources (images, PDFs, etc.). + Crawl an entire website's sitemap and return all discovered page URLs. Args: - domain: Domain name to crawl sitemaps for (e.g., 'example.com'). The domain will be - automatically normalized and validated. + domain: Domain to build a sitemap for max_links: Maximum number of links to return from the sitemap crawl. Defaults to 10,000. Minimum is 1, maximum is 100,000. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + + url_regex: Optional RE2-compatible regex pattern. Only URLs matching this pattern are + returned and counted against maxLinks. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -2156,6 +2445,8 @@ def web_scrape_sitemap( { "domain": domain, "max_links": max_links, + "timeout_ms": timeout_ms, + "url_regex": url_regex, }, brand_web_scrape_sitemap_params.BrandWebScrapeSitemapParams, ), @@ -2189,63 +2480,129 @@ async def retrieve( *, domain: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2263,8 +2620,12 @@ async def retrieve( domain: Domain name to retrieve brand data for (e.g., 'example.com', 'google.com'). Cannot be used with name or ticker parameters. - force_language: Optional parameter to force the language of the retrieved brand data. Works with - all three lookup methods. + force_language: Optional parameter to force the language of the retrieved brand data. + + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of @@ -2293,6 +2654,7 @@ async def retrieve( { "domain": domain, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -2306,6 +2668,7 @@ async def ai_product( self, *, url: str, + max_age_ms: int | Omit = omit, timeout_ms: int | 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. @@ -2315,15 +2678,19 @@ async def ai_product( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductResponse: """ - Beta feature: Given a single URL, determines if it is a product detail page, - classifies the platform/product type, and extracts the product information. - Supports Amazon, TikTok Shop, Etsy, and generic ecommerce sites. + Given a single URL, determines if it is a product page and extracts the product + information. Args: url: The product page URL to extract product data from. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -2338,6 +2705,7 @@ async def ai_product( body=await async_maybe_transform( { "url": url, + "max_age_ms": max_age_ms, "timeout_ms": timeout_ms, }, brand_ai_product_params.BrandAIProductParams, @@ -2353,6 +2721,7 @@ async def ai_products( self, *, domain: str, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2362,19 +2731,24 @@ async def ai_products( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductsResponse: - """Beta feature: Extract product information from a brand's website. + """Extract product information from a brand's website. - We will - analyze the website and return a list of products with details such as name, - description, image, pricing, features, and more. + We will analyze the website + and return a list of products with details such as name, description, image, + pricing, features, and more. Args: domain: The domain name to analyze. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + max_products: Maximum number of products to extract. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -2391,6 +2765,7 @@ async def ai_products( self, *, direct_url: str, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2400,20 +2775,25 @@ async def ai_products( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandAIProductsResponse: - """Beta feature: Extract product information from a brand's website. + """Extract product information from a brand's website. - We will - analyze the website and return a list of products with details such as name, - description, image, pricing, features, and more. + We will analyze the website + and return a list of products with details such as name, description, image, + pricing, features, and more. Args: direct_url: A specific URL to use directly as the starting point for extraction without domain resolution. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + max_products: Maximum number of products to extract. - timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is - 300000ms (5 minutes). + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). extra_headers: Send extra headers @@ -2430,6 +2810,7 @@ async def ai_products( self, *, domain: str | Omit = omit, + max_age_ms: int | Omit = omit, max_products: int | Omit = omit, timeout_ms: int | Omit = omit, direct_url: str | Omit = omit, @@ -2445,6 +2826,7 @@ async def ai_products( body=await async_maybe_transform( { "domain": domain, + "max_age_ms": max_age_ms, "max_products": max_products, "timeout_ms": timeout_ms, "direct_url": direct_url, @@ -2513,56 +2895,6 @@ async def ai_query( cast_to=BrandAIQueryResponse, ) - async def fonts( - self, - *, - domain: str, - timeout_ms: int | 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, - ) -> BrandFontsResponse: - """ - Extract font information from a brand's website including font families, usage - statistics, fallbacks, and element/word counts. - - Args: - domain: Domain name to extract fonts from (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. - - timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer - than this value, it will be aborted with a 408 status code. Maximum allowed - value is 300000ms (5 minutes). - - 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 await self._get( - "/brand/fonts", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "domain": domain, - "timeout_ms": timeout_ms, - }, - brand_fonts_params.BrandFontsParams, - ), - ), - cast_to=BrandFontsResponse, - ) - async def identify_from_transaction( self, *, @@ -2811,61 +3143,126 @@ async def identify_from_transaction( ] | Omit = omit, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, high_confidence_only: bool | Omit = omit, @@ -2896,7 +3293,6 @@ async def identify_from_transaction( high_confidence_only: When set to true, the API will perform an additional verification steps to ensure the identified brand matches the transaction with high confidence. - Defaults to false. max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of @@ -2958,9 +3354,7 @@ async def prefetch( ) -> BrandPrefetchResponse: """ Signal that you may fetch brand data for a particular domain soon to improve - latency. This endpoint does not charge credits and is available for paid - customers to optimize future requests. [You must be on a paid plan to use this - endpoint] + latency. Args: domain: Domain name to prefetch brand data for @@ -3008,9 +3402,7 @@ async def prefetch_by_email( Signal that you may fetch brand data for a particular domain soon to improve latency. This endpoint accepts an email address, extracts the domain from it, validates that it's not a disposable or free email provider, and queues the - domain for prefetching. This endpoint does not charge credits and is available - for paid customers to optimize future requests. [You must be on a paid plan to - use this endpoint] + domain for prefetching. Args: email: Email address to prefetch brand data for. The domain will be extracted from the @@ -3049,63 +3441,129 @@ async def retrieve_by_email( *, email: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -3117,9 +3575,8 @@ async def retrieve_by_email( ) -> BrandRetrieveByEmailResponse: """ Retrieve brand information using an email address while detecting disposable and - free email addresses. This endpoint extracts the domain from the email address - and returns brand data for that domain. Disposable and free email addresses - (like gmail.com, yahoo.com) will throw a 422 error. + free email addresses. Disposable and free email addresses (like gmail.com, + yahoo.com) will throw a 422 error. Args: email: Email address to retrieve brand data for (e.g., 'contact@example.com'). The @@ -3128,6 +3585,11 @@ async def retrieve_by_email( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -3155,6 +3617,7 @@ async def retrieve_by_email( { "email": email, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -3169,63 +3632,129 @@ async def retrieve_by_isin( *, isin: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -3237,8 +3766,7 @@ async def retrieve_by_isin( ) -> BrandRetrieveByIsinResponse: """ Retrieve brand information using an ISIN (International Securities - Identification Number). This endpoint looks up the company associated with the - ISIN and returns its brand data. + Identification Number). Args: isin: ISIN (International Securities Identification Number) to retrieve brand data for @@ -3247,6 +3775,11 @@ async def retrieve_by_isin( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -3274,6 +3807,7 @@ async def retrieve_by_isin( { "isin": isin, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -3530,63 +4064,129 @@ async def retrieve_by_name( ] | Omit = omit, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, timeout_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -3596,20 +4196,23 @@ async def retrieve_by_name( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandRetrieveByNameResponse: - """Retrieve brand information using a company name. - - This endpoint searches for the - company by name and returns its brand data. + """ + Retrieve brand information using a company name. Args: name: Company name to retrieve brand data for (e.g., 'Apple Inc', 'Microsoft Corporation'). Must be 3-30 characters. - country_gl: Optional country code (GL parameter) to specify the country. This affects the - geographic location used for search queries. + country_gl: Optional country code hint (GL parameter) to specify the country for the company + name. force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -3638,6 +4241,7 @@ async def retrieve_by_name( "name": name, "country_gl": country_gl, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "timeout_ms": timeout_ms, }, @@ -3652,63 +4256,129 @@ async def retrieve_by_ticker( *, ticker: str, force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] | Omit = omit, + max_age_ms: int | Omit = omit, max_speed: bool | Omit = omit, ticker_exchange: Literal[ "AMEX", @@ -3793,10 +4463,8 @@ async def retrieve_by_ticker( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandRetrieveByTickerResponse: - """Retrieve brand information using a stock ticker symbol. - - This endpoint looks up - the company associated with the ticker and returns its brand data. + """ + Retrieve brand information using a stock ticker symbol. Args: ticker: Stock ticker symbol to retrieve brand data for (e.g., 'AAPL', 'GOOGL', 'BRK.A'). @@ -3804,6 +4472,11 @@ async def retrieve_by_ticker( force_language: Optional parameter to force the language of the retrieved brand data. + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + max_speed: Optional parameter to optimize the API call for maximum speed. When set to true, the API will skip time-consuming operations for faster response at the cost of less comprehensive data. @@ -3833,6 +4506,7 @@ async def retrieve_by_ticker( { "ticker": ticker, "force_language": force_language, + "max_age_ms": max_age_ms, "max_speed": max_speed, "ticker_exchange": ticker_exchange, "timeout_ms": timeout_ms, @@ -3843,69 +4517,11 @@ async def retrieve_by_ticker( cast_to=BrandRetrieveByTickerResponse, ) - async def retrieve_naics( - self, - *, - input: str, - max_results: int | Omit = omit, - min_results: int | Omit = omit, - timeout_ms: int | 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, - ) -> BrandRetrieveNaicsResponse: - """ - Endpoint to classify any brand into a 2022 NAICS code. - - Args: - input: Brand domain or title to retrieve NAICS code for. If a valid domain is provided - in `input`, it will be used for classification, otherwise, we will search for - the brand using the provided title. - - max_results: Maximum number of NAICS codes to return. Must be between 1 and 10. Defaults - to 5. - - min_results: Minimum number of NAICS codes to return. Must be at least 1. Defaults to 1. - - timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer - than this value, it will be aborted with a 408 status code. Maximum allowed - value is 300000ms (5 minutes). - - 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 await self._get( - "/brand/naics", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "input": input, - "max_results": max_results, - "min_results": min_results, - "timeout_ms": timeout_ms, - }, - brand_retrieve_naics_params.BrandRetrieveNaicsParams, - ), - ), - cast_to=BrandRetrieveNaicsResponse, - ) - async def retrieve_simplified( self, *, domain: str, + max_age_ms: int | Omit = omit, timeout_ms: int | 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. @@ -3916,12 +4532,17 @@ async def retrieve_simplified( ) -> BrandRetrieveSimplifiedResponse: """ Returns a simplified version of brand data containing only essential - information: domain, title, colors, logos, and backdrops. This endpoint is - optimized for faster responses and reduced data transfer. + information: domain, title, colors, logos, and backdrops. Optimized for faster + responses and reduced data transfer. Args: domain: Domain name to retrieve simplified brand data for + max_age_ms: Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes). @@ -3944,6 +4565,7 @@ async def retrieve_simplified( query=await async_maybe_transform( { "domain": domain, + "max_age_ms": max_age_ms, "timeout_ms": timeout_ms, }, brand_retrieve_simplified_params.BrandRetrieveSimplifiedParams, @@ -3952,102 +4574,45 @@ async def retrieve_simplified( cast_to=BrandRetrieveSimplifiedResponse, ) - async def screenshot( - self, - *, - domain: str, - full_screenshot: Literal["true", "false"] | Omit = omit, - page: Literal["login", "signup", "blog", "careers", "pricing", "terms", "privacy", "contact"] | Omit = omit, - prioritize: Literal["speed", "quality"] | 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, - ) -> BrandScreenshotResponse: - """Capture a screenshot of a website. - - Supports both viewport (standard browser - view) and full-page screenshots. Can also screenshot specific page types (login, - pricing, etc.) by using heuristics to find the appropriate URL. Returns a URL to - the uploaded screenshot image hosted on our CDN. - - Args: - domain: Domain name to take screenshot of (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. - - full_screenshot: Optional parameter to determine screenshot type. If 'true', takes a full page - screenshot capturing all content. If 'false' or not provided, takes a viewport - screenshot (standard browser view). - - page: Optional parameter to specify which page type to screenshot. If provided, the - system will scrape the domain's links and use heuristics to find the most - appropriate URL for the specified page type (30 supported languages). If not - provided, screenshots the main domain landing page. - - prioritize: Optional parameter to prioritize screenshot capture. If 'speed', optimizes for - faster capture with basic quality. If 'quality', optimizes for higher quality - with longer wait times. Defaults to 'quality' if not provided. - - 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 await self._get( - "/brand/screenshot", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "domain": domain, - "full_screenshot": full_screenshot, - "page": page, - "prioritize": prioritize, - }, - brand_screenshot_params.BrandScreenshotParams, - ), - ), - cast_to=BrandScreenshotResponse, - ) - - async def styleguide( + async def web_scrape_html( self, *, - direct_url: str | Omit = omit, - domain: str | Omit = omit, + url: str, + include_frames: bool | Omit = omit, + max_age_ms: int | Omit = omit, + pdf: brand_web_scrape_html_params.Pdf | Omit = omit, timeout_ms: int | Omit = omit, + wait_for_ms: int | 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, - ) -> BrandStyleguideResponse: + ) -> BrandWebScrapeHTMLResponse: """ - Automatically extract comprehensive design system information from a brand's - website including colors, typography, spacing, shadows, and UI components. - Either 'domain' or 'directUrl' must be provided as a query parameter, but not - both. + Scrapes the given URL and returns the raw HTML content of the page. Args: - direct_url: A specific URL to fetch the styleguide from directly, bypassing domain - resolution (e.g., 'https://example.com/design-system'). + url: Full URL to scrape (must include http:// or https:// protocol) + + include_frames: When true, iframes are rendered inline into the returned HTML. - domain: Domain name to extract styleguide from (e.g., 'example.com', 'google.com'). The - domain will be automatically normalized and validated. + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + pdf: PDF parsing controls. Use start/end to limit text extraction and OCR to an + inclusive 1-based page range. timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes). + wait_for_ms: + Optional browser wait time in milliseconds after initial page load. Min: 0. Max: + 30000 (30 seconds). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -4057,7 +4622,7 @@ async def styleguide( timeout: Override the client-level default timeout for this request, in seconds """ return await self._get( - "/brand/styleguide", + "/web/scrape/html", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -4065,72 +4630,55 @@ async def styleguide( timeout=timeout, query=await async_maybe_transform( { - "direct_url": direct_url, - "domain": domain, + "url": url, + "include_frames": include_frames, + "max_age_ms": max_age_ms, + "pdf": pdf, "timeout_ms": timeout_ms, + "wait_for_ms": wait_for_ms, }, - brand_styleguide_params.BrandStyleguideParams, + brand_web_scrape_html_params.BrandWebScrapeHTMLParams, ), ), - cast_to=BrandStyleguideResponse, + cast_to=BrandWebScrapeHTMLResponse, ) - async def web_scrape_html( + async def web_scrape_images( self, *, url: str, + enrichment: brand_web_scrape_images_params.Enrichment | Omit = omit, + max_age_ms: int | Omit = omit, + timeout_ms: int | Omit = omit, + wait_for_ms: int | 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, - ) -> BrandWebScrapeHTMLResponse: + ) -> BrandWebScrapeImagesResponse: """ - Scrapes the given URL and returns the raw HTML content of the page. + Extract image assets from a web page, including standard URLs, inline SVGs, data + URIs, responsive image sources, metadata, CSS backgrounds, video posters, and + embeds. The base request costs 1 credit. When enrichment is enabled, the entire + call costs 5 credits. Args: - url: Full URL to scrape (must include http:// or https:// protocol) - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request + url: Page URL to inspect. Must include http:// or https://. - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._get( - "/web/scrape/html", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"url": url}, brand_web_scrape_html_params.BrandWebScrapeHTMLParams), - ), - cast_to=BrandWebScrapeHTMLResponse, - ) + enrichment: Optional per-image processing, sent as deep-object query params such as + enrichment[resolution]=true. - async def web_scrape_images( - self, - *, - url: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> BrandWebScrapeImagesResponse: - """Scrapes all images from the given URL. + max_age_ms: Reuse a cached result this many milliseconds old or newer. Default: 86400000 (1 + day). Set to 0 to bypass cache. Maximum: 2592000000 (30 days). - Extracts images from img, svg, - picture/source, link, and video elements including inline SVGs, base64 data - URIs, and standard URLs. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). - Args: - url: Full URL to scrape images from (must include http:// or https:// protocol) + wait_for_ms: Optional browser wait time in milliseconds after initial page load before + collecting images. Min: 0. Max: 30000 (30 seconds). extra_headers: Send extra headers @@ -4148,7 +4696,14 @@ async def web_scrape_images( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"url": url}, brand_web_scrape_images_params.BrandWebScrapeImagesParams + { + "url": url, + "enrichment": enrichment, + "max_age_ms": max_age_ms, + "timeout_ms": timeout_ms, + "wait_for_ms": wait_for_ms, + }, + brand_web_scrape_images_params.BrandWebScrapeImagesParams, ), ), cast_to=BrandWebScrapeImagesResponse, @@ -4158,10 +4713,15 @@ async def web_scrape_md( self, *, url: str, + include_frames: bool | Omit = omit, include_images: bool | Omit = omit, include_links: bool | Omit = omit, + max_age_ms: int | Omit = omit, + pdf: brand_web_scrape_md_params.Pdf | Omit = omit, shorten_base64_images: bool | Omit = omit, + timeout_ms: int | Omit = omit, use_main_content_only: bool | Omit = omit, + wait_for_ms: int | 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, @@ -4170,22 +4730,37 @@ async def web_scrape_md( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandWebScrapeMdResponse: """ - Scrapes the given URL, converts the HTML content to Markdown, and returns the - result. + Scrapes the given URL into LLM usable Markdown. Args: - url: Full URL to scrape and convert to markdown (must include http:// or https:// + url: Full URL to scrape into LLM usable Markdown (must include http:// or https:// protocol) + include_frames: When true, the contents of iframes are rendered to Markdown. + include_images: Include image references in Markdown output include_links: Preserve hyperlinks in Markdown output + max_age_ms: Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + + pdf: PDF parsing controls. Use start/end to limit text extraction and OCR to an + inclusive 1-based page range. + shorten_base64_images: Shorten base64-encoded image data in the Markdown output + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + use_main_content_only: Extract only the main content of the page, excluding headers, footers, sidebars, and navigation + wait_for_ms: Optional browser wait time in milliseconds after initial page load before + converting the page to Markdown. Min: 0. Max: 30000 (30 seconds). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -4204,10 +4779,15 @@ async def web_scrape_md( query=await async_maybe_transform( { "url": url, + "include_frames": include_frames, "include_images": include_images, "include_links": include_links, + "max_age_ms": max_age_ms, + "pdf": pdf, "shorten_base64_images": shorten_base64_images, + "timeout_ms": timeout_ms, "use_main_content_only": use_main_content_only, + "wait_for_ms": wait_for_ms, }, brand_web_scrape_md_params.BrandWebScrapeMdParams, ), @@ -4220,6 +4800,8 @@ async def web_scrape_sitemap( *, domain: str, max_links: int | Omit = omit, + timeout_ms: int | Omit = omit, + url_regex: 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, @@ -4228,17 +4810,21 @@ async def web_scrape_sitemap( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BrandWebScrapeSitemapResponse: """ - Crawls the sitemap of the given domain and returns all discovered page URLs. - Supports sitemap index files (recursive), parallel fetching with concurrency - control, deduplication, and filters out non-page resources (images, PDFs, etc.). + Crawl an entire website's sitemap and return all discovered page URLs. Args: - domain: Domain name to crawl sitemaps for (e.g., 'example.com'). The domain will be - automatically normalized and validated. + domain: Domain to build a sitemap for max_links: Maximum number of links to return from the sitemap crawl. Defaults to 10,000. Minimum is 1, maximum is 100,000. + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + + url_regex: Optional RE2-compatible regex pattern. Only URLs matching this pattern are + returned and counted against maxLinks. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -4258,6 +4844,8 @@ async def web_scrape_sitemap( { "domain": domain, "max_links": max_links, + "timeout_ms": timeout_ms, + "url_regex": url_regex, }, brand_web_scrape_sitemap_params.BrandWebScrapeSitemapParams, ), @@ -4282,9 +4870,6 @@ def __init__(self, brand: BrandResource) -> None: self.ai_query = to_raw_response_wrapper( brand.ai_query, ) - self.fonts = to_raw_response_wrapper( - brand.fonts, - ) self.identify_from_transaction = to_raw_response_wrapper( brand.identify_from_transaction, ) @@ -4306,18 +4891,9 @@ def __init__(self, brand: BrandResource) -> None: self.retrieve_by_ticker = to_raw_response_wrapper( brand.retrieve_by_ticker, ) - self.retrieve_naics = to_raw_response_wrapper( - brand.retrieve_naics, - ) self.retrieve_simplified = to_raw_response_wrapper( brand.retrieve_simplified, ) - self.screenshot = to_raw_response_wrapper( - brand.screenshot, - ) - self.styleguide = to_raw_response_wrapper( - brand.styleguide, - ) self.web_scrape_html = to_raw_response_wrapper( brand.web_scrape_html, ) @@ -4348,9 +4924,6 @@ def __init__(self, brand: AsyncBrandResource) -> None: self.ai_query = async_to_raw_response_wrapper( brand.ai_query, ) - self.fonts = async_to_raw_response_wrapper( - brand.fonts, - ) self.identify_from_transaction = async_to_raw_response_wrapper( brand.identify_from_transaction, ) @@ -4372,18 +4945,9 @@ def __init__(self, brand: AsyncBrandResource) -> None: self.retrieve_by_ticker = async_to_raw_response_wrapper( brand.retrieve_by_ticker, ) - self.retrieve_naics = async_to_raw_response_wrapper( - brand.retrieve_naics, - ) self.retrieve_simplified = async_to_raw_response_wrapper( brand.retrieve_simplified, ) - self.screenshot = async_to_raw_response_wrapper( - brand.screenshot, - ) - self.styleguide = async_to_raw_response_wrapper( - brand.styleguide, - ) self.web_scrape_html = async_to_raw_response_wrapper( brand.web_scrape_html, ) @@ -4414,9 +4978,6 @@ def __init__(self, brand: BrandResource) -> None: self.ai_query = to_streamed_response_wrapper( brand.ai_query, ) - self.fonts = to_streamed_response_wrapper( - brand.fonts, - ) self.identify_from_transaction = to_streamed_response_wrapper( brand.identify_from_transaction, ) @@ -4438,18 +4999,9 @@ def __init__(self, brand: BrandResource) -> None: self.retrieve_by_ticker = to_streamed_response_wrapper( brand.retrieve_by_ticker, ) - self.retrieve_naics = to_streamed_response_wrapper( - brand.retrieve_naics, - ) self.retrieve_simplified = to_streamed_response_wrapper( brand.retrieve_simplified, ) - self.screenshot = to_streamed_response_wrapper( - brand.screenshot, - ) - self.styleguide = to_streamed_response_wrapper( - brand.styleguide, - ) self.web_scrape_html = to_streamed_response_wrapper( brand.web_scrape_html, ) @@ -4480,9 +5032,6 @@ def __init__(self, brand: AsyncBrandResource) -> None: self.ai_query = async_to_streamed_response_wrapper( brand.ai_query, ) - self.fonts = async_to_streamed_response_wrapper( - brand.fonts, - ) self.identify_from_transaction = async_to_streamed_response_wrapper( brand.identify_from_transaction, ) @@ -4504,18 +5053,9 @@ def __init__(self, brand: AsyncBrandResource) -> None: self.retrieve_by_ticker = async_to_streamed_response_wrapper( brand.retrieve_by_ticker, ) - self.retrieve_naics = async_to_streamed_response_wrapper( - brand.retrieve_naics, - ) self.retrieve_simplified = async_to_streamed_response_wrapper( brand.retrieve_simplified, ) - self.screenshot = async_to_streamed_response_wrapper( - brand.screenshot, - ) - self.styleguide = async_to_streamed_response_wrapper( - brand.styleguide, - ) self.web_scrape_html = async_to_streamed_response_wrapper( brand.web_scrape_html, ) diff --git a/src/brand/dev/types/__init__.py b/src/brand/dev/types/__init__.py index 2b7129d..8bf4c53 100644 --- a/src/brand/dev/types/__init__.py +++ b/src/brand/dev/types/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations -from .brand_fonts_params import BrandFontsParams as BrandFontsParams -from .brand_fonts_response import BrandFontsResponse as BrandFontsResponse from .brand_ai_query_params import BrandAIQueryParams as BrandAIQueryParams from .brand_prefetch_params import BrandPrefetchParams as BrandPrefetchParams from .brand_retrieve_params import BrandRetrieveParams as BrandRetrieveParams @@ -11,20 +9,14 @@ from .brand_ai_query_response import BrandAIQueryResponse as BrandAIQueryResponse from .brand_prefetch_response import BrandPrefetchResponse as BrandPrefetchResponse from .brand_retrieve_response import BrandRetrieveResponse as BrandRetrieveResponse -from .brand_screenshot_params import BrandScreenshotParams as BrandScreenshotParams -from .brand_styleguide_params import BrandStyleguideParams as BrandStyleguideParams from .brand_ai_products_params import BrandAIProductsParams as BrandAIProductsParams from .brand_ai_product_response import BrandAIProductResponse as BrandAIProductResponse -from .brand_screenshot_response import BrandScreenshotResponse as BrandScreenshotResponse -from .brand_styleguide_response import BrandStyleguideResponse as BrandStyleguideResponse from .brand_ai_products_response import BrandAIProductsResponse as BrandAIProductsResponse from .brand_web_scrape_md_params import BrandWebScrapeMdParams as BrandWebScrapeMdParams -from .brand_retrieve_naics_params import BrandRetrieveNaicsParams as BrandRetrieveNaicsParams from .brand_web_scrape_html_params import BrandWebScrapeHTMLParams as BrandWebScrapeHTMLParams from .brand_web_scrape_md_response import BrandWebScrapeMdResponse as BrandWebScrapeMdResponse from .brand_retrieve_by_isin_params import BrandRetrieveByIsinParams as BrandRetrieveByIsinParams from .brand_retrieve_by_name_params import BrandRetrieveByNameParams as BrandRetrieveByNameParams -from .brand_retrieve_naics_response import BrandRetrieveNaicsResponse as BrandRetrieveNaicsResponse from .brand_prefetch_by_email_params import BrandPrefetchByEmailParams as BrandPrefetchByEmailParams from .brand_retrieve_by_email_params import BrandRetrieveByEmailParams as BrandRetrieveByEmailParams from .brand_web_scrape_html_response import BrandWebScrapeHTMLResponse as BrandWebScrapeHTMLResponse diff --git a/src/brand/dev/types/brand_ai_product_params.py b/src/brand/dev/types/brand_ai_product_params.py index 17e62c9..0768fe0 100644 --- a/src/brand/dev/types/brand_ai_product_params.py +++ b/src/brand/dev/types/brand_ai_product_params.py @@ -13,8 +13,16 @@ class BrandAIProductParams(TypedDict, total=False): url: Required[str] """The product page URL to extract product data from.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + """ + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] """Optional timeout in milliseconds for the request. - Maximum allowed value is 300000ms (5 minutes). + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). """ diff --git a/src/brand/dev/types/brand_ai_product_response.py b/src/brand/dev/types/brand_ai_product_response.py index eb31de2..1880ca5 100644 --- a/src/brand/dev/types/brand_ai_product_response.py +++ b/src/brand/dev/types/brand_ai_product_response.py @@ -23,6 +23,9 @@ class Product(BaseModel): name: str """Name of the product""" + sku: Optional[str] = None + """Stock Keeping Unit (product identifier). Null if no identifier is found.""" + tags: List[str] """Tags associated with the product""" diff --git a/src/brand/dev/types/brand_ai_products_params.py b/src/brand/dev/types/brand_ai_products_params.py index 9a61efe..50ee523 100644 --- a/src/brand/dev/types/brand_ai_products_params.py +++ b/src/brand/dev/types/brand_ai_products_params.py @@ -14,13 +14,21 @@ class ByDomain(TypedDict, total=False): domain: Required[str] """The domain name to analyze.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + """ + max_products: Annotated[int, PropertyInfo(alias="maxProducts")] """Maximum number of products to extract.""" timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] """Optional timeout in milliseconds for the request. - Maximum allowed value is 300000ms (5 minutes). + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). """ @@ -31,13 +39,21 @@ class ByDirectURL(TypedDict, total=False): domain resolution. """ + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 7 days (604800000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + """ + max_products: Annotated[int, PropertyInfo(alias="maxProducts")] """Maximum number of products to extract.""" timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] """Optional timeout in milliseconds for the request. - Maximum allowed value is 300000ms (5 minutes). + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). """ diff --git a/src/brand/dev/types/brand_ai_products_response.py b/src/brand/dev/types/brand_ai_products_response.py index 4100a1a..a074795 100644 --- a/src/brand/dev/types/brand_ai_products_response.py +++ b/src/brand/dev/types/brand_ai_products_response.py @@ -21,6 +21,9 @@ class Product(BaseModel): name: str """Name of the product""" + sku: Optional[str] = None + """Stock Keeping Unit (product identifier). Null if no identifier is found.""" + tags: List[str] """Tags associated with the product""" diff --git a/src/brand/dev/types/brand_fonts_params.py b/src/brand/dev/types/brand_fonts_params.py deleted file mode 100644 index db13d2f..0000000 --- a/src/brand/dev/types/brand_fonts_params.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["BrandFontsParams"] - - -class BrandFontsParams(TypedDict, total=False): - domain: Required[str] - """Domain name to extract fonts from (e.g., 'example.com', 'google.com'). - - The domain will be automatically normalized and validated. - """ - - timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] - """Optional timeout in milliseconds for the request. - - If the request takes longer than this value, it will be aborted with a 408 - status code. Maximum allowed value is 300000ms (5 minutes). - """ diff --git a/src/brand/dev/types/brand_fonts_response.py b/src/brand/dev/types/brand_fonts_response.py deleted file mode 100644 index 2721af9..0000000 --- a/src/brand/dev/types/brand_fonts_response.py +++ /dev/null @@ -1,44 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .._models import BaseModel - -__all__ = ["BrandFontsResponse", "Font"] - - -class Font(BaseModel): - fallbacks: List[str] - """Array of fallback font families""" - - font: str - """Font family name""" - - num_elements: float - """Number of elements using this font""" - - num_words: float - """Number of words using this font""" - - percent_elements: float - """Percentage of elements using this font""" - - percent_words: float - """Percentage of words using this font""" - - uses: List[str] - """Array of CSS selectors or element types where this font is used""" - - -class BrandFontsResponse(BaseModel): - code: int - """HTTP status code, e.g., 200""" - - domain: str - """The normalized domain that was processed""" - - fonts: List[Font] - """Array of font usage information""" - - status: str - """Status of the response, e.g., 'ok'""" diff --git a/src/brand/dev/types/brand_identify_from_transaction_params.py b/src/brand/dev/types/brand_identify_from_transaction_params.py index e04b1e5..e6ecadd 100644 --- a/src/brand/dev/types/brand_identify_from_transaction_params.py +++ b/src/brand/dev/types/brand_identify_from_transaction_params.py @@ -263,61 +263,126 @@ class BrandIdentifyFromTransactionParams(TypedDict, total=False): """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] """Optional parameter to force the language of the retrieved brand data.""" @@ -325,7 +390,6 @@ class BrandIdentifyFromTransactionParams(TypedDict, total=False): """ When set to true, the API will perform an additional verification steps to ensure the identified brand matches the transaction with high confidence. - Defaults to false. """ max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] diff --git a/src/brand/dev/types/brand_identify_from_transaction_response.py b/src/brand/dev/types/brand_identify_from_transaction_response.py index 5c1261d..5d499ea 100644 --- a/src/brand/dev/types/brand_identify_from_transaction_response.py +++ b/src/brand/dev/types/brand_identify_from_transaction_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_by_email_params.py b/src/brand/dev/types/brand_retrieve_by_email_params.py index 886c213..580b5c1 100644 --- a/src/brand/dev/types/brand_retrieve_by_email_params.py +++ b/src/brand/dev/types/brand_retrieve_by_email_params.py @@ -18,64 +18,137 @@ class BrandRetrieveByEmailParams(TypedDict, total=False): """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] """Optional parameter to force the language of the retrieved brand data.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + """ + max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] """Optional parameter to optimize the API call for maximum speed. diff --git a/src/brand/dev/types/brand_retrieve_by_email_response.py b/src/brand/dev/types/brand_retrieve_by_email_response.py index 922fa75..db6b862 100644 --- a/src/brand/dev/types/brand_retrieve_by_email_response.py +++ b/src/brand/dev/types/brand_retrieve_by_email_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_by_isin_params.py b/src/brand/dev/types/brand_retrieve_by_isin_params.py index db559fa..630834f 100644 --- a/src/brand/dev/types/brand_retrieve_by_isin_params.py +++ b/src/brand/dev/types/brand_retrieve_by_isin_params.py @@ -18,64 +18,137 @@ class BrandRetrieveByIsinParams(TypedDict, total=False): """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] """Optional parameter to force the language of the retrieved brand data.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + """ + max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] """Optional parameter to optimize the API call for maximum speed. diff --git a/src/brand/dev/types/brand_retrieve_by_isin_response.py b/src/brand/dev/types/brand_retrieve_by_isin_response.py index 21a860e..3d080e9 100644 --- a/src/brand/dev/types/brand_retrieve_by_isin_response.py +++ b/src/brand/dev/types/brand_retrieve_by_isin_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_by_name_params.py b/src/brand/dev/types/brand_retrieve_by_name_params.py index 847bdb3..972fbd0 100644 --- a/src/brand/dev/types/brand_retrieve_by_name_params.py +++ b/src/brand/dev/types/brand_retrieve_by_name_params.py @@ -257,70 +257,143 @@ class BrandRetrieveByNameParams(TypedDict, total=False): "zm", "zw", ] - """Optional country code (GL parameter) to specify the country. - - This affects the geographic location used for search queries. + """ + Optional country code hint (GL parameter) to specify the country for the company + name. """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] """Optional parameter to force the language of the retrieved brand data.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + """ + max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] """Optional parameter to optimize the API call for maximum speed. diff --git a/src/brand/dev/types/brand_retrieve_by_name_response.py b/src/brand/dev/types/brand_retrieve_by_name_response.py index 1e462e7..9a5b18e 100644 --- a/src/brand/dev/types/brand_retrieve_by_name_response.py +++ b/src/brand/dev/types/brand_retrieve_by_name_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_by_ticker_params.py b/src/brand/dev/types/brand_retrieve_by_ticker_params.py index d328385..8ba1731 100644 --- a/src/brand/dev/types/brand_retrieve_by_ticker_params.py +++ b/src/brand/dev/types/brand_retrieve_by_ticker_params.py @@ -17,64 +17,137 @@ class BrandRetrieveByTickerParams(TypedDict, total=False): """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] """Optional parameter to force the language of the retrieved brand data.""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + """ + max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] """Optional parameter to optimize the API call for maximum speed. diff --git a/src/brand/dev/types/brand_retrieve_by_ticker_response.py b/src/brand/dev/types/brand_retrieve_by_ticker_response.py index 9815a65..964d7a4 100644 --- a/src/brand/dev/types/brand_retrieve_by_ticker_response.py +++ b/src/brand/dev/types/brand_retrieve_by_ticker_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_naics_params.py b/src/brand/dev/types/brand_retrieve_naics_params.py deleted file mode 100644 index 2803c13..0000000 --- a/src/brand/dev/types/brand_retrieve_naics_params.py +++ /dev/null @@ -1,34 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["BrandRetrieveNaicsParams"] - - -class BrandRetrieveNaicsParams(TypedDict, total=False): - input: Required[str] - """Brand domain or title to retrieve NAICS code for. - - If a valid domain is provided in `input`, it will be used for classification, - otherwise, we will search for the brand using the provided title. - """ - - max_results: Annotated[int, PropertyInfo(alias="maxResults")] - """Maximum number of NAICS codes to return. - - Must be between 1 and 10. Defaults to 5. - """ - - min_results: Annotated[int, PropertyInfo(alias="minResults")] - """Minimum number of NAICS codes to return. Must be at least 1. Defaults to 1.""" - - timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] - """Optional timeout in milliseconds for the request. - - If the request takes longer than this value, it will be aborted with a 408 - status code. Maximum allowed value is 300000ms (5 minutes). - """ diff --git a/src/brand/dev/types/brand_retrieve_naics_response.py b/src/brand/dev/types/brand_retrieve_naics_response.py deleted file mode 100644 index a53da5e..0000000 --- a/src/brand/dev/types/brand_retrieve_naics_response.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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__ = ["BrandRetrieveNaicsResponse", "Code"] - - -class Code(BaseModel): - code: str - """NAICS code""" - - confidence: Literal["high", "medium", "low"] - """Confidence level for how well this NAICS code matches the company description""" - - name: str - """NAICS title""" - - -class BrandRetrieveNaicsResponse(BaseModel): - codes: Optional[List[Code]] = None - """Array of NAICS codes and titles.""" - - domain: Optional[str] = None - """Domain found for the brand""" - - status: Optional[str] = None - """Status of the response, e.g., 'ok'""" - - type: Optional[str] = None - """Industry classification type, for naics api it will be `naics`""" diff --git a/src/brand/dev/types/brand_retrieve_params.py b/src/brand/dev/types/brand_retrieve_params.py index 03e102a..2812ab5 100644 --- a/src/brand/dev/types/brand_retrieve_params.py +++ b/src/brand/dev/types/brand_retrieve_params.py @@ -17,65 +17,135 @@ class BrandRetrieveParams(TypedDict, total=False): """ force_language: Literal[ + "afrikaans", "albanian", + "amharic", "arabic", + "armenian", + "assamese", + "aymara", "azeri", + "basque", + "belarusian", "bengali", + "bosnian", "bulgarian", + "burmese", "cantonese", + "catalan", "cebuano", + "chinese", + "corsican", "croatian", "czech", "danish", "dutch", "english", + "esperanto", "estonian", "farsi", + "fijian", "finnish", "french", + "galician", + "georgian", "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", "hausa", "hawaiian", + "hebrew", "hindi", + "hmong", "hungarian", "icelandic", + "igbo", "indonesian", + "irish", "italian", + "japanese", + "javanese", + "kannada", "kazakh", + "khmer", + "kinyarwanda", "korean", + "kurdish", "kyrgyz", + "lao", "latin", "latvian", + "lingala", "lithuanian", + "luxembourgish", "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", "mongolian", "nepali", "norwegian", + "odia", + "oromo", "pashto", "pidgin", "polish", "portuguese", + "punjabi", + "quechua", "romanian", "russian", + "samoan", + "scottish-gaelic", "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", "slovak", "slovene", "somali", "spanish", + "sundanese", "swahili", "swedish", "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", "turkish", + "turkmen", "ukrainian", "urdu", + "uyghur", "uzbek", "vietnamese", "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", ] - """Optional parameter to force the language of the retrieved brand data. + """Optional parameter to force the language of the retrieved brand data.""" - Works with all three lookup methods. + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. """ max_speed: Annotated[bool, PropertyInfo(alias="maxSpeed")] diff --git a/src/brand/dev/types/brand_retrieve_response.py b/src/brand/dev/types/brand_retrieve_response.py index 28aea6f..d272dab 100644 --- a/src/brand/dev/types/brand_retrieve_response.py +++ b/src/brand/dev/types/brand_retrieve_response.py @@ -415,8 +415,42 @@ class BrandLogo(BaseModel): class BrandSocial(BaseModel): - type: Optional[str] = None - """Type of social media, e.g., 'facebook', 'twitter'""" + type: Optional[ + Literal[ + "x", + "facebook", + "instagram", + "linkedin", + "youtube", + "pinterest", + "tiktok", + "dribbble", + "github", + "behance", + "snapchat", + "whatsapp", + "telegram", + "line", + "discord", + "twitch", + "vimeo", + "imdb", + "tumblr", + "flickr", + "giphy", + "medium", + "spotify", + "soundcloud", + "tripadvisor", + "yelp", + "producthunt", + "reddit", + "crunchbase", + "appstore", + "playstore", + ] + ] = None + """Type of social media platform""" url: Optional[str] = None """URL of the social media page""" @@ -470,6 +504,136 @@ class Brand(BaseModel): phone: Optional[str] = None """Company phone number""" + primary_language: Optional[ + Literal[ + "afrikaans", + "albanian", + "amharic", + "arabic", + "armenian", + "assamese", + "aymara", + "azeri", + "basque", + "belarusian", + "bengali", + "bosnian", + "bulgarian", + "burmese", + "cantonese", + "catalan", + "cebuano", + "chinese", + "corsican", + "croatian", + "czech", + "danish", + "dutch", + "english", + "esperanto", + "estonian", + "farsi", + "fijian", + "finnish", + "french", + "galician", + "georgian", + "german", + "greek", + "guarani", + "gujarati", + "haitian-creole", + "hausa", + "hawaiian", + "hebrew", + "hindi", + "hmong", + "hungarian", + "icelandic", + "igbo", + "indonesian", + "irish", + "italian", + "japanese", + "javanese", + "kannada", + "kazakh", + "khmer", + "kinyarwanda", + "korean", + "kurdish", + "kyrgyz", + "lao", + "latin", + "latvian", + "lingala", + "lithuanian", + "luxembourgish", + "macedonian", + "malagasy", + "malay", + "malayalam", + "maltese", + "maori", + "marathi", + "mongolian", + "nepali", + "norwegian", + "odia", + "oromo", + "pashto", + "pidgin", + "polish", + "portuguese", + "punjabi", + "quechua", + "romanian", + "russian", + "samoan", + "scottish-gaelic", + "serbian", + "sesotho", + "shona", + "sindhi", + "sinhala", + "slovak", + "slovene", + "somali", + "spanish", + "sundanese", + "swahili", + "swedish", + "tagalog", + "tajik", + "tamil", + "tatar", + "telugu", + "thai", + "tibetan", + "tigrinya", + "tongan", + "tswana", + "turkish", + "turkmen", + "ukrainian", + "urdu", + "uyghur", + "uzbek", + "vietnamese", + "welsh", + "wolof", + "xhosa", + "yiddish", + "yoruba", + "zulu", + ] + ] = None + """The primary language of the brand's website content. + + Detected from the HTML lang tag, page content analysis, or social media + descriptions. + """ + slogan: Optional[str] = None """The brand's slogan""" diff --git a/src/brand/dev/types/brand_retrieve_simplified_params.py b/src/brand/dev/types/brand_retrieve_simplified_params.py index b9d9cd3..4a60ba7 100644 --- a/src/brand/dev/types/brand_retrieve_simplified_params.py +++ b/src/brand/dev/types/brand_retrieve_simplified_params.py @@ -13,6 +13,14 @@ class BrandRetrieveSimplifiedParams(TypedDict, total=False): domain: Required[str] """Domain name to retrieve simplified brand data for""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Maximum age in milliseconds for cached brand data before the API performs a hard + refresh. Defaults to 3 months (7776000000 ms). Values below 1 day (86400000 ms) + are clamped to 1 day; values above 1 year (31536000000 ms) are clamped to 1 + year. + """ + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] """Optional timeout in milliseconds for the request. diff --git a/src/brand/dev/types/brand_screenshot_params.py b/src/brand/dev/types/brand_screenshot_params.py deleted file mode 100644 index 4f26b1f..0000000 --- a/src/brand/dev/types/brand_screenshot_params.py +++ /dev/null @@ -1,40 +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, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["BrandScreenshotParams"] - - -class BrandScreenshotParams(TypedDict, total=False): - domain: Required[str] - """Domain name to take screenshot of (e.g., 'example.com', 'google.com'). - - The domain will be automatically normalized and validated. - """ - - full_screenshot: Annotated[Literal["true", "false"], PropertyInfo(alias="fullScreenshot")] - """Optional parameter to determine screenshot type. - - If 'true', takes a full page screenshot capturing all content. If 'false' or not - provided, takes a viewport screenshot (standard browser view). - """ - - page: Literal["login", "signup", "blog", "careers", "pricing", "terms", "privacy", "contact"] - """Optional parameter to specify which page type to screenshot. - - If provided, the system will scrape the domain's links and use heuristics to - find the most appropriate URL for the specified page type (30 supported - languages). If not provided, screenshots the main domain landing page. - """ - - prioritize: Literal["speed", "quality"] - """Optional parameter to prioritize screenshot capture. - - If 'speed', optimizes for faster capture with basic quality. If 'quality', - optimizes for higher quality with longer wait times. Defaults to 'quality' if - not provided. - """ diff --git a/src/brand/dev/types/brand_screenshot_response.py b/src/brand/dev/types/brand_screenshot_response.py deleted file mode 100644 index c43ae74..0000000 --- a/src/brand/dev/types/brand_screenshot_response.py +++ /dev/null @@ -1,27 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["BrandScreenshotResponse"] - - -class BrandScreenshotResponse(BaseModel): - code: Optional[int] = None - """HTTP status code""" - - domain: Optional[str] = None - """The normalized domain that was processed""" - - screenshot: Optional[str] = None - """Public URL of the uploaded screenshot image""" - - screenshot_type: Optional[Literal["viewport", "fullPage"]] = FieldInfo(alias="screenshotType", default=None) - """Type of screenshot that was captured""" - - status: Optional[str] = None - """Status of the response, e.g., 'ok'""" diff --git a/src/brand/dev/types/brand_styleguide_params.py b/src/brand/dev/types/brand_styleguide_params.py deleted file mode 100644 index 9c858b5..0000000 --- a/src/brand/dev/types/brand_styleguide_params.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["BrandStyleguideParams"] - - -class BrandStyleguideParams(TypedDict, total=False): - direct_url: Annotated[str, PropertyInfo(alias="directUrl")] - """ - A specific URL to fetch the styleguide from directly, bypassing domain - resolution (e.g., 'https://example.com/design-system'). - """ - - domain: str - """Domain name to extract styleguide from (e.g., 'example.com', 'google.com'). - - The domain will be automatically normalized and validated. - """ - - timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] - """Optional timeout in milliseconds for the request. - - If the request takes longer than this value, it will be aborted with a 408 - status code. Maximum allowed value is 300000ms (5 minutes). - """ diff --git a/src/brand/dev/types/brand_styleguide_response.py b/src/brand/dev/types/brand_styleguide_response.py deleted file mode 100644 index edc2d29..0000000 --- a/src/brand/dev/types/brand_styleguide_response.py +++ /dev/null @@ -1,395 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = [ - "BrandStyleguideResponse", - "Styleguide", - "StyleguideColors", - "StyleguideComponents", - "StyleguideComponentsButton", - "StyleguideComponentsButtonLink", - "StyleguideComponentsButtonPrimary", - "StyleguideComponentsButtonSecondary", - "StyleguideComponentsCard", - "StyleguideElementSpacing", - "StyleguideShadows", - "StyleguideTypography", - "StyleguideTypographyHeadings", - "StyleguideTypographyHeadingsH1", - "StyleguideTypographyHeadingsH2", - "StyleguideTypographyHeadingsH3", - "StyleguideTypographyHeadingsH4", - "StyleguideTypographyP", -] - - -class StyleguideColors(BaseModel): - """Primary colors used on the website""" - - accent: str - """Accent color (hex format)""" - - background: str - """Background color (hex format)""" - - text: str - """Text color (hex format)""" - - -class StyleguideComponentsButtonLink(BaseModel): - background_color: str = FieldInfo(alias="backgroundColor") - - border_color: str = FieldInfo(alias="borderColor") - """ - Border color as CSS hex (#RRGGBB or #RRGGBBAA when computed border-color has - alpha) - """ - - border_radius: str = FieldInfo(alias="borderRadius") - - border_style: str = FieldInfo(alias="borderStyle") - - border_width: str = FieldInfo(alias="borderWidth") - - box_shadow: str = FieldInfo(alias="boxShadow") - """Computed box-shadow (comma-separated layers when present)""" - - color: str - - css: str - """Ready-to-use CSS declaration block for this component style""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - min_height: str = FieldInfo(alias="minHeight") - """Sampled minimum height of the button box (typically px)""" - - min_width: str = FieldInfo(alias="minWidth") - """Sampled minimum width of the button box (typically px)""" - - padding: str - - text_decoration: str = FieldInfo(alias="textDecoration") - - font_fallbacks: Optional[List[str]] = FieldInfo(alias="fontFallbacks", default=None) - """Full ordered font list from computed font-family""" - - font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) - """Primary button typeface (first in fontFallbacks)""" - - text_decoration_color: Optional[str] = FieldInfo(alias="textDecorationColor", default=None) - """Hex color of the underline when it differs from the text color""" - - -class StyleguideComponentsButtonPrimary(BaseModel): - background_color: str = FieldInfo(alias="backgroundColor") - - border_color: str = FieldInfo(alias="borderColor") - """ - Border color as CSS hex (#RRGGBB or #RRGGBBAA when computed border-color has - alpha) - """ - - border_radius: str = FieldInfo(alias="borderRadius") - - border_style: str = FieldInfo(alias="borderStyle") - - border_width: str = FieldInfo(alias="borderWidth") - - box_shadow: str = FieldInfo(alias="boxShadow") - """Computed box-shadow (comma-separated layers when present)""" - - color: str - - css: str - """Ready-to-use CSS declaration block for this component style""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - min_height: str = FieldInfo(alias="minHeight") - """Sampled minimum height of the button box (typically px)""" - - min_width: str = FieldInfo(alias="minWidth") - """Sampled minimum width of the button box (typically px)""" - - padding: str - - text_decoration: str = FieldInfo(alias="textDecoration") - - font_fallbacks: Optional[List[str]] = FieldInfo(alias="fontFallbacks", default=None) - """Full ordered font list from computed font-family""" - - font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) - """Primary button typeface (first in fontFallbacks)""" - - text_decoration_color: Optional[str] = FieldInfo(alias="textDecorationColor", default=None) - """Hex color of the underline when it differs from the text color""" - - -class StyleguideComponentsButtonSecondary(BaseModel): - background_color: str = FieldInfo(alias="backgroundColor") - - border_color: str = FieldInfo(alias="borderColor") - """ - Border color as CSS hex (#RRGGBB or #RRGGBBAA when computed border-color has - alpha) - """ - - border_radius: str = FieldInfo(alias="borderRadius") - - border_style: str = FieldInfo(alias="borderStyle") - - border_width: str = FieldInfo(alias="borderWidth") - - box_shadow: str = FieldInfo(alias="boxShadow") - """Computed box-shadow (comma-separated layers when present)""" - - color: str - - css: str - """Ready-to-use CSS declaration block for this component style""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - min_height: str = FieldInfo(alias="minHeight") - """Sampled minimum height of the button box (typically px)""" - - min_width: str = FieldInfo(alias="minWidth") - """Sampled minimum width of the button box (typically px)""" - - padding: str - - text_decoration: str = FieldInfo(alias="textDecoration") - - font_fallbacks: Optional[List[str]] = FieldInfo(alias="fontFallbacks", default=None) - """Full ordered font list from computed font-family""" - - font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) - """Primary button typeface (first in fontFallbacks)""" - - text_decoration_color: Optional[str] = FieldInfo(alias="textDecorationColor", default=None) - """Hex color of the underline when it differs from the text color""" - - -class StyleguideComponentsButton(BaseModel): - """Button component styles""" - - link: Optional[StyleguideComponentsButtonLink] = None - - primary: Optional[StyleguideComponentsButtonPrimary] = None - - secondary: Optional[StyleguideComponentsButtonSecondary] = None - - -class StyleguideComponentsCard(BaseModel): - """Card component style""" - - background_color: str = FieldInfo(alias="backgroundColor") - - border_color: str = FieldInfo(alias="borderColor") - """ - Border color as CSS hex (#RRGGBB or #RRGGBBAA when computed border-color has - alpha) - """ - - border_radius: str = FieldInfo(alias="borderRadius") - - border_style: str = FieldInfo(alias="borderStyle") - - border_width: str = FieldInfo(alias="borderWidth") - - box_shadow: str = FieldInfo(alias="boxShadow") - - css: str - """Ready-to-use CSS declaration block for this component style""" - - padding: str - - text_color: str = FieldInfo(alias="textColor") - - -class StyleguideComponents(BaseModel): - """UI component styles""" - - button: StyleguideComponentsButton - """Button component styles""" - - card: Optional[StyleguideComponentsCard] = None - """Card component style""" - - -class StyleguideElementSpacing(BaseModel): - """Spacing system used on the website""" - - lg: str - - md: str - - sm: str - - xl: str - - xs: str - - -class StyleguideShadows(BaseModel): - """Shadow styles used on the website""" - - inner: str - - lg: str - - md: str - - sm: str - - xl: str - - -class StyleguideTypographyHeadingsH1(BaseModel): - font_fallbacks: List[str] = FieldInfo(alias="fontFallbacks") - """Full ordered font list from resolved computed font-family""" - - font_family: str = FieldInfo(alias="fontFamily") - """Primary face (first family in the computed stack)""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - letter_spacing: str = FieldInfo(alias="letterSpacing") - - line_height: str = FieldInfo(alias="lineHeight") - - -class StyleguideTypographyHeadingsH2(BaseModel): - font_fallbacks: List[str] = FieldInfo(alias="fontFallbacks") - """Full ordered font list from resolved computed font-family""" - - font_family: str = FieldInfo(alias="fontFamily") - """Primary face (first family in the computed stack)""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - letter_spacing: str = FieldInfo(alias="letterSpacing") - - line_height: str = FieldInfo(alias="lineHeight") - - -class StyleguideTypographyHeadingsH3(BaseModel): - font_fallbacks: List[str] = FieldInfo(alias="fontFallbacks") - """Full ordered font list from resolved computed font-family""" - - font_family: str = FieldInfo(alias="fontFamily") - """Primary face (first family in the computed stack)""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - letter_spacing: str = FieldInfo(alias="letterSpacing") - - line_height: str = FieldInfo(alias="lineHeight") - - -class StyleguideTypographyHeadingsH4(BaseModel): - font_fallbacks: List[str] = FieldInfo(alias="fontFallbacks") - """Full ordered font list from resolved computed font-family""" - - font_family: str = FieldInfo(alias="fontFamily") - """Primary face (first family in the computed stack)""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - letter_spacing: str = FieldInfo(alias="letterSpacing") - - line_height: str = FieldInfo(alias="lineHeight") - - -class StyleguideTypographyHeadings(BaseModel): - """Heading styles""" - - h1: Optional[StyleguideTypographyHeadingsH1] = None - - h2: Optional[StyleguideTypographyHeadingsH2] = None - - h3: Optional[StyleguideTypographyHeadingsH3] = None - - h4: Optional[StyleguideTypographyHeadingsH4] = None - - -class StyleguideTypographyP(BaseModel): - font_fallbacks: List[str] = FieldInfo(alias="fontFallbacks") - """Full ordered font list from resolved computed font-family""" - - font_family: str = FieldInfo(alias="fontFamily") - """Primary face (first family in the computed stack)""" - - font_size: str = FieldInfo(alias="fontSize") - - font_weight: float = FieldInfo(alias="fontWeight") - - letter_spacing: str = FieldInfo(alias="letterSpacing") - - line_height: str = FieldInfo(alias="lineHeight") - - -class StyleguideTypography(BaseModel): - """Typography styles used on the website""" - - headings: StyleguideTypographyHeadings - """Heading styles""" - - p: Optional[StyleguideTypographyP] = None - - -class Styleguide(BaseModel): - """Comprehensive styleguide data extracted from the website""" - - colors: StyleguideColors - """Primary colors used on the website""" - - components: StyleguideComponents - """UI component styles""" - - element_spacing: StyleguideElementSpacing = FieldInfo(alias="elementSpacing") - """Spacing system used on the website""" - - mode: Literal["light", "dark"] - """The primary color mode of the website design""" - - shadows: StyleguideShadows - """Shadow styles used on the website""" - - typography: StyleguideTypography - """Typography styles used on the website""" - - -class BrandStyleguideResponse(BaseModel): - code: Optional[int] = None - """HTTP status code""" - - domain: Optional[str] = None - """The normalized domain that was processed""" - - status: Optional[str] = None - """Status of the response, e.g., 'ok'""" - - styleguide: Optional[Styleguide] = None - """Comprehensive styleguide data extracted from the website""" diff --git a/src/brand/dev/types/brand_web_scrape_html_params.py b/src/brand/dev/types/brand_web_scrape_html_params.py index 86246f4..57cbf2e 100644 --- a/src/brand/dev/types/brand_web_scrape_html_params.py +++ b/src/brand/dev/types/brand_web_scrape_html_params.py @@ -2,11 +2,69 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Required, Annotated, TypedDict -__all__ = ["BrandWebScrapeHTMLParams"] +from .._utils import PropertyInfo + +__all__ = ["BrandWebScrapeHTMLParams", "Pdf"] class BrandWebScrapeHTMLParams(TypedDict, total=False): url: Required[str] """Full URL to scrape (must include http:// or https:// protocol)""" + + include_frames: Annotated[bool, PropertyInfo(alias="includeFrames")] + """When true, iframes are rendered inline into the returned HTML.""" + + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + """ + + pdf: Pdf + """PDF parsing controls. + + Use start/end to limit text extraction and OCR to an inclusive 1-based page + range. + """ + + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + + wait_for_ms: Annotated[int, PropertyInfo(alias="waitForMs")] + """Optional browser wait time in milliseconds after initial page load. + + Min: 0. Max: 30000 (30 seconds). + """ + + +class Pdf(TypedDict, total=False): + """PDF parsing controls. + + Use start/end to limit text extraction and OCR to an inclusive 1-based page range. + """ + + end: int + """Last 1-based PDF page to parse. + + When omitted, parsing ends at the last page. Must be greater than or equal to + start when both are provided. + """ + + should_parse: Annotated[bool, PropertyInfo(alias="shouldParse")] + """When true, PDF URLs are fetched and parsed. + + When false, PDF URLs are skipped and a 400 WEBSITE_ACCESS_ERROR is returned. + """ + + start: int + """First 1-based PDF page to parse. + + When omitted, parsing starts at the first page. + """ diff --git a/src/brand/dev/types/brand_web_scrape_images_params.py b/src/brand/dev/types/brand_web_scrape_images_params.py index 665c5f0..4ecfebe 100644 --- a/src/brand/dev/types/brand_web_scrape_images_params.py +++ b/src/brand/dev/types/brand_web_scrape_images_params.py @@ -2,11 +2,60 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Required, Annotated, TypedDict -__all__ = ["BrandWebScrapeImagesParams"] +from .._utils import PropertyInfo + +__all__ = ["BrandWebScrapeImagesParams", "Enrichment"] class BrandWebScrapeImagesParams(TypedDict, total=False): url: Required[str] - """Full URL to scrape images from (must include http:// or https:// protocol)""" + """Page URL to inspect. Must include http:// or https://.""" + + enrichment: Enrichment + """ + Optional per-image processing, sent as deep-object query params such as + enrichment[resolution]=true. + """ + + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """Reuse a cached result this many milliseconds old or newer. + + Default: 86400000 (1 day). Set to 0 to bypass cache. Maximum: 2592000000 (30 + days). + """ + + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + + wait_for_ms: Annotated[int, PropertyInfo(alias="waitForMs")] + """ + Optional browser wait time in milliseconds after initial page load before + collecting images. Min: 0. Max: 30000 (30 seconds). + """ + + +class Enrichment(TypedDict, total=False): + """ + Optional per-image processing, sent as deep-object query params such as enrichment[resolution]=true. + """ + + classification: bool + """Classify each image by visual asset type.""" + + hosted_url: Annotated[bool, PropertyInfo(alias="hostedUrl")] + """ + Host materializable images on the Brand.dev CDN and return their URL and MIME + type. + """ + + max_time_per_ms: Annotated[int, PropertyInfo(alias="maxTimePerMs")] + """Per-image enrichment timeout in milliseconds. Default: 30000. Maximum: 60000.""" + + resolution: bool + """Measure image width and height when possible.""" diff --git a/src/brand/dev/types/brand_web_scrape_images_response.py b/src/brand/dev/types/brand_web_scrape_images_response.py index 7f475f8..83294fd 100644 --- a/src/brand/dev/types/brand_web_scrape_images_response.py +++ b/src/brand/dev/types/brand_web_scrape_images_response.py @@ -5,29 +5,53 @@ from .._models import BaseModel -__all__ = ["BrandWebScrapeImagesResponse", "Image"] +__all__ = ["BrandWebScrapeImagesResponse", "Image", "ImageEnrichment"] + + +class ImageEnrichment(BaseModel): + """Requested metadata for images that could be processed.""" + + height: Optional[int] = None + """Image height in pixels, when measured.""" + + mimetype: Optional[str] = None + """Detected MIME type, when hosted.""" + + type: Optional[ + Literal["photography", "illustration", "logo", "wordmark", "icon", "pattern", "graphic", "other"] + ] = None + """Visual asset category, when classified.""" + + url: Optional[str] = None + """Brand.dev CDN URL, when hosted.""" + + width: Optional[int] = None + """Image width in pixels, when measured.""" class Image(BaseModel): alt: Optional[str] = None - """Alt text of the image, or null if not present""" + """Image alt text, or null when unavailable.""" element: Literal["img", "svg", "link", "source", "video", "css", "object", "meta", "background"] - """The HTML element the image was found in""" + """Where the image was found.""" src: str - """The image source - can be a URL, inline HTML (for SVGs), or a base64 data URI""" + """Original image value: URL, inline SVG or HTML, or base64 data URI.""" type: Literal["url", "html", "base64"] - """The type/format of the src value""" + """Format of src.""" + + enrichment: Optional[ImageEnrichment] = None + """Requested metadata for images that could be processed.""" class BrandWebScrapeImagesResponse(BaseModel): images: List[Image] - """Array of scraped images""" + """Images found on the page.""" success: Literal[True] - """Indicates success""" + """Always true on success.""" url: str - """The URL that was scraped""" + """Page URL that was scraped.""" diff --git a/src/brand/dev/types/brand_web_scrape_md_params.py b/src/brand/dev/types/brand_web_scrape_md_params.py index 6bf905e..8d3163b 100644 --- a/src/brand/dev/types/brand_web_scrape_md_params.py +++ b/src/brand/dev/types/brand_web_scrape_md_params.py @@ -6,27 +6,83 @@ from .._utils import PropertyInfo -__all__ = ["BrandWebScrapeMdParams"] +__all__ = ["BrandWebScrapeMdParams", "Pdf"] class BrandWebScrapeMdParams(TypedDict, total=False): url: Required[str] """ - Full URL to scrape and convert to markdown (must include http:// or https:// + Full URL to scrape into LLM usable Markdown (must include http:// or https:// protocol) """ + include_frames: Annotated[bool, PropertyInfo(alias="includeFrames")] + """When true, the contents of iframes are rendered to Markdown.""" + include_images: Annotated[bool, PropertyInfo(alias="includeImages")] """Include image references in Markdown output""" include_links: Annotated[bool, PropertyInfo(alias="includeLinks")] """Preserve hyperlinks in Markdown output""" + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """ + Return a cached result if a prior scrape for the same parameters exists and is + younger than this many milliseconds. Defaults to 1 day (86400000 ms) when + omitted. Max is 30 days (2592000000 ms). Set to 0 to always scrape fresh. + """ + + pdf: Pdf + """PDF parsing controls. + + Use start/end to limit text extraction and OCR to an inclusive 1-based page + range. + """ + shorten_base64_images: Annotated[bool, PropertyInfo(alias="shortenBase64Images")] """Shorten base64-encoded image data in the Markdown output""" + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + use_main_content_only: Annotated[bool, PropertyInfo(alias="useMainContentOnly")] """ Extract only the main content of the page, excluding headers, footers, sidebars, and navigation """ + + wait_for_ms: Annotated[int, PropertyInfo(alias="waitForMs")] + """ + Optional browser wait time in milliseconds after initial page load before + converting the page to Markdown. Min: 0. Max: 30000 (30 seconds). + """ + + +class Pdf(TypedDict, total=False): + """PDF parsing controls. + + Use start/end to limit text extraction and OCR to an inclusive 1-based page range. + """ + + end: int + """Last 1-based PDF page to parse. + + When omitted, parsing ends at the last page. Must be greater than or equal to + start when both are provided. + """ + + should_parse: Annotated[bool, PropertyInfo(alias="shouldParse")] + """When true, PDF URLs are fetched and parsed. + + When false, PDF URLs are skipped and a 400 WEBSITE_ACCESS_ERROR is returned. + """ + + start: int + """First 1-based PDF page to parse. + + When omitted, parsing starts at the first page. + """ diff --git a/src/brand/dev/types/brand_web_scrape_sitemap_params.py b/src/brand/dev/types/brand_web_scrape_sitemap_params.py index e675dae..95b4bfc 100644 --- a/src/brand/dev/types/brand_web_scrape_sitemap_params.py +++ b/src/brand/dev/types/brand_web_scrape_sitemap_params.py @@ -11,13 +11,23 @@ class BrandWebScrapeSitemapParams(TypedDict, total=False): domain: Required[str] - """Domain name to crawl sitemaps for (e.g., 'example.com'). - - The domain will be automatically normalized and validated. - """ + """Domain to build a sitemap for""" max_links: Annotated[int, PropertyInfo(alias="maxLinks")] """Maximum number of links to return from the sitemap crawl. Defaults to 10,000. Minimum is 1, maximum is 100,000. """ + + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + + url_regex: Annotated[str, PropertyInfo(alias="urlRegex")] + """Optional RE2-compatible regex pattern. + + Only URLs matching this pattern are returned and counted against maxLinks. + """ diff --git a/tests/api_resources/test_brand.py b/tests/api_resources/test_brand.py index 5587585..4073a06 100644 --- a/tests/api_resources/test_brand.py +++ b/tests/api_resources/test_brand.py @@ -10,16 +10,12 @@ from brand.dev import BrandDev, AsyncBrandDev from tests.utils import assert_matches_type from brand.dev.types import ( - BrandFontsResponse, BrandAIQueryResponse, BrandPrefetchResponse, BrandRetrieveResponse, BrandAIProductResponse, BrandAIProductsResponse, - BrandScreenshotResponse, - BrandStyleguideResponse, BrandWebScrapeMdResponse, - BrandRetrieveNaicsResponse, BrandWebScrapeHTMLResponse, BrandRetrieveByIsinResponse, BrandRetrieveByNameResponse, @@ -51,7 +47,8 @@ def test_method_retrieve(self, client: BrandDev) -> None: def test_method_retrieve_with_all_params(self, client: BrandDev) -> None: brand = client.brand.retrieve( domain="domain", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -96,6 +93,7 @@ def test_method_ai_product(self, client: BrandDev) -> None: def test_method_ai_product_with_all_params(self, client: BrandDev) -> None: brand = client.brand.ai_product( url="https://example.com", + max_age_ms=0, timeout_ms=1000, ) assert_matches_type(BrandAIProductResponse, brand, path=["response"]) @@ -139,6 +137,7 @@ def test_method_ai_products_overload_1(self, client: BrandDev) -> None: def test_method_ai_products_with_all_params_overload_1(self, client: BrandDev) -> None: brand = client.brand.ai_products( domain="domain", + max_age_ms=0, max_products=1, timeout_ms=1000, ) @@ -183,6 +182,7 @@ def test_method_ai_products_overload_2(self, client: BrandDev) -> None: def test_method_ai_products_with_all_params_overload_2(self, client: BrandDev) -> None: brand = client.brand.ai_products( direct_url="https://example.com", + max_age_ms=0, max_products=1, timeout_ms=1000, ) @@ -305,49 +305,6 @@ def test_streaming_response_ai_query(self, client: BrandDev) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_fonts(self, client: BrandDev) -> None: - brand = client.brand.fonts( - domain="domain", - ) - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_fonts_with_all_params(self, client: BrandDev) -> None: - brand = client.brand.fonts( - domain="domain", - timeout_ms=1000, - ) - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_fonts(self, client: BrandDev) -> None: - response = client.brand.with_raw_response.fonts( - domain="domain", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = response.parse() - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_fonts(self, client: BrandDev) -> None: - with client.brand.with_streaming_response.fonts( - domain="domain", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = response.parse() - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_identify_from_transaction(self, client: BrandDev) -> None: @@ -363,7 +320,7 @@ def test_method_identify_from_transaction_with_all_params(self, client: BrandDev transaction_info="transaction_info", city="city", country_gl="ad", - force_language="albanian", + force_language="afrikaans", high_confidence_only=True, max_speed=True, mcc="mcc", @@ -497,7 +454,8 @@ def test_method_retrieve_by_email(self, client: BrandDev) -> None: def test_method_retrieve_by_email_with_all_params(self, client: BrandDev) -> None: brand = client.brand.retrieve_by_email( email="dev@stainless.com", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -542,7 +500,8 @@ def test_method_retrieve_by_isin(self, client: BrandDev) -> None: def test_method_retrieve_by_isin_with_all_params(self, client: BrandDev) -> None: brand = client.brand.retrieve_by_isin( isin="SE60513A9993", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -588,7 +547,8 @@ def test_method_retrieve_by_name_with_all_params(self, client: BrandDev) -> None brand = client.brand.retrieve_by_name( name="xxx", country_gl="ad", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -633,7 +593,8 @@ def test_method_retrieve_by_ticker(self, client: BrandDev) -> None: def test_method_retrieve_by_ticker_with_all_params(self, client: BrandDev) -> None: brand = client.brand.retrieve_by_ticker( ticker="ticker", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, ticker_exchange="AMEX", timeout_ms=1000, @@ -666,51 +627,6 @@ def test_streaming_response_retrieve_by_ticker(self, client: BrandDev) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve_naics(self, client: BrandDev) -> None: - brand = client.brand.retrieve_naics( - input="input", - ) - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_retrieve_naics_with_all_params(self, client: BrandDev) -> None: - brand = client.brand.retrieve_naics( - input="input", - max_results=1, - min_results=1, - timeout_ms=1000, - ) - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_retrieve_naics(self, client: BrandDev) -> None: - response = client.brand.with_raw_response.retrieve_naics( - input="input", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = response.parse() - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_retrieve_naics(self, client: BrandDev) -> None: - with client.brand.with_streaming_response.retrieve_naics( - input="input", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = response.parse() - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_simplified(self, client: BrandDev) -> None: @@ -724,6 +640,7 @@ def test_method_retrieve_simplified(self, client: BrandDev) -> None: def test_method_retrieve_simplified_with_all_params(self, client: BrandDev) -> None: brand = client.brand.retrieve_simplified( domain="domain", + max_age_ms=86400000, timeout_ms=1000, ) assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) @@ -756,92 +673,26 @@ def test_streaming_response_retrieve_simplified(self, client: BrandDev) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_screenshot(self, client: BrandDev) -> None: - brand = client.brand.screenshot( - domain="domain", - ) - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_screenshot_with_all_params(self, client: BrandDev) -> None: - brand = client.brand.screenshot( - domain="domain", - full_screenshot="true", - page="login", - prioritize="speed", - ) - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_screenshot(self, client: BrandDev) -> None: - response = client.brand.with_raw_response.screenshot( - domain="domain", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = response.parse() - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_screenshot(self, client: BrandDev) -> None: - with client.brand.with_streaming_response.screenshot( - domain="domain", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = response.parse() - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_styleguide(self, client: BrandDev) -> None: - brand = client.brand.styleguide() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_styleguide_with_all_params(self, client: BrandDev) -> None: - brand = client.brand.styleguide( - direct_url="https://example.com", - domain="domain", - timeout_ms=1000, + def test_method_web_scrape_html(self, client: BrandDev) -> None: + brand = client.brand.web_scrape_html( + url="https://example.com", ) - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_styleguide(self, client: BrandDev) -> None: - response = client.brand.with_raw_response.styleguide() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = response.parse() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_styleguide(self, client: BrandDev) -> None: - with client.brand.with_streaming_response.styleguide() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = response.parse() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True + assert_matches_type(BrandWebScrapeHTMLResponse, brand, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_web_scrape_html(self, client: BrandDev) -> None: + def test_method_web_scrape_html_with_all_params(self, client: BrandDev) -> None: brand = client.brand.web_scrape_html( url="https://example.com", + include_frames=True, + max_age_ms=0, + pdf={ + "end": 1, + "should_parse": True, + "start": 1, + }, + timeout_ms=1000, + wait_for_ms=0, ) assert_matches_type(BrandWebScrapeHTMLResponse, brand, path=["response"]) @@ -879,6 +730,23 @@ def test_method_web_scrape_images(self, client: BrandDev) -> None: ) assert_matches_type(BrandWebScrapeImagesResponse, brand, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_web_scrape_images_with_all_params(self, client: BrandDev) -> None: + brand = client.brand.web_scrape_images( + url="https://example.com", + enrichment={ + "classification": True, + "hosted_url": True, + "max_time_per_ms": 1, + "resolution": True, + }, + max_age_ms=0, + timeout_ms=1000, + wait_for_ms=0, + ) + assert_matches_type(BrandWebScrapeImagesResponse, brand, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_web_scrape_images(self, client: BrandDev) -> None: @@ -918,10 +786,19 @@ def test_method_web_scrape_md(self, client: BrandDev) -> None: def test_method_web_scrape_md_with_all_params(self, client: BrandDev) -> None: brand = client.brand.web_scrape_md( url="https://example.com", + include_frames=True, include_images=True, include_links=True, + max_age_ms=0, + pdf={ + "end": 1, + "should_parse": True, + "start": 1, + }, shorten_base64_images=True, + timeout_ms=1000, use_main_content_only=True, + wait_for_ms=0, ) assert_matches_type(BrandWebScrapeMdResponse, brand, path=["response"]) @@ -965,6 +842,8 @@ def test_method_web_scrape_sitemap_with_all_params(self, client: BrandDev) -> No brand = client.brand.web_scrape_sitemap( domain="domain", max_links=1, + timeout_ms=1000, + url_regex="^https?://[^/]+/blog/", ) assert_matches_type(BrandWebScrapeSitemapResponse, brand, path=["response"]) @@ -1013,7 +892,8 @@ async def test_method_retrieve(self, async_client: AsyncBrandDev) -> None: async def test_method_retrieve_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.retrieve( domain="domain", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -1058,6 +938,7 @@ async def test_method_ai_product(self, async_client: AsyncBrandDev) -> None: async def test_method_ai_product_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.ai_product( url="https://example.com", + max_age_ms=0, timeout_ms=1000, ) assert_matches_type(BrandAIProductResponse, brand, path=["response"]) @@ -1101,6 +982,7 @@ async def test_method_ai_products_overload_1(self, async_client: AsyncBrandDev) async def test_method_ai_products_with_all_params_overload_1(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.ai_products( domain="domain", + max_age_ms=0, max_products=1, timeout_ms=1000, ) @@ -1145,6 +1027,7 @@ async def test_method_ai_products_overload_2(self, async_client: AsyncBrandDev) async def test_method_ai_products_with_all_params_overload_2(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.ai_products( direct_url="https://example.com", + max_age_ms=0, max_products=1, timeout_ms=1000, ) @@ -1267,49 +1150,6 @@ async def test_streaming_response_ai_query(self, async_client: AsyncBrandDev) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_fonts(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.fonts( - domain="domain", - ) - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_fonts_with_all_params(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.fonts( - domain="domain", - timeout_ms=1000, - ) - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_fonts(self, async_client: AsyncBrandDev) -> None: - response = await async_client.brand.with_raw_response.fonts( - domain="domain", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = await response.parse() - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_fonts(self, async_client: AsyncBrandDev) -> None: - async with async_client.brand.with_streaming_response.fonts( - domain="domain", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = await response.parse() - assert_matches_type(BrandFontsResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_identify_from_transaction(self, async_client: AsyncBrandDev) -> None: @@ -1325,7 +1165,7 @@ async def test_method_identify_from_transaction_with_all_params(self, async_clie transaction_info="transaction_info", city="city", country_gl="ad", - force_language="albanian", + force_language="afrikaans", high_confidence_only=True, max_speed=True, mcc="mcc", @@ -1459,7 +1299,8 @@ async def test_method_retrieve_by_email(self, async_client: AsyncBrandDev) -> No async def test_method_retrieve_by_email_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.retrieve_by_email( email="dev@stainless.com", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -1504,7 +1345,8 @@ async def test_method_retrieve_by_isin(self, async_client: AsyncBrandDev) -> Non async def test_method_retrieve_by_isin_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.retrieve_by_isin( isin="SE60513A9993", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -1550,7 +1392,8 @@ async def test_method_retrieve_by_name_with_all_params(self, async_client: Async brand = await async_client.brand.retrieve_by_name( name="xxx", country_gl="ad", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, timeout_ms=1000, ) @@ -1595,7 +1438,8 @@ async def test_method_retrieve_by_ticker(self, async_client: AsyncBrandDev) -> N async def test_method_retrieve_by_ticker_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.retrieve_by_ticker( ticker="ticker", - force_language="albanian", + force_language="afrikaans", + max_age_ms=86400000, max_speed=True, ticker_exchange="AMEX", timeout_ms=1000, @@ -1628,51 +1472,6 @@ async def test_streaming_response_retrieve_by_ticker(self, async_client: AsyncBr assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve_naics(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.retrieve_naics( - input="input", - ) - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_retrieve_naics_with_all_params(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.retrieve_naics( - input="input", - max_results=1, - min_results=1, - timeout_ms=1000, - ) - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_retrieve_naics(self, async_client: AsyncBrandDev) -> None: - response = await async_client.brand.with_raw_response.retrieve_naics( - input="input", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = await response.parse() - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_retrieve_naics(self, async_client: AsyncBrandDev) -> None: - async with async_client.brand.with_streaming_response.retrieve_naics( - input="input", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = await response.parse() - assert_matches_type(BrandRetrieveNaicsResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_simplified(self, async_client: AsyncBrandDev) -> None: @@ -1686,6 +1485,7 @@ async def test_method_retrieve_simplified(self, async_client: AsyncBrandDev) -> async def test_method_retrieve_simplified_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.retrieve_simplified( domain="domain", + max_age_ms=86400000, timeout_ms=1000, ) assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) @@ -1718,92 +1518,26 @@ async def test_streaming_response_retrieve_simplified(self, async_client: AsyncB @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_screenshot(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.screenshot( - domain="domain", - ) - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_screenshot_with_all_params(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.screenshot( - domain="domain", - full_screenshot="true", - page="login", - prioritize="speed", - ) - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_screenshot(self, async_client: AsyncBrandDev) -> None: - response = await async_client.brand.with_raw_response.screenshot( - domain="domain", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = await response.parse() - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_screenshot(self, async_client: AsyncBrandDev) -> None: - async with async_client.brand.with_streaming_response.screenshot( - domain="domain", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = await response.parse() - assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_styleguide(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.styleguide() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_styleguide_with_all_params(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.styleguide( - direct_url="https://example.com", - domain="domain", - timeout_ms=1000, + async def test_method_web_scrape_html(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.web_scrape_html( + url="https://example.com", ) - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_styleguide(self, async_client: AsyncBrandDev) -> None: - response = await async_client.brand.with_raw_response.styleguide() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - brand = await response.parse() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_styleguide(self, async_client: AsyncBrandDev) -> None: - async with async_client.brand.with_streaming_response.styleguide() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - brand = await response.parse() - assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) - - assert cast(Any, response.is_closed) is True + assert_matches_type(BrandWebScrapeHTMLResponse, brand, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_web_scrape_html(self, async_client: AsyncBrandDev) -> None: + async def test_method_web_scrape_html_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.web_scrape_html( url="https://example.com", + include_frames=True, + max_age_ms=0, + pdf={ + "end": 1, + "should_parse": True, + "start": 1, + }, + timeout_ms=1000, + wait_for_ms=0, ) assert_matches_type(BrandWebScrapeHTMLResponse, brand, path=["response"]) @@ -1841,6 +1575,23 @@ async def test_method_web_scrape_images(self, async_client: AsyncBrandDev) -> No ) assert_matches_type(BrandWebScrapeImagesResponse, brand, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_web_scrape_images_with_all_params(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.web_scrape_images( + url="https://example.com", + enrichment={ + "classification": True, + "hosted_url": True, + "max_time_per_ms": 1, + "resolution": True, + }, + max_age_ms=0, + timeout_ms=1000, + wait_for_ms=0, + ) + assert_matches_type(BrandWebScrapeImagesResponse, brand, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_web_scrape_images(self, async_client: AsyncBrandDev) -> None: @@ -1880,10 +1631,19 @@ async def test_method_web_scrape_md(self, async_client: AsyncBrandDev) -> None: async def test_method_web_scrape_md_with_all_params(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.web_scrape_md( url="https://example.com", + include_frames=True, include_images=True, include_links=True, + max_age_ms=0, + pdf={ + "end": 1, + "should_parse": True, + "start": 1, + }, shorten_base64_images=True, + timeout_ms=1000, use_main_content_only=True, + wait_for_ms=0, ) assert_matches_type(BrandWebScrapeMdResponse, brand, path=["response"]) @@ -1927,6 +1687,8 @@ async def test_method_web_scrape_sitemap_with_all_params(self, async_client: Asy brand = await async_client.brand.web_scrape_sitemap( domain="domain", max_links=1, + timeout_ms=1000, + url_regex="^https?://[^/]+/blog/", ) assert_matches_type(BrandWebScrapeSitemapResponse, brand, path=["response"]) diff --git a/tests/test_client.py b/tests/test_client.py index 9a1bb3d..5622393 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -429,6 +429,30 @@ def test_default_query_option(self) -> None: client.close() + def test_hardcoded_query_params_in_url(self, client: BrandDev) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: BrandDev) -> None: request = client._build_request( FinalRequestOptions( @@ -1324,6 +1348,30 @@ async def test_default_query_option(self) -> None: await client.close() + async def test_hardcoded_query_params_in_url(self, async_client: AsyncBrandDev) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: BrandDev) -> None: request = client._build_request( FinalRequestOptions( diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index 937bf72..0000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from brand.dev._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 72d0a8b..ba150ec 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from brand.dev._types import FileTypes +from brand.dev._types import FileTypes, ArrayFormat from brand.dev._utils import extract_files @@ -35,6 +35,12 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ @@ -62,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index 9af98b8..62632e1 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from brand.dev._files import to_httpx_files, async_to_httpx_files +from brand.dev._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from brand.dev._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert [entry for _, entry in extracted] == [file1, file2] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + } diff --git a/tests/test_models.py b/tests/test_models.py index 66e3565..8e9ab3a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,8 @@ import json -from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated, TypeAliasType +from collections import deque +from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType import pytest import pydantic @@ -9,7 +10,7 @@ from brand.dev._utils import PropertyInfo from brand.dev._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from brand.dev._models import DISCRIMINATOR_CACHE, BaseModel, construct_type +from brand.dev._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type class BasicModel(BaseModel): @@ -961,3 +962,56 @@ def __getattr__(self, attr: str) -> Item: ... assert model.a.prop == 1 assert isinstance(model.a, Item) assert model.other == "foo" + + +# NOTE: Workaround for Pydantic Iterable behavior. +# Iterable fields are replaced with a ValidatorIterator and may be consumed +# during serialization, which can cause subsequent dumps to return empty data. +# See: https://github.com/pydantic/pydantic/issues/9541 +@pytest.mark.parametrize( + "data, expected_validated", + [ + ([1, 2, 3], [1, 2, 3]), + ((1, 2, 3), (1, 2, 3)), + (set([1, 2, 3]), set([1, 2, 3])), + (iter([1, 2, 3]), [1, 2, 3]), + ([], []), + ((x for x in [1, 2, 3]), [1, 2, 3]), + (map(lambda x: x, [1, 2, 3]), [1, 2, 3]), + (frozenset([1, 2, 3]), frozenset([1, 2, 3])), + (deque([1, 2, 3]), deque([1, 2, 3])), + ], + ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"], +) +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None: + class TypeWithIterable(TypedDict): + items: EagerIterable[int] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": data}}) + assert m.data["items"] == expected_validated + + # Verify repeated dumps don't lose data (the original bug) + assert m.model_dump()["data"]["items"] == list(expected_validated) + assert m.model_dump()["data"]["items"] == list(expected_validated) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction_str_falls_back_to_list() -> None: + # str is iterable (over chars), but str(list_of_chars) produces the list's repr + # rather than reconstructing a string from items. We special-case str to fall + # back to list instead of attempting reconstruction. + class TypeWithIterable(TypedDict): + items: EagerIterable[str] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": "hello"}}) + + # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"]) + assert m.data["items"] == ["h", "e", "l", "l", "o"] + assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]