Skip to content

Commit

Permalink
Merge pull request #2076 from Zac-HD/from-abstract-type
Browse files Browse the repository at this point in the history
Support `from_type` for abstract classes with unknown concrete subclasses
  • Loading branch information
Zac-HD committed Aug 23, 2019
2 parents e55b968 + 689b605 commit 63eebe3
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 4 deletions.
6 changes: 6 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RELEASE_TYPE: minor

The :func:`~hypothesis.strategies.from_type` strategy now knows to look up
the subclasses of abstract types, which cannot be instantiated directly.

This is very useful for :pypi:`hypothesmith` to support :pypi:`libCST`.
22 changes: 18 additions & 4 deletions hypothesis-python/src/hypothesis/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from decimal import Context, Decimal, localcontext
from fractions import Fraction
from functools import reduce
from inspect import isclass
from inspect import isabstract, isclass
from uuid import UUID

import attr
Expand Down Expand Up @@ -1308,7 +1308,7 @@ def from_type(thing):
naive time and datetime strategies.
The resolution logic may be changed in a future version, but currently
tries these four options:
tries these five options:
1. If ``thing`` is in the default lookup mapping or user-registered lookup,
return the corresponding strategy. The default lookup covers all types
Expand All @@ -1319,7 +1319,13 @@ def from_type(thing):
the union of the strategies for those types that are not subtypes of
other elements in the lookup.
4. Finally, if ``thing`` has type annotations for all required arguments,
it is resolved via :func:`~hypothesis.strategies.builds`.
and is not an abstract class, it is resolved via
:func:`~hypothesis.strategies.builds`.
5. Because :mod:`abstract types <python:abc>` cannot be instantiated,
we treat abstract types as the union of their concrete subclasses.
Note that this lookup works via inheritance but not via
:obj:`~python:abc.ABCMeta.register`, so you may still need to use
:func:`~hypothesis.strategies.register_type_strategy`.
There is a valuable recipe for leveraging ``from_type()`` to generate
"everything except" values from a specified type. I.e.
Expand Down Expand Up @@ -1430,7 +1436,15 @@ def as_strategy(strat_or_callable, thing):
"using register_type_strategy" % (thing,)
)
# Finally, try to build an instance by calling the type object
return builds(thing)
if not isabstract(thing):
return builds(thing)
subclasses = thing.__subclasses__()
if not subclasses:
raise ResolutionFailed(
"Could not resolve %r to a strategy, because it is an abstract type "
"without any subclasses. Consider using register_type_strategy" % (thing,)
)
return sampled_from(subclasses).flatmap(from_type)


@cacheable
Expand Down
31 changes: 31 additions & 0 deletions hypothesis-python/tests/py3/test_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from __future__ import absolute_import, division, print_function

import abc
import collections
import enum
import io
Expand All @@ -37,6 +38,7 @@
from hypothesis.searchstrategy import types
from hypothesis.strategies import from_type
from tests.common.debug import minimal
from tests.common.utils import fails_with

typing = pytest.importorskip("typing")
sentinel = object()
Expand Down Expand Up @@ -496,3 +498,32 @@ def test_resolves_ellipses_callable_to_function(f):
f(1)
f(1, 2, 3)
f(accepts_kwargs_too=1)


class AbstractFoo(abc.ABC):
@abc.abstractmethod
def foo(self):
pass


class ConcreteFoo(AbstractFoo):
def foo(self):
pass


@given(st.from_type(AbstractFoo))
def test_can_resolve_abstract_class(instance):
assert isinstance(instance, ConcreteFoo)
instance.foo()


class AbstractBar(abc.ABC):
@abc.abstractmethod
def bar(self):
pass


@fails_with(ResolutionFailed)
@given(st.from_type(AbstractBar))
def test_cannot_resolve_abstract_class_with_no_concrete_subclass(instance):
assert False, "test body unreachable as strategy cannot resolve"

0 comments on commit 63eebe3

Please sign in to comment.