Skip to content

Commit

Permalink
filter-rewriting for dates
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Oct 13, 2024
1 parent 882cb85 commit b300edf
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
5 changes: 5 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RELEASE_TYPE: patch

This patch improves the performance of :func:`~hypothesis.strategies.from_type` with
`pydantic.types.condate <https://docs.pydantic.dev/latest/api/types/#pydantic.types.condate>`__
(:issue:`4000`).
6 changes: 3 additions & 3 deletions hypothesis-python/src/hypothesis/internal/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ def get_numeric_predicate_bounds(predicate: Predicate) -> ConstructivePredicate:
options = {
# We're talking about op(arg, x) - the reverse of our usual intuition!
operator.lt: {"min_value": arg, "exclude_min": True}, # lambda x: arg < x
operator.le: {"min_value": arg}, # lambda x: arg <= x
operator.eq: {"min_value": arg, "max_value": arg}, # lambda x: arg == x
operator.ge: {"max_value": arg}, # lambda x: arg >= x
operator.le: {"min_value": arg}, # lambda x: arg <= x
operator.eq: {"min_value": arg, "max_value": arg}, # lambda x: arg == x
operator.ge: {"max_value": arg}, # lambda x: arg >= x
operator.gt: {"max_value": arg, "exclude_max": True}, # lambda x: arg > x
# Special-case our default predicates for length bounds
min_len: {"min_value": arg, "len": True},
Expand Down
37 changes: 34 additions & 3 deletions hypothesis-python/src/hypothesis/strategies/_internal/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
# obtain one at https://mozilla.org/MPL/2.0/.

import datetime as dt
import operator as op
import zoneinfo
from calendar import monthrange
from functools import lru_cache
from functools import lru_cache, partial
from importlib import resources
from pathlib import Path
from typing import Optional

from hypothesis.errors import InvalidArgument
from hypothesis.internal.validation import check_type, check_valid_interval
from hypothesis.strategies._internal.core import sampled_from
from hypothesis.strategies._internal.misc import just, none
from hypothesis.strategies._internal.misc import just, none, nothing
from hypothesis.strategies._internal.strategies import SearchStrategy
from hypothesis.strategies._internal.utils import defines_strategy

Expand Down Expand Up @@ -267,6 +267,37 @@ def do_draw(self, data):
**draw_capped_multipart(data, self.min_value, self.max_value, DATENAMES)
)

def filter(self, condition):
if (
isinstance(condition, partial)
and len(args := condition.args) == 1
and not condition.keywords
and isinstance(arg := args[0], dt.date)
and condition.func in (op.lt, op.le, op.eq, op.ge, op.gt)
):
try:
arg += dt.timedelta(days={op.lt: 1, op.gt: -1}.get(condition.func, 0))
except OverflowError: # gt date.max, or lt date.min
return nothing()
lo, hi = {
# We're talking about op(arg, x) - the reverse of our usual intuition!
op.lt: (arg, self.max_value), # lambda x: arg < x
op.le: (arg, self.max_value), # lambda x: arg <= x
op.eq: (arg, arg), # lambda x: arg == x
op.ge: (self.min_value, arg), # lambda x: arg >= x
op.gt: (self.min_value, arg), # lambda x: arg > x
}[condition.func]
lo = max(lo, self.min_value)
hi = min(hi, self.max_value)
print(lo, hi)
if hi < lo:
return nothing()
if lo <= self.min_value and self.max_value <= hi:
return self
return dates(lo, hi)

return super().filter(condition)


@defines_strategy(force_reusable_values=True)
def dates(
Expand Down
14 changes: 14 additions & 0 deletions hypothesis-python/tests/cover/test_filter_rewriting.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.

import datetime as dt
import decimal
import math
import operator
Expand Down Expand Up @@ -624,3 +625,16 @@ def test_regex_filter_rewriting(data, strategy, pattern, method):
@given(st.text().filter(re.compile("abc").sub))
def test_error_on_method_which_requires_multiple_args(_):
pass


def test_dates_filter_rewriting():
today = dt.date.today()

assert st.dates().filter(partial(operator.lt, dt.date.max)).is_empty
assert st.dates().filter(partial(operator.gt, dt.date.min)).is_empty
assert st.dates(min_value=today).filter(partial(operator.gt, today)).is_empty
assert st.dates(max_value=today).filter(partial(operator.lt, today)).is_empty

bare = unwrap_strategies(st.dates())
assert bare.filter(partial(operator.ge, dt.date.max)) is bare
assert bare.filter(partial(operator.le, dt.date.min)) is bare

0 comments on commit b300edf

Please sign in to comment.