FIX/options.expirations returns an empty DataFrame when filtering columns#27
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #27 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 52 52
Lines 2281 2284 +3
=========================================
+ Hits 2281 2284 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
MarketDataDev02
left a comment
There was a problem hiding this comment.
Approved with two comments from automated analysis. Other comments I do not include since they dont make sense contextually.
-
The fix is narrow to updated. If a caller filters to a set that excludes expirations (e.g. columns=["updated"]), the INTERNAL path's OptionsExpirations(**data) would still raise, because expirations remains a required field — the same failure class #23 describes, just a different column. Likely fine in practice (the API almost certainly always returns expirations as the core payload, and #23 is specifically about columns=["expirations"]), so this may be deliberately out of scope. ~40% confidence it's a real gap.
-
OptionsExpirationsHumanReadable.Date got no equivalent treatment — a partial human-readable INTERNAL response missing the date would still crash. Edge-of edge, and the human-readable path wasn't part of #23. Low confidence.
I agree with the comment and also see that it's unlikely to happen. Since it's outside the scope of this issue, I created a new one to follow up on it #34 |
Fix:
options.expirationsreturns an empty DataFrame when filtering columnsCloses #23
Summary
options.expirations(..., columns=["expirations"])returned an empty DataFrame,and the
INTERNALoutput format crashed on partial API responses. Both stem fromthe same trigger: the
columnsparameter is applied server-side, so requestingcolumns=["expirations"]makes the API respond with only{"s": "ok", "expirations": [...]}— without the
updatedfield the SDK assumed was always present.This PR fixes both failure modes and adds regression tests.
Problem
Two distinct bugs:
Empty DataFrame (primary). The resource always forced
index_columns=["expirations"]. Whenexpirationsis the only column returned,the pandas handler promotes it to the index via
set_index, leaving theDataFrame with zero data columns — so it looks empty even though the data is
there (in the index).
INTERNALcrash on partial responses (secondary).OptionsExpirationsrequired the
updatedfield. A response missing it (e.g. a filtered/partialresponse) raised
TypeError, which@handle_exceptionsturned into aMarketDataClientErrorResult.Changes
resources/options/expirations.py— primary fixStop forcing
expirationsinto the index when the user explicitly filters columns.The default behavior (no
columnsfilter →expirationsas index) is unchanged.output_types/options_expirations.py— secondary fixMake
updatedoptional and skip conversion when it is absent.output_handlers/base.py— required follow-on fixChanging
updatedto the PEP 604 uniondatetime | Nonebroke date-columndetection:
_type_includesonly recognizedtyping.Union, butX | Nonehasorigin
types.UnionType. Without this,updatedstopped being converted to adatetime and broke two pre-existing DataFrame tests. Now both union forms are
handled.
Testing
Added 4 tests in
src/tests/test_options_expirations.py:test_options_expirations_optional_updated— dataclass acceptsupdated=None.test_get_options_expirations_columns_filter_dataframe_pandas— filtered DataFrameis not empty; compared against a full expected DataFrame via
assert_frame_equal.test_get_options_expirations_columns_filter_dataframe_polars— regression guard(polars never had the bug).
test_get_options_expirations_partial_response_internal—INTERNALparses aresponse missing
updated, leaving itNone.Developed with a TDD red → green cycle.
Full suite green — no regressions.
Files changed
src/marketdata/resources/options/expirations.pysrc/marketdata/output_types/options_expirations.pyupdatedmade optionalsrc/marketdata/output_handlers/base.pyX | Nonein date detectionsrc/tests/test_options_expirations.py