Summary
Follow-up to #27 (which fixed #23). The columns parameter on
options.expirations is applied server-side, so the API can omit any
field from the response — not just updated. PR #27 fixed the specific
columns=["expirations"] case and made updated optional, but the underlying
class of bug remains for other column combinations.
This is a low-priority hardening issue: in practice the API almost certainly
always returns expirations as the core payload, so these paths are unlikely
to trigger today. Filing to track the root cause rather than leave it implicit.
Root cause
The INTERNAL output format builds the dataclass via output_model(**data).
Any field that is not present in a filtered response (because the user
excluded it via columns) will raise TypeError if it's a required field.
Gaps still present
-
columns=["updated"] (or any filter excluding expirations) crashes the
INTERNAL path. OptionsExpirations.expirations is still a required field:
@dataclass
class OptionsExpirations:
s: str
expirations: list[datetime.datetime] # required
updated: datetime.datetime | None = None # optional (fixed in #27)
A response without expirations → TypeError → MarketDataClientErrorResult
— the same failure class as #23, just a different column.
-
OptionsExpirationsHumanReadable got no equivalent treatment. Both
Expirations and Date are required, and post_init calls
format_timestamp(self.Date) unconditionally. A partial human-readable
INTERNAL response missing Date would crash:
@DataClass
class OptionsExpirationsHumanReadable:
Expirations: list[datetime.datetime] # required
Date: datetime.datetime # required; format_timestamp called unconditionally
Proposed fix
Treat every server-side-filterable field as optional in both INTERNAL models
(default None + guarded conversion in post_init), consistent with how
updated was handled in #27. Decide explicitly whether expirations /
Expirations should be optional or whether an empty payload should be rejected
with a clearer error — a response with no expirations data is arguably
meaningless, so a deliberate validation error may be preferable to silent None.
Acceptance criteria
References
Summary
Follow-up to #27 (which fixed #23). The
columnsparameter onoptions.expirationsis applied server-side, so the API can omit anyfield from the response — not just
updated. PR #27 fixed the specificcolumns=["expirations"]case and madeupdatedoptional, but the underlyingclass of bug remains for other column combinations.
This is a low-priority hardening issue: in practice the API almost certainly
always returns
expirationsas the core payload, so these paths are unlikelyto trigger today. Filing to track the root cause rather than leave it implicit.
Root cause
The INTERNAL output format builds the dataclass via
output_model(**data).Any field that is not present in a filtered response (because the user
excluded it via
columns) will raiseTypeErrorif it's a required field.Gaps still present
columns=["updated"](or any filter excludingexpirations) crashes theINTERNAL path.
OptionsExpirations.expirationsis still a required field:OptionsExpirationsHumanReadable got no equivalent treatment. Both
Expirations and Date are required, and post_init calls
format_timestamp(self.Date) unconditionally. A partial human-readable
INTERNAL response missing Date would crash:
@DataClass
class OptionsExpirationsHumanReadable:
Expirations: list[datetime.datetime] # required
Date: datetime.datetime # required; format_timestamp called unconditionally
Proposed fix
Treat every server-side-filterable field as optional in both INTERNAL models
(default None + guarded conversion in post_init), consistent with how
updated was handled in #27. Decide explicitly whether expirations /
Expirations should be optional or whether an empty payload should be rejected
with a clearer error — a response with no expirations data is arguably
meaningless, so a deliberate validation error may be preferable to silent None.
Acceptance criteria
combination on options.expirations.
with OptionsExpirations.
human-readable response.
References
options.expirationsreturns an empty DataFrame whencolumns=["expirations"]is passed #23