diff --git a/AUTHORS.rst b/AUTHORS.rst index 60987ab370..ff0c99ac00 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -73,6 +73,7 @@ their individual contributions. * `Gregory Petrosyan `_ * `Grzegorz Zieba `_ (g.zieba@erax.pl) * `Grigorios Giannakopoulos `_ +* `Hal Blackburn `_ * `Hugo van Kemenade `_ * `Humberto Rocha `_ * `Ilya Lebedev `_ (melevir@gmail.com) diff --git a/HypothesisWorks.github.io/_posts/2016-05-26-exploring-voting-with-hypothesis.md b/HypothesisWorks.github.io/_posts/2016-05-26-exploring-voting-with-hypothesis.md index 472705c165..502f84a3f0 100644 --- a/HypothesisWorks.github.io/_posts/2016-05-26-exploring-voting-with-hypothesis.md +++ b/HypothesisWorks.github.io/_posts/2016-05-26-exploring-voting-with-hypothesis.md @@ -87,7 +87,7 @@ some point). If we have zero, that's a draw. If we have one, that's a victory. It seems pretty plausible that these would not produce the same answer -all the time (it would be surpising if they did!), but it's maybe not +all the time (it would be surprising if they did!), but it's maybe not obvious how you would go about constructing an example that shows it. Fortunately, we don't have to because Hypothesis can do it for us! diff --git a/brand/README.rst b/brand/README.rst index afbe05f0c2..2d2227dc26 100644 --- a/brand/README.rst +++ b/brand/README.rst @@ -38,7 +38,7 @@ Colour palette in GIMP format A `colour palette in GIMP format `__ (``.gpl``) is also provided with the intent of making it easier to produce graphics and documents which -re-use the colours in the Hypothesis Dragonfly logo by Libby Berrie. +reuse the colours in the Hypothesis Dragonfly logo by Libby Berrie. The ``hypothesis.gpl`` file should be copied or imported to the appropriate location on your filesystem. For example: diff --git a/hypothesis-python/docs/changes.rst b/hypothesis-python/docs/changes.rst index 913670e3f8..6ffba2c9d4 100644 --- a/hypothesis-python/docs/changes.rst +++ b/hypothesis-python/docs/changes.rst @@ -18,6 +18,16 @@ Hypothesis 6.x .. include:: ../RELEASE.rst +.. _v6.88.0: + +------------------- +6.88.0 - 2023-10-15 +------------------- + +This release allows strategy-generating functions registered with +:func:`~hypothesis.strategies.register_type_strategy` to conditionally not +return a strategy, by returning :data:`NotImplemented` (:issue:`3767`). + .. _v6.87.4: ------------------- diff --git a/hypothesis-python/examples/test_basic.py b/hypothesis-python/examples/test_basic.py index 3e3bc762d5..45d4a206bd 100644 --- a/hypothesis-python/examples/test_basic.py +++ b/hypothesis-python/examples/test_basic.py @@ -20,7 +20,7 @@ def get_discount_price(self, discount_percentage: float): return self.price * (discount_percentage / 100) -# The @given decorater generates examples for us! +# The @given decorator generates examples for us! @given( price=st.floats(min_value=0, allow_nan=False, allow_infinity=False), discount_percentage=st.floats( diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py index a01fc7486e..70a58866cb 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py @@ -1235,6 +1235,8 @@ def as_strategy(strat_or_callable, thing): strategy = strat_or_callable(thing) else: strategy = strat_or_callable + if strategy is NotImplemented: + return NotImplemented if not isinstance(strategy, SearchStrategy): raise ResolutionFailed( f"Error: {thing} was registered for {nicerepr(strat_or_callable)}, " @@ -1277,7 +1279,9 @@ def from_type_guarded(thing): # Check if we have an explicitly registered strategy for this thing, # resolve it so, and otherwise resolve as for the base type. if thing in types._global_type_lookup: - return as_strategy(types._global_type_lookup[thing], thing) + strategy = as_strategy(types._global_type_lookup[thing], thing) + if strategy is not NotImplemented: + return strategy return _from_type(thing.__supertype__) # Unions are not instances of `type` - but we still want to resolve them! if types.is_a_union(thing): @@ -1287,7 +1291,9 @@ def from_type_guarded(thing): # They are represented as instances like `~T` when they come here. # We need to work with their type instead. if isinstance(thing, TypeVar) and type(thing) in types._global_type_lookup: - return as_strategy(types._global_type_lookup[type(thing)], thing) + strategy = as_strategy(types._global_type_lookup[type(thing)], thing) + if strategy is not NotImplemented: + return strategy if not types.is_a_type(thing): if isinstance(thing, str): # See https://github.com/HypothesisWorks/hypothesis/issues/3016 @@ -1312,7 +1318,9 @@ def from_type_guarded(thing): # convert empty results into an explicit error. try: if thing in types._global_type_lookup: - return as_strategy(types._global_type_lookup[thing], thing) + strategy = as_strategy(types._global_type_lookup[thing], thing) + if strategy is not NotImplemented: + return strategy except TypeError: # pragma: no cover # This is due to a bizarre divergence in behaviour under Python 3.9.0: # typing.Callable[[], foo] has __args__ = (foo,) but collections.abc.Callable @@ -1372,11 +1380,16 @@ def from_type_guarded(thing): # type. For example, `Number -> integers() | floats()`, but bools() is # not included because bool is a subclass of int as well as Number. strategies = [ - as_strategy(v, thing) - for k, v in sorted(types._global_type_lookup.items(), key=repr) - if isinstance(k, type) - and issubclass(k, thing) - and sum(types.try_issubclass(k, typ) for typ in types._global_type_lookup) == 1 + s + for s in ( + as_strategy(v, thing) + for k, v in sorted(types._global_type_lookup.items(), key=repr) + if isinstance(k, type) + and issubclass(k, thing) + and sum(types.try_issubclass(k, typ) for typ in types._global_type_lookup) + == 1 + ) + if s is not NotImplemented ] if any(not s.is_empty for s in strategies): return one_of(strategies) @@ -2142,7 +2155,10 @@ def register_type_strategy( for an argument with a default value. ``strategy`` may be a search strategy, or a function that takes a type and - returns a strategy (useful for generic types). + returns a strategy (useful for generic types). The function may return + :data:`NotImplemented` to conditionally not provide a strategy for the type + (the type will still be resolved by other methods, if possible, as if the + function was not registered). Note that you may not register a parametrised generic type (such as ``MyCollection[int]``) directly, because the resolution logic does not diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/types.py b/hypothesis-python/src/hypothesis/strategies/_internal/types.py index 34831ff8ba..51185fb38f 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/types.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/types.py @@ -451,9 +451,13 @@ def from_typing_type(thing): mapping.pop(t) # Sort strategies according to our type-sorting heuristic for stable output strategies = [ - v if isinstance(v, st.SearchStrategy) else v(thing) - for k, v in sorted(mapping.items(), key=lambda kv: type_sorting_key(kv[0])) - if sum(try_issubclass(k, T) for T in mapping) == 1 + s + for s in ( + v if isinstance(v, st.SearchStrategy) else v(thing) + for k, v in sorted(mapping.items(), key=lambda kv: type_sorting_key(kv[0])) + if sum(try_issubclass(k, T) for T in mapping) == 1 + ) + if s != NotImplemented ] empty = ", ".join(repr(s) for s in strategies if s.is_empty) if empty or not strategies: @@ -491,6 +495,14 @@ def _networks(bits): # As a general rule, we try to limit this to scalars because from_type() # would have to decide on arbitrary collection elements, and we'd rather # not (with typing module generic types and some builtins as exceptions). +# +# Strategy Callables may return NotImplemented, which should be treated in the +# same way as if the type was not registered. +# +# Note that NotImplemented cannot be typed in Python 3.8 because there's no type +# exposed for it, and NotImplemented itself is typed as Any so that it can be +# returned without being listed in a function signature: +# https://github.com/python/mypy/issues/6710#issuecomment-485580032 _global_type_lookup: typing.Dict[ type, typing.Union[st.SearchStrategy, typing.Callable[[type], st.SearchStrategy]] ] = { diff --git a/hypothesis-python/src/hypothesis/version.py b/hypothesis-python/src/hypothesis/version.py index 509f373ec3..4f14552eea 100644 --- a/hypothesis-python/src/hypothesis/version.py +++ b/hypothesis-python/src/hypothesis/version.py @@ -8,5 +8,5 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -__version_info__ = (6, 87, 4) +__version_info__ = (6, 88, 0) __version__ = ".".join(map(str, __version_info__)) diff --git a/hypothesis-python/tests/array_api/test_arrays.py b/hypothesis-python/tests/array_api/test_arrays.py index 08d6750774..756576a730 100644 --- a/hypothesis-python/tests/array_api/test_arrays.py +++ b/hypothesis-python/tests/array_api/test_arrays.py @@ -391,7 +391,7 @@ def test_generate_unique_arrays_without_fill(xp, xps): Covers the collision-related branches for fully dense unique arrays. Choosing 25 of 256 possible values means we're almost certain to see - colisions thanks to the birthday paradox, but finding unique values should + collisions thanks to the birthday paradox, but finding unique values should still be easy. """ skip_on_missing_unique_values(xp) diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py index 5d63b0d58d..9801d6d3cf 100644 --- a/hypothesis-python/tests/cover/test_lookup.py +++ b/hypothesis-python/tests/cover/test_lookup.py @@ -11,6 +11,7 @@ import abc import builtins import collections +import contextlib import datetime import enum import inspect @@ -21,6 +22,7 @@ import sys import typing import warnings +from dataclasses import dataclass from inspect import signature from numbers import Real @@ -376,6 +378,25 @@ def test_typevars_can_be_redefine_with_factory(): assert_all_examples(st.from_type(A), lambda obj: obj == "A") +def test_typevars_can_be_resolved_conditionally(): + sentinel = object() + A = typing.TypeVar("A") + B = typing.TypeVar("B") + + def resolve_type_var(thing): + assert thing in (A, B) + if thing == A: + return st.just(sentinel) + return NotImplemented + + with temp_registered(typing.TypeVar, resolve_type_var): + assert st.from_type(A).example() is sentinel + # We've re-defined the default TypeVar resolver, so there is no fallback. + # This causes the lookup to fail. + with pytest.raises(InvalidArgument): + st.from_type(B).example() + + def annotated_func(a: int, b: int = 2, *, c: int, d: int = 4): return a + b + c + d @@ -470,6 +491,24 @@ def test_resolves_NewType(): assert isinstance(from_type(uni).example(), (int, type(None))) +@pytest.mark.parametrize("is_handled", [True, False]) +def test_resolves_NewType_conditionally(is_handled): + sentinel = object() + typ = typing.NewType("T", int) + + def resolve_custom_strategy(thing): + assert thing is typ + if is_handled: + return st.just(sentinel) + return NotImplemented + + with temp_registered(typ, resolve_custom_strategy): + if is_handled: + assert st.from_type(typ).example() is sentinel + else: + assert isinstance(st.from_type(typ).example(), int) + + E = enum.Enum("E", "a b c") @@ -807,6 +846,58 @@ def test_supportsop_types_support_protocol(protocol, data): assert issubclass(type(value), protocol) +@pytest.mark.parametrize("restrict_custom_strategy", [True, False]) +def test_generic_aliases_can_be_conditionally_resolved_by_registered_function( + restrict_custom_strategy, +): + # Check that a custom strategy function may provide no strategy for a + # generic alias request like Container[T]. We test this under two scenarios: + # - where CustomContainer CANNOT be generated from requests for Container[T] + # (only for requests for exactly CustomContainer[T]) + # - where CustomContainer CAN be generated from requests for Container[T] + T = typing.TypeVar("T") + + @dataclass + class CustomContainer(typing.Container[T]): + content: T + + def __contains__(self, value: object) -> bool: + return self.content == value + + def get_custom_container_strategy(thing): + if restrict_custom_strategy and typing.get_origin(thing) != CustomContainer: + return NotImplemented + return st.builds( + CustomContainer, content=st.from_type(typing.get_args(thing)[0]) + ) + + with temp_registered(CustomContainer, get_custom_container_strategy): + + def is_custom_container_with_str(example): + return isinstance(example, CustomContainer) and isinstance( + example.content, str + ) + + def is_non_custom_container(example): + return isinstance(example, typing.Container) and not isinstance( + example, CustomContainer + ) + + assert_all_examples( + st.from_type(CustomContainer[str]), is_custom_container_with_str + ) + # If the strategy function is restricting, it doesn't return a strategy + # for requests for Container[...], so it's never generated. When not + # restricting, it is generated. + if restrict_custom_strategy: + assert_all_examples( + st.from_type(typing.Container[str]), is_non_custom_container + ) + else: + find_any(st.from_type(typing.Container[str]), is_custom_container_with_str) + find_any(st.from_type(typing.Container[str]), is_non_custom_container) + + @pytest.mark.parametrize( "protocol, typ", [ @@ -1069,3 +1160,31 @@ def test_tuple_subclasses_not_generic_sequences(): with temp_registered(TupleSubtype, st.builds(TupleSubtype)): s = st.from_type(typing.Sequence[int]) assert_no_examples(s, lambda x: isinstance(x, tuple)) + + +def test_custom_strategy_function_resolves_types_conditionally(): + sentinel = object() + + class A: + pass + + class B(A): + pass + + class C(A): + pass + + def resolve_custom_strategy_for_b(thing): + if thing == B: + return st.just(sentinel) + return NotImplemented + + with contextlib.ExitStack() as stack: + stack.enter_context(temp_registered(B, resolve_custom_strategy_for_b)) + stack.enter_context(temp_registered(C, st.builds(C))) + + # C's strategy can be used for A, but B's cannot because its function + # only returns a strategy for requests for exactly B. + assert_all_examples(st.from_type(A), lambda example: type(example) == C) + assert_all_examples(st.from_type(B), lambda example: example is sentinel) + assert_all_examples(st.from_type(C), lambda example: type(example) == C) diff --git a/hypothesis-python/tests/cover/test_targeting.py b/hypothesis-python/tests/cover/test_targeting.py index c645643f9d..a80bab561c 100644 --- a/hypothesis-python/tests/cover/test_targeting.py +++ b/hypothesis-python/tests/cover/test_targeting.py @@ -102,21 +102,3 @@ def test_cannot_target_default_label_twice(_): target(0.0) with pytest.raises(InvalidArgument): target(1.0) - - -@given(st.lists(st.integers()), st.none()) -def test_targeting_with_following_empty(ls, n): - # This exercises some logic in the optimiser that prevents it from trying - # to mutate empty examples at the end of the test case. - target(float(len(ls))) - - -@given( - st.tuples( - *([st.none()] * 10 + [st.integers()] + [st.none()] * 10 + [st.integers()]) - ) -) -def test_targeting_with_many_empty(_): - # This exercises some logic in the optimiser that prevents it from trying - # to mutate empty examples in the middle of the test case. - target(1.0) diff --git a/hypothesis-python/tests/cover/test_type_lookup.py b/hypothesis-python/tests/cover/test_type_lookup.py index 1b913a1672..0071718e86 100644 --- a/hypothesis-python/tests/cover/test_type_lookup.py +++ b/hypothesis-python/tests/cover/test_type_lookup.py @@ -95,10 +95,19 @@ def test_lookup_keys_are_types(): assert "int" not in types._global_type_lookup -def test_lookup_values_are_strategies(): +@pytest.mark.parametrize( + "typ, not_a_strategy", + [ + (int, 42), # Values must be strategies + # Can't register NotImplemented directly, even though strategy functions + # can return it. + (int, NotImplemented), + ], +) +def test_lookup_values_are_strategies(typ, not_a_strategy): with pytest.raises(InvalidArgument): - st.register_type_strategy(int, 42) - assert 42 not in types._global_type_lookup.values() + st.register_type_strategy(typ, not_a_strategy) + assert not_a_strategy not in types._global_type_lookup.values() @pytest.mark.parametrize("typ", sorted(types_with_core_strat, key=str)) @@ -147,6 +156,24 @@ def test_custom_type_resolution_with_function_non_strategy(): st.from_type(ParentUnknownType).example() +@pytest.mark.parametrize("strategy_returned", [True, False]) +def test_conditional_type_resolution_with_function(strategy_returned): + sentinel = object() + + def resolve_strategy(thing): + assert thing == UnknownType + if strategy_returned: + return st.just(sentinel) + return NotImplemented + + with temp_registered(UnknownType, resolve_strategy): + if strategy_returned: + assert st.from_type(UnknownType).example() is sentinel + else: + with pytest.raises(ResolutionFailed): + st.from_type(UnknownType).example() + + def test_errors_if_generic_resolves_empty(): with temp_registered(UnknownType, lambda _: st.nothing()): fails_1 = st.from_type(UnknownType) diff --git a/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py b/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py index a682b6a4d2..46e55c92b6 100644 --- a/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py +++ b/hypothesis-python/tests/cover/test_type_lookup_forward_ref.py @@ -54,7 +54,7 @@ def test_bound_correct_forward_ref(built): assert isinstance(built, int) -# Alises: +# Aliases: _Alias = TypeVar("_Alias ", bound="OurAlias") diff --git a/hypothesis-python/tests/nocover/test_recursive.py b/hypothesis-python/tests/nocover/test_recursive.py index 783cbfa31b..c6b0c640a7 100644 --- a/hypothesis-python/tests/nocover/test_recursive.py +++ b/hypothesis-python/tests/nocover/test_recursive.py @@ -177,6 +177,7 @@ def test(data): ) +@settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.filter_too_much]) @given(SELF_REF) def test_self_ref_regression(_): # See https://github.com/HypothesisWorks/hypothesis/issues/2794 diff --git a/hypothesis-python/tests/numpy/test_gen_data.py b/hypothesis-python/tests/numpy/test_gen_data.py index 20dc5b56fe..56fe1c84f8 100644 --- a/hypothesis-python/tests/numpy/test_gen_data.py +++ b/hypothesis-python/tests/numpy/test_gen_data.py @@ -439,7 +439,7 @@ def test_unique_array_with_fill_can_use_all_elements(arr): @given(nps.arrays(dtype="uint8", shape=25, unique=True, fill=st.nothing())) def test_unique_array_without_fill(arr): # This test covers the collision-related branches for fully dense unique arrays. - # Choosing 25 of 256 possible elements means we're almost certain to see colisions + # Choosing 25 of 256 possible elements means we're almost certain to see collisions # thanks to the 'birthday paradox', but finding unique elemennts is still easy. assume(len(set(arr)) == arr.size) diff --git a/hypothesis-python/tests/typing_extensions/test_backported_types.py b/hypothesis-python/tests/typing_extensions/test_backported_types.py index ddfdd2e155..b1031e39df 100644 --- a/hypothesis-python/tests/typing_extensions/test_backported_types.py +++ b/hypothesis-python/tests/typing_extensions/test_backported_types.py @@ -71,7 +71,7 @@ def test_typing_extensions_Type_int(): assert from_type(Type[int]).example() is int -@given(from_type(Type[Union[str, list]])) +@given(from_type(Union[Type[str], Type[list]])) def test_typing_extensions_Type_Union(ex): assert ex in (str, list) diff --git a/requirements/coverage.txt b/requirements/coverage.txt index 0e48279260..ec4f7679f3 100644 --- a/requirements/coverage.txt +++ b/requirements/coverage.txt @@ -14,7 +14,7 @@ click==8.1.7 # via # -r requirements/coverage.in # black -coverage==7.3.1 +coverage==7.3.2 # via -r requirements/coverage.in dpcontracts==0.6.0 # via -r requirements/coverage.in @@ -30,17 +30,17 @@ iniconfig==2.0.0 # via pytest lark==1.1.7 # via -r requirements/coverage.in -libcst==1.0.1 +libcst==1.1.0 # via -r requirements/coverage.in mypy-extensions==1.0.0 # via # black # typing-inspect -numpy==1.26.0 +numpy==1.26.1 # via # -r requirements/coverage.in # pandas -packaging==23.1 +packaging==23.2 # via # black # pytest @@ -50,7 +50,7 @@ pathspec==0.11.2 # via black pexpect==4.8.0 # via -r requirements/test.in -platformdirs==3.10.0 +platformdirs==3.11.0 # via black pluggy==1.3.0 # via pytest diff --git a/requirements/fuzzing.txt b/requirements/fuzzing.txt index a2bf39951b..9dc731bd83 100644 --- a/requirements/fuzzing.txt +++ b/requirements/fuzzing.txt @@ -27,11 +27,11 @@ click==8.1.7 # black # flask # hypothesis -coverage==7.3.1 +coverage==7.3.2 # via # -r requirements/coverage.in # hypofuzz -dash==2.13.0 +dash==2.14.0 # via hypofuzz dash-core-components==2.0.0 # via dash @@ -54,10 +54,12 @@ flask==2.2.5 # via dash hypofuzz==23.7.1 # via -r requirements/fuzzing.in -hypothesis[cli]==6.87.0 +hypothesis[cli]==6.87.4 # via hypofuzz idna==3.4 # via requests +importlib-metadata==6.8.0 + # via dash iniconfig==2.0.0 # via pytest itsdangerous==2.1.2 @@ -66,7 +68,7 @@ jinja2==3.1.2 # via flask lark==1.1.7 # via -r requirements/coverage.in -libcst==1.0.1 +libcst==1.1.0 # via # -r requirements/coverage.in # hypofuzz @@ -84,11 +86,11 @@ mypy-extensions==1.0.0 # typing-inspect nest-asyncio==1.5.8 # via dash -numpy==1.26.0 +numpy==1.26.1 # via # -r requirements/coverage.in # pandas -packaging==23.1 +packaging==23.2 # via # black # plotly @@ -101,7 +103,7 @@ pathspec==0.11.2 # via black pexpect==4.8.0 # via -r requirements/test.in -platformdirs==3.10.0 +platformdirs==3.11.0 # via black plotly==5.17.0 # via dash @@ -166,12 +168,14 @@ typing-inspect==0.9.0 # via libcst tzdata==2023.3 # via pandas -urllib3==2.0.5 +urllib3==2.0.6 # via requests werkzeug==2.2.3 # via # dash # flask +zipp==3.17.0 + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==68.2.2 diff --git a/requirements/test.txt b/requirements/test.txt index 4a97655b8b..3ce63bcc4c 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -14,7 +14,7 @@ execnet==2.0.2 # via pytest-xdist iniconfig==2.0.0 # via pytest -packaging==23.1 +packaging==23.2 # via pytest pexpect==4.8.0 # via -r requirements/test.in diff --git a/requirements/tools.txt b/requirements/tools.txt index 1da4da7739..9e4eae4c83 100644 --- a/requirements/tools.txt +++ b/requirements/tools.txt @@ -14,7 +14,7 @@ attrs==23.1.0 # via hypothesis (hypothesis-python/setup.py) autoflake==2.2.1 # via shed -babel==2.12.1 +babel==2.13.0 # via sphinx backcall==0.2.0 # via ipython @@ -38,13 +38,13 @@ click==8.1.7 # via # black # pip-tools -codespell==2.2.5 +codespell==2.2.6 # via -r requirements/tools.in colorama==0.4.6 # via tox com2ann==0.3.0 # via shed -coverage==7.3.1 +coverage==7.3.2 # via -r requirements/tools.in cryptography==41.0.4 # via @@ -55,7 +55,7 @@ decorator==5.1.1 # via ipython distlib==0.3.7 # via virtualenv -django==4.2.5 +django==4.2.6 # via -r requirements/tools.in docutils==0.18.1 # via @@ -86,13 +86,13 @@ importlib-metadata==6.8.0 # twine iniconfig==2.0.0 # via pytest -ipython==8.16.0 +ipython==8.16.1 # via -r requirements/tools.in isort==5.12.0 # via shed jaraco-classes==3.3.0 # via keyring -jedi==0.19.0 +jedi==0.19.1 # via ipython jeepney==0.8.0 # via @@ -104,7 +104,7 @@ keyring==24.2.0 # via twine lark==1.1.7 # via -r requirements/tools.in -libcst==1.0.1 +libcst==1.1.0 # via # -r requirements/tools.in # shed @@ -118,7 +118,7 @@ mdurl==0.1.2 # via markdown-it-py more-itertools==10.1.0 # via jaraco-classes -mypy==1.5.1 +mypy==1.6.0 # via -r requirements/tools.in mypy-extensions==1.0.0 # via @@ -129,7 +129,7 @@ nh3==0.2.14 # via readme-renderer nodeenv==1.8.0 # via pyright -packaging==23.1 +packaging==23.2 # via # black # build @@ -149,7 +149,7 @@ pip-tools==7.3.0 # via -r requirements/tools.in pkginfo==1.9.6 # via twine -platformdirs==3.10.0 +platformdirs==3.11.0 # via # black # tox @@ -178,13 +178,13 @@ pyproject-api==1.6.1 # via tox pyproject-hooks==1.0.0 # via build -pyright==1.1.329 +pyright==1.1.331 # via -r requirements/tools.in pytest==7.4.2 # via -r requirements/tools.in python-dateutil==2.8.2 # via -r requirements/tools.in -pyupgrade==3.13.0 +pyupgrade==3.15.0 # via shed pyyaml==6.0.1 # via libcst @@ -204,7 +204,7 @@ rfc3986==2.0.0 # via twine rich==13.6.0 # via twine -ruff==0.0.291 +ruff==0.0.292 # via -r requirements/tools.in secretstorage==3.3.3 # via keyring @@ -275,7 +275,7 @@ tomli==2.0.1 # tox tox==4.11.3 # via -r requirements/tools.in -traitlets==5.10.1 +traitlets==5.11.2 # via # ipython # matplotlib-inline @@ -301,7 +301,7 @@ typing-extensions==4.8.0 # typing-inspect typing-inspect==0.9.0 # via libcst -urllib3==2.0.5 +urllib3==2.0.6 # via # requests # twine diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index 348987aabd..84e6caf818 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -392,12 +392,12 @@ def run_tox(task, version, *args): "3.8": "3.8.18", "3.9": "3.9.18", "3.10": "3.10.13", - "3.11": "3.11.5", - "3.12": "3.12-dev", - "3.13": "3.13-dev", + "3.11": "3.11.6", + "3.12": "3.12.0", + "3.13": "3.13.0a1", "pypy3.8": "pypy3.8-7.3.11", - "pypy3.9": "pypy3.9-7.3.12", - "pypy3.10": "pypy3.10-7.3.12", + "pypy3.9": "pypy3.9-7.3.13", + "pypy3.10": "pypy3.10-7.3.13", } ci_version = "3.10" # Keep this in sync with GH Actions main.yml and .readthedocs.yml diff --git a/whole-repo-tests/test_mypy.py b/whole-repo-tests/test_mypy.py index f75f04eb67..0bf11e7399 100644 --- a/whole-repo-tests/test_mypy.py +++ b/whole-repo-tests/test_mypy.py @@ -115,7 +115,7 @@ def convert_lines(): "one_of(integers(), text(), none(), binary(), builds(list), builds(dict))", "Any", ), - ("tuples()", "tuple[]"), # Should be `tuple[()]`, but this is what mypy prints + ("tuples()", "tuple[()]"), ("tuples(integers())", "tuple[int]"), ("tuples(integers(), text())", "tuple[int, str]"), (