Skip to content

Commit

Permalink
Handle subtypes in instance_of validator
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed May 24, 2018
1 parent b1fa94b commit 82ad134
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 7 deletions.
14 changes: 9 additions & 5 deletions hypothesis-python/src/hypothesis/searchstrategy/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from hypothesis.errors import ResolutionFailed
from hypothesis.internal.compat import string_types, get_type_hints
from hypothesis.utils.conventions import infer
from hypothesis.searchstrategy.types import type_sorting_key


def from_attrs(target, args, kwargs, to_infer):
Expand Down Expand Up @@ -107,17 +108,20 @@ def types_to_strategy(attrib, types):
return st.one_of(*map(st.from_type, typ))
return st.from_type(typ)
elif types:
# Multiple type validators. Pick common type(s) that satisfy all.
# TODO: use a more sophisticated aggregation that understands subtypes,
# generic types, etc. Consider pulling some code out of st.from_type?
# We have a list of tuples of types, and want to find a type
# (or tuple of types) that is a subclass of all of of them.
type_tuples = [k if isinstance(k, tuple) else (k,) for k in types]
return st.one_of(*map(st.from_type, ordered_intersection(type_tuples)))
# Flatten the list, and filter out types that would fail validation
allowed = [t for t in set(sum(type_tuples, ()))
if all(issubclass(t, tup) for tup in type_tuples)]
allowed.sort(key=type_sorting_key) # Sort so order is stable between runs
return st.one_of([st.from_type(t) for t in allowed])

# Otherwise, try the `type` attribute as a fallback, and finally try
# the type hints on a converter (desperate!) before giving up.
if isinstance(getattr(attrib, 'type', None), type):
# The convoluted test is because variable annotations may be stored
# in string form, and pass through attrs unevaluated.
# in string form; attrs doesn't evaluate them and we don't handle them.
# See PEP 526, PEP 563, and Hypothesis issue #1004 for details.
return st.from_type(attrib.type)

Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/src/hypothesis/searchstrategy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ def type_sorting_key(t):
if not isinstance(t, type):
raise InvalidArgument('thing=%s must be a type' % (t,))
if t is None or t is type(None): # noqa: E721
return -1
return issubclass(t, collections.abc.Container)
return (-1, repr(t))
return (issubclass(t, collections.abc.Container), repr(t))


def try_issubclass(thing, maybe_superclass):
Expand Down
5 changes: 5 additions & 0 deletions hypothesis-python/tests/cover/test_attrs_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class Inferrables(object):
attr.validators.instance_of(str),
attr.validators.instance_of((str, int, bool)),
])
validator_type_has_overlap = attr.ib(validator=[
attr.validators.instance_of(str),
attr.validators.instance_of((str, list)),
attr.validators.instance_of(object),
])
validator_optional = attr.ib(
validator=attr.validators.optional(lambda inst, atrib, val: float(val))
)
Expand Down

0 comments on commit 82ad134

Please sign in to comment.