diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5f2211..5b251b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fbd9082..7deae33 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.0" + ".": "1.6.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index fc24298..f0d7da7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-a960d67a89f2e62fcb3fb61f13e0cba71a803ff00b378730cf72a8209ae8e36a.yml -openapi_spec_hash: a2c7aa9e4b1e5265d502d3f005ffb5f9 -config_hash: bb3f3ba0dca413263e40968648f9a1a6 +configured_endpoints: 10 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-3f6d4c0f819a0d3128951d315ad216df22050fbc47c5f601d261d56f8b1d80a5.yml +openapi_spec_hash: 8e7953259a1b6bd7440a780eccad1742 +config_hash: 8f3ee44d690a305369555016a77ed016 diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f62dd..a80ea98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## 1.6.0 (2025-06-19) + +Full Changelog: [v1.5.0...v1.6.0](https://github.com/brand-dot-dev/python-sdk/compare/v1.5.0...v1.6.0) + +### Features + +* **api:** manual updates ([315c5dc](https://github.com/brand-dot-dev/python-sdk/commit/315c5dcb1aa4982bf5e90b3fbe1223c969234a64)) +* **api:** manual updates ([adc4bd6](https://github.com/brand-dot-dev/python-sdk/commit/adc4bd6260c7bc6471fa060b66c1f3e8f368bf07)) + + +### Bug Fixes + +* **client:** correctly parse binary response | stream ([26be0c4](https://github.com/brand-dot-dev/python-sdk/commit/26be0c417636a9be6f5b52fd079038b3eeb11484)) +* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([b2bff71](https://github.com/brand-dot-dev/python-sdk/commit/b2bff712704ec48751c0b2ea4f51960913811a98)) + + +### Chores + +* **ci:** enable for pull requests ([d908899](https://github.com/brand-dot-dev/python-sdk/commit/d9088990a6b2e760d1a425117848697882a2b5bd)) +* **internal:** update conftest.py ([fd16274](https://github.com/brand-dot-dev/python-sdk/commit/fd16274f9eb87fac48f5d746e628f135f98ff052)) +* **readme:** update badges ([727b0fa](https://github.com/brand-dot-dev/python-sdk/commit/727b0fa230d8a40abbd1e36e592ba3ba05b6f8eb)) +* **tests:** add tests for httpx client instantiation & proxies ([bb70721](https://github.com/brand-dot-dev/python-sdk/commit/bb70721dd5ac47752cc749ac9269eb4e7d1bb6d4)) +* **tests:** run tests in parallel ([b12584c](https://github.com/brand-dot-dev/python-sdk/commit/b12584c87749c3a9e06653418a9e3365f80cfee0)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([05629cf](https://github.com/brand-dot-dev/python-sdk/commit/05629cf3a6cef8bf5b65aa4df60528e313883a7b)) + ## 1.5.0 (2025-06-08) Full Changelog: [v1.4.0...v1.5.0](https://github.com/brand-dot-dev/python-sdk/compare/v1.4.0...v1.5.0) diff --git a/README.md b/README.md index 81f4734..40020f4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Brand Dev Python API library -[![PyPI version](https://img.shields.io/pypi/v/brand.dev.svg)](https://pypi.org/project/brand.dev/) +[![PyPI version]()](https://pypi.org/project/brand.dev/) The Brand Dev Python library provides convenient access to the Brand Dev REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, @@ -170,7 +170,7 @@ client.with_options(max_retries=5).brand.retrieve( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from brand.dev import BrandDev diff --git a/api.md b/api.md index 0792451..da93d3d 100644 --- a/api.md +++ b/api.md @@ -10,7 +10,10 @@ from brand.dev.types import ( BrandPrefetchResponse, BrandRetrieveByTickerResponse, BrandRetrieveNaicsResponse, + BrandRetrieveSimplifiedResponse, + BrandScreenshotResponse, BrandSearchResponse, + BrandStyleguideResponse, ) ``` @@ -22,4 +25,7 @@ Methods: - client.brand.prefetch(\*\*params) -> BrandPrefetchResponse - 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.search(\*\*params) -> BrandSearchResponse +- client.brand.styleguide(\*\*params) -> BrandStyleguideResponse diff --git a/pyproject.toml b/pyproject.toml index 84c87d5..5c8307d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "brand.dev" -version = "1.5.0" +version = "1.6.0" description = "The official Python library for the brand.dev API" dynamic = ["readme"] license = "Apache-2.0" @@ -54,6 +54,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +126,7 @@ replacement = '[\1](https://github.com/brand-dot-dev/python-sdk/tree/main/\g<2>) [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index f169f3b..8c972f9 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -30,6 +30,8 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv h11==0.14.0 @@ -72,7 +74,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 diff --git a/src/brand/dev/_base_client.py b/src/brand/dev/_base_client.py index 35eb16c..359751d 100644 --- a/src/brand/dev/_base_client.py +++ b/src/brand/dev/_base_client.py @@ -1071,7 +1071,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1574,7 +1581,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") diff --git a/src/brand/dev/_version.py b/src/brand/dev/_version.py index 929055c..44f0df3 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.5.0" # x-release-please-version +__version__ = "1.6.0" # x-release-please-version diff --git a/src/brand/dev/resources/brand.py b/src/brand/dev/resources/brand.py index b3fd81a..542b0ff 100644 --- a/src/brand/dev/resources/brand.py +++ b/src/brand/dev/resources/brand.py @@ -12,8 +12,11 @@ brand_ai_query_params, brand_prefetch_params, brand_retrieve_params, + brand_screenshot_params, + brand_styleguide_params, brand_retrieve_naics_params, brand_retrieve_by_ticker_params, + brand_retrieve_simplified_params, brand_identify_from_transaction_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven @@ -31,8 +34,11 @@ 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_screenshot_response import BrandScreenshotResponse +from ..types.brand_styleguide_response import BrandStyleguideResponse from ..types.brand_retrieve_naics_response import BrandRetrieveNaicsResponse from ..types.brand_retrieve_by_ticker_response import BrandRetrieveByTickerResponse +from ..types.brand_retrieve_simplified_response import BrandRetrieveSimplifiedResponse from ..types.brand_identify_from_transaction_response import BrandIdentifyFromTransactionResponse __all__ = ["BrandResource", "AsyncBrandResource"] @@ -422,6 +428,108 @@ def retrieve_naics( cast_to=BrandRetrieveNaicsResponse, ) + def retrieve_simplified( + self, + *, + domain: str, + timeout_ms: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> 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. + + Args: + domain: Domain name to retrieve simplified brand data for + + 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/retrieve-simplified", + 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_retrieve_simplified_params.BrandRetrieveSimplifiedParams, + ), + ), + cast_to=BrandRetrieveSimplifiedResponse, + ) + + def screenshot( + self, + *, + domain: str, + full_screenshot: Literal["true", "false"] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BrandScreenshotResponse: + """Beta feature: Capture a screenshot of a website. + + Supports both viewport + (standard browser view) and full-page screenshots. 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). + + 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, + }, + brand_screenshot_params.BrandScreenshotParams, + ), + ), + cast_to=BrandScreenshotResponse, + ) + def search( self, *, @@ -470,6 +578,58 @@ def search( cast_to=BrandSearchResponse, ) + def styleguide( + self, + *, + domain: str, + timeout_ms: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BrandStyleguideResponse: + """ + Beta feature: Automatically extract comprehensive design system information from + a brand's website including colors, typography, spacing, shadows, and UI + components. Uses AI-powered analysis of website screenshots to identify design + patterns and create a reusable styleguide. + + Args: + domain: Domain name to extract styleguide 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/styleguide", + 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_styleguide_params.BrandStyleguideParams, + ), + ), + cast_to=BrandStyleguideResponse, + ) + class AsyncBrandResource(AsyncAPIResource): @cached_property @@ -855,6 +1015,108 @@ async def retrieve_naics( cast_to=BrandRetrieveNaicsResponse, ) + async def retrieve_simplified( + self, + *, + domain: str, + timeout_ms: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> 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. + + Args: + domain: Domain name to retrieve simplified brand data for + + 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/retrieve-simplified", + 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_retrieve_simplified_params.BrandRetrieveSimplifiedParams, + ), + ), + cast_to=BrandRetrieveSimplifiedResponse, + ) + + async def screenshot( + self, + *, + domain: str, + full_screenshot: Literal["true", "false"] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BrandScreenshotResponse: + """Beta feature: Capture a screenshot of a website. + + Supports both viewport + (standard browser view) and full-page screenshots. 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). + + 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, + }, + brand_screenshot_params.BrandScreenshotParams, + ), + ), + cast_to=BrandScreenshotResponse, + ) + async def search( self, *, @@ -903,6 +1165,58 @@ async def search( cast_to=BrandSearchResponse, ) + async def styleguide( + self, + *, + domain: str, + timeout_ms: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BrandStyleguideResponse: + """ + Beta feature: Automatically extract comprehensive design system information from + a brand's website including colors, typography, spacing, shadows, and UI + components. Uses AI-powered analysis of website screenshots to identify design + patterns and create a reusable styleguide. + + Args: + domain: Domain name to extract styleguide 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/styleguide", + 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_styleguide_params.BrandStyleguideParams, + ), + ), + cast_to=BrandStyleguideResponse, + ) + class BrandResourceWithRawResponse: def __init__(self, brand: BrandResource) -> None: @@ -926,9 +1240,18 @@ def __init__(self, brand: BrandResource) -> None: 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.search = to_raw_response_wrapper( brand.search, ) + self.styleguide = to_raw_response_wrapper( + brand.styleguide, + ) class AsyncBrandResourceWithRawResponse: @@ -953,9 +1276,18 @@ def __init__(self, brand: AsyncBrandResource) -> None: 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.search = async_to_raw_response_wrapper( brand.search, ) + self.styleguide = async_to_raw_response_wrapper( + brand.styleguide, + ) class BrandResourceWithStreamingResponse: @@ -980,9 +1312,18 @@ def __init__(self, brand: BrandResource) -> None: 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.search = to_streamed_response_wrapper( brand.search, ) + self.styleguide = to_streamed_response_wrapper( + brand.styleguide, + ) class AsyncBrandResourceWithStreamingResponse: @@ -1007,6 +1348,15 @@ def __init__(self, brand: AsyncBrandResource) -> None: 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.search = async_to_streamed_response_wrapper( brand.search, ) + self.styleguide = async_to_streamed_response_wrapper( + brand.styleguide, + ) diff --git a/src/brand/dev/types/__init__.py b/src/brand/dev/types/__init__.py index 3feb8cc..c7428ce 100644 --- a/src/brand/dev/types/__init__.py +++ b/src/brand/dev/types/__init__.py @@ -10,10 +10,16 @@ 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_screenshot_response import BrandScreenshotResponse as BrandScreenshotResponse +from .brand_styleguide_response import BrandStyleguideResponse as BrandStyleguideResponse from .brand_retrieve_naics_params import BrandRetrieveNaicsParams as BrandRetrieveNaicsParams from .brand_retrieve_naics_response import BrandRetrieveNaicsResponse as BrandRetrieveNaicsResponse from .brand_retrieve_by_ticker_params import BrandRetrieveByTickerParams as BrandRetrieveByTickerParams +from .brand_retrieve_simplified_params import BrandRetrieveSimplifiedParams as BrandRetrieveSimplifiedParams from .brand_retrieve_by_ticker_response import BrandRetrieveByTickerResponse as BrandRetrieveByTickerResponse +from .brand_retrieve_simplified_response import BrandRetrieveSimplifiedResponse as BrandRetrieveSimplifiedResponse from .brand_identify_from_transaction_params import ( BrandIdentifyFromTransactionParams as BrandIdentifyFromTransactionParams, ) diff --git a/src/brand/dev/types/brand_retrieve_simplified_params.py b/src/brand/dev/types/brand_retrieve_simplified_params.py new file mode 100644 index 0000000..b9d9cd3 --- /dev/null +++ b/src/brand/dev/types/brand_retrieve_simplified_params.py @@ -0,0 +1,21 @@ +# 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__ = ["BrandRetrieveSimplifiedParams"] + + +class BrandRetrieveSimplifiedParams(TypedDict, total=False): + domain: Required[str] + """Domain name to retrieve simplified brand data for""" + + 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_simplified_response.py b/src/brand/dev/types/brand_retrieve_simplified_response.py new file mode 100644 index 0000000..b865ff8 --- /dev/null +++ b/src/brand/dev/types/brand_retrieve_simplified_response.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "BrandRetrieveSimplifiedResponse", + "Brand", + "BrandBackdrop", + "BrandBackdropColor", + "BrandBackdropResolution", + "BrandColor", + "BrandLogo", + "BrandLogoColor", + "BrandLogoResolution", +] + + +class BrandBackdropColor(BaseModel): + hex: Optional[str] = None + """Color in hexadecimal format""" + + name: Optional[str] = None + """Name of the color""" + + +class BrandBackdropResolution(BaseModel): + aspect_ratio: Optional[float] = None + """Aspect ratio of the image (width/height)""" + + height: Optional[int] = None + """Height of the image in pixels""" + + width: Optional[int] = None + """Width of the image in pixels""" + + +class BrandBackdrop(BaseModel): + colors: Optional[List[BrandBackdropColor]] = None + """Array of colors in the backdrop image""" + + resolution: Optional[BrandBackdropResolution] = None + """Resolution of the backdrop image""" + + url: Optional[str] = None + """URL of the backdrop image""" + + +class BrandColor(BaseModel): + hex: Optional[str] = None + """Color in hexadecimal format""" + + name: Optional[str] = None + """Name of the color""" + + +class BrandLogoColor(BaseModel): + hex: Optional[str] = None + """Color in hexadecimal format""" + + name: Optional[str] = None + """Name of the color""" + + +class BrandLogoResolution(BaseModel): + aspect_ratio: Optional[float] = None + """Aspect ratio of the image (width/height)""" + + height: Optional[int] = None + """Height of the image in pixels""" + + width: Optional[int] = None + """Width of the image in pixels""" + + +class BrandLogo(BaseModel): + colors: Optional[List[BrandLogoColor]] = None + """Array of colors in the logo""" + + group: Optional[int] = None + """Group identifier for logos""" + + mode: Optional[str] = None + """Mode of the logo, e.g., 'dark', 'light'""" + + resolution: Optional[BrandLogoResolution] = None + """Resolution of the logo image""" + + type: Optional[str] = None + """Type of the logo based on resolution (e.g., 'icon', 'logo', 'banner')""" + + url: Optional[str] = None + """URL of the logo image""" + + +class Brand(BaseModel): + backdrops: Optional[List[BrandBackdrop]] = None + """An array of backdrop images for the brand""" + + colors: Optional[List[BrandColor]] = None + """An array of brand colors""" + + domain: Optional[str] = None + """The domain name of the brand""" + + logos: Optional[List[BrandLogo]] = None + """An array of logos associated with the brand""" + + title: Optional[str] = None + """The title or name of the brand""" + + +class BrandRetrieveSimplifiedResponse(BaseModel): + brand: Optional[Brand] = None + """Simplified brand information""" + + code: Optional[int] = None + """HTTP status code of the response""" + + status: Optional[str] = None + """Status of the response, e.g., 'ok'""" diff --git a/src/brand/dev/types/brand_screenshot_params.py b/src/brand/dev/types/brand_screenshot_params.py new file mode 100644 index 0000000..5027d90 --- /dev/null +++ b/src/brand/dev/types/brand_screenshot_params.py @@ -0,0 +1,24 @@ +# 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). + """ diff --git a/src/brand/dev/types/brand_screenshot_response.py b/src/brand/dev/types/brand_screenshot_response.py new file mode 100644 index 0000000..c43ae74 --- /dev/null +++ b/src/brand/dev/types/brand_screenshot_response.py @@ -0,0 +1,27 @@ +# 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 new file mode 100644 index 0000000..42b3f38 --- /dev/null +++ b/src/brand/dev/types/brand_styleguide_params.py @@ -0,0 +1,24 @@ +# 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__ = ["BrandStyleguideParams"] + + +class BrandStyleguideParams(TypedDict, total=False): + domain: Required[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 new file mode 100644 index 0000000..37198f2 --- /dev/null +++ b/src/brand/dev/types/brand_styleguide_response.py @@ -0,0 +1,291 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +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): + accent: Optional[str] = None + """Accent color of the website (hex format)""" + + background: Optional[str] = None + """Background color of the website (hex format)""" + + text: Optional[str] = None + """Text color of the website (hex format)""" + + +class StyleguideComponentsButtonLink(BaseModel): + background_color: Optional[str] = FieldInfo(alias="backgroundColor", default=None) + + border_color: Optional[str] = FieldInfo(alias="borderColor", default=None) + + border_radius: Optional[str] = FieldInfo(alias="borderRadius", default=None) + + border_style: Optional[str] = FieldInfo(alias="borderStyle", default=None) + + border_width: Optional[str] = FieldInfo(alias="borderWidth", default=None) + + box_shadow: Optional[str] = FieldInfo(alias="boxShadow", default=None) + + color: Optional[str] = None + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + padding: Optional[str] = None + + text_decoration: Optional[str] = FieldInfo(alias="textDecoration", default=None) + + +class StyleguideComponentsButtonPrimary(BaseModel): + background_color: Optional[str] = FieldInfo(alias="backgroundColor", default=None) + + border_color: Optional[str] = FieldInfo(alias="borderColor", default=None) + + border_radius: Optional[str] = FieldInfo(alias="borderRadius", default=None) + + border_style: Optional[str] = FieldInfo(alias="borderStyle", default=None) + + border_width: Optional[str] = FieldInfo(alias="borderWidth", default=None) + + box_shadow: Optional[str] = FieldInfo(alias="boxShadow", default=None) + + color: Optional[str] = None + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + padding: Optional[str] = None + + text_decoration: Optional[str] = FieldInfo(alias="textDecoration", default=None) + + +class StyleguideComponentsButtonSecondary(BaseModel): + background_color: Optional[str] = FieldInfo(alias="backgroundColor", default=None) + + border_color: Optional[str] = FieldInfo(alias="borderColor", default=None) + + border_radius: Optional[str] = FieldInfo(alias="borderRadius", default=None) + + border_style: Optional[str] = FieldInfo(alias="borderStyle", default=None) + + border_width: Optional[str] = FieldInfo(alias="borderWidth", default=None) + + box_shadow: Optional[str] = FieldInfo(alias="boxShadow", default=None) + + color: Optional[str] = None + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + padding: Optional[str] = None + + text_decoration: Optional[str] = FieldInfo(alias="textDecoration", default=None) + + +class StyleguideComponentsButton(BaseModel): + link: Optional[StyleguideComponentsButtonLink] = None + """Link button style""" + + primary: Optional[StyleguideComponentsButtonPrimary] = None + """Primary button style""" + + secondary: Optional[StyleguideComponentsButtonSecondary] = None + """Secondary button style""" + + +class StyleguideComponentsCard(BaseModel): + background_color: Optional[str] = FieldInfo(alias="backgroundColor", default=None) + + border_color: Optional[str] = FieldInfo(alias="borderColor", default=None) + + border_radius: Optional[str] = FieldInfo(alias="borderRadius", default=None) + + border_style: Optional[str] = FieldInfo(alias="borderStyle", default=None) + + border_width: Optional[str] = FieldInfo(alias="borderWidth", default=None) + + box_shadow: Optional[str] = FieldInfo(alias="boxShadow", default=None) + + padding: Optional[str] = None + + text_color: Optional[str] = FieldInfo(alias="textColor", default=None) + + +class StyleguideComponents(BaseModel): + button: Optional[StyleguideComponentsButton] = None + """Button component styles""" + + card: Optional[StyleguideComponentsCard] = None + """Card component style""" + + +class StyleguideElementSpacing(BaseModel): + lg: Optional[str] = None + """Large spacing value""" + + md: Optional[str] = None + """Medium spacing value""" + + sm: Optional[str] = None + """Small spacing value""" + + xl: Optional[str] = None + """Extra large spacing value""" + + xs: Optional[str] = None + """Extra small spacing value""" + + +class StyleguideShadows(BaseModel): + inner: Optional[str] = None + """Inner shadow value""" + + lg: Optional[str] = None + """Large shadow value""" + + md: Optional[str] = None + """Medium shadow value""" + + sm: Optional[str] = None + """Small shadow value""" + + xl: Optional[str] = None + """Extra large shadow value""" + + +class StyleguideTypographyHeadingsH1(BaseModel): + font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + letter_spacing: Optional[str] = FieldInfo(alias="letterSpacing", default=None) + + line_height: Optional[str] = FieldInfo(alias="lineHeight", default=None) + + +class StyleguideTypographyHeadingsH2(BaseModel): + font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + letter_spacing: Optional[str] = FieldInfo(alias="letterSpacing", default=None) + + line_height: Optional[str] = FieldInfo(alias="lineHeight", default=None) + + +class StyleguideTypographyHeadingsH3(BaseModel): + font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + letter_spacing: Optional[str] = FieldInfo(alias="letterSpacing", default=None) + + line_height: Optional[str] = FieldInfo(alias="lineHeight", default=None) + + +class StyleguideTypographyHeadingsH4(BaseModel): + font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + letter_spacing: Optional[str] = FieldInfo(alias="letterSpacing", default=None) + + line_height: Optional[str] = FieldInfo(alias="lineHeight", default=None) + + +class StyleguideTypographyHeadings(BaseModel): + h1: Optional[StyleguideTypographyHeadingsH1] = None + + h2: Optional[StyleguideTypographyHeadingsH2] = None + + h3: Optional[StyleguideTypographyHeadingsH3] = None + + h4: Optional[StyleguideTypographyHeadingsH4] = None + + +class StyleguideTypographyP(BaseModel): + font_family: Optional[str] = FieldInfo(alias="fontFamily", default=None) + + font_size: Optional[str] = FieldInfo(alias="fontSize", default=None) + + font_weight: Optional[float] = FieldInfo(alias="fontWeight", default=None) + + letter_spacing: Optional[str] = FieldInfo(alias="letterSpacing", default=None) + + line_height: Optional[str] = FieldInfo(alias="lineHeight", default=None) + + +class StyleguideTypography(BaseModel): + headings: Optional[StyleguideTypographyHeadings] = None + """Heading styles""" + + p: Optional[StyleguideTypographyP] = None + """Paragraph text styles""" + + +class Styleguide(BaseModel): + colors: Optional[StyleguideColors] = None + """Primary colors used on the website""" + + components: Optional[StyleguideComponents] = None + """UI component styles""" + + element_spacing: Optional[StyleguideElementSpacing] = FieldInfo(alias="elementSpacing", default=None) + """Spacing system used on the website""" + + shadows: Optional[StyleguideShadows] = None + """Shadow styles used on the website""" + + typography: Optional[StyleguideTypography] = None + """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/tests/api_resources/test_brand.py b/tests/api_resources/test_brand.py index dcfe95d..5647f52 100644 --- a/tests/api_resources/test_brand.py +++ b/tests/api_resources/test_brand.py @@ -14,8 +14,11 @@ BrandAIQueryResponse, BrandPrefetchResponse, BrandRetrieveResponse, + BrandScreenshotResponse, + BrandStyleguideResponse, BrandRetrieveNaicsResponse, BrandRetrieveByTickerResponse, + BrandRetrieveSimplifiedResponse, BrandIdentifyFromTransactionResponse, ) @@ -327,6 +330,92 @@ def test_streaming_response_retrieve_naics(self, client: BrandDev) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_method_retrieve_simplified(self, client: BrandDev) -> None: + brand = client.brand.retrieve_simplified( + domain="domain", + ) + assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve_simplified_with_all_params(self, client: BrandDev) -> None: + brand = client.brand.retrieve_simplified( + domain="domain", + timeout_ms=1, + ) + assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve_simplified(self, client: BrandDev) -> None: + response = client.brand.with_raw_response.retrieve_simplified( + domain="domain", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + brand = response.parse() + assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve_simplified(self, client: BrandDev) -> None: + with client.brand.with_streaming_response.retrieve_simplified( + 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(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @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() + @parametrize + def test_method_screenshot_with_all_params(self, client: BrandDev) -> None: + brand = client.brand.screenshot( + domain="domain", + full_screenshot="true", + ) + assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) + + @pytest.mark.skip() + @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() + @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() @parametrize def test_method_search(self, client: BrandDev) -> None: @@ -370,6 +459,49 @@ def test_streaming_response_search(self, client: BrandDev) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_method_styleguide(self, client: BrandDev) -> None: + brand = client.brand.styleguide( + domain="domain", + ) + assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_styleguide_with_all_params(self, client: BrandDev) -> None: + brand = client.brand.styleguide( + domain="domain", + timeout_ms=1, + ) + assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_styleguide(self, client: BrandDev) -> None: + response = client.brand.with_raw_response.styleguide( + domain="domain", + ) + + 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() + @parametrize + def test_streaming_response_styleguide(self, client: BrandDev) -> None: + with client.brand.with_streaming_response.styleguide( + 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(BrandStyleguideResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncBrand: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -676,6 +808,92 @@ async def test_streaming_response_retrieve_naics(self, async_client: AsyncBrandD assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_simplified(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.retrieve_simplified( + domain="domain", + ) + assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_simplified_with_all_params(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.retrieve_simplified( + domain="domain", + timeout_ms=1, + ) + assert_matches_type(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve_simplified(self, async_client: AsyncBrandDev) -> None: + response = await async_client.brand.with_raw_response.retrieve_simplified( + 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(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve_simplified(self, async_client: AsyncBrandDev) -> None: + async with async_client.brand.with_streaming_response.retrieve_simplified( + 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(BrandRetrieveSimplifiedResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @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() + @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", + ) + assert_matches_type(BrandScreenshotResponse, brand, path=["response"]) + + @pytest.mark.skip() + @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() + @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() @parametrize async def test_method_search(self, async_client: AsyncBrandDev) -> None: @@ -718,3 +936,46 @@ async def test_streaming_response_search(self, async_client: AsyncBrandDev) -> N assert_matches_type(BrandSearchResponse, brand, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_styleguide(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.styleguide( + domain="domain", + ) + assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_styleguide_with_all_params(self, async_client: AsyncBrandDev) -> None: + brand = await async_client.brand.styleguide( + domain="domain", + timeout_ms=1, + ) + assert_matches_type(BrandStyleguideResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_styleguide(self, async_client: AsyncBrandDev) -> None: + response = await async_client.brand.with_raw_response.styleguide( + 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(BrandStyleguideResponse, brand, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_styleguide(self, async_client: AsyncBrandDev) -> None: + async with async_client.brand.with_streaming_response.styleguide( + 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(BrandStyleguideResponse, brand, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/conftest.py b/tests/conftest.py index 5486879..4871488 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os diff --git a/tests/test_client.py b/tests/test_client.py index 391b339..1156a03 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -24,12 +24,13 @@ from brand.dev import BrandDev, AsyncBrandDev, APIResponseValidationError from brand.dev._types import Omit from brand.dev._models import BaseModel, FinalRequestOptions -from brand.dev._constants import RAW_RESPONSE_HEADER from brand.dev._exceptions import BrandDevError, APIStatusError, APITimeoutError, APIResponseValidationError from brand.dev._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, make_request_options, ) @@ -711,26 +712,21 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("brand.dev._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: BrandDev) -> None: respx_mock.get("/brand/retrieve").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.get( - "/brand/retrieve", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} - ) + client.brand.with_streaming_response.retrieve(domain="domain").__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("brand.dev._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: BrandDev) -> None: respx_mock.get("/brand/retrieve").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.get( - "/brand/retrieve", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} - ) - + client.brand.with_streaming_response.retrieve(domain="domain").__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -814,6 +810,28 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects @@ -1512,26 +1530,25 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("brand.dev._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_timeout_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncBrandDev + ) -> None: respx_mock.get("/brand/retrieve").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.get( - "/brand/retrieve", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} - ) + await async_client.brand.with_streaming_response.retrieve(domain="domain").__aenter__() assert _get_open_connections(self.client) == 0 @mock.patch("brand.dev._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_status_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncBrandDev + ) -> None: respx_mock.get("/brand/retrieve").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.get( - "/brand/retrieve", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} - ) - + await async_client.brand.with_streaming_response.retrieve(domain="domain").__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1663,6 +1680,28 @@ async def test_main() -> None: time.sleep(0.1) + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) async def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects