Skip to content

Commit d3bf9c5

Browse files
authored
Merge pull request #3285 from silasary/forward_typed_dict
2 parents 04be98e + 8235f70 commit d3bf9c5

File tree

6 files changed

+83
-13
lines changed

6 files changed

+83
-13
lines changed

AUTHORS.rst

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ their individual contributions.
7979
* `jwg4 <https://www.github.com/jwg4>`_
8080
* `Kai Chen <https://www.github.com/kx-chen>`_ ([email protected])
8181
* `Karthikeyan Singaravelan <https://www.github.com/tirkarthi>`_ ([email protected])
82+
* `Katelyn Gigante <https://github.com/silasary>`_
8283
* `Katrina Durance <https://github.com/kdurance>`_
8384
* `kbara <https://www.github.com/kbara>`_
8485
* `Kristian Glass <https://www.github.com/doismellburning>`_

hypothesis-python/RELEASE.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch fixes :func:`~hypothesis.strategies.from_type` on a :class:`~python:typing.TypedDict`
4+
with complex annotations, defined in a file using ``from __future__ import annotations``.
5+
Thanks to Katelyn Gigante for identifying and fixing this bug!

hypothesis-python/src/hypothesis/strategies/_internal/core.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,13 @@ def as_strategy(strat_or_callable, thing, final=True):
10761076
# way to tell and we just have to assume that everything is required.
10771077
# See https://github.com/python/cpython/pull/17214 for details.
10781078
optional = getattr(thing, "__optional_keys__", ())
1079-
anns = {k: from_type(v) for k, v in thing.__annotations__.items()}
1079+
anns = {k: from_type(v) for k, v in get_type_hints(thing).items()}
1080+
if (
1081+
(not anns)
1082+
and thing.__annotations__
1083+
and ".<locals>." in getattr(thing, "__qualname__", "")
1084+
):
1085+
raise InvalidArgument("Failed to retrieve type annotations for local type")
10801086
return fixed_dictionaries( # type: ignore
10811087
mapping={k: v for k, v in anns.items() if k not in optional},
10821088
optional={k: v for k, v in anns.items() if k in optional},

hypothesis-python/src/hypothesis/strategies/_internal/strategies.py

+17-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import sys
1212
import warnings
13-
from collections import defaultdict
13+
from collections import abc, defaultdict
1414
from random import shuffle
1515
from typing import (
1616
Any,
@@ -811,17 +811,22 @@ def pack(self, x):
811811
raise NotImplementedError(f"{self.__class__.__name__}.pack()")
812812

813813
def do_draw(self, data: ConjectureData) -> Ex:
814-
for _ in range(3):
815-
i = data.index
816-
try:
817-
data.start_example(MAPPED_SEARCH_STRATEGY_DO_DRAW_LABEL)
818-
result = self.pack(data.draw(self.mapped_strategy))
819-
data.stop_example()
820-
return result
821-
except UnsatisfiedAssumption:
822-
data.stop_example(discard=True)
823-
if data.index == i:
824-
raise
814+
with warnings.catch_warnings():
815+
if isinstance(self.pack, type) and issubclass(
816+
self.pack, (abc.Mapping, abc.Set)
817+
):
818+
warnings.simplefilter("ignore", BytesWarning)
819+
for _ in range(3):
820+
i = data.index
821+
try:
822+
data.start_example(MAPPED_SEARCH_STRATEGY_DO_DRAW_LABEL)
823+
result = self.pack(data.draw(self.mapped_strategy))
824+
data.stop_example()
825+
return result
826+
except UnsatisfiedAssumption:
827+
data.stop_example(discard=True)
828+
if data.index == i:
829+
raise
825830
raise UnsatisfiedAssumption()
826831

827832
@property

hypothesis-python/tests/cover/test_lookup.py

+5
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,11 @@ def test_generic_collections_only_use_hashable_elements(typ, data):
713713
data.draw(from_type(typ))
714714

715715

716+
@given(st.sets(st.integers() | st.binary(), min_size=2))
717+
def test_no_byteswarning(_):
718+
pass
719+
720+
716721
def test_hashable_type_unhashable_value():
717722
# Decimal("snan") is not hashable; we should be able to generate it.
718723
# See https://github.com/HypothesisWorks/hypothesis/issues/2320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# This file is part of Hypothesis, which may be found at
2+
# https://github.com/HypothesisWorks/hypothesis/
3+
#
4+
# Copyright the Hypothesis Authors.
5+
# Individual contributors are listed in AUTHORS.rst and the git log.
6+
#
7+
# This Source Code Form is subject to the terms of the Mozilla Public License,
8+
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9+
# obtain one at https://mozilla.org/MPL/2.0/.
10+
11+
from __future__ import annotations
12+
13+
from typing import TypedDict, Union
14+
15+
import pytest
16+
17+
from hypothesis import given, strategies as st
18+
from hypothesis.errors import InvalidArgument
19+
20+
alias = Union[int, str]
21+
22+
23+
class A(TypedDict):
24+
a: int
25+
26+
27+
class B(TypedDict):
28+
a: A
29+
b: alias
30+
31+
32+
@given(st.from_type(B))
33+
def test_complex_forward_ref_in_typed_dict(d):
34+
assert isinstance(d["a"], dict)
35+
assert isinstance(d["a"]["a"], int)
36+
assert isinstance(d["b"], (int, str))
37+
38+
39+
def test_complex_forward_ref_in_typed_dict_local():
40+
local_alias = Union[int, str]
41+
42+
class C(TypedDict):
43+
a: A
44+
b: local_alias
45+
46+
c_strategy = st.from_type(C)
47+
with pytest.raises(InvalidArgument):
48+
c_strategy.example()

0 commit comments

Comments
 (0)