Repository: HypothesisWorks/hypothesis

Issue:
Support typing.NewType in `from_type`
Using version 3.30.4

```
In [13]: from typing import NewType, NamedTuple
    ...: MyInt = NewType('MyInt', int)
    ...: class T(NamedTuple):
    ...:     x: MyInt
    ...:     y: str
    ...:
```
Produces a rather gross stack trace with `InvalidArgument: thing=<function NewType.<locals>.new_type at 0x107fd40d0> must be a type`


<details>
<summary> Trace </summary>

```
In [14]: st.from_type(T).example()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in cached_strategy(*args, **kwargs)
    104         try:
--> 105             return STRATEGY_CACHE[cache_key]
    106         except TypeError:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/cache.py in __getitem__(self, key)
     69     def __getitem__(self, key):
---> 70         i = self.keys_to_indices[key]
     71         result = self.data[i]

KeyError: (<function from_type at 0x106c740d0>, ((<class 'type'>, <class '__main__.T'>),), frozenset())

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in cached_strategy(*args, **kwargs)
    104         try:
--> 105             return STRATEGY_CACHE[cache_key]
    106         except TypeError:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/cache.py in __getitem__(self, key)
     69     def __getitem__(self, key):
---> 70         i = self.keys_to_indices[key]
     71         result = self.data[i]

KeyError: (<function from_type at 0x106c740d0>, ((<class 'function'>, <function NewType.<locals>.new_type at 0x107fd40d0>),), frozenset())

During handling of the above exception, another exception occurred:

InvalidArgument                           Traceback (most recent call last)
<ipython-input-14-87157b77bd3c> in <module>()
----> 1 st.from_type(T).example()

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/strategies.py in example(self, random)
    284                     max_iterations=1000,
    285                     database=None,
--> 286                     verbosity=Verbosity.quiet,
    287                 )
    288             )

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/core.py in find(specifier, condition, settings, random, database_key)
   1067         database_key=database_key,
   1068     )
-> 1069     runner.run()
   1070     note_engine_for_statistics(runner)
   1071     run_time = time.time() - start

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in run(self)
    380         with self.settings:
    381             try:
--> 382                 self._run()
    383             except RunIsComplete:
    384                 pass

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in _run(self)
    754
    755         self.reuse_existing_examples()
--> 756         self.generate_new_examples()
    757
    758         if (

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in generate_new_examples(self)
    655             draw_bytes=lambda data, n: self.__rewrite_for_novelty(
    656                 data, hbytes(n)))
--> 657         self.test_function(zero_data)
    658
    659         count = 0

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/engine.py in test_function(self, data)
    122         self.call_count += 1
    123         try:
--> 124             self._test_function(data)
    125             data.freeze()
    126         except StopTest as e:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/core.py in template_condition(data)
   1036             try:
   1037                 data.is_find = True
-> 1038                 result = data.draw(search)
   1039                 data.note(result)
   1040                 success = condition(result)

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    123             try:
    124                 sys.settrace(None)
--> 125                 return self.__draw(strategy)
    126             finally:
    127                 sys.settrace(original_tracer)

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/lazy.py in do_draw(self, data)
    165
    166     def do_draw(self, data):
--> 167         return data.draw(self.wrapped_strategy)

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/strategies.py in do_draw(self, data)
    505             i = data.index
    506             try:
--> 507                 return self.pack(data.draw(self.mapped_strategy))
    508             except UnsatisfiedAssumption:
    509                 if data.index == i:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/lazy.py in do_draw(self, data)
    165
    166     def do_draw(self, data):
--> 167         return data.draw(self.wrapped_strategy)

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in do_draw(self, data)
     58     def do_draw(self, data):
     59         return self.newtuple(
---> 60             data.draw(e) for e in self.element_strategies
     61         )
     62

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in newtuple(self, xs)
     54     def newtuple(self, xs):
     55         """Produce a new tuple of the correct type."""
---> 56         return tuple(xs)
     57
     58     def do_draw(self, data):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in <genexpr>(.0)
     58     def do_draw(self, data):
     59         return self.newtuple(
---> 60             data.draw(e) for e in self.element_strategies
     61         )
     62

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/strategies.py in do_draw(self, data)
    505             i = data.index
    506             try:
--> 507                 return self.pack(data.draw(self.mapped_strategy))
    508             except UnsatisfiedAssumption:
    509                 if data.index == i:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in do_draw(self, data)
     58     def do_draw(self, data):
     59         return self.newtuple(
---> 60             data.draw(e) for e in self.element_strategies
     61         )
     62

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in newtuple(self, xs)
     54     def newtuple(self, xs):
     55         """Produce a new tuple of the correct type."""
---> 56         return tuple(xs)
     57
     58     def do_draw(self, data):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/collections.py in <genexpr>(.0)
     58     def do_draw(self, data):
     59         return self.newtuple(
---> 60             data.draw(e) for e in self.element_strategies
     61         )
     62

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in draw(self, strategy)
    127                 sys.settrace(original_tracer)
    128         else:
--> 129             return self.__draw(strategy)
    130
    131     def __draw(self, strategy):

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/internal/conjecture/data.py in __draw(self, strategy)
    132         self.start_example()
    133         try:
--> 134             return strategy.do_draw(self)
    135         finally:
    136             if not self.frozen:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/searchstrategy/strategies.py in do_draw(self, data)
    505             i = data.index
    506             try:
--> 507                 return self.pack(data.draw(self.mapped_strategy))
    508             except UnsatisfiedAssumption:
    509                 if data.index == i:

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in <lambda>(value)
    871         kwargs[ms] = from_type(hints[ms])
    872     return tuples(tuples(*args), fixed_dictionaries(kwargs)).map(
--> 873         lambda value: target(*value[0], **value[1])
    874     )
    875

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in lazy_error()
    891
    892             def lazy_error():
--> 893                 raise error
    894
    895             return builds(lazy_error)

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in inner(*args, **kwargs)
    886     def inner(*args, **kwargs):
    887         try:
--> 888             return func(*args, **kwargs)
    889         except Exception as e:
    890             error = e

/Users/daenyth/.pyenv/versions/3.6.1/envs/obs-venv/lib/python3.6/site-packages/hypothesis/strategies.py in from_type(thing)
    938         except ImportError:  # pragma: no cover
    939             pass
--> 940         raise InvalidArgument('thing=%s must be a type' % (thing,))
    941     # Now that we know `thing` is a type, the first step is to check for an
    942     # explicitly registered strategy.  This is the best (and hopefully most

InvalidArgument: thing=<function NewType.<locals>.new_type at 0x107fd40d0> must be a type
```

</details>


Discussion:

Resolving patch:
diff --git a/src/hypothesis/strategies.py b/src/hypothesis/strategies.py
--- a/src/hypothesis/strategies.py
+++ b/src/hypothesis/strategies.py
@@ -21,7 +21,7 @@
 import datetime as dt
 import operator
 from decimal import Context, Decimal
-from inspect import isclass
+from inspect import isclass, isfunction
 from numbers import Rational
 from fractions import Fraction
 
@@ -927,11 +927,18 @@ def from_type(thing):
     """
     from hypothesis.searchstrategy import types
     if not isinstance(thing, type):
-        # Under Python 3.6, Unions are not instances of `type` - but we still
-        # want to resolve them!  This __origin__ check only passes if thing is
-        # a Union with parameters; if it doesn't we can't resolve it anyway.
         try:
+            # At runtime, `typing.NewType` returns an identity function rather
+            # than an actual type, but we can check that for a possible match
+            # and then read the magic attribute to unwrap it.
             import typing
+            if all([
+                hasattr(thing, '__supertype__'), hasattr(typing, 'NewType'),
+                isfunction(thing), getattr(thing, '__module__', 0) == 'typing'
+            ]):
+                return from_type(thing.__supertype__)
+            # Under Python 3.6, Unions are not instances of `type` - but we
+            # still want to resolve them!
             if getattr(thing, '__origin__', None) is typing.Union:
                 args = sorted(thing.__args__, key=types.type_sorting_key)
                 return one_of([from_type(t) for t in args])


Test patch:
diff --git a/tests/py3/test_lookup.py b/tests/py3/test_lookup.py
--- a/tests/py3/test_lookup.py
+++ b/tests/py3/test_lookup.py
@@ -28,7 +28,7 @@
 from hypothesis.errors import NoExamples, InvalidArgument, ResolutionFailed
 from hypothesis.strategies import from_type
 from hypothesis.searchstrategy import types
-from hypothesis.internal.compat import get_type_hints
+from hypothesis.internal.compat import integer_types, get_type_hints
 
 typing = pytest.importorskip('typing')
 sentinel = object()
@@ -299,3 +299,13 @@ def inner(a: UnknownType):
         pass
     with pytest.raises(InvalidArgument):
         inner()
+
+
+@pytest.mark.skipif(not hasattr(typing, 'NewType'), reason='test for NewType')
+def test_resolves_NewType():
+    typ = typing.NewType('T', int)
+    nested = typing.NewType('NestedT', typ)
+    uni = typing.NewType('UnionT', typing.Optional[int])
+    assert isinstance(from_type(typ).example(), integer_types)
+    assert isinstance(from_type(nested).example(), integer_types)
+    assert isinstance(from_type(uni).example(), integer_types + (type(None),))