diff --git a/.agents/skills/generate-sdk-and-open-pr/SKILL.md b/.agents/skills/generate-sdk-and-open-pr/SKILL.md
index ff95852..ae08df4 100644
--- a/.agents/skills/generate-sdk-and-open-pr/SKILL.md
+++ b/.agents/skills/generate-sdk-and-open-pr/SKILL.md
@@ -301,18 +301,18 @@ grep -rl "YOU_API_KEY_AUTH" --include="*.py" --include="*.md" . | grep -v __pyca
# in the fallback precedence.
```
-#### 4i-2. Verify server URLs (do NOT change search/contents URLs)
+#### 4i-2. Verify server URLs
-The OpenAPI specs for search and contents use `https://ydc-index.io` as the server URL. This is correct and documented at `you.com/docs/api-reference/search/v1-search` (the page explicitly shows `GET https://ydc-index.io/v1/search`). The `api.you.com` host is a free MCP-only proxy (`/v1/agents/search`, 100 searches/day, IP-tracked) — the SDK should NOT use it for search or contents.
+The OpenAPI specs for search and contents use `https://api.you.com` as the server URL. This is the canonical host for the SDK and is documented at `you.com/docs/api-reference/search/v1-search`.
-**Verify** (do not change) that these files still have `ydc-index.io`:
+**Verify** (do not change) that these files still have `api.you.com`:
```bash
-grep "ydc-index.io" src/youdotcom/models/searchop.py src/youdotcom/models/searchpostop.py src/youdotcom/models/contentsop.py
-# All three should show "https://ydc-index.io"
+grep "api.you.com" src/youdotcom/models/searchop.py src/youdotcom/models/searchpostop.py src/youdotcom/models/contentsop.py
+# All three should show "https://api.you.com"
```
-The base `SERVERS` in `src/youdotcom/sdkconfiguration.py` should remain `https://api.you.com` (used by research, finance_research, agents).
+The base `SERVERS` in `src/youdotcom/sdkconfiguration.py` should also remain `https://api.you.com` (used by all endpoints: search, contents, research, finance_research, agents).
#### 4i-3. Preserve and verify hand-maintained files
@@ -412,7 +412,7 @@ Speakeasy assembles per-parameter `example` values into one combined request. Fo
- `boost_domains` **cannot** be combined with `include_domains` (returns `422`).
- `exclude_domains` + `boost_domains` **is** valid.
-After every regen, grep the lead Search examples in `USAGE.md` and `README.md` (specifically the `` blocks) to confirm no `search_post`/`search.unified` example combines all three of `include_domains`, `exclude_domains`, and `boost_domains`:
+After every regen, grep the lead Search examples in `USAGE.md` and `README.md` (specifically the `` blocks) to confirm no `search` example combines all three of `include_domains`, `exclude_domains`, and `boost_domains`:
```bash
# Each occurrence with all three is a bug — drop include_domains (keep
@@ -424,11 +424,11 @@ grep -nE 'include_domains=\[' USAGE.md README.md
The long-term fix lives upstream: add a request-level `example` block on `SearchRequestBody` / `SearchRequest` in `overlays/python_overlay.yaml` (or the front-end OpenAPI specs) that uses a single valid pair, so Speakeasy prefers that example instead of concatenating per-field ones. Track that as a follow-up; the hand-fix above is what keeps 2.4.0 correct in the meantime.
-Also scan for the `RetryConfig(...)` positional-after-kwargs regression that Speakeasy can produce when `search_post` is the lead example operation:
+Also scan for the `RetryConfig(...)` positional-after-kwargs regression that Speakeasy can produce when `search` is the lead example operation:
```bash
# If you see ", RetryConfig(...)" or similar after a keyword argument in a
-# search_post example, the generated Python is a SyntaxError. Fix by
+# search example, the generated Python is a SyntaxError. Fix by
# passing `retries=RetryConfig(...)`.
grep -nE ', RetryConfig\(' README.md USAGE.md
```
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1c0689c..3ca03e7 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -28,8 +28,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -e ".[dev]"
- pip install pytest pytest-asyncio
+ pip install -e .
+ pip install "mypy>=2.3.0" "pytest>=9.0.0" "pytest-asyncio>=1.0.0"
- name: Build and start mock server
working-directory: tests/mockserver
@@ -40,4 +40,7 @@ jobs:
curl -sf http://localhost:18080/ || echo "mock server ready"
- name: Run tests
- run: pytest tests/ -v --tb=short -x
+ run: pytest tests/ -v --tb=short -x --ignore=tests/test_live.py
+
+ - name: Run mypy
+ run: mypy src/youdotcom/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75363e7..7296a55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,46 @@ All notable changes to the You.com Python SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.6.0] - 2026-08-04
+
+### Breaking Changes
+
+- **Sub-SDK Removal**: The sub-SDKs (`Agents`, `Search`, `ContentsSDK`) and the `search_helpers` module have been removed. Methods are now direct on the `You` class. The `you.agents`, `you.search`, and `you.contents` attributes no longer resolve to sub-SDK objects — they are now method calls. `you.search.unified()` (GET `/v1/search`) has been removed; use `you.search()` (POST `/v1/agents/search`) instead. The standalone `search_helpers.search()` function has been merged into `you.search()`. The old→new mapping:
+
+| Old (removed) | New |
+|---------------|-----|
+| `you.agents.runs.create(request=...)` | `you.agents(request=...)` |
+| `you.agents.runs.create_async(request=...)` | `you.agents_async(request=...)` |
+| `you.search.unified(query=...)` | `you.search(query=...)` (POST `/v1/agents/search`) |
+| `you.search_post(query=...)` | `you.search(query=...)` |
+| `you.search_post_async(query=...)` | `you.search_async(query=...)` |
+| `you.contents.generate(urls=...)` | `you.contents(urls=...)` |
+| `you.contents.generate_async(urls=...)` | `you.contents_async(urls=...)` |
+| `search_helpers.search(client, query=...)` | `you.search(query=...)` |
+| `search_helpers.search_async(client, query=...)` | `you.search_async(query=...)` |
+
+Accessing `you.agents`, `you.search`, or `you.contents` as attributes will now fail at runtime — update to the direct method calls above.
+
+### Added
+
+- **Answer API**: New direct method `you.answer()` / `you.answer_async()` for `POST /v1/answer`. Returns a synthesized markdown answer with inline citations (`[[1, 2]]`), a citations array (source URLs + supporting excerpts), and web results. Accepts `query` (required), `freshness`, `country`, `language`, `include_domains`, `exclude_domains`, `boost_domains`. Requires an API key. Country and language accept plain strings (e.g. `"us"`, `"en"`) and are normalized to uppercase automatically.
+- **Keyless search**: `you.search()` / `you.search_async()` now target `POST /v1/agents/search` on `api.you.com` — the keyless-capable proxy. With no API key, runs in the free tier (100 queries/day, count ≤ 50, no livecrawl). With a key, the proxy forwards to the full search endpoint. Country and language strings are normalized to uppercase. The standalone `search_helpers` module has been removed; its functionality is now a direct method on `You`.
+- **`PaymentRequiredResponseError`**: New first-class error class for HTTP 402 responses, matching the `UpgradeRequiredResponse` schema (`error`, `message`, `upgrade_url`, `limit`, `used`, `period`, `reset_at`). Shared by both search and answer 402 handlers. Replaces the previous `FreeTierLimitError`.
+
+### Changed
+
+- **Direct methods on `You`**: The sub-SDK access patterns (`you.agents.runs.create()`, `you.search.unified()`, `you.contents.generate()`) and the direct aliases (`you.create_run()`, `you.search_unified()`, `you.generate_contents()`, `you.search_post()`) from the prior deprecation have all been replaced with the final direct method names: `you.agents()`, `you.search()`, `you.contents()` (plus async variants). See the Breaking Changes table above.
+- **Search/Contents host**: `SEARCH_OP_SERVERS`, `SEARCH_POST_OP_SERVERS`, and `CONTENTS_OP_SERVERS` changed from `https://ydc-index.io` to `https://api.you.com` to align with the MCP server and published docs. `you.search()` targets the keyless-capable proxy at `api.you.com/v1/agents/search`.
+- **422 error data model**: `UnprocessableEntityResponseErrorData` now includes optional `detail` (FastAPI validation array) and `errors` (JSON:API array) fields in addition to the existing `error` field. All three 422 response shapes deserialize without crashing. Backward compatible — existing code accessing `.error` still works.
+- **500 error data model**: `InternalServerErrorResponseData` now includes an optional `errors` field for JSON:API format 500 responses. Backward compatible.
+- **No longer generated by Speakeasy**: Removed all "Code generated by Speakeasy — DO NOT EDIT" disclaimers and the Speakeasy badge from the README. The SDK is now hand-maintained.
+- **Removed `YDCUserAgentOverrideHook`**: The hook existed to rewrite Speakeasy's default UA (`speakeasy-sdk/python ...`) to `youdotcom-python-sdk/{version}`. Now that `__user_agent__` is already `youdotcom-python-sdk/{version}`, `BaseSDK._build_request` sets it directly — the hook was a no-op. Integrations that need a custom UA still just set `client.sdk_configuration.user_agent`.
+- **`__user_agent__` derived from resolved `__version__`**: The user-agent string is now built from the package's resolved version at runtime rather than a hardcoded value.
+- **Dead code removal**: Deleted `_hooks/registration.py` (no-op `init_hooks`), `models/answerop.py` (unused `ANSWER_OP_SERVERS`), unused `importlib` and `TYPE_CHECKING` imports from `sdk.py`.
+- **`search()` server_url fix**: `search()` and `search_async()` now use `self._get_url(None, None)` instead of hardcoded `SEARCH_POST_OP_SERVERS[0]`, so custom `server_url` passed to `You()` is respected (previously ignored for search only).
+- **`answer()` type annotations**: `country` and `language` parameters changed from `Optional[str]` to `Optional[models.Country]` / `Optional[models.Language]` to match `search()` and reflect the actual enum constraint. String values are still accepted and normalized via `.upper()`.
+- **Dev dependencies updated**: mypy `1.15.0` → `>=2.3.0`, pylint `3.2.3` → `>=4.0.0`, pytest floor `>=8.0.0` → `>=9.0.0`, pytest-asyncio floor `>=0.24.0` → `>=1.0.0`. Runtime dependencies (httpx, httpcore, pydantic) unchanged — already at latest stable.
+
## [2.5.0] - 2026-07-20
### Added
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d585717..b376021 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributing to This Repository
-Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements.
+Thank you for your interest in contributing to the You.com Python SDK! This SDK is hand-maintained (not generated) and we welcome pull requests.
## How to Report Issues
@@ -13,9 +13,29 @@ If you encounter any bugs or have suggestions for improvements, please open an i
- Information about your environment (e.g., operating system, software versions)
- For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed
-## Issue Triage and Upstream Fixes
+## Pull Requests
-We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code.
+1. Fork the repository and create a branch from `main`.
+2. Make your changes. Follow existing code style and patterns.
+3. Add or update tests as needed. Run `pytest tests/ --ignore=tests/test_live.py` for unit tests (live tests require `YDC_API_KEY`).
+4. Run `mypy src/youdotcom/` to ensure type safety.
+5. Update documentation (README, CHANGELOG, `docs/` directory) if your change adds or modifies public API surface.
+6. Open a pull request with a clear description of the change.
+
+## Development Setup
+
+```bash
+python -m venv .venv
+source .venv/bin/activate
+pip install -e .
+pip install mypy pylint pyright pytest pytest-asyncio
+```
+
+Or with [uv](https://docs.astral.sh/uv/):
+
+```bash
+uv sync --dev
+```
## Contact
diff --git a/MIGRATION.md b/MIGRATION.md
index 1a52ebd..3a13fa0 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,6 +1,89 @@
# Migration Guide
-## 2.4.0 → 2.5.0 (Latest)
+## 2.5.0 → 2.6.0 (major version)
+
+> **This is a major version release with breaking changes.** Sub-SDKs have been removed and methods are now direct on the `You` class. Update your code before upgrading.
+
+### Answer API
+
+New direct method `you.answer()` for `POST /v1/answer`:
+
+```python
+import os
+from youdotcom import You
+
+with You(api_key_auth=os.getenv("YDC_API_KEY")) as you:
+ res = you.answer(query="What causes the 2008 financial crisis?")
+ print(res.answer) # markdown with [[1, 2]] citations
+ print(res.citations[0].source) # source URL
+ print(res.results.web[0].title) # web result title
+```
+
+Requires an API key. `country` and `language` accept plain strings (e.g. `"us"`, `"en"`) and are normalized to uppercase.
+
+### Keyless Search
+
+`you.search()` / `you.search_async()` target `POST /v1/agents/search` on `api.you.com` — the keyless-capable proxy. No API key required for the free tier (100 queries/day, count ≤ 50, no livecrawl). With a key, the proxy forwards to the full search endpoint.
+
+The standalone `search_helpers` module has been removed. Its `search()` / `search_async()` functions are now direct methods on `You`:
+
+```python
+# Before (2.5.x): from youdotcom.search_helpers import search
+# search(you, query="...")
+
+# After (2.6.0):
+you.search(query="...")
+```
+
+### Host Change: ydc-index.io → api.you.com
+
+`SEARCH_OP_SERVERS`, `SEARCH_POST_OP_SERVERS`, and `CONTENTS_OP_SERVERS` changed from `https://ydc-index.io` to `https://api.you.com`. This aligns with the MCP server and published docs. No code changes required — the SDK resolves the server automatically.
+
+### FreeTierLimitError → PaymentRequiredResponseError
+
+The standalone `FreeTierLimitError` exception has been replaced with the first-class `PaymentRequiredResponseError` (extends `YouError`). The new error provides structured data:
+
+```python
+from youdotcom import You
+from youdotcom.errors import PaymentRequiredResponseError
+
+with You() as you:
+ try:
+ you.search(query="test", count=100) # exceeds free tier
+ except PaymentRequiredResponseError as e:
+ print(e.data.message) # "Insufficient credits"
+ print(e.data.upgrade_url) # "https://you.com/platform"
+ print(e.data.limit) # 100
+ print(e.data.reset_at) # "2026-08-05T00:00:00Z"
+```
+
+### 422/500 Error Models Expanded
+
+`UnprocessableEntityResponseErrorData` now includes optional `detail` (FastAPI validation array) and `errors` (JSON:API array) fields in addition to the existing `error` field. `InternalServerErrorResponseData` now includes an optional `errors` field. These are additive — existing code accessing `.error` or `.detail` still works.
+
+### No Longer Generated by Speakeasy
+
+The SDK is now hand-maintained. All "Code generated by Speakeasy — DO NOT EDIT" disclaimers have been removed. The `__gen_version__` / `SPEAKEASY_GENERATOR_VERSION` exports have been removed.
+
+### Sub-SDK Removal (Breaking)
+
+The sub-SDKs (`Agents`, `Search`, `ContentsSDK`) and the `search_helpers` module have been removed. The `you.agents`, `you.search`, and `you.contents` attributes no longer resolve to sub-SDK objects — they are now method calls. Methods are now direct on the `You` class:
+
+| Old (removed) | New |
+|---------------|-----|
+| `you.agents.runs.create(request=...)` | `you.agents(request=...)` |
+| `you.agents.runs.create_async(request=...)` | `you.agents_async(request=...)` |
+| `you.search.unified(query=...)` | `you.search(query=...)` (POST `/v1/agents/search`) |
+| `you.search_post(query=...)` | `you.search(query=...)` |
+| `you.search_post_async(query=...)` | `you.search_async(query=...)` |
+| `you.contents.generate(urls=...)` | `you.contents(urls=...)` |
+| `you.contents.generate_async(urls=...)` | `you.contents_async(urls=...)` |
+| `search_helpers.search(client, query=...)` | `you.search(query=...)` |
+| `search_helpers.search_async(client, query=...)` | `you.search_async(query=...)` |
+
+`GET /v1/search` (`you.search.unified()`) has been removed. Use `POST /v1/agents/search` (`you.search()`) instead. Accessing `you.agents`, `you.search`, or `you.contents` as sub-SDK attributes will now fail at runtime — update to the direct method calls above. Importing from `youdotcom.search_helpers` will also fail — use `you.search()` directly.
+
+## 2.4.0 → 2.5.0
### New `frontier` Research Effort Tier
diff --git a/README.md b/README.md
index d9d0461..1291f83 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,6 @@ The official developer-friendly & type-safe Python SDK specifically designed to
-
@@ -22,9 +21,10 @@ Multi-step reasoning with comprehensive research capabilities
Finance-focused multi-step research with competitive accuracy at same price points and latencies as the Research API
Comprehensive API for You.com services:
- **Agents API**: Execute queries using Express, Advanced, and Custom AI agents
+- **Answer API**: Get synthesized, citation-backed answers grounded in real-time web results
- **Research API**: In-depth, multi-step research with citations and sources
- **Finance Research API**: Finance-focused multi-step research with citations and sources
-- **Search API**: Get search results from web and news sources
+- **Search API**: Get search results from web and news sources (keyless-capable via `/v1/agents/search`)
- **Contents API**: Retrieve and process web page content
@@ -141,7 +141,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -169,7 +169,7 @@ async def main():
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = await you.search_post_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = await you.search_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -206,7 +206,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -228,23 +228,29 @@ with You(
### [You SDK](docs/sdks/you/README.md)
-* [search_post](docs/sdks/you/README.md#search_post) - Returns a list of unified search results from web and news sources
+* [answer](docs/sdks/answer/README.md#answer) - Returns a synthesized answer with citations from web search results
+* [agents](docs/sdks/you/README.md#agents) - Run an Agent
+* [search](docs/sdks/you/README.md#search) - Returns a list of unified search results from web and news sources
+* [contents](docs/sdks/you/README.md#contents) - Returns the content of the web pages
* [research](docs/sdks/you/README.md#research) - Returns comprehensive research-grade answers with multi-step reasoning
* [get_research_task](docs/sdks/you/README.md#get_research_task) - Get the status of a background research task
* [stream_research_task](docs/sdks/you/README.md#stream_research_task) - Stream updates for a background research task
* [finance_research](docs/sdks/you/README.md#finance_research) - Returns comprehensive finance-grade research answers with multi-step reasoning
-### [Agents.Runs](docs/sdks/runs/README.md)
+### Keyless Search
-* [create](docs/sdks/runs/README.md#create) - Run an Agent
+`you.search()` targets `POST /v1/agents/search` — the keyless-capable proxy. No API key required for the free tier (100 queries/day, count ≤ 50, no livecrawl).
-### [Contents](docs/sdks/contentssdk/README.md)
-
-* [generate](docs/sdks/contentssdk/README.md#generate) - Returns the content of the web pages
+```python
+from youdotcom import You
-### [Search](docs/sdks/search/README.md)
+# No API key — uses the free tier
+you = You()
+res = you.search(query="What is the capital of France?", count=5)
+print(res.results.web[0].title)
+```
-* [unified](docs/sdks/search/README.md#unified) - Returns a list of unified search results from web and news sources
+With an API key, the same method forwards to the full search endpoint with no restrictions. A `402` response raises `PaymentRequiredResponseError` with structured data (`message`, `upgrade_url`, `limit`, `used`, `period`, `reset_at`).
@@ -282,7 +288,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- response = you.agents.runs.create(request=ExpressAgentRunsRequest(
+ response = you.agents(request=ExpressAgentRunsRequest(
input="Restaurants in San Francisco",
stream=True,
tools=[
@@ -352,7 +358,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -378,7 +384,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -418,7 +424,7 @@ with You(
res = None
try:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -526,13 +532,13 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
"nytimes.com",
"wired.com",
- ], crawl_timeout=10, server_url="https://ydc-index.io")
+ ], crawl_timeout=10, server_url="https://api.you.com")
# Handle response
print(res)
@@ -664,9 +670,9 @@ s = You(debug_logger=logging.getLogger("youdotcom"))
```
You can also enable a default debug logger by setting an environment variable `YOU_DEBUG` to true.
-
-
+**Warning:** Debug logs include full request headers and bodies, which may contain API keys and sensitive data. Do not enable debug logging in production or commit debug logs to version control.
+
# Development
@@ -705,7 +711,5 @@ For more details on testing, see the [tests README](tests/README.md).
## Contributions
-While we value open-source contributions to this SDK, this library is generated programmatically. Any manual changes added to internal files will be overwritten on the next generation.
+While we value open-source contributions to this SDK, this library is hand-maintained. We welcome pull requests — see [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
We look forward to hearing your feedback. Feel free to open a PR or an issue with a proof of concept and we'll do our best to include it in a future release.
-
-### SDK Created by [Speakeasy](https://www.speakeasy.com/?utm_source=youdotcom&utm_campaign=python)
diff --git a/USAGE.md b/USAGE.md
index 37e0e9d..17f7411 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -9,7 +9,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -37,7 +37,7 @@ async def main():
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = await you.search_post_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = await you.search_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
diff --git a/docs/errors/internalservererrorresponse.md b/docs/errors/internalservererrorresponse.md
index b99d7b0..25fa818 100644
--- a/docs/errors/internalservererrorresponse.md
+++ b/docs/errors/internalservererrorresponse.md
@@ -2,9 +2,14 @@
Internal Server Error during authentication/authorization middleware.
+Handles two possible 500 body shapes:
+- `{"detail": "..."}` — plain detail string
+- `{"errors": [{"status": "500", "code": "...", "title": "...", ...}]}` — JSON:API format
+
## Fields
-| Field | Type | Required | Description |
-| ------------------ | ------------------ | ------------------ | ------------------ |
-| `detail` | *Optional[str]* | :heavy_minus_sign: | N/A |
\ No newline at end of file
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `detail` | *Optional[str]* | :heavy_minus_sign: | A description of the error. |
+| `errors` | *Optional[List[dict]]* | :heavy_minus_sign: | JSON:API error array from controller-level error handlers. |
\ No newline at end of file
diff --git a/docs/errors/paymentrequiredresponseerror.md b/docs/errors/paymentrequiredresponseerror.md
new file mode 100644
index 0000000..4b4872e
--- /dev/null
+++ b/docs/errors/paymentrequiredresponseerror.md
@@ -0,0 +1,16 @@
+# PaymentRequiredResponseError
+
+Payment Required (402). The account cannot make paid API requests.
+
+
+## Fields
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `error` | *Optional[str]* | :heavy_minus_sign: | The error code (e.g. `"payment_required"`). |
+| `message` | *Optional[str]* | :heavy_minus_sign: | A human-readable description of the error. |
+| `upgrade_url` | *Optional[str]* | :heavy_minus_sign: | URL for adding credits or upgrading the account. |
+| `limit` | *Optional[int]* | :heavy_minus_sign: | The usage limit, when available. |
+| `used` | *Optional[int]* | :heavy_minus_sign: | The usage consumed, when available. |
+| `period` | *Optional[str]* | :heavy_minus_sign: | The usage period, when available. |
+| `reset_at` | *Optional[str]* | :heavy_minus_sign: | The reset timestamp, when available. |
diff --git a/docs/errors/unprocessableentityresponseerror.md b/docs/errors/unprocessableentityresponseerror.md
index 5d44499..214b6b0 100644
--- a/docs/errors/unprocessableentityresponseerror.md
+++ b/docs/errors/unprocessableentityresponseerror.md
@@ -2,9 +2,16 @@
Unprocessable Entity. Invalid request parameter combination.
+Handles three possible 422 body shapes:
+- `{"error": "..."}` — search spec format
+- `{"detail": [{"type": "...", "loc": [...], "msg": "...", ...}]}` — FastAPI validation errors
+- `{"errors": [{"status": "422", "code": "...", "title": "...", ...}]}` — JSON:API format
+
## Fields
-| Field | Type | Required | Description |
-| ------------------ | ------------------ | ------------------ | ------------------ |
-| `error` | *Optional[str]* | :heavy_minus_sign: | N/A |
\ No newline at end of file
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `error` | *Optional[str]* | :heavy_minus_sign: | Error code from the search spec 422 format. |
+| `detail` | *Optional[List[dict]]* | :heavy_minus_sign: | Validation error array from FastAPI's RequestValidationError. |
+| `errors` | *Optional[List[dict]]* | :heavy_minus_sign: | JSON:API error array from controller-level error handlers. |
\ No newline at end of file
diff --git a/docs/models/answercitation.md b/docs/models/answercitation.md
new file mode 100644
index 0000000..69c0d7d
--- /dev/null
+++ b/docs/models/answercitation.md
@@ -0,0 +1,11 @@
+# AnswerCitation
+
+A source cited in the answer, with supporting excerpts.
+
+
+## Fields
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `source` | *str* | :heavy_check_mark: | The URL of the cited source. |
+| `excerpts` | Optional[List[*str*]] | :heavy_minus_sign: | Verbatim excerpts from the cited source that support the answer. |
diff --git a/docs/models/answerrequestbody.md b/docs/models/answerrequestbody.md
new file mode 100644
index 0000000..2015fc9
--- /dev/null
+++ b/docs/models/answerrequestbody.md
@@ -0,0 +1,16 @@
+# AnswerRequestBody
+
+Request body for `POST /v1/answer`.
+
+
+## Fields
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `query` | *str* | :heavy_check_mark: | The search query used to retrieve relevant web results. Max 400 characters. Search operators (`site:`, `OR`, etc.) are not supported. |
+| `freshness` | [Optional[models.FreshnessValue]](../models/freshnessvalue.md) | :heavy_minus_sign: | Specifies the freshness of the results. One of `day`, `week`, `month`, `year`, or `YYYY-MM-DDtoYYYY-MM-DD`. |
+| `country` | [Optional[models.Country]](../models/country.md) | :heavy_minus_sign: | A supported country code that determines the geographical focus of the web results. |
+| `language` | [Optional[models.Language]](../models/language.md) | :heavy_minus_sign: | A supported BCP 47 language tag that determines the language of the web results. |
+| `include_domains` | Optional[List[*str*]] | :heavy_minus_sign: | Domains to exclusively include. Cannot combine with `exclude_domains` or `boost_domains`. Max 500. |
+| `exclude_domains` | Optional[List[*str*]] | :heavy_minus_sign: | Domains to exclude. Cannot combine with `include_domains`. Can combine with `boost_domains`. Max 500. |
+| `boost_domains` | Optional[List[*str*]] | :heavy_minus_sign: | Domains to prefer in ranking. Cannot combine with `include_domains`. Can combine with `exclude_domains`. Max 500. |
diff --git a/docs/models/answerresponse.md b/docs/models/answerresponse.md
new file mode 100644
index 0000000..0367952
--- /dev/null
+++ b/docs/models/answerresponse.md
@@ -0,0 +1,19 @@
+# AnswerResponse
+
+A synthesized answer with citations and supporting search results.
+
+## AnswerResults
+
+Search results grouped by result type.
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `web` | Optional[List[[models.AnswerSearchResult](../models/answersearchresult.md)]] | :heavy_minus_sign: | All web search results considered during answer synthesis. |
+
+## AnswerResponse
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `answer` | *str* | :heavy_check_mark: | The synthesized response with numbered inline citations that reference items in the `citations` array. |
+| `citations` | Optional[List[[models.AnswerCitation](../models/answercitation.md)]] | :heavy_minus_sign: | The sources cited in the answer, in citation order. |
+| `results` | [Optional[models.AnswerResults]](#answerresults) | :heavy_minus_sign: | Search results grouped by result type. |
diff --git a/docs/models/answersearchresult.md b/docs/models/answersearchresult.md
new file mode 100644
index 0000000..241633e
--- /dev/null
+++ b/docs/models/answersearchresult.md
@@ -0,0 +1,13 @@
+# AnswerSearchResult
+
+A web search result used during answer synthesis.
+
+
+## Fields
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `url` | *str* | :heavy_check_mark: | The URL of the source webpage. |
+| `title` | *str* | :heavy_check_mark: | The title of the source webpage. |
+| `snippets` | Optional[List[*str*]] | :heavy_minus_sign: | Text snippets from the search result that preview its content. |
+| `page_age` | *Optional[str]* | :heavy_minus_sign: | The publication date or age supplied by the search result. |
diff --git a/docs/models/contentsmetadata.md b/docs/models/contentsmetadata.md
index 6050bc0..cfac725 100644
--- a/docs/models/contentsmetadata.md
+++ b/docs/models/contentsmetadata.md
@@ -8,4 +8,4 @@ Metadata about the web page. Only returned when 'metadata' is included in the fo
| Field | Type | Required | Description | Example |
| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- |
| `site_name` | *OptionalNullable[str]* | :heavy_minus_sign: | The OpenGraph site name of the web page. | You.com |
-| `favicon_url` | *Optional[str]* | :heavy_minus_sign: | The URL of the favicon of the web page's domain. | https://api.ydc-index.io/favicon?domain=you.com&size=128 |
\ No newline at end of file
+| `favicon_url` | *Optional[str]* | :heavy_minus_sign: | The URL of the favicon of the web page's domain. | https://api.you.com/favicon?domain=you.com&size=128 |
\ No newline at end of file
diff --git a/docs/sdks/answer/README.md b/docs/sdks/answer/README.md
new file mode 100644
index 0000000..8b71213
--- /dev/null
+++ b/docs/sdks/answer/README.md
@@ -0,0 +1,65 @@
+# Answer
+
+## Overview
+
+The Answer API returns a synthesized natural-language answer with citations and the web results used to generate it. Send a `query` with optional freshness, locale, and domain controls.
+
+Called as a direct method on the `You` client: `you.answer(query=...)`.
+
+### Available Operations
+
+* [answer](#answer) - Returns a synthesized answer with citations from web search results
+
+## answer
+
+Returns a synthesized natural-language answer with citations and the web results used to generate it. Provide a `query` and optional freshness, locale, and domain controls.
+
+### Example Usage
+
+```python
+import os
+from youdotcom import You
+
+
+with You(
+ api_key_auth=os.getenv("YDC_API_KEY", ""),
+) as you:
+
+ res = you.answer(query="What are the main causes of the 2008 financial crisis?")
+
+ # Handle response
+ print(res.answer)
+ for citation in res.citations:
+ print(f" [{citation.source}] {citation.excerpts[0]}")
+```
+
+### Parameters
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `query` | *str* | :heavy_check_mark: | The search query. Max 400 characters. Search operators (`site:`, `OR`, etc.) are not supported. |
+| `freshness` | *Optional[str]* | :heavy_minus_sign: | `day`, `week`, `month`, `year`, or `YYYY-MM-DDtoYYYY-MM-DD` |
+| `country` | *Optional[str]* | :heavy_minus_sign: | Country code (e.g. `US`, `GB`, `FR`). Normalized to uppercase. |
+| `language` | *Optional[str]* | :heavy_minus_sign: | BCP 47 language tag (e.g. `EN`, `EN-GB`, `FR`). Normalized to uppercase. |
+| `include_domains` | *Optional[List[str]]* | :heavy_minus_sign: | Domains to exclusively include. Cannot combine with `exclude_domains` or `boost_domains`. Max 500. |
+| `exclude_domains` | *Optional[List[str]]* | :heavy_minus_sign: | Domains to exclude. Cannot combine with `include_domains`. Can combine with `boost_domains`. Max 500. |
+| `boost_domains` | *Optional[List[str]]* | :heavy_minus_sign: | Domains to prefer in ranking. Cannot combine with `include_domains`. Can combine with `exclude_domains`. Max 500. |
+| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. |
+| `server_url` | *Optional[str]* | :heavy_minus_sign: | An optional server URL to use. |
+| `timeout_ms` | *Optional[int]* | :heavy_minus_sign: | Override the default request timeout in milliseconds. |
+| `http_headers` | *Optional[Mapping[str, str]]* | :heavy_minus_sign: | Additional headers to set or replace on requests. |
+
+### Response
+
+**[models.AnswerResponse](../../models/answerresponse.md)**
+
+### Errors
+
+| Error Type | Status Code | Content Type |
+|------------|-------------|-------------|
+| errors.UnauthorizedResponseError | 401 | application/json |
+| errors.PaymentRequiredResponseError | 402 | application/json |
+| errors.ForbiddenResponseError | 403 | application/json |
+| errors.UnprocessableEntityResponseError | 422 | application/json |
+| errors.InternalServerErrorResponse | 500 | application/json |
+| errors.YouDefaultError | 4XX, 5XX | \*/\* |
diff --git a/docs/sdks/contentssdk/README.md b/docs/sdks/contentssdk/README.md
index b1617a1..cdea1d0 100644
--- a/docs/sdks/contentssdk/README.md
+++ b/docs/sdks/contentssdk/README.md
@@ -1,5 +1,12 @@
# Contents
+> **DEPRECATED — removed in the current major version.** The `ContentsSDK` sub-SDK is no longer available. Use the direct method on the `You` client instead:
+>
+> - `you.contents(urls=...)` (was `you.contents.generate(urls=...)`)
+> - `you.contents_async(urls=...)` (was `you.contents.generate_async(urls=...)`)
+>
+> See [docs/sdks/you/README.md](../you/README.md#contents) for the current API. The content below is kept for reference only.
+
## Overview
### Available Operations
diff --git a/docs/sdks/runs/README.md b/docs/sdks/runs/README.md
index 11795dd..6d9ef70 100644
--- a/docs/sdks/runs/README.md
+++ b/docs/sdks/runs/README.md
@@ -1,5 +1,12 @@
# Agents.Runs
+> **DEPRECATED — removed in the current major version.** The `Agents` sub-SDK is no longer available. Use the direct method on the `You` client instead:
+>
+> - `you.agents(request=...)` (was `you.agents.runs.create(request=...)`)
+> - `you.agents_async(request=...)` (was `you.agents.runs.create_async(request=...)`)
+>
+> See [docs/sdks/you/README.md](../you/README.md#agents) for the current API. The content below is kept for reference only.
+
## Overview
### Available Operations
diff --git a/docs/sdks/search/README.md b/docs/sdks/search/README.md
index b02eb4d..a693fcc 100644
--- a/docs/sdks/search/README.md
+++ b/docs/sdks/search/README.md
@@ -1,5 +1,12 @@
# Search
+> **DEPRECATED — removed in the current major version.** The `Search` sub-SDK is no longer available. `GET /v1/search` (`you.search.unified()`) has been removed. Use the direct `POST /v1/agents/search` method on the `You` client instead:
+>
+> - `you.search(query=...)` (was `you.search.unified(query=...)` and `you.search_post(query=...)`)
+> - `you.search_async(query=...)` (was `you.search_post_async(query=...)`)
+>
+> See [docs/sdks/you/README.md](../you/README.md#search) for the current API. The content below is kept for reference only.
+
## Overview
### Available Operations
diff --git a/docs/sdks/you/README.md b/docs/sdks/you/README.md
index 58c8b5a..5c189ae 100644
--- a/docs/sdks/you/README.md
+++ b/docs/sdks/you/README.md
@@ -9,28 +9,77 @@ Multi-step reasoning with comprehensive research capabilities
Finance-focused multi-step research with competitive accuracy at same price points and latencies as the Research API
Comprehensive API for You.com services:
- **Agents API**: Execute queries using Express, Advanced, and Custom AI agents
+- **Answer API**: Get synthesized, citation-backed answers grounded in real-time web results
- **Research API**: In-depth, multi-step research with citations and sources
- **Finance Research API**: Finance-focused multi-step research with citations and sources
-- **Search API**: Get search results from web and news sources
+- **Search API**: Get search results from web and news sources (keyless-capable via `/v1/agents/search`)
- **Contents API**: Retrieve and process web page content
### Available Operations
-* [search_post](#search_post) - Returns a list of unified search results from web and news sources
+* [answer](#answer) - Returns a synthesized answer with citations from web search results
+* [agents](#agents) - Run an Agent
+* [search](#search) - Returns a list of unified search results from web and news sources
+* [contents](#contents) - Returns the content of the web pages
* [research](#research) - Returns comprehensive research-grade answers with multi-step reasoning
* [get_research_task](#get_research_task) - Get the status of a background research task
* [stream_research_task](#stream_research_task) - Stream updates for a background research task
* [finance_research](#finance_research) - Returns comprehensive finance-grade research answers with multi-step reasoning
-## search_post
+## answer
-This endpoint is designed to return LLM-ready web results based on a user's query. Based on a classification mechanism, it can return web results and news associated with your query. If you need to feed an LLM with the results of a query that sounds like `What are the latest geopolitical updates from India`, then this endpoint is the right one for you.
+Returns a synthesized answer with citations from web search results.
-`POST` is the recommended method when using complex parameters such as `include_domains` or `exclude_domains`. These fields accept JSON arrays in the request body, which is unambiguous and supports up to 500 domains per request—something that would exceed URL length limits with GET. Use GET for simple queries where HTTP cacheability matters.
+### Example Usage
+
+```python
+import os
+from youdotcom import You
+
+with You(
+ api_key_auth=os.getenv("YDC_API_KEY", ""),
+) as you:
+ res = you.answer(query="What is the capital of France?")
+ print(res)
+```
+
+## agents
+
+Execute queries using You.com's AI agents (Express, Advanced, or Custom).
+
+### Example Usage
+
+```python
+import os
+from youdotcom import You, models
+
+with You(
+ api_key_auth=os.getenv("YDC_API_KEY", ""),
+) as you:
+ res = you.agents(request=models.ExpressAgentRunsRequest(
+ input="What are the latest AI developments?",
+ ))
+ print(res)
+```
+
+## search
+
+Search via `POST /v1/agents/search` — the keyless-capable proxy. With no API key configured, runs in the free tier (100 queries/day, count ≤ 50, no livecrawl). With a key, the proxy forwards to the full search endpoint. Country and language accept plain strings and are normalized to uppercase.
+
+### Example Usage: keyless
+
+```python
+from youdotcom import You
+
+# No API key — uses the free tier
+you = You()
+res = you.search(query="What is the capital of France?", count=5)
+print(res.results.web[0].title)
+```
### Example Usage: authFailure
-
+
```python
import os
from youdotcom import You, models
@@ -40,7 +89,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -54,7 +103,7 @@ with You(
```
### Example Usage: authorizationFailure
-
+
```python
import os
from youdotcom import You, models
@@ -64,7 +113,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -78,7 +127,7 @@ with You(
```
### Example Usage: invalidOrExpired
-
+
```python
import os
from youdotcom import You, models
@@ -88,7 +137,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -102,7 +151,7 @@ with You(
```
### Example Usage: invalidParams
-
+
```python
import os
from youdotcom import You, models
@@ -112,7 +161,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -126,7 +175,7 @@ with You(
```
### Example Usage: missingApiKey
-
+
```python
import os
from youdotcom import You, models
@@ -136,7 +185,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -150,7 +199,7 @@ with You(
```
### Example Usage: missingScopes
-
+
```python
import os
from youdotcom import You, models
@@ -160,7 +209,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -174,7 +223,7 @@ with You(
```
### Example Usage: otherAuthParsing
-
+
```python
import os
from youdotcom import You, models
@@ -184,7 +233,7 @@ with You(
api_key_auth=os.getenv("YDC_API_KEY", ""),
) as you:
- res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
+ res = you.search(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, exclude_domains=[
"spam-site.com",
"other-site.com",
], boost_domains=[
@@ -225,12 +274,30 @@ with You(
| Error Type | Status Code | Content Type |
| --------------------------------------- | --------------------------------------- | --------------------------------------- |
+| errors.PaymentRequiredResponseError | 402 | application/json |
| errors.UnauthorizedResponseError | 401 | application/json |
| errors.ForbiddenResponseError | 403 | application/json |
| errors.UnprocessableEntityResponseError | 422 | application/json |
| errors.InternalServerErrorResponse | 500 | application/json |
| errors.YouDefaultError | 4XX, 5XX | \*/\* |
+## contents
+
+Returns the HTML or Markdown of target web pages.
+
+### Example Usage
+
+```python
+import os
+from youdotcom import You, models
+
+with You(
+ api_key_auth=os.getenv("YDC_API_KEY", ""),
+) as you:
+ res = you.contents(urls=["https://example.com"], formats=[models.ContentsFormats.MARKDOWN])
+ print(res)
+```
+
## research
Research goes beyond a single web search. In response to your question, it runs multiple searches, reads through the sources, and synthesizes everything into a thorough, well-cited answer. Use it when a question is too complex for a simple lookup, and when you need a response you can actually trust and verify.
diff --git a/examples/api-example-calls.py b/examples/api-example-calls.py
index 9136685..692014f 100755
--- a/examples/api-example-calls.py
+++ b/examples/api-example-calls.py
@@ -60,7 +60,7 @@ def express_batch_request():
assert you is not None, "SDK client not initialized"
- results = you.agents.runs.create(request=ExpressAgentRunsRequest(
+ results = you.agents(request=ExpressAgentRunsRequest(
input="What is the capital of France?",
stream=False,
tools=[
@@ -90,7 +90,7 @@ def express_streaming_request():
assert you is not None, "SDK client not initialized"
- response = you.agents.runs.create(request=ExpressAgentRunsRequest(
+ response = you.agents(request=ExpressAgentRunsRequest(
input="Restaurants in San Francisco",
stream=True,
tools=[
@@ -159,7 +159,7 @@ def advanced_batch_request():
]
)
- results = you.agents.runs.create(request=request)
+ results = you.agents(request=request)
# Access the results - check if it's a batch response
if isinstance(results, AgentRunsBatchResponse):
@@ -195,7 +195,7 @@ def custom_batch_request():
)
try:
- results = you.agents.runs.create(request=request)
+ results = you.agents(request=request)
print(results)
except Exception as e:
print(f"Error: {e}")
@@ -210,7 +210,7 @@ def search_request():
assert you is not None, "SDK client not initialized"
- results = you.search.unified(
+ results = you.search(
query="artificial intelligence in farming",
count=1,
livecrawl=LiveCrawl.WEB,
@@ -242,7 +242,7 @@ def content_request():
# Example 1: Get markdown content
print("Example 1: Fetching markdown content...")
- results = you.contents.generate(
+ results = you.contents(
urls=["https://you.com"],
formats=[ContentsFormats.MARKDOWN]
)
@@ -257,7 +257,7 @@ def content_request():
# Example 2: Get multiple formats including metadata (json+ld, opengraph info)
print("Example 2: Fetching HTML + metadata...")
- results = you.contents.generate(
+ results = you.contents(
urls=["https://you.com"],
formats=[ContentsFormats.HTML, ContentsFormats.METADATA],
crawl_timeout=30 # Optional: set custom timeout (1-60 seconds)
@@ -487,10 +487,10 @@ def search_request_with_boost():
assert you is not None, "SDK client not initialized"
- results = you.search.unified(
+ results = you.search(
query="latest advances in fusion energy research",
count=5,
- boost_domains="nature.com,science.org,arxiv.org",
+ boost_domains=["nature.com", "science.org", "arxiv.org"],
)
print("Top results:")
@@ -509,7 +509,7 @@ def content_request_with_max_age():
assert you is not None, "SDK client not initialized"
- results = you.contents.generate(
+ results = you.contents(
urls=["https://example.com/page"],
formats=[ContentsFormats.MARKDOWN],
crawl_timeout=20,
diff --git a/pyproject.toml b/pyproject.toml
index e14db01..d3c2916 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "youdotcom"
-version = "2.5.0"
+version = "2.6.0"
description = "The official You.com Python SDK."
authors = [{ name = "You.com" },]
readme = "README.md"
@@ -14,11 +14,11 @@ license = { text = "Apache-2.0" }
[dependency-groups]
dev = [
- "mypy ==1.15.0",
- "pylint ==3.2.3",
- "pyright ==1.1.398",
- "pytest >=8.0.0",
- "pytest-asyncio >=0.24.0",
+ "mypy >=2.3.0",
+ "pylint >=4.0.0",
+ "pyright >=1.1.398",
+ "pytest >=9.0.0",
+ "pytest-asyncio >=1.0.0",
]
[tool.setuptools.packages.find]
diff --git a/src/youdotcom/__init__.py b/src/youdotcom/__init__.py
index 833c68c..4153b35 100644
--- a/src/youdotcom/__init__.py
+++ b/src/youdotcom/__init__.py
@@ -1,10 +1,9 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from ._version import (
__title__,
__version__,
__openapi_doc_version__,
- __gen_version__,
__user_agent__,
)
from .sdk import *
@@ -13,5 +12,4 @@
VERSION: str = __version__
OPENAPI_DOC_VERSION = __openapi_doc_version__
-SPEAKEASY_GENERATOR_VERSION = __gen_version__
USER_AGENT = __user_agent__
diff --git a/src/youdotcom/_hooks/__init__.py b/src/youdotcom/_hooks/__init__.py
index 2ee66cd..2fc0799 100644
--- a/src/youdotcom/_hooks/__init__.py
+++ b/src/youdotcom/_hooks/__init__.py
@@ -1,5 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from .sdkhooks import *
from .types import *
-from .registration import *
diff --git a/src/youdotcom/_hooks/registration.py b/src/youdotcom/_hooks/registration.py
deleted file mode 100644
index fb7f679..0000000
--- a/src/youdotcom/_hooks/registration.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from .types import Hooks, BeforeRequestHook, BeforeRequestContext
-import httpx
-from typing import Union
-
-
-# This file is only ever generated once on the first generation and then is free to be modified.
-# Any hooks you wish to add should be registered in the init_hooks function. Feel free to define them
-# in this file or in separate files in the hooks folder.
-
-_DEFAULT_UA_PREFIX = "speakeasy-sdk/"
-
-
-class YDCUserAgentOverrideHook(BeforeRequestHook):
- """Hook that overrides the User-Agent header on every request.
-
- Behaviour:
- - If ``sdk_configuration.user_agent`` has been overridden away from the
- speakeasy-default (``speakeasy-sdk/python ...``), pass it through so
- integrations (langchain-youdotcom, youdotcom-temporal,
- n8n-nodes-youdotcom) can identify their traffic.
- - Otherwise, emit the SDK-default ``youdotcom-python-sdk/{sdk_version}``.
- """
-
- def before_request(self, hook_ctx: BeforeRequestContext, request: httpx.Request) -> Union[httpx.Request, Exception]:
- sdk_version = hook_ctx.config.sdk_version
- configured_ua = hook_ctx.config.user_agent
-
- # `not startswith(_DEFAULT_UA_PREFIX)` already handles the default-UA
- # case (the speakeasy default always starts with the prefix), so a
- # separate `configured_ua != __user_agent__` check is redundant.
- is_custom = bool(configured_ua) and not configured_ua.startswith(_DEFAULT_UA_PREFIX)
-
- request.headers["User-Agent"] = (
- configured_ua if is_custom else f"youdotcom-python-sdk/{sdk_version}"
- )
-
- return request
-
-
-def init_hooks(hooks: Hooks):
- # pylint: disable=unused-argument
- """Add hooks by calling hooks.register{sdk_init/before_request/after_success/after_error}Hook
- with an instance of a hook that implements that specific Hook interface
- Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance"""
- hooks.register_before_request_hook(YDCUserAgentOverrideHook())
diff --git a/src/youdotcom/_hooks/sdkhooks.py b/src/youdotcom/_hooks/sdkhooks.py
index 48d0589..d9d8ffb 100644
--- a/src/youdotcom/_hooks/sdkhooks.py
+++ b/src/youdotcom/_hooks/sdkhooks.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import httpx
from .types import (
@@ -11,7 +11,6 @@
AfterErrorHook,
Hooks,
)
-from .registration import init_hooks
from typing import List, Optional, Tuple
from youdotcom.sdkconfiguration import SDKConfiguration
@@ -22,7 +21,6 @@ def __init__(self) -> None:
self.before_request_hooks: List[BeforeRequestHook] = []
self.after_success_hooks: List[AfterSuccessHook] = []
self.after_error_hooks: List[AfterErrorHook] = []
- init_hooks(self)
def register_sdk_init_hook(self, hook: SDKInitHook) -> None:
self.sdk_init_hooks.append(hook)
diff --git a/src/youdotcom/_hooks/types.py b/src/youdotcom/_hooks/types.py
index 2b03ad3..c41ea53 100644
--- a/src/youdotcom/_hooks/types.py
+++ b/src/youdotcom/_hooks/types.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from abc import ABC, abstractmethod
import httpx
diff --git a/src/youdotcom/_version.py b/src/youdotcom/_version.py
index 3cd2c70..7e641ce 100644
--- a/src/youdotcom/_version.py
+++ b/src/youdotcom/_version.py
@@ -1,15 +1,14 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
import importlib.metadata
__title__: str = "youdotcom"
-__version__: str = "2.5.0"
+__version__: str = "2.6.0"
__openapi_doc_version__: str = "1.0.0"
-__gen_version__: str = "2.918.1"
-__user_agent__: str = "speakeasy-sdk/python 2.5.0 2.918.1 1.0.0 youdotcom"
try:
if __package__ is not None:
__version__ = importlib.metadata.version(__package__)
except importlib.metadata.PackageNotFoundError:
pass
+
+__user_agent__: str = f"youdotcom-python-sdk/{__version__}"
diff --git a/src/youdotcom/agents.py b/src/youdotcom/agents.py
deleted file mode 100644
index 9090364..0000000
--- a/src/youdotcom/agents.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
-from .basesdk import BaseSDK
-from .sdkconfiguration import SDKConfiguration
-from typing import Optional
-from youdotcom.runs import Runs
-
-
-class Agents(BaseSDK):
- runs: Runs
-
- def __init__(
- self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
- ) -> None:
- BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
- self.sdk_configuration = sdk_config
- self._init_sdks()
-
- def _init_sdks(self):
- self.runs = Runs(self.sdk_configuration, parent_ref=self.parent_ref)
diff --git a/src/youdotcom/basesdk.py b/src/youdotcom/basesdk.py
index 8665d7f..3429c46 100644
--- a/src/youdotcom/basesdk.py
+++ b/src/youdotcom/basesdk.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from .sdkconfiguration import SDKConfiguration
import httpx
diff --git a/src/youdotcom/contents_sdk.py b/src/youdotcom/contents_sdk.py
deleted file mode 100644
index e7ce613..0000000
--- a/src/youdotcom/contents_sdk.py
+++ /dev/null
@@ -1,239 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
-from .basesdk import BaseSDK
-from typing import Any, Iterable, List, Mapping, Optional
-from youdotcom import errors, models, utils
-from youdotcom._hooks import HookContext
-from youdotcom.types import OptionalNullable, UNSET
-from youdotcom.utils import get_security_from_env
-from youdotcom.utils.unmarshal_json_response import unmarshal_json_response
-
-
-class ContentsSDK(BaseSDK):
- def generate(
- self,
- *,
- urls: Optional[Iterable[str]] = None,
- formats: Optional[Iterable[models.ContentsFormats]] = None,
- crawl_timeout: Optional[int] = 10,
- max_age: OptionalNullable[int] = None,
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> List[models.ContentsResponse]:
- r"""Returns the content of the web pages
-
- Returns the HTML or Markdown of a target webpage.
-
- :param urls: Array of URLs to fetch the contents from.
- :param formats: Array of content formats to return. All included formats are returned in the response. Include \"metadata\" to get JSON-LD and OpenGraph information, if available.
- :param crawl_timeout: Maximum time in seconds to wait for page content. Must be between 1 and 60 seconds. Default is 10 seconds.
- :param max_age: Maximum allowed age of cached content in seconds. When set, cached content older than this threshold is ignored and the page is re-fetched. Must be 0 or greater. Default: null (no age limit, cached content is returned regardless of age).
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.CONTENTS_OP_SERVERS[0]
-
- request = models.ContentsRequest(
- urls=utils.unmarshal(urls, Optional[List[str]]),
- formats=utils.unmarshal(formats, Optional[List[models.ContentsFormats]]),
- crawl_timeout=crawl_timeout,
- max_age=max_age,
- )
-
- req = self._build_request(
- method="POST",
- path="/v1/contents",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=True,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.ContentsRequest
- ),
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = self.do_request(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="contents",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["contents"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(List[models.ContentsResponse], http_res)
- if utils.match_response(http_res, "401", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsUnauthorizedErrorData, http_res
- )
- raise errors.ContentsUnauthorizedError(response_data, http_res)
- if utils.match_response(http_res, "403", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsForbiddenErrorData, http_res
- )
- raise errors.ContentsForbiddenError(response_data, http_res)
- if utils.match_response(http_res, "500", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsInternalServerErrorData, http_res
- )
- raise errors.ContentsInternalServerError(response_data, http_res)
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- raise errors.YouDefaultError("Unexpected response received", http_res)
-
- async def generate_async(
- self,
- *,
- urls: Optional[Iterable[str]] = None,
- formats: Optional[Iterable[models.ContentsFormats]] = None,
- crawl_timeout: Optional[int] = 10,
- max_age: OptionalNullable[int] = None,
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> List[models.ContentsResponse]:
- r"""Returns the content of the web pages
-
- Returns the HTML or Markdown of a target webpage.
-
- :param urls: Array of URLs to fetch the contents from.
- :param formats: Array of content formats to return. All included formats are returned in the response. Include \"metadata\" to get JSON-LD and OpenGraph information, if available.
- :param crawl_timeout: Maximum time in seconds to wait for page content. Must be between 1 and 60 seconds. Default is 10 seconds.
- :param max_age: Maximum allowed age of cached content in seconds. When set, cached content older than this threshold is ignored and the page is re-fetched. Must be 0 or greater. Default: null (no age limit, cached content is returned regardless of age).
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.CONTENTS_OP_SERVERS[0]
-
- request = models.ContentsRequest(
- urls=utils.unmarshal(urls, Optional[List[str]]),
- formats=utils.unmarshal(formats, Optional[List[models.ContentsFormats]]),
- crawl_timeout=crawl_timeout,
- max_age=max_age,
- )
-
- req = self._build_request_async(
- method="POST",
- path="/v1/contents",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=True,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.ContentsRequest
- ),
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = await self.do_request_async(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="contents",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["contents"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(List[models.ContentsResponse], http_res)
- if utils.match_response(http_res, "401", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsUnauthorizedErrorData, http_res
- )
- raise errors.ContentsUnauthorizedError(response_data, http_res)
- if utils.match_response(http_res, "403", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsForbiddenErrorData, http_res
- )
- raise errors.ContentsForbiddenError(response_data, http_res)
- if utils.match_response(http_res, "500", "application/json"):
- response_data = unmarshal_json_response(
- errors.ContentsInternalServerErrorData, http_res
- )
- raise errors.ContentsInternalServerError(response_data, http_res)
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- raise errors.YouDefaultError("Unexpected response received", http_res)
diff --git a/src/youdotcom/errors/__init__.py b/src/youdotcom/errors/__init__.py
index aea2e79..9646be6 100644
--- a/src/youdotcom/errors/__init__.py
+++ b/src/youdotcom/errors/__init__.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from .youerror import YouError
from typing import Any, TYPE_CHECKING
@@ -55,6 +55,10 @@
InternalServerErrorResponseData,
)
from .no_response_error import NoResponseError
+ from .paymentrequired_response_error import (
+ PaymentRequiredResponseError,
+ PaymentRequiredResponseErrorData,
+ )
from .researchop import (
ResearchForbiddenError,
ResearchForbiddenErrorData,
@@ -120,6 +124,8 @@
"InternalServerErrorResponse",
"InternalServerErrorResponseData",
"NoResponseError",
+ "PaymentRequiredResponseError",
+ "PaymentRequiredResponseErrorData",
"ResearchForbiddenError",
"ResearchForbiddenErrorData",
"ResearchInternalServerError",
@@ -179,6 +185,8 @@
"InternalServerErrorResponse": ".internalservererror_response",
"InternalServerErrorResponseData": ".internalservererror_response",
"NoResponseError": ".no_response_error",
+ "PaymentRequiredResponseError": ".paymentrequired_response_error",
+ "PaymentRequiredResponseErrorData": ".paymentrequired_response_error",
"ResearchForbiddenError": ".researchop",
"ResearchForbiddenErrorData": ".researchop",
"ResearchInternalServerError": ".researchop",
diff --git a/src/youdotcom/errors/agentruns400response_error.py b/src/youdotcom/errors/agentruns400response_error.py
index bce533b..d8d8aeb 100644
--- a/src/youdotcom/errors/agentruns400response_error.py
+++ b/src/youdotcom/errors/agentruns400response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/agentruns401response_error.py b/src/youdotcom/errors/agentruns401response_error.py
index 50df678..d89aaa1 100644
--- a/src/youdotcom/errors/agentruns401response_error.py
+++ b/src/youdotcom/errors/agentruns401response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/agentruns422response_error.py b/src/youdotcom/errors/agentruns422response_error.py
index c86c94a..89dc807 100644
--- a/src/youdotcom/errors/agentruns422response_error.py
+++ b/src/youdotcom/errors/agentruns422response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/contentsop.py b/src/youdotcom/errors/contentsop.py
index 443fd30..3329e53 100644
--- a/src/youdotcom/errors/contentsop.py
+++ b/src/youdotcom/errors/contentsop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/finance_researchop.py b/src/youdotcom/errors/finance_researchop.py
index a42f34d..2ce54e6 100644
--- a/src/youdotcom/errors/finance_researchop.py
+++ b/src/youdotcom/errors/finance_researchop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/forbidden_response_error.py b/src/youdotcom/errors/forbidden_response_error.py
index 575dded..e40a871 100644
--- a/src/youdotcom/errors/forbidden_response_error.py
+++ b/src/youdotcom/errors/forbidden_response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/getresearchtaskop.py b/src/youdotcom/errors/getresearchtaskop.py
index 1c3cda1..4b5eac8 100644
--- a/src/youdotcom/errors/getresearchtaskop.py
+++ b/src/youdotcom/errors/getresearchtaskop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/internalservererror_response.py b/src/youdotcom/errors/internalservererror_response.py
index 4a8c1a8..1ddc8ce 100644
--- a/src/youdotcom/errors/internalservererror_response.py
+++ b/src/youdotcom/errors/internalservererror_response.py
@@ -1,15 +1,27 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+"""Error model for HTTP 500 responses.
+
+Handles two possible 500 body shapes:
+ - ``{"detail": "..."}`` — plain detail string
+ - ``{"errors": [{"status": "500", "code": "...", "title": "...", ...}]}`` —
+ JSON:API format (returned by controller-level error handlers)
+
+Both fields are optional so either shape deserializes without crashing.
+"""
from __future__ import annotations
from dataclasses import dataclass, field
import httpx
-from typing import Optional
+from typing import Any, List, Optional
from youdotcom.errors import YouError
from youdotcom.types import BaseModel
class InternalServerErrorResponseData(BaseModel):
detail: Optional[str] = None
+ r"""A description of the error."""
+
+ errors: Optional[List[dict[str, Any]]] = None
+ r"""JSON:API error array from controller-level error handlers."""
@dataclass(unsafe_hash=True)
diff --git a/src/youdotcom/errors/no_response_error.py b/src/youdotcom/errors/no_response_error.py
index 1deab64..f72947a 100644
--- a/src/youdotcom/errors/no_response_error.py
+++ b/src/youdotcom/errors/no_response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from dataclasses import dataclass
diff --git a/src/youdotcom/errors/paymentrequired_response_error.py b/src/youdotcom/errors/paymentrequired_response_error.py
new file mode 100644
index 0000000..08d1a2b
--- /dev/null
+++ b/src/youdotcom/errors/paymentrequired_response_error.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+from dataclasses import dataclass, field
+import httpx
+from typing import Optional
+from youdotcom.errors import YouError
+from youdotcom.types import BaseModel
+
+
+class PaymentRequiredResponseErrorData(BaseModel):
+ r"""Body of a 402 response — returned when the account
+ cannot make paid API requests (free-tier limit exceeded, insufficient credits).
+ """
+ error: Optional[str] = None
+ r"""The error code (e.g. ``"payment_required"``)."""
+ message: Optional[str] = None
+ r"""A human-readable description of the error."""
+ upgrade_url: Optional[str] = None
+ r"""URL for adding credits or upgrading the account."""
+ limit: Optional[int] = None
+ r"""The usage limit, when available."""
+ used: Optional[int] = None
+ r"""The usage consumed, when available."""
+ period: Optional[str] = None
+ r"""The usage period, when available."""
+ reset_at: Optional[str] = None
+ r"""The reset timestamp, when available."""
+
+
+@dataclass(unsafe_hash=True)
+class PaymentRequiredResponseError(YouError):
+ r"""Payment Required (402). The account cannot make paid API requests."""
+
+ data: PaymentRequiredResponseErrorData = field(hash=False)
+
+ def __init__(
+ self,
+ data: PaymentRequiredResponseErrorData,
+ raw_response: httpx.Response,
+ body: Optional[str] = None,
+ ):
+ message = body or raw_response.text
+ super().__init__(message, raw_response, body)
+ object.__setattr__(self, "data", data)
diff --git a/src/youdotcom/errors/researchop.py b/src/youdotcom/errors/researchop.py
index a64bebb..e8bfa98 100644
--- a/src/youdotcom/errors/researchop.py
+++ b/src/youdotcom/errors/researchop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/responsevalidationerror.py b/src/youdotcom/errors/responsevalidationerror.py
index 8e3bb21..f92fdda 100644
--- a/src/youdotcom/errors/responsevalidationerror.py
+++ b/src/youdotcom/errors/responsevalidationerror.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import httpx
from typing import Optional
diff --git a/src/youdotcom/errors/streamresearchtaskop.py b/src/youdotcom/errors/streamresearchtaskop.py
index a2ee4a0..c6e64dd 100644
--- a/src/youdotcom/errors/streamresearchtaskop.py
+++ b/src/youdotcom/errors/streamresearchtaskop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/unauthorized_response_error.py b/src/youdotcom/errors/unauthorized_response_error.py
index dbc2f9f..82c2081 100644
--- a/src/youdotcom/errors/unauthorized_response_error.py
+++ b/src/youdotcom/errors/unauthorized_response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from dataclasses import dataclass, field
diff --git a/src/youdotcom/errors/unprocessableentity_response_error.py b/src/youdotcom/errors/unprocessableentity_response_error.py
index f915365..f7696c2 100644
--- a/src/youdotcom/errors/unprocessableentity_response_error.py
+++ b/src/youdotcom/errors/unprocessableentity_response_error.py
@@ -1,15 +1,34 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+"""Error model for HTTP 422 responses.
+
+Handles three possible 422 body shapes returned across You.com endpoints:
+ - ``{"error": "..."}`` — search spec format
+ - ``{"detail": [{"type": "...", "loc": [...], "msg": "...", ...}]}`` — FastAPI
+ request validation errors (returned before handler runs)
+ - ``{"errors": [{"status": "422", "code": "...", "title": "...", ...}]}`` —
+ JSON:API format (returned by controller-level error handlers)
+
+All fields are optional so any shape deserializes without crashing. The raw
+response is always preserved on the error object for callers that need the
+full body.
+"""
from __future__ import annotations
from dataclasses import dataclass, field
import httpx
-from typing import Optional
+from typing import Any, List, Optional
from youdotcom.errors import YouError
from youdotcom.types import BaseModel
class UnprocessableEntityResponseErrorData(BaseModel):
error: Optional[str] = None
+ r"""Error code from the search spec 422 format."""
+
+ detail: Optional[List[dict[str, Any]]] = None
+ r"""Validation error array from FastAPI's RequestValidationError."""
+
+ errors: Optional[List[dict[str, Any]]] = None
+ r"""JSON:API error array from controller-level error handlers."""
@dataclass(unsafe_hash=True)
diff --git a/src/youdotcom/errors/youdefaulterror.py b/src/youdotcom/errors/youdefaulterror.py
index 795cc56..9bc1e8e 100644
--- a/src/youdotcom/errors/youdefaulterror.py
+++ b/src/youdotcom/errors/youdefaulterror.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import httpx
from typing import Optional
diff --git a/src/youdotcom/errors/youerror.py b/src/youdotcom/errors/youerror.py
index cedd8c0..56e1179 100644
--- a/src/youdotcom/errors/youerror.py
+++ b/src/youdotcom/errors/youerror.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import httpx
from typing import Optional
diff --git a/src/youdotcom/httpclient.py b/src/youdotcom/httpclient.py
index 89560b5..e0ea6aa 100644
--- a/src/youdotcom/httpclient.py
+++ b/src/youdotcom/httpclient.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
# pyright: reportReturnType = false
import asyncio
diff --git a/src/youdotcom/models/__init__.py b/src/youdotcom/models/__init__.py
index ad650a3..6955718 100644
--- a/src/youdotcom/models/__init__.py
+++ b/src/youdotcom/models/__init__.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import Any, TYPE_CHECKING
@@ -13,6 +13,10 @@
WorkflowConfig,
WorkflowConfigTypedDict,
)
+ from .answercitation import AnswerCitation
+ from .answerrequestbody import AnswerRequestBody
+ from .answerresponse import AnswerResponse, AnswerResults
+ from .answersearchresult import AnswerSearchResult
from .agentruns422response_error import Detail, DetailTypedDict, Loc, LocTypedDict
from .agentrunsbatchresponse import (
AgentRunsBatchResponse,
@@ -166,7 +170,6 @@
from .searcheffort import SearchEffort
from .searchmetadata import SearchMetadata, SearchMetadataTypedDict
from .searchop import SEARCH_OP_SERVERS, SearchRequest, SearchRequestTypedDict
- from .searchpostop import SEARCH_POST_OP_SERVERS
from .searchrequestbody import SearchRequestBody, SearchRequestBodyTypedDict
from .searchresponse import (
Results,
@@ -197,6 +200,11 @@
"AGENTS_RUNS_OP_SERVERS",
"AdvancedAgentRunsRequest",
"AdvancedAgentRunsRequestTypedDict",
+ "AnswerCitation",
+ "AnswerRequestBody",
+ "AnswerResponse",
+ "AnswerResults",
+ "AnswerSearchResult",
"AgentRunsBatchResponse",
"AgentRunsBatchResponseTypedDict",
"AgentRunsResponseOutput",
@@ -320,7 +328,6 @@
"ResultsTypedDict",
"Role",
"SEARCH_OP_SERVERS",
- "SEARCH_POST_OP_SERVERS",
"SafeSearch",
"SearchEffort",
"SearchMetadata",
@@ -366,6 +373,11 @@
"ToolTypedDict": ".advancedagentrunsrequest",
"WorkflowConfig": ".advancedagentrunsrequest",
"WorkflowConfigTypedDict": ".advancedagentrunsrequest",
+ "AnswerCitation": ".answercitation",
+ "AnswerRequestBody": ".answerrequestbody",
+ "AnswerResponse": ".answerresponse",
+ "AnswerResults": ".answerresponse",
+ "AnswerSearchResult": ".answersearchresult",
"Detail": ".agentruns422response_error",
"DetailTypedDict": ".agentruns422response_error",
"Loc": ".agentruns422response_error",
@@ -497,7 +509,6 @@
"SEARCH_OP_SERVERS": ".searchop",
"SearchRequest": ".searchop",
"SearchRequestTypedDict": ".searchop",
- "SEARCH_POST_OP_SERVERS": ".searchpostop",
"SearchRequestBody": ".searchrequestbody",
"SearchRequestBodyTypedDict": ".searchrequestbody",
"Results": ".searchresponse",
diff --git a/src/youdotcom/models/advancedagentrunsrequest.py b/src/youdotcom/models/advancedagentrunsrequest.py
index 8511c0a..8519e5c 100644
--- a/src/youdotcom/models/advancedagentrunsrequest.py
+++ b/src/youdotcom/models/advancedagentrunsrequest.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .computetool import ComputeTool, ComputeToolTypedDict
diff --git a/src/youdotcom/models/agentruns422response_error.py b/src/youdotcom/models/agentruns422response_error.py
index 4124056..2d004f7 100644
--- a/src/youdotcom/models/agentruns422response_error.py
+++ b/src/youdotcom/models/agentruns422response_error.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from typing import List, Union
diff --git a/src/youdotcom/models/agentrunsbatchresponse.py b/src/youdotcom/models/agentrunsbatchresponse.py
index df8614a..de4e1c8 100644
--- a/src/youdotcom/models/agentrunsbatchresponse.py
+++ b/src/youdotcom/models/agentrunsbatchresponse.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .agentrunsresponseoutput import (
diff --git a/src/youdotcom/models/agentrunsresponseoutput.py b/src/youdotcom/models/agentrunsresponseoutput.py
index 1eaa631..f0213af 100644
--- a/src/youdotcom/models/agentrunsresponseoutput.py
+++ b/src/youdotcom/models/agentrunsresponseoutput.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .agentrunsresponsewebsearchresult import (
diff --git a/src/youdotcom/models/agentrunsresponsewebsearchresult.py b/src/youdotcom/models/agentrunsresponsewebsearchresult.py
index 7f9d3c3..0a1394f 100644
--- a/src/youdotcom/models/agentrunsresponsewebsearchresult.py
+++ b/src/youdotcom/models/agentrunsresponsewebsearchresult.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/agentrunsstreamingresponse.py b/src/youdotcom/models/agentrunsstreamingresponse.py
index 1a2c119..71e329f 100644
--- a/src/youdotcom/models/agentrunsstreamingresponse.py
+++ b/src/youdotcom/models/agentrunsstreamingresponse.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .response_created import ResponseCreated, ResponseCreatedTypedDict
diff --git a/src/youdotcom/models/agentsrunsop.py b/src/youdotcom/models/agentsrunsop.py
index 52a57d5..2b7565d 100644
--- a/src/youdotcom/models/agentsrunsop.py
+++ b/src/youdotcom/models/agentsrunsop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .advancedagentrunsrequest import (
diff --git a/src/youdotcom/models/answercitation.py b/src/youdotcom/models/answercitation.py
new file mode 100644
index 0000000..2cee1cd
--- /dev/null
+++ b/src/youdotcom/models/answercitation.py
@@ -0,0 +1,13 @@
+from __future__ import annotations
+from typing import List, Optional
+from youdotcom.types import BaseModel
+
+
+class AnswerCitation(BaseModel):
+ r"""A source cited in the answer, with supporting excerpts."""
+
+ source: str
+ r"""The URL of the cited source."""
+
+ excerpts: Optional[List[str]] = None
+ r"""Verbatim excerpts from the cited source that support the answer."""
diff --git a/src/youdotcom/models/answerrequestbody.py b/src/youdotcom/models/answerrequestbody.py
new file mode 100644
index 0000000..90a16c6
--- /dev/null
+++ b/src/youdotcom/models/answerrequestbody.py
@@ -0,0 +1,50 @@
+from __future__ import annotations
+from .country import Country
+from .freshnessvalue import FreshnessValue
+from .language import Language
+from pydantic import model_serializer
+from typing import List, Optional
+from youdotcom.types import BaseModel, UNSET_SENTINEL
+
+
+class AnswerRequestBody(BaseModel):
+ r"""Request body for ``POST /v1/answer``."""
+
+ query: str
+ r"""The search query used to retrieve relevant web results. Max 400 characters. Search operators (``site:``, ``OR``, etc.) are not supported."""
+
+ freshness: Optional[FreshnessValue] = None
+ r"""Specifies the freshness of the results. One of ``day``, ``week``, ``month``, ``year``, or ``YYYY-MM-DDtoYYYY-MM-DD``."""
+
+ country: Optional[Country] = None
+ r"""A supported country code that determines the geographical focus of the web results."""
+
+ language: Optional[Language] = None
+ r"""A supported BCP 47 language tag that determines the language of the web results."""
+
+ include_domains: Optional[List[str]] = None
+ r"""Domains to exclusively include. Cannot combine with ``exclude_domains`` or ``boost_domains``. Max 500."""
+
+ exclude_domains: Optional[List[str]] = None
+ r"""Domains to exclude. Cannot combine with ``include_domains``. Can combine with ``boost_domains``. Max 500."""
+
+ boost_domains: Optional[List[str]] = None
+ r"""Domains to prefer in ranking. Cannot combine with ``include_domains``. Can combine with ``exclude_domains``. Max 500."""
+
+ @model_serializer(mode="wrap")
+ def serialize_model(self, handler):
+ optional_fields = set(
+ ["freshness", "country", "language", "include_domains", "exclude_domains", "boost_domains"]
+ )
+ serialized = handler(self)
+ m = {}
+
+ for n, f in type(self).model_fields.items():
+ k = f.alias or n
+ val = serialized.get(k, serialized.get(n))
+
+ if val != UNSET_SENTINEL:
+ if val is not None or k not in optional_fields:
+ m[k] = val
+
+ return m
diff --git a/src/youdotcom/models/answerresponse.py b/src/youdotcom/models/answerresponse.py
new file mode 100644
index 0000000..2b67887
--- /dev/null
+++ b/src/youdotcom/models/answerresponse.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+from .answercitation import AnswerCitation
+from .answersearchresult import AnswerSearchResult
+from typing import List, Optional
+from youdotcom.types import BaseModel
+
+
+class AnswerResults(BaseModel):
+ r"""Search results grouped by result type."""
+
+ web: Optional[List[AnswerSearchResult]] = None
+ r"""All web search results considered during answer synthesis."""
+
+
+class AnswerResponse(BaseModel):
+ r"""A synthesized answer with citations and supporting search results."""
+
+ answer: str
+ r"""The synthesized response with numbered inline citations that reference items in the ``citations`` array."""
+
+ citations: Optional[List[AnswerCitation]] = None
+ r"""The sources cited in the answer, in citation order."""
+
+ results: Optional[AnswerResults] = None
+ r"""Search results grouped by result type."""
diff --git a/src/youdotcom/models/answersearchresult.py b/src/youdotcom/models/answersearchresult.py
new file mode 100644
index 0000000..3286c63
--- /dev/null
+++ b/src/youdotcom/models/answersearchresult.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+from typing import List, Optional
+from youdotcom.types import BaseModel
+
+
+class AnswerSearchResult(BaseModel):
+ r"""A web search result used during answer synthesis."""
+
+ url: str
+ r"""The URL of the source webpage."""
+
+ title: str
+ r"""The title of the source webpage."""
+
+ snippets: Optional[List[str]] = None
+ r"""Text snippets from the search result that preview its content."""
+
+ page_age: Optional[str] = None
+ r"""The publication date or age supplied by the search result."""
diff --git a/src/youdotcom/models/computetool.py b/src/youdotcom/models/computetool.py
index 1253881..3fc3886 100644
--- a/src/youdotcom/models/computetool.py
+++ b/src/youdotcom/models/computetool.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/contents.py b/src/youdotcom/models/contents.py
index 37d69b8..f2423a3 100644
--- a/src/youdotcom/models/contents.py
+++ b/src/youdotcom/models/contents.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/contentsformats.py b/src/youdotcom/models/contentsformats.py
index 91f16e0..c7d8ee9 100644
--- a/src/youdotcom/models/contentsformats.py
+++ b/src/youdotcom/models/contentsformats.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/contentsmetadata.py b/src/youdotcom/models/contentsmetadata.py
index b6b8e50..bba55c3 100644
--- a/src/youdotcom/models/contentsmetadata.py
+++ b/src/youdotcom/models/contentsmetadata.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/contentsop.py b/src/youdotcom/models/contentsop.py
index 9e10292..2b59919 100644
--- a/src/youdotcom/models/contentsop.py
+++ b/src/youdotcom/models/contentsop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .contentsformats import ContentsFormats
@@ -10,7 +10,7 @@
CONTENTS_OP_SERVERS = [
- "https://ydc-index.io",
+ "https://api.you.com",
]
diff --git a/src/youdotcom/models/country.py b/src/youdotcom/models/country.py
index 720e606..8169402 100644
--- a/src/youdotcom/models/country.py
+++ b/src/youdotcom/models/country.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/customagentrunsrequest.py b/src/youdotcom/models/customagentrunsrequest.py
index 181e560..285dcf7 100644
--- a/src/youdotcom/models/customagentrunsrequest.py
+++ b/src/youdotcom/models/customagentrunsrequest.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/expressagentrunsrequest.py b/src/youdotcom/models/expressagentrunsrequest.py
index 8211741..85d2910 100644
--- a/src/youdotcom/models/expressagentrunsrequest.py
+++ b/src/youdotcom/models/expressagentrunsrequest.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .websearchtool import WebSearchTool, WebSearchToolTypedDict
diff --git a/src/youdotcom/models/finance_researchop.py b/src/youdotcom/models/finance_researchop.py
index 47259fd..f28fc6d 100644
--- a/src/youdotcom/models/finance_researchop.py
+++ b/src/youdotcom/models/finance_researchop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .financeresearcheffort import FinanceResearchEffort
diff --git a/src/youdotcom/models/financeresearcheffort.py b/src/youdotcom/models/financeresearcheffort.py
index 909b887..9478b0f 100644
--- a/src/youdotcom/models/financeresearcheffort.py
+++ b/src/youdotcom/models/financeresearcheffort.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
# Manual overlay: `lite` tier added via overlays/python_overlay.yaml
# until the upstream OpenAPI spec includes it. See that file for details.
diff --git a/src/youdotcom/models/freshness.py b/src/youdotcom/models/freshness.py
index 83281eb..5228076 100644
--- a/src/youdotcom/models/freshness.py
+++ b/src/youdotcom/models/freshness.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/freshnessvalue.py b/src/youdotcom/models/freshnessvalue.py
index 66ff778..8742860 100644
--- a/src/youdotcom/models/freshnessvalue.py
+++ b/src/youdotcom/models/freshnessvalue.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .freshness import Freshness
diff --git a/src/youdotcom/models/getresearchtaskop.py b/src/youdotcom/models/getresearchtaskop.py
index 0330943..914f673 100644
--- a/src/youdotcom/models/getresearchtaskop.py
+++ b/src/youdotcom/models/getresearchtaskop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from typing_extensions import Annotated, TypedDict
diff --git a/src/youdotcom/models/language.py b/src/youdotcom/models/language.py
index 83704f1..852c77d 100644
--- a/src/youdotcom/models/language.py
+++ b/src/youdotcom/models/language.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/livecrawl.py b/src/youdotcom/models/livecrawl.py
index c28464e..aac7e1d 100644
--- a/src/youdotcom/models/livecrawl.py
+++ b/src/youdotcom/models/livecrawl.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/livecrawlformats.py b/src/youdotcom/models/livecrawlformats.py
index ceca02d..18eb4cd 100644
--- a/src/youdotcom/models/livecrawlformats.py
+++ b/src/youdotcom/models/livecrawlformats.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/newsresult.py b/src/youdotcom/models/newsresult.py
index 9a89295..d414648 100644
--- a/src/youdotcom/models/newsresult.py
+++ b/src/youdotcom/models/newsresult.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .contents import Contents, ContentsTypedDict
diff --git a/src/youdotcom/models/reportverbosity.py b/src/youdotcom/models/reportverbosity.py
index 14a8b43..a767bb7 100644
--- a/src/youdotcom/models/reportverbosity.py
+++ b/src/youdotcom/models/reportverbosity.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/researcheffort.py b/src/youdotcom/models/researcheffort.py
index 506ea33..4da2dcf 100644
--- a/src/youdotcom/models/researcheffort.py
+++ b/src/youdotcom/models/researcheffort.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
# Manual overlay: `frontier` tier added via overlays/python_overlay.yaml
# until the upstream OpenAPI spec includes it. See that file for details.
diff --git a/src/youdotcom/models/researchop.py b/src/youdotcom/models/researchop.py
index 15349a0..151bc33 100644
--- a/src/youdotcom/models/researchop.py
+++ b/src/youdotcom/models/researchop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .researcheffort import ResearchEffort
diff --git a/src/youdotcom/models/researchresponse.py b/src/youdotcom/models/researchresponse.py
index 8e0ef6c..f1a29b1 100644
--- a/src/youdotcom/models/researchresponse.py
+++ b/src/youdotcom/models/researchresponse.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/researchtaskstreamevent.py b/src/youdotcom/models/researchtaskstreamevent.py
index fe7f123..6596a05 100644
--- a/src/youdotcom/models/researchtaskstreamevent.py
+++ b/src/youdotcom/models/researchtaskstreamevent.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/researchtool.py b/src/youdotcom/models/researchtool.py
index 445f60c..e2465aa 100644
--- a/src/youdotcom/models/researchtool.py
+++ b/src/youdotcom/models/researchtool.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .reportverbosity import ReportVerbosity
diff --git a/src/youdotcom/models/response_created.py b/src/youdotcom/models/response_created.py
index 509085d..9385569 100644
--- a/src/youdotcom/models/response_created.py
+++ b/src/youdotcom/models/response_created.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/response_done.py b/src/youdotcom/models/response_done.py
index 599f8a7..152b8af 100644
--- a/src/youdotcom/models/response_done.py
+++ b/src/youdotcom/models/response_done.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/response_output_content_full.py b/src/youdotcom/models/response_output_content_full.py
index b2bd70c..00e55df 100644
--- a/src/youdotcom/models/response_output_content_full.py
+++ b/src/youdotcom/models/response_output_content_full.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .agentrunsresponsewebsearchresult import (
diff --git a/src/youdotcom/models/response_output_item_added.py b/src/youdotcom/models/response_output_item_added.py
index 7294962..78fcf52 100644
--- a/src/youdotcom/models/response_output_item_added.py
+++ b/src/youdotcom/models/response_output_item_added.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/response_output_item_done.py b/src/youdotcom/models/response_output_item_done.py
index 54f4cbc..240cb11 100644
--- a/src/youdotcom/models/response_output_item_done.py
+++ b/src/youdotcom/models/response_output_item_done.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/response_output_text_delta.py b/src/youdotcom/models/response_output_text_delta.py
index 36aa5a4..de88170 100644
--- a/src/youdotcom/models/response_output_text_delta.py
+++ b/src/youdotcom/models/response_output_text_delta.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/response_starting.py b/src/youdotcom/models/response_starting.py
index 438a191..47b292c 100644
--- a/src/youdotcom/models/response_starting.py
+++ b/src/youdotcom/models/response_starting.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/models/safesearch.py b/src/youdotcom/models/safesearch.py
index 5a64287..bb32a49 100644
--- a/src/youdotcom/models/safesearch.py
+++ b/src/youdotcom/models/safesearch.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/searcheffort.py b/src/youdotcom/models/searcheffort.py
index cec092e..9d4331d 100644
--- a/src/youdotcom/models/searcheffort.py
+++ b/src/youdotcom/models/searcheffort.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/searchmetadata.py b/src/youdotcom/models/searchmetadata.py
index 758b0c2..f788e74 100644
--- a/src/youdotcom/models/searchmetadata.py
+++ b/src/youdotcom/models/searchmetadata.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/searchop.py b/src/youdotcom/models/searchop.py
index 358a4fa..e2e3120 100644
--- a/src/youdotcom/models/searchop.py
+++ b/src/youdotcom/models/searchop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .country import Country
@@ -15,7 +15,7 @@
SEARCH_OP_SERVERS = [
- "https://ydc-index.io",
+ "https://api.you.com",
]
diff --git a/src/youdotcom/models/searchpostop.py b/src/youdotcom/models/searchpostop.py
deleted file mode 100644
index e0f8b5b..0000000
--- a/src/youdotcom/models/searchpostop.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
-from __future__ import annotations
-
-
-SEARCH_POST_OP_SERVERS = [
- "https://ydc-index.io",
-]
diff --git a/src/youdotcom/models/searchrequestbody.py b/src/youdotcom/models/searchrequestbody.py
index df94c4c..247db23 100644
--- a/src/youdotcom/models/searchrequestbody.py
+++ b/src/youdotcom/models/searchrequestbody.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .country import Country
diff --git a/src/youdotcom/models/searchresponse.py b/src/youdotcom/models/searchresponse.py
index ee9313e..e17262f 100644
--- a/src/youdotcom/models/searchresponse.py
+++ b/src/youdotcom/models/searchresponse.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .newsresult import NewsResult, NewsResultTypedDict
diff --git a/src/youdotcom/models/security.py b/src/youdotcom/models/security.py
index 2313b52..765679f 100644
--- a/src/youdotcom/models/security.py
+++ b/src/youdotcom/models/security.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/streamresearchtaskop.py b/src/youdotcom/models/streamresearchtaskop.py
index 6b57b1d..8bc0384 100644
--- a/src/youdotcom/models/streamresearchtaskop.py
+++ b/src/youdotcom/models/streamresearchtaskop.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from pydantic import model_serializer
diff --git a/src/youdotcom/models/taskdetail.py b/src/youdotcom/models/taskdetail.py
index f039294..a8e4a6c 100644
--- a/src/youdotcom/models/taskdetail.py
+++ b/src/youdotcom/models/taskdetail.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from datetime import datetime
diff --git a/src/youdotcom/models/taskresponse.py b/src/youdotcom/models/taskresponse.py
index 490476d..3f5fe9a 100644
--- a/src/youdotcom/models/taskresponse.py
+++ b/src/youdotcom/models/taskresponse.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from datetime import datetime
diff --git a/src/youdotcom/models/verbosity.py b/src/youdotcom/models/verbosity.py
index 666d75a..a87e176 100644
--- a/src/youdotcom/models/verbosity.py
+++ b/src/youdotcom/models/verbosity.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from enum import Enum
diff --git a/src/youdotcom/models/webresult.py b/src/youdotcom/models/webresult.py
index 27df725..73edde0 100644
--- a/src/youdotcom/models/webresult.py
+++ b/src/youdotcom/models/webresult.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
from .contents import Contents, ContentsTypedDict
diff --git a/src/youdotcom/models/websearchtool.py b/src/youdotcom/models/websearchtool.py
index 7834ec3..b396e44 100644
--- a/src/youdotcom/models/websearchtool.py
+++ b/src/youdotcom/models/websearchtool.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
import pydantic
diff --git a/src/youdotcom/research_helpers.py b/src/youdotcom/research_helpers.py
index a4b0078..e56bfbf 100644
--- a/src/youdotcom/research_helpers.py
+++ b/src/youdotcom/research_helpers.py
@@ -1,7 +1,6 @@
"""Hand-maintained research workflow helpers.
-This module is NOT regenerated by Speakeasy. It adds convenience helpers on
-top of the auto-generated research endpoints:
+Convenience helpers on top of the research endpoints:
- ``research_background`` / ``research_background_async``: Submit a research
task with ``background=True`` and return the ``TaskResponse`` directly so
diff --git a/src/youdotcom/runs.py b/src/youdotcom/runs.py
deleted file mode 100644
index 0c63569..0000000
--- a/src/youdotcom/runs.py
+++ /dev/null
@@ -1,293 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
-from .basesdk import BaseSDK
-from typing import Any, Mapping, Optional, Union, cast
-from youdotcom import errors, models, utils
-from youdotcom._hooks import HookContext
-from youdotcom.types import BaseModel, OptionalNullable, UNSET
-from youdotcom.utils import eventstreaming, get_security_from_env
-from youdotcom.utils.unmarshal_json_response import unmarshal_json_response
-
-
-class Runs(BaseSDK):
- def create(
- self,
- *,
- request: Union[models.AgentsRunsRequest, models.AgentsRunsRequestTypedDict],
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> Union[
- models.AgentRunsBatchResponse,
- eventstreaming.EventStream[models.AgentRunsStreamingResponse],
- ]:
- r"""Run an Agent
-
- Execute queries using You.com's AI agents. This endpoint supports three agent types:
-
- - **Express Agent**: Fast responses with optional web search (max 1 search)
- - **Advanced Agent**: Complex queries with multi-turn reasoning, planning, and tool usage
- - **Custom Agent**: User-configured assistants created in the You.com UI
-
- The response format depends on the `stream` parameter - either a complete JSON payload or Server-Sent Events (SSE).
-
-
- :param request: The request object to send.
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.AGENTS_RUNS_OP_SERVERS[0]
-
- if not isinstance(request, BaseModel):
- request = utils.unmarshal(request, models.AgentsRunsRequest)
- request = cast(models.AgentsRunsRequest, request)
-
- req = self._build_request(
- method="POST",
- path="/v1/agents/runs",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=True,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="text/event-stream"
- if getattr(request, "stream", False) is True
- else "application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.AgentsRunsRequest
- ),
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = self.do_request(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="AgentsRuns",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["agents.runs"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- stream=getattr(request, "stream", False) is True,
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- http_res_text = utils.stream_to_text(http_res)
- return unmarshal_json_response(
- models.AgentRunsBatchResponse, http_res, http_res_text
- )
- if utils.match_response(http_res, "200", "text/event-stream"):
- return eventstreaming.EventStream(
- http_res,
- lambda raw: unmarshal_json_response(
- models.AgentRunsStreamingResponse, http_res, raw
- ),
- client_ref=self,
- )
- if utils.match_response(http_res, "400", "application/json"):
- http_res_text = utils.stream_to_text(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns400ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns400ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "401", "application/json"):
- http_res_text = utils.stream_to_text(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns401ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns401ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "422", "application/json"):
- http_res_text = utils.stream_to_text(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns422ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns422ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError(
- "Unexpected response received", http_res, http_res_text
- )
-
- async def create_async(
- self,
- *,
- request: Union[models.AgentsRunsRequest, models.AgentsRunsRequestTypedDict],
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> Union[
- models.AgentRunsBatchResponse,
- eventstreaming.EventStreamAsync[models.AgentRunsStreamingResponse],
- ]:
- r"""Run an Agent
-
- Execute queries using You.com's AI agents. This endpoint supports three agent types:
-
- - **Express Agent**: Fast responses with optional web search (max 1 search)
- - **Advanced Agent**: Complex queries with multi-turn reasoning, planning, and tool usage
- - **Custom Agent**: User-configured assistants created in the You.com UI
-
- The response format depends on the `stream` parameter - either a complete JSON payload or Server-Sent Events (SSE).
-
-
- :param request: The request object to send.
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.AGENTS_RUNS_OP_SERVERS[0]
-
- if not isinstance(request, BaseModel):
- request = utils.unmarshal(request, models.AgentsRunsRequest)
- request = cast(models.AgentsRunsRequest, request)
-
- req = self._build_request_async(
- method="POST",
- path="/v1/agents/runs",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=True,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="text/event-stream"
- if getattr(request, "stream", False) is True
- else "application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.AgentsRunsRequest
- ),
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = await self.do_request_async(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="AgentsRuns",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["agents.runs"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- stream=getattr(request, "stream", False) is True,
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- http_res_text = await utils.stream_to_text_async(http_res)
- return unmarshal_json_response(
- models.AgentRunsBatchResponse, http_res, http_res_text
- )
- if utils.match_response(http_res, "200", "text/event-stream"):
- return eventstreaming.EventStreamAsync(
- http_res,
- lambda raw: unmarshal_json_response(
- models.AgentRunsStreamingResponse, http_res, raw
- ),
- client_ref=self,
- )
- if utils.match_response(http_res, "400", "application/json"):
- http_res_text = await utils.stream_to_text_async(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns400ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns400ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "401", "application/json"):
- http_res_text = await utils.stream_to_text_async(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns401ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns401ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "422", "application/json"):
- http_res_text = await utils.stream_to_text_async(http_res)
- response_data = unmarshal_json_response(
- errors.AgentRuns422ResponseErrorData, http_res, http_res_text
- )
- raise errors.AgentRuns422ResponseError(
- response_data, http_res, http_res_text
- )
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError(
- "Unexpected response received", http_res, http_res_text
- )
diff --git a/src/youdotcom/sdk.py b/src/youdotcom/sdk.py
index 166fce4..f6ce3a7 100644
--- a/src/youdotcom/sdk.py
+++ b/src/youdotcom/sdk.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from .basesdk import BaseSDK
from .httpclient import AsyncHttpClient, ClientOwner, HttpClient, close_clients
@@ -6,8 +6,6 @@
from .utils.logger import Logger, get_default_logger
from .utils.retries import RetryConfig
import httpx
-import importlib
-import sys
from typing import (
Any,
Callable,
@@ -16,22 +14,16 @@
List,
Mapping,
Optional,
- TYPE_CHECKING,
Union,
cast,
)
import weakref
from youdotcom import errors, models, utils
from youdotcom._hooks import HookContext, SDKHooks
-from youdotcom.types import OptionalNullable, UNSET
+from youdotcom.types import BaseModel, OptionalNullable, UNSET
from youdotcom.utils import eventstreaming, get_security_from_env
from youdotcom.utils.unmarshal_json_response import unmarshal_json_response
-if TYPE_CHECKING:
- from youdotcom.agents import Agents
- from youdotcom.contents_sdk import ContentsSDK
- from youdotcom.search import Search
-
class You(BaseSDK):
r"""You.com API: Unified API for Express, Advanced, and Custom Agents from You.com
@@ -41,21 +33,13 @@ class You(BaseSDK):
Finance-focused multi-step research with competitive accuracy at same price points and latencies as the Research API
Comprehensive API for You.com services:
- **Agents API**: Execute queries using Express, Advanced, and Custom AI agents
+ - **Answer API**: Get synthesized, citation-backed answers grounded in real-time web results
- **Research API**: In-depth, multi-step research with citations and sources
- **Finance Research API**: Finance-focused multi-step research with citations and sources
- **Search API**: Get search results from web and news sources
- **Contents API**: Retrieve and process web page content
"""
- agents: "Agents"
- search: "Search"
- contents: "ContentsSDK"
- _sub_sdk_map = {
- "agents": ("youdotcom.agents", "Agents"),
- "search": ("youdotcom.search", "Search"),
- "contents": ("youdotcom.contents_sdk", "ContentsSDK"),
- }
-
def __init__(
self,
api_key_auth: Optional[
@@ -149,44 +133,6 @@ def __init__(
self.sdk_configuration.async_client_supplied,
)
- def dynamic_import(self, modname, retries=3):
- for attempt in range(retries):
- try:
- return importlib.import_module(modname)
- except KeyError:
- # Clear any half-initialized module and retry
- sys.modules.pop(modname, None)
- if attempt == retries - 1:
- break
- raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
-
- def __getattr__(self, name: str):
- if name in self._sub_sdk_map:
- module_path, class_name = self._sub_sdk_map[name]
- try:
- module = self.dynamic_import(module_path)
- klass = getattr(module, class_name)
- instance = klass(self.sdk_configuration, parent_ref=self)
- setattr(self, name, instance)
- return instance
- except ImportError as e:
- raise AttributeError(
- f"Failed to import module {module_path} for attribute {name}: {e}"
- ) from e
- except AttributeError as e:
- raise AttributeError(
- f"Failed to find class {class_name} in module {module_path} for attribute {name}: {e}"
- ) from e
-
- raise AttributeError(
- f"'{type(self).__name__}' object has no attribute '{name}'"
- )
-
- def __dir__(self):
- default_attrs = list(super().__dir__())
- lazy_attrs = list(self._sub_sdk_map.keys())
- return sorted(list(set(default_attrs + lazy_attrs)))
-
def __enter__(self):
return self
@@ -209,57 +155,49 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.sdk_configuration.async_client.aclose()
self.sdk_configuration.async_client = None
- def search_post(
+ def answer(
self,
*,
query: str,
- count: Optional[int] = 10,
freshness: Optional[
Union[models.FreshnessValue, models.FreshnessValueTypedDict]
] = None,
- offset: Optional[int] = None,
country: Optional[models.Country] = None,
- language: Optional[models.Language] = models.Language.EN,
- safesearch: Optional[models.SafeSearch] = None,
- livecrawl: Optional[models.LiveCrawl] = None,
- livecrawl_formats: Optional[Iterable[models.LiveCrawlFormats]] = None,
+ language: Optional[models.Language] = None,
include_domains: Optional[Iterable[str]] = None,
exclude_domains: Optional[Iterable[str]] = None,
boost_domains: Optional[Iterable[str]] = None,
- crawl_timeout: Optional[int] = 10,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
http_headers: Optional[Mapping[str, str]] = None,
- ) -> models.SearchResponse:
- r"""Returns a list of unified search results from web and news sources
-
- This endpoint is designed to return LLM-ready web results based on a user's query. Based on a classification mechanism, it can return web results and news associated with your query. If you need to feed an LLM with the results of a query that sounds like `What are the latest geopolitical updates from India`, then this endpoint is the right one for you.
-
- `POST` is the recommended method when using complex parameters such as `include_domains` or `exclude_domains`. These fields accept JSON arrays in the request body, which is unambiguous and supports up to 500 domains per request—something that would exceed URL length limits with GET. Use GET for simple queries where HTTP cacheability matters.
-
- :param query: The search query used to retrieve relevant results from the web. You can also include [search operators](https://docs.you.com/search/search-operators) to refine your search.
- :param count: Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).
- :param freshness: Specifies the freshness of the results to return. Provide either one of `day`, `week`, `month`, `year`, or a date range string in the format `YYYY-MM-DDtoYYYY-MM-DD`.
-
- When your search query includes a temporal keyword and you also set a freshness parameter, the search will use the broader (i.e., less restrictive) of the two timeframes. For example, if you use `query=news+this+week&freshness=month`, the results will use a freshness of month.
- :param offset: Indicates the `offset` for pagination. The `offset` is calculated in multiples of `count`. For example, if `count = 5` and `offset = 1`, results 5–10 will be returned. Range `0 ≤ offset ≤ 9`.
- :param country: The country code that determines the geographical focus of the web results.
- :param language: The language of the web results that will be returned (BCP 47 format).
- :param safesearch: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.
- :param livecrawl: Indicates which section(s) of search results to livecrawl and return full page content.
- :param livecrawl_formats: Indicates the format(s) of the livecrawled content. Pass one or both values (`html`, `markdown`). In a GET request, repeat the parameter: `?livecrawl_formats=html&livecrawl_formats=markdown`. In a POST body, provide a JSON array: `[\"html\", \"markdown\"]`.
- :param include_domains: A list of domains to restrict search results to. Only results from these domains will be returned. Supports up to 500 domains. This is a strict allowlist, not a boost — results are limited exclusively to the specified domains.
-
- Cannot be combined with `exclude_domains`; passing both will return a `422` error.
- :param exclude_domains: A list of domains to exclude from search results. Results from these domains will be filtered out. Supports up to 500 domains.
-
- Cannot be combined with `include_domains`; passing both will return a `422` error.
- :param boost_domains: A list of domains to boost in search ranking. Matching results from these domains receive a relative ranking boost, but results are not limited to these domains. Supports up to 500 domains. Can be combined with `exclude_domains`, but cannot be combined with `include_domains` (returns `422`).
- :param crawl_timeout: Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds.
+ ) -> models.AnswerResponse:
+ r"""Returns a synthesized answer with citations from web search results.
+
+ Provide a ``query`` and optional freshness, locale, and domain controls.
+ The response includes a markdown answer with inline citations, a
+ citations array with source URLs and supporting excerpts, and the web
+ results used to generate the answer.
+
+ :param query: The search query used to retrieve relevant web results.
+ Max 400 characters. Search operators (``site:``, ``OR``, etc.) are
+ not supported.
+ :param freshness: Specifies the freshness of the results. One of ``day``,
+ ``week``, ``month``, ``year``, or ``YYYY-MM-DDtoYYYY-MM-DD``.
+ :param country: A supported country code that determines the geographical
+ focus of the web results.
+ :param language: A supported BCP 47 language tag that determines the
+ language of the web results.
+ :param include_domains: Domains to exclusively include. Cannot combine
+ with ``exclude_domains`` or ``boost_domains``. Max 500.
+ :param exclude_domains: Domains to exclude. Cannot combine with
+ ``include_domains``. Can combine with ``boost_domains``. Max 500.
+ :param boost_domains: Domains to prefer in ranking. Cannot combine with
+ ``include_domains``. Can combine with ``exclude_domains``. Max 500.
:param retries: Override the default retry configuration for this method
:param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param timeout_ms: Override the default request timeout configuration for
+ this method in milliseconds
:param http_headers: Additional headers to set or replace on requests.
"""
base_url = None
@@ -270,41 +208,34 @@ def search_post(
if server_url is not None:
base_url = server_url
else:
- base_url = models.SEARCH_POST_OP_SERVERS[0]
+ base_url = self._get_url(None, None)
- request = models.SearchRequestBody(
+ body: dict = dict(
query=query,
- count=count,
freshness=freshness,
- offset=offset,
- country=country,
- language=language,
- safesearch=safesearch,
- livecrawl=livecrawl,
- livecrawl_formats=utils.unmarshal(
- livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
- ),
+ country=country.upper() if isinstance(country, str) else country,
+ language=language.upper() if isinstance(language, str) else language,
include_domains=utils.unmarshal(include_domains, Optional[List[str]]),
exclude_domains=utils.unmarshal(exclude_domains, Optional[List[str]]),
boost_domains=utils.unmarshal(boost_domains, Optional[List[str]]),
- crawl_timeout=crawl_timeout,
)
+ request = models.AnswerRequestBody(**body)
req = self._build_request(
method="POST",
- path="/v1/search",
+ path="/v1/answer",
base_url=base_url,
url_variables=url_variables,
request=request,
request_body_required=True,
request_has_path_params=False,
- request_has_query_params=True,
+ request_has_query_params=False,
user_agent_header="user-agent",
accept_header_value="application/json",
http_headers=http_headers,
security=self.sdk_configuration.security,
get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.SearchRequestBody
+ request, False, False, "json", models.AnswerRequestBody
),
allow_empty_value=None,
timeout_ms=timeout_ms,
@@ -322,12 +253,12 @@ def search_post(
hook_ctx=HookContext(
config=self.sdk_configuration,
base_url=base_url or "",
- operation_id="searchPost",
+ operation_id="answer",
oauth2_scopes=None,
security_source=get_security_from_env(
self.sdk_configuration.security, models.Security
),
- tags=None,
+ tags=["answer"],
extensions=None,
),
request=req,
@@ -337,12 +268,17 @@ def search_post(
response_data: Any = None
if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(models.SearchResponse, http_res)
+ return unmarshal_json_response(models.AnswerResponse, http_res)
if utils.match_response(http_res, "401", "application/json"):
response_data = unmarshal_json_response(
errors.UnauthorizedResponseErrorData, http_res
)
raise errors.UnauthorizedResponseError(response_data, http_res)
+ if utils.match_response(http_res, "402", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.PaymentRequiredResponseErrorData, http_res
+ )
+ raise errors.PaymentRequiredResponseError(response_data, http_res)
if utils.match_response(http_res, "403", "application/json"):
response_data = unmarshal_json_response(
errors.ForbiddenResponseErrorData, http_res
@@ -367,54 +303,178 @@ def search_post(
raise errors.YouDefaultError("Unexpected response received", http_res)
- async def search_post_async(
+ async def answer_async(
self,
*,
query: str,
- count: Optional[int] = 10,
freshness: Optional[
Union[models.FreshnessValue, models.FreshnessValueTypedDict]
] = None,
- offset: Optional[int] = None,
country: Optional[models.Country] = None,
- language: Optional[models.Language] = models.Language.EN,
- safesearch: Optional[models.SafeSearch] = None,
- livecrawl: Optional[models.LiveCrawl] = None,
- livecrawl_formats: Optional[Iterable[models.LiveCrawlFormats]] = None,
+ language: Optional[models.Language] = None,
include_domains: Optional[Iterable[str]] = None,
exclude_domains: Optional[Iterable[str]] = None,
boost_domains: Optional[Iterable[str]] = None,
- crawl_timeout: Optional[int] = 10,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
http_headers: Optional[Mapping[str, str]] = None,
- ) -> models.SearchResponse:
- r"""Returns a list of unified search results from web and news sources
+ ) -> models.AnswerResponse:
+ r"""Returns a synthesized answer with citations from web search results.
+
+ Provide a ``query`` and optional freshness, locale, and domain controls.
+ The response includes a markdown answer with inline citations, a
+ citations array with source URLs and supporting excerpts, and the web
+ results used to generate the answer.
+
+ :param query: The search query used to retrieve relevant web results.
+ Max 400 characters. Search operators (``site:``, ``OR``, etc.) are
+ not supported.
+ :param freshness: Specifies the freshness of the results. One of ``day``,
+ ``week``, ``month``, ``year``, or ``YYYY-MM-DDtoYYYY-MM-DD``.
+ :param country: A supported country code that determines the geographical
+ focus of the web results.
+ :param language: A supported BCP 47 language tag that determines the
+ language of the web results.
+ :param include_domains: Domains to exclusively include. Cannot combine
+ with ``exclude_domains`` or ``boost_domains``. Max 500.
+ :param exclude_domains: Domains to exclude. Cannot combine with
+ ``include_domains``. Can combine with ``boost_domains``. Max 500.
+ :param boost_domains: Domains to prefer in ranking. Cannot combine with
+ ``include_domains``. Can combine with ``exclude_domains``. Max 500.
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for
+ this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ body: dict = dict(
+ query=query,
+ freshness=freshness,
+ country=country.upper() if isinstance(country, str) else country,
+ language=language.upper() if isinstance(language, str) else language,
+ include_domains=utils.unmarshal(include_domains, Optional[List[str]]),
+ exclude_domains=utils.unmarshal(exclude_domains, Optional[List[str]]),
+ boost_domains=utils.unmarshal(boost_domains, Optional[List[str]]),
+ )
+ request = models.AnswerRequestBody(**body)
+
+ req = self._build_request_async(
+ method="POST",
+ path="/v1/answer",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=False,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.AnswerRequestBody
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = await self.do_request_async(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="answer",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["answer"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(models.AnswerResponse, http_res)
+ if utils.match_response(http_res, "401", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.UnauthorizedResponseErrorData, http_res
+ )
+ raise errors.UnauthorizedResponseError(response_data, http_res)
+ if utils.match_response(http_res, "402", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.PaymentRequiredResponseErrorData, http_res
+ )
+ raise errors.PaymentRequiredResponseError(response_data, http_res)
+ if utils.match_response(http_res, "403", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ForbiddenResponseErrorData, http_res
+ )
+ raise errors.ForbiddenResponseError(response_data, http_res)
+ if utils.match_response(http_res, "422", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.UnprocessableEntityResponseErrorData, http_res
+ )
+ raise errors.UnprocessableEntityResponseError(response_data, http_res)
+ if utils.match_response(http_res, "500", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.InternalServerErrorResponseData, http_res
+ )
+ raise errors.InternalServerErrorResponse(response_data, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ raise errors.YouDefaultError("Unexpected response received", http_res)
- This endpoint is designed to return LLM-ready web results based on a user's query. Based on a classification mechanism, it can return web results and news associated with your query. If you need to feed an LLM with the results of a query that sounds like `What are the latest geopolitical updates from India`, then this endpoint is the right one for you.
+ def agents(
+ self,
+ *,
+ request: Union[models.AgentsRunsRequest, models.AgentsRunsRequestTypedDict],
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> Union[
+ models.AgentRunsBatchResponse,
+ eventstreaming.EventStream[models.AgentRunsStreamingResponse],
+ ]:
+ r"""Run an Agent
- `POST` is the recommended method when using complex parameters such as `include_domains` or `exclude_domains`. These fields accept JSON arrays in the request body, which is unambiguous and supports up to 500 domains per request—something that would exceed URL length limits with GET. Use GET for simple queries where HTTP cacheability matters.
+ Execute queries using You.com's AI agents. This endpoint supports three agent types:
- :param query: The search query used to retrieve relevant results from the web. You can also include [search operators](https://docs.you.com/search/search-operators) to refine your search.
- :param count: Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).
- :param freshness: Specifies the freshness of the results to return. Provide either one of `day`, `week`, `month`, `year`, or a date range string in the format `YYYY-MM-DDtoYYYY-MM-DD`.
+ - **Express Agent**: Fast responses with optional web search (max 1 search)
+ - **Advanced Agent**: Complex queries with multi-turn reasoning, planning, and tool usage
+ - **Custom Agent**: User-configured assistants created in the You.com UI
- When your search query includes a temporal keyword and you also set a freshness parameter, the search will use the broader (i.e., less restrictive) of the two timeframes. For example, if you use `query=news+this+week&freshness=month`, the results will use a freshness of month.
- :param offset: Indicates the `offset` for pagination. The `offset` is calculated in multiples of `count`. For example, if `count = 5` and `offset = 1`, results 5–10 will be returned. Range `0 ≤ offset ≤ 9`.
- :param country: The country code that determines the geographical focus of the web results.
- :param language: The language of the web results that will be returned (BCP 47 format).
- :param safesearch: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.
- :param livecrawl: Indicates which section(s) of search results to livecrawl and return full page content.
- :param livecrawl_formats: Indicates the format(s) of the livecrawled content. Pass one or both values (`html`, `markdown`). In a GET request, repeat the parameter: `?livecrawl_formats=html&livecrawl_formats=markdown`. In a POST body, provide a JSON array: `[\"html\", \"markdown\"]`.
- :param include_domains: A list of domains to restrict search results to. Only results from these domains will be returned. Supports up to 500 domains. This is a strict allowlist, not a boost — results are limited exclusively to the specified domains.
+ The response format depends on the `stream` parameter - either a complete JSON payload or Server-Sent Events (SSE).
- Cannot be combined with `exclude_domains`; passing both will return a `422` error.
- :param exclude_domains: A list of domains to exclude from search results. Results from these domains will be filtered out. Supports up to 500 domains.
- Cannot be combined with `include_domains`; passing both will return a `422` error.
- :param boost_domains: A list of domains to boost in search ranking. Matching results from these domains receive a relative ranking boost, but results are not limited to these domains. Supports up to 500 domains. Can be combined with `exclude_domains`, but cannot be combined with `include_domains` (returns `422`).
- :param crawl_timeout: Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds.
+ :param request: The request object to send.
:param retries: Override the default retry configuration for this method
:param server_url: Override the default server URL for this method
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
@@ -428,29 +488,156 @@ async def search_post_async(
if server_url is not None:
base_url = server_url
else:
- base_url = models.SEARCH_POST_OP_SERVERS[0]
+ base_url = self._get_url(None, None)
- request = models.SearchRequestBody(
- query=query,
- count=count,
- freshness=freshness,
- offset=offset,
- country=country,
- language=language,
- safesearch=safesearch,
- livecrawl=livecrawl,
- livecrawl_formats=utils.unmarshal(
- livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
+ if not isinstance(request, BaseModel):
+ request = utils.unmarshal(request, models.AgentsRunsRequest)
+ request = cast(models.AgentsRunsRequest, request)
+
+ req = self._build_request(
+ method="POST",
+ path="/v1/agents/runs",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="text/event-stream"
+ if getattr(request, "stream", False) is True
+ else "application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.AgentsRunsRequest
),
- include_domains=utils.unmarshal(include_domains, Optional[List[str]]),
- exclude_domains=utils.unmarshal(exclude_domains, Optional[List[str]]),
- boost_domains=utils.unmarshal(boost_domains, Optional[List[str]]),
- crawl_timeout=crawl_timeout,
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = self.do_request(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="AgentsRuns",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["agents.runs"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ stream=getattr(request, "stream", False) is True,
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ http_res_text = utils.stream_to_text(http_res)
+ return unmarshal_json_response(
+ models.AgentRunsBatchResponse, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "200", "text/event-stream"):
+ return eventstreaming.EventStream(
+ http_res,
+ lambda raw: unmarshal_json_response(
+ models.AgentRunsStreamingResponse, http_res, raw
+ ),
+ client_ref=self,
+ )
+ if utils.match_response(http_res, "400", "application/json"):
+ http_res_text = utils.stream_to_text(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns400ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns400ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "401", "application/json"):
+ http_res_text = utils.stream_to_text(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns401ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns401ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "422", "application/json"):
+ http_res_text = utils.stream_to_text(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns422ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns422ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError(
+ "Unexpected response received", http_res, http_res_text
)
+ async def agents_async(
+ self,
+ *,
+ request: Union[models.AgentsRunsRequest, models.AgentsRunsRequestTypedDict],
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> Union[
+ models.AgentRunsBatchResponse,
+ eventstreaming.EventStreamAsync[models.AgentRunsStreamingResponse],
+ ]:
+ r"""Run an Agent
+
+ Execute queries using You.com's AI agents. This endpoint supports three agent types:
+
+ - **Express Agent**: Fast responses with optional web search (max 1 search)
+ - **Advanced Agent**: Complex queries with multi-turn reasoning, planning, and tool usage
+ - **Custom Agent**: User-configured assistants created in the You.com UI
+
+ The response format depends on the `stream` parameter - either a complete JSON payload or Server-Sent Events (SSE).
+
+
+ :param request: The request object to send.
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ if not isinstance(request, BaseModel):
+ request = utils.unmarshal(request, models.AgentsRunsRequest)
+ request = cast(models.AgentsRunsRequest, request)
+
req = self._build_request_async(
method="POST",
- path="/v1/search",
+ path="/v1/agents/runs",
base_url=base_url,
url_variables=url_variables,
request=request,
@@ -458,11 +645,13 @@ async def search_post_async(
request_has_path_params=False,
request_has_query_params=True,
user_agent_header="user-agent",
- accept_header_value="application/json",
+ accept_header_value="text/event-stream"
+ if getattr(request, "stream", False) is True
+ else "application/json",
http_headers=http_headers,
security=self.sdk_configuration.security,
get_serialized_body=lambda: utils.serialize_request_body(
- request, False, False, "json", models.SearchRequestBody
+ request, False, False, "json", models.AgentsRunsRequest
),
allow_empty_value=None,
timeout_ms=timeout_ms,
@@ -480,22 +669,571 @@ async def search_post_async(
hook_ctx=HookContext(
config=self.sdk_configuration,
base_url=base_url or "",
- operation_id="searchPost",
+ operation_id="AgentsRuns",
oauth2_scopes=None,
security_source=get_security_from_env(
self.sdk_configuration.security, models.Security
),
- tags=None,
+ tags=["agents.runs"],
extensions=None,
),
request=req,
is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ stream=getattr(request, "stream", False) is True,
retry_config=retry_config,
)
response_data: Any = None
if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(models.SearchResponse, http_res)
+ http_res_text = await utils.stream_to_text_async(http_res)
+ return unmarshal_json_response(
+ models.AgentRunsBatchResponse, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "200", "text/event-stream"):
+ return eventstreaming.EventStreamAsync(
+ http_res,
+ lambda raw: unmarshal_json_response(
+ models.AgentRunsStreamingResponse, http_res, raw
+ ),
+ client_ref=self,
+ )
+ if utils.match_response(http_res, "400", "application/json"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns400ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns400ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "401", "application/json"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns401ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns401ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "422", "application/json"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ response_data = unmarshal_json_response(
+ errors.AgentRuns422ResponseErrorData, http_res, http_res_text
+ )
+ raise errors.AgentRuns422ResponseError(
+ response_data, http_res, http_res_text
+ )
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError(
+ "Unexpected response received", http_res, http_res_text
+ )
+
+ def contents(
+ self,
+ *,
+ urls: Optional[Iterable[str]] = None,
+ formats: Optional[Iterable[models.ContentsFormats]] = None,
+ crawl_timeout: Optional[int] = 10,
+ max_age: OptionalNullable[int] = None,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> List[models.ContentsResponse]:
+ r"""Returns the content of the web pages
+
+ Returns the HTML or Markdown of a target webpage.
+
+ :param urls: Array of URLs to fetch the contents from.
+ :param formats: Array of content formats to return. All included formats are returned in the response. Include \"metadata\" to get JSON-LD and OpenGraph information, if available.
+ :param crawl_timeout: Maximum time in seconds to wait for page content. Must be between 1 and 60 seconds. Default is 10 seconds.
+ :param max_age: Maximum allowed age of cached content in seconds. When set, cached content older than this threshold is ignored and the page is re-fetched. Must be 0 or greater. Default: null (no age limit, cached content is returned regardless of age).
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ request = models.ContentsRequest(
+ urls=utils.unmarshal(urls, Optional[List[str]]),
+ formats=utils.unmarshal(formats, Optional[List[models.ContentsFormats]]),
+ crawl_timeout=crawl_timeout,
+ max_age=max_age,
+ )
+
+ req = self._build_request(
+ method="POST",
+ path="/v1/contents",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.ContentsRequest
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = self.do_request(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="contents",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["contents"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(List[models.ContentsResponse], http_res)
+ if utils.match_response(http_res, "401", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsUnauthorizedErrorData, http_res
+ )
+ raise errors.ContentsUnauthorizedError(response_data, http_res)
+ if utils.match_response(http_res, "403", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsForbiddenErrorData, http_res
+ )
+ raise errors.ContentsForbiddenError(response_data, http_res)
+ if utils.match_response(http_res, "500", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsInternalServerErrorData, http_res
+ )
+ raise errors.ContentsInternalServerError(response_data, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ raise errors.YouDefaultError("Unexpected response received", http_res)
+
+ async def contents_async(
+ self,
+ *,
+ urls: Optional[Iterable[str]] = None,
+ formats: Optional[Iterable[models.ContentsFormats]] = None,
+ crawl_timeout: Optional[int] = 10,
+ max_age: OptionalNullable[int] = None,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> List[models.ContentsResponse]:
+ r"""Returns the content of the web pages
+
+ Returns the HTML or Markdown of a target webpage.
+
+ :param urls: Array of URLs to fetch the contents from.
+ :param formats: Array of content formats to return. All included formats are returned in the response. Include \"metadata\" to get JSON-LD and OpenGraph information, if available.
+ :param crawl_timeout: Maximum time in seconds to wait for page content. Must be between 1 and 60 seconds. Default is 10 seconds.
+ :param max_age: Maximum allowed age of cached content in seconds. When set, cached content older than this threshold is ignored and the page is re-fetched. Must be 0 or greater. Default: null (no age limit, cached content is returned regardless of age).
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ request = models.ContentsRequest(
+ urls=utils.unmarshal(urls, Optional[List[str]]),
+ formats=utils.unmarshal(formats, Optional[List[models.ContentsFormats]]),
+ crawl_timeout=crawl_timeout,
+ max_age=max_age,
+ )
+
+ req = self._build_request_async(
+ method="POST",
+ path="/v1/contents",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.ContentsRequest
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = await self.do_request_async(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="contents",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["contents"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(List[models.ContentsResponse], http_res)
+ if utils.match_response(http_res, "401", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsUnauthorizedErrorData, http_res
+ )
+ raise errors.ContentsUnauthorizedError(response_data, http_res)
+ if utils.match_response(http_res, "403", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsForbiddenErrorData, http_res
+ )
+ raise errors.ContentsForbiddenError(response_data, http_res)
+ if utils.match_response(http_res, "500", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ContentsInternalServerErrorData, http_res
+ )
+ raise errors.ContentsInternalServerError(response_data, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ raise errors.YouDefaultError("Unexpected response received", http_res)
+
+ def search(
+ self,
+ *,
+ query: str,
+ count: Optional[int] = 10,
+ freshness: Optional[str] = None,
+ offset: Optional[int] = None,
+ country: Optional[str] = None,
+ language: Optional[str] = None,
+ safesearch: Optional[str] = None,
+ livecrawl: Optional[str] = None,
+ livecrawl_formats: Optional[Iterable[str]] = None,
+ include_domains: Optional[Iterable[str]] = None,
+ exclude_domains: Optional[Iterable[str]] = None,
+ boost_domains: Optional[Iterable[str]] = None,
+ crawl_timeout: Optional[int] = 10,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> models.SearchResponse:
+ r"""Search via ``POST /v1/agents/search`` (keyless-capable).
+
+ With no API key configured, runs in the free tier
+ (100 queries/day, count <= 50, no livecrawl).
+ With a key, the proxy forwards to the full search endpoint.
+ A ``402`` response raises
+ :class:`~youdotcom.errors.PaymentRequiredResponseError`.
+
+ Enum-typed parameters (``country``, ``safesearch``, ``livecrawl``,
+ ``freshness``) accept plain strings -- pydantic coerces them when
+ building the request body, so callers don't need to import enum classes.
+
+ :param query: The search query used to retrieve relevant results from the web.
+ :param count: Max results per section (1-50 on the free tier).
+ :param freshness: ``"day"``, ``"week"``, ``"month"``, ``"year"``, or
+ ``"YYYY-MM-DDtoYYYY-MM-DD"``.
+ :param offset: Pagination offset (multiples of ``count``).
+ :param country: Country code for geographical focus.
+ :param language: BCP 47 language code (default ``"en"``).
+ :param safesearch: ``"strict"``, ``"moderate"``, or ``"off"``.
+ :param livecrawl: ``"web"``, ``"news"``, or ``"all"`` (not allowed on
+ the free tier).
+ :param livecrawl_formats: ``["html"]``, ``["markdown"]``, or both.
+ :param include_domains: Restrict results to these domains (<= 500).
+ :param exclude_domains: Exclude these domains (<= 500).
+ :param boost_domains: Boost these domains in ranking (<= 500).
+ :param crawl_timeout: Max seconds to wait for livecrawl (1-60, default 10).
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ body: dict[str, Any] = dict(
+ query=query,
+ count=count,
+ freshness=freshness,
+ offset=offset,
+ country=country.upper() if isinstance(country, str) else country,
+ safesearch=safesearch,
+ livecrawl=livecrawl,
+ livecrawl_formats=utils.unmarshal(
+ livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
+ ),
+ include_domains=utils.unmarshal(include_domains, Optional[List[str]]),
+ exclude_domains=utils.unmarshal(exclude_domains, Optional[List[str]]),
+ boost_domains=utils.unmarshal(boost_domains, Optional[List[str]]),
+ crawl_timeout=crawl_timeout,
+ )
+ if language is not None:
+ body["language"] = language.upper() if isinstance(language, str) else language
+ request = models.SearchRequestBody(**body)
+
+ req = self._build_request(
+ method="POST",
+ path="/v1/agents/search",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.SearchRequestBody
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = self.do_request(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="agentsSearch",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["search"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(models.SearchResponse, http_res)
+ if utils.match_response(http_res, "402", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.PaymentRequiredResponseErrorData, http_res
+ )
+ raise errors.PaymentRequiredResponseError(response_data, http_res)
+ if utils.match_response(http_res, "401", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.UnauthorizedResponseErrorData, http_res
+ )
+ raise errors.UnauthorizedResponseError(response_data, http_res)
+ if utils.match_response(http_res, "403", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.ForbiddenResponseErrorData, http_res
+ )
+ raise errors.ForbiddenResponseError(response_data, http_res)
+ if utils.match_response(http_res, "422", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.UnprocessableEntityResponseErrorData, http_res
+ )
+ raise errors.UnprocessableEntityResponseError(response_data, http_res)
+ if utils.match_response(http_res, "500", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.InternalServerErrorResponseData, http_res
+ )
+ raise errors.InternalServerErrorResponse(response_data, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
+
+ raise errors.YouDefaultError("Unexpected response received", http_res)
+
+ async def search_async(
+ self,
+ *,
+ query: str,
+ count: Optional[int] = 10,
+ freshness: Optional[str] = None,
+ offset: Optional[int] = None,
+ country: Optional[str] = None,
+ language: Optional[str] = None,
+ safesearch: Optional[str] = None,
+ livecrawl: Optional[str] = None,
+ livecrawl_formats: Optional[Iterable[str]] = None,
+ include_domains: Optional[Iterable[str]] = None,
+ exclude_domains: Optional[Iterable[str]] = None,
+ boost_domains: Optional[Iterable[str]] = None,
+ crawl_timeout: Optional[int] = 10,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> models.SearchResponse:
+ r"""Search via ``POST /v1/agents/search`` (keyless-capable).
+
+ Async variant of :meth:`search`.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(None, None)
+
+ body: dict[str, Any] = dict(
+ query=query,
+ count=count,
+ freshness=freshness,
+ offset=offset,
+ country=country.upper() if isinstance(country, str) else country,
+ safesearch=safesearch,
+ livecrawl=livecrawl,
+ livecrawl_formats=utils.unmarshal(
+ livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
+ ),
+ include_domains=utils.unmarshal(include_domains, Optional[List[str]]),
+ exclude_domains=utils.unmarshal(exclude_domains, Optional[List[str]]),
+ boost_domains=utils.unmarshal(boost_domains, Optional[List[str]]),
+ crawl_timeout=crawl_timeout,
+ )
+ if language is not None:
+ body["language"] = language.upper() if isinstance(language, str) else language
+ request = models.SearchRequestBody(**body)
+
+ req = self._build_request_async(
+ method="POST",
+ path="/v1/agents/search",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.SearchRequestBody
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = await self.do_request_async(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="agentsSearch",
+ oauth2_scopes=None,
+ security_source=get_security_from_env(
+ self.sdk_configuration.security, models.Security
+ ),
+ tags=["search"],
+ extensions=None,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ response_data: Any = None
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(models.SearchResponse, http_res)
+ if utils.match_response(http_res, "402", "application/json"):
+ response_data = unmarshal_json_response(
+ errors.PaymentRequiredResponseErrorData, http_res
+ )
+ raise errors.PaymentRequiredResponseError(response_data, http_res)
if utils.match_response(http_res, "401", "application/json"):
response_data = unmarshal_json_response(
errors.UnauthorizedResponseErrorData, http_res
diff --git a/src/youdotcom/sdkconfiguration.py b/src/youdotcom/sdkconfiguration.py
index ea49a89..ec05e3e 100644
--- a/src/youdotcom/sdkconfiguration.py
+++ b/src/youdotcom/sdkconfiguration.py
@@ -1,7 +1,6 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from ._version import (
- __gen_version__,
__openapi_doc_version__,
__user_agent__,
__version__,
@@ -34,7 +33,6 @@ class SDKConfiguration:
language: str = "python"
openapi_doc_version: str = __openapi_doc_version__
sdk_version: str = __version__
- gen_version: str = __gen_version__
user_agent: str = __user_agent__
retry_config: OptionalNullable[RetryConfig] = Field(default_factory=lambda: UNSET)
timeout_ms: Optional[int] = None
diff --git a/src/youdotcom/search.py b/src/youdotcom/search.py
deleted file mode 100644
index 9e69fa0..0000000
--- a/src/youdotcom/search.py
+++ /dev/null
@@ -1,325 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
-from .basesdk import BaseSDK
-from typing import Any, Iterable, List, Mapping, Optional, Union
-from youdotcom import errors, models, utils
-from youdotcom._hooks import HookContext
-from youdotcom.types import OptionalNullable, UNSET
-from youdotcom.utils import get_security_from_env
-from youdotcom.utils.unmarshal_json_response import unmarshal_json_response
-
-
-class Search(BaseSDK):
- def unified(
- self,
- *,
- query: str,
- count: Optional[int] = 10,
- freshness: Optional[
- Union[models.FreshnessValue, models.FreshnessValueTypedDict]
- ] = None,
- offset: Optional[int] = None,
- country: Optional[models.Country] = None,
- language: Optional[models.Language] = models.Language.EN,
- safesearch: Optional[models.SafeSearch] = None,
- livecrawl: Optional[models.LiveCrawl] = None,
- livecrawl_formats: Optional[Iterable[models.LiveCrawlFormats]] = None,
- include_domains: Optional[str] = None,
- exclude_domains: Optional[str] = None,
- boost_domains: Optional[str] = None,
- crawl_timeout: Optional[int] = 10,
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> models.SearchResponse:
- r"""Returns a list of unified search results from web and news sources
-
- This endpoint is designed to return LLM-ready web results based on a user's query. Based on a classification mechanism, it can return web results and news associated with your query. If you need to feed an LLM with the results of a query that sounds like `What are the latest geopolitical updates from India`, then this endpoint is the right one for you.
-
- `GET` is a good choice for simple queries where HTTP cacheability matters—GET responses can be cached at CDN and proxy layers, whereas POST responses are not cached by default per the HTTP spec. For requests with complex parameters such as `include_domains` or `exclude_domains`, use POST instead - domain lists are passed as comma-separated strings in GET and are limited by URL length.
-
- :param query:
- :param count:
- :param freshness: Specifies the freshness of the results to return. Provide either one of `day`, `week`, `month`, `year`, or a date range string in the format `YYYY-MM-DDtoYYYY-MM-DD`.
-
- When your search query includes a temporal keyword and you also set a freshness parameter, the search will use the broader (i.e., less restrictive) of the two timeframes. For example, if you use `query=news+this+week&freshness=month`, the results will use a freshness of month.
- :param offset:
- :param country: The country code that determines the geographical focus of the web results.
- :param language: The language of the web results that will be returned (BCP 47 format).
- :param safesearch: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.
- :param livecrawl: Indicates which section(s) of search results to livecrawl and return full page content.
- :param livecrawl_formats:
- :param include_domains: A list of domains to restrict search results to. Only results from these domains will be returned. For large domain lists (up to 500), use POST with a JSON array instead. This is a strict allowlist — cannot be combined with `exclude_domains` (returns `422`).
-
- **Important:** Use a single comma-separated value (e.g. `include_domains=nytimes.com,bbc.com`). Repeated parameters (`include_domains=a.com&include_domains=b.com`) are not supported.
- :param exclude_domains: A list of domains to exclude from search results. Results from these domains will be filtered out. For large domain lists (up to 500), use POST with a JSON array instead. Cannot be combined with `include_domains` (returns `422`).
-
- **Important:** You must use a single comma-separated value (e.g. `exclude_domains=spam-site.com,other-site.com`). Repeated parameters are not supported.
- :param boost_domains: A list of domains to boost in search ranking. Matching results from these domains receive a relative ranking boost, but results are not limited to these domains. Supports up to 500 domains. Can be combined with `exclude_domains`, but cannot be combined with `include_domains` (returns `422`).
-
- **Important:** You must use a single comma-separated value (e.g. `boost_domains=nytimes.com,wired.com`). Repeated parameters are not supported.
- :param crawl_timeout:
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.SEARCH_OP_SERVERS[0]
-
- request = models.SearchRequest(
- query=query,
- count=count,
- freshness=freshness,
- offset=offset,
- country=country,
- language=language,
- safesearch=safesearch,
- livecrawl=livecrawl,
- livecrawl_formats=utils.unmarshal(
- livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
- ),
- include_domains=include_domains,
- exclude_domains=exclude_domains,
- boost_domains=boost_domains,
- crawl_timeout=crawl_timeout,
- )
-
- req = self._build_request(
- method="GET",
- path="/v1/search",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=False,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = self.do_request(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="search",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["search"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(models.SearchResponse, http_res)
- if utils.match_response(http_res, "401", "application/json"):
- response_data = unmarshal_json_response(
- errors.UnauthorizedResponseErrorData, http_res
- )
- raise errors.UnauthorizedResponseError(response_data, http_res)
- if utils.match_response(http_res, "403", "application/json"):
- response_data = unmarshal_json_response(
- errors.ForbiddenResponseErrorData, http_res
- )
- raise errors.ForbiddenResponseError(response_data, http_res)
- if utils.match_response(http_res, "422", "application/json"):
- response_data = unmarshal_json_response(
- errors.UnprocessableEntityResponseErrorData, http_res
- )
- raise errors.UnprocessableEntityResponseError(response_data, http_res)
- if utils.match_response(http_res, "500", "application/json"):
- response_data = unmarshal_json_response(
- errors.InternalServerErrorResponseData, http_res
- )
- raise errors.InternalServerErrorResponse(response_data, http_res)
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = utils.stream_to_text(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- raise errors.YouDefaultError("Unexpected response received", http_res)
-
- async def unified_async(
- self,
- *,
- query: str,
- count: Optional[int] = 10,
- freshness: Optional[
- Union[models.FreshnessValue, models.FreshnessValueTypedDict]
- ] = None,
- offset: Optional[int] = None,
- country: Optional[models.Country] = None,
- language: Optional[models.Language] = models.Language.EN,
- safesearch: Optional[models.SafeSearch] = None,
- livecrawl: Optional[models.LiveCrawl] = None,
- livecrawl_formats: Optional[Iterable[models.LiveCrawlFormats]] = None,
- include_domains: Optional[str] = None,
- exclude_domains: Optional[str] = None,
- boost_domains: Optional[str] = None,
- crawl_timeout: Optional[int] = 10,
- retries: OptionalNullable[utils.RetryConfig] = UNSET,
- server_url: Optional[str] = None,
- timeout_ms: Optional[int] = None,
- http_headers: Optional[Mapping[str, str]] = None,
- ) -> models.SearchResponse:
- r"""Returns a list of unified search results from web and news sources
-
- This endpoint is designed to return LLM-ready web results based on a user's query. Based on a classification mechanism, it can return web results and news associated with your query. If you need to feed an LLM with the results of a query that sounds like `What are the latest geopolitical updates from India`, then this endpoint is the right one for you.
-
- `GET` is a good choice for simple queries where HTTP cacheability matters—GET responses can be cached at CDN and proxy layers, whereas POST responses are not cached by default per the HTTP spec. For requests with complex parameters such as `include_domains` or `exclude_domains`, use POST instead - domain lists are passed as comma-separated strings in GET and are limited by URL length.
-
- :param query:
- :param count:
- :param freshness: Specifies the freshness of the results to return. Provide either one of `day`, `week`, `month`, `year`, or a date range string in the format `YYYY-MM-DDtoYYYY-MM-DD`.
-
- When your search query includes a temporal keyword and you also set a freshness parameter, the search will use the broader (i.e., less restrictive) of the two timeframes. For example, if you use `query=news+this+week&freshness=month`, the results will use a freshness of month.
- :param offset:
- :param country: The country code that determines the geographical focus of the web results.
- :param language: The language of the web results that will be returned (BCP 47 format).
- :param safesearch: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.
- :param livecrawl: Indicates which section(s) of search results to livecrawl and return full page content.
- :param livecrawl_formats:
- :param include_domains: A list of domains to restrict search results to. Only results from these domains will be returned. For large domain lists (up to 500), use POST with a JSON array instead. This is a strict allowlist — cannot be combined with `exclude_domains` (returns `422`).
-
- **Important:** Use a single comma-separated value (e.g. `include_domains=nytimes.com,bbc.com`). Repeated parameters (`include_domains=a.com&include_domains=b.com`) are not supported.
- :param exclude_domains: A list of domains to exclude from search results. Results from these domains will be filtered out. For large domain lists (up to 500), use POST with a JSON array instead. Cannot be combined with `include_domains` (returns `422`).
-
- **Important:** You must use a single comma-separated value (e.g. `exclude_domains=spam-site.com,other-site.com`). Repeated parameters are not supported.
- :param boost_domains: A list of domains to boost in search ranking. Matching results from these domains receive a relative ranking boost, but results are not limited to these domains. Supports up to 500 domains. Can be combined with `exclude_domains`, but cannot be combined with `include_domains` (returns `422`).
-
- **Important:** You must use a single comma-separated value (e.g. `boost_domains=nytimes.com,wired.com`). Repeated parameters are not supported.
- :param crawl_timeout:
- :param retries: Override the default retry configuration for this method
- :param server_url: Override the default server URL for this method
- :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
- :param http_headers: Additional headers to set or replace on requests.
- """
- base_url = None
- url_variables = None
- if timeout_ms is None:
- timeout_ms = self.sdk_configuration.timeout_ms
-
- if server_url is not None:
- base_url = server_url
- else:
- base_url = models.SEARCH_OP_SERVERS[0]
-
- request = models.SearchRequest(
- query=query,
- count=count,
- freshness=freshness,
- offset=offset,
- country=country,
- language=language,
- safesearch=safesearch,
- livecrawl=livecrawl,
- livecrawl_formats=utils.unmarshal(
- livecrawl_formats, Optional[List[models.LiveCrawlFormats]]
- ),
- include_domains=include_domains,
- exclude_domains=exclude_domains,
- boost_domains=boost_domains,
- crawl_timeout=crawl_timeout,
- )
-
- req = self._build_request_async(
- method="GET",
- path="/v1/search",
- base_url=base_url,
- url_variables=url_variables,
- request=request,
- request_body_required=False,
- request_has_path_params=False,
- request_has_query_params=True,
- user_agent_header="user-agent",
- accept_header_value="application/json",
- http_headers=http_headers,
- security=self.sdk_configuration.security,
- allow_empty_value=None,
- timeout_ms=timeout_ms,
- )
-
- if retries == UNSET:
- if self.sdk_configuration.retry_config is not UNSET:
- retries = self.sdk_configuration.retry_config
-
- retry_config = None
- if isinstance(retries, utils.RetryConfig):
- retry_config = (retries, ["429", "500", "502", "503", "504"])
-
- http_res = await self.do_request_async(
- hook_ctx=HookContext(
- config=self.sdk_configuration,
- base_url=base_url or "",
- operation_id="search",
- oauth2_scopes=None,
- security_source=get_security_from_env(
- self.sdk_configuration.security, models.Security
- ),
- tags=["search"],
- extensions=None,
- ),
- request=req,
- is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
- retry_config=retry_config,
- )
-
- response_data: Any = None
- if utils.match_response(http_res, "200", "application/json"):
- return unmarshal_json_response(models.SearchResponse, http_res)
- if utils.match_response(http_res, "401", "application/json"):
- response_data = unmarshal_json_response(
- errors.UnauthorizedResponseErrorData, http_res
- )
- raise errors.UnauthorizedResponseError(response_data, http_res)
- if utils.match_response(http_res, "403", "application/json"):
- response_data = unmarshal_json_response(
- errors.ForbiddenResponseErrorData, http_res
- )
- raise errors.ForbiddenResponseError(response_data, http_res)
- if utils.match_response(http_res, "422", "application/json"):
- response_data = unmarshal_json_response(
- errors.UnprocessableEntityResponseErrorData, http_res
- )
- raise errors.UnprocessableEntityResponseError(response_data, http_res)
- if utils.match_response(http_res, "500", "application/json"):
- response_data = unmarshal_json_response(
- errors.InternalServerErrorResponseData, http_res
- )
- raise errors.InternalServerErrorResponse(response_data, http_res)
- if utils.match_response(http_res, "4XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
- if utils.match_response(http_res, "5XX", "*"):
- http_res_text = await utils.stream_to_text_async(http_res)
- raise errors.YouDefaultError("API error occurred", http_res, http_res_text)
-
- raise errors.YouDefaultError("Unexpected response received", http_res)
diff --git a/src/youdotcom/types/__init__.py b/src/youdotcom/types/__init__.py
index faa2681..74b9dc1 100644
--- a/src/youdotcom/types/__init__.py
+++ b/src/youdotcom/types/__init__.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from .base64fileinput import Base64EncodedString, Base64FileInput
from .basemodel import (
diff --git a/src/youdotcom/types/base64fileinput.py b/src/youdotcom/types/base64fileinput.py
index 862566f..9816c09 100644
--- a/src/youdotcom/types/base64fileinput.py
+++ b/src/youdotcom/types/base64fileinput.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from __future__ import annotations
diff --git a/src/youdotcom/types/basemodel.py b/src/youdotcom/types/basemodel.py
index a9a640a..d0c2583 100644
--- a/src/youdotcom/types/basemodel.py
+++ b/src/youdotcom/types/basemodel.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from pydantic import ConfigDict, model_serializer
from pydantic import BaseModel as PydanticBaseModel
diff --git a/src/youdotcom/utils/__init__.py b/src/youdotcom/utils/__init__.py
index c48a36c..81eacf5 100644
--- a/src/youdotcom/utils/__init__.py
+++ b/src/youdotcom/utils/__init__.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import Any, TYPE_CHECKING, Callable, TypeVar
import asyncio
diff --git a/src/youdotcom/utils/annotations.py b/src/youdotcom/utils/annotations.py
index 12e0aa4..188c8f6 100644
--- a/src/youdotcom/utils/annotations.py
+++ b/src/youdotcom/utils/annotations.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from enum import Enum
from typing import Any, Optional
diff --git a/src/youdotcom/utils/datetimes.py b/src/youdotcom/utils/datetimes.py
index adad247..6452522 100644
--- a/src/youdotcom/utils/datetimes.py
+++ b/src/youdotcom/utils/datetimes.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from datetime import datetime, timedelta
import sys
diff --git a/src/youdotcom/utils/dynamic_imports.py b/src/youdotcom/utils/dynamic_imports.py
index 673edf8..7f88737 100644
--- a/src/youdotcom/utils/dynamic_imports.py
+++ b/src/youdotcom/utils/dynamic_imports.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from importlib import import_module
import builtins
diff --git a/src/youdotcom/utils/enums.py b/src/youdotcom/utils/enums.py
index 3324e1b..b56be22 100644
--- a/src/youdotcom/utils/enums.py
+++ b/src/youdotcom/utils/enums.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import enum
import sys
diff --git a/src/youdotcom/utils/eventstreaming.py b/src/youdotcom/utils/eventstreaming.py
index a8d4fe5..09b85c1 100644
--- a/src/youdotcom/utils/eventstreaming.py
+++ b/src/youdotcom/utils/eventstreaming.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import re
import json
diff --git a/src/youdotcom/utils/forms.py b/src/youdotcom/utils/forms.py
index 193f264..5d1ccb0 100644
--- a/src/youdotcom/utils/forms.py
+++ b/src/youdotcom/utils/forms.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import io
from typing import (
diff --git a/src/youdotcom/utils/headers.py b/src/youdotcom/utils/headers.py
index 37864cb..5e938f0 100644
--- a/src/youdotcom/utils/headers.py
+++ b/src/youdotcom/utils/headers.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import (
Any,
diff --git a/src/youdotcom/utils/logger.py b/src/youdotcom/utils/logger.py
index 6ae3abd..cd063d5 100644
--- a/src/youdotcom/utils/logger.py
+++ b/src/youdotcom/utils/logger.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import httpx
import logging
diff --git a/src/youdotcom/utils/metadata.py b/src/youdotcom/utils/metadata.py
index 5abddd5..a70634a 100644
--- a/src/youdotcom/utils/metadata.py
+++ b/src/youdotcom/utils/metadata.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import Optional, Type, TypeVar, Union
from dataclasses import dataclass
diff --git a/src/youdotcom/utils/queryparams.py b/src/youdotcom/utils/queryparams.py
index c04e0db..f831b89 100644
--- a/src/youdotcom/utils/queryparams.py
+++ b/src/youdotcom/utils/queryparams.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import (
Any,
diff --git a/src/youdotcom/utils/requestbodies.py b/src/youdotcom/utils/requestbodies.py
index 591415a..17aba70 100644
--- a/src/youdotcom/utils/requestbodies.py
+++ b/src/youdotcom/utils/requestbodies.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import io
from dataclasses import dataclass
diff --git a/src/youdotcom/utils/retries.py b/src/youdotcom/utils/retries.py
index ca7b59e..2a7a52d 100644
--- a/src/youdotcom/utils/retries.py
+++ b/src/youdotcom/utils/retries.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import asyncio
import random
diff --git a/src/youdotcom/utils/security.py b/src/youdotcom/utils/security.py
index 2468bf1..3feb799 100644
--- a/src/youdotcom/utils/security.py
+++ b/src/youdotcom/utils/security.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
import base64
diff --git a/src/youdotcom/utils/serializers.py b/src/youdotcom/utils/serializers.py
index 1031ed9..7ca3db3 100644
--- a/src/youdotcom/utils/serializers.py
+++ b/src/youdotcom/utils/serializers.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from decimal import Decimal
import functools
diff --git a/src/youdotcom/utils/unmarshal_json_response.py b/src/youdotcom/utils/unmarshal_json_response.py
index 131b392..12d397d 100644
--- a/src/youdotcom/utils/unmarshal_json_response.py
+++ b/src/youdotcom/utils/unmarshal_json_response.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from typing import Any, Optional, Type, TypeVar, overload
diff --git a/src/youdotcom/utils/url.py b/src/youdotcom/utils/url.py
index c78ccba..5c7be7f 100644
--- a/src/youdotcom/utils/url.py
+++ b/src/youdotcom/utils/url.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from decimal import Decimal
from typing import (
diff --git a/src/youdotcom/utils/values.py b/src/youdotcom/utils/values.py
index dae01a4..6e25f4b 100644
--- a/src/youdotcom/utils/values.py
+++ b/src/youdotcom/utils/values.py
@@ -1,4 +1,4 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
from datetime import datetime
from enum import Enum
diff --git a/tests/PERFORMANCE_TESTING.md b/tests/PERFORMANCE_TESTING.md
index e91d623..3e2426e 100644
--- a/tests/PERFORMANCE_TESTING.md
+++ b/tests/PERFORMANCE_TESTING.md
@@ -334,11 +334,11 @@ Example:
```python
def test_search_with_new_filter(self, server_url, api_key, iterations, show_detailed):
"""Test description."""
- client = create_test_http_client("get_/v1/search")
+ client = create_test_http_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="test", new_filter="value")
+ you.search(query="test", new_filter="value")
metrics = measure_sdk_call(call, client, iterations, "Search: new filter")
ALL_METRICS.append(metrics)
diff --git a/tests/README.md b/tests/README.md
index ca38eb3..d7e388b 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -52,13 +52,12 @@ pytest tests/ -v
### Test Files
- `test_client.py` - Helper utilities for creating test HTTP clients
-- `test_search.py` - Tests for the Search API (`/v1/search`)
+- `test_search.py` - Tests for the Search API (`/v1/agents/search`)
- `test_contents.py` - Tests for the Contents API (`/v1/contents`)
- `test_runs.py` - Tests for the Agents/Runs API (`/v1/agents/runs`)
- `test_research.py` - Tests for the Research API (`/v1/research`) including background mode, output_schema, and source_control
- `test_research_helpers.py` - Tests for the hand-maintained `research_helpers` module (background submission, polling, streaming, research_and_wait)
- `test_security_env.py` - Tests for environment variable precedence (`YDC_API_KEY` / `YOU_API_KEY_AUTH`)
-- `test_user_agent_hook.py` - Tests for the `YDCUserAgentOverrideHook` custom user-agent pass-through
- `test_performance.py` - Performance/instrumentation tests measuring SDK overhead
- `test_live.py` - Live API tests that run against the real You.com API (requires API key)
@@ -103,12 +102,15 @@ Tests are organized into logical classes using pytest:
### Running Live Tests
-The `test_live.py` file contains tests that run against the real You.com API. These are skipped by default unless an API key is provided:
+The `test_live.py` file contains tests that run against the real You.com API. Keyed tests are skipped unless an API key is provided. Keyless search tests (`TestLiveSearchKeyless`) run without an API key by default, since they verify the free-tier `/v1/agents/search` proxy:
```bash
-# Run live tests with your API key
+# Run live tests with your API key (enables keyed tests)
YDC_API_KEY="your-api-key" pytest tests/test_live.py -v
+# Run only keyless live tests (no API key needed)
+pytest tests/test_live.py::TestLiveSearchKeyless -v
+
# Run all tests except live tests
pytest tests/ --ignore=tests/test_live.py -v
```
@@ -116,9 +118,7 @@ pytest tests/ --ignore=tests/test_live.py -v
## Test Coverage
All tests cover the functionality demonstrated in the `examples/` directory:
-- ✓ All search examples (`examples/search.py`)
-- ✓ All contents examples (`examples/contents.py`)
-- ✓ All agents examples (`examples/agents.py`)
+- ✓ All API examples (`examples/api-example-calls.py`)
Additionally, tests include:
- ✓ Error response handling for all endpoints
diff --git a/tests/__init__.py b/tests/__init__.py
index 368144a..ae78246 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,3 +1 @@
-"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
-
# tests/__init__.py
diff --git a/tests/mockserver/internal/handler/generated_handlers.go b/tests/mockserver/internal/handler/generated_handlers.go
index e2bdae4..c4e5548 100644
--- a/tests/mockserver/internal/handler/generated_handlers.go
+++ b/tests/mockserver/internal/handler/generated_handlers.go
@@ -13,6 +13,7 @@ import (
func GeneratedHandlers(ctx context.Context, dir *logging.HTTPFileDirectory, rt *tracking.RequestTracker) []*GeneratedHandler {
return []*GeneratedHandler{
NewGeneratedHandler(ctx, http.MethodGet, "/v1/search", pathGetV1Search(dir, rt)),
+ NewGeneratedHandler(ctx, http.MethodPost, "/v1/agents/search", pathPostV1Search(dir, rt)),
NewGeneratedHandler(ctx, http.MethodPost, "/v1/agents/runs", pathPostV1AgentsRuns(dir, rt)),
NewGeneratedHandler(ctx, http.MethodPost, "/v1/contents", pathPostV1Contents(dir, rt)),
NewGeneratedHandler(ctx, http.MethodPost, "/v1/research", pathPostV1Research(dir, rt)),
diff --git a/tests/mockserver/internal/handler/pathpostv1search.go b/tests/mockserver/internal/handler/pathpostv1search.go
new file mode 100644
index 0000000..f9cd968
--- /dev/null
+++ b/tests/mockserver/internal/handler/pathpostv1search.go
@@ -0,0 +1,56 @@
+package handler
+
+import (
+ "log"
+ "mockserver/internal/handler/assert"
+ "mockserver/internal/logging"
+ "mockserver/internal/tracking"
+ "net/http"
+)
+
+func pathPostV1Search(dir *logging.HTTPFileDirectory, rt *tracking.RequestTracker) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ if err := assert.SecurityHeader(req, "X-API-Key", true); err != nil {
+ log.Printf("assertion error: %s\n", err)
+ http.Error(w, err.Error(), http.StatusUnauthorized)
+ return
+ }
+ if err := assert.HeaderExists(req, "User-Agent"); err != nil {
+ log.Printf("assertion error: %s\n", err)
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(`{
+ "results": {
+ "web": [
+ {
+ "url": "https://you.com",
+ "title": "The World's Greatest Search Engine!",
+ "description": "Search on YDC",
+ "snippets": ["I'm an AI assistant that helps you get more done."],
+ "thumbnail_url": "https://www.somethumbnailsite.com/thumbnail.jpg",
+ "page_age": "2025-06-25T11:41:00Z",
+ "favicon_url": "https://someurl.com/favicon"
+ }
+ ],
+ "news": [
+ {
+ "title": "You.com becomes the backbone of the EU's AI strategy",
+ "description": "You.com becomes the backbone of the EU's AI strategy.",
+ "page_age": "2025-06-25T11:41:00Z",
+ "thumbnail_url": "https://www.somethumbnailsite.com/thumbnail.jpg",
+ "url": "https://www.you.com/news/eu-ai-strategy-youcom"
+ }
+ ]
+ },
+ "metadata": {
+ "search_uuid": "942ccbdd-7705-4d9c-9d37-4ef386658e90",
+ "query": "Your query",
+ "latency": 0.123
+ }
+ }`))
+ }
+}
diff --git a/tests/test_answer.py b/tests/test_answer.py
new file mode 100644
index 0000000..fdcc863
--- /dev/null
+++ b/tests/test_answer.py
@@ -0,0 +1,282 @@
+"""Tests for youdotcom.answer — POST /v1/answer."""
+
+import json
+
+import httpx
+import pytest
+
+from youdotcom import You
+from youdotcom.errors import (
+ ForbiddenResponseError,
+ InternalServerErrorResponse,
+ PaymentRequiredResponseError,
+ UnauthorizedResponseError,
+ UnprocessableEntityResponseError,
+ YouDefaultError,
+)
+from youdotcom.models import AnswerResponse
+
+_ANSWER_BODY = json.dumps(
+ {
+ "answer": "Quantum computing advanced in 2025[[1, 2]].",
+ "citations": [
+ {"source": "https://example.com/quantum", "excerpts": ["IBM announced a new processor."]},
+ {"source": "https://example.com/ibm", "excerpts": ["Google achieved error correction.", "IBM unveiled 1000 qubits."]},
+ ],
+ "results": {
+ "web": [
+ {"url": "https://example.com/quantum", "title": "Quantum News", "snippets": ["IBM announced a new processor."], "page_age": "2025-06-25T11:41:00"},
+ {"url": "https://example.com/ibm", "title": "IBM Quantum", "snippets": ["Google achieved error correction."]},
+ ]
+ },
+ }
+)
+
+
+def _make_handler(status: int = 200, body: str = _ANSWER_BODY):
+ def handler(request):
+ return httpx.Response(
+ status, headers={"content-type": "application/json"}, content=body
+ )
+
+ return handler
+
+
+def _sync_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "client": httpx.Client(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+def _async_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "async_client": httpx.AsyncClient(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+class TestAnswerSuccess:
+ def test_returns_answer_response(self):
+ res = _sync_you(_make_handler(200)).answer(query="quantum computing 2025")
+ assert isinstance(res, AnswerResponse)
+ assert "Quantum computing" in res.answer
+ assert len(res.citations) == 2
+ assert res.citations[0].source == "https://example.com/quantum"
+ assert len(res.citations[0].excerpts) == 1
+ assert res.citations[1].source == "https://example.com/ibm"
+ assert len(res.citations[1].excerpts) == 2
+ assert len(res.results.web) == 2
+ assert res.results.web[0].title == "Quantum News"
+ assert res.results.web[0].page_age == "2025-06-25T11:41:00"
+ assert res.results.web[1].page_age is None
+
+ def test_posts_to_answer_endpoint(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ captured["method"] = request.method
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ _sync_you(handler).answer(query="test")
+ assert captured["method"] == "POST"
+ assert "/v1/answer" in captured["url"]
+
+ def test_domain_params_serialized(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ _sync_you(handler).answer(
+ query="test",
+ include_domains=["nature.com", "science.org"],
+ country="US",
+ language="EN",
+ freshness="week",
+ )
+ assert captured["body"]["include_domains"] == ["nature.com", "science.org"]
+ assert captured["body"]["country"] == "US"
+ assert captured["body"]["freshness"] == "week"
+
+ def test_exclude_and_boost_domains_serialized(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ _sync_you(handler).answer(
+ query="test",
+ exclude_domains=["spam.com"],
+ boost_domains=["reuters.com"],
+ )
+ assert captured["body"]["exclude_domains"] == ["spam.com"]
+ assert captured["body"]["boost_domains"] == ["reuters.com"]
+ assert "include_domains" not in captured["body"]
+
+ def test_server_url_override_honored(self):
+ """You(server_url=...) should be respected by answer.create()."""
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ you = You(
+ server_url="http://custom.local",
+ client=httpx.Client(transport=httpx.MockTransport(handler)),
+ api_key_auth="test-key",
+ )
+ you.answer(query="test")
+ assert "http://custom.local" in captured["url"]
+ assert "/v1/answer" in captured["url"]
+
+ def test_lowercase_language_and_country_normalized(self):
+ """language='en' and country='us' should be normalized to uppercase."""
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ _sync_you(handler).answer(query="test", language="en", country="us")
+ assert captured["body"]["language"] == "EN"
+ assert captured["body"]["country"] == "US"
+
+ def test_omits_optional_params_when_not_set(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_ANSWER_BODY
+ )
+
+ _sync_you(handler).answer(query="test")
+ body = captured["body"]
+ assert "freshness" not in body
+ assert "country" not in body
+ assert "include_domains" not in body
+ assert body["query"] == "test"
+
+ @pytest.mark.asyncio
+ async def test_async_returns_answer_response(self):
+ res = await _async_you(_make_handler(200)).answer_async(query="quantum")
+ assert isinstance(res, AnswerResponse)
+ assert len(res.citations) == 2
+ assert len(res.results.web) == 2
+
+
+class TestAnswerErrors:
+ def test_402_raises_payment_required_error(self):
+ body = json.dumps({
+ "error": "payment_required",
+ "message": "Insufficient credits",
+ "upgrade_url": "https://you.com/platform",
+ })
+ with pytest.raises(PaymentRequiredResponseError) as exc_info:
+ _sync_you(_make_handler(402, body)).answer(query="test")
+ assert exc_info.value.status_code == 402
+ assert exc_info.value.data.message == "Insufficient credits"
+ assert exc_info.value.data.upgrade_url == "https://you.com/platform"
+
+ def test_402_with_usage_fields(self):
+ """402 response with optional limit/used/period/reset_at fields."""
+ body = json.dumps({
+ "error": "payment_required",
+ "message": "Daily limit exceeded",
+ "upgrade_url": "https://you.com/platform",
+ "limit": 100,
+ "used": 100,
+ "period": "day",
+ "reset_at": "2026-08-05T00:00:00Z",
+ })
+ with pytest.raises(PaymentRequiredResponseError) as exc_info:
+ _sync_you(_make_handler(402, body)).answer(query="test")
+ assert exc_info.value.data.limit == 100
+ assert exc_info.value.data.used == 100
+ assert exc_info.value.data.period == "day"
+ assert exc_info.value.data.reset_at == "2026-08-05T00:00:00Z"
+
+ def test_401_raises_unauthorized_error(self):
+ body = json.dumps({"detail": "Invalid or expired API key"})
+ with pytest.raises(UnauthorizedResponseError):
+ _sync_you(_make_handler(401, body), api_key="bad-key").answer(query="test")
+
+ def test_403_raises_forbidden_error(self):
+ body = json.dumps({"detail": "Missing required scopes"})
+ with pytest.raises(ForbiddenResponseError):
+ _sync_you(_make_handler(403, body)).answer(query="test")
+
+ def test_422_raises_unprocessable_entity_error(self):
+ body = json.dumps({"detail": [{"type": "missing", "loc": ["body", "query"], "msg": "Field required"}]})
+ with pytest.raises(UnprocessableEntityResponseError) as exc_info:
+ _sync_you(_make_handler(422, body)).answer(query="")
+ # FastAPI validation format: detail array
+ assert exc_info.value.data.detail is not None
+ assert exc_info.value.data.detail[0]["type"] == "missing"
+
+ def test_422_json_api_format(self):
+ """422 in JSON:API format {errors: [{status, code, title, detail}]}."""
+ body = json.dumps({"errors": [{"status": "422", "code": "unprocessable_entity", "title": "Unprocessable Entity", "detail": "invalid request parameter(s)"}]})
+ with pytest.raises(UnprocessableEntityResponseError) as exc_info:
+ _sync_you(_make_handler(422, body)).answer(query="")
+ assert exc_info.value.data.errors is not None
+ assert exc_info.value.data.errors[0]["code"] == "unprocessable_entity"
+
+ def test_422_search_spec_format(self):
+ """422 in search spec format {error: string}."""
+ body = json.dumps({"error": "invalid request parameter(s)"})
+ with pytest.raises(UnprocessableEntityResponseError) as exc_info:
+ _sync_you(_make_handler(422, body)).answer(query="")
+ assert exc_info.value.data.error == "invalid request parameter(s)"
+
+ def test_500_with_json_api_errors(self):
+ """500 in JSON:API format {errors: [...]}."""
+ body = json.dumps({"errors": [{"status": "500", "code": "internal_server_error", "title": "Internal Server Error"}]})
+ with pytest.raises(InternalServerErrorResponse) as exc_info:
+ _sync_you(_make_handler(500, body)).answer(query="test")
+ assert exc_info.value.data.errors is not None
+ assert exc_info.value.data.errors[0]["code"] == "internal_server_error"
+
+ def test_4xx_fallback_raises_default_error(self):
+ body = json.dumps({"detail": "rate limited"})
+ with pytest.raises(YouDefaultError):
+ _sync_you(_make_handler(429, body)).answer(query="test")
+
+ @pytest.mark.asyncio
+ async def test_async_402_raises_payment_required_error(self):
+ body = json.dumps({
+ "error": "payment_required",
+ "message": "Insufficient credits",
+ "upgrade_url": "https://you.com/platform",
+ })
+ with pytest.raises(PaymentRequiredResponseError) as exc_info:
+ await _async_you(_make_handler(402, body)).answer_async(query="test")
+ assert exc_info.value.status_code == 402
+ assert exc_info.value.data.error == "payment_required"
+
+ @pytest.mark.asyncio
+ async def test_async_500_raises_internal_server_error(self):
+ body = json.dumps({"detail": "internal server error"})
+ with pytest.raises(InternalServerErrorResponse):
+ await _async_you(_make_handler(500, body)).answer_async(query="test")
diff --git a/tests/test_contents.py b/tests/test_contents.py
index 87ce0fa..3b108ee 100644
--- a/tests/test_contents.py
+++ b/tests/test_contents.py
@@ -26,7 +26,7 @@ def test_html_format(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.python.org", "https://www.example.com"],
formats=[ContentsFormats.HTML],
server_url=server_url,
@@ -41,7 +41,7 @@ def test_markdown_format(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.MARKDOWN],
server_url=server_url,
@@ -55,7 +55,7 @@ def test_metadata_format(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.METADATA],
server_url=server_url,
@@ -71,7 +71,7 @@ def test_multiple_formats(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.HTML, ContentsFormats.MARKDOWN, ContentsFormats.METADATA],
server_url=server_url,
@@ -85,7 +85,7 @@ def test_multiple_urls(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=[
"https://www.you.com",
"https://www.github.com",
@@ -103,7 +103,7 @@ def test_single_url(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.HTML],
server_url=server_url,
@@ -117,7 +117,7 @@ def test_without_formats(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
server_url=server_url,
)
@@ -130,7 +130,7 @@ def test_crawl_timeout(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.HTML],
crawl_timeout=30, # Set timeout to 30 seconds
@@ -145,7 +145,7 @@ def test_max_age(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.MARKDOWN],
max_age=86400, # 1 day in seconds
@@ -162,7 +162,7 @@ def test_unauthorized(self, server_url):
with You(server_url=server_url, client=client, api_key_auth="invalid") as you:
with pytest.raises(ContentsUnauthorizedError):
- you.contents.generate(
+ you.contents(
urls=["https://www.example.com"],
server_url=server_url,
)
@@ -172,7 +172,7 @@ def test_forbidden(self, server_url, api_key):
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
with pytest.raises(ContentsForbiddenError):
- you.contents.generate(
+ you.contents(
urls=["https://www.example.com"],
server_url=server_url,
)
@@ -181,7 +181,7 @@ def test_empty_urls(self, server_url, api_key):
client = create_test_http_client("post_/v1/contents")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.contents.generate(
+ res = you.contents(
urls=[],
formats=[ContentsFormats.HTML],
server_url=server_url,
diff --git a/tests/test_direct_methods.py b/tests/test_direct_methods.py
new file mode 100644
index 0000000..093fad9
--- /dev/null
+++ b/tests/test_direct_methods.py
@@ -0,0 +1,219 @@
+"""Tests for the direct methods on You.
+
+Verifies that you.agents(), you.contents(), and you.search() hit the
+correct endpoints, pass params correctly, and work in both sync and
+async modes. Uses httpx.MockTransport (no live server required).
+"""
+
+import json
+
+import httpx
+import pytest
+
+from youdotcom import You
+from youdotcom.models import (
+ AgentRunsBatchResponse,
+ ContentsResponse,
+ ExpressAgentRunsRequest,
+ SearchResponse,
+)
+
+
+_SEARCH_BODY = json.dumps(
+ {"results": {"web": [{"title": "Test", "url": "https://example.com"}]}}
+)
+_CONTENTS_BODY = json.dumps(
+ [{"url": "https://example.com", "html": "
Hello
"}]
+)
+_RUNS_BODY = json.dumps(
+ {
+ "agent": "express",
+ "input": [{"role": "user", "content": "Hello"}],
+ "output": [{"type": "message.answer", "text": "Hello world"}],
+ }
+)
+
+
+def _sync_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "client": httpx.Client(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+def _async_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "async_client": httpx.AsyncClient(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+# ---------------------------------------------------------------------------
+# you.search() — POST /v1/agents/search (keyless-capable)
+# ---------------------------------------------------------------------------
+
+
+class TestSearchDirect:
+ def test_returns_search_response(self):
+ res = _sync_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )).search(query="python")
+ assert isinstance(res, SearchResponse)
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) == 1
+
+ def test_posts_to_search_endpoint(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ captured["method"] = request.method
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )
+
+ _sync_you(handler).search(query="test")
+ assert captured["method"] == "POST"
+ assert "/v1/agents/search" in captured["url"]
+
+ def test_passes_params_in_body(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )
+
+ _sync_you(handler).search(
+ query="ai news",
+ count=5,
+ freshness="week",
+ country="US",
+ include_domains=["nature.com"],
+ )
+ assert captured["body"]["query"] == "ai news"
+ assert captured["body"]["count"] == 5
+ assert captured["body"]["freshness"] == "week"
+ assert captured["body"]["include_domains"] == ["nature.com"]
+
+ @pytest.mark.asyncio
+ async def test_async_returns_search_response(self):
+ res = await _async_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )).search_async(query="python")
+ assert isinstance(res, SearchResponse)
+
+
+# ---------------------------------------------------------------------------
+# you.contents() — POST /v1/contents
+# ---------------------------------------------------------------------------
+
+
+class TestContentsDirect:
+ def test_returns_contents_response_list(self):
+ res = _sync_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_CONTENTS_BODY
+ )).contents(urls=["https://example.com"])
+ assert isinstance(res, list)
+ assert isinstance(res[0], ContentsResponse)
+ assert res[0].url == "https://example.com"
+
+ def test_posts_to_contents_endpoint(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ captured["method"] = request.method
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_CONTENTS_BODY
+ )
+
+ _sync_you(handler).contents(urls=["https://example.com"])
+ assert captured["method"] == "POST"
+ assert "/v1/contents" in captured["url"]
+
+ def test_passes_urls_in_body(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_CONTENTS_BODY
+ )
+
+ _sync_you(handler).contents(urls=["https://example.com", "https://python.org"])
+ assert captured["body"]["urls"] == ["https://example.com", "https://python.org"]
+
+ @pytest.mark.asyncio
+ async def test_async_returns_contents_response_list(self):
+ res = await _async_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_CONTENTS_BODY
+ )).contents_async(urls=["https://example.com"])
+ assert isinstance(res, list)
+ assert isinstance(res[0], ContentsResponse)
+
+
+# ---------------------------------------------------------------------------
+# you.agents() — POST /v1/agents/runs
+# ---------------------------------------------------------------------------
+
+
+class TestAgentsDirect:
+ def test_returns_agent_runs_batch_response(self):
+ res = _sync_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_RUNS_BODY
+ )).agents(
+ request=ExpressAgentRunsRequest(input="Hello", stream=False),
+ )
+ assert isinstance(res, AgentRunsBatchResponse)
+ assert res.output is not None
+ assert len(res.output) == 1
+
+ def test_posts_to_agents_runs_endpoint(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ captured["method"] = request.method
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_RUNS_BODY
+ )
+
+ _sync_you(handler).agents(
+ request=ExpressAgentRunsRequest(input="Hello", stream=False),
+ )
+ assert captured["method"] == "POST"
+ assert "/v1/agents/runs" in captured["url"]
+
+ def test_passes_request_in_body(self):
+ captured: dict = {}
+
+ def handler(request):
+ captured["body"] = json.loads(request.content)
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_RUNS_BODY
+ )
+
+ _sync_you(handler).agents(
+ request=ExpressAgentRunsRequest(input="Teach me Python", stream=False),
+ )
+ assert captured["body"]["agent"] == "express"
+ assert captured["body"]["input"] == "Teach me Python"
+ assert captured["body"]["stream"] is False
+
+ @pytest.mark.asyncio
+ async def test_async_returns_agent_runs_batch_response(self):
+ res = await _async_you(lambda req: httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_RUNS_BODY
+ )).agents_async(
+ request=ExpressAgentRunsRequest(input="Hello", stream=False),
+ )
+ assert isinstance(res, AgentRunsBatchResponse)
diff --git a/tests/test_live.py b/tests/test_live.py
index ebe9304..b232ea1 100644
--- a/tests/test_live.py
+++ b/tests/test_live.py
@@ -16,6 +16,7 @@
"""
import os
+import httpx
import pytest
from youdotcom import You
@@ -37,7 +38,9 @@
TaskResponse,
TaskDetail,
FinanceResearchEffort,
+ AnswerResponse,
)
+
from youdotcom.research_helpers import (
research_background,
poll_research_task,
@@ -51,11 +54,11 @@
)
-# Skip all tests in this file if no API key is provided.
+# Skip keyed tests if no API key is provided.
# Mirror the SDK's own env-var precedence (YDC_API_KEY first, then
# YOU_API_KEY_AUTH as the documented 2.3.x fallback) so users on the
# fallback env var don't get their live suite silently skipped.
-pytestmark = pytest.mark.skipif(
+requires_api_key = pytest.mark.skipif(
not (os.getenv("YDC_API_KEY") or os.getenv("YOU_API_KEY_AUTH")),
reason="YDC_API_KEY or YOU_API_KEY_AUTH environment variable not set"
)
@@ -85,13 +88,14 @@ def you_client(api_key):
)
+@requires_api_key
class TestLiveSearch:
"""Live tests for the Search API."""
def test_basic_search(self, you_client):
"""Test basic search functionality against live API."""
with you_client as you:
- res = you.search.unified(query="Python programming language")
+ res = you.search(query="Python programming language")
assert res.results is not None
assert res.metadata is not None
@@ -102,7 +106,7 @@ def test_basic_search(self, you_client):
def test_search_with_filters(self, you_client):
"""Test search with filters against live API."""
with you_client as you:
- res = you.search.unified(
+ res = you.search(
query="artificial intelligence",
count=5,
freshness=Freshness.WEEK,
@@ -119,7 +123,7 @@ def test_search_with_filters(self, you_client):
def test_search_with_livecrawl_web(self, you_client):
"""Test search with livecrawl for web results."""
with you_client as you:
- res = you.search.unified(
+ res = you.search(
query="machine learning tutorials",
count=3,
livecrawl=LiveCrawl.WEB,
@@ -134,12 +138,13 @@ def test_search_with_livecrawl_web(self, you_client):
# Check that we can access the contents field
if result.contents:
# At least one of html or markdown should be present
- assert result.contents.markdown or result.contents.html
+ # (API may return empty string for some URLs)
+ assert result.contents.markdown is not None or result.contents.html is not None
def test_search_with_livecrawl_news(self, you_client):
"""Test search with livecrawl for news results (new in 2.2.0)."""
with you_client as you:
- res = you.search.unified(
+ res = you.search(
query="technology news today",
count=3,
livecrawl=LiveCrawl.NEWS,
@@ -159,7 +164,7 @@ def test_search_with_livecrawl_news(self, you_client):
def test_search_with_livecrawl_all(self, you_client):
"""Test search with livecrawl=ALL for both web and news."""
with you_client as you:
- res = you.search.unified(
+ res = you.search(
query="breaking tech news",
count=3,
livecrawl=LiveCrawl.ALL,
@@ -167,33 +172,16 @@ def test_search_with_livecrawl_all(self, you_client):
)
assert res.results is not None
-
- # Both web and news should be able to have contents
- has_any_contents = False
-
- if res.results.web:
- for result in res.results.web:
- if result.contents:
- has_any_contents = True
- break
-
- if res.results.news:
- for news_item in res.results.news:
- if news_item.contents:
- has_any_contents = True
- break
-
- # We expect at least some results to have contents with livecrawl=ALL
- # (This assertion may be relaxed if the API doesn't always return contents)
+@requires_api_key
class TestLiveContents:
"""Live tests for the Contents API."""
def test_html_format(self, you_client):
"""Test fetching content in HTML format."""
with you_client as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.HTML],
)
@@ -208,7 +196,7 @@ def test_html_format(self, you_client):
def test_markdown_format(self, you_client):
"""Test fetching content in Markdown format."""
with you_client as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.MARKDOWN],
)
@@ -219,7 +207,7 @@ def test_markdown_format(self, you_client):
def test_metadata_format(self, you_client):
"""Test fetching metadata from a page."""
with you_client as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.METADATA],
)
@@ -232,7 +220,7 @@ def test_metadata_format(self, you_client):
def test_multiple_formats(self, you_client):
"""Test fetching multiple formats at once."""
with you_client as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.HTML, ContentsFormats.MARKDOWN],
)
@@ -241,13 +229,14 @@ def test_multiple_formats(self, you_client):
assert len(res) > 0
+@requires_api_key
class TestLiveAgents:
"""Live tests for the Agents API."""
def test_express_agent(self, you_client):
"""Test Express agent with basic query."""
with you_client as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=ExpressAgentRunsRequest(
input="What is the capital of France?",
stream=False,
@@ -262,7 +251,7 @@ def test_express_agent(self, you_client):
def test_advanced_agent_with_research(self, you_client):
"""Test Advanced agent with ResearchTool."""
with you_client as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=AdvancedAgentRunsRequest(
input="What are the latest developments in AI?",
stream=False,
@@ -277,6 +266,7 @@ def test_advanced_agent_with_research(self, you_client):
assert res.output is not None
+@requires_api_key
class TestLiveResearch:
"""Live tests for the Research API (new in 2.3.0)."""
@@ -338,6 +328,7 @@ def test_research_with_sources(self, you_client):
assert source.url is not None
+@requires_api_key
class TestLiveResearchOutputSchema:
"""Live test for Research `output_schema` parameter (beta feature).
@@ -375,6 +366,7 @@ def test_research_output_schema_structured_payload(self, you_client):
assert "same_entity" in res.output.content
+@requires_api_key
class TestLiveResearchSourceControl:
"""Live test for Research `source_control` parameter (beta feature).
@@ -399,6 +391,7 @@ def test_research_source_control_with_boost_domains(self, you_client):
assert len(res.output.content) > 0
+@requires_api_key
class TestLiveFinanceResearch:
"""Live tests for the Finance Research API."""
@@ -442,6 +435,7 @@ def test_finance_research_lite_effort(self, you_client):
assert source.url is not None
+@requires_api_key
class TestLiveContentsMaxAge:
"""Live test for Contents `max_age` parameter.
@@ -452,7 +446,7 @@ class TestLiveContentsMaxAge:
def test_contents_with_max_age(self, you_client):
"""max_age is accepted as an optional parameter."""
with you_client as you:
- res = you.contents.generate(
+ res = you.contents(
urls=["https://www.example.com"],
formats=[ContentsFormats.MARKDOWN],
max_age=86400, # 1 day
@@ -462,6 +456,7 @@ def test_contents_with_max_age(self, you_client):
assert len(res) > 0
+@requires_api_key
class TestLiveSearchBoostDomains:
"""Live test for Search `boost_domains` parameter.
@@ -469,10 +464,10 @@ class TestLiveSearchBoostDomains:
other domains — for a more permissive alternative to `include_domains`.
"""
- def test_search_post_boost_domains_list(self, you_client):
- """search_post accepts a Python list of boost domains."""
+ def test_search_boost_domains_list(self, you_client):
+ """search accepts a Python list of boost domains."""
with you_client as you:
- res = you.search_post(
+ res = you.search(
query="Python type hints vs TypeScript inference",
count=5,
boost_domains=["python.org", "realpython.com"],
@@ -499,6 +494,7 @@ def test_search_post_boost_domains_list(self, you_client):
_BG_TIMEOUT_S = 120.0 # generous wall-clock for LITE background tasks
+@requires_api_key
class TestLiveResearchBackground:
"""Live tests for background-mode research (POST /v1/research?background=true)."""
@@ -603,6 +599,7 @@ def test_research_and_wait(self, you_client):
assert "output" in result_dump
+@requires_api_key
class TestLiveResearchBackgroundHelpers:
"""Live tests for the research_helpers convenience functions."""
@@ -669,6 +666,7 @@ def test_stream_research(self, you_client):
# For a live test we use a simple query and a generous but bounded timeout.
# Marked slow so it can be skipped with `-m "not slow"`.
# ---------------------------------------------------------------------------
+@requires_api_key
class TestLiveResearchFrontier:
"""Live tests for frontier research effort (requires background=true)."""
@@ -708,6 +706,145 @@ def test_frontier_without_background_raises_422(self, you_client):
)
+# ---------------------------------------------------------------------------
+# Answer API (new in 2.6.0)
+# ---------------------------------------------------------------------------
+@requires_api_key
+class TestLiveAnswer:
+ """Live tests for the Answer API (POST /v1/answer).
+
+ The Answer API returns a synthesized answer with citations and web results.
+ Requires an API key (not keyless).
+ """
+
+ def test_basic_answer(self, you_client):
+ """Test basic answer query returns AnswerResponse with answer + citations."""
+ with you_client as you:
+ res = you.answer(query="What is the capital of France?")
+
+ assert isinstance(res, AnswerResponse)
+ assert len(res.answer) > 0
+ # Citations should be present for a factual query
+ assert len(res.citations) > 0
+ for citation in res.citations:
+ assert citation.source is not None
+ assert len(citation.source) > 0
+ # Web results should be present
+ assert len(res.results.web) > 0
+ for result in res.results.web:
+ assert result.url is not None
+ assert result.title is not None
+
+ def test_answer_with_freshness(self, you_client):
+ """Test answer with freshness filter."""
+ with you_client as you:
+ res = you.answer(
+ query="Latest AI developments",
+ freshness="week",
+ )
+
+ assert isinstance(res, AnswerResponse)
+ assert len(res.answer) > 0
+
+ def test_answer_with_country(self, you_client):
+ """Test answer with country filter."""
+ with you_client as you:
+ res = you.answer(
+ query="Best restaurants in London",
+ country=Country.GB,
+ )
+
+ assert isinstance(res, AnswerResponse)
+ assert len(res.answer) > 0
+
+ def test_answer_with_boost_domains(self, you_client):
+ """Test answer with boost_domains (can combine with exclude, not include)."""
+ with you_client as you:
+ res = you.answer(
+ query="Python type hints",
+ boost_domains=["python.org", "docs.python.org"],
+ )
+
+ assert isinstance(res, AnswerResponse)
+ assert len(res.answer) > 0
+
+ @pytest.mark.asyncio
+ async def test_async_answer(self, you_client):
+ """Test async answer.create_async()."""
+ with you_client as you:
+ res = await you.answer_async(query="What is 2+2?")
+
+ assert isinstance(res, AnswerResponse)
+ assert len(res.answer) > 0
+
+
+# ---------------------------------------------------------------------------
+# Keyless search (POST /v1/agents/search — no API key required for free tier)
+# ---------------------------------------------------------------------------
+class TestLiveSearchKeyless:
+ """Live tests for you.search() → POST /v1/agents/search (keyless-capable).
+
+ These verify the keyless-capable search method against the real API.
+ With an API key, the proxy forwards to /v1/search with full features.
+ """
+
+ @requires_api_key
+ def test_keyed_search(self, you_client):
+ """search() with an API key returns full SearchResponse."""
+ with you_client as you:
+ res = you.search(query="Python programming language", count=5)
+
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) > 0
+
+ def test_keyless_search(self):
+ """search() with NO API key works via the free-tier proxy."""
+ you = You(timeout_ms=LIVE_TIMEOUT_MS)
+ with you:
+ res = you.search(query="Python programming language", count=5)
+
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) > 0
+
+ def test_keyless_search_with_filters(self):
+ """Keyless search accepts country/freshness/safesearch (server enforces limits)."""
+ you = You(timeout_ms=LIVE_TIMEOUT_MS)
+ with you:
+ res = you.search(
+ query="artificial intelligence news",
+ count=3,
+ country="US",
+ freshness="week",
+ safesearch="moderate",
+ )
+
+ assert res.results is not None
+ assert res.results.web is not None
+
+ @pytest.mark.asyncio
+ async def test_async_keyless_search(self):
+ """search_async() with NO API key works via the free-tier proxy."""
+ you = You(timeout_ms=LIVE_TIMEOUT_MS)
+ async with you:
+ res = await you.search_async(query="What is machine learning?", count=3)
+
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) > 0
+
+ def test_custom_user_agent_keyless(self):
+ """A custom user_agent doesn't break the keyless search path."""
+ you = You(timeout_ms=LIVE_TIMEOUT_MS)
+ you.sdk_configuration.user_agent = "test-integration/1.0"
+ with you:
+ res = you.search(query="Python programming language", count=3)
+
+ assert res.results is not None
+ assert res.results.web is not None
+
+
if __name__ == "__main__":
# Run with: python -m pytest tests/test_live.py -v
pytest.main([__file__, "-v"])
diff --git a/tests/test_performance.py b/tests/test_performance.py
index 5e958eb..3b7cbd6 100644
--- a/tests/test_performance.py
+++ b/tests/test_performance.py
@@ -168,11 +168,11 @@ class TestSearchPerformance:
def test_search_basic(self, server_url, api_key, iterations, show_detailed):
"""Basic search with query only."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="latest AI developments", server_url=server_url)
+ you.search(query="latest AI developments", server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: basic query")
ALL_METRICS.append(metrics)
@@ -181,11 +181,11 @@ def call():
def test_search_with_count(self, server_url, api_key, iterations, show_detailed):
"""Search with result count limit."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="python programming", count=10, server_url=server_url)
+ you.search(query="python programming", count=10, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: with count=10")
ALL_METRICS.append(metrics)
@@ -194,11 +194,11 @@ def call():
def test_search_with_freshness_day(self, server_url, api_key, iterations, show_detailed):
"""Search with freshness filter (day)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="breaking news", freshness=Freshness.DAY, server_url=server_url)
+ you.search(query="breaking news", freshness=Freshness.DAY, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: freshness=DAY")
ALL_METRICS.append(metrics)
@@ -207,11 +207,11 @@ def call():
def test_search_with_freshness_week(self, server_url, api_key, iterations, show_detailed):
"""Search with freshness filter (week)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="renewable energy", freshness=Freshness.WEEK, server_url=server_url)
+ you.search(query="renewable energy", freshness=Freshness.WEEK, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: freshness=WEEK")
ALL_METRICS.append(metrics)
@@ -220,11 +220,11 @@ def call():
def test_search_with_country_us(self, server_url, api_key, iterations, show_detailed):
"""Search with country filter (US)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="local restaurants", country=Country.US, server_url=server_url)
+ you.search(query="local restaurants", country=Country.US, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: country=US")
ALL_METRICS.append(metrics)
@@ -233,11 +233,11 @@ def call():
def test_search_with_country_gb(self, server_url, api_key, iterations, show_detailed):
"""Search with country filter (GB)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="football news", country=Country.GB, server_url=server_url)
+ you.search(query="football news", country=Country.GB, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: country=GB")
ALL_METRICS.append(metrics)
@@ -246,11 +246,11 @@ def call():
def test_search_with_language_en(self, server_url, api_key, iterations, show_detailed):
"""Search with language filter (English)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="machine learning", language=Language.EN, server_url=server_url)
+ you.search(query="machine learning", language=Language.EN, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: language=EN")
ALL_METRICS.append(metrics)
@@ -259,11 +259,11 @@ def call():
def test_search_with_language_es(self, server_url, api_key, iterations, show_detailed):
"""Search with language filter (Spanish)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="tecnología", language=Language.ES, server_url=server_url)
+ you.search(query="tecnología", language=Language.ES, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: language=ES")
ALL_METRICS.append(metrics)
@@ -272,11 +272,11 @@ def call():
def test_search_with_safesearch_off(self, server_url, api_key, iterations, show_detailed):
"""Search with safesearch off."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="research", safesearch=SafeSearch.OFF, server_url=server_url)
+ you.search(query="research", safesearch=SafeSearch.OFF, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: safesearch=OFF")
ALL_METRICS.append(metrics)
@@ -285,11 +285,11 @@ def call():
def test_search_with_safesearch_moderate(self, server_url, api_key, iterations, show_detailed):
"""Search with safesearch moderate."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="family content", safesearch=SafeSearch.MODERATE, server_url=server_url)
+ you.search(query="family content", safesearch=SafeSearch.MODERATE, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: safesearch=MODERATE")
ALL_METRICS.append(metrics)
@@ -298,11 +298,11 @@ def call():
def test_search_with_safesearch_strict(self, server_url, api_key, iterations, show_detailed):
"""Search with safesearch strict."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="kids learning", safesearch=SafeSearch.STRICT, server_url=server_url)
+ you.search(query="kids learning", safesearch=SafeSearch.STRICT, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: safesearch=STRICT")
ALL_METRICS.append(metrics)
@@ -311,11 +311,11 @@ def call():
def test_search_with_pagination(self, server_url, api_key, iterations, show_detailed):
"""Search with pagination (offset)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(query="python tutorials", count=5, offset=2, server_url=server_url)
+ you.search(query="python tutorials", count=5, offset=2, server_url=server_url)
metrics = measure_sdk_call(call, client, iterations, "Search: with pagination (offset=2)")
ALL_METRICS.append(metrics)
@@ -324,11 +324,11 @@ def call():
def test_search_with_livecrawl_web(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl enabled for web results."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="machine learning tutorials",
count=3,
livecrawl=LiveCrawl.WEB,
@@ -342,11 +342,11 @@ def call():
def test_search_with_livecrawl_news(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl enabled for news results."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="tech news",
count=3,
livecrawl=LiveCrawl.NEWS,
@@ -360,11 +360,11 @@ def call():
def test_search_with_livecrawl_all(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl enabled for all results."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="quantum computing",
count=3,
livecrawl=LiveCrawl.ALL,
@@ -378,11 +378,11 @@ def call():
def test_search_with_livecrawl_html(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl returning HTML format."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="AI research",
count=3,
livecrawl=LiveCrawl.WEB,
@@ -397,11 +397,11 @@ def call():
def test_search_with_livecrawl_markdown(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl returning Markdown format."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="documentation guides",
count=3,
livecrawl=LiveCrawl.WEB,
@@ -416,11 +416,11 @@ def call():
def test_search_with_all_filters(self, server_url, api_key, iterations, show_detailed):
"""Search with multiple filters combined."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="quantum computing research",
count=10,
freshness=Freshness.MONTH,
@@ -438,11 +438,11 @@ def call():
def test_search_with_filters_and_livecrawl(self, server_url, api_key, iterations, show_detailed):
"""Search with filters and livecrawl combined."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="AI developments",
count=5,
freshness=Freshness.WEEK,
@@ -459,11 +459,11 @@ def call():
def test_search_with_news_livecrawl(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl for news results (news now supports contents)."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="technology news",
count=5,
livecrawl=LiveCrawl.NEWS,
@@ -478,11 +478,11 @@ def call():
def test_search_with_livecrawl_all_news_contents(self, server_url, api_key, iterations, show_detailed):
"""Search with livecrawl=ALL for both web and news contents."""
- client = create_timing_client("get_/v1/search")
+ client = create_timing_client("post_/v1/agents/search")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.search.unified(
+ you.search(
query="breaking tech news",
count=3,
livecrawl=LiveCrawl.ALL,
@@ -509,7 +509,7 @@ def test_agents_express_no_tools(self, server_url, api_key, iterations, show_det
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="Teach me how to make an omelet",
stream=False,
@@ -528,7 +528,7 @@ def test_agents_express_with_websearch(self, server_url, api_key, iterations, sh
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="What are the latest AI developments?",
stream=False,
@@ -548,7 +548,7 @@ def test_agents_express_with_websearch_force(self, server_url, api_key, iteratio
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="Tell me about Python",
stream=False,
@@ -568,7 +568,7 @@ def test_agents_advanced_no_tools(self, server_url, api_key, iterations, show_de
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Explain quantum entanglement",
stream=False,
@@ -587,7 +587,7 @@ def test_agents_advanced_with_research(self, server_url, api_key, iterations, sh
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Research the latest breakthroughs in quantum computing",
stream=False,
@@ -610,7 +610,7 @@ def test_agents_advanced_with_research_low_effort(self, server_url, api_key, ite
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Quick research on AI",
stream=False,
@@ -633,7 +633,7 @@ def test_agents_advanced_with_research_high_effort(self, server_url, api_key, it
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Deep research on climate change",
stream=False,
@@ -656,7 +656,7 @@ def test_agents_advanced_with_research_verbosity_low(self, server_url, api_key,
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Brief summary of AI trends",
stream=False,
@@ -679,7 +679,7 @@ def test_agents_advanced_with_research_verbosity_high(self, server_url, api_key,
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Detailed analysis of blockchain",
stream=False,
@@ -702,7 +702,7 @@ def test_agents_advanced_with_compute(self, server_url, api_key, iterations, sho
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Calculate the square root of 169",
stream=False,
@@ -722,7 +722,7 @@ def test_agents_advanced_with_websearch_and_research(self, server_url, api_key,
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Find and research AI startups",
stream=False,
@@ -745,7 +745,7 @@ def test_agents_advanced_with_websearch_and_compute(self, server_url, api_key, i
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Find stock prices and calculate averages",
stream=False,
@@ -765,7 +765,7 @@ def test_agents_advanced_with_research_and_compute(self, server_url, api_key, it
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Research market trends and calculate growth rates",
stream=False,
@@ -788,7 +788,7 @@ def test_agents_advanced_with_all_tools(self, server_url, api_key, iterations, s
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=AdvancedAgentRunsRequest(
input="Research tech trends, find data, and calculate statistics",
stream=False,
@@ -811,7 +811,7 @@ def test_agents_express_verbosity_low(self, server_url, api_key, iterations, sho
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="Brief overview of Python",
stream=False,
@@ -830,7 +830,7 @@ def test_agents_express_verbosity_high(self, server_url, api_key, iterations, sh
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="Detailed explanation of Python",
stream=False,
@@ -857,7 +857,7 @@ def test_contents_single_url_html(self, server_url, api_key, iterations, show_de
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.HTML],
server_url=server_url,
@@ -874,7 +874,7 @@ def test_contents_single_url_markdown(self, server_url, api_key, iterations, sho
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.MARKDOWN],
server_url=server_url,
@@ -891,7 +891,7 @@ def test_contents_single_url_metadata(self, server_url, api_key, iterations, sho
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.METADATA],
server_url=server_url,
@@ -908,7 +908,7 @@ def test_contents_multiple_formats(self, server_url, api_key, iterations, show_d
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.HTML, ContentsFormats.MARKDOWN, ContentsFormats.METADATA],
server_url=server_url,
@@ -925,7 +925,7 @@ def test_contents_with_crawl_timeout(self, server_url, api_key, iterations, show
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=["https://www.python.org"],
formats=[ContentsFormats.HTML],
crawl_timeout=30,
@@ -943,7 +943,7 @@ def test_contents_multiple_urls_html(self, server_url, api_key, iterations, show
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=[
"https://www.python.org",
"https://www.github.com",
@@ -964,7 +964,7 @@ def test_contents_multiple_urls_markdown(self, server_url, api_key, iterations,
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=[
"https://www.python.org",
"https://www.github.com",
@@ -985,7 +985,7 @@ def test_contents_many_urls_html(self, server_url, api_key, iterations, show_det
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
def call():
- you.contents.generate(
+ you.contents(
urls=[
"https://www.python.org",
"https://www.github.com",
diff --git a/tests/test_runs.py b/tests/test_runs.py
index 3a949f9..9195a3c 100644
--- a/tests/test_runs.py
+++ b/tests/test_runs.py
@@ -37,7 +37,7 @@ def test_basic(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=ExpressAgentRunsRequest(
input="Teach me how to make an omelet",
stream=False,
@@ -54,7 +54,7 @@ def test_streaming(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=ExpressAgentRunsRequest(
input="Teach me how to make an omelet",
stream=True,
@@ -70,7 +70,7 @@ def test_with_web_search_tool(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=ExpressAgentRunsRequest(
input="Summarize today's top AI research headlines.",
stream=False,
@@ -88,7 +88,7 @@ def test_with_research_tool(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=AdvancedAgentRunsRequest(
input="Summarize today's top AI research headlines.",
stream=False,
@@ -107,7 +107,7 @@ def test_with_compute_tool(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=AdvancedAgentRunsRequest(
input="Calculate 15 * 23 and explain the steps.",
stream=False,
@@ -123,7 +123,7 @@ def test_with_multiple_tools(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=AdvancedAgentRunsRequest(
input="Research and calculate the square root of 169.",
stream=True,
@@ -146,7 +146,7 @@ def test_research_tool_configuration(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=AdvancedAgentRunsRequest(
input="Research quantum computing breakthroughs.",
stream=False,
@@ -169,7 +169,7 @@ def test_with_uuid(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=CustomAgentRunsRequest(
agent="c12fa027-424e-4002-9659-746c16e74faa",
input="Teach me how to make an omelet",
@@ -185,7 +185,7 @@ def test_with_tools(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=CustomAgentRunsRequest(
agent="c12fa027-424e-4002-9659-746c16e74faa",
input="Search for Python best practices.",
@@ -205,7 +205,7 @@ def test_unauthorized(self, server_url):
with You(server_url=server_url, client=client, api_key_auth="invalid") as you:
with pytest.raises(AgentRuns401ResponseError):
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="test",
stream=False,
@@ -220,7 +220,7 @@ def test_forbidden(self, server_url, api_key):
# Mock server returns 403 which gets caught as a default error
# In production API, this would be a more specific error type
with pytest.raises(YouDefaultError):
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="test",
stream=False,
@@ -233,7 +233,7 @@ def test_bad_request(self, server_url, api_key):
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
with pytest.raises((AgentRuns400ResponseError, YouDefaultError)):
- you.agents.runs.create(
+ you.agents(
request=ExpressAgentRunsRequest(
input="test",
stream=False,
@@ -245,7 +245,7 @@ def test_empty_input(self, server_url, api_key):
client = create_test_http_client("post_/v1/agents/runs")
with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.agents.runs.create(
+ res = you.agents(
request=ExpressAgentRunsRequest(
input="",
stream=False,
diff --git a/tests/test_search.py b/tests/test_search.py
index 1374717..19a6812 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -1,204 +1,29 @@
-import os
import json
import pytest
import httpx
-from tests.test_client import create_test_http_client
from youdotcom import You
from youdotcom.errors import (
ForbiddenResponseError,
UnauthorizedResponseError,
UnprocessableEntityResponseError,
- YouDefaultError,
)
-from youdotcom.models import (
- Country,
- Freshness,
- LiveCrawl,
- LiveCrawlFormats,
- SafeSearch,
-)
-
-
-@pytest.fixture
-def server_url():
- return os.getenv("TEST_SERVER_URL", "http://localhost:18080")
-
-
-@pytest.fixture
-def api_key():
- return "test-api-key"
-
-
-class TestSearchBasic:
- def test_basic_search(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(query="latest AI developments", server_url=server_url)
-
- assert res.results is not None
- assert res.metadata is not None
- assert res.metadata.query is not None
- assert res.results.web or res.results.news
-
-
-class TestSearchFilters:
- def test_search_with_filters(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="renewable energy",
- count=10,
- freshness=Freshness.WEEK,
- country=Country.US,
- safesearch=SafeSearch.MODERATE,
- server_url=server_url,
- )
-
- assert res.results is not None
- assert res.metadata is not None
-
- def test_search_with_pagination(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="python programming",
- count=5,
- offset=1,
- server_url=server_url,
- )
-
- assert res.results is not None
- assert res.metadata is not None
-
- def test_search_with_livecrawl(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="machine learning tutorials",
- count=3,
- livecrawl=LiveCrawl.WEB,
- livecrawl_formats=[LiveCrawlFormats.MARKDOWN],
- server_url=server_url,
- )
-
- assert res.results is not None
-
- if res.results.web:
- for result in res.results.web:
- if hasattr(result, "contents") and result.contents:
- assert result.contents.markdown is not None
-
- def test_search_all_parameters(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="quantum computing",
- count=20,
- offset=0,
- freshness=Freshness.MONTH,
- country=Country.GB,
- safesearch=SafeSearch.STRICT,
- livecrawl=LiveCrawl.WEB,
- livecrawl_formats=[LiveCrawlFormats.HTML],
- server_url=server_url,
- )
-
- assert res.results is not None
- assert res.metadata is not None
-
- if res.results.web:
- for result in res.results.web:
- if hasattr(result, "contents") and result.contents:
- assert result.contents.html is not None
-
- def test_search_news_with_livecrawl(self, server_url, api_key):
- """Test that news results can have contents when livecrawl is enabled (new in 2.2.0)."""
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="technology news",
- count=5,
- livecrawl=LiveCrawl.NEWS,
- livecrawl_formats=[LiveCrawlFormats.MARKDOWN],
- server_url=server_url,
- )
-
- assert res.results is not None
-
- # News results can now have contents field when livecrawl is enabled
- if res.results.news:
- for news_item in res.results.news:
- # Contents field is optional but should be accessible
- if hasattr(news_item, "contents") and news_item.contents:
- # If contents exists, it should have markdown when requested
- assert news_item.contents.markdown is not None or news_item.contents.html is not None
-
- def test_search_livecrawl_all_with_news_contents(self, server_url, api_key):
- """Test livecrawl=ALL returns contents for both web and news results."""
- client = create_test_http_client("get_/v1/search")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- res = you.search.unified(
- query="breaking tech news",
- count=3,
- livecrawl=LiveCrawl.ALL,
- livecrawl_formats=[LiveCrawlFormats.HTML],
- server_url=server_url,
- )
-
- assert res.results is not None
-
- # Both web and news can have contents with livecrawl=ALL
- if res.results.web:
- for result in res.results.web:
- if hasattr(result, "contents") and result.contents:
- assert result.contents.html is not None
-
- if res.results.news:
- for news_item in res.results.news:
- if hasattr(news_item, "contents") and news_item.contents:
- assert news_item.contents.html is not None
-
-
-class TestSearchErrors:
- def test_unauthorized(self, server_url):
- client = create_test_http_client("get_/v1/search-unauthorized")
-
- with You(server_url=server_url, client=client, api_key_auth="invalid") as you:
- with pytest.raises((UnauthorizedResponseError, ForbiddenResponseError, YouDefaultError)):
- you.search.unified(query="test", server_url=server_url)
-
- def test_forbidden(self, server_url, api_key):
- client = create_test_http_client("get_/v1/search-forbidden")
-
- with You(server_url=server_url, client=client, api_key_auth=api_key) as you:
- with pytest.raises((ForbiddenResponseError, YouDefaultError)):
- you.search.unified(query="test", server_url=server_url)
# ---------------------------------------------------------------------------
-# POST-side error tests: search_post() must raise the same consolidated
-# *ResponseError classes as search.unified() (GET). Uses MockTransport
-# because the mockserver has no POST /v1/search handler.
+# Error tests: search() (POST /v1/agents/search) must raise the
+# consolidated *ResponseError classes. Uses MockTransport.
# ---------------------------------------------------------------------------
-class TestSearchPostErrors:
- """Verify search_post() raises the consolidated *ResponseError classes.
+class TestSearchErrors:
+ """Verify search() raises the consolidated *ResponseError classes.
- The CHANGELOG documents that both Search endpoints (GET and POST) raise
- the consolidated error classes. These tests lock that contract for the
- POST path so a regen that mis-wires POST errors would fail CI.
+ These tests lock the error-class contract so a regen that mis-wires
+ errors would fail CI. Each test asserts the specific error class.
"""
- def test_post_unauthorized(self):
+ def test_unauthorized(self):
def handler(request):
return httpx.Response(
401,
@@ -209,11 +34,11 @@ def handler(request):
transport = httpx.MockTransport(handler)
sdk_client = httpx.Client(transport=transport)
you = You(server_url="http://mock.local", client=sdk_client, api_key_auth="invalid")
- with pytest.raises((UnauthorizedResponseError, YouDefaultError)):
- you.search_post(query="test")
+ with pytest.raises(UnauthorizedResponseError):
+ you.search(query="test")
sdk_client.close()
- def test_post_forbidden(self):
+ def test_forbidden(self):
def handler(request):
return httpx.Response(
403,
@@ -224,11 +49,11 @@ def handler(request):
transport = httpx.MockTransport(handler)
sdk_client = httpx.Client(transport=transport)
you = You(server_url="http://mock.local", client=sdk_client, api_key_auth="test")
- with pytest.raises((ForbiddenResponseError, YouDefaultError)):
- you.search_post(query="test")
+ with pytest.raises(ForbiddenResponseError):
+ you.search(query="test")
sdk_client.close()
- def test_post_unprocessable(self):
+ def test_unprocessable(self):
def handler(request):
return httpx.Response(
422,
@@ -239,8 +64,8 @@ def handler(request):
transport = httpx.MockTransport(handler)
sdk_client = httpx.Client(transport=transport)
you = You(server_url="http://mock.local", client=sdk_client, api_key_auth="test")
- with pytest.raises((UnprocessableEntityResponseError, YouDefaultError)):
- you.search_post(
+ with pytest.raises(UnprocessableEntityResponseError):
+ you.search(
query="test",
include_domains=["example.com"],
exclude_domains=["spam.com"],
@@ -248,10 +73,10 @@ def handler(request):
sdk_client.close()
-class TestSearchPostBoostDomains:
- """Verify search_post() forwards boost_domains in the request body."""
+class TestSearchBoostDomains:
+ """Verify search() forwards boost_domains in the request body."""
- def test_post_boost_domains_forwarded(self):
+ def test_boost_domains_forwarded(self):
def handler(request):
body = json.loads(request.content)
assert "boost_domains" in body
@@ -272,10 +97,9 @@ def handler(request):
transport = httpx.MockTransport(handler)
sdk_client = httpx.Client(transport=transport)
you = You(server_url="http://mock.local", client=sdk_client, api_key_auth="test")
- res = you.search_post(
+ res = you.search(
query="Python type hints",
boost_domains=["python.org", "realpython.com"],
)
assert res.results is not None
sdk_client.close()
-
diff --git a/tests/test_search_keyless.py b/tests/test_search_keyless.py
new file mode 100644
index 0000000..5c8f636
--- /dev/null
+++ b/tests/test_search_keyless.py
@@ -0,0 +1,283 @@
+"""Tests for you.search() — keyless-capable ``POST /v1/agents/search``.
+
+The search method now targets ``/v1/agents/search`` (the keyless-capable
+proxy) instead of ``/v1/search``. With no API key it runs in the free tier;
+with a key the proxy forwards to the full search endpoint.
+
+Three test layers:
+- **Unit** (MockTransport): TestSearchSuccess, TestSearchErrors — fast, no server.
+- **Mock server** (Go): TestSearchKeylessMockServer — verifies the keyless path
+ against the mock server with no API key header.
+- **Live**: tests/test_live.py::TestLiveSearchKeyless — hits the real API.
+"""
+
+import json
+import os
+
+import httpx
+import pytest
+
+from youdotcom import You
+from youdotcom._version import __version__
+from youdotcom.errors import (
+ InternalServerErrorResponse,
+ PaymentRequiredResponseError,
+ UnauthorizedResponseError,
+ UnprocessableEntityResponseError,
+ YouDefaultError,
+)
+from youdotcom.models import SearchResponse
+
+
+_SEARCH_BODY = json.dumps(
+ {"results": {"web": [{"title": "Test Result", "url": "https://example.com"}]}}
+)
+
+
+def _make_handler(status: int = 200, body: str = _SEARCH_BODY):
+ def handler(request):
+ return httpx.Response(
+ status, headers={"content-type": "application/json"}, content=body
+ )
+
+ return handler
+
+
+def _sync_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "client": httpx.Client(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+def _async_you(handler, *, api_key: str | None = "test-key"):
+ kwargs: dict = {
+ "server_url": "http://mock.local",
+ "async_client": httpx.AsyncClient(transport=httpx.MockTransport(handler)),
+ }
+ if api_key is not None:
+ kwargs["api_key_auth"] = api_key
+ return You(**kwargs)
+
+
+class TestSearchSuccess:
+ def test_keyed_search_returns_search_response(self):
+ res = _sync_you(_make_handler(200)).search(query="python", count=5)
+ assert isinstance(res, SearchResponse)
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) == 1
+ assert res.results.web[0].title == "Test Result"
+
+ def test_keyless_search_returns_search_response(self):
+ """No api_key_auth → keyless free-tier path still returns SearchResponse."""
+ res = _sync_you(_make_handler(200), api_key=None).search(query="python", count=5)
+ assert isinstance(res, SearchResponse)
+
+ def test_string_enum_params_accepted(self):
+ """country/safesearch/livecrawl/freshness accept plain strings."""
+ res = _sync_you(_make_handler(200)).search(
+ query="python",
+ country="US",
+ safesearch="strict",
+ livecrawl="all",
+ freshness="week",
+ )
+ assert isinstance(res, SearchResponse)
+
+ def test_lowercase_language_is_normalized(self):
+ """language='en' should be normalized to 'EN' before model validation."""
+ res = _sync_you(_make_handler(200)).search(query="python", language="en")
+ assert isinstance(res, SearchResponse)
+
+ def test_lowercase_country_is_normalized(self):
+ """country='us' should be normalized to 'US' before model validation."""
+ res = _sync_you(_make_handler(200)).search(query="python", country="us")
+ assert isinstance(res, SearchResponse)
+
+ def test_exclude_and_boost_domains_accepted(self):
+ """exclude_domains and boost_domains should be accepted as lists."""
+ res = _sync_you(_make_handler(200)).search(
+ query="python",
+ exclude_domains=["spam.com"],
+ boost_domains=["realpython.com"],
+ )
+ assert isinstance(res, SearchResponse)
+
+ def test_posts_to_agents_search_endpoint(self):
+ """Request must hit /v1/agents/search, not /v1/search."""
+ captured: dict = {}
+
+ def handler(request):
+ captured["url"] = str(request.url)
+ captured["method"] = request.method
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )
+
+ _sync_you(handler).search(query="python")
+ assert captured["method"] == "POST"
+ assert "/v1/agents/search" in captured["url"]
+
+ def test_default_user_agent_is_set(self):
+ """Default UA on the request is youdotcom-python-sdk/{version}."""
+ captured: dict = {}
+
+ def handler(request):
+ captured["ua"] = request.headers.get("user-agent", "")
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )
+
+ _sync_you(handler).search(query="python")
+ assert captured["ua"] == f"youdotcom-python-sdk/{__version__}"
+
+ def test_custom_user_agent_passes_through(self):
+ """A custom user_agent overrides the default on the wire."""
+ captured: dict = {}
+
+ def handler(request):
+ captured["ua"] = request.headers.get("user-agent", "")
+ return httpx.Response(
+ 200, headers={"content-type": "application/json"}, content=_SEARCH_BODY
+ )
+
+ you = You(
+ api_key_auth="test-key",
+ server_url="http://mock.local",
+ client=httpx.Client(transport=httpx.MockTransport(handler)),
+ )
+ you.sdk_configuration.user_agent = "my-integration/1.0"
+ you.search(query="python")
+ assert captured["ua"] == "my-integration/1.0"
+
+ @pytest.mark.asyncio
+ async def test_async_keyed_search_returns_search_response(self):
+ res = await _async_you(_make_handler(200)).search_async(query="python", count=5)
+ assert isinstance(res, SearchResponse)
+
+ @pytest.mark.asyncio
+ async def test_async_keyless_search_returns_search_response(self):
+ res = await _async_you(_make_handler(200), api_key=None).search_async(query="python", count=5)
+ assert isinstance(res, SearchResponse)
+
+
+class TestSearchErrors:
+ def test_402_raises_payment_required_error(self):
+ body = json.dumps({
+ "error": "payment_required",
+ "message": "Insufficient credits",
+ "upgrade_url": "https://you.com/platform",
+ })
+ with pytest.raises(PaymentRequiredResponseError) as exc_info:
+ _sync_you(_make_handler(402, body)).search(query="python", count=100)
+ assert exc_info.value.status_code == 402
+ assert exc_info.value.data.message == "Insufficient credits"
+ assert exc_info.value.data.upgrade_url == "https://you.com/platform"
+
+ def test_401_raises_unauthorized_error(self):
+ body = json.dumps({"detail": "invalid api key"})
+ with pytest.raises(UnauthorizedResponseError):
+ _sync_you(_make_handler(401, body), api_key="bad-key").search(query="python")
+
+ def test_422_raises_unprocessable_entity_error(self):
+ body = json.dumps({"error": "include_domains and exclude_domains are mutually exclusive"})
+ with pytest.raises(UnprocessableEntityResponseError):
+ _sync_you(_make_handler(422, body)).search(query="python")
+
+ def test_500_raises_internal_server_error(self):
+ body = json.dumps({"detail": "internal server error"})
+ with pytest.raises(InternalServerErrorResponse):
+ _sync_you(_make_handler(500, body)).search(query="python")
+
+ def test_4xx_fallback_raises_default_error(self):
+ body = json.dumps({"detail": "rate limited"})
+ with pytest.raises(YouDefaultError):
+ _sync_you(_make_handler(429, body)).search(query="python")
+
+ @pytest.mark.asyncio
+ async def test_async_402_raises_payment_required_error(self):
+ body = json.dumps({
+ "error": "payment_required",
+ "message": "Free tier limit exceeded",
+ "upgrade_url": "https://you.com/platform",
+ })
+ with pytest.raises(PaymentRequiredResponseError) as exc_info:
+ await _async_you(_make_handler(402, body)).search_async(query="python", count=100)
+ assert exc_info.value.status_code == 402
+ assert exc_info.value.data.error == "payment_required"
+
+ @pytest.mark.asyncio
+ async def test_async_401_raises_unauthorized_error(self):
+ body = json.dumps({"detail": "unauthorized"})
+ with pytest.raises(UnauthorizedResponseError):
+ await _async_you(_make_handler(401, body), api_key="bad-key").search_async(query="python")
+
+ @pytest.mark.asyncio
+ async def test_async_500_raises_internal_server_error(self):
+ body = json.dumps({"detail": "internal server error"})
+ with pytest.raises(InternalServerErrorResponse):
+ await _async_you(_make_handler(500, body)).search_async(query="python")
+
+
+# ---------------------------------------------------------------------------
+# Mock server tests — verify the keyless path against the Go mock server.
+# The mock server's POST /v1/agents/search handler accepts requests without
+# an X-API-Key header (keyless-capable). These tests require the mock server
+# running on localhost:18080 (same as performance tests).
+# ---------------------------------------------------------------------------
+
+
+def _mock_server_url() -> str:
+ return os.getenv("TEST_SERVER_URL", "http://localhost:18080")
+
+
+@pytest.fixture
+def mock_server_running():
+ """Skip if the mock server isn't running."""
+ import socket
+
+ try:
+ with socket.create_connection(("localhost", 18080), timeout=1):
+ pass
+ except (ConnectionRefusedError, OSError):
+ pytest.skip("Mock server not running on localhost:18080")
+
+
+@pytest.mark.usefixtures("mock_server_running")
+class TestSearchKeylessMockServer:
+ """Keyless search against the Go mock server (no API key)."""
+
+ def test_keyless_search_returns_results(self):
+ """you.search() with no API key hits /v1/agents/search on the mock server."""
+ you = You(server_url=_mock_server_url())
+ with you:
+ res = you.search(query="test query", count=5)
+ assert res.results is not None
+ assert res.results.web is not None
+ assert len(res.results.web) > 0
+
+ def test_keyless_search_with_string_params(self):
+ """Keyless search accepts plain string params (country, freshness, safesearch)."""
+ you = You(server_url=_mock_server_url())
+ with you:
+ res = you.search(
+ query="AI news",
+ count=3,
+ country="US",
+ freshness="week",
+ safesearch="moderate",
+ )
+ assert res.results is not None
+
+ @pytest.mark.asyncio
+ async def test_async_keyless_search(self):
+ """you.search_async() with no API key works against the mock server."""
+ you = You(server_url=_mock_server_url())
+ async with you:
+ res = await you.search_async(query="test query", count=3)
+ assert res.results is not None
+ assert res.results.web is not None
diff --git a/tests/test_user_agent_hook.py b/tests/test_user_agent_hook.py
deleted file mode 100644
index 768e73c..0000000
--- a/tests/test_user_agent_hook.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""Unit tests for YDCUserAgentOverrideHook.
-
-This hook is a hand-maintained addition (not regenerated by Speakeasy).
-The CHANGELOG flags it as regen-fragile: future SDK regens will overwrite
-the file and the hook MUST be re-applied. These tests ensure a regen
-revert is caught by CI, mirroring how tests/test_security_env.py guards
-the env-var precedence hand-edit.
-"""
-
-import httpx
-from unittest.mock import Mock
-
-from youdotcom._hooks.registration import YDCUserAgentOverrideHook
-from youdotcom._hooks.types import BeforeRequestContext, HookContext
-from youdotcom._version import __user_agent__, __version__
-
-
-def _make_ctx(user_agent: str, sdk_version: str = __version__) -> BeforeRequestContext:
- """Build a minimal BeforeRequestContext for hook testing."""
- config = Mock()
- config.user_agent = user_agent
- config.sdk_version = sdk_version
- parent = HookContext(
- config=config,
- base_url="http://mock.local",
- operation_id="test",
- oauth2_scopes=None,
- security_source=None,
- tags=None,
- extensions=None,
- )
- return BeforeRequestContext(parent)
-
-
-class TestYDCUserAgentOverrideHook:
- def test_default_speakeasy_ua_is_overridden(self):
- """When user_agent is the speakeasy default, the hook rewrites it to youdotcom-python-sdk/{version}."""
- hook = YDCUserAgentOverrideHook()
- ctx = _make_ctx(__user_agent__)
- request = httpx.Request("GET", "http://mock.local/test")
- hook.before_request(ctx, request)
- assert request.headers["User-Agent"] == f"youdotcom-python-sdk/{__version__}"
-
- def test_custom_user_agent_passes_through(self):
- """When user_agent is a custom value (e.g. integration UA), the hook passes it through unchanged."""
- hook = YDCUserAgentOverrideHook()
- custom_ua = "langchain-youdotcom/1.0"
- ctx = _make_ctx(custom_ua)
- request = httpx.Request("GET", "http://mock.local/test")
- hook.before_request(ctx, request)
- assert request.headers["User-Agent"] == custom_ua
-
- def test_speakeasy_prefix_ua_is_overridden(self):
- """A UA starting with speakeasy-sdk/ (but different from default) is still overridden."""
- hook = YDCUserAgentOverrideHook()
- ctx = _make_ctx("speakeasy-sdk/python 9.9.9 custom")
- request = httpx.Request("GET", "http://mock.local/test")
- hook.before_request(ctx, request)
- assert request.headers["User-Agent"] == f"youdotcom-python-sdk/{__version__}"
diff --git a/uv.lock b/uv.lock
index 4307a77..ecb1231 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,8 @@ version = 1
revision = 3
requires-python = ">=3.10"
resolution-markers = [
- "python_full_version >= '3.12'",
+ "python_full_version >= '3.15'",
+ "python_full_version >= '3.12' and python_full_version < '3.15'",
"python_full_version == '3.11.*'",
"python_full_version < '3.11'",
]
@@ -30,16 +31,57 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
]
+[[package]]
+name = "ast-serialize"
+version = "0.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" },
+ { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" },
+ { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" },
+ { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" },
+ { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" },
+ { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" },
+ { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" },
+ { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" },
+ { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" },
+ { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" },
+ { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" },
+ { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" },
+ { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" },
+]
+
[[package]]
name = "astroid"
-version = "3.2.4"
+version = "4.0.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9e/53/1067e1113ecaf58312357f2cd93063674924119d80d173adc3f6f2387aa2/astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", size = 397576, upload-time = "2024-07-20T12:57:43.26Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/07/63/0adf26577da5eff6eb7a177876c1cfa213856be9926a000f65c4add9692b/astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0", size = 406358, upload-time = "2026-02-07T23:35:07.509Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/80/96/b32bbbb46170a1c8b8b1f28c794202e25cfe743565e9d3469b8eb1e0cc05/astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25", size = 276348, upload-time = "2024-07-20T12:57:40.886Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/cf/1c5f42b110e57bc5502eb80dbc3b03d256926062519224835ef08134f1f9/astroid-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753", size = 276445, upload-time = "2026-02-07T23:35:05.344Z" },
]
[[package]]
@@ -83,7 +125,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -154,6 +196,93 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" },
]
+[[package]]
+name = "librt"
+version = "0.13.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/89/2f/ec5241c38e7fa0fe6c26bfc450e78b9489a6c3c08b394b85d2c10e506975/librt-0.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5", size = 148654, upload-time = "2026-07-08T12:24:30.622Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/1a/d651e18d3ee7aa2879322368c4f278bb7ecaa6b90caadfdec4ebfa8389f3/librt-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547", size = 153537, upload-time = "2026-07-08T12:24:31.773Z" },
+ { url = "https://files.pythonhosted.org/packages/45/18/10bff2122577246009d9619b6569596daf69b7648812f997ca9ca0426f60/librt-0.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2", size = 494336, upload-time = "2026-07-08T12:24:33.079Z" },
+ { url = "https://files.pythonhosted.org/packages/67/69/87dfee871b852970f137fdeae8e2ca356c5ab38e6f21d2a3299535fc3159/librt-0.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929", size = 485393, upload-time = "2026-07-08T12:24:34.324Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/d5/625447a8c0441ff5f15f4ac5e1d323fb9d4d256ebfde7a3c8e003f646057/librt-0.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a", size = 515382, upload-time = "2026-07-08T12:24:35.575Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/d8/1c8c49ea04235960426444deece9092a6b3a9587a850a81bae2335317411/librt-0.13.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac", size = 509483, upload-time = "2026-07-08T12:24:36.923Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/65/f1760fc48050e215201a03506c32b7270159088d01f64557b53e39e74a45/librt-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7", size = 532503, upload-time = "2026-07-08T12:24:38.203Z" },
+ { url = "https://files.pythonhosted.org/packages/18/1b/793e281dcf494879eff99f642b63ebc9c7c58694a1c2d1e93362a22c7041/librt-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40", size = 537027, upload-time = "2026-07-08T12:24:39.34Z" },
+ { url = "https://files.pythonhosted.org/packages/69/45/0801bbb40c9eea795d3dd3ce91c4c5f3fe7d42d23ec4be3e8cb283bcc754/librt-0.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a", size = 517100, upload-time = "2026-07-08T12:24:40.907Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/6c/eb5f514f8e29d4924bc0ff4601dd7b4175557e182e7c0721e84cffa39b8a/librt-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde", size = 558653, upload-time = "2026-07-08T12:24:42.359Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/bf/f140100d1b59fe87ff40b5ecbb4e27924335b189a784e230ee465452f6c2/librt-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8", size = 104402, upload-time = "2026-07-08T12:24:43.668Z" },
+ { url = "https://files.pythonhosted.org/packages/22/7c/57e40fef7cfb61869341cb28bdcefe8a950bebcbecca74a397bae14dce4a/librt-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc", size = 125002, upload-time = "2026-07-08T12:24:44.793Z" },
+ { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" },
+ { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" },
+ { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" },
+ { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" },
+ { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" },
+ { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" },
+ { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" },
+ { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" },
+ { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" },
+ { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" },
+ { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" },
+ { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" },
+ { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" },
+ { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" },
+ { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" },
+ { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" },
+ { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" },
+ { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" },
+ { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" },
+ { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" },
+ { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" },
+ { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" },
+ { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" },
+ { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" },
+]
+
[[package]]
name = "mccabe"
version = "0.7.0"
@@ -165,40 +294,62 @@ wheels = [
[[package]]
name = "mypy"
-version = "1.15.0"
+version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
+ { name = "ast-serialize" },
+ { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
{ name = "mypy-extensions" },
+ { name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433, upload-time = "2025-02-05T03:49:29.145Z" },
- { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472, upload-time = "2025-02-05T03:49:16.986Z" },
- { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424, upload-time = "2025-02-05T03:49:46.908Z" },
- { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450, upload-time = "2025-02-05T03:50:05.89Z" },
- { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765, upload-time = "2025-02-05T03:49:33.56Z" },
- { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701, upload-time = "2025-02-05T03:49:38.981Z" },
- { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" },
- { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" },
- { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" },
- { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" },
- { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" },
- { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" },
- { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" },
- { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" },
- { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" },
- { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" },
- { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" },
- { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" },
- { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" },
- { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" },
- { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" },
- { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" },
- { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" },
- { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" },
- { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/09/f2f5f45dae0c9a0891e4751a73312730e009395102e5d72a22a976cca41f/mypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc", size = 14927774, upload-time = "2026-07-13T11:28:38.224Z" },
+ { url = "https://files.pythonhosted.org/packages/56/b9/345367effd3a6877275a94d481614bfca983f45e028c6290e2cc54603811/mypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3", size = 14000127, upload-time = "2026-07-13T11:30:19.57Z" },
+ { url = "https://files.pythonhosted.org/packages/99/6c/a10b7a7b9f0a755fb94e27ae834d4cea9ad6c5221f9325eef8f182641feb/mypy-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805", size = 14229437, upload-time = "2026-07-13T11:28:17.765Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/bd/a26a602acb1bbf849fa4bdac4bc657ee2f11c0c2a764a2cc87a5304e865c/mypy-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117", size = 15171457, upload-time = "2026-07-13T11:29:01.834Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/14/124f462bef69bcbc90b9358088460b6091954a3e004852fcd9948db617a5/mypy-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5", size = 15478281, upload-time = "2026-07-13T11:32:23.413Z" },
+ { url = "https://files.pythonhosted.org/packages/db/a4/8bdca6a8ac8d856d82ed049144af2721245a135c2e8001d3890c93975852/mypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494", size = 11148008, upload-time = "2026-07-13T11:34:17.332Z" },
+ { url = "https://files.pythonhosted.org/packages/83/41/490eea348e60ba50decec20bc750605444149a5d7a8cc560042f90ba2c75/mypy-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee", size = 10142329, upload-time = "2026-07-13T11:32:52.116Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" },
+ { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" },
+ { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" },
+ { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" },
+ { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" },
+ { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" },
+ { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" },
+ { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" },
+ { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" },
+ { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" },
+ { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" },
+ { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" },
+ { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" },
+ { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" },
+ { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" },
+ { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" },
+ { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" },
]
[[package]]
@@ -228,6 +379,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
+]
+
[[package]]
name = "platformdirs"
version = "4.5.1"
@@ -390,7 +550,7 @@ wheels = [
[[package]]
name = "pylint"
-version = "3.2.3"
+version = "4.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "astroid" },
@@ -402,9 +562,9 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "tomlkit" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9a/e9/60280b14cc1012794120345ce378504cf17409e38cd88f455dc24e0ad6b5/pylint-3.2.3.tar.gz", hash = "sha256:02f6c562b215582386068d52a30f520d84fdbcf2a95fc7e855b816060d048b60", size = 1506739, upload-time = "2024-06-06T14:19:17.955Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/1d/3bb57f303701549550d74bf7ced2b07412be97125c167a0c9d216aa9f762/pylint-4.0.6.tar.gz", hash = "sha256:52f19191bee08bf103f9705ad1a0ece4aa5a0a4ef2bdcbd969375a1e6f6579d5", size = 1585588, upload-time = "2026-06-14T14:43:26.772Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/50/d3/d346f779cbc9384d8b805a7557b5f2b8ee9f842bffebec9fc6364d6ae183/pylint-3.2.3-py3-none-any.whl", hash = "sha256:b3d7d2708a3e04b4679e02d99e72329a8b7ee8afb8d04110682278781f889fa8", size = 519244, upload-time = "2024-06-06T14:19:13.228Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/da/acb2e7d4dbd2dfb792d38c0d850481f29ad7049b356d23f56c687d35203b/pylint-4.0.6-py3-none-any.whl", hash = "sha256:d11a0e1fdb7b1cd46ec5d6fc78fee8b95f28695b2d6140e5809925f61e32ea54", size = 538389, upload-time = "2026-06-14T14:43:24.873Z" },
]
[[package]]
@@ -533,7 +693,7 @@ wheels = [
[[package]]
name = "youdotcom"
-version = "2.5.0"
+version = "2.6.0"
source = { editable = "." }
dependencies = [
{ name = "httpcore" },
@@ -559,9 +719,9 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
- { name = "mypy", specifier = "==1.15.0" },
- { name = "pylint", specifier = "==3.2.3" },
- { name = "pyright", specifier = "==1.1.398" },
- { name = "pytest", specifier = ">=8.0.0" },
- { name = "pytest-asyncio", specifier = ">=0.24.0" },
+ { name = "mypy", specifier = ">=2.3.0" },
+ { name = "pylint", specifier = ">=4.0.0" },
+ { name = "pyright", specifier = ">=1.1.398" },
+ { name = "pytest", specifier = ">=9.0.0" },
+ { name = "pytest-asyncio", specifier = ">=1.0.0" },
]