Skip to content

Commit

Permalink
Merge pull request #3201 from HypothesisWorks/disallow-type-alias
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD authored Dec 30, 2021
2 parents a583d36 + 7472782 commit 76847e3
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 4 deletions.
12 changes: 12 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
RELEASE_TYPE: minor

This release disallows using :obj:`python:typing.TypeAlias`
with :func:`~hypothesis.strategies.from_type`
and :func:`~hypothesis.strategies.register_type_strategy`.

Why? Because ``TypeAlias`` is not really a type,
it is a tag for type checkers that some expression is a type alias,
not something else.

It does not make sense for Hypothesis to resolve it as a strategy.
References :issue:`2978`.
2 changes: 1 addition & 1 deletion hypothesis-python/scripts/basic-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pip install fakeredis
$PYTEST tests/redis/
pip uninstall -y redis fakeredis

pip install 'typing_extensions!=3.10.0.1'
pip install 'typing_extensions>=4.0.0'
$PYTEST tests/typing_extensions/
if [ "$(python -c 'import sys; print(sys.version_info[:2] == (3, 7))')" = "False" ] ; then
# Required by importlib_metadata backport, which we don't want to break
Expand Down
3 changes: 2 additions & 1 deletion hypothesis-python/src/hypothesis/extra/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@


class RedisExampleDatabase(ExampleDatabase):
"""Store Hypothesis examples as sets in the given :class:`redis.Redis` datastore.
"""Store Hypothesis examples as sets in the given :class:`~redis.client.Redis`
datastore.
This is particularly useful for shared databases, as per the recipe
for a :class:`~hypothesis.database.MultiplexedDatabase`.
Expand Down
11 changes: 11 additions & 0 deletions hypothesis-python/src/hypothesis/strategies/_internal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,12 @@ def as_strategy(strat_or_callable, thing, final=True):
"strings."
)
raise InvalidArgument(f"thing={thing!r} must be a type") # pragma: no cover
if thing in types.TypeAliasTypes:
# Code like `st.from_type(TypeAlias)` does not make sense.
raise InvalidArgument(
"Cannot resolve TypeAlias to a strategy, "
"because there are no instances of it at runtime"
)
# Now that we know `thing` is a type, the first step is to check for an
# explicitly registered strategy. This is the best (and hopefully most
# common) way to resolve a type to a strategy. Note that the value in the
Expand Down Expand Up @@ -1751,6 +1757,11 @@ def register_type_strategy(

if not types.is_a_type(custom_type):
raise InvalidArgument(f"custom_type={custom_type!r} must be a type")
elif custom_type in types.TypeAliasTypes:
raise InvalidArgument(
f"custom_type={custom_type!r} is not allowed to be registered, "
"because there is no such thing as a runtime instance of TypeAlias"
)
elif not (isinstance(strategy, SearchStrategy) or callable(strategy)):
raise InvalidArgument(
"strategy=%r must be a SearchStrategy, or a function that takes "
Expand Down
10 changes: 10 additions & 0 deletions hypothesis-python/src/hypothesis/strategies/_internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@
except ImportError:
_AnnotatedAlias = ()

TypeAliasTypes: tuple = ()
try:
TypeAliasTypes += (typing.TypeAlias,) # type: ignore
except AttributeError:
pass # Is missing for `python<3.10`
try:
TypeAliasTypes += (typing_extensions.TypeAlias,)
except AttributeError: # pragma: no cover
pass # Is missing for `typing_extensions<3.10`


# We use this variable to be sure that we are working with a type from `typing`:
typing_root_type = (typing._Final, typing._GenericAlias) # type: ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,23 @@
# obtain one at https://mozilla.org/MPL/2.0/.

import collections
import sys
import typing
from typing import Dict, List, Union

import pytest
from typing_extensions import Annotated, DefaultDict, Literal, NewType, Type, TypedDict
from typing_extensions import (
Annotated,
DefaultDict,
Literal,
NewType,
Type,
TypeAlias,
TypedDict,
)

from hypothesis import assume, given, strategies as st
from hypothesis.errors import InvalidArgument
from hypothesis.strategies import from_type


Expand Down Expand Up @@ -126,3 +137,24 @@ def test_annotated_with_two_strategies(data):
@given(st.data())
def test_annotated_extra_metadata(data):
assert data.draw(st.from_type(ExtraAnnotationNoStrategy)) > 0


@pytest.mark.parametrize(
"type_alias_type",
[
TypeAlias, # It is always available from recent versions of `typing_extensions`
pytest.param(
getattr(typing, "TypeAlias", None),
marks=pytest.mark.skipif(
sys.version_info < (3, 10), reason="TypeAlias was added in 3.10"
),
),
],
)
def test_type_alias_type(type_alias_type):
strategy = st.from_type(type_alias_type)
with pytest.raises(InvalidArgument, match="Cannot resolve TypeAlias to a strategy"):
strategy.example()

with pytest.raises(InvalidArgument, match="is not allowed to be registered"):
st.register_type_strategy(type_alias_type, st.none())
2 changes: 1 addition & 1 deletion hypothesis-python/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ commands_pre =
ignore_errors = true
commands =
python -m coverage run --rcfile=.coveragerc --source=hypothesis -m pytest -n0 --strict-markers --ff {posargs} \
tests/cover tests/conjecture tests/datetime tests/numpy tests/pandas tests/lark tests/redis tests/dpcontracts tests/codemods
tests/cover tests/conjecture tests/datetime tests/numpy tests/pandas tests/lark tests/redis tests/dpcontracts tests/codemods tests/typing_extensions
python -m coverage report -m --fail-under=100 --show-missing --skip-covered
python scripts/validate_branch_check.py

Expand Down
1 change: 1 addition & 0 deletions requirements/coverage.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ numpy
pandas
python-dateutil
pytz
typing-extensions
-r test.in
1 change: 1 addition & 0 deletions requirements/coverage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ tomli==1.2.3
# via black
typing-extensions==4.0.1
# via
# -r requirements/coverage.in
# black
# libcst
# typing-inspect
Expand Down

0 comments on commit 76847e3

Please sign in to comment.