Release v3.0.0: Answer API, Speakeasy removal, flattened API surface - #32
Conversation
### Added - Answer API: you.answer() / you.answer_async() for POST /v1/answer. Returns synthesized markdown answers with inline citations and web results. - PaymentRequiredResponseError: first-class 402 error class for the answer API (UpgradeRequiredResponse schema). ### Changed - Flattened API surface: sub-SDK chains (you.search.unified(), you.contents.generate()) are deprecated with DeprecationWarning and delegate to new direct methods (you.search(), you.contents()). Plain strings accepted instead of enum imports for country, language, safesearch, livecrawl, freshness. - Removed all Speakeasy generated code and disclaimers. SDK is now fully hand-maintained. - Removed YDCUserAgentOverrideHook (no-op after Speakeasy removal). __user_agent__ derived from __version__ at runtime. - search_helpers module merged into you.search() direct method. - 422/500 error data models expanded with optional detail/errors fields (backward compatible). - Dev dependencies updated: mypy >=2.3.0, pylint >=4.0.0, pytest >=9.0.0, pytest-asyncio >=1.0.0. - CI: replaced pylint/pyright with mypy, excluded live tests from CI. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… empty api_key_auth opt-out - Fix broken MIGRATION.md anchor link (#250-to-260 → #250--260) - CONTRIBUTING.md: add --ignore=tests/test_performance.py to unit test command - Security: explicit empty api_key_auth now constructs Security(api_key_auth=None) instead of falling back to env vars (prevents confused-deputy footgun) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…t_live.py - Delete .agents/skills/generate-sdk-and-open-pr/SKILL.md — was a Speakeasy generation workflow, no longer relevant since SDK is hand-maintained - Remove empty .agents/ directory tree - Fix stale 'overlay-generated' comment in test_live.py Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Keyless search never shipped in a release, so referencing a revert is misleading. Renamed heading and header to describe the current state without implying users experienced keyless. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Remove 'Hand-maintained additions' section from CHANGELOG 2.4.0 (references deleted registration.py, removed YDCUserAgentOverrideHook, and Speakeasy regen workflow that no longer exists) - Remove .speakeasy/ entries from .gitignore - Fix stale 'overlay' references in examples/api-example-calls.py, MIGRATION.md, tests/test_research.py - Fix stale 'reverted overlay' comment in tests/test_research.py - Fix stale 'Speakeasy regen' comment in tests/test_security_env.py - Clean up Speakeasy reference in CHANGELOG 2.4.0 Fixed section Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The POST /v1/search endpoint was already supported in 2.5.x. The actual change is search_helpers.search() became you.search(). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…example paths - Fix SDKConfiguration: use dataclasses.field instead of pydantic.Field (stdlib @DataClass doesn't interpret pydantic.Field) - Fix tests/README.md: replace broken 'pip install -e ".[dev"]' with 'pip install -e . mypy pylint pyright pytest pytest-asyncio' - Fix PERFORMANCE_TESTING.md: document pytest-xdist requirement for -n auto - Fix examples/api-example-calls.py: venv name (.venv not venv) and run path (python examples/api-example-calls.py) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… not 'is not' is identity comparison, not equality — can silently fail for strings depending on interning, causing globals-based overrides to be skipped. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Same fix as CONTRIBUTING.md — performance tests need timing infrastructure and are slow, not suitable for CI unit test run. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Hit /_mockserver/health (returns 200) with retry loop instead of broken 'curl -sf / || echo ready' that always printed ready - Remove -x flag so CI shows all failures, not just the first one Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- scripts/check_drift.py: fetches OpenAPI specs from you.com and compares endpoints, server URLs, and enum values against the SDK surface - .github/workflows/test.yml: non-blocking drift check on every PR - .github/workflows/drift-check.yml: weekly scheduled check that opens a GitHub issue if drift is detected, closes stale issues when clean Known uncovered APIs (billing, images) and endpoints (GET /v1/search) are excluded via exception lists. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- New handler: tests/mockserver/internal/handler/pathpostv1answer.go - Register route in generated_handlers.go - Add TestAnswerMockServer class with 4 tests hitting the mock server (basic, freshness, boost_domains, async) - Existing MockTransport tests retained for error case coverage - Update tests/README.md test count (19 -> 23) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- drift-check.yml: add explicit permissions (contents: read, issues: write) so GITHUB_TOKEN can create/close issues in scheduled runs - check_drift.py: use context manager for httpx.Client to guarantee cleanup on exception paths Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Drift check (check_drift.py): - Add request parameter drift: compares spec request body properties against SDK method signatures - Add response schema drift: compares spec 200 response schema fields against SDK pydantic model fields - Fix resolution for request bodies - Fix array response schema handling (contents returns List[]) Real drift found and fixed: - ResearchResponse missing 'warnings' field (List[str]) per OpenAPI spec - Added to model and TypedDict, backward-compatible (Optional, defaults None) CI improvements (test.yml): - Add Python 3.11 and 3.13 to test matrix (was 3.10 + 3.12 only) - Add pytest-cov coverage reporting (--cov=youdotcom --cov-report=term-missing) - Add pylint errors-only check (non-blocking, continue-on-error) - Add package build check job (python -m build --sdist --wheel) Dependabot (.github/dependabot.yml): - Weekly checks for pip and github-actions dependency updates Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… drift workflow P1: You.__aexit__ now closes SDK-owned sync client too; __exit__ closes SDK-owned async client (prevents socket leaks in async-only usage) P2: Fix Security.serialize_model() optional_fields — was 'ApiKeyAuth' but actual field key is 'api_key_auth' (no alias), causing null fields to be re-introduced despite exclude_none P2: Drift workflow now distinguishes 'DRIFT DETECTED' from 'ERROR:' before opening an issue (prevents false drift issues on network failures) P2: Search docs — removed stale GET recommendation and changed UsageSnippet method from get to post P2: Document test client cleanup expectations in create_test_http_client Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
P1: Fix __exit__ async client close under running event loop — use get_running_loop() + run_coroutine_threadsafe() pattern (matching close_clients()) instead of asyncio.run() which raises RuntimeError in notebooks/async contexts P1: Clarify server_url constructor docstring — search/contents default to ydc-index.io and are not affected by the constructor server_url unless the per-method server_url argument is passed Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- CHANGELOG: Remove 'Search/Contents host' from Changed (was already the case in 2.5.0, not a new change in 2.6.0) - CHANGELOG: Reword 'search() server_url fix' to accurately state that constructor server_url does NOT affect search/contents (only per-method server_url is respected) - README: Fix error count from 27 to 28 (3 httpx + 25 YouError subclasses) - MIGRATION: Add note that server URL behavior was already the case in 2.5.0, documented for reference - tests/mockserver/README: Replace stale 'auto-generated code from Speakeasy' with 'hand-maintained', remove deleted pathgetv1search.go from handler list, add all current handler files - PR description: Remove 'Search/Contents host split' from Breaking Changes (was already the case in 2.5.0) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
FinanceResearchEffort no longer includes a LITE tier (only DEEP and EXHAUSTIVE per official You.com docs). Removed stale 'lite' bullet from docstrings in finance_researchop.py (2 places) and sdk.py finance_research/finance_research_async (2 places). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
P1: Fix drift-check.yml re-execution TOCTOU race — capture first run's
output once via GITHUB_ENV instead of re-running the script a second
time. Use distinct exit codes (0=no drift, 1=drift, 2=fetch error)
instead of grepping free-form stdout, eliminating the silent-loss
fallback where neither DRIFT DETECTED nor ERROR: appeared.
P1: Fix test_answer.py resource leaks — test_async_answer was creating
a real httpx.AsyncClient() (no MockTransport) that opened TCP sockets
to the mock server and was never closed. Switched to MockTransport
pattern (matching the rest of the file) with explicit try/finally
cleanup. Also added client.close() to the 3 sync mock-server tests
that were leaking create_test_http_client instances.
P2: Fix check_drift.py request param schema extraction — was using
bare schema.get('properties', {}).keys() while the response path used
_get_schema_properties() which handles oneOf/anyOf/allOf and arrays.
Unified to use _get_schema_properties for both paths. Also removed
redundant inline 'import re' in fetch_specs() (already at module level).
P2: Add upper bounds to pyproject.toml dev deps — mypy, pylint, pytest,
pytest-asyncio were unbounded across semver-major bumps. Pinned upper
bounds (<3, <5, <10, <2) so Dependabot can't float untested majors.
P3: Add failure sentinel to test.yml mock-server readiness loop — if
all 10 retries fail, the loop now exits 1 with a clear error message
instead of silently falling through to pytest.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Addresses the review findings on the v2.6.0 release PR.
Versioning:
- Bump 2.6.0 -> 3.0.0. The Agents API, `search_post()`, `search_helpers`,
and the sub-SDK classes are removed, which is breaking and needs a major
version under the SemVer policy the CHANGELOG claims to follow.
Behavior:
- An empty `api_key_auth` now raises ValueError at construction instead of
silently falling back to the environment. Every endpoint requires a key,
so `""` is never valid -- it means a key was expected and none arrived,
nearly always `os.getenv("YDC_API_KEY", "")` with the variable unset.
Falling back there ran the request under a different identity than the
code asked for. Callables are checked on first use.
- `search(language=None)` sends no language again. Omitting the argument
still uses the API default (EN); previously an explicit None also
resolved to EN, with no way to opt out of the filter.
- `__exit__`/`__aexit__` no longer let a failing client close replace the
exception propagating out of the block, matching `close_clients`.
- `country`/`language`/`safesearch`/`livecrawl`/`livecrawl_formats`/
`freshness` are now genuinely case-normalized, which the docs already
claimed. Also fixes the `to` separator in uppercase date ranges.
- `search_async()`/`contents_async()` are real methods with full
signatures instead of `**kwargs: Any` wrappers that erased them for type
checkers and IDEs. Clears the one pylint error in the tree.
Tests (154 -> 249):
- New coverage for debug-log redaction, empty-key rejection, dual client
close and its non-masking property, and the normalization matrix
including the deprecated `unified()` path. Redaction and the key
handling were both shipped untested.
Docs:
- Replace all 52 `os.getenv("YDC_API_KEY", "")` examples, which the new
key handling turns into an error.
- CHANGELOG gains Removed/Security/Fixed sections; drops an entry for a
file that never existed on main and two no-op entries. Security is now
redaction alone -- the key change is fail-fast, not a closed hole.
- MIGRATION gains an action-required table plus sections on key
resolution, client lifecycle, redaction, and `language`.
CI:
- Drift issues update an existing open issue instead of filing a duplicate
weekly; body written via --body-file so the code fence renders; a broken
checker exits 3 and fails the job rather than reporting "no drift"
forever; --strict and --verbose mean something again.
- Dev tooling installs from the pyproject dependency group instead of a
duplicated list. pylint gates now that the tree is clean. dependabot
switched pip -> uv so uv.lock actually gets updated.
Verified: 249 tests pass, mypy clean (80 files), pylint 10.00/10, drift
check green against the live specs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Debug logging is off by default (NoOpLogger unless YOU_DEBUG is set), so the previous wording overstated the blast radius. State the precondition, the default, and the one action that matters for anyone who was affected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses two P1 review comments on #32: two async tests built an httpx.AsyncClient, handed it to You(async_client=...), and never closed it. The SDK deliberately never closes caller-supplied transports, so those clients leaked. Both now use `async with httpx.AsyncClient(...)`. The tests added earlier in this branch had the same defect, so they are fixed too rather than leaving the pattern half-applied: - test_redaction.py, test_security_env.py: close the mock-backed client in a finally. - test_param_normalization.py: fold client ownership into a `_capture` context manager, which also removes four copies of the same handler boilerplate. - test_client_lifecycle.py: back every client with a MockTransport. The `_Boom*` clients raise on close by design and so can never be disposed of normally; with no connection pool there is nothing to leak. All four files now run clean under `-W error::ResourceWarning`. Not addressed here: 25 pre-existing unclosed clients in test_research.py and others, none of which this PR touches (confirmed against origin/main). They are the same defect class and worth a follow-up, but folding a mechanical sweep of untouched files into a release PR would obscure the release diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the two P1 review comments to their conclusion: the suite had 72 unclosed sockets, not 2. All are now closed, and CI enforces it. Where they were: test_research.py (27), test_performance.py (29), test_contents.py (12), test_research_helpers.py (4). Tests built on httpx.MockTransport have no connection pool and never leaked, which is why the count is far below the ~110 client constructions in the suite. Rather than 72 call-site edits, ownership moves to the shared factories. create_test_http_client() and create_timing_client() register what they build, and an autouse fixture in the new tests/conftest.py closes the registry after each test. Three edits cover 68 sites and every future caller. The remaining 4 were inline async clients and are now wrapped in `async with`. The guard: filterwarnings promotes ResourceWarning and PytestUnraisableExceptionWarning to errors. Both are needed — a leaked transport surfaces during finalization, which pytest reports as an unraisable exception rather than a failure. The gc.collect() in the fixture is load-bearing for the same reason: without it, finalization happens at an arbitrary later point, often after the session, where it fails nothing. Verified by deliberately leaking a socket and confirming the run errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The empty-key row read 'a callable returning one', which pointed back at the non-empty row above it and inverted the meaning. Say 'a callable returning an empty string'. Applies to README, MIGRATION, and CHANGELOG. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Test resource leaks (5 comments): - test_answer.py: converted _sync_you/_async_you helpers to context managers (@contextmanager/@asynccontextmanager) that close the httpx.Client/AsyncClient in finally. Updated all 19 call sites to use with/async with blocks. - test_direct_methods.py: same context manager conversion for _sync_you/_async_you. Updated all 8 call sites. - test_shims.py: converted _you helper to @contextmanager. Fixed 2 async tests to use try/finally with aclose(). - test_research_helpers.py: converted TestStreamResearchTypedErrors _sync_you/_async_you to context managers. Added explicit close/ aclose calls to 34 inline-client tests that were leaking. - test_research.py: already addressed (all async tests already use async with httpx.AsyncClient(...)). README SSE example (1 comment): - Replaced you.stream_research_task() example with stream_research() helper from research_helpers. The strict pydantic decoder in stream_research_task raises ResponseValidationError on undocumented intermediate event names; stream_research uses a tolerant decoder. API key resolution table (3 comments): - README/MIGRATION/CHANGELOG: already addressed — the empty-key row already says 'a callable returning an empty string', not the contradictory 'a callable returning one'. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Review summaryThe PR is a large, well-structured v3 surface refactor with drift-checking and expanded test coverage, and no high-confidence correctness or security issues stood out in review. One README warning line still contradicts the new credential-redaction behavior for debug logging. |
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Review summaryOverall the v3.0.0 refactor looks solid and well-tested. One concrete correctness bug remains in query param serialization merging, and two small hardening items are worth addressing (clearer lifecycle error when reused after close, and percent-encoding path parameters to prevent path injection). |
… path param encoding - queryparams.py: Fixed extend(value) → append(value) for serialized query params. _get_serialized_params() returns dict[str, str], so extend() was treating the string as an iterable of characters, corrupting the query param list. (P1) - basesdk.py: Added 'if client is None: raise ValueError' guard in _build_request_with_client() before calling client.build_request(). Mirrors the existing guard in do_request/do_request_async. Prevents AttributeError when reusing an SDK instance after __exit__/__aexit__. (P2) - url.py: Percent-encode path params with urllib.parse.quote(value, safe=) before substituting into URL templates. Prevents path injection when params contain /, ?, #, or ... Applied to both generate_url() and template_url(). (P2 security) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The file was still shaped by Speakeasy: 663 lines, 28 generated-section markers, a hand-maintained table of contents, and the first API call at line 139 behind four package managers, a uv shebang tutorial, and a PyCharm plugin note. Compared against the Tavily, Exa, and Parallel Python SDKs. The first two organize by capability and reach a working call within ~15 lines; Parallel is reference-shaped like ours because it is Stainless-generated. For a search product competing with the first two, capability-first is the right genre. Restructured accordingly: Install, Quickstart, one section per API, Async, Long-running research, then Authentication, Errors, and Configuration below the fold. 403 lines, first call at line 32. Corrections found while checking claims against the code: - Retries: the old text said the SDK "will fall back to the default retry strategy provided by the API". It does not retry at all unless given a RetryConfig (sdkconfiguration.retry_config defaults to UNSET). - Maturity: said breaking changes could land without a major version bump, which contradicts the SemVer policy this release is built on. Replaced with a Versioning section. - The lead example used `language=models.Language.EN`, arguing against the plain-string normalization this release added, and ended in `print(res)` dumping a whole model. - Answer, the headline 3.0.0 feature, had no example anywhere in the body. - The 28-class error list is now a status-by-endpoint table, generated from the actual raise sites in sdk.py. - The research background helpers, previously mentioned only in passing, now have their own section. Verified: every code example executed against a mock transport, every signature checked against the source, every link resolved (with a control 404 to confirm the docs host is not returning SPA shells for bad paths). No pricing figures — the plans page is linked instead. The license badge is dropped rather than carried over; see the PR comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repository had no license text committed, even though the README has
always declared MIT. Adds the canonical SPDX MIT text, verified
word-identical to the SPDX register apart from the copyright line.
Also moves pyproject off the deprecated `license = { text = ... }` table to
the PEP 639 SPDX expression plus `license-files`, so the built metadata
carries `License-Expression: MIT` rather than a free-text field — the
machine-readable form license scanners and SBOM tooling consume. Verified
the file ships in both artifacts: `youdotcom-3.0.0/LICENSE` in the sdist,
`dist-info/licenses/LICENSE` in the wheel.
Also fixes an ambiguity in the new README's auth table: the empty-key row
said "a callable returning one", which pointed back at the valid-key row
above it — the same wording the review bot flagged in MIGRATION and
CHANGELOG.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f9bab53 to
91dc4d2
Compare
The README rewrite kept the redaction note but dropped the operational guidance added in b54d003 — don't enable debug logging in production, and don't commit debug logs. Both belong: headers are redacted, bodies are not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every docs.you.com URL 301s to you.com/docs, so the short host was costing a redirect on each link. Two paths had also gone stale beyond that: - /search/search-operators -> /guides/search-operators (moved) - the docs root -> /welcome Switched to the canonical host throughout and to the current search-operators path, which now resolves in zero hops instead of two. Also covers the two generated doc pages and the SearchRequestBody docstrings, which carried the old URL into user-facing help text. Left as-is: you.com/docs and /docs/api-reference each keep one landing redirect, which is how the docs site routes its entry points. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Droid finished @tyler5673's task —— View job Review summaryOverall the v3.0.0 refactor looks solid and well-scoped. One CI-blocking issue stands out: the workflow’s dependency-group install command is missing a target, which is likely to fail before tests run. |
Replace separate 'pip install -e .' and 'pip install --group dev' with a single 'pip install --group dev -e .' so pip has the project context needed to read dependency groups from pyproject.toml. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Droid finished reviewing PR #32. Review summaryThe v3.0.0 refactor looks solid and well-tested. One small documentation issue remains: the README debug logging snippet is missing an Posted 1 inline comment. |
The debug logging snippet used logging.getLogger() without importing the logging module, causing NameError on copy-paste. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Droid finished reviewing PR #32. Review summaryNo candidate inline comments were provided for Phase 2 validation, so nothing was posted. At the current head SHA, no high-confidence, actionable correctness or security issues remain outstanding in this PR. Posted 0 inline comments. |
Prose pass over README, MIGRATION, and CHANGELOG: 75 em and en dashes removed, rewritten as real clauses rather than swapped for commas, since most were bolting an afterthought onto a finished sentence. Table cells using an em dash as a "not applicable" marker became `n/a`. Three fixes from an independent demo built solely off the README: - The Configuration examples constructed `you = You(...)` bare, with no context manager and no cleanup, while Resource Management said to use one. Since there is no public `close()`, those examples modelled a leak. All four now use `with`, and the custom-client example closes the transport it supplies. - Reworded "close it when you're done" in Resource Management, which implied a `close()` method that does not exist. - Timeouts: with no `timeout_ms`, requests inherit httpx's 5 second default (basesdk falls back to `httpx.USE_CLIENT_DEFAULT`). The answer, research, and finance_research endpoints take far longer than that, so the Quickstart as written would have raised ReadTimeout. Confirmed against a real socket server rather than MockTransport, which ignores timeout extensions. The Quickstart now sets `timeout_ms`, and the Timeouts section leads with the constraint. Every README example re-executed against a mock transport after the edits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The timeout note landed between the example and the sentence explaining it, splitting the two, and the section had two notes in different formats. Both now sit together after the code, as one blockquote. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Droid finished @tyler5673's task —— View job Droid finished reviewing PR #32. Review summaryNo candidate inline comments were provided for Phase 2 validation, so nothing was posted. At the current head SHA, no high-confidence, actionable correctness or security issues remain outstanding in this PR. Posted 0 inline comments. |
youdotcom 3.0.0
Adds the Answer API, removes the Agents API, and flattens the sub-SDK layer into direct methods on
You. The SDK is also off code generation now: hand-maintained, withscripts/check_drift.pycatching what the generator used to prevent.Major bump because of the removals. Supersedes #31, which was numbered 2.6.0.
Upgrade impact
you.agents*youdotcom<3, or callPOST /v1/agents/runsdirectlysearch_helpers,you.search_post()removedyou.search()__gen_version__,SPEAKEASY_GENERATOR_VERSIONremoved__version__api_key_authraisesos.getenv("YDC_API_KEY", "")""defaultsearch(language=None)sends no languagelanguage=NoneENEverything else in this release is additive.
you.search.unified()andyou.contents.generate()continue to work behind shims that emitDeprecationWarning.Architecture
One class, direct methods
The sub-SDK layer is gone.
Search,ContentsSDK,Agents, andRunswere eachBaseSDKsubclasses reached through a lazy-import mechanism onYou: a_sub_sdk_map, adynamic_import()helper with its own retry-on-KeyErrorloop, and__getattr__/__dir__overrides to make the attributes appear. A call spent three hops getting to the HTTP client, and the machinery defeated static analysis on the way.Every operation is now a method on
You. The dynamic import machinery is deleted outright, signatures take plain strings instead of enum imports, and domain lists are lists rather than comma-joined strings. Async is a real method with a real signature, not a**kwargsforwarder. The old spellings survive as thin deprecation shims that translate arguments and delegate.Hand-maintained, with drift detection
Dropping Speakeasy means dropping the guarantee that the SDK matches the published spec. That guarantee is worth replacing rather than losing, so
scripts/check_drift.pynow diffs endpoints, server URLs, enum values, request parameters, and response fields against the live OpenAPI documents on every PR and weekly on a schedule. Codegen enforced conformance by construction; this enforces it by observation, and reports rather than rewrites.Breaking changes
Agents API removed
you.agents(),you.agents_async(), theyou.agents.runssub-SDK shim, and all associated model and error classes are deleted. The endpoint itself is unaffected. Only SDK support is withdrawn.Empty API key is rejected at construction
Every endpoint requires a key, so
""was never a meaningful argument. It indicates a key was expected and did not arrive. Rationale for rejecting rather than ignoring is under Review notes.Callables are resolved lazily and therefore validated on first use.
Context managers release both transports
Youinstantiates a sync and an async transport. Previously__exit__closed only the sync client and__aexit__only the async one, leaking whichever the block did not use. Both now close both.Caller-supplied transports are still never closed by the SDK.
search(language=...)you.search(query=q)ENENyou.search(query=q, language="fr")FRFRyou.search(query=q, language=None)ENThere was previously no way to issue an unfiltered query.
Added
Answer API
POST /v1/answeronapi.you.com, sync and async, withfreshness,country,language, and the three domain controls. Full error coverage including 402.PaymentRequiredResponseErrorFirst-class 402 handling with the quota fields parsed out:
upgrade_url,limit,used,period,reset_at.Case-insensitive enum parameters
country,language,safesearch,livecrawl,livecrawl_formats, andfreshnessaccept plain strings in any case and normalize to the casing the API expects. Enum members continue to work. This behavior was already documented; it is now implemented. The same path corrects the separator in uppercase date ranges (2026-01-01TO2026-02-01becomes...to...).Expanded 422/500 models
UnprocessableEntityResponseErrorDatagainsdetail(FastAPI) anderrors(JSON:API) alongsideerror;InternalServerErrorResponseDatagainserrors. All observed response shapes now deserialize. Additive.Drift detection
scripts/check_drift.py, described under Architecture. Non-blocking on every PR, scheduled weekly, and it files or updates a single tracking issue when it finds something.Security
Debug logging emitted the API key in plaintext.
Authorization,X-API-Key,Cookie, andSet-Cookieare now redacted before request and response headers reach the logger, on both transports.Debug logging is off by default, since
get_default_logger()returns aNoOpLoggerunlessYOU_DEBUGis set, so a default configuration never wrote these values anywhere. The exposure was limited to callers who deliberately enabled debug output, either throughYOU_DEBUGor by passing their owndebug_logger. For those callers the key appeared in plaintext in whatever sink the logger was wired to. If that describes your deployment and those logs left the host, rotate the key.Fixed
__exit__propagated in place of whatever exception was leaving thewithblock, so the user saw a teardown error instead of their own. Now guarded, consistent withclose_clients.SDKConfiguration.retry_confighad a non-functional default. The field usedpydantic.Field(default_factory=...)on a stdlib@dataclass, which does not interpretFieldInfoand left the descriptor itself as the default. Nowdataclasses.field.Security.serialize_modelnever matched its own field.optional_fieldscontained"ApiKeyAuth"against a field namedapi_key_auth, so aNonekey was serialized rather than omitted._populate_from_globalscompared strings by identity.is notin place of!=, correct only while the strings happen to be interned.search_async()andcontents_async()were**kwargs: Anywrappers, erasing their signatures for type checkers and IDEs. They are now the implementations, with explicit parameters. This also resolved the one pylint error in the tree.filterwarningsplus agc.collect()in teardown now fail the build on a regression.Internal
Speakeasy removal is complete: all generation banners,
YDCUserAgentOverrideHook,_hooks/registration.py,overlays/python_overlay.yaml, and the.speakeasy/gitignore entries.__user_agent__derives from the resolved__version__. Custom user agents set viasdk_configuration.user_agentcontinue to be honored.Dead code removal takes mypy's footprint from 105 source files to 80: agent models and errors, 36 orphaned doc files, and Go handlers for agents and
GET /v1/search.The README is rewritten for a hand-maintained SDK, 663 lines down to 403, organized by capability rather than by mechanism. A
LICENSEfile is committed for the first time.CI
/_mockserver/healthand fails the job on timeout, instead of proceeding into misleading test failures.pyrightis declared in the group but was never installed or run in CI.continue-on-error.--enable=Ecovers the error tier, undefined names and bad call signatures and unreachable code, which is the same class mypy already gates on, and a step that cannot fail cannot enforce it. The check pays for itself here: the untyped-async defect under Fixed surfaced as E1125.pipecosystem touv, which is what actually maintainsuv.lock.concurrencyadded.Verification
New coverage targets what previously shipped untested: header redaction, empty-key rejection, dual-transport teardown and its exception-masking property, and the normalization and
languagematrices including the deprecated shim path.Every README code example was executed against a mock transport rather than eyeballed, and every documentation link was resolved with a control request to confirm the docs host returns 404 for bad paths rather than an SPA shell.
Review notes
Three items are judgment calls rather than mechanical fixes, and are the ones worth scrutiny:
""as an opt-out, was considered and rejected: no endpoint supports unauthenticated access, so the only reachable outcome was a 401, and an immediateValueErrornames the defect better than a delayed HTTP error.<5pin bounds new checks to a dependabot PR rather than an unrelated feature branch.