Skip to content

Commit 085babd

Browse files
committed
Fix for pandas pre 2.2.0
Fixes to accommodate pandas pre 2.2.0 default frequency strings, "T" (now "min"), "H" (now "h"), "S" (now "s"), "M" (now "ME") and "Y" (now "YE").
1 parent b1e3c24 commit 085babd

File tree

7 files changed

+77
-7
lines changed

7 files changed

+77
-7
lines changed

.github/workflows/release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- name: Install from testpypi and import
3838
shell: bash
3939
run: |
40+
sleep 5
4041
i=0
4142
while [ $i -lt 12 ] && [ "${{ github.ref_name }}" != $(pip index versions -i https://test.pypi.org/simple --pre market_prices | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\
4243
do echo "waiting for package to appear in test index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; let i++; echo "next i is $i"; done

src/market_prices/intervals.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ def freq_unit(self) -> typing.Literal["min", "h", "D"]:
5656
5757
Returns either "min", "h" or "D".
5858
"""
59-
return self.as_pdtd.resolution_string
59+
unit = self.as_pdtd.resolution_string
60+
# for pre pandas 2.2 compatibility...
61+
if unit == "T":
62+
unit = "min"
63+
if unit == "H":
64+
unit = "h"
65+
return unit
6066

6167
@property
6268
def freq_value(self) -> int:
@@ -449,8 +455,7 @@ def to_ptinterval(interval: str | timedelta | pd.Timedelta) -> PTInterval:
449455
" interval in terms of months pass as a string, for"
450456
' example "1m" for one month.'
451457
)
452-
453-
valid_resolutions = ["min", "h", "D"]
458+
valid_resolutions = ["min", "h", "D"] + ["T", "H"] # + form pandas pre 2.2
454459
if interval.resolution_string not in valid_resolutions:
455460
raise ValueError(error_msg)
456461

src/market_prices/pt.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,9 @@ def downsample( # pylint: disable=arguments-differ
14801480
else:
14811481
return self._downsample_days(pdfreq)
14821482

1483-
if unit in ["h", "min", "s", "L", "ms", "U", "us", "N", "ns"]:
1483+
invalid_units = ["h", "min", "MIN", "s", "L", "ms", "U", "us", "N", "ns"]
1484+
ext = ["t", "T", "H", "S"] # for pandas pre 2.2 compatibility
1485+
if unit in invalid_units + ext:
14841486
raise ValueError(
14851487
"Cannot downsample to a `pdfreq` with a unit more precise than 'd'."
14861488
)
@@ -2328,6 +2330,12 @@ def downsample(
23282330
)
23292331

23302332
unit = genutils.remove_digits(pdfreq)
2333+
# for pandas pre 2.2. compatibility
2334+
if unit == "T":
2335+
unit = "min"
2336+
if unit == "H":
2337+
unit = "h"
2338+
23312339
valid_units = ["min", "h"]
23322340
if unit not in valid_units:
23332341
raise ValueError(

src/market_prices/utils/pandas_utils.py

+28
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ def timestamps_in_interval_of_intervals(
7676
7777
Examples
7878
--------
79+
>>> # ignore first part, for testing purposes only...
80+
>>> import pytest, pandas
81+
>>> v = pandas.__version__
82+
>>> if (
83+
... (v.count(".") == 1 and float(v) < 2.2)
84+
... or (
85+
... v.count(".") > 1
86+
... and float(v[:v.index(".", v.index(".") + 1)]) < 2.2
87+
... )
88+
... ):
89+
... pytest.skip("printed return only valid from pandas 2.2")
90+
>>> #
91+
>>> # example from here...
7992
>>> timestamps = pd.DatetimeIndex(
8093
... [
8194
... pd.Timestamp('2021-03-12 14:00'),
@@ -96,6 +109,7 @@ def timestamps_in_interval_of_intervals(
96109
>>> timestamps_in_interval_of_intervals(timestamps, intervals)
97110
True
98111
"""
112+
# NOTE Can lose doctest skip when pandas support is >= 2.2
99113
timestamps = [timestamps] if isinstance(timestamps, pd.Timestamp) else timestamps
100114
ser = intervals.to_series()
101115
bv = ser.apply(lambda x: all({ts in x for ts in timestamps}))
@@ -387,6 +401,19 @@ def remove_intervals_from_interval(
387401
388402
Examples
389403
--------
404+
>>> # ignore first part, for testing purposes only...
405+
>>> import pytest, pandas
406+
>>> v = pandas.__version__
407+
>>> if (
408+
... (v.count(".") == 1 and float(v) < 2.2)
409+
... or (
410+
... v.count(".") > 1
411+
... and float(v[:v.index(".", v.index(".") + 1)]) < 2.2
412+
... )
413+
... ):
414+
... pytest.skip("printed return only valid from pandas 2.2")
415+
>>> #
416+
>>> # example from here...
390417
>>> from pprint import pprint
391418
>>> left = pd.date_range('2021-05-01 12:00', periods=5, freq='h')
392419
>>> right = left + pd.Timedelta(30, 'min')
@@ -411,6 +438,7 @@ def remove_intervals_from_interval(
411438
Interval(2021-05-01 15:30:00, 2021-05-01 16:00:00, closed='left'),
412439
Interval(2021-05-01 16:30:00, 2021-05-01 17:30:00, closed='left')]
413440
"""
441+
# NOTE Can lose doctest skip when pandas support is >= 2.2
414442
if not intervals.is_monotonic_increasing:
415443
raise ValueError(
416444
"`intervals` must be monotonically increasing although receieved"

tests/conftest.py

+13
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,16 @@ def xlon_calendar_extended(
280280
def xhkg_calendar(today, side, mock_now) -> abc.Iterator[xcals.ExchangeCalendar]:
281281
"""XLON calendar."""
282282
yield xcals.get_calendar("XHKG", side=side, end=today)
283+
284+
285+
@pytest.fixture
286+
def pandas_pre_22() -> abc.Iterator[bool]:
287+
"""Installed pandas is pre version 2.2."""
288+
v = pd.__version__
289+
if v.count(".") == 1:
290+
rtrn = float(v) < 2.2
291+
else:
292+
stop = v.index(".", v.index(".") + 1)
293+
minor_v = float(v[:stop])
294+
rtrn = minor_v < 2.2
295+
yield rtrn

tests/hypstrtgy.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@ def pp_days(draw, calendar_name: str) -> st.SearchStrategy[dict[str, typing.Any]
361361
return pp
362362

363363

364+
# set PRE_PANDAS_22
365+
v = pd.__version__
366+
if v.count(".") == 1:
367+
PRE_PANDAS_22 = float(v) < 2.2
368+
else:
369+
stop = v.index(".", v.index(".") + 1)
370+
minor_v = float(v[:stop])
371+
PRE_PANDAS_22 = minor_v < 2.2
372+
373+
364374
@st.composite
365375
def pp_days_start_session(
366376
draw,
@@ -387,7 +397,10 @@ def pp_days_start_session(
387397
sessions = calendar.sessions
388398
limit_r = sessions[-pp["days"]]
389399
if start_will_roll_to_ms:
390-
offset = pd.tseries.frequencies.to_offset("ME")
400+
# NOTE when min pandas support moves to >= 2.2 can hard code this as ME
401+
# and lose the PRE_PANDAS_22 global.
402+
freq = "M" if PRE_PANDAS_22 else "ME"
403+
offset = pd.tseries.frequencies.to_offset(freq)
391404
if TYPE_CHECKING:
392405
assert offset is not None
393406
limit_r = offset.rollback(limit_r)

tests/test_pt.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -2289,7 +2289,7 @@ def xnys_open(self, xnys, session) -> abc.Iterator[pd.Timestamp]:
22892289
def xnys_close(self, xnys, session) -> abc.Iterator[pd.Timestamp]:
22902290
yield xnys.session_close(session)
22912291

2292-
def test_errors(self, intraday_pt, composite_intraday_pt, one_min):
2292+
def test_errors(self, intraday_pt, composite_intraday_pt, one_min, pandas_pre_22):
22932293
"""Verify raising expected errors for intraday price table."""
22942294
df = intraday_pt
22952295
f = df.pt.downsample
@@ -2328,7 +2328,9 @@ def match_f(pdfreq) -> str:
23282328
f" received `pdfreq` as {pdfreq}."
23292329
)
23302330

2331-
invalid_pdfreqs = ["1d", "1s", "1ns", "1ms", "1ME", "1YE"]
2331+
invalid_pdfreqs = ["1d", "1s", "1ns", "1ms"]
2332+
ext = ["1M", "1Y"] if pandas_pre_22 else ["1ME", "1YE"]
2333+
invalid_pdfreqs += ext
23322334
for pdfreq in invalid_pdfreqs:
23332335
with pytest.raises(ValueError, match=match_f(pdfreq)):
23342336
f(pdfreq)

0 commit comments

Comments
 (0)