diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4dedeae..511dd51 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.5.0" + ".": "2.6.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 0ac1eb3..33b7080 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 32 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-242450ea46eb8c3e843fd6c4bf87e73192b5f62f6da697cd091d13c6aa7a991b.yml -openapi_spec_hash: c1c561976de1abcacede71fd5ab9b3d9 -config_hash: 70e7e80b5e87f94981bee396c6cd41e8 +configured_endpoints: 37 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-960cb623c7ec84bf4dc0f5945cbc19eec9cca48271071f400d96066eaa55dbd6.yml +openapi_spec_hash: 84fd39e3f4dc964bf0c32d4e95da1b34 +config_hash: 2bea1743c84d63bd61f8501a6ea63065 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4444631..5cebd96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 2.6.0 (2026-07-31) + +Full Changelog: [v2.5.0...v2.6.0](https://github.com/context-dot-dev/context-python-sdk/compare/v2.5.0...v2.6.0) + +### Features + +* **api:** api update ([6d99f45](https://github.com/context-dot-dev/context-python-sdk/commit/6d99f453a62eb95134599a6e1d9c648788dfe3b7)) +* **api:** api update ([ff409ed](https://github.com/context-dot-dev/context-python-sdk/commit/ff409edadd0a514fa22948f8abd741b9676ca1b6)) +* **api:** api update ([925ed2c](https://github.com/context-dot-dev/context-python-sdk/commit/925ed2c6ec48e165f9807101bbbfdbda9c668709)) +* **api:** api update ([3f3a903](https://github.com/context-dot-dev/context-python-sdk/commit/3f3a90368fe919e2d727f484db792ca2d631f3bf)) +* **api:** api update ([226b29c](https://github.com/context-dot-dev/context-python-sdk/commit/226b29c8e73b0c785ad9c2692225cfac44365395)) +* **api:** manual updates ([f32255d](https://github.com/context-dot-dev/context-python-sdk/commit/f32255d61d0e43804dacbfc7b578bd909899db61)) + ## 2.5.0 (2026-07-22) Full Changelog: [v2.4.0...v2.5.0](https://github.com/context-dot-dev/context-python-sdk/compare/v2.4.0...v2.5.0) diff --git a/api.md b/api.md index fe5b201..d95bf72 100644 --- a/api.md +++ b/api.md @@ -133,3 +133,27 @@ Methods: - client.monitors.list_runs(monitor_id, \*\*params) -> MonitorListRunsResponse - client.monitors.retrieve_change(change_id) -> MonitorRetrieveChangeResponse - client.monitors.run(monitor_id) -> MonitorRunResponse + +# Batch + +Types: + +```python +from context.dev.types import ( + ErrorCount, + Error, + BatchRetrieveResponse, + BatchListResponse, + BatchCancelResponse, + BatchGetResultsResponse, + BatchSubmitResponse, +) +``` + +Methods: + +- client.batch.retrieve(batch_id) -> BatchRetrieveResponse +- client.batch.list(\*\*params) -> BatchListResponse +- client.batch.cancel(batch_id) -> BatchCancelResponse +- client.batch.get_results(batch_id, \*\*params) -> BatchGetResultsResponse +- client.batch.submit(\*\*params) -> BatchSubmitResponse diff --git a/pyproject.toml b/pyproject.toml index 32eb11e..7493050 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "context.dev" -version = "2.5.0" +version = "2.6.0" description = "The official Python library for the context.dev API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/context/dev/_client.py b/src/context/dev/_client.py index 800374f..cf4d545 100644 --- a/src/context/dev/_client.py +++ b/src/context/dev/_client.py @@ -35,9 +35,10 @@ ) if TYPE_CHECKING: - from .resources import ai, web, brand, parse, utility, industry, monitors + from .resources import ai, web, batch, brand, parse, utility, industry, monitors from .resources.ai import AIResource, AsyncAIResource from .resources.web import WebResource, AsyncWebResource + from .resources.batch import BatchResource, AsyncBatchResource from .resources.brand import BrandResource, AsyncBrandResource from .resources.parse import ParseResource, AsyncParseResource from .resources.utility import UtilityResource, AsyncUtilityResource @@ -165,6 +166,12 @@ def monitors(self) -> MonitorsResource: return MonitorsResource(self) + @cached_property + def batch(self) -> BatchResource: + from .resources.batch import BatchResource + + return BatchResource(self) + @cached_property def with_raw_response(self) -> ContextDevWithRawResponse: return ContextDevWithRawResponse(self) @@ -387,6 +394,12 @@ def monitors(self) -> AsyncMonitorsResource: return AsyncMonitorsResource(self) + @cached_property + def batch(self) -> AsyncBatchResource: + from .resources.batch import AsyncBatchResource + + return AsyncBatchResource(self) + @cached_property def with_raw_response(self) -> AsyncContextDevWithRawResponse: return AsyncContextDevWithRawResponse(self) @@ -551,6 +564,12 @@ def monitors(self) -> monitors.MonitorsResourceWithRawResponse: return MonitorsResourceWithRawResponse(self._client.monitors) + @cached_property + def batch(self) -> batch.BatchResourceWithRawResponse: + from .resources.batch import BatchResourceWithRawResponse + + return BatchResourceWithRawResponse(self._client.batch) + class AsyncContextDevWithRawResponse: _client: AsyncContextDev @@ -603,6 +622,12 @@ def monitors(self) -> monitors.AsyncMonitorsResourceWithRawResponse: return AsyncMonitorsResourceWithRawResponse(self._client.monitors) + @cached_property + def batch(self) -> batch.AsyncBatchResourceWithRawResponse: + from .resources.batch import AsyncBatchResourceWithRawResponse + + return AsyncBatchResourceWithRawResponse(self._client.batch) + class ContextDevWithStreamedResponse: _client: ContextDev @@ -655,6 +680,12 @@ def monitors(self) -> monitors.MonitorsResourceWithStreamingResponse: return MonitorsResourceWithStreamingResponse(self._client.monitors) + @cached_property + def batch(self) -> batch.BatchResourceWithStreamingResponse: + from .resources.batch import BatchResourceWithStreamingResponse + + return BatchResourceWithStreamingResponse(self._client.batch) + class AsyncContextDevWithStreamedResponse: _client: AsyncContextDev @@ -707,6 +738,12 @@ def monitors(self) -> monitors.AsyncMonitorsResourceWithStreamingResponse: return AsyncMonitorsResourceWithStreamingResponse(self._client.monitors) + @cached_property + def batch(self) -> batch.AsyncBatchResourceWithStreamingResponse: + from .resources.batch import AsyncBatchResourceWithStreamingResponse + + return AsyncBatchResourceWithStreamingResponse(self._client.batch) + Client = ContextDev diff --git a/src/context/dev/_version.py b/src/context/dev/_version.py index 76defcd..de35f16 100644 --- a/src/context/dev/_version.py +++ b/src/context/dev/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "context.dev" -__version__ = "2.5.0" # x-release-please-version +__version__ = "2.6.0" # x-release-please-version diff --git a/src/context/dev/resources/__init__.py b/src/context/dev/resources/__init__.py index 12b45f9..c99c0f9 100644 --- a/src/context/dev/resources/__init__.py +++ b/src/context/dev/resources/__init__.py @@ -16,6 +16,14 @@ WebResourceWithStreamingResponse, AsyncWebResourceWithStreamingResponse, ) +from .batch import ( + BatchResource, + AsyncBatchResource, + BatchResourceWithRawResponse, + AsyncBatchResourceWithRawResponse, + BatchResourceWithStreamingResponse, + AsyncBatchResourceWithStreamingResponse, +) from .brand import ( BrandResource, AsyncBrandResource, @@ -100,4 +108,10 @@ "AsyncMonitorsResourceWithRawResponse", "MonitorsResourceWithStreamingResponse", "AsyncMonitorsResourceWithStreamingResponse", + "BatchResource", + "AsyncBatchResource", + "BatchResourceWithRawResponse", + "AsyncBatchResourceWithRawResponse", + "BatchResourceWithStreamingResponse", + "AsyncBatchResourceWithStreamingResponse", ] diff --git a/src/context/dev/resources/batch.py b/src/context/dev/resources/batch.py new file mode 100644 index 0000000..50e9ded --- /dev/null +++ b/src/context/dev/resources/batch.py @@ -0,0 +1,641 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..types import batch_list_params, batch_submit_params, batch_get_results_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.batch_list_response import BatchListResponse +from ..types.batch_cancel_response import BatchCancelResponse +from ..types.batch_submit_response import BatchSubmitResponse +from ..types.batch_retrieve_response import BatchRetrieveResponse +from ..types.batch_get_results_response import BatchGetResultsResponse + +__all__ = ["BatchResource", "AsyncBatchResource"] + + +class BatchResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BatchResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/context-dot-dev/context-python-sdk#accessing-raw-response-data-eg-headers + """ + return BatchResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BatchResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/context-dot-dev/context-python-sdk#with_streaming_response + """ + return BatchResourceWithStreamingResponse(self) + + def retrieve( + self, + batch_id: 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, + ) -> BatchRetrieveResponse: + """Check progress and get download links when the batch finishes. + + Also returns the + rejected-URL list and webhook signing secret from submission, so nothing is lost + if the submit response was dropped. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return self._get( + path_template("/batch/{batch_id}", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchRetrieveResponse, + ) + + def list( + self, + *, + cursor: str | Omit = omit, + limit: int | Omit = omit, + q: str | Omit = omit, + search_type: Literal["exact", "prefix"] | Omit = omit, + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] | Omit = omit, + tags: 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, + ) -> BatchListResponse: + """List your batches from newest to oldest. + + Filter by status or continue with a + cursor. + + Args: + cursor: Cursor from the previous page. + + limit: Batches per page. Defaults to 25. + + q: Free-text search term, matched against the batch id, crawl source (start URL or + sitemap domain), and tags. + + search_type: `prefix` for as-you-type prefix matching (default), `exact` for full-token + matching. + + status: Filter by status. + + tags: Comma-separated list of tags to filter by (matches batches having any of them). + + 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( + "/batch/list", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + "q": q, + "search_type": search_type, + "status": status, + "tags": tags, + }, + batch_list_params.BatchListParams, + ), + ), + cast_to=BatchListResponse, + ) + + def cancel( + self, + batch_id: 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, + ) -> BatchCancelResponse: + """Stop a batch from starting new pages. + + In-progress pages finish, and unused + credits are refunded. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return self._post( + path_template("/batch/{batch_id}/cancel", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchCancelResponse, + ) + + def get_results( + self, + batch_id: str, + *, + cursor: str | Omit = omit, + limit: 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, + ) -> BatchGetResultsResponse: + """ + Page through the result records of a finished batch as JSON, in the same order + as the downloadable result files. Use this instead of downloading and parsing + the NDJSON files yourself. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + cursor: next_cursor from the previous page. + + limit: Records per page. Defaults to 25. A page can close early so its payload stays + under ~8 MB; rely on next_cursor rather than counting records. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return self._get( + path_template("/batch/{batch_id}/results", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + batch_get_results_params.BatchGetResultsParams, + ), + ), + cast_to=BatchGetResultsResponse, + ) + + def submit( + self, + *, + identifiers: batch_submit_params.Identifiers, + tags: SequenceNotStr[str] | 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, + ) -> BatchSubmitResponse: + """ + Retrieve and normalize a person profile from identifiers. + + Args: + identifiers: Known identifiers for the person. At least one identifier is required. + + tags: Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters. + + 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._post( + "/people/retrieve", + body=maybe_transform( + { + "identifiers": identifiers, + "tags": tags, + "timeout_ms": timeout_ms, + }, + batch_submit_params.BatchSubmitParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchSubmitResponse, + ) + + +class AsyncBatchResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBatchResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/context-dot-dev/context-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncBatchResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBatchResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/context-dot-dev/context-python-sdk#with_streaming_response + """ + return AsyncBatchResourceWithStreamingResponse(self) + + async def retrieve( + self, + batch_id: 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, + ) -> BatchRetrieveResponse: + """Check progress and get download links when the batch finishes. + + Also returns the + rejected-URL list and webhook signing secret from submission, so nothing is lost + if the submit response was dropped. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return await self._get( + path_template("/batch/{batch_id}", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchRetrieveResponse, + ) + + async def list( + self, + *, + cursor: str | Omit = omit, + limit: int | Omit = omit, + q: str | Omit = omit, + search_type: Literal["exact", "prefix"] | Omit = omit, + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] | Omit = omit, + tags: 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, + ) -> BatchListResponse: + """List your batches from newest to oldest. + + Filter by status or continue with a + cursor. + + Args: + cursor: Cursor from the previous page. + + limit: Batches per page. Defaults to 25. + + q: Free-text search term, matched against the batch id, crawl source (start URL or + sitemap domain), and tags. + + search_type: `prefix` for as-you-type prefix matching (default), `exact` for full-token + matching. + + status: Filter by status. + + tags: Comma-separated list of tags to filter by (matches batches having any of them). + + 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( + "/batch/list", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "cursor": cursor, + "limit": limit, + "q": q, + "search_type": search_type, + "status": status, + "tags": tags, + }, + batch_list_params.BatchListParams, + ), + ), + cast_to=BatchListResponse, + ) + + async def cancel( + self, + batch_id: 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, + ) -> BatchCancelResponse: + """Stop a batch from starting new pages. + + In-progress pages finish, and unused + credits are refunded. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return await self._post( + path_template("/batch/{batch_id}/cancel", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchCancelResponse, + ) + + async def get_results( + self, + batch_id: str, + *, + cursor: str | Omit = omit, + limit: 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, + ) -> BatchGetResultsResponse: + """ + Page through the result records of a finished batch as JSON, in the same order + as the downloadable result files. Use this instead of downloading and parsing + the NDJSON files yourself. + + Args: + batch_id: ID of the batch to retrieve or cancel. + + cursor: next_cursor from the previous page. + + limit: Records per page. Defaults to 25. A page can close early so its payload stays + under ~8 MB; rely on next_cursor rather than counting records. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return await self._get( + path_template("/batch/{batch_id}/results", batch_id=batch_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + batch_get_results_params.BatchGetResultsParams, + ), + ), + cast_to=BatchGetResultsResponse, + ) + + async def submit( + self, + *, + identifiers: batch_submit_params.Identifiers, + tags: SequenceNotStr[str] | 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, + ) -> BatchSubmitResponse: + """ + Retrieve and normalize a person profile from identifiers. + + Args: + identifiers: Known identifiers for the person. At least one identifier is required. + + tags: Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters. + + 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._post( + "/people/retrieve", + body=await async_maybe_transform( + { + "identifiers": identifiers, + "tags": tags, + "timeout_ms": timeout_ms, + }, + batch_submit_params.BatchSubmitParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BatchSubmitResponse, + ) + + +class BatchResourceWithRawResponse: + def __init__(self, batch: BatchResource) -> None: + self._batch = batch + + self.retrieve = to_raw_response_wrapper( + batch.retrieve, + ) + self.list = to_raw_response_wrapper( + batch.list, + ) + self.cancel = to_raw_response_wrapper( + batch.cancel, + ) + self.get_results = to_raw_response_wrapper( + batch.get_results, + ) + self.submit = to_raw_response_wrapper( + batch.submit, + ) + + +class AsyncBatchResourceWithRawResponse: + def __init__(self, batch: AsyncBatchResource) -> None: + self._batch = batch + + self.retrieve = async_to_raw_response_wrapper( + batch.retrieve, + ) + self.list = async_to_raw_response_wrapper( + batch.list, + ) + self.cancel = async_to_raw_response_wrapper( + batch.cancel, + ) + self.get_results = async_to_raw_response_wrapper( + batch.get_results, + ) + self.submit = async_to_raw_response_wrapper( + batch.submit, + ) + + +class BatchResourceWithStreamingResponse: + def __init__(self, batch: BatchResource) -> None: + self._batch = batch + + self.retrieve = to_streamed_response_wrapper( + batch.retrieve, + ) + self.list = to_streamed_response_wrapper( + batch.list, + ) + self.cancel = to_streamed_response_wrapper( + batch.cancel, + ) + self.get_results = to_streamed_response_wrapper( + batch.get_results, + ) + self.submit = to_streamed_response_wrapper( + batch.submit, + ) + + +class AsyncBatchResourceWithStreamingResponse: + def __init__(self, batch: AsyncBatchResource) -> None: + self._batch = batch + + self.retrieve = async_to_streamed_response_wrapper( + batch.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + batch.list, + ) + self.cancel = async_to_streamed_response_wrapper( + batch.cancel, + ) + self.get_results = async_to_streamed_response_wrapper( + batch.get_results, + ) + self.submit = async_to_streamed_response_wrapper( + batch.submit, + ) diff --git a/src/context/dev/resources/monitors.py b/src/context/dev/resources/monitors.py index b0e6b7a..8285f02 100644 --- a/src/context/dev/resources/monitors.py +++ b/src/context/dev/resources/monitors.py @@ -73,11 +73,11 @@ def with_streaming_response(self) -> MonitorsResourceWithStreamingResponse: def create( self, *, - change_detection: monitor_create_params.ChangeDetection, name: str, - schedule: monitor_create_params.Schedule, target: monitor_create_params.Target, + change_detection: monitor_create_params.ChangeDetection | Omit = omit, mode: Literal["web"] | Omit = omit, + schedule: monitor_create_params.Schedule | Omit = omit, tags: SequenceNotStr[str] | Omit = omit, webhook: Optional[monitor_create_params.Webhook] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -94,17 +94,17 @@ def create( its initial baseline. Args: + target: Discriminated union describing what the monitor watches. + change_detection: Discriminated union describing how changes are detected. + mode: Top-level monitor category. Always `web` today; the concrete behavior is + described by `target` and `change_detection`. + schedule: Run the monitor on a fixed interval defined by a frequency and a unit, e.g. every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year. - target: Discriminated union describing what the monitor watches. - - mode: Top-level monitor category. Always `web` today; the concrete behavior is - described by `target` and `change_detection`. - tags: User-defined tags for grouping and filtering monitors and their changes. Duplicates are removed. @@ -120,11 +120,11 @@ def create( "/monitors", body=maybe_transform( { - "change_detection": change_detection, "name": name, - "schedule": schedule, "target": target, + "change_detection": change_detection, "mode": mode, + "schedule": schedule, "tags": tags, "webhook": webhook, }, @@ -749,11 +749,11 @@ def with_streaming_response(self) -> AsyncMonitorsResourceWithStreamingResponse: async def create( self, *, - change_detection: monitor_create_params.ChangeDetection, name: str, - schedule: monitor_create_params.Schedule, target: monitor_create_params.Target, + change_detection: monitor_create_params.ChangeDetection | Omit = omit, mode: Literal["web"] | Omit = omit, + schedule: monitor_create_params.Schedule | Omit = omit, tags: SequenceNotStr[str] | Omit = omit, webhook: Optional[monitor_create_params.Webhook] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -770,17 +770,17 @@ async def create( its initial baseline. Args: + target: Discriminated union describing what the monitor watches. + change_detection: Discriminated union describing how changes are detected. + mode: Top-level monitor category. Always `web` today; the concrete behavior is + described by `target` and `change_detection`. + schedule: Run the monitor on a fixed interval defined by a frequency and a unit, e.g. every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year. - target: Discriminated union describing what the monitor watches. - - mode: Top-level monitor category. Always `web` today; the concrete behavior is - described by `target` and `change_detection`. - tags: User-defined tags for grouping and filtering monitors and their changes. Duplicates are removed. @@ -796,11 +796,11 @@ async def create( "/monitors", body=await async_maybe_transform( { - "change_detection": change_detection, "name": name, - "schedule": schedule, "target": target, + "change_detection": change_detection, "mode": mode, + "schedule": schedule, "tags": tags, "webhook": webhook, }, diff --git a/src/context/dev/types/__init__.py b/src/context/dev/types/__init__.py index e1ced36..c49ba0d 100644 --- a/src/context/dev/types/__init__.py +++ b/src/context/dev/types/__init__.py @@ -2,20 +2,28 @@ from __future__ import annotations +from .error import Error as Error +from .error_count import ErrorCount as ErrorCount from .webhook_delivery import WebhookDelivery as WebhookDelivery +from .batch_list_params import BatchListParams as BatchListParams from .web_search_params import WebSearchParams as WebSearchParams from .web_extract_params import WebExtractParams as WebExtractParams +from .batch_list_response import BatchListResponse as BatchListResponse +from .batch_submit_params import BatchSubmitParams as BatchSubmitParams from .monitor_list_params import MonitorListParams as MonitorListParams from .parse_handle_params import ParseHandleParams as ParseHandleParams from .web_search_response import WebSearchResponse as WebSearchResponse from .monitor_run_response import MonitorRunResponse as MonitorRunResponse from .web_extract_response import WebExtractResponse as WebExtractResponse +from .batch_cancel_response import BatchCancelResponse as BatchCancelResponse +from .batch_submit_response import BatchSubmitResponse as BatchSubmitResponse from .brand_retrieve_params import BrandRetrieveParams as BrandRetrieveParams from .monitor_create_params import MonitorCreateParams as MonitorCreateParams from .monitor_list_response import MonitorListResponse as MonitorListResponse from .monitor_update_params import MonitorUpdateParams as MonitorUpdateParams from .parse_handle_response import ParseHandleResponse as ParseHandleResponse from .web_screenshot_params import WebScreenshotParams as WebScreenshotParams +from .batch_retrieve_response import BatchRetrieveResponse as BatchRetrieveResponse from .brand_retrieve_response import BrandRetrieveResponse as BrandRetrieveResponse from .monitor_create_response import MonitorCreateResponse as MonitorCreateResponse from .monitor_delete_response import MonitorDeleteResponse as MonitorDeleteResponse @@ -23,6 +31,7 @@ from .utility_prefetch_params import UtilityPrefetchParams as UtilityPrefetchParams from .web_screenshot_response import WebScreenshotResponse as WebScreenshotResponse from .web_web_crawl_md_params import WebWebCrawlMdParams as WebWebCrawlMdParams +from .batch_get_results_params import BatchGetResultsParams as BatchGetResultsParams from .monitor_list_runs_params import MonitorListRunsParams as MonitorListRunsParams from .web_extract_fonts_params import WebExtractFontsParams as WebExtractFontsParams from .web_web_scrape_md_params import WebWebScrapeMdParams as WebWebScrapeMdParams @@ -31,6 +40,7 @@ from .utility_prefetch_response import UtilityPrefetchResponse as UtilityPrefetchResponse from .web_web_crawl_md_response import WebWebCrawlMdResponse as WebWebCrawlMdResponse from .ai_extract_products_params import AIExtractProductsParams as AIExtractProductsParams +from .batch_get_results_response import BatchGetResultsResponse as BatchGetResultsResponse from .monitor_list_runs_response import MonitorListRunsResponse as MonitorListRunsResponse from .web_extract_fonts_response import WebExtractFontsResponse as WebExtractFontsResponse from .web_web_scrape_html_params import WebWebScrapeHTMLParams as WebWebScrapeHTMLParams diff --git a/src/context/dev/types/batch_cancel_response.py b/src/context/dev/types/batch_cancel_response.py new file mode 100644 index 0000000..9ac6ff5 --- /dev/null +++ b/src/context/dev/types/batch_cancel_response.py @@ -0,0 +1,141 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .error import Error +from .._models import BaseModel +from .error_count import ErrorCount + +__all__ = ["BatchCancelResponse", "Credits", "Input", "Progress", "Results", "ResultsFile", "Timing", "KeyMetadata"] + + +class Credits(BaseModel): + """Reserved and used credits.""" + + charged: int + """Credits used by successful pages.""" + + estimated: int + """Credits reserved when the batch was accepted.""" + + +class Input(BaseModel): + """Submission counts.""" + + accepted: int + """Pages accepted, or the crawl page limit. Credits are reserved for this count.""" + + duplicates: int + """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls.""" + + invalid: int + """Pages rejected during validation.""" + + submitted: int + """Pages submitted before validation. For a crawl, the page limit.""" + + +class Progress(BaseModel): + """Current processing counts. Use `status` to check completion.""" + + failed: int + """Pages that could not be scraped.""" + + pending: int + """Accepted pages not yet attempted. + + Always 0 once the batch completes; a crawl can finish under its page limit when + the site has no more reachable pages. + """ + + succeeded: int + """Pages scraped successfully.""" + + +class ResultsFile(BaseModel): + bytes: int + """Compressed file size in bytes.""" + + items: int + """Results in this file.""" + + url: str + """Temporary URL for a gzipped NDJSON file.""" + + +class Results(BaseModel): + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + expires_at: str + """When the download URLs expire.""" + + files: List[ResultsFile] + """Result files. Order is not guaranteed.""" + + +class Timing(BaseModel): + completed_at: Optional[str] = None + """When processing finished. Null while active.""" + + created_at: str + """When the batch was created.""" + + started_at: Optional[str] = None + """When processing started. Null while queued.""" + + +class KeyMetadata(BaseModel): + """API key usage for this request.""" + + credits_consumed: int + """The number of credits consumed by this request.""" + + credits_remaining: int + """The number of credits remaining for your organization after this request.""" + + +class BatchCancelResponse(BaseModel): + id: str + """Batch ID used to retrieve or cancel the job.""" + + credits: Credits + """Reserved and used credits.""" + + error: Optional[Error] = None + """Why the batch failed.""" + + errors: List[ErrorCount] + """Page failures grouped by error code.""" + + input: Input + """Submission counts.""" + + mode: Literal["scrape", "crawl"] + """How pages are selected.""" + + progress: Progress + """Current processing counts. Use `status` to check completion.""" + + results: Optional[Results] = None + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] + """Current state. `completed`, `cancelled`, and `failed` are final.""" + + tags: List[str] + """Tags stored on the batch at submission.""" + + timing: Timing + + type: Literal["markdown", "html"] + """Output format.""" + + key_metadata: Optional[KeyMetadata] = None + """API key usage for this request.""" diff --git a/src/context/dev/types/batch_get_results_params.py b/src/context/dev/types/batch_get_results_params.py new file mode 100644 index 0000000..cd0bb38 --- /dev/null +++ b/src/context/dev/types/batch_get_results_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["BatchGetResultsParams"] + + +class BatchGetResultsParams(TypedDict, total=False): + cursor: str + """next_cursor from the previous page.""" + + limit: int + """Records per page. + + Defaults to 25. A page can close early so its payload stays under ~8 MB; rely on + next_cursor rather than counting records. + """ diff --git a/src/context/dev/types/batch_get_results_response.py b/src/context/dev/types/batch_get_results_response.py new file mode 100644 index 0000000..13b3ee0 --- /dev/null +++ b/src/context/dev/types/batch_get_results_response.py @@ -0,0 +1,184 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "BatchGetResultsResponse", + "Data", + "DataOk", + "DataOkMetadata", + "DataOkMetadataAlternate", + "DataError", + "KeyMetadata", +] + + +class DataOkMetadataAlternate(BaseModel): + href: str + """Resolved alternate URL.""" + + hreflang: Optional[str] = None + """Language or locale for the alternate URL, when present.""" + + title: Optional[str] = None + """Alternate resource title, when present.""" + + type: Optional[str] = None + """Alternate resource MIME type, when present.""" + + +class DataOkMetadata(BaseModel): + """Metadata extracted from the scraped page HTML.""" + + final_url: str = FieldInfo(alias="finalUrl") + """Final URL scraped after redirects or scraper fallback, when known. + + Falls back to sourceUrl when unavailable. + """ + + source_url: str = FieldInfo(alias="sourceUrl") + """Original URL requested by the caller.""" + + additional_meta: Optional[Dict[str, Union[str, List[str]]]] = FieldInfo(alias="additionalMeta", default=None) + """Additional non-social meta tags not promoted to top-level metadata fields.""" + + alternates: Optional[List[DataOkMetadataAlternate]] = None + """Resolved alternate links from link rel=alternate tags.""" + + author: Optional[str] = None + """Author metadata, when present.""" + + canonical_url: Optional[str] = FieldInfo(alias="canonicalUrl", default=None) + """Resolved canonical URL, when present.""" + + description: Optional[str] = None + """Best description extracted from standard, Open Graph, or Twitter metadata.""" + + favicon: Optional[str] = None + """Resolved favicon URL, when present.""" + + image: Optional[str] = None + """Primary resolved preview image from Open Graph, Twitter, or image metadata.""" + + json_ld: Optional[List[Dict[str, object]]] = FieldInfo(alias="jsonLd", default=None) + """JSON-LD structured data blocks parsed from the page.""" + + keywords: Optional[List[str]] = None + """Keywords extracted from the page's keywords meta tag.""" + + language: Optional[str] = None + """Language extracted from html lang or language meta tags.""" + + modified_time: Optional[str] = FieldInfo(alias="modifiedTime", default=None) + """Modified timestamp/date from page metadata, when present.""" + + open_graph: Optional[Dict[str, Union[str, List[str]]]] = FieldInfo(alias="openGraph", default=None) + """Open Graph metadata with the og: prefix removed and keys camel-cased.""" + + published_time: Optional[str] = FieldInfo(alias="publishedTime", default=None) + """Published timestamp/date from page metadata, when present.""" + + robots: Optional[str] = None + """Robots meta directive, when present.""" + + site_name: Optional[str] = FieldInfo(alias="siteName", default=None) + """Site or application name from page metadata.""" + + title: Optional[str] = None + """Best title extracted from the page.""" + + twitter: Optional[Dict[str, Union[str, List[str]]]] = None + """Twitter card metadata with the twitter: prefix removed and keys camel-cased.""" + + +class DataOk(BaseModel): + """A page the batch fetched successfully.""" + + final_url: str + """URL the content was read from, after redirects.""" + + http_status: Optional[int] = None + """HTTP status of the final response, when known.""" + + metadata: DataOkMetadata + """Metadata extracted from the scraped page HTML.""" + + status: Literal["ok"] + """The page was scraped.""" + + url: str + """URL as submitted, or as discovered by the crawl.""" + + html: Optional[str] = None + """Raw page HTML. Present on html batches.""" + + item_id: Optional[str] = FieldInfo(alias="itemId", default=None) + """Caller-supplied identifier echoed from submission.""" + + markdown: Optional[str] = None + """Page content as Markdown. Present on markdown batches.""" + + meta: Optional[Dict[str, object]] = None + """Caller-supplied metadata echoed from submission.""" + + +class DataError(BaseModel): + """A page the batch could not fetch.""" + + error_code: str + """Why the page failed.""" + + message: str + """Human-readable failure detail.""" + + status: Literal["error"] + """The page could not be scraped.""" + + url: str + """URL as submitted, or as discovered by the crawl.""" + + item_id: Optional[str] = FieldInfo(alias="itemId", default=None) + """Caller-supplied identifier echoed from submission.""" + + meta: Optional[Dict[str, object]] = None + """Caller-supplied metadata echoed from submission.""" + + +Data: TypeAlias = Annotated[Union[DataOk, DataError], PropertyInfo(discriminator="status")] + + +class KeyMetadata(BaseModel): + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the response status is not 200. + """ + + credits_consumed: int + """The number of credits consumed by this request.""" + + credits_remaining: int + """The number of credits remaining for your organization after this request.""" + + +class BatchGetResultsResponse(BaseModel): + data: Optional[List[Data]] = None + """Result records on this page.""" + + has_more: Optional[bool] = None + """Whether another page is available.""" + + key_metadata: Optional[KeyMetadata] = None + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the + response status is not 200. + """ + + next_cursor: Optional[str] = None + """Cursor for the next page.""" diff --git a/src/context/dev/types/batch_list_params.py b/src/context/dev/types/batch_list_params.py new file mode 100644 index 0000000..276350b --- /dev/null +++ b/src/context/dev/types/batch_list_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["BatchListParams"] + + +class BatchListParams(TypedDict, total=False): + cursor: str + """Cursor from the previous page.""" + + limit: int + """Batches per page. Defaults to 25.""" + + q: str + """ + Free-text search term, matched against the batch id, crawl source (start URL or + sitemap domain), and tags. + """ + + search_type: Literal["exact", "prefix"] + """ + `prefix` for as-you-type prefix matching (default), `exact` for full-token + matching. + """ + + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] + """Filter by status.""" + + tags: str + """Comma-separated list of tags to filter by (matches batches having any of them).""" diff --git a/src/context/dev/types/batch_list_response.py b/src/context/dev/types/batch_list_response.py new file mode 100644 index 0000000..c91eb07 --- /dev/null +++ b/src/context/dev/types/batch_list_response.py @@ -0,0 +1,171 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .error import Error +from .._models import BaseModel +from .error_count import ErrorCount + +__all__ = [ + "BatchListResponse", + "Data", + "DataCredits", + "DataInput", + "DataProgress", + "DataResults", + "DataResultsFile", + "DataTiming", + "KeyMetadata", +] + + +class DataCredits(BaseModel): + """Reserved and used credits.""" + + charged: int + """Credits used by successful pages.""" + + estimated: int + """Credits reserved when the batch was accepted.""" + + +class DataInput(BaseModel): + """Submission counts.""" + + accepted: int + """Pages accepted, or the crawl page limit. Credits are reserved for this count.""" + + duplicates: int + """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls.""" + + invalid: int + """Pages rejected during validation.""" + + submitted: int + """Pages submitted before validation. For a crawl, the page limit.""" + + +class DataProgress(BaseModel): + """Current processing counts. Use `status` to check completion.""" + + failed: int + """Pages that could not be scraped.""" + + pending: int + """Accepted pages not yet attempted. + + Always 0 once the batch completes; a crawl can finish under its page limit when + the site has no more reachable pages. + """ + + succeeded: int + """Pages scraped successfully.""" + + +class DataResultsFile(BaseModel): + bytes: int + """Compressed file size in bytes.""" + + items: int + """Results in this file.""" + + url: str + """Temporary URL for a gzipped NDJSON file.""" + + +class DataResults(BaseModel): + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + expires_at: str + """When the download URLs expire.""" + + files: List[DataResultsFile] + """Result files. Order is not guaranteed.""" + + +class DataTiming(BaseModel): + completed_at: Optional[str] = None + """When processing finished. Null while active.""" + + created_at: str + """When the batch was created.""" + + started_at: Optional[str] = None + """When processing started. Null while queued.""" + + +class Data(BaseModel): + """An asynchronous web scraping job.""" + + id: str + """Batch ID used to retrieve or cancel the job.""" + + credits: DataCredits + """Reserved and used credits.""" + + error: Optional[Error] = None + """Why the batch failed.""" + + errors: List[ErrorCount] + """Page failures grouped by error code.""" + + input: DataInput + """Submission counts.""" + + mode: Literal["scrape", "crawl"] + """How pages are selected.""" + + progress: DataProgress + """Current processing counts. Use `status` to check completion.""" + + results: Optional[DataResults] = None + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] + """Current state. `completed`, `cancelled`, and `failed` are final.""" + + tags: List[str] + """Tags stored on the batch at submission.""" + + timing: DataTiming + + type: Literal["markdown", "html"] + """Output format.""" + + +class KeyMetadata(BaseModel): + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the response status is not 200. + """ + + credits_consumed: int + """The number of credits consumed by this request.""" + + credits_remaining: int + """The number of credits remaining for your organization after this request.""" + + +class BatchListResponse(BaseModel): + data: Optional[List[Data]] = None + """Batches on this page.""" + + has_more: Optional[bool] = None + """Whether another page is available.""" + + key_metadata: Optional[KeyMetadata] = None + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the + response status is not 200. + """ + + next_cursor: Optional[str] = None + """Cursor for the next page.""" diff --git a/src/context/dev/types/batch_retrieve_response.py b/src/context/dev/types/batch_retrieve_response.py new file mode 100644 index 0000000..d2ef3e4 --- /dev/null +++ b/src/context/dev/types/batch_retrieve_response.py @@ -0,0 +1,165 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .error import Error +from .._models import BaseModel +from .error_count import ErrorCount + +__all__ = [ + "BatchRetrieveResponse", + "Credits", + "Input", + "InvalidURL", + "Progress", + "Results", + "ResultsFile", + "Timing", + "KeyMetadata", +] + + +class Credits(BaseModel): + """Reserved and used credits.""" + + charged: int + """Credits used by successful pages.""" + + estimated: int + """Credits reserved when the batch was accepted.""" + + +class Input(BaseModel): + """Submission counts.""" + + accepted: int + """Pages accepted, or the crawl page limit. Credits are reserved for this count.""" + + duplicates: int + """Duplicate URL and `itemId` pairs skipped. Always 0 for crawls.""" + + invalid: int + """Pages rejected during validation.""" + + submitted: int + """Pages submitted before validation. For a crawl, the page limit.""" + + +class InvalidURL(BaseModel): + reason: str + """Why it was rejected.""" + + url: str + """Rejected URL.""" + + +class Progress(BaseModel): + """Current processing counts. Use `status` to check completion.""" + + failed: int + """Pages that could not be scraped.""" + + pending: int + """Accepted pages not yet attempted. + + Always 0 once the batch completes; a crawl can finish under its page limit when + the site has no more reachable pages. + """ + + succeeded: int + """Pages scraped successfully.""" + + +class ResultsFile(BaseModel): + bytes: int + """Compressed file size in bytes.""" + + items: int + """Results in this file.""" + + url: str + """Temporary URL for a gzipped NDJSON file.""" + + +class Results(BaseModel): + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + expires_at: str + """When the download URLs expire.""" + + files: List[ResultsFile] + """Result files. Order is not guaranteed.""" + + +class Timing(BaseModel): + completed_at: Optional[str] = None + """When processing finished. Null while active.""" + + created_at: str + """When the batch was created.""" + + started_at: Optional[str] = None + """When processing started. Null while queued.""" + + +class KeyMetadata(BaseModel): + """API key usage for this request.""" + + credits_consumed: int + """The number of credits consumed by this request.""" + + credits_remaining: int + """The number of credits remaining for your organization after this request.""" + + +class BatchRetrieveResponse(BaseModel): + id: str + """Batch ID used to retrieve or cancel the job.""" + + credits: Credits + """Reserved and used credits.""" + + error: Optional[Error] = None + """Why the batch failed.""" + + errors: List[ErrorCount] + """Page failures grouped by error code.""" + + input: Input + """Submission counts.""" + + invalid_urls: List[InvalidURL] + """Rejected URLs, up to 100. These are not charged.""" + + mode: Literal["scrape", "crawl"] + """How pages are selected.""" + + progress: Progress + """Current processing counts. Use `status` to check completion.""" + + results: Optional[Results] = None + """Download links available when the batch finishes. + + GET /batch/{batch_id}/results serves the same records as paginated JSON. + """ + + status: Literal["queued", "running", "cancelling", "completed", "cancelled", "failed"] + """Current state. `completed`, `cancelled`, and `failed` are final.""" + + tags: List[str] + """Tags stored on the batch at submission.""" + + timing: Timing + + type: Literal["markdown", "html"] + """Output format.""" + + key_metadata: Optional[KeyMetadata] = None + """API key usage for this request.""" + + webhook_secret: Optional[str] = None + """Webhook signing secret. Also returned by GET /batch/{batch_id}.""" diff --git a/src/context/dev/types/batch_submit_params.py b/src/context/dev/types/batch_submit_params.py new file mode 100644 index 0000000..3ae0986 --- /dev/null +++ b/src/context/dev/types/batch_submit_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo + +__all__ = ["BatchSubmitParams", "Identifiers"] + + +class BatchSubmitParams(TypedDict, total=False): + identifiers: Required[Identifiers] + """Known identifiers for the person. At least one identifier is required.""" + + tags: SequenceNotStr[str] + """Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.""" + + 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). + """ + + +class Identifiers(TypedDict, total=False): + """Known identifiers for the person. At least one identifier is required.""" + + linkedin_url: Annotated[str, PropertyInfo(alias="linkedinUrl")] + """LinkedIn profile URL, e.g. https://www.linkedin.com/in/yahia-bakour/.""" diff --git a/src/context/dev/types/batch_submit_response.py b/src/context/dev/types/batch_submit_response.py new file mode 100644 index 0000000..6fd2690 --- /dev/null +++ b/src/context/dev/types/batch_submit_response.py @@ -0,0 +1,267 @@ +# 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__ = [ + "BatchSubmitResponse", + "Metadata", + "MetadataIdentifiers", + "Person", + "PersonEducation", + "PersonEducationInstitution", + "PersonEducationDates", + "PersonEducationDatesEndDate", + "PersonEducationDatesStartDate", + "PersonExperience", + "PersonExperienceCompany", + "PersonExperienceDates", + "PersonExperienceDatesEndDate", + "PersonExperienceDatesStartDate", + "PersonProfile", + "PersonSkill", + "KeyMetadata", +] + + +class MetadataIdentifiers(BaseModel): + """Identifiers returned for the person.""" + + linkedin_url: Optional[str] = FieldInfo(alias="linkedinUrl", default=None) + """LinkedIn profile URL.""" + + +class Metadata(BaseModel): + """Additional response details.""" + + identifiers: MetadataIdentifiers + """Identifiers returned for the person.""" + + sources_attempted: List[Literal["linkedin", "cv", "manual", "github", "other"]] = FieldInfo( + alias="sourcesAttempted" + ) + """Source categories checked.""" + + sources_succeeded: List[Literal["linkedin", "cv", "manual", "github", "other"]] = FieldInfo( + alias="sourcesSucceeded" + ) + """Source categories with data.""" + + urls_analyzed: List[str] = FieldInfo(alias="urlsAnalyzed") + """URLs reviewed for this profile.""" + + personal_website_url: Optional[str] = FieldInfo(alias="personalWebsiteUrl", default=None) + """Personal website URL, when found.""" + + +class PersonEducationInstitution(BaseModel): + """School or institution name.""" + + display: str + """Display name.""" + + normalized: Optional[str] = None + """Standardized name, when available.""" + + +class PersonEducationDatesEndDate(BaseModel): + """End date, when known.""" + + year: int + """Year value.""" + + day: Optional[int] = None + """Day value, when known.""" + + month: Optional[int] = None + """Month value, when known.""" + + +class PersonEducationDatesStartDate(BaseModel): + """Start date, when known.""" + + year: int + """Year value.""" + + day: Optional[int] = None + """Day value, when known.""" + + month: Optional[int] = None + """Month value, when known.""" + + +class PersonEducationDates(BaseModel): + """Education dates.""" + + end_date: Optional[PersonEducationDatesEndDate] = FieldInfo(alias="endDate", default=None) + """End date, when known.""" + + is_current: Optional[bool] = FieldInfo(alias="isCurrent", default=None) + """Whether the entry is current.""" + + start_date: Optional[PersonEducationDatesStartDate] = FieldInfo(alias="startDate", default=None) + """Start date, when known.""" + + +class PersonEducation(BaseModel): + institution: PersonEducationInstitution + """School or institution name.""" + + dates: Optional[PersonEducationDates] = None + """Education dates.""" + + description: Optional[str] = None + """Additional education details.""" + + field_of_study: Optional[str] = FieldInfo(alias="fieldOfStudy", default=None) + """Area of study.""" + + qualification: Optional[str] = None + """Degree, certificate, or credential.""" + + +class PersonExperienceCompany(BaseModel): + """Company or organization name.""" + + display: str + """Display name.""" + + normalized: Optional[str] = None + """Standardized name, when available.""" + + +class PersonExperienceDatesEndDate(BaseModel): + """End date, when known.""" + + year: int + """Year value.""" + + day: Optional[int] = None + """Day value, when known.""" + + month: Optional[int] = None + """Month value, when known.""" + + +class PersonExperienceDatesStartDate(BaseModel): + """Start date, when known.""" + + year: int + """Year value.""" + + day: Optional[int] = None + """Day value, when known.""" + + month: Optional[int] = None + """Month value, when known.""" + + +class PersonExperienceDates(BaseModel): + """Role dates.""" + + end_date: Optional[PersonExperienceDatesEndDate] = FieldInfo(alias="endDate", default=None) + """End date, when known.""" + + is_current: Optional[bool] = FieldInfo(alias="isCurrent", default=None) + """Whether the entry is current.""" + + start_date: Optional[PersonExperienceDatesStartDate] = FieldInfo(alias="startDate", default=None) + """Start date, when known.""" + + +class PersonExperience(BaseModel): + company: PersonExperienceCompany + """Company or organization name.""" + + title: str + """Role or job title.""" + + dates: Optional[PersonExperienceDates] = None + """Role dates.""" + + description: Optional[str] = None + """Role description.""" + + +class PersonProfile(BaseModel): + """Core profile details.""" + + full_name: Optional[str] = FieldInfo(alias="fullName", default=None) + """Person's full name.""" + + headline: Optional[str] = None + """Short professional headline.""" + + location: Optional[str] = None + """Person's listed location.""" + + profile_picture_url: Optional[str] = FieldInfo(alias="profilePictureUrl", default=None) + """Profile image URL.""" + + summary: Optional[str] = None + """Brief profile summary.""" + + +class PersonSkill(BaseModel): + name: str + """Skill name.""" + + normalized: Optional[str] = None + """Standardized skill name, when available.""" + + proficiency: Optional[str] = None + """Skill proficiency, when available.""" + + +class Person(BaseModel): + """Retrieved person profile.""" + + education: List[PersonEducation] + """Education history.""" + + experience: List[PersonExperience] + """Work history.""" + + profile: PersonProfile + """Core profile details.""" + + skills: List[PersonSkill] + """Listed skills.""" + + +class KeyMetadata(BaseModel): + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the response status is not 200. + """ + + credits_consumed: int + """The number of credits consumed by this request.""" + + credits_remaining: int + """The number of credits remaining for your organization after this request.""" + + +class BatchSubmitResponse(BaseModel): + code: Literal[200] + """HTTP status code.""" + + metadata: Metadata + """Additional response details.""" + + person: Person + """Retrieved person profile.""" + + status: Literal["ok"] + """Response status.""" + + key_metadata: Optional[KeyMetadata] = None + """Metadata about the API key used for the request. + + Included in every response whenever a valid API key is provided, even when the + response status is not 200. + """ diff --git a/src/context/dev/types/error.py b/src/context/dev/types/error.py new file mode 100644 index 0000000..c564450 --- /dev/null +++ b/src/context/dev/types/error.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["Error"] + + +class Error(BaseModel): + """Why the batch failed.""" + + code: str + """Batch error code.""" + + message: str + """Batch error message.""" diff --git a/src/context/dev/types/error_count.py b/src/context/dev/types/error_count.py new file mode 100644 index 0000000..783906d --- /dev/null +++ b/src/context/dev/types/error_count.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["ErrorCount"] + + +class ErrorCount(BaseModel): + """Page failures sharing one error code.""" + + code: str + """Error code for these failures.""" + + count: int + """Pages that failed with this code.""" diff --git a/src/context/dev/types/monitor_create_params.py b/src/context/dev/types/monitor_create_params.py index bdf55ea..d621a74 100644 --- a/src/context/dev/types/monitor_create_params.py +++ b/src/context/dev/types/monitor_create_params.py @@ -9,34 +9,27 @@ __all__ = [ "MonitorCreateParams", - "ChangeDetection", - "ChangeDetectionMonitorsExactChangeDetection", - "ChangeDetectionMonitorsSemanticChangeDetection", - "Schedule", "Target", "TargetMonitorsPageTarget", "TargetMonitorsSitemapTarget", "TargetMonitorsExtractTarget", + "ChangeDetection", + "ChangeDetectionMonitorsExactChangeDetection", + "ChangeDetectionMonitorsSemanticChangeDetection", + "Schedule", "Webhook", ] class MonitorCreateParams(TypedDict, total=False): - change_detection: Required[ChangeDetection] - """Discriminated union describing how changes are detected.""" - name: Required[str] - schedule: Required[Schedule] - """Run the monitor on a fixed interval defined by a frequency and a unit, e.g. - - every 6 hours or every 2 days. The total interval (frequency × unit) must be - between 10 minutes and 1 year. - """ - target: Required[Target] """Discriminated union describing what the monitor watches.""" + change_detection: ChangeDetection + """Discriminated union describing how changes are detected.""" + mode: Literal["web"] """Top-level monitor category. @@ -44,6 +37,13 @@ class MonitorCreateParams(TypedDict, total=False): `change_detection`. """ + schedule: Schedule + """Run the monitor on a fixed interval defined by a frequency and a unit, e.g. + + every 6 hours or every 2 days. The total interval (frequency × unit) must be + between 10 minutes and 1 year. + """ + tags: SequenceNotStr[str] """User-defined tags for grouping and filtering monitors and their changes. @@ -53,56 +53,22 @@ class MonitorCreateParams(TypedDict, total=False): webhook: Optional[Webhook] -class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False): - """Detect exact changes. - - For page targets, this means visible text diffs. For sitemap targets, this means URL additions and removals. - """ - - type: Required[Literal["exact"]] - +class TargetMonitorsPageTarget(TypedDict, total=False): + """Watch a single web page. -class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False): - """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. """ - type: Required[Literal["semantic"]] - - confidence_threshold: float - - -ChangeDetection: TypeAlias = Union[ - ChangeDetectionMonitorsExactChangeDetection, ChangeDetectionMonitorsSemanticChangeDetection -] + type: Required[Literal["page"]] + url: Required[str] -class Schedule(TypedDict, total=False): - """Run the monitor on a fixed interval defined by a frequency and a unit, e.g. + instructions: str + """Plain-language goal describing which page changes matter. - every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year. + When provided without change_detection, semantic detection is inferred. """ - frequency: Required[int] - """Number of units between runs. - - The resulting interval (frequency × unit) must be at least 10 minutes and at - most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is - days). - """ - - type: Required[Literal["interval"]] - - unit: Required[Literal["minutes", "hours", "days"]] - - -class TargetMonitorsPageTarget(TypedDict, total=False): - """Watch a single web page.""" - - type: Required[Literal["page"]] - - url: Required[str] - normalize_whitespace: bool """Normalize whitespace before comparing or analyzing text.""" @@ -169,6 +135,49 @@ class TargetMonitorsExtractTarget(TypedDict, total=False): Target: TypeAlias = Union[TargetMonitorsPageTarget, TargetMonitorsSitemapTarget, TargetMonitorsExtractTarget] +class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False): + """Detect exact changes. + + For page targets, this means visible text diffs. For sitemap targets, this means URL additions and removals. + """ + + type: Required[Literal["exact"]] + + +class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False): + """ + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). + """ + + type: Required[Literal["semantic"]] + + confidence_threshold: float + + +ChangeDetection: TypeAlias = Union[ + ChangeDetectionMonitorsExactChangeDetection, ChangeDetectionMonitorsSemanticChangeDetection +] + + +class Schedule(TypedDict, total=False): + """Run the monitor on a fixed interval defined by a frequency and a unit, e.g. + + every 6 hours or every 2 days. The total interval (frequency × unit) must be between 10 minutes and 1 year. + """ + + frequency: Required[int] + """Number of units between runs. + + The resulting interval (frequency × unit) must be at least 10 minutes and at + most 1 year (e.g. minimum 10 when unit is minutes; maximum 365 when unit is + days). + """ + + type: Required[Literal["interval"]] + + unit: Required[Literal["minutes", "hours", "days"]] + + class Webhook(TypedDict, total=False): url: Required[str] """Webhook URL events are delivered to.""" diff --git a/src/context/dev/types/monitor_create_response.py b/src/context/dev/types/monitor_create_response.py index 0a23ad1..cb47069 100644 --- a/src/context/dev/types/monitor_create_response.py +++ b/src/context/dev/types/monitor_create_response.py @@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel): class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel): """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). """ type: Literal["semantic"] @@ -74,12 +74,21 @@ class Schedule(BaseModel): class TargetMonitorsPageTarget(BaseModel): - """Watch a single web page.""" + """Watch a single web page. + + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. + """ type: Literal["page"] url: str + instructions: Optional[str] = None + """Plain-language goal describing which page changes matter. + + When provided without change_detection, semantic detection is inferred. + """ + normalize_whitespace: Optional[bool] = None """Normalize whitespace before comparing or analyzing text.""" @@ -251,9 +260,8 @@ class WebhookFailure(BaseModel): class MonitorCreateResponse(BaseModel): - """A web monitor. - - `mode` is the constant `web`; behavior is described by `target` (page/sitemap/extract) and `change_detection` (exact/semantic). + """ + A newly created monitor plus `initial_run_id`, the id of the baseline run queued at creation. """ id: str @@ -263,6 +271,13 @@ class MonitorCreateResponse(BaseModel): created_at: datetime + initial_run_id: Optional[str] = None + """ + The baseline run queued by this create call, or null if it could not be queued + immediately (in which case the baseline runs on the next scheduled tick). Poll + GET /monitors/{monitor_id}/runs/{run_id}. + """ + mode: Literal["web"] """Top-level monitor category. diff --git a/src/context/dev/types/monitor_list_response.py b/src/context/dev/types/monitor_list_response.py index 475b767..81d9c81 100644 --- a/src/context/dev/types/monitor_list_response.py +++ b/src/context/dev/types/monitor_list_response.py @@ -41,7 +41,7 @@ class DataChangeDetectionMonitorsExactChangeDetection(BaseModel): class DataChangeDetectionMonitorsSemanticChangeDetection(BaseModel): """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). """ type: Literal["semantic"] @@ -75,12 +75,21 @@ class DataSchedule(BaseModel): class DataTargetMonitorsPageTarget(BaseModel): - """Watch a single web page.""" + """Watch a single web page. + + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. + """ type: Literal["page"] url: str + instructions: Optional[str] = None + """Plain-language goal describing which page changes matter. + + When provided without change_detection, semantic detection is inferred. + """ + normalize_whitespace: Optional[bool] = None """Normalize whitespace before comparing or analyzing text.""" diff --git a/src/context/dev/types/monitor_retrieve_response.py b/src/context/dev/types/monitor_retrieve_response.py index e47be52..d96cd96 100644 --- a/src/context/dev/types/monitor_retrieve_response.py +++ b/src/context/dev/types/monitor_retrieve_response.py @@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel): class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel): """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). """ type: Literal["semantic"] @@ -74,12 +74,21 @@ class Schedule(BaseModel): class TargetMonitorsPageTarget(BaseModel): - """Watch a single web page.""" + """Watch a single web page. + + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. + """ type: Literal["page"] url: str + instructions: Optional[str] = None + """Plain-language goal describing which page changes matter. + + When provided without change_detection, semantic detection is inferred. + """ + normalize_whitespace: Optional[bool] = None """Normalize whitespace before comparing or analyzing text.""" diff --git a/src/context/dev/types/monitor_update_params.py b/src/context/dev/types/monitor_update_params.py index da61ef0..ad9e94b 100644 --- a/src/context/dev/types/monitor_update_params.py +++ b/src/context/dev/types/monitor_update_params.py @@ -60,7 +60,7 @@ class ChangeDetectionMonitorsExactChangeDetection(TypedDict, total=False): class ChangeDetectionMonitorsSemanticChangeDetection(TypedDict, total=False): """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). """ type: Required[Literal["semantic"]] @@ -93,12 +93,21 @@ class Schedule(TypedDict, total=False): class TargetMonitorsPageTarget(TypedDict, total=False): - """Watch a single web page.""" + """Watch a single web page. + + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. + """ type: Required[Literal["page"]] url: Required[str] + instructions: str + """Plain-language goal describing which page changes matter. + + When provided without change_detection, semantic detection is inferred. + """ + normalize_whitespace: bool """Normalize whitespace before comparing or analyzing text.""" diff --git a/src/context/dev/types/monitor_update_response.py b/src/context/dev/types/monitor_update_response.py index 61ee9ef..0d70014 100644 --- a/src/context/dev/types/monitor_update_response.py +++ b/src/context/dev/types/monitor_update_response.py @@ -40,7 +40,7 @@ class ChangeDetectionMonitorsExactChangeDetection(BaseModel): class ChangeDetectionMonitorsSemanticChangeDetection(BaseModel): """ - Detect meaning-level changes to tracked page content, ignoring cosmetic or paraphrase-only differences. Which changes are meaningful is judged against the extract target's `instructions` (and `schema`, when provided). + Detect meaning-level changes to page content, ignoring cosmetic or instruction-irrelevant differences. Which changes are meaningful is judged against the page or extract target's `instructions` (and an extract target's `schema`, when provided). """ type: Literal["semantic"] @@ -74,12 +74,21 @@ class Schedule(BaseModel): class TargetMonitorsPageTarget(BaseModel): - """Watch a single web page.""" + """Watch a single web page. + + Exact detection reports visible-text diffs; semantic detection judges confirmed stable diffs against `instructions`. + """ type: Literal["page"] url: str + instructions: Optional[str] = None + """Plain-language goal describing which page changes matter. + + When provided without change_detection, semantic detection is inferred. + """ + normalize_whitespace: Optional[bool] = None """Normalize whitespace before comparing or analyzing text.""" diff --git a/src/context/dev/types/web_web_scrape_html_response.py b/src/context/dev/types/web_web_scrape_html_response.py index a1d07e8..6ea2dd9 100644 --- a/src/context/dev/types/web_web_scrape_html_response.py +++ b/src/context/dev/types/web_web_scrape_html_response.py @@ -7,7 +7,7 @@ from .._models import BaseModel -__all__ = ["WebWebScrapeHTMLResponse", "Metadata", "MetadataAlternate", "KeyMetadata"] +__all__ = ["WebWebScrapeHTMLResponse", "Metadata", "MetadataAlternate", "ActionsApplied", "KeyMetadata"] class MetadataAlternate(BaseModel): @@ -88,6 +88,27 @@ class Metadata(BaseModel): """Twitter card metadata with the twitter: prefix removed and keys camel-cased.""" +class ActionsApplied(BaseModel): + instruction: str + + status: Literal["applied", "failed", "skipped"] + """Applied means the requested page state was visibly verified. + + Failed means it was not verified. Skipped means it was not attempted. + """ + + completion_evidence: Optional[str] = FieldInfo(alias="completionEvidence", default=None) + """Visible page evidence used to verify an applied action.""" + + duration_ms: Optional[float] = FieldInfo(alias="durationMs", default=None) + + error: Optional[str] = None + + method: Optional[str] = None + + target_description: Optional[str] = FieldInfo(alias="targetDescription", default=None) + + class KeyMetadata(BaseModel): """Metadata about the API key used for the request. @@ -130,6 +151,15 @@ class WebWebScrapeHTMLResponse(BaseModel): url: str """The URL that was scraped""" + actions_applied: Optional[List[ActionsApplied]] = FieldInfo(alias="actionsApplied", default=None) + """One verified outcome per requested browser action, in request order.""" + + actions_html_stale: Optional[bool] = FieldInfo(alias="actionsHtmlStale", default=None) + """ + True when an action was applied but the returned content could not be refreshed + afterward. + """ + key_metadata: Optional[KeyMetadata] = None """Metadata about the API key used for the request. diff --git a/src/context/dev/types/web_web_scrape_md_response.py b/src/context/dev/types/web_web_scrape_md_response.py index 1005d9e..e02866b 100644 --- a/src/context/dev/types/web_web_scrape_md_response.py +++ b/src/context/dev/types/web_web_scrape_md_response.py @@ -7,7 +7,7 @@ from .._models import BaseModel -__all__ = ["WebWebScrapeMdResponse", "Metadata", "MetadataAlternate", "KeyMetadata"] +__all__ = ["WebWebScrapeMdResponse", "Metadata", "MetadataAlternate", "ActionsApplied", "KeyMetadata"] class MetadataAlternate(BaseModel): @@ -88,6 +88,27 @@ class Metadata(BaseModel): """Twitter card metadata with the twitter: prefix removed and keys camel-cased.""" +class ActionsApplied(BaseModel): + instruction: str + + status: Literal["applied", "failed", "skipped"] + """Applied means the requested page state was visibly verified. + + Failed means it was not verified. Skipped means it was not attempted. + """ + + completion_evidence: Optional[str] = FieldInfo(alias="completionEvidence", default=None) + """Visible page evidence used to verify an applied action.""" + + duration_ms: Optional[float] = FieldInfo(alias="durationMs", default=None) + + error: Optional[str] = None + + method: Optional[str] = None + + target_description: Optional[str] = FieldInfo(alias="targetDescription", default=None) + + class KeyMetadata(BaseModel): """Metadata about the API key used for the request. @@ -121,6 +142,15 @@ class WebWebScrapeMdResponse(BaseModel): url: str """The URL that was scraped""" + actions_applied: Optional[List[ActionsApplied]] = FieldInfo(alias="actionsApplied", default=None) + """One verified outcome per requested browser action, in request order.""" + + actions_html_stale: Optional[bool] = FieldInfo(alias="actionsHtmlStale", default=None) + """ + True when an action was applied but the returned content could not be refreshed + afterward. + """ + key_metadata: Optional[KeyMetadata] = None """Metadata about the API key used for the request. diff --git a/tests/api_resources/test_batch.py b/tests/api_resources/test_batch.py new file mode 100644 index 0000000..2cce1cf --- /dev/null +++ b/tests/api_resources/test_batch.py @@ -0,0 +1,472 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from context.dev import ContextDev, AsyncContextDev +from tests.utils import assert_matches_type +from context.dev.types import ( + BatchListResponse, + BatchCancelResponse, + BatchSubmitResponse, + BatchRetrieveResponse, + BatchGetResultsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBatch: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: ContextDev) -> None: + batch = client.batch.retrieve( + "batch_9f2c8a", + ) + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: ContextDev) -> None: + response = client.batch.with_raw_response.retrieve( + "batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: ContextDev) -> None: + with client.batch.with_streaming_response.retrieve( + "batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: ContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + client.batch.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: ContextDev) -> None: + batch = client.batch.list() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: ContextDev) -> None: + batch = client.batch.list( + cursor="cursor", + limit=1, + q="batch_1a2b", + search_type="exact", + status="queued", + tags="docs,competitor", + ) + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: ContextDev) -> None: + response = client.batch.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: ContextDev) -> None: + with client.batch.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_cancel(self, client: ContextDev) -> None: + batch = client.batch.cancel( + "batch_9f2c8a", + ) + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_cancel(self, client: ContextDev) -> None: + response = client.batch.with_raw_response.cancel( + "batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_cancel(self, client: ContextDev) -> None: + with client.batch.with_streaming_response.cancel( + "batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_cancel(self, client: ContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + client.batch.with_raw_response.cancel( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get_results(self, client: ContextDev) -> None: + batch = client.batch.get_results( + batch_id="batch_9f2c8a", + ) + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get_results_with_all_params(self, client: ContextDev) -> None: + batch = client.batch.get_results( + batch_id="batch_9f2c8a", + cursor="cursor", + limit=1, + ) + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_get_results(self, client: ContextDev) -> None: + response = client.batch.with_raw_response.get_results( + batch_id="batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_get_results(self, client: ContextDev) -> None: + with client.batch.with_streaming_response.get_results( + batch_id="batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_get_results(self, client: ContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + client.batch.with_raw_response.get_results( + batch_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_submit(self, client: ContextDev) -> None: + batch = client.batch.submit( + identifiers={}, + ) + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_submit_with_all_params(self, client: ContextDev) -> None: + batch = client.batch.submit( + identifiers={"linkedin_url": "https://www.linkedin.com/in/yahia-bakour/"}, + tags=["production", "team-alpha"], + timeout_ms=1000, + ) + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_submit(self, client: ContextDev) -> None: + response = client.batch.with_raw_response.submit( + identifiers={}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_submit(self, client: ContextDev) -> None: + with client.batch.with_streaming_response.submit( + identifiers={}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncBatch: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.retrieve( + "batch_9f2c8a", + ) + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncContextDev) -> None: + response = await async_client.batch.with_raw_response.retrieve( + "batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = await response.parse() + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncContextDev) -> None: + async with async_client.batch.with_streaming_response.retrieve( + "batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(BatchRetrieveResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + await async_client.batch.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.list() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.list( + cursor="cursor", + limit=1, + q="batch_1a2b", + search_type="exact", + status="queued", + tags="docs,competitor", + ) + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncContextDev) -> None: + response = await async_client.batch.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = await response.parse() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncContextDev) -> None: + async with async_client.batch.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(BatchListResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_cancel(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.cancel( + "batch_9f2c8a", + ) + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncContextDev) -> None: + response = await async_client.batch.with_raw_response.cancel( + "batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = await response.parse() + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncContextDev) -> None: + async with async_client.batch.with_streaming_response.cancel( + "batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(BatchCancelResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_cancel(self, async_client: AsyncContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + await async_client.batch.with_raw_response.cancel( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get_results(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.get_results( + batch_id="batch_9f2c8a", + ) + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get_results_with_all_params(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.get_results( + batch_id="batch_9f2c8a", + cursor="cursor", + limit=1, + ) + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_get_results(self, async_client: AsyncContextDev) -> None: + response = await async_client.batch.with_raw_response.get_results( + batch_id="batch_9f2c8a", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = await response.parse() + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_get_results(self, async_client: AsyncContextDev) -> None: + async with async_client.batch.with_streaming_response.get_results( + batch_id="batch_9f2c8a", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(BatchGetResultsResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_get_results(self, async_client: AsyncContextDev) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + await async_client.batch.with_raw_response.get_results( + batch_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_submit(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.submit( + identifiers={}, + ) + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_submit_with_all_params(self, async_client: AsyncContextDev) -> None: + batch = await async_client.batch.submit( + identifiers={"linkedin_url": "https://www.linkedin.com/in/yahia-bakour/"}, + tags=["production", "team-alpha"], + timeout_ms=1000, + ) + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_submit(self, async_client: AsyncContextDev) -> None: + response = await async_client.batch.with_raw_response.submit( + identifiers={}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = await response.parse() + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_submit(self, async_client: AsyncContextDev) -> None: + async with async_client.batch.with_streaming_response.submit( + identifiers={}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(BatchSubmitResponse, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_monitors.py b/tests/api_resources/test_monitors.py index 14ee10b..8a438f7 100644 --- a/tests/api_resources/test_monitors.py +++ b/tests/api_resources/test_monitors.py @@ -36,13 +36,7 @@ class TestMonitors: @parametrize def test_method_create(self, client: ContextDev) -> None: monitor = client.monitors.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -54,19 +48,20 @@ def test_method_create(self, client: ContextDev) -> None: @parametrize def test_method_create_with_all_params(self, client: ContextDev) -> None: monitor = client.monitors.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", + "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.", "normalize_whitespace": True, }, + change_detection={"type": "exact"}, mode="web", + schedule={ + "frequency": 6, + "type": "interval", + "unit": "hours", + }, tags=["pricing", "competitor"], webhook={ "url": "https://example.com/webhook", @@ -79,13 +74,7 @@ def test_method_create_with_all_params(self, client: ContextDev) -> None: @parametrize def test_raw_response_create(self, client: ContextDev) -> None: response = client.monitors.with_raw_response.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -101,13 +90,7 @@ def test_raw_response_create(self, client: ContextDev) -> None: @parametrize def test_streaming_response_create(self, client: ContextDev) -> None: with client.monitors.with_streaming_response.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -188,6 +171,7 @@ def test_method_update_with_all_params(self, client: ContextDev) -> None: target={ "type": "page", "url": "https://acme.com/pricing", + "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.", "normalize_whitespace": True, }, webhook={ @@ -666,13 +650,7 @@ class TestAsyncMonitors: @parametrize async def test_method_create(self, async_client: AsyncContextDev) -> None: monitor = await async_client.monitors.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -684,19 +662,20 @@ async def test_method_create(self, async_client: AsyncContextDev) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncContextDev) -> None: monitor = await async_client.monitors.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", + "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.", "normalize_whitespace": True, }, + change_detection={"type": "exact"}, mode="web", + schedule={ + "frequency": 6, + "type": "interval", + "unit": "hours", + }, tags=["pricing", "competitor"], webhook={ "url": "https://example.com/webhook", @@ -709,13 +688,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncContextDev @parametrize async def test_raw_response_create(self, async_client: AsyncContextDev) -> None: response = await async_client.monitors.with_raw_response.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -731,13 +704,7 @@ async def test_raw_response_create(self, async_client: AsyncContextDev) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncContextDev) -> None: async with async_client.monitors.with_streaming_response.create( - change_detection={"type": "exact"}, name="Acme pricing page", - schedule={ - "frequency": 6, - "type": "interval", - "unit": "hours", - }, target={ "type": "page", "url": "https://acme.com/pricing", @@ -818,6 +785,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncContextDev target={ "type": "page", "url": "https://acme.com/pricing", + "instructions": "Report pricing or plan availability changes. Ignore counters, timestamps, testimonials, and navigation.", "normalize_whitespace": True, }, webhook={