diff --git a/.agents/skills/generate-sdk-and-open-pr/SKILL.md b/.agents/skills/generate-sdk-and-open-pr/SKILL.md index 5911f7d..ca9fd14 100644 --- a/.agents/skills/generate-sdk-and-open-pr/SKILL.md +++ b/.agents/skills/generate-sdk-and-open-pr/SKILL.md @@ -4,7 +4,7 @@ description: Generate the Speakeasy SDK for a new version and open a release PR allowed-tools: Bash, Read, Write, Edit, Glob, Grep metadata: author: youdotcom-oss - version: "1.0.0" + version: "1.1.0" category: release keywords: release, version, publish, pypi --- @@ -13,49 +13,9 @@ metadata: Release a new version of the `youdotcom` Python SDK to PyPI and GitHub. -## Step 1: Verify OpenAPI specs +The SDK is generated from remote OpenAPI specs defined in `.speakeasy/workflow.yaml`. These are merged with the overlay at `overlays/python_overlay.yaml` and output to `.speakeasy/out.openapi.yaml`. No local spec overrides are supported — if specs need changes, update them at the source URLs before running this skill. -Speakeasy generates the SDK from OpenAPI specs defined in `.speakeasy/workflow.yaml`. The current source specs are: - -- `https://you.com/specs/openapi_unified_agents.yaml` -- `https://you.com/specs/openapi_search_v1.yaml` -- `https://you.com/specs/openapi_contents.yaml` -- `https://you.com/specs/openapi_base.yaml` - -These are merged with the overlay at `overlays/python_overlay.yaml` and output to `.speakeasy/out.openapi.yaml`. - -### 1a. Ask the user about spec sources - -Use `AskUserQuestion` to ask: - -``` -The SDK is generated from these OpenAPI specs: - -1. https://you.com/specs/openapi_unified_agents.yaml -2. https://you.com/specs/openapi_search_v1.yaml -3. https://you.com/specs/openapi_contents.yaml -4. https://you.com/specs/openapi_base.yaml - -Are the updates for this release already reflected in these specs, or do you have custom specs to use? -``` - -Options: -- **Use existing specs** (the remote URLs already have the changes) -- **Use custom specs** (user will provide spec content or file paths) - -### 1b. If custom specs - -If the user provides custom specs: - -1. Ask which spec(s) they want to replace and get the new content or file path -2. Update the `inputs` locations in `.speakeasy/workflow.yaml` to point to the custom spec files (e.g. change the remote URL to a local path) -3. **IMPORTANT**: Do NOT commit changes to `.speakeasy/workflow.yaml`. These are temporary overrides for generation only. Remind the user that these changes should be reverted or excluded from the release commit. - -If using existing specs, move on to step 2. - -## Step 2: Check current versions and fetch latest changes - -Before anything else, gather the current state of the world. +## Step 1: Check current versions and fetch latest changes ### 1a. Fetch all remote changes @@ -89,7 +49,7 @@ Present a summary to the user: If any versions are out of sync, warn the user before proceeding. -## Step 3: Confirm the next version with the user +## Step 2: Confirm the next version with the user Analyze the unreleased commits from step 1e to determine the appropriate semver bump: - **patch** (X.Y.Z+1): bug fixes, dependency updates, docs changes only @@ -114,27 +74,9 @@ Offer the suggested version as the recommended option, plus the other two semver Do NOT proceed until the user confirms. -## Step 4: Generate the SDK and open a release PR +## Step 3: Generate the SDK and open a release PR -### 4a. Confirm SDK generation - -Use `AskUserQuestion` to confirm: - -``` -Ready to run Speakeasy SDK generation for version X.Y.Z. This will regenerate the SDK source code from the OpenAPI specs. - -Proceed with generation? -``` - -Options: -- **Yes, generate** (recommended) -- **No, cancel** - -Do NOT proceed if the user cancels. - -### 4b. Bump version via Speakeasy - -Use `speakeasy bump` to set the version in `.speakeasy/gen.yaml`. This is the canonical way to update the Speakeasy target version. +### 3a. Bump version via Speakeasy ```bash speakeasy bump -v X.Y.Z -t you @@ -142,14 +84,14 @@ speakeasy bump -v X.Y.Z -t you This updates `python.version` in `.speakeasy/gen.yaml` to the confirmed version. -### 4c. Run Speakeasy generation +### 3b. Run Speakeasy generation ```bash speakeasy run ``` This will: -- Fetch the OpenAPI specs (remote URLs or local overrides from step 1) +- Fetch the OpenAPI specs from the remote URLs in `.speakeasy/workflow.yaml` - Apply the overlay from `overlays/python_overlay.yaml` - Regenerate all SDK source files under `src/` - Regenerate `USAGE.md` and auto-generated sections in `README.md` (the `` blocks) @@ -157,27 +99,19 @@ This will: Wait for the command to complete and check for errors. If it fails, report the error to the user and stop. -### 4d. Revert temporary workflow changes - -If custom specs were used in step 1, revert `.speakeasy/workflow.yaml` back to the original remote URLs: - -```bash -git checkout -- .speakeasy/workflow.yaml -``` - -### 4e. Create a release branch +### 3c. Create a release branch ```bash git checkout -b release/X.Y.Z ``` -### 4f. Update version in all locations +### 3d. Update version in all locations Update the version string in these files (if not already updated by Speakeasy): - `pyproject.toml` — `version = "X.Y.Z"` - `src/youdotcom/_version.py` — `__version__: str = "X.Y.Z"` and the `__user_agent__` string -### 4g. Update markdown documentation +### 3e. Update markdown documentation #### CHANGELOG.md Add a new section at the top (below the header), following the existing Keep a Changelog format: @@ -219,15 +153,15 @@ After generation and doc updates, ensure the test suite is compatible with the n - **Integration tests** (`tests/test_live.py`): Run against the real You.com API. Require `YOU_API_KEY_AUTH` env var. - **Client tests** (`tests/test_client.py`): Test HTTP client setup helpers. -#### 4h-1. Update tests for new/changed APIs +#### 3f-1. Update tests for new/changed APIs -Review the generated diff from step 4c. If Speakeasy added, removed, or changed any models, endpoints, or parameters: +Review the generated diff from step 3b. If Speakeasy added, removed, or changed any models, endpoints, or parameters: 1. Update unit tests to reflect the new request/response shapes 2. Update integration tests (`test_live.py`) if endpoints or model imports changed 3. Add new test cases for any new endpoints or features -#### 4h-2. Run unit tests +#### 3f-2. Run unit tests ```bash pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_performance.py -v @@ -235,7 +169,7 @@ pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_performance.py -v If tests fail, fix the test code (or SDK issues if applicable) and re-run. -#### 4h-3. Run integration tests (if API key is available) +#### 3f-3. Run integration tests (if API key is available) ```bash pytest tests/test_live.py -v @@ -243,7 +177,7 @@ pytest tests/test_live.py -v If `YOU_API_KEY_AUTH` is not set, skip this step and note it in the PR description. -#### 4h-4. Validate tests line by line +#### 3f-4. Validate tests line by line After all tests pass, read through every changed test file line by line. Check for: - Incorrect model imports that no longer exist @@ -252,20 +186,16 @@ After all tests pass, read through every changed test file line by line. Check f - Dead test cases for removed endpoints - Inconsistencies between test expectations and the actual generated SDK code -If this review surfaces any changes needed, make the fixes and go back to step 4h-2. Repeat this loop until a full line-by-line review finds no additional changes needed. +If this review surfaces any changes needed, make the fixes and go back to step 3f-2. Repeat until a full line-by-line review finds no additional changes needed. -### 4i. Commit all changes - -Stage and commit all generated and manually updated files to the release branch: +### 3g. Commit all changes ```bash git add -A git commit -m "feat: Python SDK X.Y.Z" ``` -Do NOT commit `.speakeasy/workflow.yaml` if it still contains local spec overrides — it should have been reverted in step 4d. - -### 4j. Push and open a PR +### 3h. Push and open a PR ```bash git push -u origin release/X.Y.Z diff --git a/.github/workflows/generate-sdk.yml b/.github/workflows/generate-sdk.yml index a73d0cb..5bba0d4 100644 --- a/.github/workflows/generate-sdk.yml +++ b/.github/workflows/generate-sdk.yml @@ -7,161 +7,54 @@ on: description: 'Version to release (e.g. 2.4.0)' required: true type: string - issues: - types: [labeled] permissions: contents: write pull-requests: write - issues: write - id-token: write jobs: generate-sdk: - if: | - github.event_name == 'workflow_dispatch' || - (github.event_name == 'issues' && github.event.label.name == 'sdk-release') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Parse inputs - id: parse - env: - EVENT_NAME: ${{ github.event_name }} - DISPATCH_VERSION: ${{ inputs.version }} - ISSUE_BODY: ${{ github.event.issue.body }} - run: | - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - echo "version=$DISPATCH_VERSION" >> "$GITHUB_OUTPUT" - echo "use_custom_specs=false" >> "$GITHUB_OUTPUT" - echo "custom_unified_agents=" >> "$GITHUB_OUTPUT" - echo "custom_search_v1=" >> "$GITHUB_OUTPUT" - echo "custom_contents=" >> "$GITHUB_OUTPUT" - echo "custom_base=" >> "$GITHUB_OUTPUT" - else - # Parse version from issue body (### Version section) - version=$(echo "$ISSUE_BODY" | sed -n '/### Version/{n;/^$/d;p;}' | head -1 | xargs) - echo "version=$version" >> "$GITHUB_OUTPUT" - - # Parse which specs to override from the dropdown - overrides=$(echo "$ISSUE_BODY" | sed -n '/### Which specs to override/,/### /{/### Which specs to override/d;/### /d;p;}' | grep -v '^$' | head -1) - - # Parse each custom spec textarea - has_custom=false - - unified=$(echo "$ISSUE_BODY" | sed -n '/### Custom spec: unified agents/,/### /{/### Custom spec: unified agents/d;/### /d;p;}' | sed '/^$/d') - if [ -n "$unified" ] && [ "$unified" != "_No response_" ]; then - has_custom=true - # Write to file for multiline support - echo "$unified" > /tmp/custom_unified_agents.yaml - echo "custom_unified_agents=/tmp/custom_unified_agents.yaml" >> "$GITHUB_OUTPUT" - else - echo "custom_unified_agents=" >> "$GITHUB_OUTPUT" - fi - - search=$(echo "$ISSUE_BODY" | sed -n '/### Custom spec: search v1/,/### /{/### Custom spec: search v1/d;/### /d;p;}' | sed '/^$/d') - if [ -n "$search" ] && [ "$search" != "_No response_" ]; then - has_custom=true - echo "$search" > /tmp/custom_search_v1.yaml - echo "custom_search_v1=/tmp/custom_search_v1.yaml" >> "$GITHUB_OUTPUT" - else - echo "custom_search_v1=" >> "$GITHUB_OUTPUT" - fi - - contents=$(echo "$ISSUE_BODY" | sed -n '/### Custom spec: contents/,/### /{/### Custom spec: contents/d;/### /d;p;}' | sed '/^$/d') - if [ -n "$contents" ] && [ "$contents" != "_No response_" ]; then - has_custom=true - echo "$contents" > /tmp/custom_contents.yaml - echo "custom_contents=/tmp/custom_contents.yaml" >> "$GITHUB_OUTPUT" - else - echo "custom_contents=" >> "$GITHUB_OUTPUT" - fi - - base=$(echo "$ISSUE_BODY" | sed -n '/### Custom spec: base/,/### \|$/{/### Custom spec: base/d;/### /d;p;}' | sed '/^$/d') - if [ -n "$base" ] && [ "$base" != "_No response_" ]; then - has_custom=true - echo "$base" > /tmp/custom_base.yaml - echo "custom_base=/tmp/custom_base.yaml" >> "$GITHUB_OUTPUT" - else - echo "custom_base=" >> "$GITHUB_OUTPUT" - fi - - echo "use_custom_specs=$has_custom" >> "$GITHUB_OUTPUT" - fi - - uses: anthropics/claude-code-action@v1 - id: claude with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} allowed_tools: 'Bash,Read,Write,Edit,Glob,Grep' prompt: | - You are running a non-interactive SDK release workflow. All inputs have been provided — do NOT use AskUserQuestion at any point. + You are running a non-interactive SDK release workflow. Do NOT use AskUserQuestion at any point. ## Inputs - - **Version**: ${{ steps.parse.outputs.version }} - - **Use custom specs**: ${{ steps.parse.outputs.use_custom_specs }} - - **Custom spec file (unified agents)**: ${{ steps.parse.outputs.custom_unified_agents }} - - **Custom spec file (search v1)**: ${{ steps.parse.outputs.custom_search_v1 }} - - **Custom spec file (contents)**: ${{ steps.parse.outputs.custom_contents }} - - **Custom spec file (base)**: ${{ steps.parse.outputs.custom_base }} + - **Version**: ${{ inputs.version }} ## Instructions - Follow the generate-sdk-and-open-pr skill in `.agents/skills/generate-sdk-and-open-pr/SKILL.md`, but skip all `AskUserQuestion` steps — the inputs above replace those interactions. Specifically: - - ### Step 1: Handle custom specs - - If "Use custom specs" is `true`, check which custom spec file paths are non-empty. For each non-empty path, read the file and update `.speakeasy/workflow.yaml` to point to that local file instead of the remote URL. Map: - - `custom_unified_agents` → replaces `https://you.com/specs/openapi_unified_agents.yaml` - - `custom_search_v1` → replaces `https://you.com/specs/openapi_search_v1.yaml` - - `custom_contents` → replaces `https://you.com/specs/openapi_contents.yaml` - - `custom_base` → replaces `https://you.com/specs/openapi_base.yaml` - - Leave specs with empty paths unchanged (they keep the remote URL). + Follow the generate-sdk-and-open-pr skill in `.agents/skills/generate-sdk-and-open-pr/SKILL.md`, skipping all interactive steps. Specifically: - ### Step 2: Check current versions + ### Step 1: Check current versions - Run the version checks from the skill (git tags, gh release, PyPI, local files). Log the findings but do not ask for confirmation — proceed automatically. + Run the version checks (git tags, gh release, PyPI, local files). Log the findings but do not ask for confirmation — proceed automatically. - ### Step 3: Use the provided version + ### Step 2: Use the provided version - The version is `${{ steps.parse.outputs.version }}`. Do not suggest alternatives — use this version directly. + The version is `${{ inputs.version }}`. Use this version directly. - ### Step 4: Generate and release + ### Step 3: Generate and release - Follow steps 4b through 4j from the skill exactly: - - `speakeasy bump -v ${{ steps.parse.outputs.version }} -t you` + Follow steps 3a through 3i from the skill exactly: + - `speakeasy bump -v ${{ inputs.version }} -t you` - `speakeasy run` - - Revert `.speakeasy/workflow.yaml` if custom specs were used - - Create branch `release/${{ steps.parse.outputs.version }}` - - Update versions in pyproject.toml and _version.py + - Create branch `release/${{ inputs.version }}` + - Update versions in pyproject.toml and _version.py if not already updated by Speakeasy - Update CHANGELOG.md, MIGRATION.md (if major), verify USAGE.md and README.md - Update and run tests: `pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_performance.py -v` - Fix any test failures and re-run until clean - - Commit: `git add -A && git commit -m "feat: Python SDK ${{ steps.parse.outputs.version }}"` - - Push: `git push -u origin release/${{ steps.parse.outputs.version }}` - - Open PR: `gh pr create --title "Python SDK ${{ steps.parse.outputs.version }}" --base main` + - Commit: `git add -A && git commit -m "feat: Python SDK ${{ inputs.version }}"` + - Push: `git push -u origin release/${{ inputs.version }}` + - Open PR: `gh pr create --title "Python SDK ${{ inputs.version }}" --base main` Include a structured PR body with Summary, Changes (from changelog), and Checklist. - - - name: Comment on issue and close - if: github.event_name == 'issues' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - VERSION: ${{ steps.parse.outputs.version }} - run: | - # Find the PR that was just created - pr_url=$(gh pr list --head "release/$VERSION" --json url --jq '.[0].url' 2>/dev/null || echo "") - - if [ -n "$pr_url" ]; then - gh issue comment "$ISSUE_NUMBER" --body "SDK release workflow completed. PR opened: $pr_url" - else - gh issue comment "$ISSUE_NUMBER" --body "SDK release workflow completed for version $VERSION. Check the Actions tab for details." - fi - - gh issue close "$ISSUE_NUMBER" diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 409aa50..72a4b61 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,29 +1,29 @@ lockVersion: 2.0.0 id: 77dbe0f5-bd71-4808-8102-230d4ffc4dc7 management: - docChecksum: 7d5cd58eeb46bb65394f8d93701de5d3 + docChecksum: d7a73950aed8742ef19ac1c4bd39fbd7 docVersion: 1.0.0 - speakeasyVersion: 1.733.4 - generationVersion: 2.845.12 - releaseVersion: 2.3.0 - configChecksum: 8a3f331950b6f7e77a32f7cd1db0b5f8 + speakeasyVersion: 1.761.9 + generationVersion: 2.881.4 + releaseVersion: 2.3.1 + configChecksum: bd64a66a9a4a950c98b53da4a7104b0f published: true persistentEdits: - generation_id: 137cb5fb-bdb8-4499-aeff-a6321a1bdb1b - pristine_commit_hash: 6b6328c7225337c31677e43d97830359df07c179 - pristine_tree_hash: bf00b109fbef4edf704a638751b1cd9a7c8e666a + generation_id: 2085e8ef-c1ad-4cc8-87fc-5b8483af53eb + pristine_commit_hash: 8263475ff9ce5616b6f0c82b54f31974cfc937b5 + pristine_tree_hash: 3adcb1ea9d37dddbdb60cd211d5e42413dd19993 features: python: acceptHeaders: 3.0.0 additionalDependencies: 1.0.0 constsAndDefaults: 1.0.7 - core: 6.0.15 + core: 6.0.21 defaultEnabledRetries: 0.2.0 - enumUnions: 0.1.0 + enumUnions: 0.1.1 envVarSecurityUsage: 0.3.2 flatRequests: 1.0.1 flattening: 3.1.1 - globalSecurity: 3.0.5 + globalSecurity: 3.0.7 globalSecurityCallbacks: 1.0.0 globalSecurityFlattening: 1.0.0 globalServerURLs: 3.2.1 @@ -32,9 +32,9 @@ features: nameOverrides: 3.0.3 nullables: 1.0.2 responseFormat: 1.1.0 - retries: 3.0.4 + retries: 3.0.5 sdkHooks: 1.2.1 - serverEvents: 1.0.13 + serverEvents: 1.0.15 unions: 3.1.4 trackedFiles: .gitattributes: @@ -47,8 +47,8 @@ trackedFiles: pristine_git_object: 8d79f0abb72526f1fb34a4c03e5bba612c6ba2ae USAGE.md: id: 3aed33ce6e6f - last_write_checksum: sha1:9c6e80ea166a9a6d5973a69ce2788e6c65f264c6 - pristine_git_object: 9f08d215c86cda516bb2283683def17f64339774 + last_write_checksum: sha1:6f7707f52339c3631d1065d9259a8ae8e6e7177c + pristine_git_object: aaf1f8fed9f4bdf3d96270be8ebc85d0e51d74e1 docs/errors/agentruns400responseerror.md: id: 4a255bc34dbc last_write_checksum: sha1:52fd0795019408368d8d17c72c7dafe6267e7799 @@ -73,6 +73,14 @@ trackedFiles: id: c673d1fd50f2 last_write_checksum: sha1:c3aae77709d87098770406f004c19a27ef930e83 pristine_git_object: 486b384d58421877ad73fa963a812c8cc83b5f1c + docs/errors/forbiddenresponseerror.md: + id: 7decb227f487 + last_write_checksum: sha1:2704ca01ccd03ee1de3814613057dbac5d662203 + pristine_git_object: 5d49673dfa18a1a6fb21a5c4412d4daaf486e315 + docs/errors/internalservererrorresponse.md: + id: f9d2a251e010 + last_write_checksum: sha1:6699c675081cfa78d7b154a90d02271965b63548 + pristine_git_object: b99d7b0942e2efbe3e202f3ffc92a74f35cd580a docs/errors/researchforbiddenerror.md: id: 38294d725501 last_write_checksum: sha1:6a2a0e75dc6ba751562e90bbb85fda2dda20ff60 @@ -85,22 +93,18 @@ trackedFiles: id: 90348ea8ad14 last_write_checksum: sha1:8948e2a9d19578d31dc9556b3a498208f6eaea1e pristine_git_object: 9516b1732d6641bf0077ceee9c044ef662d456c8 - docs/errors/searchforbiddenerror.md: - id: fb63bcabfad1 - last_write_checksum: sha1:b7e3a2369be34808acaa576f9384b9daf32b9b2c - pristine_git_object: 3a7b4fc18f4f9344cee2250bf78d73a8ae267534 - docs/errors/searchinternalservererror.md: - id: 6aa0b8aa8b2c - last_write_checksum: sha1:3299b61b84ba075aea6830091acc52e96ff121fb - pristine_git_object: 28d41b1c3dc2096be2a879157857e208bed3db44 - docs/errors/searchunauthorizederror.md: - id: bd64fef413da - last_write_checksum: sha1:1d70ee061964342b0e48d2f9f726bbc49d492820 - pristine_git_object: f799351c432b366215f25cff8710c8b082ceb997 - docs/errors/unprocessableentityerror.md: - id: 49570373f8e1 - last_write_checksum: sha1:6995ccac169fa137740bbbf5136a8fd7ee016e15 - pristine_git_object: 5abff965877a345c70cc64d6138b7e84946622f7 + docs/errors/researchunprocessableentityerror.md: + id: 04be881a410c + last_write_checksum: sha1:5b8d4a7da29c0081ce83e2117b1964e369a68da0 + pristine_git_object: 603a7fdb346007af5fafb87bdfc8847bb0021a68 + docs/errors/unauthorizedresponseerror.md: + id: 500194829c27 + last_write_checksum: sha1:29d332afa85c2b7a4d2a8a955eb24010944433ca + pristine_git_object: fe026600dfac122697e638bb3613de731389f638 + docs/errors/unprocessableentityresponseerror.md: + id: cc0e37e6516e + last_write_checksum: sha1:783bebd4bf3453a0e67d63c16e6b9b1f0647631f + pristine_git_object: 5d4449977cb77c2f2265ca82a85f5b6013a22c56 docs/models/advancedagentrunsrequest.md: id: 1b4e175934b4 last_write_checksum: sha1:73a48e950bb5313ee31f36c8bafff77c22078861 @@ -139,8 +143,8 @@ trackedFiles: pristine_git_object: a19143ec48165b836977bc9621ab61c11a52c337 docs/models/contentsformats.md: id: afdc3b11d819 - last_write_checksum: sha1:a34d7a952e3e349c8a098f205e80e328b6dc9fb5 - pristine_git_object: 1944732d23cbe1f44e01d879d907031138e4ab50 + last_write_checksum: sha1:9276989fa981477f876146dc5b0c76482c1a41d4 + pristine_git_object: 8510c9af12a7d962caaf2decdd8cf313179ff018 docs/models/contentsmetadata.md: id: 9240f2df8fbf last_write_checksum: sha1:2e8998401af96080a89aa177135011e8f79be878 @@ -155,12 +159,12 @@ trackedFiles: pristine_git_object: 7e81c7aa44ebb89cd11fc8ed4e75ad0802496589 docs/models/contenttype.md: id: 78e9266f4216 - last_write_checksum: sha1:b07de74e6e51de014aac32016de80db12ff83ee3 - pristine_git_object: b043bcf6036d260ace3df072d2dc57d4fdcb4656 + last_write_checksum: sha1:f7f7b3435b9d2dc12977b49e6b79829deded7203 + pristine_git_object: a01aeb01c373850fa623eac1477f61fd0eadb4a8 docs/models/country.md: id: a9be7df1a5df - last_write_checksum: sha1:fe1e2a0d00c9e256751e759eb55ba5e722562be8 - pristine_git_object: a4fc0c4a5fe56d54ac00725850ea1e243b0d8f64 + last_write_checksum: sha1:bf2348c1ad242cd575be6fac84df7d152b75e9af + pristine_git_object: 1bee618806380b9c459faf7df54bd13e832794e7 docs/models/customagentrunsrequest.md: id: 985d655f63bc last_write_checksum: sha1:35757db39efe4a2d68133956ccc14f54a918c561 @@ -179,8 +183,12 @@ trackedFiles: pristine_git_object: 271ad1a06da7cc2c5a02c2dcf14091aceaba5fe6 docs/models/freshness.md: id: 88acdb1a4cde - last_write_checksum: sha1:5a20b012ce095064aaedfc09db5dbdf170ffc27c - pristine_git_object: e3b507d2b9e37f660ba541d2d4967ae2db681434 + last_write_checksum: sha1:f55d65e3cd1f3a40eb8f43e64390112bec8674dd + pristine_git_object: 38377ef33d46910d8f1cd3d2d759d5b833bc37cb + docs/models/freshnessvalue.md: + id: a0ab09970422 + last_write_checksum: sha1:ff17988d9c2ce0ca2217247d227b7f6d1d454c7c + pristine_git_object: 4c5c0585ee55b19f51feea0f726761b3303fc694 docs/models/input1.md: id: 13ac5c7b8e41 last_write_checksum: sha1:cd17fe02727f29475456bd76bfd5dd2ecf61612d @@ -191,44 +199,40 @@ trackedFiles: pristine_git_object: fefc4e562d59c1e90c2c3886d6c1735be5d88a9d docs/models/language.md: id: 5bac2bb42c7c - last_write_checksum: sha1:06df16a4f694c3844bfcd18e3f44e23b3f49efc9 - pristine_git_object: 862060bbb008c512f27d50fc792a74ae49d47de9 + last_write_checksum: sha1:aa6bebe21084deb3021b004d5fb70fc6866d009b + pristine_git_object: a401ff5cc009595e27881c4619e2e806d126c44f docs/models/livecrawl.md: id: 8f56dc6b7dc1 - last_write_checksum: sha1:0fa766715e42797a2b53a6c3f905d171c007c8e4 - pristine_git_object: ff4239adeea3c3273d4d2b77ee98be032782e79c + last_write_checksum: sha1:262a55ae586cbf797d208ab335f3d5505a4c0f89 + pristine_git_object: c62c4ee26a56f1183589c591d0dbcbdacd08161d docs/models/livecrawlformats.md: id: e1f2ecc26149 - last_write_checksum: sha1:d5f25c6bd17417afa054143b342d82099cccb005 - pristine_git_object: dcfb11252321864cc52c4761d626f702c5ef88d8 + last_write_checksum: sha1:e4da6638be2cab806a029d2fd186a5dd2b5a4c48 + pristine_git_object: c2d0a6b5c3c6ba084aaf0fd105f4618483bb4c25 docs/models/loc.md: id: b071d5a509cc last_write_checksum: sha1:09a04749333ab50ae806c3ac6adcaa90d54df0f1 pristine_git_object: d6094ac2c6e0326c039dad2f6b89158694ef6aa7 - docs/models/metadata.md: - id: ad63dba5a4f8 - last_write_checksum: sha1:aea47d78e0d9e08c5a4709833dc6426334c82503 - pristine_git_object: 66efc64ae136f21939a01251b2bf89ad216e3bfb - docs/models/news.md: - id: c070462247dc - last_write_checksum: sha1:f5aea8d54b1d4f21cd98d934ae913ecdecd35a6b - pristine_git_object: fec0835c5d443cb3675a49ff221152b17b6bb027 + docs/models/newsresult.md: + id: 6faa8e42dd7f + last_write_checksum: sha1:bd70dd78f300f80a0ef8f64623657685a630e14a + pristine_git_object: 0aad594860f49247b39b19cc210080776c39ca45 docs/models/output.md: id: 376633b966cd last_write_checksum: sha1:45d7d4baf3d5bd47998470a3261d5d842216a895 pristine_git_object: d7c2771a56efeb0f63d0c388c621748c5ac93572 docs/models/reportverbosity.md: id: d06de274a3c5 - last_write_checksum: sha1:a9b998858b5efb701cfee75690c1a0e160ffdce1 - pristine_git_object: 1a2aae5cdb01b31eb1590b76e52f08b9ab6d19f0 + last_write_checksum: sha1:9b7dce8c7da103c20041cc87e52de081401d245f + pristine_git_object: 7c3d17fc8487c0ebe15ea35e754895b2a8531f00 docs/models/researchdetail.md: id: ac4ed946dccb last_write_checksum: sha1:f43174f11eb01588ded19f3e06c072a3589c4df9 pristine_git_object: e635556b70c8aff5211a13aab43c6aa27fad4b4e docs/models/researcheffort.md: id: 5b67676468b4 - last_write_checksum: sha1:8e3dc42805efa230a574cce116659bff6a550061 - pristine_git_object: e54e2072a76ab8bad9edc76563323a501a761204 + last_write_checksum: sha1:b5b71faf30e50d38cc54892aa94485ca8b434c42 + pristine_git_object: 27ca90da768f977245c5b117a6c9f4029ba73998 docs/models/researchinput.md: id: a5099da2ac37 last_write_checksum: sha1:a2212309beac7493be856f4dcff2c08af5fd3c9e @@ -299,48 +303,36 @@ trackedFiles: pristine_git_object: 5d468de3e2eddb033379b1d482fd8dd094bdb491 docs/models/results.md: id: c3a733d7c7d3 - last_write_checksum: sha1:cbc367f031a3d43e64e94d3ae44ee34abe109aec - pristine_git_object: c178f25b346e5be3f1f4811833c328077b7e1a38 + last_write_checksum: sha1:c66d85da84634b1a4cab5dcb60101f86a5ce90b9 + pristine_git_object: 342f0ea7f84a9f91517333400a3bd5b3a6df6593 docs/models/role.md: id: b694540a5b1e - last_write_checksum: sha1:74be0fbc7ed9966430da371eeadcdc51a4682568 - pristine_git_object: 3313880be54fd8c5d8761a4d8c8fb7c0e5b547c9 + last_write_checksum: sha1:d92cc3fcf9deaa2505304aa0b28a361993ba7df2 + pristine_git_object: 1f861cc27d1fd4b1b9a4020eb6dadcd0c53d2c57 docs/models/safesearch.md: id: 03e75d7f2e0d - last_write_checksum: sha1:6c928a770884f09c5ed23dc5932eadde3de93f0a - pristine_git_object: 2eae368bba6cff41ac768346fa6e665763443e8a - docs/models/searchcountry.md: - id: 4975534e7011 - last_write_checksum: sha1:99f418edaa8659ea654b30dfaf1d27da3a1e5853 - pristine_git_object: 9a95aec4988b5988130ec13393e9df3a3dd09330 + last_write_checksum: sha1:2c1d5ff1fa333a417ca8c1224163b53eb44b9a28 + pristine_git_object: 9efddcb711df36aa570dc30cbf40492b2b8f747f docs/models/searcheffort.md: id: d518e1b065c1 - last_write_checksum: sha1:daf57c9492c74819e1d4d51a62493000d7601ced - pristine_git_object: 95bb5b7394fb231b3b9cef813050863140ea65b9 - docs/models/searchfreshness.md: - id: 11d81669a994 - last_write_checksum: sha1:fa45060666c3fa42d6b65c2d7ec27b5cb0c9b483 - pristine_git_object: e9478183db391bc49b2f3a0a9a877e358cb4899d - docs/models/searchlivecrawl.md: - id: "953023757e55" - last_write_checksum: sha1:ffbcb9b63322706b9bc9fe1bae22fc6d83efc405 - pristine_git_object: ccf920074bc843e17549a6075b487b54b959e52a - docs/models/searchlivecrawlformats.md: - id: 3cc9911fcc52 - last_write_checksum: sha1:86e52959e8423e180e49d799a9eafa0cca09f1ec - pristine_git_object: 690de1f50a63ba69c5a73a5a2f7792b94b968436 + last_write_checksum: sha1:2f8259825f919cd9e5f4522d660f22547cd1c132 + pristine_git_object: 6f1c1ed8866231412ba7b38262ef17d92967ed09 + docs/models/searchmetadata.md: + id: 7fab78f275a5 + last_write_checksum: sha1:54940e0966c1feadafd666525700f988985b4e3c + pristine_git_object: 01105636a662e21e977fbaf3f04e189a0aafae5f docs/models/searchrequest.md: id: bc36c5e5aee1 - last_write_checksum: sha1:da9699efe63a7983e7265387fd12334508881ef3 - pristine_git_object: 1adb45e0d501c83f4ecdbb7b84879eefb20a3d52 + last_write_checksum: sha1:071949ae7aa49b0a23719c7f8c4b062466015f37 + pristine_git_object: 77b946f0bd9fb44bd1c22304a28d4da09e601ed5 + docs/models/searchrequestbody.md: + id: d4e2dd80df2d + last_write_checksum: sha1:7d1c7af22fdabfca7e57171ca961571541239a59 + pristine_git_object: 08398e0291fb773f6f4874a8427f37684cb1ef4c docs/models/searchresponse.md: id: d5606b4d403f - last_write_checksum: sha1:27a70c88fc494aa9dfda622d16da17d1a5c06996 - pristine_git_object: 5a85ee387b6c5acf7a1a032f16c0217f0b5eaffe - docs/models/searchsafesearch.md: - id: eb4c497e183a - last_write_checksum: sha1:ab9775fc51fb957ba3867ffbefc000e230c45d2c - pristine_git_object: fdad51ae07ea1ea8316d0a5d74d178751a5cc34a + last_write_checksum: sha1:bf8f309cc2f4d9a1236e1cc8afbfc3900861d2a3 + pristine_git_object: 0a0dcb77858a45a169e986cb0cd2b870db63acd7 docs/models/security.md: id: 452e4d4eb67a last_write_checksum: sha1:71fdd7bbeb4eccce70c9b87e9631d708571e0f17 @@ -355,20 +347,20 @@ trackedFiles: pristine_git_object: bb6d45112ef7ab37ea8cfab1f85eb32e9954679d docs/models/type.md: id: 98c32f09b2c8 - last_write_checksum: sha1:b600867b718b88b2f1e2cfe3c0f21b96d76810fe - pristine_git_object: db75a343248f6ae78c434dd3d6f08a7a5f6d5374 + last_write_checksum: sha1:d65a0a63069409574549340807ed0bbc2fc0be91 + pristine_git_object: 31b2152c9a0fe6cc1acfb95300c42c693e004a0f docs/models/utils/retryconfig.md: id: 4343ac43161c last_write_checksum: sha1:562c0f21e308ad10c27f85f75704c15592c6929d pristine_git_object: 69dd549ec7f5f885101d08dd502e25748183aebf docs/models/verbosity.md: id: a29d18f70f3d - last_write_checksum: sha1:9e4cb70588e5f8f031ba87a597cce3990bf352a5 - pristine_git_object: 3c30d679f2f512001915219f9b55774095c37ba6 - docs/models/web.md: - id: 79d8feb1f106 - last_write_checksum: sha1:d078cecf81002cee78b56ca4ebcbdfc301b80473 - pristine_git_object: b66b06507308267f48b10a0d012494f59d2d272d + last_write_checksum: sha1:bf8d880a60a36b0230d73c4883f8c6c945a5b5a8 + pristine_git_object: 35bfe62e1f58576bb086eb4566cca60afad89ffc + docs/models/webresult.md: + id: 8a203dd46ef9 + last_write_checksum: sha1:04c21830cc109bbce380a3193577a73b966c8212 + pristine_git_object: efac9dfaa8c5a97905db8f136a7ea043e3db865a docs/models/websearchtool.md: id: fc4df52fb9b5 last_write_checksum: sha1:3dd3b260252f363f9c6ae617fd38288bd007fad1 @@ -383,16 +375,16 @@ trackedFiles: pristine_git_object: e4ce470a54ad136244116e5d218ef62f54402da9 docs/sdks/runs/README.md: id: 4598fd39b715 - last_write_checksum: sha1:a4fa67e2225b766de164057368ea6fea5e9035ed - pristine_git_object: c8d570f44294f1c6d2905701506867ef1ff4576b + last_write_checksum: sha1:42773cd1f5a922aad84df2549cdfd52a435460f4 + pristine_git_object: a653b912b254879a88ad52fcde0f68e9f077288e docs/sdks/search/README.md: id: 5c534716244c - last_write_checksum: sha1:2a9b67f8171c4ff82f2e46d8fef716e645fedfe8 - pristine_git_object: 72bfdc70ed5ced95fbf35318614a49976a823af1 + last_write_checksum: sha1:ed2fde07486635ceed208aec8c8b3fc92e323450 + pristine_git_object: 57fd078ac971441649057e9adf714f9d89c6fff3 docs/sdks/you/README.md: id: 1abb954b0afb - last_write_checksum: sha1:4f5546910a5dfcda054541096cc98b945d41cab6 - pristine_git_object: 6be14705d30d36358fe5e422c8d84c45171b1467 + last_write_checksum: sha1:bcc7e65a31c64d88055c181676ed8df84fd2b348 + pristine_git_object: dafde5b080d9824ff1c68b8a5c432bb380550a75 py.typed: id: 258c3ed47ae4 last_write_checksum: sha1:8efc425ffe830805ffcc0f3055871bdcdc542c60 @@ -403,8 +395,8 @@ trackedFiles: pristine_git_object: f456032107a9387ba6c98afd1c981df2f4b3d636 pyproject.toml: id: 5d07e7d72637 - last_write_checksum: sha1:52fa68a283dba10ff1069fed74cf81ab03fc1468 - pristine_git_object: 290ebddeeb9306329af4f48500a7fc9a708eff70 + last_write_checksum: sha1:d387308c7c94594e36ebf7ecfd1d39b3cb780c70 + pristine_git_object: 2135dab3528e17e8c5705380865cccc3e6755725 scripts/publish.sh: id: fe273b08f514 last_write_checksum: sha1:adc9b741c12ad1591ab4870eabe20f0d0a86cd1a @@ -427,24 +419,24 @@ trackedFiles: pristine_git_object: 13987cea395263dc4b60a5ee4cb9d8c65f95fc9f src/youdotcom/_version.py: id: 5224f82ecc7b - last_write_checksum: sha1:01235a099c799feb9e36352e6c10fcfd6371552b - pristine_git_object: c998bfdeb29e4fee6ea9edb89caaec14cac7c449 + last_write_checksum: sha1:3539c9b0f3851a033e4c28ba9e87427736222f24 + pristine_git_object: 73d9694463a8d3587a6874b4a33797dfc3a98161 src/youdotcom/agents.py: id: 0ec0f4c4e0d0 last_write_checksum: sha1:4b58c15455f5410f050cfc8be831bfb6401d68ad pristine_git_object: 9090364d995f43bed128ecf046863c697691fd83 src/youdotcom/basesdk.py: id: c1c9ef882178 - last_write_checksum: sha1:21f701e27915aa678685dead53bb65f8b9c1f25b - pristine_git_object: ea145dd9a33a92ca4d64ca9ee88d499dcf8adc78 + last_write_checksum: sha1:403b2306c57c2fb84c5ee7048eaf168d573bdae4 + pristine_git_object: 8665d7f872dae803866e57bda2cc9288fb8d5fe0 src/youdotcom/contents_sdk.py: id: 0684c08251f6 - last_write_checksum: sha1:df4747161f79c38ccdcfbdf858f361e8d611ca11 - pristine_git_object: a21a07c01058262259ce70c61c5cc04d708e2086 + last_write_checksum: sha1:dc6a5d87faa3612dd0c0f707a469ee417932dbee + pristine_git_object: 04b01baad7ece480804f75e8a8eb129d43169c37 src/youdotcom/errors/__init__.py: id: e7ee44aa2c0f - last_write_checksum: sha1:3cda7690491c92a99b17243d42ffdc41cdb64abd - pristine_git_object: 2193daa39144685fdad94368aae2789a134d11d3 + last_write_checksum: sha1:c0e87d2c403fb84a281e048a37aac82a931773c0 + pristine_git_object: ec2447172a823afa444f86ebbd0c015e28e64b9f src/youdotcom/errors/agentruns400response_error.py: id: 6e04ce5f87f1 last_write_checksum: sha1:3d8694955e3799f0c762f605074d403b8b2203ab @@ -461,22 +453,34 @@ trackedFiles: id: 92163cd72b73 last_write_checksum: sha1:0589479e94a35d68deac9064f098ce0569b3e36d pristine_git_object: 443fd304ff07e7623911e094281ab2f1661843f1 + src/youdotcom/errors/forbidden_response_error.py: + id: 777fa6546b44 + last_write_checksum: sha1:afe4203758b823712ebd8c8be02fdd7649e94c64 + pristine_git_object: 575dded2c86c4c8fd55107c2dda8a09a92de0908 + src/youdotcom/errors/internalservererror_response.py: + id: 7b3a4b21c280 + last_write_checksum: sha1:a82788befcb10990f3271d8b9b461956990cd78d + pristine_git_object: 4a8c1a8ff1c1b6906af38e19b492a98636ed46bb src/youdotcom/errors/no_response_error.py: id: 9f953dc697cf last_write_checksum: sha1:7f326424a7d5ae1bcd5c89a0d6b3dbda9138942f pristine_git_object: 1deab64bc43e1e65bf3c412d326a4032ce342366 src/youdotcom/errors/researchop.py: id: 8143ca635d3f - last_write_checksum: sha1:66e92b01868dda50157ced7254e072293e868283 - pristine_git_object: b1a5836fc9ac80feae48c5bc937f5ebb61dc803c + last_write_checksum: sha1:696a48d30e1e40cd8fc2ad162524107f0ab1dc2c + pristine_git_object: a64bebbb06286b54a281d95d5573ccf6c27dfb91 src/youdotcom/errors/responsevalidationerror.py: id: 0ad5034298b3 last_write_checksum: sha1:f95060059297e22c183f9e387df44eb03aac1f5c pristine_git_object: 8e3bb217198ec204c2c92aa0c3f1aa92ce1ec5c3 - src/youdotcom/errors/searchop.py: - id: d7ef659447d5 - last_write_checksum: sha1:84a1f680cc4df4ccb54b5c7aaa7c68c02a431d67 - pristine_git_object: 9bb37947302e02e38676bbef8f5f12b4f831c3df + src/youdotcom/errors/unauthorized_response_error.py: + id: 7f23fe11fee3 + last_write_checksum: sha1:8fffa1bbbae4188098c085f1fd8e2374abb47a78 + pristine_git_object: dbc2f9f28572fbbb2dbbaad3c9cd2c387bd969a4 + src/youdotcom/errors/unprocessableentity_response_error.py: + id: d4a6fd9c273f + last_write_checksum: sha1:5a25ee76ff251002f745a4fd23eb0735e11903dd + pristine_git_object: f9153650c749efc975429515a53eb321b2e42225 src/youdotcom/errors/youdefaulterror.py: id: 4a5d0619a409 last_write_checksum: sha1:1b4ccd64f7c7845f589d1a43735da0a0ea186459 @@ -491,92 +495,100 @@ trackedFiles: pristine_git_object: 89560b566073785535643e694c112bedbd3db13d src/youdotcom/models/__init__.py: id: ad350e4fd8c1 - last_write_checksum: sha1:b7ac540f79ae19ad6bc96101a99b3d2df3d29325 - pristine_git_object: 804e5c280c7b35c49fcb7ea9db759c0b5444274d + last_write_checksum: sha1:49889bcb2a7a02dfe75b9f8bfdabae1c2b56d894 + pristine_git_object: 3cf387e9aefe1bbce3669f40f62cb297e4d6ad0f src/youdotcom/models/advancedagentrunsrequest.py: id: 6bb8d5dd67d4 - last_write_checksum: sha1:d1207266cea794cac55224e3bc6aed2f66422949 - pristine_git_object: f271b2bff38f6c55a2a6e3f890ef04bb6b1405c9 + last_write_checksum: sha1:ac336ef380378a65f90a9b70dba67b2e2db26401 + pristine_git_object: 8511c0aebc0698e8aad0bee3b9766271eb65f447 src/youdotcom/models/agentruns422response_error.py: id: 6731cdd29afd last_write_checksum: sha1:e9c70fe90257d4f3a93ea7d730e4d7f662dbd7bb pristine_git_object: 41240562660e2a4dd1ee4823ae31e843ce51c056 src/youdotcom/models/agentrunsbatchresponse.py: id: 38fe9f202ccb - last_write_checksum: sha1:0717ce0715c0cd84de6f21e9822c8029197bcf67 - pristine_git_object: 187616cbe44da1ecaeffe76bb0fc55985f1e242d + last_write_checksum: sha1:17249052ef4d14c046975e83c70f197b9f38447c + pristine_git_object: 1a211037a4bada9376fe2610affc7ebc51214752 src/youdotcom/models/agentrunsresponseoutput.py: id: 6ac5478f43e0 - last_write_checksum: sha1:8ebe3e761e97a1fcb81a3e180dd9643f705e7367 - pristine_git_object: 14726f52263a8f553a12ee6dd24f5451a65adf62 + last_write_checksum: sha1:bb2f18b91a61766c5c050214e98999e8cc0f111d + pristine_git_object: 1eaa631665b302a4aea097bf859bc07727e2c335 src/youdotcom/models/agentrunsresponsewebsearchresult.py: id: e85bdd982508 - last_write_checksum: sha1:03d003761e73fea392805382b36545792acac7dd - pristine_git_object: a0a105cab1cf1c161370a4579b7ef93cda0ed4c0 + last_write_checksum: sha1:dfe451543399d856efea36949e16d0cebdef9299 + pristine_git_object: 7f9d3c3ae17aecd76f7f5c4f408979ed8f5b2feb src/youdotcom/models/agentrunsstreamingresponse.py: id: 1423c3f03bf9 last_write_checksum: sha1:0fff2d6e0785c7744a50455853c309f7ca609fd5 pristine_git_object: 1a2c119e6ecee3f1116cc98968a9cc86cdde503e src/youdotcom/models/agentsrunsop.py: id: b0d55d74eb29 - last_write_checksum: sha1:3b2198586c6819837835eb8a6a2b5e90722f0136 - pristine_git_object: 2ac90c01209da9babf694e453a9bdd7ba8f7f517 + last_write_checksum: sha1:7b2b7d6c14bb77b25efc2ddc29e9336d04aed473 + pristine_git_object: 52a57d5de789822fbeb65ad45b7273c6402ea1d2 src/youdotcom/models/computetool.py: id: 5353a2de6f97 last_write_checksum: sha1:4c65a3868a85ac0ca4a08e8ab10f1dc34bf7f364 pristine_git_object: 12538817aaf1efe50c87ee29995f423b1c89567c src/youdotcom/models/contents.py: id: c1d5af212a4c - last_write_checksum: sha1:c3b6a119c617166269004dd2967c5121c36ad98d - pristine_git_object: ecf165e3b60ee5779f96276b3ce6ae9a59a0c381 + last_write_checksum: sha1:b7a585d24a3635704779bafc6141f811268cd499 + pristine_git_object: 37d69b8fd439a008b0a9459819fc35135b03f572 src/youdotcom/models/contentsformats.py: id: 0d5f457da03c last_write_checksum: sha1:c226d046ba051044bde0ba070143fcc2cabe5ed8 pristine_git_object: 91f16e0e9d599e1a9c4b819ac25ac07a8246cb4c src/youdotcom/models/contentsmetadata.py: id: 4e49905aae0b - last_write_checksum: sha1:065212c12a231f1f080a194d6b31375a5fb2ff88 - pristine_git_object: 6324e63c7f280e72fd1c0d1711b78b3c37be448b + last_write_checksum: sha1:5dc844d2c2bf653caff438b7f4ee1eadf37e0ced + pristine_git_object: b6b8e50f233dedf42297e1f52cce6b2430ffb833 src/youdotcom/models/contentsop.py: id: ca42ef875c5f - last_write_checksum: sha1:281db131fb9b8e4feef7f8c31cea58fc99aa89e0 - pristine_git_object: fc47c7fd77d691d12d06e8cd6a2f9d8ddca8a7a3 + last_write_checksum: sha1:4ec4915c81b62da286e8f74a2acd1e57cf89e978 + pristine_git_object: 76d7efbbef77353710e16729cb686e63c5ba6196 src/youdotcom/models/country.py: id: 725d2a57cc07 last_write_checksum: sha1:7827b3e65afd2ee86078e45ac373cedfe5d68766 pristine_git_object: 720e60691d5f653cae80488c00e54063953596e8 src/youdotcom/models/customagentrunsrequest.py: id: 089bb3a5b607 - last_write_checksum: sha1:d29b92460db8c7195464d851d436d18c47334357 - pristine_git_object: 759f45bd1053d6b4f52400434edadb027661958b + last_write_checksum: sha1:745881c56a61fc877f3a5f28b6a69faf5663a945 + pristine_git_object: 181e560c7a144fe28f84bbcc3d35d8ba67d5ce0e src/youdotcom/models/expressagentrunsrequest.py: id: 0f698f43a90d - last_write_checksum: sha1:182e79b7735aaa20c2e7203a787267cde8f2c2b7 - pristine_git_object: f4f5de0e9c96adf70854876f0eb06d319e464eba + last_write_checksum: sha1:44ac13388d585adceb28c1164d852ddb15550327 + pristine_git_object: 8211741b2403722bb0dff06a7b31b29918dd544d src/youdotcom/models/freshness.py: id: c7c960c20e5e - last_write_checksum: sha1:48ae86226706f17277fc58f132b55eef545888a4 - pristine_git_object: da5bd9fe7c0e3abd649a48b5114ce9845646d3bc + last_write_checksum: sha1:9ed62b1edeac55bd86aa69d3092a6864046487ff + pristine_git_object: 83281eb315fc1d2675302c34c15f81d408fd462f + src/youdotcom/models/freshnessvalue.py: + id: 53cb2e2fc925 + last_write_checksum: sha1:1408b2d4e83ed5cf93f15da95797b6c44ea00b2a + pristine_git_object: 66ff7788852bd6ff23f2d4941e2fffb436740b30 src/youdotcom/models/language.py: id: 4e51e1ee857d - last_write_checksum: sha1:ad95c07ec07475141310e8ae5b4ac02dc1fbd703 - pristine_git_object: 25de23324976b84667fff8470e76d190d0e0f98a + last_write_checksum: sha1:4becfe836c36fbcff9c0cd8d42f32676c351d641 + pristine_git_object: 171f3585bd0f7a55a16ce2d7a86a26e6c5b7f149 src/youdotcom/models/livecrawl.py: id: e5dd4948ff3f last_write_checksum: sha1:f2b60aa9d84b622f961f81f781574f958c247953 pristine_git_object: c28464e9c8784e91c7f1e8e2ea417220040eaf58 src/youdotcom/models/livecrawlformats.py: id: 775d02437b81 - last_write_checksum: sha1:9dcad24e8794aefcb929db54d21594ef9809cbb8 - pristine_git_object: 27c9da850bdb854fab602fa2f7df88aedea33b40 + last_write_checksum: sha1:09894aa858c80f89c322d61c378ee033f3e9aef1 + pristine_git_object: ceca02dde497a61488a00d1312ece701f6f7ce37 + src/youdotcom/models/newsresult.py: + id: daffe8db4b1b + last_write_checksum: sha1:d8a48605207bf6cbd4a143c3d5acd5bdf492f89e + pristine_git_object: 9a892957291c73bfde00e33cdfd3c19e4428035f src/youdotcom/models/reportverbosity.py: id: 5a8683f42b91 last_write_checksum: sha1:b7c084407a5584d770deb61970d4953825a3bd2c pristine_git_object: 14a8b432a575bc4c291723e98f2586beae4f5939 src/youdotcom/models/researchop.py: id: c1ae2c3f13d9 - last_write_checksum: sha1:fc7c95702b93a5e31edbda349b8acc0199ad084b - pristine_git_object: f8ec45ce17672dc92b004d989d6c677f89b06668 + last_write_checksum: sha1:2828ffec9c7ab5aefa4b03bc200e980b8e2bf8b6 + pristine_git_object: 647dadcc8dc95ee040eaf87541e8615bce16b278 src/youdotcom/models/researchtool.py: id: 4e0236b1b670 last_write_checksum: sha1:e90b83ae9dbf18da27bbbf2db81fbd18a16cb4d1 @@ -617,18 +629,38 @@ trackedFiles: id: 8864bba8ca75 last_write_checksum: sha1:8709eb40bec8635ce221ddfad0a3abc8ee3b400d pristine_git_object: cec092e4f820d3dfd8d09f8b7bc224c418b8fd49 + src/youdotcom/models/searchmetadata.py: + id: de041a4286e9 + last_write_checksum: sha1:1adb9cdd44100896aa387cd1f995cb86994dc34f + pristine_git_object: 758b0c2297d21b7c483fd164f3778dc9506e5718 src/youdotcom/models/searchop.py: id: 525c0a4e8872 - last_write_checksum: sha1:ea521b1b3cf6d34aeabe0268b571f4eec698bf23 - pristine_git_object: a06a0fdcef28814a51726bba187cbc82958d7f48 + last_write_checksum: sha1:b7305df34f0ab2cb773ce25070e6a0cb3be4898b + pristine_git_object: ad2f09435b6a60a8fa6a8626c15c3d51d0f620ce + src/youdotcom/models/searchpostop.py: + id: 57d9a686a4d2 + last_write_checksum: sha1:393291e98f851ad723e1b239a69c0a045c38fc67 + pristine_git_object: e0f8b5b537deccd0bd4213f783dd502cf340597d + src/youdotcom/models/searchrequestbody.py: + id: e36cf1876cdd + last_write_checksum: sha1:70279df1a7cafb2afd02e8740a8958a6d6b3b745 + pristine_git_object: 09d11ba51fb21e9aa4d5edcf82f34e255023b56e + src/youdotcom/models/searchresponse.py: + id: 777112b1d670 + last_write_checksum: sha1:8c597f06af6b2f1d47ae564d1a462b6dadf636ac + pristine_git_object: ee9313ea8ad7ca83082a1c7e2b726ef7fcef750f src/youdotcom/models/security.py: id: 3a94d17768c4 - last_write_checksum: sha1:8b1c11f19ca3684c0330a4884c0f45b165578a7b - pristine_git_object: 77789954602ab51b6083ecc41e63511cf49d9e5a + last_write_checksum: sha1:f55a74714c74d2f19f22b23fd8c798be81828804 + pristine_git_object: 2313b5207ca434956401f8f32265c71790396370 src/youdotcom/models/verbosity.py: id: 68768141a514 last_write_checksum: sha1:ba9d4ee35bed37f14d67ca27f38cb0efc44eb132 pristine_git_object: 666d75aaa5f5ab22a259d8fe1aa08f599c79e104 + src/youdotcom/models/webresult.py: + id: 4f4f8e119dbe + last_write_checksum: sha1:160bda43c198787655edcc6b031824488929b183 + pristine_git_object: d8f6f6c49923e82a46eb6d077d309f20d2eb0c96 src/youdotcom/models/websearchtool.py: id: 8240ffdc3807 last_write_checksum: sha1:44338571c786fbf9377a82ca0e0ff57a26472e66 @@ -639,20 +671,20 @@ trackedFiles: pristine_git_object: 3e38f1a929f7d6b1d6de74604aa87e3d8f010544 src/youdotcom/runs.py: id: 8011c1ffa5a1 - last_write_checksum: sha1:08b88bce4f60cfac1d6744f077e27175a20b1987 - pristine_git_object: 0fc591713191bcd659057fcdc05e2b07139b3457 + last_write_checksum: sha1:2b49a3396737855b70640f0f9e019edcb52f4419 + pristine_git_object: feb8fcdda975272dc28efb434bfe6090b81eafe3 src/youdotcom/sdk.py: id: 90954e74e7b7 - last_write_checksum: sha1:5e14bed0ce6e55976b40bacba13ccd526ec818fe - pristine_git_object: c72bef1f7ba827516449fd4e30e1378189a9542b + last_write_checksum: sha1:afcfc305b73620450b36c3e276066b2abc2005db + pristine_git_object: e0e82deb1d31f438e29c8f7dca6a031f156070dc src/youdotcom/sdkconfiguration.py: id: eb56427350d9 last_write_checksum: sha1:f62ffed1bbe732f18d96076c622fccf3eb8c2f6c pristine_git_object: ea49a89ef4840777202f1440fbf116e2f63d7a56 src/youdotcom/search.py: id: 5e475f9db47f - last_write_checksum: sha1:fa2b85d71f14cc589fbb190c2475f387aea8f5fa - pristine_git_object: 355ce7774b9722b1f02ab98634f9481831ace609 + last_write_checksum: sha1:540cd0cddeb9c0683c81bc37b5b5e883e4fc1f63 + pristine_git_object: 4a518bc39548209f97f6fda73d5e57ccfe20c828 src/youdotcom/types/__init__.py: id: 5e0774b59bbc last_write_checksum: sha1:140ebdd01a46f92ffc710c52c958c4eba3cf68ed @@ -683,8 +715,8 @@ trackedFiles: pristine_git_object: 3324e1bc2668c54c4d5f5a1a845675319757a828 src/youdotcom/utils/eventstreaming.py: id: 5f31d9da5a93 - last_write_checksum: sha1:ffa870a25a7e4e2015bfd7a467ccd3aa1de97f0e - pristine_git_object: f2052fc22d9fd6c663ba3dce019fe234ca37108b + last_write_checksum: sha1:620d78a8b4e3b854e08d136e02e40a01a786bd70 + pristine_git_object: 3bdcd6d3d4fc772cb7f5fca8685dcdc8c85e13e8 src/youdotcom/utils/forms.py: id: 1bf9b877c054 last_write_checksum: sha1:0ca31459b99f761fcc6d0557a0a38daac4ad50f4 @@ -699,8 +731,8 @@ trackedFiles: pristine_git_object: 6ae3abd220a08af699d685aa61dd4168df6dbcfa src/youdotcom/utils/metadata.py: id: f6d2fb72eae3 - last_write_checksum: sha1:c6a560bd0c63ab158582f34dadb69433ea73b3d4 - pristine_git_object: 173b3e5ce658675c2f504222a56b3daaaa68107d + last_write_checksum: sha1:e703e5cbb5255144aacf86898d1420529afaaff8 + pristine_git_object: 5abddd588837ac297050ca3b543627faadb350a9 src/youdotcom/utils/queryparams.py: id: 1340b8e3e103 last_write_checksum: sha1:b94c3f314fd3da0d1d215afc2731f48748e2aa59 @@ -715,12 +747,12 @@ trackedFiles: pristine_git_object: af07d4e941007af4213c5ec9047ef8a2fca04e5e src/youdotcom/utils/security.py: id: 41e3b3176b50 - last_write_checksum: sha1:8f41e203536f0ab841ea31498ca73556d5cb92b1 - pristine_git_object: e51915d1bd67d63cb62077f19b1b5cd13f747dd6 + last_write_checksum: sha1:802ca60459e7a12b60dba8c2060dcdedd52fd1ee + pristine_git_object: cd67559004f44eaeec27d09183a6b7ce2fced666 src/youdotcom/utils/serializers.py: id: 360f8e2583cc - last_write_checksum: sha1:ce1d8d7f500a9ccba0aeca5057cee9c271f4dfd7 - pristine_git_object: 14321eb479de81d0d9580ec8291e0ff91bf29e57 + last_write_checksum: sha1:61009f2e4ef6613a1a5af813fe020373dae5a492 + pristine_git_object: d2149f8b909cb96628db140ac3cddb1b1e981367 src/youdotcom/utils/unmarshal_json_response.py: id: ae5a7f2d9dc3 last_write_checksum: sha1:aae3840de6b5894dcf8f167fd83b6b77294041ef @@ -826,21 +858,31 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" + header: + X-API-Key: "" responses: "200": - application/json: {"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. What can I help you with?"], "thumbnail_url": "https://www.somethumbnailsite.com/thumbnail.jpg", "page_age": "2025-06-25T11:41:00", "authors": ["John Doe"], "favicon_url": "https://someurl.com/favicon"}], "news": [{"title": "Exclusive | You.com becomes the backbone of the EU's AI strategy", "description": "As the EU's AI strategy is being debated, You.com becomes the backbone of the EU's AI strategy.", "page_age": "2025-06-25T11:41:00", "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}} + application/json: {"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. What can I help you with?"], "thumbnail_url": "https://www.somethumbnailsite.com/thumbnail.jpg", "page_age": "2025-06-25T11:41:00", "authors": ["John Doe"], "favicon_url": "https://someurl.com/favicon"}], "news": [{"title": "Exclusive | You.com becomes the backbone of the EU's AI strategy", "description": "As the EU's AI strategy is being debated, You.com becomes the backbone of the EU's AI strategy.", "page_age": "2025-06-25T11:41:00", "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": "What are the latest geopolitical updates from India", "latency": 0.123}} "401": application/json: {} "403": application/json: {} "500": application/json: {} + "422": + application/json: {} missingApiKey: parameters: query: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "401": application/json: {"detail": "API key is required"} @@ -850,6 +892,9 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "401": application/json: {"detail": "Invalid or expired API key"} @@ -859,6 +904,9 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "401": application/json: {"detail": ""} @@ -868,6 +916,9 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "403": application/json: {"detail": "Missing required scopes"} @@ -877,6 +928,9 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "500": application/json: {"detail": "Internal authentication error"} @@ -886,9 +940,24 @@ examples: query: "Your query" language: "EN" count: 10 + crawl_timeout: 10 + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" responses: "500": application/json: {"detail": "Internal authorization error"} + invalidParams: + parameters: + query: + query: "What are the latest geopolitical updates from India" + count: 10 + language: "EN" + include_domains: "nytimes.com,bbc.com" + exclude_domains: "spam-site.com,other-site.com" + crawl_timeout: 10 + responses: + "422": + application/json: {"error": "invalid request parameter(s)"} contents: missingApiKey: requestBody: @@ -927,6 +996,9 @@ examples: "500": application/json: {"detail": "Internal authorization error"} speakeasy-default-contents: + parameters: + header: + X-API-Key: "" requestBody: application/json: {"urls": ["https://www.you.com"], "formats": ["html", "markdown"], "crawl_timeout": 10} responses: @@ -940,6 +1012,9 @@ examples: application/json: {} research: speakeasy-default-research: + parameters: + header: + X-API-Key: "" requestBody: application/json: {"input": "", "research_effort": "standard"} responses: @@ -1013,5 +1088,78 @@ examples: responses: "500": application/json: {"detail": "Internal authorization error"} + search-post: + speakeasy-default-search-post: + parameters: + header: + X-API-Key: "" + responses: + "200": + application/json: {} + "401": + application/json: {} + "403": + application/json: {} + "422": + application/json: {} + "500": + application/json: {} + searchPost: + missingApiKey: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "401": + application/json: {"detail": "API key is required"} + invalidOrExpired: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "401": + application/json: {"detail": "Invalid or expired API key"} + otherAuthParsing: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "401": + application/json: {"detail": ""} + missingScopes: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "403": + application/json: {"detail": "Missing required scopes"} + invalidParams: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "422": + application/json: {"error": "invalid request parameter(s)"} + authFailure: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "500": + application/json: {"detail": "Internal authentication error"} + authorizationFailure: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "500": + application/json: {"detail": "Internal authorization error"} + speakeasy-default-search-post: + requestBody: + application/json: {"query": "What are the latest geopolitical updates from India", "count": 10, "language": "EN", "include_domains": ["nytimes.com", "bbc.com"], "exclude_domains": ["spam-site.com", "other-site.com"], "crawl_timeout": 10} + responses: + "200": + application/json: {"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. What can I help you with?"], "thumbnail_url": "https://www.somethumbnailsite.com/thumbnail.jpg", "page_age": "2025-06-25T11:41:00", "authors": ["John Doe"], "favicon_url": "https://someurl.com/favicon"}], "news": [{"title": "Exclusive | You.com becomes the backbone of the EU's AI strategy", "description": "As the EU's AI strategy is being debated, You.com becomes the backbone of the EU's AI strategy.", "page_age": "2025-06-25T11:41:00", "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": "What are the latest geopolitical updates from India", "latency": 0.123}} + "401": + application/json: {} + "403": + application/json: {} + "422": + application/json: {} + "500": + application/json: {} examplesVersion: 1.0.2 generatedTests: {} diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index f2ffbc8..046b956 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -24,7 +24,7 @@ generation: schemas: allOfMergeStrategy: shallowMerge requestBodyFieldName: body - versioningStrategy: automatic + versioningStrategy: manual persistentEdits: {} tests: generateTests: true @@ -34,7 +34,7 @@ generation: examples: - usage.md python: - version: 2.3.0 + version: 2.3.1 additionalDependencies: dev: {} main: {} diff --git a/.speakeasy/out.openapi.yaml b/.speakeasy/out.openapi.yaml index 85b6237..086e97b 100644 --- a/.speakeasy/out.openapi.yaml +++ b/.speakeasy/out.openapi.yaml @@ -8,6 +8,7 @@ info: Multi-step reasoning with comprehensive research capabilities Comprehensive API for You.com services: - **Agents API**: Execute queries using Express, Advanced, and Custom AI agents + - **Research API**: In-depth, 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 version: 1.0.0 @@ -115,251 +116,85 @@ paths: application/json: schema: $ref: "#/components/schemas/AgentRuns422Response" + servers: + - url: https://api.you.com tags: - agents.runs x-speakeasy-name-override: create /v1/search: + post: + operationId: searchPost + summary: Returns a list of unified search results from web and news sources + description: |- + 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. + servers: + - url: https://ydc-index.io + security: + - ApiKeyAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SearchRequestBody' + required: true + responses: + "200": + $ref: '#/components/responses/SearchSuccess' + "401": + $ref: '#/components/responses/Unauthorized' + "403": + $ref: '#/components/responses/Forbidden' + "422": + $ref: '#/components/responses/UnprocessableEntity' + "500": + $ref: '#/components/responses/InternalServerError' get: operationId: search summary: Returns a list of unified search results from web and news sources - description: 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. + description: |- + 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. + servers: + - url: https://ydc-index.io security: - ApiKeyAuth: [] parameters: - - name: query - in: query - description: 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. - required: true - schema: - type: string - default: "Your query" - example: "Your query" - - name: count - in: query - description: Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). - required: false - schema: - type: integer - maximum: 100 - minimum: 1 - default: 10 - - name: freshness - in: query - description: |- - 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. - required: false - schema: - oneOf: - - $ref: '#/components/schemas/Freshness' - - type: string - - name: offset - in: query - description: 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`. - required: false - schema: - type: integer - - name: country - in: query - description: The country code that determines the geographical focus of the web results. - required: false - schema: - oneOf: - - $ref: '#/components/schemas/Country' - - type: string - - name: language - in: query - description: The language of the web results that will be returned (BCP 47 format). - required: false - schema: - $ref: '#/components/schemas/Language' - - name: safesearch - in: query - description: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. - required: false - schema: - oneOf: - - $ref: '#/components/schemas/SafeSearch' - - type: string - - name: livecrawl - in: query - description: Indicates which section(s) of search results to livecrawl and return full page content. - required: false - schema: - oneOf: - - $ref: '#/components/schemas/LiveCrawl' - - type: string - - name: livecrawl_formats - in: query - description: Indicates the format of the livecrawled content. - required: false - schema: - oneOf: - - $ref: '#/components/schemas/LiveCrawlFormats' - - type: string + - $ref: '#/components/parameters/Query' + - $ref: '#/components/parameters/Count' + - $ref: '#/components/parameters/FreshnessParam' + - $ref: '#/components/parameters/Offset' + - $ref: '#/components/parameters/CountryParam' + - $ref: '#/components/parameters/LanguageParam' + - $ref: '#/components/parameters/SafeSearchParam' + - $ref: '#/components/parameters/LiveCrawlParam' + - $ref: '#/components/parameters/LiveCrawlFormatsParam' + - $ref: '#/components/parameters/IncludeDomainsParam' + - $ref: '#/components/parameters/ExcludeDomainsParam' + - $ref: '#/components/parameters/CrawlTimeout' responses: "200": - description: A JSON object containing unified search results from web and news sources - content: - application/json: - schema: - type: object - properties: - results: - type: object - properties: - web: - type: array - items: - type: object - properties: - url: - type: string - description: The URL of the specific search result. - example: "https://you.com" - title: - type: string - description: The title or name of the search result. - example: "The World's Greatest Search Engine!" - description: - type: string - description: A brief description of the content of the search result. - example: "Search on YDC" - snippets: - type: array - items: - type: string - example: "I'm an AI assistant that helps you get more done. What can I help you with?" - description: An array of text snippets from the search result, providing a preview of the content. - thumbnail_url: - type: string - description: URL of the thumbnail. - example: "https://www.somethumbnailsite.com/thumbnail.jpg" - page_age: - type: string - format: date-time - description: The age of the search result. - example: "2025-06-25T11:41:00" - contents: - $ref: '#/components/schemas/Contents' - authors: - type: array - items: - type: string - example: "John Doe" - description: An array of authors of the search result. - favicon_url: - type: string - description: The URL of the favicon of the search result's domain. - example: "https://someurl.com/favicon" - news: - type: array - items: - type: object - properties: - title: - type: string - description: The title of the news result. - example: "Exclusive | You.com becomes the backbone of the EU's AI strategy" - description: - type: string - description: A brief description of the content of the news result. - example: "As the EU's AI strategy is being debated, You.com becomes the backbone of the EU's AI strategy." - page_age: - type: string - format: date-time - description: UTC timestamp of the article's publication date. - example: "2025-06-25T11:41:00" - thumbnail_url: - type: string - description: URL of the thumbnail. - example: "https://www.somethumbnailsite.com/thumbnail.jpg" - url: - type: string - description: The URL of the news result. - example: "https://www.you.com/news/eu-ai-strategy-youcom" - contents: - $ref: '#/components/schemas/Contents' - metadata: - type: object - properties: - search_uuid: - type: string - format: uuid - example: "942ccbdd-7705-4d9c-9d37-4ef386658e90" - query: - type: string - description: Returns the search query used to retrieve the results. - example: "Your query" - latency: - type: number - example: 0.123 + $ref: '#/components/responses/SearchSuccess' "401": - description: Unauthorized. Problems with API key. - content: - application/json: - schema: - type: object - properties: - detail: - type: string - description: Error detail message. - examples: - missingApiKey: - summary: Missing API key - value: - detail: "API key is required" - invalidOrExpired: - summary: Invalid/expired API key - value: - detail: "Invalid or expired API key" - otherAuthParsing: - summary: Other auth parsing errors - value: - detail: "" + $ref: '#/components/responses/Unauthorized' "403": - description: Forbidden. API key lacks scope for this path. - content: - application/json: - schema: - type: object - properties: - detail: - type: string - examples: - missingScopes: - summary: Missing required scopes - value: - detail: "Missing required scopes" + $ref: '#/components/responses/Forbidden' + "422": + $ref: '#/components/responses/UnprocessableEntity' "500": - description: Internal Server Error during authentication/authorization middleware. - content: - application/json: - schema: - type: object - properties: - detail: - type: string - examples: - authFailure: - summary: Authentication failure - value: - detail: "Internal authentication error" - authorizationFailure: - summary: Authorization failure - value: - detail: "Internal authorization error" + $ref: '#/components/responses/InternalServerError' tags: - search x-speakeasy-name-override: unified - servers: - - url: https://ydc-index.io /v1/contents: post: operationId: contents summary: Returns the content of the web pages description: Returns the HTML or Markdown of a target webpage. + security: + - ApiKeyAuth: [] requestBody: content: application/json: @@ -1257,6 +1092,16 @@ components: - loc - msg - input + SearchQuery: + type: string + description: 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. + example: What are the latest geopolitical updates from India + Count: + type: integer + maximum: 100 + minimum: 1 + description: Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). + default: 10 Freshness: type: string enum: @@ -1264,106 +1109,119 @@ components: - week - month - year - description: Specifies the freshness of the results to return. + FreshnessValue: + oneOf: + - $ref: '#/components/schemas/Freshness' + - type: string + description: |- + 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. + Offset: + type: integer + maximum: 9 + minimum: 0 + description: 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`. Country: type: string enum: - - "AR" - - "AU" - - "AT" - - "BE" - - "BR" - - "CA" - - "CL" - - "DK" - - "FI" - - "FR" - - "DE" - - "HK" - - "IN" - - "ID" - - "IT" - - "JP" - - "KR" - - "MY" - - "MX" - - "NL" - - "NZ" + - AR + - AU + - AT + - BE + - BR + - CA + - CL + - DK + - FI + - FR + - DE + - HK + - IN + - ID + - IT + - JP + - KR + - MY + - MX + - NL + - NZ - "NO" - - "CN" - - "PL" - - "PT" - - "PH" - - "RU" - - "SA" - - "ZA" - - "ES" - - "SE" - - "CH" - - "TW" - - "TR" - - "GB" - - "US" + - CN + - PL + - PT + - PH + - RU + - SA + - ZA + - ES + - SE + - CH + - TW + - TR + - GB + - US description: The country code that determines the geographical focus of the web results. Language: type: string enum: - - "AR" - - "EU" - - "BN" - - "BG" - - "CA" - - "ZH-HANS" - - "ZH-HANT" - - "HR" - - "CS" - - "DA" - - "NL" - - "EN" - - "EN-GB" - - "ET" - - "FI" - - "FR" - - "GL" - - "DE" - - "EL" - - "GU" - - "HE" - - "HI" - - "HU" - - "IS" - - "IT" - - "JP" - - "KN" - - "KO" - - "LV" - - "LT" - - "MS" - - "ML" - - "MR" - - "NB" - - "PL" - - "PT-BR" - - "PT-PT" - - "PA" - - "RO" - - "RU" - - "SR" - - "SK" - - "SL" - - "ES" - - "SV" - - "TA" - - "TE" - - "TH" - - "TR" - - "UK" - - "VI" - default: "EN" + - AR + - EU + - BN + - BG + - CA + - ZH-HANS + - ZH-HANT + - HR + - CS + - DA + - NL + - EN + - EN-GB + - ET + - FI + - FR + - GL + - DE + - EL + - GU + - HE + - HI + - HU + - IS + - IT + - JP + - KN + - KO + - LV + - LT + - MS + - ML + - MR + - NB + - PL + - PT-BR + - PT-PT + - PA + - RO + - RU + - SR + - SK + - SL + - ES + - SV + - TA + - TE + - TH + - TR + - UK + - VI + description: The language of the web results that will be returned (BCP 47 format). + default: EN SafeSearch: type: string enum: - - off + - "off" - moderate - strict description: Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. @@ -1375,11 +1233,172 @@ components: - all description: Indicates which section(s) of search results to livecrawl and return full page content. LiveCrawlFormats: - type: string - enum: - - html - - markdown - description: Indicates the format of the livecrawled content. + type: array + items: + type: string + enum: + - html + - markdown + description: '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"]`.' + IncludeDomains: + type: array + items: + type: string + description: |- + 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. + example: + - nytimes.com + - bbc.com + ExcludeDomains: + type: array + items: + type: string + description: |- + 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. + example: + - spam-site.com + - other-site.com + CrawlTimeout: + type: integer + maximum: 60 + minimum: 1 + description: Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds. + default: 10 + example: 10 + SearchRequestBody: + type: object + properties: + query: + $ref: '#/components/schemas/SearchQuery' + count: + $ref: '#/components/schemas/Count' + freshness: + $ref: '#/components/schemas/FreshnessValue' + offset: + $ref: '#/components/schemas/Offset' + country: + $ref: '#/components/schemas/Country' + language: + $ref: '#/components/schemas/Language' + safesearch: + $ref: '#/components/schemas/SafeSearch' + livecrawl: + $ref: '#/components/schemas/LiveCrawl' + livecrawl_formats: + $ref: '#/components/schemas/LiveCrawlFormats' + include_domains: + $ref: '#/components/schemas/IncludeDomains' + exclude_domains: + $ref: '#/components/schemas/ExcludeDomains' + crawl_timeout: + $ref: '#/components/schemas/CrawlTimeout' + required: + - query + SearchResponse: + type: object + properties: + results: + type: object + properties: + web: + type: array + items: + $ref: '#/components/schemas/WebResult' + news: + type: array + items: + $ref: '#/components/schemas/NewsResult' + metadata: + $ref: '#/components/schemas/SearchMetadata' + WebResult: + type: object + properties: + url: + type: string + description: The URL of the specific search result. + example: https://you.com + title: + type: string + description: The title or name of the search result. + example: The World's Greatest Search Engine! + description: + type: string + description: A brief description of the content of the search result. + example: Search on YDC + snippets: + type: array + items: + type: string + example: >- + I'm an AI assistant that helps you get more done. What can I help you with? + description: An array of text snippets from the search result, providing a preview of the content. + thumbnail_url: + type: string + description: URL of the thumbnail. + example: https://www.somethumbnailsite.com/thumbnail.jpg + page_age: + type: string + format: date-time + description: The age of the search result. + example: "2025-06-25T11:41:00" + contents: + $ref: '#/components/schemas/Contents' + authors: + type: array + items: + type: string + example: John Doe + description: An array of authors of the search result. + favicon_url: + type: string + description: The URL of the favicon of the search result's domain. + example: https://someurl.com/favicon + NewsResult: + type: object + properties: + title: + type: string + description: The title of the news result. + example: >- + Exclusive | You.com becomes the backbone of the EU's AI strategy + description: + type: string + description: A brief description of the content of the news result. + example: >- + As the EU's AI strategy is being debated, You.com becomes the backbone of the EU's AI strategy. + page_age: + type: string + format: date-time + description: UTC timestamp of the article's publication date. + example: "2025-06-25T11:41:00" + thumbnail_url: + type: string + description: URL of the thumbnail. + example: https://www.somethumbnailsite.com/thumbnail.jpg + url: + type: string + description: The URL of the news result. + example: https://www.you.com/news/eu-ai-strategy-youcom + contents: + $ref: '#/components/schemas/Contents' + SearchMetadata: + type: object + properties: + search_uuid: + type: string + format: uuid + example: 942ccbdd-7705-4d9c-9d37-4ef386658e90 + query: + type: string + description: Returns the search query used to retrieve the results. + example: What are the latest geopolitical updates from India + latency: + type: number + example: 0.123 Contents: type: object properties: @@ -1414,5 +1433,164 @@ components: example: "https://api.ydc-index.io/favicon?domain=you.com&size=128" description: Metadata about the web page. Only returned when 'metadata' is included in the formats array. nullable: true -security: - - ApiKeyAuth: [] + responses: + SearchSuccess: + description: A JSON object containing unified search results from web and news sources + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResponse' + Unauthorized: + description: Unauthorized. Problems with API key. + content: + application/json: + schema: + type: object + properties: + detail: + type: string + description: Error detail message. + examples: + missingApiKey: + summary: Missing API key + value: + detail: API key is required + invalidOrExpired: + summary: Invalid/expired API key + value: + detail: Invalid or expired API key + otherAuthParsing: + summary: Other auth parsing errors + value: + detail: + Forbidden: + description: Forbidden. API key lacks scope for this path. + content: + application/json: + schema: + type: object + properties: + detail: + type: string + examples: + missingScopes: + summary: Missing required scopes + value: + detail: Missing required scopes + UnprocessableEntity: + description: Unprocessable Entity. Invalid request parameter combination. + content: + application/json: + schema: + type: object + properties: + error: + type: string + examples: + invalidParams: + summary: Invalid request parameters + value: + error: "invalid request parameter(s)" + InternalServerError: + description: Internal Server Error during authentication/authorization middleware. + content: + application/json: + schema: + type: object + properties: + detail: + type: string + examples: + authFailure: + summary: Authentication failure + value: + detail: Internal authentication error + authorizationFailure: + summary: Authorization failure + value: + detail: Internal authorization error + parameters: + Query: + name: query + in: query + required: true + schema: + $ref: '#/components/schemas/SearchQuery' + Count: + name: count + in: query + required: false + schema: + $ref: '#/components/schemas/Count' + FreshnessParam: + name: freshness + in: query + required: false + schema: + $ref: '#/components/schemas/FreshnessValue' + Offset: + name: offset + in: query + required: false + schema: + $ref: '#/components/schemas/Offset' + CountryParam: + name: country + in: query + required: false + schema: + $ref: '#/components/schemas/Country' + LanguageParam: + name: language + in: query + required: false + schema: + $ref: '#/components/schemas/Language' + SafeSearchParam: + name: safesearch + in: query + required: false + schema: + $ref: '#/components/schemas/SafeSearch' + LiveCrawlParam: + name: livecrawl + in: query + required: false + schema: + $ref: '#/components/schemas/LiveCrawl' + LiveCrawlFormatsParam: + name: livecrawl_formats + in: query + required: false + style: form + explode: true + schema: + $ref: '#/components/schemas/LiveCrawlFormats' + IncludeDomainsParam: + name: include_domains + in: query + description: |- + 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. + required: false + schema: + type: string + example: nytimes.com,bbc.com + ExcludeDomainsParam: + name: exclude_domains + in: query + description: |- + 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. + required: false + schema: + type: string + example: spam-site.com,other-site.com + CrawlTimeout: + name: crawl_timeout + in: query + required: false + schema: + $ref: '#/components/schemas/CrawlTimeout' diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 55180ec..a95d589 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,9 +1,9 @@ -speakeasyVersion: 1.733.4 +speakeasyVersion: 1.761.9 sources: You.com API: sourceNamespace: you-com-search-api - sourceRevisionDigest: sha256:1d9828035b2b9ec387808b7167bd6f5d833492b9c95a7c236e736cacb24c2e2a - sourceBlobDigest: sha256:b436d65210d9571c37986fbf57326dabb68500ef8c1ac06ae8378768e6c8a254 + sourceRevisionDigest: sha256:21b61adfb37e5271ef2670871f39b63bb40dca516af4c1a408e0d6b49544e2bf + sourceBlobDigest: sha256:4d4a51d32309f54a93b6918cc5c83eec660fef3778669ad929fe1824c7512499 tags: - latest - 1.0.0 @@ -11,21 +11,21 @@ targets: you: source: You.com API sourceNamespace: you-com-search-api - sourceRevisionDigest: sha256:1d9828035b2b9ec387808b7167bd6f5d833492b9c95a7c236e736cacb24c2e2a - sourceBlobDigest: sha256:b436d65210d9571c37986fbf57326dabb68500ef8c1ac06ae8378768e6c8a254 + sourceRevisionDigest: sha256:21b61adfb37e5271ef2670871f39b63bb40dca516af4c1a408e0d6b49544e2bf + sourceBlobDigest: sha256:4d4a51d32309f54a93b6918cc5c83eec660fef3778669ad929fe1824c7512499 codeSamplesNamespace: you-com-search-api-code-samples - codeSamplesRevisionDigest: sha256:15160af083a36e6567c9dbd9a52eb1f963895eeb92bc129469febf19f65e74bb + codeSamplesRevisionDigest: sha256:0b093740e74cfd0253d0a1fbed2904ce8f454896c33baed3364f98115bceb46f workflow: workflowVersion: 1.0.0 speakeasyVersion: latest sources: You.com API: inputs: - - location: https://youdotcom-pr-11819.vercel.app/specs/openapi_unified_agents.yaml - - location: https://youdotcom-pr-11819.vercel.app/specs/openapi_search_v1.yaml - - location: https://youdotcom-pr-11819.vercel.app/specs/openapi_contents.yaml - - location: https://youdotcom-pr-11819.vercel.app/specs/openapi_research.yaml - - location: https://youdotcom-pr-11819.vercel.app/specs/openapi_base.yaml + - location: https://you.com/specs/openapi_unified_agents.yaml + - location: https://you.com/specs/openapi_search_v1.yaml + - location: https://you.com/specs/openapi_contents.yaml + - location: https://you.com/specs/openapi_research.yaml + - location: https://you.com/specs/openapi_base.yaml overlays: - location: ./overlays/python_overlay.yaml output: .speakeasy/out.openapi.yaml diff --git a/.speakeasy/workflow.yaml b/.speakeasy/workflow.yaml index de942a4..299e02b 100644 --- a/.speakeasy/workflow.yaml +++ b/.speakeasy/workflow.yaml @@ -6,6 +6,7 @@ sources: - location: https://you.com/specs/openapi_unified_agents.yaml - location: https://you.com/specs/openapi_search_v1.yaml - location: https://you.com/specs/openapi_contents.yaml + - location: https://you.com/specs/openapi_research.yaml - location: https://you.com/specs/openapi_base.yaml overlays: - location: ./overlays/python_overlay.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a3a98..f7c170d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ 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.3.1] - 2026-04-08 + +### Added + +- **`crawl_timeout` parameter** on `search()` and `search_async()`: Controls the maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Defaults to `10`. + +```python +from youdotcom import You +from youdotcom.models import LiveCrawl + +you = You() +res = you.search.search( + query="Python async programming", + livecrawl=LiveCrawl.ALWAYS, + crawl_timeout=30, +) +``` + +### Changed + +- **Research API moved to `Agents` namespace**: The `research()` method has been refactored and is no longer directly on the `You` client. The Research API is now available through the Agents namespace. Refer to the updated `USAGE.md` and `README.md` for the new calling convention. +- **Removed standalone research models**: `ResearchRequest`, `ResearchResponse`, `ResearchEffort`, `ResearchInput`, `ResearchLoc`, `ResearchDetail`, `ContentType`, `Output`, `Source`, `Input1`, `Input2` are no longer exported from `youdotcom.models`. +- **Removed research error types**: `ResearchUnauthorizedError`, `ResearchForbiddenError`, `ResearchInternalServerError`, `UnprocessableEntityError` are no longer exported from `youdotcom.errors`. +- **Renamed `Input1` → `Input`** in `AgentRunsBatchResponse`: The ambiguous `Input1` type is now exported as `Input`. +- Internal SDK improvements to event streaming, security handling, and serialization utilities. + +### Removed + +- `You.research()` and `You.research_async()` top-level methods removed from the `You` client. +- `src/youdotcom/models/researchop.py` and `src/youdotcom/errors/researchop.py` deleted. + ## [2.3.0] - 2026-02-27 ### Added diff --git a/README.md b/README.md index ae045c0..0b2755c 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,13 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.research(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -161,7 +167,13 @@ async def main(): api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = await you.research_async(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = await you.search_post_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -192,7 +204,13 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.research(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -208,6 +226,7 @@ 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 * [research](docs/sdks/you/README.md#research) - Returns comprehensive research-grade answers with multi-step reasoning ### [Agents.Runs](docs/sdks/runs/README.md) @@ -328,11 +347,14 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.research( - input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", - research_effort=models.ResearchEffort.LITE, - retries=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False), - ) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10, + RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False)) # Handle response print(res) @@ -351,7 +373,13 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.research(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -385,7 +413,13 @@ with You( res = None try: - res = you.research(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -400,7 +434,7 @@ with You( print(e.raw_response) # Depending on the method different errors may be thrown - if isinstance(e, errors.ResearchUnauthorizedError): + if isinstance(e, errors.UnauthorizedResponseError): print(e.data.detail) # Optional[str] ``` @@ -408,7 +442,7 @@ with You( **Primary error:** * [`YouError`](./src/youdotcom/errors/youerror.py): The base class for HTTP error responses. -
Less common errors (18) +
Less common errors (19)
@@ -419,19 +453,20 @@ with You( **Inherit from [`YouError`](./src/youdotcom/errors/youerror.py)**: -* [`AgentRuns400ResponseError`](./src/youdotcom/errors/agentruns400responseerror.py): The message returned by the error. Status code `400`. Applicable to 1 of 4 methods.* -* [`ResearchUnauthorizedError`](./src/youdotcom/errors/researchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 4 methods.* -* [`SearchUnauthorizedError`](./src/youdotcom/errors/searchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 4 methods.* -* [`ContentsUnauthorizedError`](./src/youdotcom/errors/contentsunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 4 methods.* -* [`AgentRuns401ResponseError`](./src/youdotcom/errors/agentruns401responseerror.py): The message returned by the error. Status code `401`. Applicable to 1 of 4 methods.* -* [`ResearchForbiddenError`](./src/youdotcom/errors/researchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 4 methods.* -* [`SearchForbiddenError`](./src/youdotcom/errors/searchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 4 methods.* -* [`ContentsForbiddenError`](./src/youdotcom/errors/contentsforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 4 methods.* -* [`UnprocessableEntityError`](./src/youdotcom/errors/unprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 4 methods.* -* [`AgentRuns422ResponseError`](./src/youdotcom/errors/agentruns422responseerror.py): Unprocessable Entity - Invalid request data. Status code `422`. Applicable to 1 of 4 methods.* -* [`ResearchInternalServerError`](./src/youdotcom/errors/researchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 4 methods.* -* [`SearchInternalServerError`](./src/youdotcom/errors/searchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 4 methods.* -* [`ContentsInternalServerError`](./src/youdotcom/errors/contentsinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 4 methods.* +* [`UnauthorizedResponseError`](./src/youdotcom/errors/unauthorizedresponseerror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 2 of 5 methods.* +* [`ForbiddenResponseError`](./src/youdotcom/errors/forbiddenresponseerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 2 of 5 methods.* +* [`UnprocessableEntityResponseError`](./src/youdotcom/errors/unprocessableentityresponseerror.py): Unprocessable Entity. Invalid request parameter combination. Status code `422`. Applicable to 2 of 5 methods.* +* [`InternalServerErrorResponse`](./src/youdotcom/errors/internalservererrorresponse.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 2 of 5 methods.* +* [`AgentRuns400ResponseError`](./src/youdotcom/errors/agentruns400responseerror.py): The message returned by the error. Status code `400`. Applicable to 1 of 5 methods.* +* [`ResearchUnauthorizedError`](./src/youdotcom/errors/researchunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 5 methods.* +* [`ContentsUnauthorizedError`](./src/youdotcom/errors/contentsunauthorizederror.py): Unauthorized. Problems with API key. Status code `401`. Applicable to 1 of 5 methods.* +* [`AgentRuns401ResponseError`](./src/youdotcom/errors/agentruns401responseerror.py): The message returned by the error. Status code `401`. Applicable to 1 of 5 methods.* +* [`ResearchForbiddenError`](./src/youdotcom/errors/researchforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 5 methods.* +* [`ContentsForbiddenError`](./src/youdotcom/errors/contentsforbiddenerror.py): Forbidden. API key lacks scope for this path. Status code `403`. Applicable to 1 of 5 methods.* +* [`ResearchUnprocessableEntityError`](./src/youdotcom/errors/researchunprocessableentityerror.py): Unprocessable Entity. Request validation failed. Status code `422`. Applicable to 1 of 5 methods.* +* [`AgentRuns422ResponseError`](./src/youdotcom/errors/agentruns422responseerror.py): Unprocessable Entity - Invalid request data. Status code `422`. Applicable to 1 of 5 methods.* +* [`ResearchInternalServerError`](./src/youdotcom/errors/researchinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 5 methods.* +* [`ContentsInternalServerError`](./src/youdotcom/errors/contentsinternalservererror.py): Internal Server Error during authentication/authorization middleware. Status code `500`. Applicable to 1 of 5 methods.* * [`ResponseValidationError`](./src/youdotcom/errors/responsevalidationerror.py): Type mismatch between the response data and the expected Pydantic model. Provides access to the Pydantic validation error via the `cause` attribute.
@@ -474,7 +509,13 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.search.unified(query="Your query", count=10, language=models.Language.EN, server_url="https://ydc-index.io") + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10, server_url="https://ydc-index.io") # Handle response print(res) diff --git a/USAGE.md b/USAGE.md index 9f08d21..aaf1f8f 100644 --- a/USAGE.md +++ b/USAGE.md @@ -9,7 +9,13 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.research(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) @@ -31,7 +37,13 @@ async def main(): api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = await you.research_async(input="Which global cities improved air quality the most over the past 10 years, and what measurable actions contributed?", research_effort=models.ResearchEffort.LITE) + res = await you.search_post_async(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) # Handle response print(res) diff --git a/docs/errors/searchforbiddenerror.md b/docs/errors/forbiddenresponseerror.md similarity index 92% rename from docs/errors/searchforbiddenerror.md rename to docs/errors/forbiddenresponseerror.md index 3a7b4fc..5d49673 100644 --- a/docs/errors/searchforbiddenerror.md +++ b/docs/errors/forbiddenresponseerror.md @@ -1,4 +1,4 @@ -# SearchForbiddenError +# ForbiddenResponseError Forbidden. API key lacks scope for this path. diff --git a/docs/errors/searchinternalservererror.md b/docs/errors/internalservererrorresponse.md similarity index 91% rename from docs/errors/searchinternalservererror.md rename to docs/errors/internalservererrorresponse.md index 28d41b1..b99d7b0 100644 --- a/docs/errors/searchinternalservererror.md +++ b/docs/errors/internalservererrorresponse.md @@ -1,4 +1,4 @@ -# SearchInternalServerError +# InternalServerErrorResponse Internal Server Error during authentication/authorization middleware. diff --git a/docs/errors/unprocessableentityerror.md b/docs/errors/researchunprocessableentityerror.md similarity index 95% rename from docs/errors/unprocessableentityerror.md rename to docs/errors/researchunprocessableentityerror.md index 5abff96..603a7fd 100644 --- a/docs/errors/unprocessableentityerror.md +++ b/docs/errors/researchunprocessableentityerror.md @@ -1,4 +1,4 @@ -# UnprocessableEntityError +# ResearchUnprocessableEntityError Unprocessable Entity. Request validation failed. diff --git a/docs/errors/searchunauthorizederror.md b/docs/errors/unauthorizedresponseerror.md similarity index 92% rename from docs/errors/searchunauthorizederror.md rename to docs/errors/unauthorizedresponseerror.md index f799351..fe02660 100644 --- a/docs/errors/searchunauthorizederror.md +++ b/docs/errors/unauthorizedresponseerror.md @@ -1,4 +1,4 @@ -# SearchUnauthorizedError +# UnauthorizedResponseError Unauthorized. Problems with API key. diff --git a/docs/errors/unprocessableentityresponseerror.md b/docs/errors/unprocessableentityresponseerror.md new file mode 100644 index 0000000..5d44499 --- /dev/null +++ b/docs/errors/unprocessableentityresponseerror.md @@ -0,0 +1,10 @@ +# UnprocessableEntityResponseError + +Unprocessable Entity. Invalid request parameter combination. + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `error` | *Optional[str]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/contentsformats.md b/docs/models/contentsformats.md index 1944732..8510c9a 100644 --- a/docs/models/contentsformats.md +++ b/docs/models/contentsformats.md @@ -1,5 +1,13 @@ # ContentsFormats +## Example Usage + +```python +from youdotcom.models import ContentsFormats + +value = ContentsFormats.HTML +``` + ## Values diff --git a/docs/models/contenttype.md b/docs/models/contenttype.md index b043bcf..a01aeb0 100644 --- a/docs/models/contenttype.md +++ b/docs/models/contenttype.md @@ -2,6 +2,14 @@ The format of the content field. +## Example Usage + +```python +from youdotcom.models import ContentType + +value = ContentType.TEXT +``` + ## Values diff --git a/docs/models/country.md b/docs/models/country.md index a4fc0c4..1bee618 100644 --- a/docs/models/country.md +++ b/docs/models/country.md @@ -2,6 +2,14 @@ The country code that determines the geographical focus of the web results. +## Example Usage + +```python +from youdotcom.models import Country + +value = Country.AR +``` + ## Values diff --git a/docs/models/freshness.md b/docs/models/freshness.md index e3b507d..38377ef 100644 --- a/docs/models/freshness.md +++ b/docs/models/freshness.md @@ -1,6 +1,12 @@ # Freshness -Specifies the freshness of the results to return. +## Example Usage + +```python +from youdotcom.models import Freshness + +value = Freshness.DAY +``` ## Values diff --git a/docs/models/searchfreshness.md b/docs/models/freshnessvalue.md similarity index 97% rename from docs/models/searchfreshness.md rename to docs/models/freshnessvalue.md index e947818..4c5c058 100644 --- a/docs/models/searchfreshness.md +++ b/docs/models/freshnessvalue.md @@ -1,4 +1,4 @@ -# SearchFreshness +# FreshnessValue 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`. diff --git a/docs/models/language.md b/docs/models/language.md index 862060b..a401ff5 100644 --- a/docs/models/language.md +++ b/docs/models/language.md @@ -1,5 +1,15 @@ # Language +The language of the web results that will be returned (BCP 47 format). + +## Example Usage + +```python +from youdotcom.models import Language + +value = Language.AR +``` + ## Values diff --git a/docs/models/livecrawl.md b/docs/models/livecrawl.md index ff4239a..c62c4ee 100644 --- a/docs/models/livecrawl.md +++ b/docs/models/livecrawl.md @@ -2,6 +2,14 @@ Indicates which section(s) of search results to livecrawl and return full page content. +## Example Usage + +```python +from youdotcom.models import LiveCrawl + +value = LiveCrawl.WEB +``` + ## Values diff --git a/docs/models/livecrawlformats.md b/docs/models/livecrawlformats.md index dcfb112..c2d0a6b 100644 --- a/docs/models/livecrawlformats.md +++ b/docs/models/livecrawlformats.md @@ -1,6 +1,12 @@ # LiveCrawlFormats -Indicates the format of the livecrawled content. +## Example Usage + +```python +from youdotcom.models import LiveCrawlFormats + +value = LiveCrawlFormats.HTML +``` ## Values diff --git a/docs/models/news.md b/docs/models/newsresult.md similarity index 99% rename from docs/models/news.md rename to docs/models/newsresult.md index fec0835..0aad594 100644 --- a/docs/models/news.md +++ b/docs/models/newsresult.md @@ -1,4 +1,4 @@ -# News +# NewsResult ## Fields diff --git a/docs/models/reportverbosity.md b/docs/models/reportverbosity.md index 1a2aae5..7c3d17f 100644 --- a/docs/models/reportverbosity.md +++ b/docs/models/reportverbosity.md @@ -2,6 +2,14 @@ Select whether to receive a medium or high length model response. +## Example Usage + +```python +from youdotcom.models import ReportVerbosity + +value = ReportVerbosity.MEDIUM +``` + ## Values diff --git a/docs/models/researcheffort.md b/docs/models/researcheffort.md index e54e207..27ca90d 100644 --- a/docs/models/researcheffort.md +++ b/docs/models/researcheffort.md @@ -8,6 +8,14 @@ Available levels: - `deep`: Spends more time researching and cross-referencing sources. Use this when accuracy and thoroughness matter more than speed. - `exhaustive`: The most thorough option. Explores the topic as fully as possible, best suited for complex research tasks where you want the highest quality result. +## Example Usage + +```python +from youdotcom.models import ResearchEffort + +value = ResearchEffort.LITE +``` + ## Values diff --git a/docs/models/results.md b/docs/models/results.md index c178f25..342f0ea 100644 --- a/docs/models/results.md +++ b/docs/models/results.md @@ -3,7 +3,7 @@ ## Fields -| Field | Type | Required | Description | -| -------------------------------------- | -------------------------------------- | -------------------------------------- | -------------------------------------- | -| `web` | List[[models.Web](../models/web.md)] | :heavy_minus_sign: | N/A | -| `news` | List[[models.News](../models/news.md)] | :heavy_minus_sign: | N/A | \ No newline at end of file +| Field | Type | Required | Description | +| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | +| `web` | List[[models.WebResult](../models/webresult.md)] | :heavy_minus_sign: | N/A | +| `news` | List[[models.NewsResult](../models/newsresult.md)] | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/role.md b/docs/models/role.md index 3313880..1f861cc 100644 --- a/docs/models/role.md +++ b/docs/models/role.md @@ -2,6 +2,14 @@ The access based role of the user +## Example Usage + +```python +from youdotcom.models import Role + +value = Role.USER +``` + ## Values diff --git a/docs/models/safesearch.md b/docs/models/safesearch.md index 2eae368..9efddcb 100644 --- a/docs/models/safesearch.md +++ b/docs/models/safesearch.md @@ -2,6 +2,14 @@ Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. +## Example Usage + +```python +from youdotcom.models import SafeSearch + +value = SafeSearch.OFF +``` + ## Values diff --git a/docs/models/searchcountry.md b/docs/models/searchcountry.md deleted file mode 100644 index 9a95aec..0000000 --- a/docs/models/searchcountry.md +++ /dev/null @@ -1,19 +0,0 @@ -# SearchCountry - -The country code that determines the geographical focus of the web results. - - -## Supported Types - -### `models.Country` - -```python -value: models.Country = /* values here */ -``` - -### `str` - -```python -value: str = /* values here */ -``` - diff --git a/docs/models/searcheffort.md b/docs/models/searcheffort.md index 95bb5b7..6f1c1ed 100644 --- a/docs/models/searcheffort.md +++ b/docs/models/searcheffort.md @@ -4,6 +4,14 @@ This parameter maps to different configurations regarding the depth of research Alternatively, use `auto` mode for a more dynamic search approach, allowing the tool the freedom to adjust its subparameters. +## Example Usage + +```python +from youdotcom.models import SearchEffort + +value = SearchEffort.AUTO +``` + ## Values diff --git a/docs/models/searchlivecrawl.md b/docs/models/searchlivecrawl.md deleted file mode 100644 index ccf9200..0000000 --- a/docs/models/searchlivecrawl.md +++ /dev/null @@ -1,19 +0,0 @@ -# SearchLivecrawl - -Indicates which section(s) of search results to livecrawl and return full page content. - - -## Supported Types - -### `models.LiveCrawl` - -```python -value: models.LiveCrawl = /* values here */ -``` - -### `str` - -```python -value: str = /* values here */ -``` - diff --git a/docs/models/searchlivecrawlformats.md b/docs/models/searchlivecrawlformats.md deleted file mode 100644 index 690de1f..0000000 --- a/docs/models/searchlivecrawlformats.md +++ /dev/null @@ -1,19 +0,0 @@ -# SearchLivecrawlFormats - -Indicates the format of the livecrawled content. - - -## Supported Types - -### `models.LiveCrawlFormats` - -```python -value: models.LiveCrawlFormats = /* values here */ -``` - -### `str` - -```python -value: str = /* values here */ -``` - diff --git a/docs/models/metadata.md b/docs/models/searchmetadata.md similarity index 92% rename from docs/models/metadata.md rename to docs/models/searchmetadata.md index 66efc64..0110563 100644 --- a/docs/models/metadata.md +++ b/docs/models/searchmetadata.md @@ -1,4 +1,4 @@ -# Metadata +# SearchMetadata ## Fields @@ -6,5 +6,5 @@ | Field | Type | Required | Description | Example | | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | | `search_uuid` | *Optional[str]* | :heavy_minus_sign: | N/A | 942ccbdd-7705-4d9c-9d37-4ef386658e90 | -| `query` | *Optional[str]* | :heavy_minus_sign: | Returns the search query used to retrieve the results. | Your query | +| `query` | *Optional[str]* | :heavy_minus_sign: | Returns the search query used to retrieve the results. | What are the latest geopolitical updates from India | | `latency` | *Optional[float]* | :heavy_minus_sign: | N/A | 0.123 | \ No newline at end of file diff --git a/docs/models/searchrequest.md b/docs/models/searchrequest.md index 1adb45e..77b946f 100644 --- a/docs/models/searchrequest.md +++ b/docs/models/searchrequest.md @@ -5,12 +5,15 @@ | Field | Type | Required | Description | Example | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `query` | *str* | :heavy_check_mark: | 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. | Your query | -| `count` | *Optional[int]* | :heavy_minus_sign: | Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). | | -| `freshness` | [Optional[models.SearchFreshness]](../models/searchfreshness.md) | :heavy_minus_sign: | 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. | | -| `offset` | *Optional[int]* | :heavy_minus_sign: | 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`. | | -| `country` | [Optional[models.SearchCountry]](../models/searchcountry.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | +| `query` | *str* | :heavy_check_mark: | N/A | What are the latest geopolitical updates from India | +| `count` | *Optional[int]* | :heavy_minus_sign: | N/A | | +| `freshness` | [Optional[models.FreshnessValue]](../models/freshnessvalue.md) | :heavy_minus_sign: | 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. | | +| `offset` | *Optional[int]* | :heavy_minus_sign: | N/A | | +| `country` | [Optional[models.Country]](../models/country.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | | `language` | [Optional[models.Language]](../models/language.md) | :heavy_minus_sign: | The language of the web results that will be returned (BCP 47 format). | | -| `safesearch` | [Optional[models.SearchSafesearch]](../models/searchsafesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | -| `livecrawl` | [Optional[models.SearchLivecrawl]](../models/searchlivecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | -| `livecrawl_formats` | [Optional[models.SearchLivecrawlFormats]](../models/searchlivecrawlformats.md) | :heavy_minus_sign: | Indicates the format of the livecrawled content. | | \ No newline at end of file +| `safesearch` | [Optional[models.SafeSearch]](../models/safesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | +| `livecrawl` | [Optional[models.LiveCrawl]](../models/livecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | +| `livecrawl_formats` | List[[models.LiveCrawlFormats](../models/livecrawlformats.md)] | :heavy_minus_sign: | N/A | | +| `include_domains` | *Optional[str]* | :heavy_minus_sign: | 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. | nytimes.com,bbc.com | +| `exclude_domains` | *Optional[str]* | :heavy_minus_sign: | 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. | spam-site.com,other-site.com | +| `crawl_timeout` | *Optional[int]* | :heavy_minus_sign: | N/A | 10 | \ No newline at end of file diff --git a/docs/models/searchrequestbody.md b/docs/models/searchrequestbody.md new file mode 100644 index 0000000..08398e0 --- /dev/null +++ b/docs/models/searchrequestbody.md @@ -0,0 +1,19 @@ +# SearchRequestBody + + +## Fields + +| Field | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `query` | *str* | :heavy_check_mark: | 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. | What are the latest geopolitical updates from India | +| `count` | *Optional[int]* | :heavy_minus_sign: | Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). | | +| `freshness` | [Optional[models.FreshnessValue]](../models/freshnessvalue.md) | :heavy_minus_sign: | 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. | | +| `offset` | *Optional[int]* | :heavy_minus_sign: | 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`. | | +| `country` | [Optional[models.Country]](../models/country.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | +| `language` | [Optional[models.Language]](../models/language.md) | :heavy_minus_sign: | The language of the web results that will be returned (BCP 47 format). | | +| `safesearch` | [Optional[models.SafeSearch]](../models/safesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | +| `livecrawl` | [Optional[models.LiveCrawl]](../models/livecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | +| `livecrawl_formats` | List[[models.LiveCrawlFormats](../models/livecrawlformats.md)] | :heavy_minus_sign: | 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"]`. | | +| `include_domains` | List[*str*] | :heavy_minus_sign: | 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. | [
"nytimes.com",
"bbc.com"
] | +| `exclude_domains` | List[*str*] | :heavy_minus_sign: | 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. | [
"spam-site.com",
"other-site.com"
] | +| `crawl_timeout` | *Optional[int]* | :heavy_minus_sign: | Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds. | 10 | \ No newline at end of file diff --git a/docs/models/searchresponse.md b/docs/models/searchresponse.md index 5a85ee3..0a0dcb7 100644 --- a/docs/models/searchresponse.md +++ b/docs/models/searchresponse.md @@ -5,7 +5,7 @@ A JSON object containing unified search results from web and news sources ## Fields -| Field | Type | Required | Description | -| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -| `results` | [Optional[models.Results]](../models/results.md) | :heavy_minus_sign: | N/A | -| `metadata` | [Optional[models.Metadata]](../models/metadata.md) | :heavy_minus_sign: | N/A | \ No newline at end of file +| Field | Type | Required | Description | +| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | +| `results` | [Optional[models.Results]](../models/results.md) | :heavy_minus_sign: | N/A | +| `metadata` | [Optional[models.SearchMetadata]](../models/searchmetadata.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/searchsafesearch.md b/docs/models/searchsafesearch.md deleted file mode 100644 index fdad51a..0000000 --- a/docs/models/searchsafesearch.md +++ /dev/null @@ -1,19 +0,0 @@ -# SearchSafesearch - -Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. - - -## Supported Types - -### `models.SafeSearch` - -```python -value: models.SafeSearch = /* values here */ -``` - -### `str` - -```python -value: str = /* values here */ -``` - diff --git a/docs/models/type.md b/docs/models/type.md index db75a34..31b2152 100644 --- a/docs/models/type.md +++ b/docs/models/type.md @@ -4,6 +4,14 @@ The type of output. This can either be: * `message.answer` for text responses * `web_search.results` for output that contains web links. `web_search.results` only appear when you use the `research` tool or express agent with web_search +## Example Usage + +```python +from youdotcom.models import Type + +value = Type.MESSAGE_ANSWER +``` + ## Values diff --git a/docs/models/verbosity.md b/docs/models/verbosity.md index 3c30d67..35bfe62 100644 --- a/docs/models/verbosity.md +++ b/docs/models/verbosity.md @@ -2,6 +2,14 @@ Controls the level of detail provided by the agent's response. Choosing high maps to a long-form report while medium maps to a medium verbosity report that captures most details but is less comprehensive. +## Example Usage + +```python +from youdotcom.models import Verbosity + +value = Verbosity.MEDIUM +``` + ## Values diff --git a/docs/models/web.md b/docs/models/webresult.md similarity index 99% rename from docs/models/web.md rename to docs/models/webresult.md index b66b065..efac9df 100644 --- a/docs/models/web.md +++ b/docs/models/webresult.md @@ -1,4 +1,4 @@ -# Web +# WebResult ## Fields diff --git a/docs/sdks/runs/README.md b/docs/sdks/runs/README.md index c8d570f..a653b91 100644 --- a/docs/sdks/runs/README.md +++ b/docs/sdks/runs/README.md @@ -185,6 +185,7 @@ with You( | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | | `request` | [models.AgentsRunsRequest](../../models/agentsrunsrequest.md) | :heavy_check_mark: | The request object to use for the request. | | `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. | ### Response diff --git a/docs/sdks/search/README.md b/docs/sdks/search/README.md index 72bfdc7..57fd078 100644 --- a/docs/sdks/search/README.md +++ b/docs/sdks/search/README.md @@ -10,6 +10,8 @@ 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. + ### Example Usage @@ -22,7 +24,7 @@ with You( api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), ) as you: - res = you.search.unified(query="Your query", count=10, language=models.Language.EN) + res = you.search.unified(query="Your query", count=10, language=models.Language.EN, include_domains="nytimes.com,bbc.com", exclude_domains="spam-site.com,other-site.com", crawl_timeout=10) # Handle response print(res) @@ -33,15 +35,18 @@ with You( | Parameter | Type | Required | Description | Example | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `query` | *str* | :heavy_check_mark: | 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. | Your query | -| `count` | *Optional[int]* | :heavy_minus_sign: | Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). | | -| `freshness` | [Optional[models.SearchFreshness]](../../models/searchfreshness.md) | :heavy_minus_sign: | 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. | | -| `offset` | *Optional[int]* | :heavy_minus_sign: | 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`. | | -| `country` | [Optional[models.SearchCountry]](../../models/searchcountry.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | +| `query` | *str* | :heavy_check_mark: | N/A | What are the latest geopolitical updates from India | +| `count` | *Optional[int]* | :heavy_minus_sign: | N/A | | +| `freshness` | [Optional[models.FreshnessValue]](../../models/freshnessvalue.md) | :heavy_minus_sign: | 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. | | +| `offset` | *Optional[int]* | :heavy_minus_sign: | N/A | | +| `country` | [Optional[models.Country]](../../models/country.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | | `language` | [Optional[models.Language]](../../models/language.md) | :heavy_minus_sign: | The language of the web results that will be returned (BCP 47 format). | | -| `safesearch` | [Optional[models.SearchSafesearch]](../../models/searchsafesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | -| `livecrawl` | [Optional[models.SearchLivecrawl]](../../models/searchlivecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | -| `livecrawl_formats` | [Optional[models.SearchLivecrawlFormats]](../../models/searchlivecrawlformats.md) | :heavy_minus_sign: | Indicates the format of the livecrawled content. | | +| `safesearch` | [Optional[models.SafeSearch]](../../models/safesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | +| `livecrawl` | [Optional[models.LiveCrawl]](../../models/livecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | +| `livecrawl_formats` | List[[models.LiveCrawlFormats](../../models/livecrawlformats.md)] | :heavy_minus_sign: | N/A | | +| `include_domains` | *Optional[str]* | :heavy_minus_sign: | 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. | nytimes.com,bbc.com | +| `exclude_domains` | *Optional[str]* | :heavy_minus_sign: | 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. | spam-site.com,other-site.com | +| `crawl_timeout` | *Optional[int]* | :heavy_minus_sign: | N/A | 10 | | `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. | http://localhost:8080 | @@ -51,9 +56,10 @@ with You( ### Errors -| Error Type | Status Code | Content Type | -| -------------------------------- | -------------------------------- | -------------------------------- | -| errors.SearchUnauthorizedError | 401 | application/json | -| errors.SearchForbiddenError | 403 | application/json | -| errors.SearchInternalServerError | 500 | application/json | -| errors.YouDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file +| Error Type | Status Code | Content Type | +| --------------------------------------- | --------------------------------------- | --------------------------------------- | +| errors.UnauthorizedResponseError | 401 | application/json | +| errors.ForbiddenResponseError | 403 | application/json | +| errors.UnprocessableEntityResponseError | 422 | application/json | +| errors.InternalServerErrorResponse | 500 | application/json | +| errors.YouDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/docs/sdks/you/README.md b/docs/sdks/you/README.md index 6be1470..dafde5b 100644 --- a/docs/sdks/you/README.md +++ b/docs/sdks/you/README.md @@ -8,13 +8,223 @@ Returns the HTML or Markdown of a target webpage Multi-step reasoning with comprehensive research capabilities Comprehensive API for You.com services: - **Agents API**: Execute queries using Express, Advanced, and Custom AI agents +- **Research API**: In-depth, 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 ### Available Operations +* [search_post](#search_post) - Returns a list of unified search results from web and news sources * [research](#research) - Returns comprehensive research-grade answers with multi-step reasoning +## search_post + +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. + +### Example Usage: authFailure + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: authorizationFailure + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: invalidOrExpired + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: invalidParams + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: missingApiKey + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: missingScopes + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` +### Example Usage: otherAuthParsing + + +```python +import os +from youdotcom import You, models + + +with You( + api_key_auth=os.getenv("YOU_API_KEY_AUTH", ""), +) as you: + + res = you.search_post(query="What are the latest geopolitical updates from India", count=10, language=models.Language.EN, include_domains=[ + "nytimes.com", + "bbc.com", + ], exclude_domains=[ + "spam-site.com", + "other-site.com", + ], crawl_timeout=10) + + # Handle response + print(res) + +``` + +### Parameters + +| Parameter | Type | Required | Description | Example | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `query` | *str* | :heavy_check_mark: | 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. | What are the latest geopolitical updates from India | +| `count` | *Optional[int]* | :heavy_minus_sign: | Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them). | | +| `freshness` | [Optional[models.FreshnessValue]](../../models/freshnessvalue.md) | :heavy_minus_sign: | 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. | | +| `offset` | *Optional[int]* | :heavy_minus_sign: | 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`. | | +| `country` | [Optional[models.Country]](../../models/country.md) | :heavy_minus_sign: | The country code that determines the geographical focus of the web results. | | +| `language` | [Optional[models.Language]](../../models/language.md) | :heavy_minus_sign: | The language of the web results that will be returned (BCP 47 format). | | +| `safesearch` | [Optional[models.SafeSearch]](../../models/safesearch.md) | :heavy_minus_sign: | Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not. | | +| `livecrawl` | [Optional[models.LiveCrawl]](../../models/livecrawl.md) | :heavy_minus_sign: | Indicates which section(s) of search results to livecrawl and return full page content. | | +| `livecrawl_formats` | List[[models.LiveCrawlFormats](../../models/livecrawlformats.md)] | :heavy_minus_sign: | 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"]`. | | +| `include_domains` | List[*str*] | :heavy_minus_sign: | 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. | [
"nytimes.com",
"bbc.com"
] | +| `exclude_domains` | List[*str*] | :heavy_minus_sign: | 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. | [
"spam-site.com",
"other-site.com"
] | +| `crawl_timeout` | *Optional[int]* | :heavy_minus_sign: | Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds. | 10 | +| `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. | http://localhost:8080 | + +### Response + +**[models.SearchResponse](../../models/searchresponse.md)** + +### Errors + +| Error Type | Status Code | Content Type | +| --------------------------------------- | --------------------------------------- | --------------------------------------- | +| errors.UnauthorizedResponseError | 401 | application/json | +| errors.ForbiddenResponseError | 403 | application/json | +| errors.UnprocessableEntityResponseError | 422 | application/json | +| errors.InternalServerErrorResponse | 500 | application/json | +| errors.YouDefaultError | 4XX, 5XX | \*/\* | + ## 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. @@ -214,10 +424,10 @@ with You( ### Errors -| Error Type | Status Code | Content Type | -| ---------------------------------- | ---------------------------------- | ---------------------------------- | -| errors.ResearchUnauthorizedError | 401 | application/json | -| errors.ResearchForbiddenError | 403 | application/json | -| errors.UnprocessableEntityError | 422 | application/json | -| errors.ResearchInternalServerError | 500 | application/json | -| errors.YouDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file +| Error Type | Status Code | Content Type | +| --------------------------------------- | --------------------------------------- | --------------------------------------- | +| errors.ResearchUnauthorizedError | 401 | application/json | +| errors.ResearchForbiddenError | 403 | application/json | +| errors.ResearchUnprocessableEntityError | 422 | application/json | +| errors.ResearchInternalServerError | 500 | application/json | +| errors.YouDefaultError | 4XX, 5XX | \*/\* | \ No newline at end of file diff --git a/examples/api-example-calls.py b/examples/api-example-calls.py index e5e0807..4450324 100755 --- a/examples/api-example-calls.py +++ b/examples/api-example-calls.py @@ -227,7 +227,7 @@ def search_request(): def content_request(): """ Contents API endpoint to fetch page content - + In 2.0.0, the Contents API now uses: - formats: Array of format types ('html', 'markdown', 'metadata') - crawl_timeout: Optional timeout between 1-60 seconds @@ -248,9 +248,9 @@ def content_request(): print(f" Title: {result.title}") if result.markdown: print(f" Markdown preview: {result.markdown[:100]}...") - + print("\n" + "-" * 40 + "\n") - + # Example 2: Get multiple formats including metadata (json+ld, opengraph info) print("Example 2: Fetching HTML + metadata...") results = you.contents.generate( @@ -265,7 +265,7 @@ def content_request(): if result.metadata: print(f" Metadata - Site Name: {result.metadata.site_name}") print(f" Metadata - Favicon: {result.metadata.favicon_url}") - + print() diff --git a/pyproject.toml b/pyproject.toml index f0e24df..2135dab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "youdotcom" -version = "2.3.0" +version = "2.3.1" description = "The official You.com Python SDK." authors = [{ name = "You.com" },] readme = "README.md" @@ -17,8 +17,6 @@ dev = [ "mypy ==1.15.0", "pylint ==3.2.3", "pyright ==1.1.398", - "pytest >=8.0.0", - "pytest-asyncio >=0.24.0", ] [tool.setuptools.packages.find] diff --git a/src/youdotcom/_version.py b/src/youdotcom/_version.py index c998bfd..73d9694 100644 --- a/src/youdotcom/_version.py +++ b/src/youdotcom/_version.py @@ -3,10 +3,10 @@ import importlib.metadata __title__: str = "youdotcom" -__version__: str = "2.3.0" +__version__: str = "2.3.1" __openapi_doc_version__: str = "1.0.0" -__gen_version__: str = "2.845.12" -__user_agent__: str = "speakeasy-sdk/python 2.3.0 2.845.12 1.0.0 youdotcom" +__gen_version__: str = "2.881.4" +__user_agent__: str = "speakeasy-sdk/python 2.3.1 2.881.4 1.0.0 youdotcom" try: if __package__ is not None: diff --git a/src/youdotcom/basesdk.py b/src/youdotcom/basesdk.py index ea145dd..8665d7f 100644 --- a/src/youdotcom/basesdk.py +++ b/src/youdotcom/basesdk.py @@ -9,6 +9,7 @@ AfterErrorContext, AfterSuccessContext, BeforeRequestContext, + HookContext, ) from youdotcom.utils import ( RetryConfig, @@ -66,6 +67,7 @@ def _build_request_async( url_override: Optional[str] = None, http_headers: Optional[Mapping[str, str]] = None, allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, ) -> httpx.Request: client = self.sdk_configuration.async_client return self._build_request_with_client( @@ -87,6 +89,7 @@ def _build_request_async( url_override, http_headers, allow_empty_value, + allowed_fields, ) def _build_request( @@ -110,6 +113,7 @@ def _build_request( url_override: Optional[str] = None, http_headers: Optional[Mapping[str, str]] = None, allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, ) -> httpx.Request: client = self.sdk_configuration.client return self._build_request_with_client( @@ -131,6 +135,7 @@ def _build_request( url_override, http_headers, allow_empty_value, + allowed_fields, ) def _build_request_with_client( @@ -155,6 +160,7 @@ def _build_request_with_client( url_override: Optional[str] = None, http_headers: Optional[Mapping[str, str]] = None, allow_empty_value: Optional[List[str]] = None, + allowed_fields: Optional[List[str]] = None, ) -> httpx.Request: query_params = {} @@ -188,7 +194,9 @@ def _build_request_with_client( security = security() security = utils.get_security_from_env(security, models.Security) if security is not None: - security_headers, security_query_params = utils.get_security(security) + security_headers, security_query_params = utils.get_security( + security, allowed_fields + ) headers = {**headers, **security_headers} query_params = {**query_params, **security_query_params} @@ -225,15 +233,15 @@ def _build_request_with_client( data=serialized_request_body.data, files=serialized_request_body.files, headers=headers, - timeout=timeout, + timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT, ) def do_request( self, - hook_ctx, - request, - error_status_codes, - stream=False, + hook_ctx: HookContext, + request: httpx.Request, + is_error_status_code: Callable[[int], bool], + stream: bool = False, retry_config: Optional[Tuple[RetryConfig, List[str]]] = None, ) -> httpx.Response: client = self.sdk_configuration.client @@ -245,6 +253,8 @@ def do(): http_res = None try: req = hooks.before_request(BeforeRequestContext(hook_ctx), request) + if "timeout" in request.extensions and "timeout" not in req.extensions: + req.extensions["timeout"] = request.extensions["timeout"] logger.debug( "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s", req.method, @@ -275,19 +285,6 @@ def do(): "" if stream else http_res.text, ) - if utils.match_status_codes(error_status_codes, http_res.status_code): - result, err = hooks.after_error( - AfterErrorContext(hook_ctx), http_res, None - ) - if err is not None: - logger.debug("Request Exception", exc_info=True) - raise err - if result is not None: - http_res = result - else: - logger.debug("Raising unexpected SDK error") - raise errors.YouDefaultError("Unexpected error occurred", http_res) - return http_res if retry_config is not None: @@ -295,17 +292,27 @@ def do(): else: http_res = do() - if not utils.match_status_codes(error_status_codes, http_res.status_code): + if is_error_status_code(http_res.status_code): + result, err = hooks.after_error(AfterErrorContext(hook_ctx), http_res, None) + if err is not None: + logger.debug("Request Exception", exc_info=True) + raise err + if result is not None: + http_res = result + else: + logger.debug("Raising unexpected SDK error") + raise errors.YouDefaultError("Unexpected error occurred", http_res) + else: http_res = hooks.after_success(AfterSuccessContext(hook_ctx), http_res) return http_res async def do_request_async( self, - hook_ctx, - request, - error_status_codes, - stream=False, + hook_ctx: HookContext, + request: httpx.Request, + is_error_status_code: Callable[[int], bool], + stream: bool = False, retry_config: Optional[Tuple[RetryConfig, List[str]]] = None, ) -> httpx.Response: client = self.sdk_configuration.async_client @@ -320,6 +327,8 @@ async def do(): hooks.before_request, BeforeRequestContext(hook_ctx), request ) + if "timeout" in request.extensions and "timeout" not in req.extensions: + req.extensions["timeout"] = request.extensions["timeout"] logger.debug( "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s", req.method, @@ -353,20 +362,6 @@ async def do(): "" if stream else http_res.text, ) - if utils.match_status_codes(error_status_codes, http_res.status_code): - result, err = await run_sync_in_thread( - hooks.after_error, AfterErrorContext(hook_ctx), http_res, None - ) - - if err is not None: - logger.debug("Request Exception", exc_info=True) - raise err - if result is not None: - http_res = result - else: - logger.debug("Raising unexpected SDK error") - raise errors.YouDefaultError("Unexpected error occurred", http_res) - return http_res if retry_config is not None: @@ -376,7 +371,20 @@ async def do(): else: http_res = await do() - if not utils.match_status_codes(error_status_codes, http_res.status_code): + if is_error_status_code(http_res.status_code): + result, err = await run_sync_in_thread( + hooks.after_error, AfterErrorContext(hook_ctx), http_res, None + ) + + if err is not None: + logger.debug("Request Exception", exc_info=True) + raise err + if result is not None: + http_res = result + else: + logger.debug("Raising unexpected SDK error") + raise errors.YouDefaultError("Unexpected error occurred", http_res) + else: http_res = await run_sync_in_thread( hooks.after_success, AfterSuccessContext(hook_ctx), http_res ) diff --git a/src/youdotcom/contents_sdk.py b/src/youdotcom/contents_sdk.py index a21a07c..04b01ba 100644 --- a/src/youdotcom/contents_sdk.py +++ b/src/youdotcom/contents_sdk.py @@ -88,7 +88,7 @@ def generate( ), ), request=req, - error_status_codes=["401", "403", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) @@ -197,7 +197,7 @@ async def generate_async( ), ), request=req, - error_status_codes=["401", "403", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) diff --git a/src/youdotcom/errors/__init__.py b/src/youdotcom/errors/__init__.py index 2193daa..ec24471 100644 --- a/src/youdotcom/errors/__init__.py +++ b/src/youdotcom/errors/__init__.py @@ -26,6 +26,14 @@ ContentsUnauthorizedError, ContentsUnauthorizedErrorData, ) + from .forbidden_response_error import ( + ForbiddenResponseError, + ForbiddenResponseErrorData, + ) + from .internalservererror_response import ( + InternalServerErrorResponse, + InternalServerErrorResponseData, + ) from .no_response_error import NoResponseError from .researchop import ( ResearchForbiddenError, @@ -34,17 +42,17 @@ ResearchInternalServerErrorData, ResearchUnauthorizedError, ResearchUnauthorizedErrorData, - UnprocessableEntityError, - UnprocessableEntityErrorData, + ResearchUnprocessableEntityError, + ResearchUnprocessableEntityErrorData, ) from .responsevalidationerror import ResponseValidationError - from .searchop import ( - SearchForbiddenError, - SearchForbiddenErrorData, - SearchInternalServerError, - SearchInternalServerErrorData, - SearchUnauthorizedError, - SearchUnauthorizedErrorData, + from .unauthorized_response_error import ( + UnauthorizedResponseError, + UnauthorizedResponseErrorData, + ) + from .unprocessableentity_response_error import ( + UnprocessableEntityResponseError, + UnprocessableEntityResponseErrorData, ) from .youdefaulterror import YouDefaultError @@ -61,6 +69,10 @@ "ContentsInternalServerErrorData", "ContentsUnauthorizedError", "ContentsUnauthorizedErrorData", + "ForbiddenResponseError", + "ForbiddenResponseErrorData", + "InternalServerErrorResponse", + "InternalServerErrorResponseData", "NoResponseError", "ResearchForbiddenError", "ResearchForbiddenErrorData", @@ -68,15 +80,13 @@ "ResearchInternalServerErrorData", "ResearchUnauthorizedError", "ResearchUnauthorizedErrorData", + "ResearchUnprocessableEntityError", + "ResearchUnprocessableEntityErrorData", "ResponseValidationError", - "SearchForbiddenError", - "SearchForbiddenErrorData", - "SearchInternalServerError", - "SearchInternalServerErrorData", - "SearchUnauthorizedError", - "SearchUnauthorizedErrorData", - "UnprocessableEntityError", - "UnprocessableEntityErrorData", + "UnauthorizedResponseError", + "UnauthorizedResponseErrorData", + "UnprocessableEntityResponseError", + "UnprocessableEntityResponseErrorData", "YouDefaultError", "YouError", ] @@ -94,6 +104,10 @@ "ContentsInternalServerErrorData": ".contentsop", "ContentsUnauthorizedError": ".contentsop", "ContentsUnauthorizedErrorData": ".contentsop", + "ForbiddenResponseError": ".forbidden_response_error", + "ForbiddenResponseErrorData": ".forbidden_response_error", + "InternalServerErrorResponse": ".internalservererror_response", + "InternalServerErrorResponseData": ".internalservererror_response", "NoResponseError": ".no_response_error", "ResearchForbiddenError": ".researchop", "ResearchForbiddenErrorData": ".researchop", @@ -101,15 +115,13 @@ "ResearchInternalServerErrorData": ".researchop", "ResearchUnauthorizedError": ".researchop", "ResearchUnauthorizedErrorData": ".researchop", - "UnprocessableEntityError": ".researchop", - "UnprocessableEntityErrorData": ".researchop", + "ResearchUnprocessableEntityError": ".researchop", + "ResearchUnprocessableEntityErrorData": ".researchop", "ResponseValidationError": ".responsevalidationerror", - "SearchForbiddenError": ".searchop", - "SearchForbiddenErrorData": ".searchop", - "SearchInternalServerError": ".searchop", - "SearchInternalServerErrorData": ".searchop", - "SearchUnauthorizedError": ".searchop", - "SearchUnauthorizedErrorData": ".searchop", + "UnauthorizedResponseError": ".unauthorized_response_error", + "UnauthorizedResponseErrorData": ".unauthorized_response_error", + "UnprocessableEntityResponseError": ".unprocessableentity_response_error", + "UnprocessableEntityResponseErrorData": ".unprocessableentity_response_error", "YouDefaultError": ".youdefaulterror", } diff --git a/src/youdotcom/errors/forbidden_response_error.py b/src/youdotcom/errors/forbidden_response_error.py new file mode 100644 index 0000000..575dded --- /dev/null +++ b/src/youdotcom/errors/forbidden_response_error.py @@ -0,0 +1,29 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +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 ForbiddenResponseErrorData(BaseModel): + detail: Optional[str] = None + + +@dataclass(unsafe_hash=True) +class ForbiddenResponseError(YouError): + r"""Forbidden. API key lacks scope for this path.""" + + data: ForbiddenResponseErrorData = field(hash=False) + + def __init__( + self, + data: ForbiddenResponseErrorData, + 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/internalservererror_response.py b/src/youdotcom/errors/internalservererror_response.py new file mode 100644 index 0000000..4a8c1a8 --- /dev/null +++ b/src/youdotcom/errors/internalservererror_response.py @@ -0,0 +1,29 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +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 InternalServerErrorResponseData(BaseModel): + detail: Optional[str] = None + + +@dataclass(unsafe_hash=True) +class InternalServerErrorResponse(YouError): + r"""Internal Server Error during authentication/authorization middleware.""" + + data: InternalServerErrorResponseData = field(hash=False) + + def __init__( + self, + data: InternalServerErrorResponseData, + 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 b1a5836..a64bebb 100644 --- a/src/youdotcom/errors/researchop.py +++ b/src/youdotcom/errors/researchop.py @@ -30,19 +30,19 @@ def __init__( object.__setattr__(self, "data", data) -class UnprocessableEntityErrorData(BaseModel): +class ResearchUnprocessableEntityErrorData(BaseModel): detail: Optional[List[models_researchop.ResearchDetail]] = None @dataclass(unsafe_hash=True) -class UnprocessableEntityError(YouError): +class ResearchUnprocessableEntityError(YouError): r"""Unprocessable Entity. Request validation failed.""" - data: UnprocessableEntityErrorData = field(hash=False) + data: ResearchUnprocessableEntityErrorData = field(hash=False) def __init__( self, - data: UnprocessableEntityErrorData, + data: ResearchUnprocessableEntityErrorData, raw_response: httpx.Response, body: Optional[str] = None, ): diff --git a/src/youdotcom/errors/searchop.py b/src/youdotcom/errors/searchop.py deleted file mode 100644 index 9bb3794..0000000 --- a/src/youdotcom/errors/searchop.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" - -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 SearchInternalServerErrorData(BaseModel): - detail: Optional[str] = None - - -@dataclass(unsafe_hash=True) -class SearchInternalServerError(YouError): - r"""Internal Server Error during authentication/authorization middleware.""" - - data: SearchInternalServerErrorData = field(hash=False) - - def __init__( - self, - data: SearchInternalServerErrorData, - 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) - - -class SearchForbiddenErrorData(BaseModel): - detail: Optional[str] = None - - -@dataclass(unsafe_hash=True) -class SearchForbiddenError(YouError): - r"""Forbidden. API key lacks scope for this path.""" - - data: SearchForbiddenErrorData = field(hash=False) - - def __init__( - self, - data: SearchForbiddenErrorData, - 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) - - -class SearchUnauthorizedErrorData(BaseModel): - detail: Optional[str] = None - r"""Error detail message.""" - - -@dataclass(unsafe_hash=True) -class SearchUnauthorizedError(YouError): - r"""Unauthorized. Problems with API key.""" - - data: SearchUnauthorizedErrorData = field(hash=False) - - def __init__( - self, - data: SearchUnauthorizedErrorData, - 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/unauthorized_response_error.py b/src/youdotcom/errors/unauthorized_response_error.py new file mode 100644 index 0000000..dbc2f9f --- /dev/null +++ b/src/youdotcom/errors/unauthorized_response_error.py @@ -0,0 +1,30 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +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 UnauthorizedResponseErrorData(BaseModel): + detail: Optional[str] = None + r"""Error detail message.""" + + +@dataclass(unsafe_hash=True) +class UnauthorizedResponseError(YouError): + r"""Unauthorized. Problems with API key.""" + + data: UnauthorizedResponseErrorData = field(hash=False) + + def __init__( + self, + data: UnauthorizedResponseErrorData, + 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/unprocessableentity_response_error.py b/src/youdotcom/errors/unprocessableentity_response_error.py new file mode 100644 index 0000000..f915365 --- /dev/null +++ b/src/youdotcom/errors/unprocessableentity_response_error.py @@ -0,0 +1,29 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +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 UnprocessableEntityResponseErrorData(BaseModel): + error: Optional[str] = None + + +@dataclass(unsafe_hash=True) +class UnprocessableEntityResponseError(YouError): + r"""Unprocessable Entity. Invalid request parameter combination.""" + + data: UnprocessableEntityResponseErrorData = field(hash=False) + + def __init__( + self, + data: UnprocessableEntityResponseErrorData, + 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/models/__init__.py b/src/youdotcom/models/__init__.py index 804e5c2..3cf387e 100644 --- a/src/youdotcom/models/__init__.py +++ b/src/youdotcom/models/__init__.py @@ -37,6 +37,7 @@ DataTypedDict, ) from .agentsrunsop import ( + AGENTS_RUNS_OP_SERVERS, AgentsRunsRequest, AgentsRunsRequestTypedDict, AgentsRunsResponse, @@ -63,9 +64,11 @@ ExpressAgentRunsRequestTypedDict, ) from .freshness import Freshness + from .freshnessvalue import FreshnessValue, FreshnessValueTypedDict from .language import Language from .livecrawl import LiveCrawl from .livecrawlformats import LiveCrawlFormats + from .newsresult import NewsResult, NewsResultTypedDict from .reportverbosity import ReportVerbosity from .researchop import ( ContentType, @@ -122,36 +125,23 @@ from .response_starting import ResponseStarting, ResponseStartingTypedDict from .safesearch import SafeSearch from .searcheffort import SearchEffort - from .searchop import ( - Metadata, - MetadataTypedDict, - News, - NewsTypedDict, + 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, ResultsTypedDict, - SEARCH_OP_SERVERS, - SearchCountry, - SearchCountryTypedDict, - SearchFreshness, - SearchFreshnessTypedDict, - SearchLivecrawl, - SearchLivecrawlFormats, - SearchLivecrawlFormatsTypedDict, - SearchLivecrawlTypedDict, - SearchRequest, - SearchRequestTypedDict, SearchResponse, SearchResponseTypedDict, - SearchSafesearch, - SearchSafesearchTypedDict, - Web, - WebTypedDict, ) from .security import Security, SecurityTypedDict from .verbosity import Verbosity + from .webresult import WebResult, WebResultTypedDict from .websearchtool import WebSearchTool, WebSearchToolTypedDict __all__ = [ + "AGENTS_RUNS_OP_SERVERS", "AdvancedAgentRunsRequest", "AdvancedAgentRunsRequestTypedDict", "AgentRunsBatchResponse", @@ -189,6 +179,8 @@ "ExpressAgentRunsRequest", "ExpressAgentRunsRequestTypedDict", "Freshness", + "FreshnessValue", + "FreshnessValueTypedDict", "Input1", "Input1TypedDict", "Input2", @@ -198,10 +190,8 @@ "LiveCrawlFormats", "Loc", "LocTypedDict", - "Metadata", - "MetadataTypedDict", - "News", - "NewsTypedDict", + "NewsResult", + "NewsResultTypedDict", "Output", "OutputTypedDict", "ReportVerbosity", @@ -246,22 +236,17 @@ "ResultsTypedDict", "Role", "SEARCH_OP_SERVERS", + "SEARCH_POST_OP_SERVERS", "SafeSearch", - "SearchCountry", - "SearchCountryTypedDict", "SearchEffort", - "SearchFreshness", - "SearchFreshnessTypedDict", - "SearchLivecrawl", - "SearchLivecrawlFormats", - "SearchLivecrawlFormatsTypedDict", - "SearchLivecrawlTypedDict", + "SearchMetadata", + "SearchMetadataTypedDict", "SearchRequest", + "SearchRequestBody", + "SearchRequestBodyTypedDict", "SearchRequestTypedDict", "SearchResponse", "SearchResponseTypedDict", - "SearchSafesearch", - "SearchSafesearchTypedDict", "Security", "SecurityTypedDict", "Source", @@ -270,10 +255,10 @@ "ToolTypedDict", "Type", "Verbosity", - "Web", + "WebResult", + "WebResultTypedDict", "WebSearchTool", "WebSearchToolTypedDict", - "WebTypedDict", "WorkflowConfig", "WorkflowConfigTypedDict", ] @@ -303,6 +288,7 @@ "AgentRunsStreamingResponseTypedDict": ".agentrunsstreamingresponse", "Data": ".agentrunsstreamingresponse", "DataTypedDict": ".agentrunsstreamingresponse", + "AGENTS_RUNS_OP_SERVERS": ".agentsrunsop", "AgentsRunsRequest": ".agentsrunsop", "AgentsRunsRequestTypedDict": ".agentsrunsop", "AgentsRunsResponse": ".agentsrunsop", @@ -325,9 +311,13 @@ "ExpressAgentRunsRequest": ".expressagentrunsrequest", "ExpressAgentRunsRequestTypedDict": ".expressagentrunsrequest", "Freshness": ".freshness", + "FreshnessValue": ".freshnessvalue", + "FreshnessValueTypedDict": ".freshnessvalue", "Language": ".language", "LiveCrawl": ".livecrawl", "LiveCrawlFormats": ".livecrawlformats", + "NewsResult": ".newsresult", + "NewsResultTypedDict": ".newsresult", "ReportVerbosity": ".reportverbosity", "ContentType": ".researchop", "Input2": ".researchop", @@ -375,32 +365,23 @@ "ResponseStartingTypedDict": ".response_starting", "SafeSearch": ".safesearch", "SearchEffort": ".searcheffort", - "Metadata": ".searchop", - "MetadataTypedDict": ".searchop", - "News": ".searchop", - "NewsTypedDict": ".searchop", - "Results": ".searchop", - "ResultsTypedDict": ".searchop", + "SearchMetadata": ".searchmetadata", + "SearchMetadataTypedDict": ".searchmetadata", "SEARCH_OP_SERVERS": ".searchop", - "SearchCountry": ".searchop", - "SearchCountryTypedDict": ".searchop", - "SearchFreshness": ".searchop", - "SearchFreshnessTypedDict": ".searchop", - "SearchLivecrawl": ".searchop", - "SearchLivecrawlFormats": ".searchop", - "SearchLivecrawlFormatsTypedDict": ".searchop", - "SearchLivecrawlTypedDict": ".searchop", "SearchRequest": ".searchop", "SearchRequestTypedDict": ".searchop", - "SearchResponse": ".searchop", - "SearchResponseTypedDict": ".searchop", - "SearchSafesearch": ".searchop", - "SearchSafesearchTypedDict": ".searchop", - "Web": ".searchop", - "WebTypedDict": ".searchop", + "SEARCH_POST_OP_SERVERS": ".searchpostop", + "SearchRequestBody": ".searchrequestbody", + "SearchRequestBodyTypedDict": ".searchrequestbody", + "Results": ".searchresponse", + "ResultsTypedDict": ".searchresponse", + "SearchResponse": ".searchresponse", + "SearchResponseTypedDict": ".searchresponse", "Security": ".security", "SecurityTypedDict": ".security", "Verbosity": ".verbosity", + "WebResult": ".webresult", + "WebResultTypedDict": ".webresult", "WebSearchTool": ".websearchtool", "WebSearchToolTypedDict": ".websearchtool", } diff --git a/src/youdotcom/models/advancedagentrunsrequest.py b/src/youdotcom/models/advancedagentrunsrequest.py index f271b2b..8511c0a 100644 --- a/src/youdotcom/models/advancedagentrunsrequest.py +++ b/src/youdotcom/models/advancedagentrunsrequest.py @@ -40,7 +40,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -94,7 +94,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/agentrunsbatchresponse.py b/src/youdotcom/models/agentrunsbatchresponse.py index 187616c..1a21103 100644 --- a/src/youdotcom/models/agentrunsbatchresponse.py +++ b/src/youdotcom/models/agentrunsbatchresponse.py @@ -65,7 +65,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/agentrunsresponseoutput.py b/src/youdotcom/models/agentrunsresponseoutput.py index 14726f5..1eaa631 100644 --- a/src/youdotcom/models/agentrunsresponseoutput.py +++ b/src/youdotcom/models/agentrunsresponseoutput.py @@ -69,7 +69,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/agentrunsresponsewebsearchresult.py b/src/youdotcom/models/agentrunsresponsewebsearchresult.py index a0a105c..7f9d3c3 100644 --- a/src/youdotcom/models/agentrunsresponsewebsearchresult.py +++ b/src/youdotcom/models/agentrunsresponsewebsearchresult.py @@ -64,7 +64,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/agentsrunsop.py b/src/youdotcom/models/agentsrunsop.py index 2ac90c0..52a57d5 100644 --- a/src/youdotcom/models/agentsrunsop.py +++ b/src/youdotcom/models/agentsrunsop.py @@ -26,6 +26,11 @@ from youdotcom.utils import eventstreaming +AGENTS_RUNS_OP_SERVERS = [ + "https://api.you.com", +] + + AgentsRunsRequestTypedDict = TypeAliasType( "AgentsRunsRequestTypedDict", Union[ diff --git a/src/youdotcom/models/contents.py b/src/youdotcom/models/contents.py index ecf165e..37d69b8 100644 --- a/src/youdotcom/models/contents.py +++ b/src/youdotcom/models/contents.py @@ -33,7 +33,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/contentsmetadata.py b/src/youdotcom/models/contentsmetadata.py index 6324e63..b6b8e50 100644 --- a/src/youdotcom/models/contentsmetadata.py +++ b/src/youdotcom/models/contentsmetadata.py @@ -34,7 +34,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/src/youdotcom/models/contentsop.py b/src/youdotcom/models/contentsop.py index fc47c7f..76d7efb 100644 --- a/src/youdotcom/models/contentsop.py +++ b/src/youdotcom/models/contentsop.py @@ -41,7 +41,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -88,7 +88,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/src/youdotcom/models/customagentrunsrequest.py b/src/youdotcom/models/customagentrunsrequest.py index 759f45b..181e560 100644 --- a/src/youdotcom/models/customagentrunsrequest.py +++ b/src/youdotcom/models/customagentrunsrequest.py @@ -34,7 +34,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/expressagentrunsrequest.py b/src/youdotcom/models/expressagentrunsrequest.py index f4f5de0..8211741 100644 --- a/src/youdotcom/models/expressagentrunsrequest.py +++ b/src/youdotcom/models/expressagentrunsrequest.py @@ -46,7 +46,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/freshness.py b/src/youdotcom/models/freshness.py index da5bd9f..83281eb 100644 --- a/src/youdotcom/models/freshness.py +++ b/src/youdotcom/models/freshness.py @@ -5,8 +5,6 @@ class Freshness(str, Enum): - r"""Specifies the freshness of the results to return.""" - DAY = "day" WEEK = "week" MONTH = "month" diff --git a/src/youdotcom/models/freshnessvalue.py b/src/youdotcom/models/freshnessvalue.py new file mode 100644 index 0000000..66ff778 --- /dev/null +++ b/src/youdotcom/models/freshnessvalue.py @@ -0,0 +1,22 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .freshness import Freshness +from typing import Union +from typing_extensions import TypeAliasType + + +FreshnessValueTypedDict = TypeAliasType( + "FreshnessValueTypedDict", Union[Freshness, str] +) +r"""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. +""" + + +FreshnessValue = TypeAliasType("FreshnessValue", Union[Freshness, str]) +r"""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. +""" diff --git a/src/youdotcom/models/language.py b/src/youdotcom/models/language.py index 25de233..171f358 100644 --- a/src/youdotcom/models/language.py +++ b/src/youdotcom/models/language.py @@ -5,6 +5,8 @@ class Language(str, Enum): + r"""The language of the web results that will be returned (BCP 47 format).""" + AR = "AR" EU = "EU" BN = "BN" diff --git a/src/youdotcom/models/livecrawlformats.py b/src/youdotcom/models/livecrawlformats.py index 27c9da8..ceca02d 100644 --- a/src/youdotcom/models/livecrawlformats.py +++ b/src/youdotcom/models/livecrawlformats.py @@ -5,7 +5,5 @@ class LiveCrawlFormats(str, Enum): - r"""Indicates the format of the livecrawled content.""" - HTML = "html" MARKDOWN = "markdown" diff --git a/src/youdotcom/models/newsresult.py b/src/youdotcom/models/newsresult.py new file mode 100644 index 0000000..9a89295 --- /dev/null +++ b/src/youdotcom/models/newsresult.py @@ -0,0 +1,62 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .contents import Contents, ContentsTypedDict +from datetime import datetime +from pydantic import model_serializer +from typing import Optional +from typing_extensions import NotRequired, TypedDict +from youdotcom.types import BaseModel, UNSET_SENTINEL + + +class NewsResultTypedDict(TypedDict): + title: NotRequired[str] + r"""The title of the news result.""" + description: NotRequired[str] + r"""A brief description of the content of the news result.""" + page_age: NotRequired[datetime] + r"""UTC timestamp of the article's publication date.""" + thumbnail_url: NotRequired[str] + r"""URL of the thumbnail.""" + url: NotRequired[str] + r"""The URL of the news result.""" + contents: NotRequired[ContentsTypedDict] + r"""Contents of the page if livecrawl was enabled.""" + + +class NewsResult(BaseModel): + title: Optional[str] = None + r"""The title of the news result.""" + + description: Optional[str] = None + r"""A brief description of the content of the news result.""" + + page_age: Optional[datetime] = None + r"""UTC timestamp of the article's publication date.""" + + thumbnail_url: Optional[str] = None + r"""URL of the thumbnail.""" + + url: Optional[str] = None + r"""The URL of the news result.""" + + contents: Optional[Contents] = None + r"""Contents of the page if livecrawl was enabled.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + ["title", "description", "page_age", "thumbnail_url", "url", "contents"] + ) + 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/researchop.py b/src/youdotcom/models/researchop.py index f8ec45c..647dadc 100644 --- a/src/youdotcom/models/researchop.py +++ b/src/youdotcom/models/researchop.py @@ -66,7 +66,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -134,7 +134,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -176,7 +176,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/searchmetadata.py b/src/youdotcom/models/searchmetadata.py new file mode 100644 index 0000000..758b0c2 --- /dev/null +++ b/src/youdotcom/models/searchmetadata.py @@ -0,0 +1,39 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from pydantic import model_serializer +from typing import Optional +from typing_extensions import NotRequired, TypedDict +from youdotcom.types import BaseModel, UNSET_SENTINEL + + +class SearchMetadataTypedDict(TypedDict): + search_uuid: NotRequired[str] + query: NotRequired[str] + r"""Returns the search query used to retrieve the results.""" + latency: NotRequired[float] + + +class SearchMetadata(BaseModel): + search_uuid: Optional[str] = None + + query: Optional[str] = None + r"""Returns the search query used to retrieve the results.""" + + latency: Optional[float] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["search_uuid", "query", "latency"]) + 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/searchop.py b/src/youdotcom/models/searchop.py index a06a0fd..ad2f094 100644 --- a/src/youdotcom/models/searchop.py +++ b/src/youdotcom/models/searchop.py @@ -1,17 +1,15 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from __future__ import annotations -from .contents import Contents, ContentsTypedDict from .country import Country -from .freshness import Freshness +from .freshnessvalue import FreshnessValue, FreshnessValueTypedDict from .language import Language from .livecrawl import LiveCrawl from .livecrawlformats import LiveCrawlFormats from .safesearch import SafeSearch -from datetime import datetime from pydantic import model_serializer -from typing import List, Optional, Union -from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict +from typing import List, Optional +from typing_extensions import Annotated, NotRequired, TypedDict from youdotcom.types import BaseModel, UNSET_SENTINEL from youdotcom.utils import FieldMetadata, QueryParamMetadata @@ -21,100 +19,49 @@ ] -SearchFreshnessTypedDict = TypeAliasType( - "SearchFreshnessTypedDict", Union[Freshness, str] -) -r"""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. -""" - - -SearchFreshness = TypeAliasType("SearchFreshness", Union[Freshness, str]) -r"""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. -""" - - -SearchCountryTypedDict = TypeAliasType("SearchCountryTypedDict", Union[Country, str]) -r"""The country code that determines the geographical focus of the web results.""" - - -SearchCountry = TypeAliasType("SearchCountry", Union[Country, str]) -r"""The country code that determines the geographical focus of the web results.""" - - -SearchSafesearchTypedDict = TypeAliasType( - "SearchSafesearchTypedDict", Union[SafeSearch, str] -) -r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" - - -SearchSafesearch = TypeAliasType("SearchSafesearch", Union[SafeSearch, str]) -r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" - - -SearchLivecrawlTypedDict = TypeAliasType( - "SearchLivecrawlTypedDict", Union[LiveCrawl, str] -) -r"""Indicates which section(s) of search results to livecrawl and return full page content.""" - - -SearchLivecrawl = TypeAliasType("SearchLivecrawl", Union[LiveCrawl, str]) -r"""Indicates which section(s) of search results to livecrawl and return full page content.""" - - -SearchLivecrawlFormatsTypedDict = TypeAliasType( - "SearchLivecrawlFormatsTypedDict", Union[LiveCrawlFormats, str] -) -r"""Indicates the format of the livecrawled content.""" - - -SearchLivecrawlFormats = TypeAliasType( - "SearchLivecrawlFormats", Union[LiveCrawlFormats, str] -) -r"""Indicates the format of the livecrawled content.""" - - class SearchRequestTypedDict(TypedDict): query: str - r"""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.""" count: NotRequired[int] - r"""Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).""" - freshness: NotRequired[SearchFreshnessTypedDict] + freshness: NotRequired[FreshnessValueTypedDict] r"""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. """ offset: NotRequired[int] - r"""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`.""" - country: NotRequired[SearchCountryTypedDict] + country: NotRequired[Country] r"""The country code that determines the geographical focus of the web results.""" language: NotRequired[Language] r"""The language of the web results that will be returned (BCP 47 format).""" - safesearch: NotRequired[SearchSafesearchTypedDict] + safesearch: NotRequired[SafeSearch] r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" - livecrawl: NotRequired[SearchLivecrawlTypedDict] + livecrawl: NotRequired[LiveCrawl] r"""Indicates which section(s) of search results to livecrawl and return full page content.""" - livecrawl_formats: NotRequired[SearchLivecrawlFormatsTypedDict] - r"""Indicates the format of the livecrawled content.""" + livecrawl_formats: NotRequired[List[LiveCrawlFormats]] + include_domains: NotRequired[str] + r"""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. + """ + exclude_domains: NotRequired[str] + r"""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. + """ + crawl_timeout: NotRequired[int] class SearchRequest(BaseModel): query: Annotated[ str, FieldMetadata(query=QueryParamMetadata(style="form", explode=True)) - ] = "Your query" - r"""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.""" + ] count: Annotated[ Optional[int], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = 10 - r"""Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).""" freshness: Annotated[ - Optional[SearchFreshness], + Optional[FreshnessValue], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None r"""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`. @@ -126,10 +73,9 @@ class SearchRequest(BaseModel): Optional[int], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None - r"""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`.""" country: Annotated[ - Optional[SearchCountry], + Optional[Country], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None r"""The country code that determines the geographical focus of the web results.""" @@ -141,22 +87,44 @@ class SearchRequest(BaseModel): r"""The language of the web results that will be returned (BCP 47 format).""" safesearch: Annotated[ - Optional[SearchSafesearch], + Optional[SafeSearch], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" livecrawl: Annotated[ - Optional[SearchLivecrawl], + Optional[LiveCrawl], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None r"""Indicates which section(s) of search results to livecrawl and return full page content.""" livecrawl_formats: Annotated[ - Optional[SearchLivecrawlFormats], + Optional[List[LiveCrawlFormats]], FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), ] = None - r"""Indicates the format of the livecrawled content.""" + + include_domains: Annotated[ + Optional[str], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + r"""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. + """ + + exclude_domains: Annotated[ + Optional[str], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = None + r"""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. + """ + + crawl_timeout: Annotated[ + Optional[int], + FieldMetadata(query=QueryParamMetadata(style="form", explode=True)), + ] = 10 @model_serializer(mode="wrap") def serialize_model(self, handler): @@ -170,6 +138,9 @@ def serialize_model(self, handler): "safesearch", "livecrawl", "livecrawl_formats", + "include_domains", + "exclude_domains", + "crawl_timeout", ] ) serialized = handler(self) @@ -177,228 +148,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) - - if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: - m[k] = val - - return m - - -class WebTypedDict(TypedDict): - url: NotRequired[str] - r"""The URL of the specific search result.""" - title: NotRequired[str] - r"""The title or name of the search result.""" - description: NotRequired[str] - r"""A brief description of the content of the search result.""" - snippets: NotRequired[List[str]] - r"""An array of text snippets from the search result, providing a preview of the content.""" - thumbnail_url: NotRequired[str] - r"""URL of the thumbnail.""" - page_age: NotRequired[datetime] - r"""The age of the search result.""" - contents: NotRequired[ContentsTypedDict] - r"""Contents of the page if livecrawl was enabled.""" - authors: NotRequired[List[str]] - r"""An array of authors of the search result.""" - favicon_url: NotRequired[str] - r"""The URL of the favicon of the search result's domain.""" - - -class Web(BaseModel): - url: Optional[str] = None - r"""The URL of the specific search result.""" - - title: Optional[str] = None - r"""The title or name of the search result.""" - - description: Optional[str] = None - r"""A brief description of the content of the search result.""" - - snippets: Optional[List[str]] = None - r"""An array of text snippets from the search result, providing a preview of the content.""" - - thumbnail_url: Optional[str] = None - r"""URL of the thumbnail.""" - - page_age: Optional[datetime] = None - r"""The age of the search result.""" - - contents: Optional[Contents] = None - r"""Contents of the page if livecrawl was enabled.""" - - authors: Optional[List[str]] = None - r"""An array of authors of the search result.""" - - favicon_url: Optional[str] = None - r"""The URL of the favicon of the search result's domain.""" - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - optional_fields = set( - [ - "url", - "title", - "description", - "snippets", - "thumbnail_url", - "page_age", - "contents", - "authors", - "favicon_url", - ] - ) - serialized = handler(self) - m = {} - - for n, f in type(self).model_fields.items(): - k = f.alias or n - val = serialized.get(k) - - if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: - m[k] = val - - return m - - -class NewsTypedDict(TypedDict): - title: NotRequired[str] - r"""The title of the news result.""" - description: NotRequired[str] - r"""A brief description of the content of the news result.""" - page_age: NotRequired[datetime] - r"""UTC timestamp of the article's publication date.""" - thumbnail_url: NotRequired[str] - r"""URL of the thumbnail.""" - url: NotRequired[str] - r"""The URL of the news result.""" - contents: NotRequired[ContentsTypedDict] - r"""Contents of the page if livecrawl was enabled.""" - - -class News(BaseModel): - title: Optional[str] = None - r"""The title of the news result.""" - - description: Optional[str] = None - r"""A brief description of the content of the news result.""" - - page_age: Optional[datetime] = None - r"""UTC timestamp of the article's publication date.""" - - thumbnail_url: Optional[str] = None - r"""URL of the thumbnail.""" - - url: Optional[str] = None - r"""The URL of the news result.""" - - contents: Optional[Contents] = None - r"""Contents of the page if livecrawl was enabled.""" - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - optional_fields = set( - ["title", "description", "page_age", "thumbnail_url", "url", "contents"] - ) - serialized = handler(self) - m = {} - - for n, f in type(self).model_fields.items(): - k = f.alias or n - val = serialized.get(k) - - if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: - m[k] = val - - return m - - -class ResultsTypedDict(TypedDict): - web: NotRequired[List[WebTypedDict]] - news: NotRequired[List[NewsTypedDict]] - - -class Results(BaseModel): - web: Optional[List[Web]] = None - - news: Optional[List[News]] = None - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - optional_fields = set(["web", "news"]) - serialized = handler(self) - m = {} - - for n, f in type(self).model_fields.items(): - k = f.alias or n - val = serialized.get(k) - - if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: - m[k] = val - - return m - - -class MetadataTypedDict(TypedDict): - search_uuid: NotRequired[str] - query: NotRequired[str] - r"""Returns the search query used to retrieve the results.""" - latency: NotRequired[float] - - -class Metadata(BaseModel): - search_uuid: Optional[str] = None - - query: Optional[str] = None - r"""Returns the search query used to retrieve the results.""" - - latency: Optional[float] = None - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - optional_fields = set(["search_uuid", "query", "latency"]) - serialized = handler(self) - m = {} - - for n, f in type(self).model_fields.items(): - k = f.alias or n - val = serialized.get(k) - - if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: - m[k] = val - - return m - - -class SearchResponseTypedDict(TypedDict): - r"""A JSON object containing unified search results from web and news sources""" - - results: NotRequired[ResultsTypedDict] - metadata: NotRequired[MetadataTypedDict] - - -class SearchResponse(BaseModel): - r"""A JSON object containing unified search results from web and news sources""" - - results: Optional[Results] = None - - metadata: Optional[Metadata] = None - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - optional_fields = set(["results", "metadata"]) - serialized = handler(self) - m = {} - - for n, f in type(self).model_fields.items(): - k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/searchpostop.py b/src/youdotcom/models/searchpostop.py new file mode 100644 index 0000000..e0f8b5b --- /dev/null +++ b/src/youdotcom/models/searchpostop.py @@ -0,0 +1,8 @@ +"""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 new file mode 100644 index 0000000..09d11ba --- /dev/null +++ b/src/youdotcom/models/searchrequestbody.py @@ -0,0 +1,126 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .country import Country +from .freshnessvalue import FreshnessValue, FreshnessValueTypedDict +from .language import Language +from .livecrawl import LiveCrawl +from .livecrawlformats import LiveCrawlFormats +from .safesearch import SafeSearch +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import NotRequired, TypedDict +from youdotcom.types import BaseModel, UNSET_SENTINEL + + +class SearchRequestBodyTypedDict(TypedDict): + query: str + r"""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.""" + count: NotRequired[int] + r"""Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).""" + freshness: NotRequired[FreshnessValueTypedDict] + r"""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. + """ + offset: NotRequired[int] + r"""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`.""" + country: NotRequired[Country] + r"""The country code that determines the geographical focus of the web results.""" + language: NotRequired[Language] + r"""The language of the web results that will be returned (BCP 47 format).""" + safesearch: NotRequired[SafeSearch] + r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" + livecrawl: NotRequired[LiveCrawl] + r"""Indicates which section(s) of search results to livecrawl and return full page content.""" + livecrawl_formats: NotRequired[List[LiveCrawlFormats]] + r"""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\"]`.""" + include_domains: NotRequired[List[str]] + r"""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. + """ + exclude_domains: NotRequired[List[str]] + r"""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. + """ + crawl_timeout: NotRequired[int] + r"""Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds.""" + + +class SearchRequestBody(BaseModel): + query: str + r"""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.""" + + count: Optional[int] = 10 + r"""Specifies the maximum number of search results to return per section (the sections are `web` and `news`. See the JSON response to visualize them).""" + + freshness: Optional[FreshnessValue] = None + r"""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. + """ + + offset: Optional[int] = None + r"""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`.""" + + country: Optional[Country] = None + r"""The country code that determines the geographical focus of the web results.""" + + language: Optional[Language] = Language.EN + r"""The language of the web results that will be returned (BCP 47 format).""" + + safesearch: Optional[SafeSearch] = None + r"""Configures the safesearch filter for content moderation. This allows you to decide whether to return NSFW content or not.""" + + livecrawl: Optional[LiveCrawl] = None + r"""Indicates which section(s) of search results to livecrawl and return full page content.""" + + livecrawl_formats: Optional[List[LiveCrawlFormats]] = None + r"""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\"]`.""" + + include_domains: Optional[List[str]] = None + r"""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. + """ + + exclude_domains: Optional[List[str]] = None + r"""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. + """ + + crawl_timeout: Optional[int] = 10 + r"""Maximum time in seconds to wait for page content when `livecrawl` is enabled. Must be between 1 and 60 seconds. Default is 10 seconds.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "count", + "freshness", + "offset", + "country", + "language", + "safesearch", + "livecrawl", + "livecrawl_formats", + "include_domains", + "exclude_domains", + "crawl_timeout", + ] + ) + 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/searchresponse.py b/src/youdotcom/models/searchresponse.py new file mode 100644 index 0000000..ee9313e --- /dev/null +++ b/src/youdotcom/models/searchresponse.py @@ -0,0 +1,68 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .newsresult import NewsResult, NewsResultTypedDict +from .searchmetadata import SearchMetadata, SearchMetadataTypedDict +from .webresult import WebResult, WebResultTypedDict +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import NotRequired, TypedDict +from youdotcom.types import BaseModel, UNSET_SENTINEL + + +class ResultsTypedDict(TypedDict): + web: NotRequired[List[WebResultTypedDict]] + news: NotRequired[List[NewsResultTypedDict]] + + +class Results(BaseModel): + web: Optional[List[WebResult]] = None + + news: Optional[List[NewsResult]] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["web", "news"]) + 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 + + +class SearchResponseTypedDict(TypedDict): + r"""A JSON object containing unified search results from web and news sources""" + + results: NotRequired[ResultsTypedDict] + metadata: NotRequired[SearchMetadataTypedDict] + + +class SearchResponse(BaseModel): + r"""A JSON object containing unified search results from web and news sources""" + + results: Optional[Results] = None + + metadata: Optional[SearchMetadata] = None + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["results", "metadata"]) + 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/security.py b/src/youdotcom/models/security.py index 7778995..2313b52 100644 --- a/src/youdotcom/models/security.py +++ b/src/youdotcom/models/security.py @@ -33,7 +33,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k) + val = serialized.get(k, serialized.get(n)) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/src/youdotcom/models/webresult.py b/src/youdotcom/models/webresult.py new file mode 100644 index 0000000..d8f6f6c --- /dev/null +++ b/src/youdotcom/models/webresult.py @@ -0,0 +1,87 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from .contents import Contents, ContentsTypedDict +from datetime import datetime +from pydantic import model_serializer +from typing import List, Optional +from typing_extensions import NotRequired, TypedDict +from youdotcom.types import BaseModel, UNSET_SENTINEL + + +class WebResultTypedDict(TypedDict): + url: NotRequired[str] + r"""The URL of the specific search result.""" + title: NotRequired[str] + r"""The title or name of the search result.""" + description: NotRequired[str] + r"""A brief description of the content of the search result.""" + snippets: NotRequired[List[str]] + r"""An array of text snippets from the search result, providing a preview of the content.""" + thumbnail_url: NotRequired[str] + r"""URL of the thumbnail.""" + page_age: NotRequired[datetime] + r"""The age of the search result.""" + contents: NotRequired[ContentsTypedDict] + r"""Contents of the page if livecrawl was enabled.""" + authors: NotRequired[List[str]] + r"""An array of authors of the search result.""" + favicon_url: NotRequired[str] + r"""The URL of the favicon of the search result's domain.""" + + +class WebResult(BaseModel): + url: Optional[str] = None + r"""The URL of the specific search result.""" + + title: Optional[str] = None + r"""The title or name of the search result.""" + + description: Optional[str] = None + r"""A brief description of the content of the search result.""" + + snippets: Optional[List[str]] = None + r"""An array of text snippets from the search result, providing a preview of the content.""" + + thumbnail_url: Optional[str] = None + r"""URL of the thumbnail.""" + + page_age: Optional[datetime] = None + r"""The age of the search result.""" + + contents: Optional[Contents] = None + r"""Contents of the page if livecrawl was enabled.""" + + authors: Optional[List[str]] = None + r"""An array of authors of the search result.""" + + favicon_url: Optional[str] = None + r"""The URL of the favicon of the search result's domain.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set( + [ + "url", + "title", + "description", + "snippets", + "thumbnail_url", + "page_age", + "contents", + "authors", + "favicon_url", + ] + ) + 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/runs.py b/src/youdotcom/runs.py index 0fc5917..feb8fcd 100644 --- a/src/youdotcom/runs.py +++ b/src/youdotcom/runs.py @@ -52,7 +52,7 @@ def create( if server_url is not None: base_url = server_url else: - base_url = self._get_url(base_url, url_variables) + base_url = models.AGENTS_RUNS_OP_SERVERS[0] if not isinstance(request, BaseModel): request = utils.unmarshal(request, models.AgentsRunsRequest) @@ -99,7 +99,7 @@ def create( ), ), request=req, - error_status_codes=["400", "401", "422", "4XX", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=True, retry_config=retry_config, ) @@ -190,7 +190,7 @@ async def create_async( if server_url is not None: base_url = server_url else: - base_url = self._get_url(base_url, url_variables) + base_url = models.AGENTS_RUNS_OP_SERVERS[0] if not isinstance(request, BaseModel): request = utils.unmarshal(request, models.AgentsRunsRequest) @@ -237,7 +237,7 @@ async def create_async( ), ), request=req, - error_status_codes=["400", "401", "422", "4XX", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), stream=True, retry_config=retry_config, ) diff --git a/src/youdotcom/sdk.py b/src/youdotcom/sdk.py index c72bef1..e0e82de 100644 --- a/src/youdotcom/sdk.py +++ b/src/youdotcom/sdk.py @@ -8,7 +8,17 @@ import httpx import importlib import sys -from typing import Any, Callable, Dict, Mapping, Optional, TYPE_CHECKING, Union, cast +from typing import ( + Any, + Callable, + Dict, + List, + Mapping, + Optional, + TYPE_CHECKING, + Union, + cast, +) import weakref from youdotcom import errors, models, utils from youdotcom._hooks import HookContext, SDKHooks @@ -29,6 +39,7 @@ class You(BaseSDK): Multi-step reasoning with comprehensive research capabilities Comprehensive API for You.com services: - **Agents API**: Execute queries using Express, Advanced, and Custom AI agents + - **Research API**: In-depth, 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 """ @@ -193,6 +204,308 @@ 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( + 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[List[models.LiveCrawlFormats]] = None, + include_domains: Optional[List[str]] = None, + exclude_domains: Optional[List[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 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 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_POST_OP_SERVERS[0] + + request = models.SearchRequestBody( + query=query, + count=count, + freshness=freshness, + offset=offset, + country=country, + language=language, + safesearch=safesearch, + livecrawl=livecrawl, + livecrawl_formats=livecrawl_formats, + include_domains=include_domains, + exclude_domains=exclude_domains, + crawl_timeout=crawl_timeout, + ) + + req = self._build_request( + method="POST", + path="/v1/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="searchPost", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + ), + 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 search_post_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[List[models.LiveCrawlFormats]] = None, + include_domains: Optional[List[str]] = None, + exclude_domains: Optional[List[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 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 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_POST_OP_SERVERS[0] + + request = models.SearchRequestBody( + query=query, + count=count, + freshness=freshness, + offset=offset, + country=country, + language=language, + safesearch=safesearch, + livecrawl=livecrawl, + livecrawl_formats=livecrawl_formats, + include_domains=include_domains, + exclude_domains=exclude_domains, + crawl_timeout=crawl_timeout, + ) + + req = self._build_request_async( + method="POST", + path="/v1/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="searchPost", + oauth2_scopes=None, + security_source=get_security_from_env( + self.sdk_configuration.security, models.Security + ), + ), + 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) + def research( self, *, @@ -278,7 +591,7 @@ def research( ), ), request=req, - error_status_codes=["401", "403", "422", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) @@ -297,9 +610,9 @@ def research( raise errors.ResearchForbiddenError(response_data, http_res) if utils.match_response(http_res, "422", "application/json"): response_data = unmarshal_json_response( - errors.UnprocessableEntityErrorData, http_res + errors.ResearchUnprocessableEntityErrorData, http_res ) - raise errors.UnprocessableEntityError(response_data, http_res) + raise errors.ResearchUnprocessableEntityError(response_data, http_res) if utils.match_response(http_res, "500", "application/json"): response_data = unmarshal_json_response( errors.ResearchInternalServerErrorData, http_res @@ -399,7 +712,7 @@ async def research_async( ), ), request=req, - error_status_codes=["401", "403", "422", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) @@ -418,9 +731,9 @@ async def research_async( raise errors.ResearchForbiddenError(response_data, http_res) if utils.match_response(http_res, "422", "application/json"): response_data = unmarshal_json_response( - errors.UnprocessableEntityErrorData, http_res + errors.ResearchUnprocessableEntityErrorData, http_res ) - raise errors.UnprocessableEntityError(response_data, http_res) + raise errors.ResearchUnprocessableEntityError(response_data, http_res) if utils.match_response(http_res, "500", "application/json"): response_data = unmarshal_json_response( errors.ResearchInternalServerErrorData, http_res diff --git a/src/youdotcom/search.py b/src/youdotcom/search.py index 355ce77..4a518bc 100644 --- a/src/youdotcom/search.py +++ b/src/youdotcom/search.py @@ -1,7 +1,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from .basesdk import BaseSDK -from typing import Any, Mapping, Optional, Union +from typing import Any, List, Mapping, Optional, Union from youdotcom import errors, models, utils from youdotcom._hooks import HookContext from youdotcom.types import OptionalNullable, UNSET @@ -13,25 +13,20 @@ class Search(BaseSDK): def unified( self, *, - query: str = "Your query", + query: str, count: Optional[int] = 10, freshness: Optional[ - Union[models.SearchFreshness, models.SearchFreshnessTypedDict] + Union[models.FreshnessValue, models.FreshnessValueTypedDict] ] = None, offset: Optional[int] = None, - country: Optional[ - Union[models.SearchCountry, models.SearchCountryTypedDict] - ] = None, + country: Optional[models.Country] = None, language: Optional[models.Language] = models.Language.EN, - safesearch: Optional[ - Union[models.SearchSafesearch, models.SearchSafesearchTypedDict] - ] = None, - livecrawl: Optional[ - Union[models.SearchLivecrawl, models.SearchLivecrawlTypedDict] - ] = None, - livecrawl_formats: Optional[ - Union[models.SearchLivecrawlFormats, models.SearchLivecrawlFormatsTypedDict] - ] = None, + safesearch: Optional[models.SafeSearch] = None, + livecrawl: Optional[models.LiveCrawl] = None, + livecrawl_formats: Optional[List[models.LiveCrawlFormats]] = None, + include_domains: Optional[str] = None, + exclude_domains: Optional[str] = None, + crawl_timeout: Optional[int] = 10, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -41,17 +36,26 @@ def unified( 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. - :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). + `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: 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 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: Indicates the format of the livecrawled 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 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 @@ -77,6 +81,9 @@ def unified( safesearch=safesearch, livecrawl=livecrawl, livecrawl_formats=livecrawl_formats, + include_domains=include_domains, + exclude_domains=exclude_domains, + crawl_timeout=crawl_timeout, ) req = self._build_request( @@ -115,7 +122,7 @@ def unified( ), ), request=req, - error_status_codes=["401", "403", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) @@ -124,19 +131,24 @@ def unified( return unmarshal_json_response(models.SearchResponse, http_res) if utils.match_response(http_res, "401", "application/json"): response_data = unmarshal_json_response( - errors.SearchUnauthorizedErrorData, http_res + errors.UnauthorizedResponseErrorData, http_res ) - raise errors.SearchUnauthorizedError(response_data, http_res) + raise errors.UnauthorizedResponseError(response_data, http_res) if utils.match_response(http_res, "403", "application/json"): response_data = unmarshal_json_response( - errors.SearchForbiddenErrorData, http_res + 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.SearchForbiddenError(response_data, http_res) + raise errors.UnprocessableEntityResponseError(response_data, http_res) if utils.match_response(http_res, "500", "application/json"): response_data = unmarshal_json_response( - errors.SearchInternalServerErrorData, http_res + errors.InternalServerErrorResponseData, http_res ) - raise errors.SearchInternalServerError(response_data, 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) @@ -149,25 +161,20 @@ def unified( async def unified_async( self, *, - query: str = "Your query", + query: str, count: Optional[int] = 10, freshness: Optional[ - Union[models.SearchFreshness, models.SearchFreshnessTypedDict] + Union[models.FreshnessValue, models.FreshnessValueTypedDict] ] = None, offset: Optional[int] = None, - country: Optional[ - Union[models.SearchCountry, models.SearchCountryTypedDict] - ] = None, + country: Optional[models.Country] = None, language: Optional[models.Language] = models.Language.EN, - safesearch: Optional[ - Union[models.SearchSafesearch, models.SearchSafesearchTypedDict] - ] = None, - livecrawl: Optional[ - Union[models.SearchLivecrawl, models.SearchLivecrawlTypedDict] - ] = None, - livecrawl_formats: Optional[ - Union[models.SearchLivecrawlFormats, models.SearchLivecrawlFormatsTypedDict] - ] = None, + safesearch: Optional[models.SafeSearch] = None, + livecrawl: Optional[models.LiveCrawl] = None, + livecrawl_formats: Optional[List[models.LiveCrawlFormats]] = None, + include_domains: Optional[str] = None, + exclude_domains: Optional[str] = None, + crawl_timeout: Optional[int] = 10, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -177,17 +184,26 @@ async def unified_async( 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. - :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). + `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: 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 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: Indicates the format of the livecrawled 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 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 @@ -213,6 +229,9 @@ async def unified_async( safesearch=safesearch, livecrawl=livecrawl, livecrawl_formats=livecrawl_formats, + include_domains=include_domains, + exclude_domains=exclude_domains, + crawl_timeout=crawl_timeout, ) req = self._build_request_async( @@ -251,7 +270,7 @@ async def unified_async( ), ), request=req, - error_status_codes=["401", "403", "4XX", "500", "5XX"], + is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c), retry_config=retry_config, ) @@ -260,19 +279,24 @@ async def unified_async( return unmarshal_json_response(models.SearchResponse, http_res) if utils.match_response(http_res, "401", "application/json"): response_data = unmarshal_json_response( - errors.SearchUnauthorizedErrorData, http_res + errors.UnauthorizedResponseErrorData, http_res ) - raise errors.SearchUnauthorizedError(response_data, http_res) + raise errors.UnauthorizedResponseError(response_data, http_res) if utils.match_response(http_res, "403", "application/json"): response_data = unmarshal_json_response( - errors.SearchForbiddenErrorData, http_res + 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.SearchForbiddenError(response_data, http_res) + raise errors.UnprocessableEntityResponseError(response_data, http_res) if utils.match_response(http_res, "500", "application/json"): response_data = unmarshal_json_response( - errors.SearchInternalServerErrorData, http_res + errors.InternalServerErrorResponseData, http_res ) - raise errors.SearchInternalServerError(response_data, 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) diff --git a/src/youdotcom/utils/eventstreaming.py b/src/youdotcom/utils/eventstreaming.py index f2052fc..3bdcd6d 100644 --- a/src/youdotcom/utils/eventstreaming.py +++ b/src/youdotcom/utils/eventstreaming.py @@ -32,9 +32,12 @@ def __init__( decoder: Callable[[str], T], sentinel: Optional[str] = None, client_ref: Optional[object] = None, + data_required: bool = True, ): self.response = response - self.generator = stream_events(response, decoder, sentinel) + self.generator = stream_events( + response, decoder, sentinel, data_required=data_required + ) self.client_ref = client_ref self._closed = False @@ -68,9 +71,12 @@ def __init__( decoder: Callable[[str], T], sentinel: Optional[str] = None, client_ref: Optional[object] = None, + data_required: bool = True, ): self.response = response - self.generator = stream_events_async(response, decoder, sentinel) + self.generator = stream_events_async( + response, decoder, sentinel, data_required=data_required + ) self.client_ref = client_ref self._closed = False @@ -116,6 +122,7 @@ async def stream_events_async( response: httpx.Response, decoder: Callable[[str], T], sentinel: Optional[str] = None, + data_required: bool = True, ) -> AsyncGenerator[T, None]: buffer = bytearray() position = 0 @@ -138,7 +145,11 @@ async def stream_events_async( block = buffer[position:i] position = i + len(seq) event, discard, event_id = _parse_event( - raw=block, decoder=decoder, sentinel=sentinel, event_id=event_id + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, ) if event is not None: yield event @@ -151,7 +162,11 @@ async def stream_events_async( position = 0 event, discard, _ = _parse_event( - raw=buffer, decoder=decoder, sentinel=sentinel, event_id=event_id + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, ) if event is not None: yield event @@ -161,6 +176,7 @@ def stream_events( response: httpx.Response, decoder: Callable[[str], T], sentinel: Optional[str] = None, + data_required: bool = True, ) -> Generator[T, None, None]: buffer = bytearray() position = 0 @@ -183,7 +199,11 @@ def stream_events( block = buffer[position:i] position = i + len(seq) event, discard, event_id = _parse_event( - raw=block, decoder=decoder, sentinel=sentinel, event_id=event_id + raw=block, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, ) if event is not None: yield event @@ -196,7 +216,11 @@ def stream_events( position = 0 event, discard, _ = _parse_event( - raw=buffer, decoder=decoder, sentinel=sentinel, event_id=event_id + raw=buffer, + decoder=decoder, + sentinel=sentinel, + event_id=event_id, + data_required=data_required, ) if event is not None: yield event @@ -208,6 +232,7 @@ def _parse_event( decoder: Callable[[str], T], sentinel: Optional[str] = None, event_id: Optional[str] = None, + data_required: bool = True, ) -> Tuple[Optional[T], bool, Optional[str]]: block = raw.decode() lines = re.split(r"\r?\n|\r", block) @@ -250,6 +275,10 @@ def _parse_event( if sentinel and data == f"{sentinel}\n": return None, True, event_id + # Skip data-less events when data is required + if not data and publish and data_required: + return None, False, event_id + if data: data = data[:-1] try: diff --git a/src/youdotcom/utils/metadata.py b/src/youdotcom/utils/metadata.py index 173b3e5..5abddd5 100644 --- a/src/youdotcom/utils/metadata.py +++ b/src/youdotcom/utils/metadata.py @@ -15,6 +15,7 @@ class SecurityMetadata: scheme_type: Optional[str] = None sub_type: Optional[str] = None field_name: Optional[str] = None + composite: bool = False def get_field_name(self, default: str) -> str: return self.field_name or default diff --git a/src/youdotcom/utils/security.py b/src/youdotcom/utils/security.py index e51915d..cd67559 100644 --- a/src/youdotcom/utils/security.py +++ b/src/youdotcom/utils/security.py @@ -19,7 +19,9 @@ import os -def get_security(security: Any) -> Tuple[Dict[str, str], Dict[str, List[str]]]: +def get_security( + security: Any, allowed_fields: Optional[List[str]] = None +) -> Tuple[Dict[str, str], Dict[str, List[str]]]: headers: Dict[str, str] = {} query_params: Dict[str, List[str]] = {} @@ -30,7 +32,14 @@ def get_security(security: Any) -> Tuple[Dict[str, str], Dict[str, List[str]]]: raise TypeError("security must be a pydantic model") sec_fields: Dict[str, FieldInfo] = security.__class__.model_fields - for name in sec_fields: + sec_field_names = ( + list(sec_fields.keys()) if allowed_fields is None else allowed_fields + ) + + for name in sec_field_names: + if name not in sec_fields: + continue + sec_field = sec_fields[name] value = getattr(security, name) @@ -52,6 +61,9 @@ def get_security(security: Any) -> Tuple[Dict[str, str], Dict[str, List[str]]]: else: _parse_security_scheme(headers, query_params, metadata, name, value) + if not metadata.composite: + return headers, query_params + return headers, query_params @@ -77,15 +89,24 @@ def _parse_security_option( raise TypeError("security option must be a pydantic model") opt_fields: Dict[str, FieldInfo] = option.__class__.model_fields + for name in opt_fields: opt_field = opt_fields[name] metadata = find_field_metadata(opt_field, SecurityMetadata) if metadata is None or not metadata.scheme: continue - _parse_security_scheme( - headers, query_params, metadata, name, getattr(option, name) - ) + + value = getattr(option, name) + if ( + metadata.scheme_type == "http" + and metadata.sub_type == "basic" + and not isinstance(value, BaseModel) + ): + _parse_basic_auth_scheme(headers, option) + return + + _parse_security_scheme(headers, query_params, metadata, name, value) def _parse_security_scheme( diff --git a/src/youdotcom/utils/serializers.py b/src/youdotcom/utils/serializers.py index 14321eb..d2149f8 100644 --- a/src/youdotcom/utils/serializers.py +++ b/src/youdotcom/utils/serializers.py @@ -17,8 +17,7 @@ def serialize_decimal(as_str: bool): def serialize(d): - # Optional[T] is a Union[T, None] - if is_union(type(d)) and type(None) in get_args(type(d)) and d is None: + if d is None: return None if isinstance(d, Unset): return d @@ -46,8 +45,7 @@ def validate_decimal(d): def serialize_float(as_str: bool): def serialize(f): - # Optional[T] is a Union[T, None] - if is_union(type(f)) and type(None) in get_args(type(f)) and f is None: + if f is None: return None if isinstance(f, Unset): return f @@ -75,8 +73,7 @@ def validate_float(f): def serialize_int(as_str: bool): def serialize(i): - # Optional[T] is a Union[T, None] - if is_union(type(i)) and type(None) in get_args(type(i)) and i is None: + if i is None: return None if isinstance(i, Unset): return i @@ -104,8 +101,7 @@ def validate_int(b): def validate_const(v): def validate(c): - # Optional[T] is a Union[T, None] - if is_union(type(c)) and type(None) in get_args(type(c)) and c is None: + if c is None: return None if v != c: diff --git a/tests/test_live.py b/tests/test_live.py index 2093180..6122d2c 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -27,8 +27,6 @@ SearchEffort, ReportVerbosity, AgentRunsBatchResponse, - ResearchEffort, - ResearchResponse, ) @@ -242,65 +240,6 @@ def test_advanced_agent_with_research(self, you_client): assert res.output is not None -class TestLiveResearch: - """Live tests for the Research API (new in 2.3.0).""" - - def test_research_basic(self, you_client): - """Test basic research query.""" - with you_client as you: - res = you.research( - input="What is the capital of France?", - research_effort=ResearchEffort.LITE, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - def test_research_deep_effort(self, you_client): - """Test research with deep effort level.""" - with you_client as you: - res = you.research( - input="Explain the tradeoffs between transformer and SSM architectures", - research_effort=ResearchEffort.DEEP, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - def test_research_exhaustive_effort(self, you_client): - """Test research with exhaustive effort level.""" - with you_client as you: - res = you.research( - input="Compare global approaches to AI regulation across the US, EU, and China", - research_effort=ResearchEffort.EXHAUSTIVE, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - def test_research_with_sources(self, you_client): - """Test research query returns sources.""" - with you_client as you: - res = you.research( - input="What are the benefits of renewable energy?", - research_effort=ResearchEffort.STANDARD, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert res.output.sources is not None - assert len(res.output.sources) > 0 - for source in res.output.sources: - assert source.url 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_research.py b/tests/test_research.py index 793a617..f7b99b6 100644 --- a/tests/test_research.py +++ b/tests/test_research.py @@ -1,21 +1,21 @@ +""" +Tests for research functionality via the Agents API. + +The standalone Research API endpoint (POST /v1/research) was removed in 2.3.1. +Research is now performed via the Agents API using AdvancedAgentRunsRequest with ResearchTool. +""" + import os -import uuid import pytest -import httpx - from tests.test_client import create_test_http_client from youdotcom import You -from youdotcom.errors import ( - ResearchForbiddenError, - ResearchInternalServerError, - ResearchUnauthorizedError, - UnprocessableEntityError, - YouDefaultError, -) from youdotcom.models import ( - ResearchEffort, - ResearchResponse, + AdvancedAgentRunsRequest, + AgentRunsBatchResponse, + ReportVerbosity, + ResearchTool, + SearchEffort, ) @@ -29,144 +29,42 @@ def api_key(): return os.getenv("YOU_API_KEY_AUTH", "test-api-key") -class TestResearchBasic: - def test_basic_research(self, server_url, api_key): - client = create_test_http_client("post_/v1/research") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - res = you.research( - input="What are the latest advances in quantum computing?", - research_effort=ResearchEffort.STANDARD, - server_url=server_url, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - def test_research_lite_effort(self, server_url, api_key): - client = create_test_http_client("post_/v1/research") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - res = you.research( - input="What is the capital of France?", - research_effort=ResearchEffort.LITE, - server_url=server_url, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - - def test_research_deep_effort(self, server_url, api_key): - client = create_test_http_client("post_/v1/research") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - res = you.research( - input="Explain the tradeoffs between transformer and SSM architectures", - research_effort=ResearchEffort.DEEP, - server_url=server_url, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - def test_research_exhaustive_effort(self, server_url, api_key): - client = create_test_http_client("post_/v1/research") +class TestResearchViaAgents: + def test_research_via_advanced_agent(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.research( - input="Compare global approaches to AI regulation across the US, EU, and China", - research_effort=ResearchEffort.EXHAUSTIVE, + res = you.agents.runs.create( + request=AdvancedAgentRunsRequest( + input="What are the latest advances in quantum computing?", + stream=False, + tools=[ResearchTool( + search_effort=SearchEffort.LOW, + report_verbosity=ReportVerbosity.MEDIUM, + )], + ), server_url=server_url, ) - assert isinstance(res, ResearchResponse) + assert isinstance(res, AgentRunsBatchResponse) assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 + assert len(res.output) > 0 - def test_research_with_sources(self, server_url, api_key): - client = create_test_http_client("post_/v1/research") + def test_research_with_high_effort(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.research( - input="What are the benefits of renewable energy?", - research_effort=ResearchEffort.STANDARD, + res = you.agents.runs.create( + request=AdvancedAgentRunsRequest( + input="Explain the tradeoffs between transformer and SSM architectures", + stream=False, + tools=[ResearchTool( + search_effort=SearchEffort.HIGH, + report_verbosity=ReportVerbosity.HIGH, + )], + ), server_url=server_url, ) - assert isinstance(res, ResearchResponse) + assert isinstance(res, AgentRunsBatchResponse) assert res.output is not None - assert res.output.sources is not None - assert len(res.output.sources) > 0 - for source in res.output.sources: - assert source.url is not None - - -class TestResearchAsync: - @pytest.mark.asyncio - async def test_basic_research_async(self, server_url, api_key): - async_client = httpx.AsyncClient( - headers={ - "x-speakeasy-test-name": "post_/v1/research", - "x-speakeasy-test-instance-id": str(uuid.uuid4()), - }, - follow_redirects=True, - ) - - async with You(server_url=server_url, async_client=async_client, api_key_auth=api_key) as you: - res = await you.research_async( - input="What are the latest advances in quantum computing?", - research_effort=ResearchEffort.STANDARD, - server_url=server_url, - ) - - assert isinstance(res, ResearchResponse) - assert res.output is not None - assert res.output.content is not None - assert len(res.output.content) > 0 - - -class TestResearchErrors: - def test_unauthorized(self, server_url): - client = create_test_http_client("post_/v1/research-unauthorized") - - with You(server_url=server_url, client=client, api_key_auth="invalid") as you: - with pytest.raises((ResearchUnauthorizedError, YouDefaultError)): - you.research( - input="test", - server_url=server_url, - ) - - def test_forbidden(self, server_url, api_key): - client = create_test_http_client("post_/v1/research-forbidden") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - with pytest.raises((ResearchForbiddenError, YouDefaultError)): - you.research( - input="test", - server_url=server_url, - ) - - def test_unprocessable_entity(self, server_url, api_key): - client = create_test_http_client("post_/v1/research-unprocessable") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - with pytest.raises((UnprocessableEntityError, YouDefaultError)): - you.research( - input="", - server_url=server_url, - ) - - def test_internal_server_error(self, server_url, api_key): - client = create_test_http_client("post_/v1/research-internal-error") - - with You(server_url=server_url, client=client, api_key_auth=api_key) as you: - with pytest.raises((ResearchInternalServerError, YouDefaultError)): - you.research( - input="test", - server_url=server_url, - ) diff --git a/uv.lock b/uv.lock index 48cdd68..a1819eb 100644 --- a/uv.lock +++ b/uv.lock @@ -456,7 +456,7 @@ wheels = [ [[package]] name = "youdotcom" -version = "2.3.0" +version = "2.3.1" source = { editable = "." } dependencies = [ { name = "httpcore" },