feat: implement the URL search params serialization standard - #611
Merged
Conversation
Port @seamapi/url-search-params-serializer to Python. It defines how the Seam SDKs and other API consumers serialize objects to URL search params, and the Seam API parses them with the corresponding parser. Output is byte-for-byte identical to the reference implementation: - Values are encoded with the application/x-www-form-urlencoded serializer, which differs from urllib in its treatment of "*" and "~". - Params are sorted by name, compared by UTF-16 code unit. - Floats are formatted using the ECMAScript Number::toString algorithm, which differs from repr for integral floats and around the exponent notation thresholds. The serialization defines the name and value of each param, where every value is a string, and leaves rendering the query string to URLSearchParams. UrlSearchParams is that layer here. Python has a single absence value, and the standard needs both: a param set to None is omitted, while a param set to NULL is serialized to an empty value, which the API reads as null. Nothing calls this yet. Serializing the params of a request is a separate change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A
razor-x
force-pushed
the
claude/url-search-params-serializer
branch
from
August 13, 2026 16:12
e88e338 to
d214664
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Python port of @seamapi/url-search-params-serializer, exported for callers using their own HTTP client. Nothing in the SDK calls it yet — serializing the params of a request is a separate change.
The layer
The serialization defines the name and value of each search param, where every value is a string, and leaves rendering the query string to
URLSearchParams.UrlSearchParamsis that layer here, soserialize_url_search_paramsis a thin wrapper over it, exactly as the reference implementation wrapsnew URLSearchParams().Three places Python differs from JavaScript
These are where a naive port would produce the wrong bytes:
application/x-www-form-urlencodedserializer leaves*bare and escapes~.urllib'squote_plusdoes the opposite, so the encoder is written against the byte set.URLSearchParams.sort()compares UTF-16 code units, so😀sorts before�. Python'sstrcomparison sorts by code point and gets that backwards; the sort key isname.encode("utf-16-be").repr(1.0)is"1.0"but JavaScript gives"1";repr(1e-7)is"1e-07"vs"1e-7"; the exponent-notation thresholds differ too (Python switches at 1e16, JavaScript at 1e21). This implements the ECMAScriptNumber::toStringalgorithm on top of Python's shortest-round-triprepr.Omitted params and null params
Python has one absence value and the standard needs both. Since sending null is rarely intended and unsetting a value cannot be undone,
Nonekeeps the safe meaning:NULLis the value to pass;Nullis its type, exported so a caller can annotate a wrapper.is_nullis internal — the package does not export it.Verification
Checked against the reference implementation running under Node:
All match byte-for-byte. Separately, 22 cases round-trip through the parser the API uses in strict mode, which is documented as a true inverse of the serializer.
The 54 unit tests in this PR are the readable subset of that: the ported test suite plus the Python-specific cases (naive datetimes,
Decimal-free float formatting, dict subclasses, sets rejected as non-deterministic).Checks
Rebased onto beta after #612, so this runs against the updated tooling: black 26, pylint 4, pytest 9 and mypy 2. 141 tests pass (87 already on beta, 54 added here), pylint 10.00/10, black, rstcheck, and
mypy seam testclean across 114 files.This PR is purely additive — six files, no changes to
pyproject.toml,uv.lock,seam/client.py, codegen, or the generated routes.