Python SDK 3.1.2: lazy imports, attribution header, SSE forward-compat - #45
Python SDK 3.1.2: lazy imports, attribution header, SSE forward-compat#45tyler5673 wants to merge 1 commit into
Conversation
|
|
||
| parts.append(f"client=youdotcom/{youdotcom.__version__}") | ||
|
|
||
| if app_title is not None: |
There was a problem hiding this comment.
[P1] Validate app_title and app_url before emitting X-Client-Info
build_client_info_header() appends title=/url= segments from user-controlled strings; if a caller passes ; (segment delimiter) or \r/\n (header injection, often rejected by HTTP stacks), the header grammar breaks and the request can fail at runtime. Consider validating or escaping these characters before appending.
| if app_title is not None: | |
| if app_title is not None: | |
| if any(ch in app_title for ch in ("\r", "\n", ";")): | |
| raise ValueError("app_title must not contain CR/LF or ';' (breaks X-Client-Info grammar)") | |
| parts.append(f"title={app_title}") | |
| if app_url is not None: | |
| if any(ch in app_url for ch in ("\r", "\n", ";")): | |
| raise ValueError("app_url must not contain CR/LF or ';' (breaks X-Client-Info grammar)") | |
| parts.append(f"url={app_url}") |
| from .sdk import You | ||
|
|
||
|
|
||
| __all__ = [ |
There was a problem hiding this comment.
[P1] Reconcile the root import surface with the "no breaking changes" claim
Previously youdotcom/__init__.py re-exported a broad surface via from .sdk import * and from .sdkconfiguration import *, so imports like from youdotcom import SDKConfiguration (and other non-underscore names from those modules) would resolve; this change restricts the root surface to You plus version constants only, which is a backward-incompatible change for any callers relying on those root re-exports. If this release is meant to be non-breaking, consider adding the key historical symbols to _dynamic_imports (lazy per-name mapping) or explicitly documenting/deprecating the removed root imports in release notes.
| `app_title` and `app_url` are optional. When omitted, those segments are | ||
| dropped entirely. The MCP-specific `X-MCP-Attribution` header is the MCP | ||
| server's responsibility and is never set by the SDK. | ||
| <!-- End SDK Example Usage [attribution] --> No newline at end of file |
There was a problem hiding this comment.
[P3] Add a trailing newline to USAGE.md
USAGE.md still ends without a final newline (shown in the diff as \\ No newline at end of file), which creates noisy diffs and can trip some tooling; add a newline at EOF.
| <!-- End SDK Example Usage [attribution] --> | |
| <!-- End SDK Example Usage [attribution] --> | |
Post-review polish from line-by-line review of PR #45. All changes are in docstrings, comments, or prose — no runtime impact. - README.md: wire format grammar `[<title=...>; <url=...>;]` → `[; title=<title>][; url=<url>]` to match the actual implementation (title/url are independently optional, no angle brackets, no trailing semicolon inside the bracket group) - CHANGELOG.md: "Both fixes" → "All changes" (three changes, not two); added note about the narrowed `from youdotcom import *` surface (previously re-exported names like `SDKConfiguration` remain available via submodules) - src/youdotcom/__init__.py: "tying out" → "trying out"; "youdotcom-tempora``#10" → "``youdotcom-temporal#10``" (missing 'l', broken RST backtick placement) - src/youdotcom/models/researchtaskstreamevent.py: `\"connected\"` and `\"completed\"` inside r-strings → unescaped quotes (raw strings don't process backslash-escape, so the rendered docstring showed literal backslash-quote) - src/youdotcom/utils/attribution.py: `str_to_map` reference → "the analytics parser" (str_to_map doesn't exist in this codebase; it's the Databricks analyst-side function) Verification: pytest 243 pass / 46 mock-server connection failures (unchanged from pre-fix baseline; Go mock server not on PATH). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Droid finished reviewing. Solid release-quality changes with clear intent and good unit coverage for the new behaviors. No additional high-confidence, inline-anchorable issues beyond what is already covered in existing PR comments. |
Two test-quality fixes from line-by-line review of PR #45. No behavioral change — tests still pass 14/14, full suite 243/243. 1. Remove dead `_capture()` context manager and its call in `test_search_sets_x_client_info`. The `with _capture() as (_, captured): pass` block created and discarded a mock transport + You instance for nothing; `captured` was immediately reassigned to `{}` on the next line. The actual test logic was the inline handler below. The `_capture` function was only referenced here and `test_app_title_and_url_propagate_to_wire` never used it. Removed the `from contextlib import contextmanager` import that was only needed by `_capture`. 2. Fix tautological assertion in `test_round_trip_through_grammar`: `assert parts[:2] == ["python-sdk", parts[1]]` reduced to `parts[0] == "python-sdk"` (real) and `parts[1] == parts[1]` (always true). Replaced with `assert parts[0] == "python-sdk"`; `parts[1].startswith("client=")` on the next line already covers the second half. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Droid finished @tyler5673's task —— View job Droid finished validating review comments. Release-quality changes with solid unit coverage. Only follow-up from this pass is aligning the CHANGELOG |
| The top-level `from youdotcom import *` surface is narrowed to the | ||
| documented public API (`You`, `VERSION`, `OPENAPI_DOC_VERSION`, | ||
| `USER_AGENT`). Previously re-exported names like `SDKConfiguration` | ||
| remain available via their submodules (e.g., | ||
| `from youdotcom.sdkconfiguration import SDKConfiguration`). |
There was a problem hiding this comment.
[P2] Align CHANGELOG import * claim with actual exports
The CHANGELOG says from youdotcom import * is narrowed to (You, VERSION, OPENAPI_DOC_VERSION, USER_AGENT), but src/youdotcom/__init__.py's __all__ also exports version metadata dunders like __version__, __user_agent__, __openapi_doc_version__, and __title__. Either update the CHANGELOG wording to mention the dunder exports, or trim __all__ so the release notes match the actual wildcard-import surface.
| The top-level `from youdotcom import *` surface is narrowed to the | |
| documented public API (`You`, `VERSION`, `OPENAPI_DOC_VERSION`, | |
| `USER_AGENT`). Previously re-exported names like `SDKConfiguration` | |
| remain available via their submodules (e.g., | |
| `from youdotcom.sdkconfiguration import SDKConfiguration`). | |
| The top-level `from youdotcom import *` surface is narrowed to the | |
| documented public API (`You`, `VERSION`, `OPENAPI_DOC_VERSION`, | |
| `USER_AGENT`). Version metadata dunders (e.g. `__version__`) remain | |
| available at the package root. Previously re-exported names like | |
| `SDKConfiguration` remain available via their submodules (e.g., | |
| `from youdotcom.sdkconfiguration import SDKConfiguration`). |
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
09420c2 to
157e9e9
Compare
|
Droid finished @tyler5673's task —— View job Droid finished validating review comments. No new high-confidence, inline-anchorable issues to raise as additional PR review comments beyond the existing discussions. Overall this looks release-quality with solid tests and docs. |
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
e1ca352 to
29ad4c0
Compare
|
Droid finished validating review comments. No new high-confidence, inline-anchorable issues beyond the existing PR discussions. |
29ad4c0 to
66c4066
Compare
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
66c4066 to
c161bbe
Compare
|
Droid encountered an error —— View job Droid is reviewing code and running a security check… |
c161bbe to
a62136a
Compare
|
Droid finished @tyler5673's task —— View job Droid finished validating review comments. Release-quality changes with solid tests and docs; no additional high-confidence, inline-anchorable issues beyond the existing PR discussions. |
…t, answer safesearch Four backward-compatible changes for 3.1.2: 1. Lazy root __init__ (PEP 562). import youdotcom no longer eagerly pulls 306 modules (including httpx and urllib.request), so a Temporal Workflow module that does from youdotcom.models import SearchResponse prepares cleanly under the default SandboxedWorkflowRunner. The from youdotcom import * surface is narrowed to the documented public API (You, VERSION, OPENAPI_DOC_VERSION, USER_AGENT); previously re-exported names like SDKConfiguration remain available via their submodules. 2. X-Client-Info attribution header on every outbound request. New optional You(app_title=..., app_url=...) constructor args populate the title= and url= segments. Wire format: python-sdk; client=youdotcom/<version>[; title=<title>][; url=<url>]; ua=python/<V> httpx/<V> 3. ResearchTaskStreamEvent.event accepts future SSE event names. The discriminator widens from Event to Union[Event, UnrecognizedStr], so a server-side event-name addition does not raise ResponseValidationError on the unmarshal path. Known names still resolve to Event enum members; unknown names unwrap as a str subclass that compares equal to its raw value. 4. safesearch parameter on You.answer() and You.answer_async(). The Answer API now supports the same explicit-content filtering as the Web Search API. Accepts off, moderate (default), or strict. Case-insensitive. Existing call sites are unaffected. 40 new tests (3 root-init, 13 attribution, 18 SSE event, 3 answer safesearch normalization, 2 answer safesearch mock, 1 answer safesearch live). 247 mock + 31 live = 278 total passing. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
a62136a to
e594781
Compare
|
Droid finished @tyler5673's task —— View job Validated candidate review comments for PR #45. Validated the candidate set: the only candidate is already present as an inline PR review comment, so no new review comments were submitted. Overall, the PR still looks release-quality with solid tests and docs for the added behaviors. |
|
Closing to reopen as a fresh draft PR (clearing comment history). Same branch, same changes. |
Python SDK 3.1.2 release
No breaking changes — every change is backward-compatible and additive
at the API level.
What's in this release
Fixed
Models are usable inside a Temporal Workflow.
youdotcom/__init__.pyno longer eagerly pulls 306 modules(including
httpxandurllib.request). Any Workflow file thatdoes
from youdotcom.models import SearchResponse(without aworkflow.unsafe.imports_passed_through()work-around) nowprepares cleanly under the default
SandboxedWorkflowRunner.PEP 562 module
__getattr__mirrors the public-import surfacewithout dragging transport; the
models/__init__.pylazypattern shipped in 3.0.0 is now lifted to the package root.
ResearchTaskStreamEvent.eventaccepts future SSE event names.The discriminator field widens from
EventtoUnion[Event, UnrecognizedStr], so a server-side event-nameaddition (new terminal status, retry signal, anything the SDK
does not yet enumerate) no longer raises
ResponseValidationErroron the unmarshal path. Known eventnames still resolve to the
Eventenum member; unknown namesunwrap as a
strsubclass that compares equal to its raw valueso callers branching on raw strings keep working unchanged.
Added
Attribution header
X-Client-Infoon every outbound request.New optional
You(app_title=..., app_url=...)kwargs populatetitle=/url=segments. Wire format:so the analytics layer can distinguish SDK traffic from other
sources.
safesearchparameter onYou.answer().The Answer API now supports the same explicit-content filtering
as the Web Search API. New optional
safesearchkwarg onanswer()andanswer_async()acceptsoff,moderate(default), or
strict. Case-insensitive. Existing call sitesare unaffected.
Verification
pytestmock-transport subset: 247 pass (was 208 baseline5 deselected.
mypy src/youdotcom/: clean (82 source files).pylint src/youdotcom/: 9.88/10. All new and changed filesindividually at 10.00/10; remaining warnings are pre-existing.
Tests added (40 new)
tests/test_root_init.py(3 tests) — subprocess assertion:import youdotcomdoes not loadhttpx/urllib.request.tests/test_attribution.py(13 tests across 3 classes) —wire format grammar, edge cases, MockTransport header round-trip.
tests/test_researchtaskstreamevent.py(18 tests across 3classes) — known-event round-trip, unknown-event UnrecognizedStr
equivalence, equality / membership /
isinstanceassertions.tests/test_param_normalization.py(3 new tests inTestAnswerNormalization) — safesearch case-insensitivity andomission-when-unset on the answer endpoint.
tests/test_answer.py(2 new mock tests inTestAnswerSuccess) —safesearch on the wire, safesearch omitted when not passed.
tests/test_live.py(1 new live test inTestLiveAnswer) —answer with
safesearch=SafeSearch.STRICTagainst the live API.Release artifacts
pyproject.tomlversion bumped to3.1.2src/youdotcom/_version.py__version__literal bumpeduv.lockauto-bumpedCHANGELOG.md## [3.1.2] - 2026-08-19entry addedREADME.mdnew### Attributionsubsection,safesearchin answer exampleUSAGE.md[attribution]example block addeddocs/models/answerrequestbody.mdsafesearch row addeddocs/sdks/you/README.mdanswer parameter table with safesearch