Repository navigation
bug(types): freezegun's freeze_time() crashes or leaves models unbuildable because of lazy imports, import-time TypeAdapters and defer_build #1310
Copy link
Copy link
Closed
Labels
Description
Summary
On 8.0.0, adopters' tests that use freezegun.freeze_time() break in two ways.
freeze_time().start()walkssys.modulesandgetattr()s every attribute after it has swappeddatetime.dateforFakeDate.adcpresolves its top-level names lazily (adcp/__init__.py:879-915), so the walk imports most of the package mid-freeze.adcp/negotiation.py:33-34buildsTypeAdapter(RefineProposalsRequest)at import time, and that build fails. The exception escapesfreeze_time().__enter__, anddatetime.datestaysFakeDatefor the rest of the process.- Every model uses
defer_build=True(adcp/types/base.py:270-275). If a model's first validate or serialize happens insidefreeze_time(), the schema build fails, even whenadcpis on freezegun's ignore list.
Which tests fail depends on test order: whether something built the model, or imported the lazy name, before time was frozen.
Reproduction (adcp 8.0.0, freezegun 1.5.5, pydantic 2.13.5)
Case 1:
import datetime
from adcp.types import AcquireRightsRequest # noqa: F401
from freezegun import freeze_time
try:
with freeze_time("2026-01-01"):
pass
except Exception as exc:
print("freeze_time:", type(exc).__name__, "|", str(exc).splitlines()[0][:90])
print("datetime.date after the failed start:", datetime.date)freeze_time: PydanticSchemaGenerationError | Unable to generate pydantic-core schema for <class 'datetime.date'>. Set `arbitrary_types_
datetime.date after the failed start: <class 'freezegun.api.FakeDate'>
The traceback runs freezegun/api.py _get_module_attributes -> adcp/__init__.py __getattr__ -> adcp/negotiation.py line 34.
Case 2, with adcp excluded from the walk:
import freezegun
from freezegun import freeze_time
freezegun.configure(extend_ignore_list=["adcp"])
from adcp.types import AcquireRightsRequest
print("schema built before freeze:", AcquireRightsRequest.__pydantic_complete__)
with freeze_time("2026-01-01"):
try:
AcquireRightsRequest.model_json_schema()
print("first build inside freeze_time: ok")
except Exception as exc:
print("first build inside freeze_time:", type(exc).__name__, "|", str(exc).splitlines()[0][:90])schema built before freeze: False
first build inside freeze_time: PydanticSchemaGenerationError | Unable to generate pydantic-core schema for <class 'datetime.date'>. Set `arbitrary_types_
Why an adopter hits this
freezegun is the usual way to test date-dependent seller logic: flight dates, delivery windows, reporting periods. Embedded Sales Agent's tests/conftest.py carries two workarounds:
freezegun.configure(extend_ignore_list=["adcp"]);- a monkeypatch of the private
freezegun.api._freeze_time.startthat first walksAdCPBaseModel.__subclasses__()and callsmodel_rebuild(force=True)on every model with__pydantic_complete__still false.
Before those, failures appeared only in some CI shards. One failed start also left the clock frozen for every later test in the process.
Ask
- Don't build
TypeAdapters at module import. Build them on first use (e.g. afunctools.cache'd accessor). 14 modules do this today, includingadcp/negotiation.py:33-34,adcp/decisioning/negotiation.py:30-31,adcp/compat/purchase_continuation.py:44and several underadcp/reporting/. - Add a public helper that finishes all deferred builds for imported models (e.g.
adcp.testing.build_all_models()), so test suites can call it once before freezing time instead of walking SDK internals. - Optionally, document the freezegun interaction in the testing guide.
Reactions are currently unavailable
Activity
bokelley commentedon Oct 2, 2026
Triage
Classification: Bug
Bucket(s): client, types (no exact label match found — flagged in run summary)
Status: ready-to-implement
Milestone: omitted (no version specified)
What the experts said:
AdCPBaseModel.__subclasses__()is a shallow walk that misses all multi-level descendants; must use a recursive walkmodel_rebuild(force=True)is expensive across 700+ models; preferforce=False(matches existing_build_deferred_serializerspattern) or justify why force-rebuild is neededadcp.testingis the correct namespace,build_all_models()with no params is the right v1 surface; ship with a docstring that includes a conftest snippetMy take: This is a clear, well-diagnosed bug with a straight fix path. Both root causes (import-time
TypeAdapterconstruction + deferred schema builds) are internal SDK concerns, not spec or protocol changes. The fix is additive/internal-refactor only — no public API removals, no signature changes, no error-class changes.Implementation scope:
TypeAdapter(...)in a@functools.cache-decorated function. The private names (_REFINE_REQUEST_ADAPTER,_CORE,_EVENT, etc.) are not in any__all__; callers in the same file use the function instead. Files:src/adcp/negotiation.py,src/adcp/decisioning/negotiation.py,src/adcp/reporting/receipts/records.py,src/adcp/reporting/receipts/capture.py,src/adcp/reporting/materializer/capture.py,src/adcp/reporting/projection/capture.py,src/adcp/reporting/projection/history.py,src/adcp/reporting/ledger/notification_models.py. Confirm whethersrc/adcp/types/legacy.py:_URL_ADAPTER(wrapsAnyUrl, nodatetime.datedep) needs to be included — check the repro traceback.adcp.testing.build_all_models(). Add tosrc/adcp/testing/(module already exists). Must use a recursive__subclasses__()walk — the shallow form misses multi-level descendants (Countries7(Countries5),CoverageGap(DistributionId), envelope subclasses, etc.). Usemodel_rebuild(force=False)to skip already-complete models (matches the existing pattern inAdCPBaseModel._build_deferred_serializers). Export fromadcp.testing.__all__. Docstring must show the conftest call site (adcp.testing.build_all_models()at module level, before anyfreeze_timeblock).pyteston affected modules,mypy src/,ruff check ./black --check ..fix(types): lazy TypeAdapter construction and build_all_models() for freezegun compatibility(Release Please reads conventional-commits titles for patch bump).Triaged by Claude Code. Session: https://claude.ai/code/session_01AJ6jSUcJ7TDpmfs9qEfm6o
Generated by Claude Code