test_required_drift (REST) and test_ws_required_drift (WS) fire warnings — not failures — when the spec marks a field required but the SDK keeps it Optional[T] | None with a None default. ~190 individual field instances across 34 model classes.
Until a policy is set, the warn flood masks new drift.
Why this matters
A field declared as Optional[T] | None with a None default misleads users in two directions:
- mypy infers
Optional[T] for callers, forcing pointless if x is not None narrowing on fields the server always sends.
- Real null-on-the-wire goes unnoticed during testing because Pydantic accepts the
None.
Affected models
Reproduce with pytest tests/test_contracts.py -W error::Warning -k 'required_drift'.
REST (22 models, ~134 fields):
| Model |
Fields |
Market |
34 |
Order |
17 |
MultivariateEventCollection |
13 |
Fill |
13 |
Settlement |
9 |
Trade |
8 |
Event |
7 |
Series |
7 |
MarketPosition |
7 |
EventPosition |
5 |
EventMetadata |
3 |
Milestone |
3 |
SportFilterDetails |
2 |
IncentiveProgram, ApiKey, SeriesFeeChange, MarketCandlesticks, ScopeList, GetOrderGroupResponse, CreateOrderGroupResponse, CreateOrderRequest |
1 each |
WS (12 models, ~80 fields):
| Payload |
Fields |
UserOrdersPayload |
18 |
FillPayload |
14 |
TickerPayload |
13 |
TradePayload |
8 |
MarketPositionsPayload |
7 |
QuoteExecutedPayload |
7 |
QuoteCreatedPayload, QuoteAcceptedPayload |
6 each |
MultivariatePayload |
4 |
RfqCreatedPayload, RfqDeletedPayload |
3 each |
OrderGroupPayload, MarketLifecyclePayload |
1 each |
Policy options
A. Tighten — drop None defaults on fields the server reliably sends. Requires confirming each field is actually-always-sent against the demo + prod APIs. Soft-breaking at construction sites (callers building these models in tests must add the field).
B. Allowlist — extend EXCLUSIONS in tests/_contract_support.py for fields the spec lies about. Documents the deviation but leaves the runtime type lax.
C. Hybrid — tighten the high-signal cases (top-level Market, Order, Fill, WS *Payloads), allowlist the long-tail with notes.
Recommendation
C (Hybrid). The top 5 models (Market, Order, Fill, UserOrdersPayload, TickerPayload) account for ~96 of ~190 violations and are the user-facing types where lying optional types hurt callers most. The rest are sub-models the user rarely instantiates — allowlist them with a typed EXCLUSIONS reason.
If C is chosen, this issue branches into:
- A tightening PR per top-5 model (5 PRs, ~96 field changes, each soft-breaking at construction sites).
- One sweep PR adding
EXCLUSIONS entries for the remaining ~94 field instances with kind="server_omits_despite_required" and per-field demo+prod observation notes.
Acceptance
- Maintainer decision recorded in the issue thread.
pytest tests/test_contracts.py -W error::Warning -k 'required_drift' passes.
test_required_drift and test_ws_required_drift promoted from warnings.warn to pytest.fail.
- Any remaining deviations carry an
EXCLUSIONS entry with kind="server_omits_despite_required" (new ExclusionKind) and a reason citing demo+prod observations.
Blocking
Needs maintainer decision (tighten vs. allowlist vs. hybrid) before code work starts. Once decided, the tightening pass is mechanical per-model.
test_required_drift(REST) andtest_ws_required_drift(WS) fire warnings — not failures — when the spec marks a fieldrequiredbut the SDK keeps itOptional[T] | Nonewith aNonedefault. ~190 individual field instances across 34 model classes.Until a policy is set, the warn flood masks new drift.
Why this matters
A field declared as
Optional[T] | Nonewith aNonedefault misleads users in two directions:Optional[T]for callers, forcing pointlessif x is not Nonenarrowing on fields the server always sends.None.Affected models
Reproduce with
pytest tests/test_contracts.py -W error::Warning -k 'required_drift'.REST (22 models, ~134 fields):
MarketOrderMultivariateEventCollectionFillSettlementTradeEventSeriesMarketPositionEventPositionEventMetadataMilestoneSportFilterDetailsIncentiveProgram,ApiKey,SeriesFeeChange,MarketCandlesticks,ScopeList,GetOrderGroupResponse,CreateOrderGroupResponse,CreateOrderRequestWS (12 models, ~80 fields):
UserOrdersPayloadFillPayloadTickerPayloadTradePayloadMarketPositionsPayloadQuoteExecutedPayloadQuoteCreatedPayload,QuoteAcceptedPayloadMultivariatePayloadRfqCreatedPayload,RfqDeletedPayloadOrderGroupPayload,MarketLifecyclePayloadPolicy options
A. Tighten — drop
Nonedefaults on fields the server reliably sends. Requires confirming each field is actually-always-sent against the demo + prod APIs. Soft-breaking at construction sites (callers building these models in tests must add the field).B. Allowlist — extend
EXCLUSIONSintests/_contract_support.pyfor fields the spec lies about. Documents the deviation but leaves the runtime type lax.C. Hybrid — tighten the high-signal cases (top-level
Market,Order,Fill, WS*Payloads), allowlist the long-tail with notes.Recommendation
C (Hybrid). The top 5 models (
Market,Order,Fill,UserOrdersPayload,TickerPayload) account for ~96 of ~190 violations and are the user-facing types where lying optional types hurt callers most. The rest are sub-models the user rarely instantiates — allowlist them with a typedEXCLUSIONSreason.If C is chosen, this issue branches into:
EXCLUSIONSentries for the remaining ~94 field instances withkind="server_omits_despite_required"and per-field demo+prod observation notes.Acceptance
pytest tests/test_contracts.py -W error::Warning -k 'required_drift'passes.test_required_driftandtest_ws_required_driftpromoted fromwarnings.warntopytest.fail.EXCLUSIONSentry withkind="server_omits_despite_required"(newExclusionKind) and areasonciting demo+prod observations.Blocking
Needs maintainer decision (tighten vs. allowlist vs. hybrid) before code work starts. Once decided, the tightening pass is mechanical per-model.