Skip to content

Type check the whole project instead of seam/resources with error codes disabled - #608

Merged
razor-x merged 5 commits into
betafrom
claude/mypy-flags-generated-code-ahxwd7
Aug 13, 2026
Merged

Type check the whole project instead of seam/resources with error codes disabled#608
razor-x merged 5 commits into
betafrom
claude/mypy-flags-generated-code-ahxwd7

Conversation

@razor-x

@razor-x razor-x commented Aug 13, 2026

Copy link
Copy Markdown
Member

Why

The mypy step checked seam/resources with two error codes disabled:

uv run mypy seam/resources --disable-error-code=arg-type --disable-error-code=import-not-found

That covered 34 of the 94 files in the package and left every hand-written module unchecked. One of them was broken in a way that affected every downstream user: seam/__init__.py carried a blanket # type: ignore, so despite the package shipping py.typed, the documented import failed for anyone type checking their own code.

$ cat app.py
from seam import Seam
$ mypy app.py
app.py:1: error: Module "seam" has no attribute "Seam"  [attr-defined]

Nothing in this repository could catch that, because the file was never checked.

What changed

just lint now runs uv run mypy seam test with nothing disabled. Getting there meant fixing what the flags were hiding — 2150 errors across 76 files, from four root causes:

Root cause Errors Fix
Generated from_dict reads every field with dict.get, so fields the payload omits arrive as None behind types that rule None out 1720 Optional field types + from_dict takes the decoded payload as Any
Generated json_payload = {} had its type inferred from whichever parameter was written first 288 Annotate as Dict[str, Any]
SeamHttpClient overrides request to return the decoded body, but post/get kept the inherited Response return type 120 Override both verb helpers
Optional handling in the hand-written modules ~22 Individual fixes

--disable-error-code=import-not-found is dropped outright. It suppressed nothing — it was only needed because mypy was pointed at a subtree; checking the package resolves those imports.

Bugs this surfaced

  • seam/__init__.py hid the package from type checkers, breaking from seam import Seam for downstream users (above).
  • SeamPaginator.flatten_to_list read pagination.has_next_page without the None guard its sibling flatten already had, so a page whose response carried no pagination raised AttributeError instead of ending the loop.
  • SeamHttpClient._handle_response compared response.status_code without accounting for it being unset, which would raise TypeError rather than handling the response.
  • SeamActionAttemptFailedError read action_attempt.error.message unguarded, which would raise AttributeError over the actual failure.
  • SeamActionAttemptTimeoutError documented its timeout as str while every caller passes a float.
  • HTTP exceptions typed request_id as str, though it comes from a response header that may be absent.

Note on the generated Optional types

This is the breaking part, which is why it is aimed at beta.

Resource fields that the API may omit or send as null are now typed Optional. Runtime behavior is unchanged — those fields could already be None, the annotations just denied it — but code that reads them under a type checker may now need a None check.

Nested objects are Optional regardless of what the schema says. The schema is not a reliable guide here: an action attempt documents both error and result as required, yet a pending one carries neither. Constructing them unconditionally would fail on those payloads, so from_dict keeps its None fallback and the field admits it.

No field gains a default, so the dataclass constructors are unchanged.

Two invariants are beyond what the checker can follow and are ignored in place with a comment: the auth option guards return True only once the values they check are set, and Seam borrows Routes.__init__ to attach the route namespaces even though the two are siblings under AbstractRoutes rather than parent and child.

Verification

  • mypy seam test — clean, 109 files, no disabled error codes
  • pylint ./seam ./test — 10.00/10
  • black --check ., rstcheck README.rst — clean
  • pytest --cov=./seam — 83 passed
  • tsc, eslint, prettier --check — clean for the codegen changes
  • Generated code was produced by npm run generate, not edited by hand

Generated by Claude Code

claude added 5 commits August 13, 2026 03:47
seam/__init__.py carried a blanket "# type: ignore", which hid the module
from type checkers entirely. Since the package also ships py.typed, every
downstream project that type checks its own code saw the documented import
fail:

    app.py:1: error: Module "seam" has no attribute "Seam"  [attr-defined]

The lint step only pointed mypy at seam/resources, so nothing in this
repository could catch it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8duZRXUbcxu7y5WeoACqM
SeamHttpClient overrides request to return the decoded body rather than the
Response that niquests.Session promises, but the verb helpers kept the
inherited Response return type. Indexing what post and get actually return
did not type check, which accounted for every "Value of type Response is not
indexable" error in the generated routes.

Both helpers now name the arguments they forward: Session.request takes
params in the position Session.post gives data, so collecting them into
*args would have rerouted positional calls.

Handling of a response without a status code is no longer a TypeError.
niquests types status_code as optional because a Response exists before it
has one, so the comparison against it is guarded and the narrowed value is
passed to the error path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8duZRXUbcxu7y5WeoACqM
Type checking these files for the first time turned up a latent crash:
SeamPaginator.flatten_to_list read pagination.has_next_page without the None
guard its sibling flatten already had, so a page whose response carried no
pagination raised AttributeError instead of ending the loop.

The rest are annotations that did not match their callers. request_id is
read from a response header that may be absent, so the HTTP exceptions take
Optional[str]. SeamActionAttemptTimeoutError documented its timeout as str
while every caller passes a float. poll_until_ready declared its timeout and
polling interval Optional though neither is ever None, which made arithmetic
on them unsound. SeamPaginator.params used an implicit Optional default, and
its response hook is handed either side of the exchange, so the request side
is turned away before the pagination is read.

A failed action attempt is assumed to carry an error; reading through it
unguarded would raise AttributeError over the actual failure.

Two invariants are beyond what the checker can follow and are ignored in
place: the auth option guards return True only once the values they check
are set, and Seam borrows Routes.__init__ to attach the route namespaces
even though the two are siblings under AbstractRoutes rather than parent and
child.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8duZRXUbcxu7y5WeoACqM
Generated dataclasses declared every field as present and non-null, but
from_dict reads each one with dict.get, so a field the payload omits arrives
as None behind a type that rules None out. That mismatch was the whole of
the ~1720 arg-type errors the lint step disabled the error code to hide.

Fields the blueprint marks optional or nullable are now generated as
Optional. So are the ones a merged shape cannot guarantee: a property only
some variants of a discriminated union carry is absent whenever the
dataclass holds a variant that omits it.

Nested objects are Optional regardless of what the schema says, because the
schema is not a reliable guide to when they arrive. An action attempt
documents both error and result as required, yet a pending one carries
neither, so from_dict keeps its None fallback and the field admits it. No
field gains a default, so the constructors are unchanged.

from_dict now takes Any. The payload is decoded JSON and every value read
out of it is untyped, so keeping that at the boundary avoids casting each
read while the fields carry the real types.

Route methods annotate json_payload as Dict[str, Any]. Left bare, its type
was inferred from whichever parameter was written first, and every later
parameter of a different type was reported as an incompatible assignment.

BREAKING CHANGE: Resource fields that the API may omit or send as null are
now typed Optional. Code that reads them under a type checker may need a
None check. Runtime behavior is unchanged: those fields could already be
None.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8duZRXUbcxu7y5WeoACqM
The mypy step checked seam/resources with two error codes disabled. That
covered 34 of the 94 files in the package and left every hand-written module
unchecked, including the package root whose blanket ignore broke type
checking for every downstream user.

It now checks seam and test with nothing disabled. The import-not-found
disable is dropped outright: it suppressed nothing, since pointing mypy at
the whole package resolves the imports it was added for.

The paginator tests narrow the pagination they assert on, the way
test_paginator_first_page already did, and the test that passes None as a
cursor on purpose says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8duZRXUbcxu7y5WeoACqM
@razor-x
razor-x requested a review from a team as a code owner August 13, 2026 03:48
@razor-x
razor-x merged commit 17deca0 into beta Aug 13, 2026
25 of 26 checks passed
@razor-x
razor-x deleted the claude/mypy-flags-generated-code-ahxwd7 branch August 13, 2026 04:04
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.

2 participants