-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Prevent passing invalid kwds to DateOffset constructors #18226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
b9fe60e
f10a1c6
807d769
687e3b7
d5443ca
5e5e0c0
17f7b5a
3834ef8
1c54e96
0e37a24
a572368
d0ff381
409dbd0
6a9233e
4406df8
44891cd
573abb6
c6cc8bc
a68f4a7
c8224c1
55779d8
d5b8302
b54e26b
fade4a2
38c2238
fe895ff
11ba1a9
bc90a19
0b3dca0
9c841fc
adee395
43dc17e
4ff8c22
d261be9
b4b9e15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -261,7 +261,7 @@ def _validate_business_time(t_input): | |
# --------------------------------------------------------------------- | ||
# Constructor Helpers | ||
|
||
_rd_kwds = set([ | ||
relativedelta_kwds = set([ | ||
'years', 'months', 'weeks', 'days', | ||
'year', 'month', 'week', 'day', 'weekday', | ||
'hour', 'minute', 'second', 'microsecond', | ||
|
@@ -406,6 +406,15 @@ class _BaseOffset(object): | |
# will raise NotImplementedError. | ||
return get_day_of_month(other, self._day_opt) | ||
|
||
def _validate_n(self, n): | ||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add the doc-string that you did for #18210 |
||
nint = int(n) | ||
except (ValueError, TypeError): | ||
raise TypeError('`n` argument must be an integer') | ||
if n != nint: | ||
raise ValueError('`n` argument must be an integer') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. show the type of what was passed (for 1st) |
||
return nint | ||
|
||
|
||
class BaseOffset(_BaseOffset): | ||
# Here we add __rfoo__ methods that don't play well with cdef classes | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
get_offset, get_standard_freq) | ||
from pandas.core.indexes.datetimes import ( | ||
_to_m8, DatetimeIndex, _daterange_cache) | ||
import pandas._libs.tslibs.offsets as liboffsets | ||
from pandas._libs.tslibs.offsets import WeekDay, CacheableOffset | ||
from pandas.tseries.offsets import (BDay, CDay, BQuarterEnd, BMonthEnd, | ||
BusinessHour, WeekOfMonth, CBMonthEnd, | ||
|
@@ -4682,9 +4683,33 @@ def test_all_offset_classes(self, tup): | |
assert first == second | ||
|
||
|
||
# --------------------------------------------------------------------- | ||
def test_get_offset_day_error(): | ||
# subclass of _BaseOffset must override _day_opt attribute, or we should | ||
# get a NotImplementedError | ||
|
||
with pytest.raises(NotImplementedError): | ||
DateOffset()._get_offset_day(datetime.now()) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prob move to liboffsets tests? also |
||
|
||
@pytest.mark.parametrize('kwd', sorted(list(liboffsets.relativedelta_kwds))) | ||
def test_valid_month_attributes(kwd, month_classes): | ||
# GH#18226 | ||
cls = month_classes | ||
# check that we cannot create e.g. MonthEnd(weeks=3) | ||
with pytest.raises(TypeError): | ||
cls(**{kwd: 3}) | ||
|
||
|
||
@pytest.mark.parametrize('kwd', sorted(list(liboffsets.relativedelta_kwds))) | ||
def test_valid_tick_attributes(kwd, tick_classes): | ||
# GH#18226 | ||
cls = tick_classes | ||
# check that we cannot create e.g. Hour(weeks=3) | ||
with pytest.raises(TypeError): | ||
cls(**{kwd: 3}) | ||
|
||
|
||
def test_validate_n_error(): | ||
with pytest.raises(TypeError): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add some more tests here, e.g. for passing floats != to the int |
||
DateOffset(n='Doh!') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
# -*- coding: utf-8 -*- | ||
from datetime import date, datetime, timedelta | ||
import functools | ||
import operator | ||
|
||
from datetime import date, datetime, timedelta | ||
from pandas.compat import range | ||
from pandas import compat | ||
import numpy as np | ||
|
@@ -167,7 +167,7 @@ def __add__(date): | |
normalize = False | ||
|
||
def __init__(self, n=1, normalize=False, **kwds): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.kwds = kwds | ||
|
||
|
@@ -474,7 +474,7 @@ class BusinessDay(BusinessMixin, SingleConstructorOffset): | |
_adjust_dst = True | ||
|
||
def __init__(self, n=1, normalize=False, offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.kwds = {'offset': offset} | ||
self._offset = offset | ||
|
@@ -783,7 +783,7 @@ class BusinessHour(BusinessHourMixin, SingleConstructorOffset): | |
|
||
def __init__(self, n=1, normalize=False, start='09:00', | ||
end='17:00', offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
super(BusinessHour, self).__init__(start=start, end=end, offset=offset) | ||
|
||
|
@@ -820,7 +820,7 @@ class CustomBusinessDay(BusinessDay): | |
|
||
def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', | ||
holidays=None, calendar=None, offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self._offset = offset | ||
self.kwds = {} | ||
|
@@ -888,7 +888,7 @@ class CustomBusinessHour(BusinessHourMixin, SingleConstructorOffset): | |
def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', | ||
holidays=None, calendar=None, | ||
start='09:00', end='17:00', offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
super(CustomBusinessHour, self).__init__(start=start, | ||
end=end, offset=offset) | ||
|
@@ -920,6 +920,13 @@ def next_bday(self): | |
class MonthOffset(SingleConstructorOffset): | ||
_adjust_dst = True | ||
|
||
def __init__(self, n=1, normalize=False): | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self._offset = timedelta(1) | ||
self._use_relativedelta = False | ||
self.kwds = {} | ||
|
||
@property | ||
def name(self): | ||
if self.isAnchored: | ||
|
@@ -995,7 +1002,8 @@ def __init__(self, n=1, normalize=False, day_of_month=None): | |
msg = 'day_of_month must be {min}<=day_of_month<=27, got {day}' | ||
raise ValueError(msg.format(min=self._min_day_of_month, | ||
day=self.day_of_month)) | ||
self.n = int(n) | ||
|
||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.kwds = {'day_of_month': self.day_of_month} | ||
|
||
|
@@ -1206,7 +1214,7 @@ class CustomBusinessMonthEnd(BusinessMixin, MonthOffset): | |
|
||
def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', | ||
holidays=None, calendar=None, offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self._offset = offset | ||
self.kwds = {} | ||
|
@@ -1279,7 +1287,7 @@ class CustomBusinessMonthBegin(BusinessMixin, MonthOffset): | |
|
||
def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', | ||
holidays=None, calendar=None, offset=timedelta(0)): | ||
self.n = int(n) | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self._offset = offset | ||
self.kwds = {} | ||
|
@@ -1346,7 +1354,7 @@ class Week(EndMixin, DateOffset): | |
_prefix = 'W' | ||
|
||
def __init__(self, n=1, normalize=False, weekday=None): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.weekday = weekday | ||
|
||
|
@@ -1425,7 +1433,7 @@ class WeekOfMonth(DateOffset): | |
_adjust_dst = True | ||
|
||
def __init__(self, n=1, normalize=False, week=None, weekday=None): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.weekday = weekday | ||
self.week = week | ||
|
@@ -1510,7 +1518,7 @@ class LastWeekOfMonth(DateOffset): | |
_prefix = 'LWOM' | ||
|
||
def __init__(self, n=1, normalize=False, weekday=None): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.weekday = weekday | ||
|
||
|
@@ -1576,13 +1584,15 @@ class QuarterOffset(DateOffset): | |
# point | ||
|
||
def __init__(self, n=1, normalize=False, startingMonth=None): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
if startingMonth is None: | ||
startingMonth = self._default_startingMonth | ||
self.startingMonth = startingMonth | ||
|
||
self.kwds = {'startingMonth': startingMonth} | ||
self._offset = timedelta(1) | ||
self._use_relativedelta = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The bigger issue with |
||
|
||
def isAnchored(self): | ||
return (self.n == 1 and self.startingMonth is not None) | ||
|
@@ -1824,7 +1834,7 @@ class FY5253(DateOffset): | |
|
||
def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1, | ||
variation="nearest"): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self.startingMonth = startingMonth | ||
self.weekday = weekday | ||
|
@@ -2074,7 +2084,7 @@ class FY5253Quarter(DateOffset): | |
|
||
def __init__(self, n=1, normalize=False, weekday=0, startingMonth=1, | ||
qtr_with_extra_week=1, variation="nearest"): | ||
self.n = n | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
|
||
self.weekday = weekday | ||
|
@@ -2201,6 +2211,13 @@ class Easter(DateOffset): | |
""" | ||
_adjust_dst = True | ||
|
||
def __init__(self, n=1, normalize=False): | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same (and for other ones you are adding) |
||
self._offset = timedelta(1) | ||
self._use_relativedelta = False | ||
self.kwds = {} | ||
|
||
@apply_wraps | ||
def apply(self, other): | ||
currentEaster = easter(other.year) | ||
|
@@ -2245,6 +2262,14 @@ class Tick(SingleConstructorOffset): | |
_inc = Timedelta(microseconds=1000) | ||
_prefix = 'undefined' | ||
|
||
def __init__(self, n=1, normalize=False): | ||
# TODO: do Tick classes with normalize=True make sense? | ||
self.n = self._validate_n(n) | ||
self.normalize = normalize | ||
self._offset = timedelta(1) | ||
self._use_relativedelta = False | ||
self.kwds = {} | ||
|
||
__gt__ = _tick_comp(operator.gt) | ||
__ge__ = _tick_comp(operator.ge) | ||
__lt__ = _tick_comp(operator.lt) | ||
|
@@ -2303,6 +2328,7 @@ def delta(self): | |
def nanos(self): | ||
return delta_to_nanoseconds(self.delta) | ||
|
||
# TODO: Should Tick have its own apply_index? | ||
def apply(self, other): | ||
# Timestamp can handle tz and nano sec, thus no need to use apply_wraps | ||
if isinstance(other, Timestamp): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add some tests to exercise this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Got one test so far, will add a few more after addressing the current test failure (which is a bug that may need to be addressed as part of a Larger Fix).