Skip to content

Python SDK 2.3.0#7

Merged
tyler5673 merged 12 commits intomainfrom
release/2.3.0
Mar 4, 2026
Merged

Python SDK 2.3.0#7
tyler5673 merged 12 commits intomainfrom
release/2.3.0

Conversation

@tyler5673
Copy link
Contributor

Summary

  • Release version 2.3.0 of the youdotcom Python SDK
  • Adds new Research API with research() and research_async() methods for comprehensive, multi-step research answers with citations
  • Related Linear ticket: DX-231

Changes

  • Research API: New you.research() and you.research_async() methods with ResearchEffort levels (lite, standard, deep, exhaustive)
  • Research models: ResearchRequest, ResearchResponse, Output, Source, ContentType, ResearchEffort
  • Research errors: ResearchUnauthorizedError, ResearchForbiddenError, ResearchInternalServerError, UnprocessableEntityError
  • Server URL: Default changed from https://ydc-index.io to https://api.you.com
  • Python version: Minimum bumped from 3.9.2 to 3.10
  • Search API: count parameter now defaults to 10
  • Contents API: crawl_timeout type changed from float to int, default is now 10s
  • Speakeasy: Updated generator from v2.801.2 to v2.845.12

Checklist

  • Speakeasy generation ran successfully
  • Version updated in pyproject.toml, gen.yaml, and _version.py
  • CHANGELOG.md updated
  • MIGRATION.md updated (if breaking changes) — N/A for minor release
  • README.md and USAGE.md verified
  • Tests updated and passing (32/32 unit tests pass)
  • Integration tests (test_live.py) — requires YOU_API_KEY_AUTH

@tyler5673
Copy link
Contributor Author

@claude review this please (testing integrtion)

@tyler5673 tyler5673 requested a review from kevmalek February 27, 2026 20:56
@tyler5673
Copy link
Contributor Author

@claude try to review again pls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tyler5673 and others added 2 commits February 27, 2026 17:50
- fix README streaming example indentation (code was outside `with` block)
- fix README retries example missing `retries=` keyword argument
- fix CHANGELOG error rename table (PostV1AgentsRunsForbiddenError was incorrectly mapped to AgentRuns422ResponseError)
- add missing AgentRuns400ResponseError to CHANGELOG
- add test_research.py with mock server tests for the Research API
- narrow overly broad pytest.raises(Exception) in test_runs.py
- add pytest and pytest-asyncio to dev dependencies in pyproject.toml
- add Research API example to interactive examples menu
- fix misleading "DO NOT EDIT" banner in test_client.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- fix streaming example `with response as AgentRunsStreamingResponse` shadowing
  the class name; use `with response as stream` and iterate over `stream`
  (README.md + examples/api-example-calls.py)
- fix MIGRATION.md quick reference: old enum was `Format`, not `ContentsFormat`
- add `or 'Untitled'` fallback for `source.title` (Optional[str]) in examples
- narrow test_runs.py test_forbidden to only catch YouDefaultError (not 422)
- fix test_research.py async test to use httpx.AsyncClient instead of sync client
- add test_runs.py test_bad_request for AgentRuns400ResponseError coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
res = await you.research_async(
input="What are the latest advances in quantum computing?",
research_effort=ResearchEffort.STANDARD,
server_url=server_url,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[TEST] Async test omits content length assertion that sync test includes

The sync test_basic_research (line 46) asserts len(res.output.content) > 0. This async test only checks is not None, so a response with an empty string would pass async but fail sync.

Suggested change
server_url=server_url,
assert res.output.content is not None
assert len(res.output.content) > 0

@tyler5673
Copy link
Contributor Author

@claude open a PR against this branch with all of your recommended changes

@claude
Copy link

claude bot commented Mar 3, 2026

Claude finished @tyler5673's task in 5m 58s —— View job


Review fixes ready for PR

All file edits are committed and pushed to branch fix/claude-review-2.3.0. The PR creation step requires user approval — please click the link below to open it on GitHub.

→ Create PR: fix/claude-review-2.3.0 → release/2.3.0


Changes in the branch (commit 69f6fc0)

Bug fixes

File Fix
src/youdotcom/utils/retries.py Inverted retry_connection_errors logic — when True the flag now retries network/timeout errors; when False raises PermanentError immediately (sync + async)
src/youdotcom/utils/retries.py On max_elapsed_time exceeded, raise instead of returning a non-2xx httpx.Response as if it were success
src/youdotcom/utils/security.py Add missing f prefix to three ValueError messages so actual sub_type/scheme_type values appear in errors
src/youdotcom/sdkconfiguration.py Replace pydantic.Field(default_factory=...) with dataclasses.field(default_factory=...) so retry_config initializes to UNSET correctly

Documentation

File Fix
README.md Add missing imports to streaming SSE example (ExpressAgentRunsRequest, WebSearchTool, eventstreaming, all ResponseXxx event types)
README.md Fix run_time_ms label: "seconds""ms"
MIGRATION.md Add 2.3.0 breaking-changes section: Python ≥3.10, Search.count default 10, crawl_timeout type int

Tests

File Fix
tests/test_research.py Replace if res.output.sources: guard with explicit assert is not None + assert len > 0
tests/test_research.py Add assert len(res.output.content) > 0 to async test to match sync test

Already fixed in prior commits (skipped): README streaming indentation, retries RetryConfig positional arg, CHANGELOG error rename table, test_runs.py broad pytest.raises, test deps in pyproject.toml.
|

github-actions bot and others added 3 commits March 3, 2026 20:35
- README.md: add missing imports to streaming SSE example
  (ExpressAgentRunsRequest, WebSearchTool, eventstreaming, all
  ResponseXxx event types were used but not imported)
- README.md: fix run_time_ms unit label ("seconds" → "ms")
- retries.py: fix inverted retry_connection_errors flag logic (sync+async)
  When True the flag now retries; when False it raises PermanentError
- retries.py: raise instead of returning error response on max_elapsed_time
  exceeded so callers are not silently handed a non-2xx httpx.Response
- security.py: add missing f-prefix to three non-interpolating ValueError
  messages so the actual sub_type/scheme_type values appear in errors
- sdkconfiguration.py: replace pydantic.Field with dataclasses.field so
  retry_config default_factory is evaluated correctly at dataclass init
- MIGRATION.md: add 2.3.0 breaking-changes section covering Python ≥3.10
  requirement, Search.count new default of 10, and crawl_timeout int type
- tests/test_research.py: replace misleading if-guard on required
  sources field with explicit assert; add len > 0 check to async test

Co-authored-by: Tyler Eastman <tyler5673@users.noreply.github.com>
Reverts changes to retries.py, security.py, and sdkconfiguration.py.
These files are marked "DO NOT EDIT" and tracked in .speakeasy/gen.lock,
so any patches here get silently overwritten on the next speakeasy
generate run. The fixes should be reported upstream to Speakeasy instead.

Specifics:
- retries.py conditional restructure was a no-op (logically equivalent)
- retries.py error-swallowing removal is a real but edge-case change
- security.py f-string prefixes only affect error-path messages
- sdkconfiguration.py pydantic.Field vs dataclasses.field has not
  caused reported issues in practice

Keeps the non-generated fixes: README.md imports/label, MIGRATION.md
2.3.0 section, and test_research.py assertion improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: apply review-recommended fixes for 2.3.0
assert res.output.content is not None
assert len(res.output.content) > 0

def test_research_lite_effort(self, server_url, api_key):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing coverage for DEEP and EXHAUSTIVE effort levels: The CHANGELOG advertises four effort levels (lite, standard, deep, exhaustive), but only LITE and STANDARD are exercised here. Add at minimum a parameterized test (or two extra cases) for ResearchEffort.DEEP and ResearchEffort.EXHAUSTIVE so regressions in those enum values or their serialisation are caught.


class TestResearchAsync:
@pytest.mark.asyncio
async def test_basic_research_async(self, server_url, api_key):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async error paths not tested: TestResearchAsync only covers the happy path. The four error cases tested synchronously in TestResearchErrors (401, 403, 422, 500) have no async counterparts. Since research_async has its own error-handling code path in sdk.py, those branches can regress independently. Consider adding async error tests, or at minimum add a comment explaining the deliberate omission.

@claude
Copy link

claude bot commented Mar 3, 2026

Code Review — SDK 2.3.0

Two inline comments on tests/test_research.py (missing effort-level coverage at line 48, missing async error tests at line 81) were posted successfully. The items below cover the rest of the review; I wasn't able to post them as inline comments due to GitHub diff-resolution constraints.


🔴 Must Fix

1. Pre-compiled binary committed to git (tests/mockserver/mockserver, 12 MB)
A platform-specific ELF binary is committed and not gitignored. Problems:

  • Cannot be code-reviewed; provenance can only be verified by rebuilding from source.
  • Will fail on macOS/Windows developer machines and arm64 CI runners.
  • Adds 12 MB per rebuild to git history.
  • Neither run_tests.sh nor the Makefile use this binary — both default to go run . or Docker.

Fix: add mockserver to tests/mockserver/.gitignore.


2. README license badge says MIT; pyproject.toml says Apache-2.0 (README.md line 12)

<img src="https://img.shields.io/badge/License-MIT-blue.svg" />

pyproject.toml has license = { text = "Apache-2.0" }. Update the badge URL to Apache--2.0.


3. Verbosity listed as "Removed" in CHANGELOG but still publicly exported (CHANGELOG.md line 372)
src/youdotcom/models/__init__.py includes "Verbosity" in both __all__ and _dynamic_imports (mapping to .verbosity). from youdotcom.models import Verbosity succeeds at runtime, which contradicts the CHANGELOG entry. Either:

  • Remove it from __all__ / _dynamic_imports (true removal), or
  • Document it as # deprecated alias kept for backward compatibility and call it out in the CHANGELOG under "Deprecated" rather than "Removed".

🟡 Should Fix

4. Dead assertion in live test (tests/test_live.py line 138–154)
has_any_contents is accumulated inside the loop but never asserted. The comment says "we expect at least some results to have contents" yet the test always passes. Add assert has_any_contents or remove the dead variable.

5. Trivially weak streaming assertion (tests/test_runs.py line 65–67)

assert res is not None

assert res is not None can never fail for a call that either returns a value or raises. Per the comment, the mock returns a AgentRunsBatchResponse for streaming requests; assert that explicitly (assert isinstance(res, AgentRunsBatchResponse)) for meaningful coverage.

6. Ambiguous empty-URL test (tests/test_contents.py line 165–175)
urls=[] is sent and the test only asserts isinstance(res, list). This passes regardless of what the server returns. Document the contract: if empty lists are invalid input, expect a ValidationError; if valid, assert len(res) == 0.

7. Pydantic lower bound is tighter than necessary (pyproject.toml line 11)
pydantic>=2.11.2 (released March 2025) may cause resolver conflicts for users who also depend on packages that cap pydantic below 2.11. Unless a feature introduced in 2.11.x is explicitly required, consider >=2.0.0 or document why 2.11.2 is the minimum.


🔵 Nice to Have

8. Missing test coverage for DEEP and EXHAUSTIVE research efforts (tests/test_research.py line 48 — already posted as inline comment)
The CHANGELOG advertises four effort levels; only LITE and STANDARD are exercised. Add at least parameterised tests for ResearchEffort.DEEP and ResearchEffort.EXHAUSTIVE so enum serialisation regressions are caught.

9. Async error paths not tested (tests/test_research.py line 81 — already posted as inline comment)
TestResearchAsync only has a happy-path test. The four error cases (401, 403, 422, 500) only run through the sync code path; research_async has its own error-handling branch in sdk.py that can regress independently.


✅ What looks good

  • CHANGELOG and MIGRATION.md are thorough and accurate; the 1.x → 2.0 and 2.0 → 2.3.0 breaking-change descriptions match the actual code.
  • The overlays/python_overlay.yaml is minimal and correct — only renames endpoints to user-friendly method names, no unnecessary modifications.
  • Test structure (TestXxx classes, shared fixtures, mock-server pattern) is clean and consistent across all test files.
  • Research API tests (test_research.py) correctly validate the response shape including output.content, output.sources, and per-source url presence.
  • The revert of Speakeasy-generated file patches (retries.py, security.py, sdkconfiguration.py) was the right call — editing files marked "DO NOT EDIT" that are tracked in gen.lock would be silently overwritten on the next speakeasy generate run.
  • No hardcoded secrets detected; API key is read from the environment in all test files.

tyler5673 and others added 5 commits March 3, 2026 17:12
the 12MB platform-specific binary can't be code-reviewed, fails on
other platforms, and bloats git history. added it to .gitignore so
it stays out after local builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
exercises ResearchEffort.DEEP and ResearchEffort.EXHAUSTIVE so all
four advertised effort levels have dedicated test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adds pathpostv1research.go with success + error handlers so the
research tests can run against the local mock server. covers all
test scenarios: success, unauthorized, forbidden, unprocessable,
and internal server error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
the test_bad_request test was sending x-speakeasy-test-name
"post_/v1/agents/runs-bad-request" but the mock server had no case
for it, so it fell through to the default 200 success handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude bot Mar 4, 2026
@claude
Copy link

claude bot commented Mar 4, 2026

PR Review — Python SDK 2.3.0

Overall this is a clean, well-structured release. The Research API implementation looks correct, CHANGELOG and MIGRATION docs are thorough, and the test count is solid (43 tests across the manually-maintained suite). Several issues need attention before merge.


🔴 Must Fix

1. License mismatch (README + pyproject.toml + missing LICENSE file)
pyproject.toml declares Apache-2.0 but the README badge links to MIT (https://opensource.org/licenses/MIT) and there is no LICENSE file in the repo at all. PyPI will surface whatever pyproject.toml says; the README and badge must agree, and a LICENSE file should be present. See inline comment on pyproject.toml:7.

2. pydantic >=2.11.2 minimum is very recent
The lock file pins Pydantic at 2.12.5, but the declared lower bound of >=2.11.2 is only ~4 months old (Nov 2025). Most production Python environments will have older Pydantic 2.x versions. The actual minimum should be verified — if the code only uses standard Pydantic v2 features, >=2.0 or >=2.5 is more practical. If newer features are required, document which ones and why.


🟡 Should Fix

3. Missing asyncio_mode in pytest config
pytest-asyncio >= 0.21 emits a deprecation warning when asyncio_mode is not set, and future releases will make it a hard failure. Add asyncio_mode = "auto" to [tool.pytest.ini_options]. See inline comment on pyproject.toml:21.

4. Missing test for crawl_timeout float rejection
MIGRATION.md explicitly promises that passing a float to crawl_timeout "will now raise a validation error," but test_contents.py has no test for this. The migration guide makes a contract the test suite doesn't verify. See inline comment on MIGRATION.md:32.

5. Overly broad error assertions in research error tests
pytest.raises((ResearchUnauthorizedError, YouDefaultError)) means the test passes even if the mock server silently falls back to a generic 500. Each error test should assert the specific error type so mock server handler gaps are caught. See inline comment on test_research.py:138.

6. Async test builds its own httpx.AsyncClient inline
TestResearchAsync.test_basic_research_async manually constructs AsyncClient with test headers rather than using a shared helper. As more async tests are added, this pattern will drift. Add create_test_async_http_client() to test_client.py. See inline comment on test_research.py:112.


🔵 Nits / Low Priority

7. Redundant server_url per method call
Every test passes server_url=server_url to both You(server_url=...) (SDK-wide) and the individual method call. The SDK-level setting already covers this. Removing the per-call argument would reduce noise. See inline comment on test_research.py:36.

8. test_empty_input is in TestRunsErrors but succeeds
test_empty_input expects res is not None (not an error), but lives inside the TestRunsErrors class. Move it to TestExpressAgent or add an expected error assertion. See inline comment on test_runs.py:244 (couldn't resolve diff line — noting here).

9. Weak streaming assertion in test_streaming
assert res is not None with a comment "Mock server returns batch response even for streaming" doesn't verify anything meaningful. Assert isinstance(res, AgentRunsBatchResponse) at minimum, and add a TODO for real SSE coverage.

10. ydc-index.io compatibility claim in CHANGELOG
The note "no action needed as both resolve to the same API" is a durability risk — if ydc-index.io is decommissioned, users who relied on this note will break. Soften the language. See inline comment on CHANGELOG.md:35.

11. ResearchInput / Input2 exported from public API
models/__init__.py exports ResearchInput, ResearchInputTypedDict, Input2, and Input2TypedDict. These are empty/internal models used only in error validation. Exporting them in the public API creates confusion. Consider marking them private or removing them from __all__.

12. /v1/research not in the overlay
The overlay only covers /v1/search, /v1/contents, and /v1/agents/runs. The research endpoint has no tag in the OpenAPI spec (so it lands on the root You SDK object, which is correct), but also no x-speakeasy-name-override in the overlay. This is likely fine since operationId: research already produces the right name — just worth confirming the generated name is intentional.


✅ Looks Good

  • New research() / research_async() implementation is clean, mirrors the pattern of other SDK methods correctly.
  • All four ResearchEffort enum levels are tested.
  • Error classes (ResearchUnauthorizedError, ResearchForbiddenError, ResearchInternalServerError, UnprocessableEntityError) are correctly defined, exported, and tested.
  • MIGRATION.md covers all three breaking changes in 2.3.0 (Python version, count default, crawl_timeout type) with before/after examples.
  • No hardcoded secrets or injection vulnerabilities found.
  • crawl_timeout correctly typed as Optional[int] with default 10 in contents_sdk.py.
  • count default of 10 correctly set in search.py.

runs pytest against the mock server on PRs and pushes to main.
builds the go mock server, starts it in the background, then runs
the full test suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tyler5673 tyler5673 merged commit 63fb6fa into main Mar 4, 2026
4 checks passed
@tyler5673 tyler5673 deleted the release/2.3.0 branch March 4, 2026 01:40
# Migration Guide: 1.x to 2.0
# Migration Guide

## 1.x → 2.3.0 (Latest)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading section title. The section below covers only the 2.x → 2.3.0 upgrade path (Python version, count default, crawl_timeout type). Labelling it "1.x → 2.3.0" will send 1.x users here and they won't find the 1.x-specific guidance until the separate "1.x to 2.0" section.

Suggested change
## 1.x → 2.3.0 (Latest)
## 2.x → 2.3.0 (Latest)


assert isinstance(res, ResearchResponse)
assert res.output is not None
assert res.output.sources is not None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant is not None check. Output.sources is typed as List[Source] (non-optional), so it can never be None. The guard will always pass, silently masking any serialization error that returns None at runtime.

Drop this line and keep only the len > 0 assertion:

Suggested change
assert res.output.sources is not None
assert res.output.sources is not None
assert len(res.output.sources) > 0

            assert len(res.output.sources) > 0

class TestResearchAsync:
@pytest.mark.asyncio
async def test_basic_research_async(self, server_url, api_key):
async_client = httpx.AsyncClient(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async tests only cover the happy path. TestResearchErrors has no async variants — the 401/403/422/500 error paths are only exercised synchronously. At minimum the unauthorized case should be duplicated as an async test, since the async and sync code paths are entirely separate in sdk.py (distinct do_request vs do_request_async calls).

Also note: this client does not set follow_redirects=True, inconsistent with how the sync tests use create_test_http_client (which also doesn't set it) but different from the default httpx.Client(follow_redirects=True) the You constructor creates when no client is supplied. Not a blocker against mock servers, but worth a comment.


### Changed

- **Default server URL**: Changed from `https://ydc-index.io` to `https://api.you.com`. If you were relying on the default, no action needed as both resolve to the same API.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording is inaccurate for Search and Contents endpoints.

The code in src/youdotcom/models/searchop.py and src/youdotcom/models/contentsop.py still has per-operation server lists that hardcode https://ydc-index.io:

SEARCH_OP_SERVERS = ["https://ydc-index.io"]
CONTENTS_OP_SERVERS = ["https://ydc-index.io"]

search.unified() and contents.generate() call models.SEARCH_OP_SERVERS[0] / models.CONTENTS_OP_SERVERS[0] directly, so those endpoints still use ydc-index.io by default unless the caller passes server_url=. The Agents and Research APIs do use https://api.you.com (global config). Consider clarifying: "The default base URL for the Agents and Research APIs changed from …" or update the per-operation server lists as well.

@claude
Copy link

claude bot commented Mar 4, 2026

Review Summary — Python SDK 2.3.0

Overall this is a solid release. The new Research API is well-structured, error types are consistent with the existing pattern, and the unit-test layout is clean. A few issues need attention before merging, with the CHANGELOG wording being the most user-facing.


Must Fix

1. CHANGELOG.md line 35 — 'Default server URL changed' is inaccurate for Search and Contents

The CHANGELOG says the default changed from https://ydc-index.io to https://api.you.com, but search.unified() and contents.generate() still resolve their base URL from SEARCH_OP_SERVERS[0] / CONTENTS_OP_SERVERS[0], both of which remain https://ydc-index.io. Only the Agents and Research APIs use the new global default. The caveat ('both resolve to the same API') is accurate but the framing is misleading. Narrow the scope:

'The default base URL for the Agents and Research APIs changed from https://ydc-index.io to https://api.you.com.'

2. test_live.py test_search_with_livecrawl_all — dead assertion

has_any_contents is accumulated but never asserted. The test always passes regardless of actual results. Either add assert has_any_contents or delete the block entirely with a comment explaining the omission (see inline comment).

3. MIGRATION.md line 3 — wrong version range in header

Header reads '1.x → 2.3.0 (Latest)' but the section exclusively covers 2.x → 2.3.0 changes. 1.x users directed here will miss the relevant context. Should be '2.x → 2.3.0' (see inline comment).


Should Fix

4. test_research.py line 103 — redundant is not None guard

Output.sources is typed as List[Source] (non-optional), so the assertion is always True and masks any deserialization bug. Keep only assert len(res.output.sources) > 0.

5. test_research.py — async error path coverage is absent

TestResearchAsync has one test (happy path only). The research_async path in sdk.py is completely separate code from the sync path (_build_request_async / do_request_async). At minimum the 401 case should have an async counterpart.

6. PR checklist inconsistency

The checklist marks 'MIGRATION.md updated' as N/A for minor release, yet MIGRATION.md was updated and documents three breaking changes in 2.3.0 (Python 3.10, count default, crawl_timeout type). Update the checkbox to [x].


Informational / Pre-existing

7. pyproject.tomlpydantic >=2.11.2 is tight (not changed in this PR)

2.11.x released early 2025; many projects pin to >=2.0. Unless a specific 2.11 API is required, consider loosening to >=2.6 to reduce install friction.

8. overlays/python_overlay.yaml — no /v1/research entry (not changed in this PR)

All three existing endpoints have x-speakeasy-name-override entries; research doesn't. The auto-generated names (research / research_async) are fine — just confirm this is intentional.

9. test_client.pyfollow_redirects not set on sync test client (pre-existing, minor)

The sync helper omits follow_redirects=True while the async client in test_research.py sets it. Not a blocker against the mock server, but worth aligning for consistency.


Security

No hardcoded secrets, injection vectors, or authentication bypasses found. API key propagation via the Security model is consistent throughout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant