diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 35c30ad..81d2de2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.30.0" + ".": "1.31.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index ef7f019..3c2db8b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-73562e26b663cf10185b9e98966accf5f151c6d3cf99b5e060ce5a847045e383.yml -openapi_spec_hash: bf5994966b84f9dda998ad5059ff8318 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-10f7ae53f4fe4f2394c22788b648d9db742a178ed41a87beb39de741660e646b.yml +openapi_spec_hash: 9885c47a02677471a38f16dddbad1823 config_hash: 6f10592c7d0c3bafefc1271472283217 diff --git a/CHANGELOG.md b/CHANGELOG.md index 50327f7..4fd5141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.31.0 (2026-02-07) + +Full Changelog: [v1.30.0...v1.31.0](https://github.com/brand-dot-dev/python-sdk/compare/v1.30.0...v1.31.0) + +### Features + +* **api:** api update ([09fa73a](https://github.com/brand-dot-dev/python-sdk/commit/09fa73a99432c4a131bba9d924d38f12a9537e51)) + ## 1.30.0 (2026-02-07) Full Changelog: [v1.29.0...v1.30.0](https://github.com/brand-dot-dev/python-sdk/compare/v1.29.0...v1.30.0) diff --git a/pyproject.toml b/pyproject.toml index 8a91b63..5c7bf0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "brand.dev" -version = "1.30.0" +version = "1.31.0" description = "The official Python library for the brand.dev API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/brand/dev/_version.py b/src/brand/dev/_version.py index 6b37f6b..a46a396 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.30.0" # x-release-please-version +__version__ = "1.31.0" # x-release-please-version diff --git a/src/brand/dev/resources/brand.py b/src/brand/dev/resources/brand.py index 49cadee..053ae2a 100644 --- a/src/brand/dev/resources/brand.py +++ b/src/brand/dev/resources/brand.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Iterable -from typing_extensions import Literal +from typing_extensions import Literal, overload import httpx @@ -25,7 +25,7 @@ brand_identify_from_transaction_params, ) from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import required_args, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -189,11 +189,11 @@ def retrieve( cast_to=BrandRetrieveResponse, ) + @overload def ai_products( self, *, - direct_url: str | Omit = omit, - domain: str | Omit = omit, + domain: str, 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. @@ -210,19 +210,51 @@ def ai_products( description, image, pricing, features, and more. Args: - direct_url: A specific URL to use directly as the starting point for extraction without - domain resolution. Useful when you want to extract products from a specific page - rather than discovering the site's product pages automatically. Either 'domain' - or 'directUrl' must be provided, but not both. + domain: The domain name to analyze. + + max_products: Maximum number of products to extract. + + timeout_ms: Optional timeout in milliseconds for the request. 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 + """ + ... + + @overload + def ai_products( + self, + *, + direct_url: str, + 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. + # 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, + ) -> BrandAIProductsResponse: + """Beta feature: Extract product information from a brand's website. - domain: The domain name to analyze. Either 'domain' or 'directUrl' must be provided, but - not both. + Brand.dev 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_products: Maximum number of products to extract. - 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). + timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is + 300000ms (5 minutes). extra_headers: Send extra headers @@ -232,14 +264,31 @@ def ai_products( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["domain"], ["direct_url"]) + def ai_products( + self, + *, + domain: str | Omit = omit, + max_products: int | Omit = omit, + timeout_ms: int | Omit = omit, + direct_url: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BrandAIProductsResponse: return self._post( "/brand/ai/products", body=maybe_transform( { - "direct_url": direct_url, "domain": domain, "max_products": max_products, "timeout_ms": timeout_ms, + "direct_url": direct_url, }, brand_ai_products_params.BrandAIProductsParams, ), @@ -1736,11 +1785,11 @@ async def retrieve( cast_to=BrandRetrieveResponse, ) + @overload async def ai_products( self, *, - direct_url: str | Omit = omit, - domain: str | Omit = omit, + domain: str, 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. @@ -1757,19 +1806,51 @@ async def ai_products( description, image, pricing, features, and more. Args: - direct_url: A specific URL to use directly as the starting point for extraction without - domain resolution. Useful when you want to extract products from a specific page - rather than discovering the site's product pages automatically. Either 'domain' - or 'directUrl' must be provided, but not both. + domain: The domain name to analyze. + + max_products: Maximum number of products to extract. + + timeout_ms: Optional timeout in milliseconds for the request. 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 + """ + ... + + @overload + async def ai_products( + self, + *, + direct_url: str, + 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. + # 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, + ) -> BrandAIProductsResponse: + """Beta feature: Extract product information from a brand's website. - domain: The domain name to analyze. Either 'domain' or 'directUrl' must be provided, but - not both. + Brand.dev 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_products: Maximum number of products to extract. - 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). + timeout_ms: Optional timeout in milliseconds for the request. Maximum allowed value is + 300000ms (5 minutes). extra_headers: Send extra headers @@ -1779,14 +1860,31 @@ async def ai_products( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["domain"], ["direct_url"]) + async def ai_products( + self, + *, + domain: str | Omit = omit, + max_products: int | Omit = omit, + timeout_ms: int | Omit = omit, + direct_url: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BrandAIProductsResponse: return await self._post( "/brand/ai/products", body=await async_maybe_transform( { - "direct_url": direct_url, "domain": domain, "max_products": max_products, "timeout_ms": timeout_ms, + "direct_url": direct_url, }, brand_ai_products_params.BrandAIProductsParams, ), diff --git a/src/brand/dev/types/brand_ai_products_params.py b/src/brand/dev/types/brand_ai_products_params.py index 783daa9..9a61efe 100644 --- a/src/brand/dev/types/brand_ai_products_params.py +++ b/src/brand/dev/types/brand_ai_products_params.py @@ -2,26 +2,33 @@ from __future__ import annotations -from typing_extensions import Annotated, TypedDict +from typing import Union +from typing_extensions import Required, Annotated, TypeAlias, TypedDict from .._utils import PropertyInfo -__all__ = ["BrandAIProductsParams"] +__all__ = ["BrandAIProductsParams", "ByDomain", "ByDirectURL"] -class BrandAIProductsParams(TypedDict, total=False): - direct_url: Annotated[str, PropertyInfo(alias="directUrl")] - """ - A specific URL to use directly as the starting point for extraction without - domain resolution. Useful when you want to extract products from a specific page - rather than discovering the site's product pages automatically. Either 'domain' - or 'directUrl' must be provided, but not both. +class ByDomain(TypedDict, total=False): + domain: Required[str] + """The domain name to analyze.""" + + 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). """ - domain: str - """The domain name to analyze. - Either 'domain' or 'directUrl' must be provided, but not both. +class ByDirectURL(TypedDict, total=False): + direct_url: Required[Annotated[str, PropertyInfo(alias="directUrl")]] + """ + A specific URL to use directly as the starting point for extraction without + domain resolution. """ max_products: Annotated[int, PropertyInfo(alias="maxProducts")] @@ -30,6 +37,8 @@ class BrandAIProductsParams(TypedDict, total=False): 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). + Maximum allowed value is 300000ms (5 minutes). """ + + +BrandAIProductsParams: TypeAlias = Union[ByDomain, ByDirectURL] diff --git a/tests/api_resources/test_brand.py b/tests/api_resources/test_brand.py index b01bbf2..478c685 100644 --- a/tests/api_resources/test_brand.py +++ b/tests/api_resources/test_brand.py @@ -80,15 +80,16 @@ def test_streaming_response_retrieve(self, client: BrandDev) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_ai_products(self, client: BrandDev) -> None: - brand = client.brand.ai_products() + def test_method_ai_products_overload_1(self, client: BrandDev) -> None: + brand = client.brand.ai_products( + domain="domain", + ) assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_ai_products_with_all_params(self, client: BrandDev) -> None: + def test_method_ai_products_with_all_params_overload_1(self, client: BrandDev) -> None: brand = client.brand.ai_products( - direct_url="https://example.com", domain="domain", max_products=1, timeout_ms=1, @@ -97,8 +98,54 @@ def test_method_ai_products_with_all_params(self, client: BrandDev) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_raw_response_ai_products(self, client: BrandDev) -> None: - response = client.brand.with_raw_response.ai_products() + def test_raw_response_ai_products_overload_1(self, client: BrandDev) -> None: + response = client.brand.with_raw_response.ai_products( + domain="domain", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + brand = response.parse() + assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_ai_products_overload_1(self, client: BrandDev) -> None: + with client.brand.with_streaming_response.ai_products( + 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(BrandAIProductsResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_ai_products_overload_2(self, client: BrandDev) -> None: + brand = client.brand.ai_products( + direct_url="https://example.com", + ) + assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + 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_products=1, + timeout_ms=1, + ) + assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_ai_products_overload_2(self, client: BrandDev) -> None: + response = client.brand.with_raw_response.ai_products( + direct_url="https://example.com", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,8 +154,10 @@ def test_raw_response_ai_products(self, client: BrandDev) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_streaming_response_ai_products(self, client: BrandDev) -> None: - with client.brand.with_streaming_response.ai_products() as response: + def test_streaming_response_ai_products_overload_2(self, client: BrandDev) -> None: + with client.brand.with_streaming_response.ai_products( + direct_url="https://example.com", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -797,15 +846,16 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrandDev) -> @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_ai_products(self, async_client: AsyncBrandDev) -> None: - brand = await async_client.brand.ai_products() + async def test_method_ai_products_overload_1(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.ai_products( + domain="domain", + ) assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_ai_products_with_all_params(self, async_client: AsyncBrandDev) -> None: + async def test_method_ai_products_with_all_params_overload_1(self, async_client: AsyncBrandDev) -> None: brand = await async_client.brand.ai_products( - direct_url="https://example.com", domain="domain", max_products=1, timeout_ms=1, @@ -814,8 +864,54 @@ async def test_method_ai_products_with_all_params(self, async_client: AsyncBrand @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_raw_response_ai_products(self, async_client: AsyncBrandDev) -> None: - response = await async_client.brand.with_raw_response.ai_products() + async def test_raw_response_ai_products_overload_1(self, async_client: AsyncBrandDev) -> None: + response = await async_client.brand.with_raw_response.ai_products( + 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(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_ai_products_overload_1(self, async_client: AsyncBrandDev) -> None: + async with async_client.brand.with_streaming_response.ai_products( + 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(BrandAIProductsResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_ai_products_overload_2(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.ai_products( + direct_url="https://example.com", + ) + assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + 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_products=1, + timeout_ms=1, + ) + assert_matches_type(BrandAIProductsResponse, brand, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_ai_products_overload_2(self, async_client: AsyncBrandDev) -> None: + response = await async_client.brand.with_raw_response.ai_products( + direct_url="https://example.com", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -824,8 +920,10 @@ async def test_raw_response_ai_products(self, async_client: AsyncBrandDev) -> No @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_streaming_response_ai_products(self, async_client: AsyncBrandDev) -> None: - async with async_client.brand.with_streaming_response.ai_products() as response: + async def test_streaming_response_ai_products_overload_2(self, async_client: AsyncBrandDev) -> None: + async with async_client.brand.with_streaming_response.ai_products( + direct_url="https://example.com", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python"