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 Jun 24, 2018
1 parent f8c4765 commit 0fc9b74
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 8 deletions.
4 changes: 4 additions & 0 deletions hypothesis-python/src/hypothesis/internal/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import codecs
import platform
import importlib
import collections
from base64 import b64encode
from collections import namedtuple

Expand All @@ -35,6 +36,7 @@
except ImportError:
from ordereddict import OrderedDict # type: ignore
from counter import Counter # type: ignore
from collections import Container

if False:
from typing import Type # noqa
Expand Down Expand Up @@ -70,6 +72,7 @@ def int_to_text(i):
ARG_NAME_ATTRIBUTE = 'arg'
integer_types = (int,)
hunichr = chr
Container = collections.abc.Container # type: ignore

def unicode_safe_repr(x):
return repr(x)
Expand Down Expand Up @@ -217,6 +220,7 @@ def shimrange():
ARG_NAME_ATTRIBUTE = 'id'
integer_types = (int, long)
hunichr = unichr
Container = collections.Container

def escape_unicode_characters(s):
return codecs.encode(s, 'string_escape')
Expand Down
15 changes: 10 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,21 @@ 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, filter types that would fail validation, and
# sort so that ordering is stable between runs and shrinks well.
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)
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
6 changes: 3 additions & 3 deletions hypothesis-python/src/hypothesis/searchstrategy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@

import hypothesis.strategies as st
from hypothesis.errors import InvalidArgument, ResolutionFailed
from hypothesis.internal.compat import PY2, text_type
from hypothesis.internal.compat import PY2, Container, text_type


def type_sorting_key(t):
"""Minimise to None, then non-container types, then container types."""
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, 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 0fc9b74

Please sign in to comment.