Skip to content

Commit 7bfe887

Browse files
committed
Drops Python3.6 support, refs HypothesisWorks#3174
1 parent 6173b2a commit 7bfe887

24 files changed

+34
-221
lines changed

.github/workflows/main.yml

-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ jobs:
2323
- check-format
2424
- check-coverage
2525
- check-conjecture-coverage
26-
- check-py36
27-
- check-pypy36
2826
- check-py37
2927
- check-pypy37
3028
- check-py38

hypothesis-python/docs/strategies.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ and then tell ``setuptools`` that this is your ``"hypothesis"`` entry point:
183183
And that's all it takes!
184184

185185
.. note::
186-
On Python 3.6 and 3.7, where the ``importlib.metadata`` module
186+
On Python 3.7, where the ``importlib.metadata`` module
187187
is not in the standard library, loading entry points requires either the
188188
:pypi:`importlib_metadata` (preferred) or :pypi:`setuptools` (fallback)
189189
package to be installed.

hypothesis-python/docs/supported.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ changes in patch releases.
2525
Python versions
2626
---------------
2727

28-
Hypothesis is supported and tested on CPython 3.6+, i.e.
28+
Hypothesis is supported and tested on CPython 3.7+, i.e.
2929
`all versions of CPython with upstream support <https://devguide.python.org/#status-of-python-branches>`_,
3030
along with PyPy for the same versions.
3131
32-bit builds of CPython also work, though we only test them on Windows.

hypothesis-python/examples/example_hypothesis_entrypoint/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
description="Minimal setup.py to register an entrypoint.",
2626
packages=setuptools.find_packages(),
2727
install_requires=["hypothesis"],
28-
python_requires=">=3.6",
28+
python_requires=">=3.7",
2929
entry_points={
3030
"hypothesis": ["_ = example_hypothesis_entrypoint:_hypothesis_setup_hook"]
3131
},

hypothesis-python/scripts/basic-test.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pip uninstall -y redis fakeredis
3333

3434
pip install 'typing_extensions!=3.10.0.1'
3535
$PYTEST tests/typing_extensions/
36-
if [ "$(python -c 'import sys; print(sys.version_info[:2] <= (3, 7))')" = "False" ] ; then
36+
if [ "$(python -c 'import sys; print(sys.version_info[:2] == (3, 7))')" = "False" ] ; then
3737
# Required by importlib_metadata backport, which we don't want to break
3838
pip uninstall -y typing_extensions
3939
fi

hypothesis-python/setup.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import setuptools
2121

22-
if sys.version_info[:2] < (3, 6):
22+
if sys.version_info[:2] < (3, 7):
2323
raise Exception(
2424
"This version of Python is too old to install new versions of Hypothesis. "
2525
"Update `pip` and `setuptools`, try again, and you will automatically "
@@ -107,7 +107,7 @@ def local_file(name):
107107
zip_safe=False,
108108
extras_require=extras,
109109
install_requires=["attrs>=19.2.0", "sortedcontainers>=2.1.0,<3.0.0"],
110-
python_requires=">=3.6",
110+
python_requires=">=3.7",
111111
classifiers=[
112112
"Development Status :: 5 - Production/Stable",
113113
"Framework :: Hypothesis",
@@ -120,7 +120,6 @@ def local_file(name):
120120
"Programming Language :: Python",
121121
"Programming Language :: Python :: 3",
122122
"Programming Language :: Python :: 3 :: Only",
123-
"Programming Language :: Python :: 3.6",
124123
"Programming Language :: Python :: 3.7",
125124
"Programming Language :: Python :: 3.8",
126125
"Programming Language :: Python :: 3.9",

hypothesis-python/src/hypothesis/_error_if_old.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
from hypothesis.version import __version__
1919

2020
message = """
21-
Hypothesis {} requires Python 3.6 or later.
21+
Hypothesis {} requires Python 3.7 or later.
2222
2323
This can only happen if your packaging toolchain is older than python_requires.
2424
See https://packaging.python.org/guides/distributing-packages-using-setuptools/
2525
"""
2626

27-
if sys.version_info[:3] < (3, 6): # pragma: no cover
27+
if sys.version_info[:2] < (3, 7): # pragma: no cover
2828
raise Exception(message.format(__version__))

hypothesis-python/src/hypothesis/extra/ghostwriter.py

+1-49
Original file line numberDiff line numberDiff line change
@@ -329,52 +329,7 @@ def _strategy_for(
329329
def _get_params(func: Callable) -> Dict[str, inspect.Parameter]:
330330
"""Get non-vararg parameters of `func` as an ordered dict."""
331331
var_param_kinds = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
332-
try:
333-
params = list(inspect.signature(func).parameters.values())
334-
except Exception:
335-
if (
336-
isinstance(func, (types.BuiltinFunctionType, types.BuiltinMethodType))
337-
and hasattr(func, "__doc__")
338-
and isinstance(func.__doc__, str)
339-
):
340-
# inspect.signature doesn't work on all builtin functions or methods.
341-
# In such cases, including the operator module on Python 3.6, we can try
342-
# to reconstruct simple signatures from the docstring.
343-
match = re.match(rf"^{func.__name__}\((.+?)\)", func.__doc__)
344-
if match is None:
345-
raise
346-
args = match.group(1).replace("[", "").replace("]", "")
347-
params = []
348-
# Even if the signature doesn't contain a /, we assume that arguments
349-
# are positional-only until shown otherwise - the / is often omitted.
350-
kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_ONLY
351-
for arg in args.split(", "):
352-
arg, *_ = arg.partition("=")
353-
if arg == "/":
354-
kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
355-
continue
356-
if arg.startswith("*"):
357-
kind = inspect.Parameter.KEYWORD_ONLY
358-
continue # we omit *varargs, if there are any
359-
if arg.startswith("**"):
360-
break # and likewise omit **varkw
361-
params.append(inspect.Parameter(name=arg, kind=kind))
362-
363-
elif _is_probably_ufunc(func):
364-
# `inspect.signature` doesn't work on ufunc objects, but we can work out
365-
# what the required parameters would look like if it did.
366-
# Note that we use args named a, b, c... to match the `operator` module,
367-
# rather than x1, x2, x3... like the Numpy docs. Because they're pos-only
368-
# this doesn't make a runtime difference, and it's much nicer for use-cases
369-
# like `equivalent(numpy.add, operator.add)`.
370-
params = [
371-
inspect.Parameter(name=name, kind=inspect.Parameter.POSITIONAL_ONLY)
372-
for name in ascii_lowercase[: func.nin] # type: ignore
373-
]
374-
else:
375-
# If we haven't managed to recover a signature through the tricks above,
376-
# we're out of ideas and should just re-raise the exception.
377-
raise
332+
params = list(inspect.signature(func).parameters.values())
378333
return OrderedDict((p.name, p) for p in params if p.kind not in var_param_kinds)
379334

380335

@@ -466,9 +421,6 @@ def _imports_for_object(obj):
466421
name = _get_qualname(obj).split(".")[0]
467422
return {(_get_module(obj), name)}
468423
except Exception:
469-
with contextlib.suppress(AttributeError):
470-
if obj.__module__ == "typing": # only on CPython 3.6
471-
return {("typing", getattr(obj, "__name__", obj.name))}
472424
return set()
473425

474426

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

+2-31
Original file line numberDiff line numberDiff line change
@@ -1001,16 +1001,7 @@ def as_strategy(strat_or_callable, thing, final=True):
10011001
# of it. We do this in three places, hence the helper function
10021002
if not isinstance(strat_or_callable, SearchStrategy):
10031003
assert callable(strat_or_callable) # Validated in register_type_strategy
1004-
try:
1005-
# On Python 3.6, typing.Hashable is just an alias for abc.Hashable,
1006-
# and the resolver function for Type throws an AttributeError because
1007-
# Hashable has no __args__. We discard such errors when attempting
1008-
# to resolve subclasses, because the function was passed a weird arg.
1009-
strategy = strat_or_callable(thing)
1010-
except Exception: # pragma: no cover
1011-
if not final:
1012-
return nothing()
1013-
raise
1004+
strategy = strat_or_callable(thing)
10141005
else:
10151006
strategy = strat_or_callable
10161007
if not isinstance(strategy, SearchStrategy):
@@ -1029,27 +1020,7 @@ def as_strategy(strat_or_callable, thing, final=True):
10291020
if thing in types._global_type_lookup:
10301021
return as_strategy(types._global_type_lookup[thing], thing)
10311022
return from_type(thing.__supertype__)
1032-
# Under Python 3.6, Unions are not instances of `type` - but we
1033-
# still want to resolve them!
1034-
if getattr(thing, "__origin__", None) is typing.Union:
1035-
args = sorted(thing.__args__, key=types.type_sorting_key)
1036-
return one_of([from_type(t) for t in args])
10371023
if not types.is_a_type(thing):
1038-
# The implementation of typing_extensions.Literal under Python 3.6 is
1039-
# *very strange*. Notably, `type(Literal[x]) != Literal` so we have to
1040-
# use the first form directly, and because it uses __values__ instead of
1041-
# __args__ we inline the relevant logic here until the end of life date.
1042-
if types.is_typing_literal(thing): # pragma: no cover
1043-
assert sys.version_info[:2] == (3, 6)
1044-
args_dfs_stack = list(thing.__values__) # type: ignore
1045-
literals = []
1046-
while args_dfs_stack:
1047-
arg = args_dfs_stack.pop()
1048-
if types.is_typing_literal(arg):
1049-
args_dfs_stack.extend(reversed(arg.__values__))
1050-
else:
1051-
literals.append(arg)
1052-
return sampled_from(literals)
10531024
if isinstance(thing, str):
10541025
# See https://github.com/HypothesisWorks/hypothesis/issues/3016
10551026
raise InvalidArgument(
@@ -1060,7 +1031,7 @@ def as_strategy(strat_or_callable, thing, final=True):
10601031
)
10611032
raise InvalidArgument(f"thing={thing!r} must be a type") # pragma: no cover
10621033
# Now that we know `thing` is a type, the first step is to check for an
1063-
# explicitly registered strategy. This is the best (and hopefully most
1034+
# explicitly registered strategy. This is the best (and hopefully most
10641035
# common) way to resolve a type to a strategy. Note that the value in the
10651036
# lookup may be a strategy or a function from type -> strategy; and we
10661037
# convert empty results into an explicit error.

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,7 @@ def timezone_keys(
396396
.. note::
397397
398398
The :mod:`python:zoneinfo` module is new in Python 3.9, so you will need
399-
to install the :pypi:`backports.zoneinfo` module on earlier versions, and
400-
the :pypi:`importlib_resources` backport on Python 3.6.
399+
to install the :pypi:`backports.zoneinfo` module on earlier versions.
401400
402401
`On Windows, you will also need to install the tzdata package
403402
<https://docs.python.org/3/library/zoneinfo.html#data-sources>`__.
@@ -457,8 +456,7 @@ def timezones(*, no_cache: bool = False) -> SearchStrategy["zoneinfo.ZoneInfo"]:
457456
.. note::
458457
459458
The :mod:`python:zoneinfo` module is new in Python 3.9, so you will need
460-
to install the :pypi:`backports.zoneinfo` module on earlier versions, and
461-
the :pypi:`importlib_resources` backport on Python 3.6.
459+
to install the :pypi:`backports.zoneinfo` module on earlier versions.
462460
463461
`On Windows, you will also need to install the tzdata package
464462
<https://docs.python.org/3/library/zoneinfo.html#data-sources>`__.

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

+9-45
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@
4949
except ImportError:
5050
typing_extensions = None # type: ignore
5151

52-
try:
53-
from typing import GenericMeta as _GenericMeta # python < 3.7
54-
except ImportError:
55-
_GenericMeta = () # type: ignore
56-
5752
try:
5853
from typing import _GenericAlias # type: ignore # python >= 3.7
5954
except ImportError:
@@ -62,17 +57,7 @@
6257
try:
6358
from typing import _AnnotatedAlias # type: ignore
6459
except ImportError:
65-
try:
66-
from typing_extensions import _AnnotatedAlias
67-
except ImportError:
68-
try:
69-
from typing_extensions import ( # type: ignore
70-
AnnotatedMeta as _AnnotatedAlias,
71-
)
72-
73-
assert sys.version_info[:2] == (3, 6)
74-
except ImportError:
75-
_AnnotatedAlias = ()
60+
from typing_extensions import _AnnotatedAlias
7661

7762

7863
def type_sorting_key(t):
@@ -111,9 +96,7 @@ def try_issubclass(thing, superclass):
11196
getattr(superclass, "__origin__", None) or superclass,
11297
):
11398
superclass_args = getattr(superclass, "__args__", None)
114-
if sys.version_info[:2] == (3, 6) or not superclass_args:
115-
# Python 3.6 doesn't have PEP-560 semantics or __orig_bases__,
116-
# so there's no point continuing; we therefore stop early.
99+
if not superclass_args:
117100
# The superclass is not generic, so we're definitely a subclass.
118101
return True
119102
# Sadly this is just some really fiddly logic to handle all the cases
@@ -157,11 +140,7 @@ def is_typing_literal(thing):
157140
hasattr(typing, "Literal")
158141
and getattr(thing, "__origin__", None) == typing.Literal
159142
or hasattr(typing_extensions, "Literal")
160-
and (
161-
getattr(thing, "__origin__", None) == typing_extensions.Literal
162-
or sys.version_info[:2] == (3, 6)
163-
and isinstance(thing, type(typing_extensions.Literal[None]))
164-
)
143+
and getattr(thing, "__origin__", None) == typing_extensions.Literal
165144
)
166145

167146

@@ -190,7 +169,7 @@ def find_annotated_strategy(annotated_type): # pragma: no cover
190169
def has_type_arguments(type_):
191170
"""Decides whethere or not this type has applied type arguments."""
192171
args = getattr(type_, "__args__", None)
193-
if args and isinstance(type_, (_GenericAlias, _GenericMeta)):
172+
if args and isinstance(type_, _GenericAlias):
194173
# There are some cases when declared types do already have type arguments
195174
# Like `Sequence`, that is `_GenericAlias(abc.Sequence[T])[T]`
196175
parameters = getattr(type_, "__parameters__", None)
@@ -202,8 +181,7 @@ def has_type_arguments(type_):
202181
def is_generic_type(type_):
203182
"""Decides whether a given type is generic or not."""
204183
# The ugly truth is that `MyClass`, `MyClass[T]`, and `MyClass[int]` are very different.
205-
# In different python versions they might have the same type (3.6)
206-
# or it can be regular type vs `_GenericAlias` (3.7+)
184+
# In different python versions it can be regular type vs `_GenericAlias` (3.7+)
207185
# We check for `MyClass[T]` and `MyClass[int]` with the first condition,
208186
# while the second condition is for `MyClass` in `python3.7+`.
209187
return isinstance(type_, typing_root_type) or (
@@ -222,14 +200,6 @@ def _try_import_forward_ref(thing, bound): # pragma: no cover
222200
try:
223201
return typing._eval_type(bound, vars(sys.modules[thing.__module__]), None)
224202
except (KeyError, AttributeError, NameError):
225-
if (
226-
isinstance(thing, typing.TypeVar)
227-
and getattr(thing, "__module__", None) == "typing"
228-
):
229-
raise ResolutionFailed(
230-
"It looks like you're using a TypeVar bound to a ForwardRef on Python "
231-
"3.6, which is not supported - try ugrading to Python 3.7 or later."
232-
) from None
233203
# We fallback to `ForwardRef` instance, you can register it as a type as well:
234204
# >>> from typing import ForwardRef
235205
# >>> from hypothesis import strategies as st
@@ -289,7 +259,6 @@ def from_typing_type(thing):
289259

290260
# Some "generic" classes are not generic *in* anything - for example both
291261
# Hashable and Sized have `__args__ == ()` on Python 3.7 or later.
292-
# (In 3.6 they're just aliases for the collections.abc classes)
293262
origin = getattr(thing, "__origin__", thing)
294263
if (
295264
typing.Hashable is not collections.abc.Hashable
@@ -310,9 +279,6 @@ def from_typing_type(thing):
310279
# ItemsView can cause test_lookup.py::test_specialised_collection_types
311280
# to fail, due to weird isinstance behaviour around the elements.
312281
mapping.pop(typing.ItemsView, None)
313-
if sys.version_info[:2] == (3, 6): # pragma: no cover
314-
# `isinstance(dict().values(), Container) is False` on py36 only -_-
315-
mapping.pop(typing.ValuesView, None)
316282
if typing.Deque in mapping and len(mapping) > 1:
317283
# Resolving generic sequences to include a deque is more trouble for e.g.
318284
# the ghostwriter than it's worth, via undefined names in the repr.
@@ -495,12 +461,10 @@ def _networks(bits):
495461
_global_type_lookup[type] = st.sampled_from(
496462
[type(None)] + sorted(_global_type_lookup, key=str)
497463
)
498-
499-
if sys.version_info[:2] >= (3, 7): # pragma: no branch
500-
_global_type_lookup[re.Match] = (
501-
st.text().map(lambda c: re.match(".", c, flags=re.DOTALL)).filter(bool)
502-
)
503-
_global_type_lookup[re.Pattern] = st.builds(re.compile, st.sampled_from(["", b""]))
464+
_global_type_lookup[re.Match] = (
465+
st.text().map(lambda c: re.match(".", c, flags=re.DOTALL)).filter(bool)
466+
)
467+
_global_type_lookup[re.Pattern] = st.builds(re.compile, st.sampled_from(["", b""]))
504468
if sys.version_info[:2] >= (3, 9): # pragma: no cover
505469
# subclass of MutableMapping, and in Python 3.9 we resolve to a union
506470
# which includes this... but we don't actually ever want to build one.

hypothesis-python/tests/conftest.py

-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
# Skip collection of tests which require the Django test runner,
3232
# or that don't work on the current version of Python.
3333
collect_ignore_glob = ["django/*"]
34-
if sys.version_info < (3, 7):
35-
collect_ignore_glob.append("cover/*py37*")
3634
if sys.version_info < (3, 8):
3735
collect_ignore_glob.append("array_api")
3836
collect_ignore_glob.append("cover/*py38*")

hypothesis-python/tests/cover/test_async_def.py

-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@
1414
# END HEADER
1515

1616
import asyncio
17-
import sys
1817
from unittest import TestCase
1918

20-
import pytest
21-
2219
from hypothesis import assume, given, strategies as st
2320

2421

@@ -29,7 +26,6 @@ class TestAsyncioRun(TestCase):
2926
def execute_example(self, f):
3027
asyncio.run(f())
3128

32-
@pytest.mark.skipif(sys.version_info[:2] < (3, 7), reason="asyncio.run() is new")
3329
@given(st.text())
3430
async def test_foo(self, x):
3531
assume(x)

hypothesis-python/tests/cover/test_datetimes.py

-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import pytest
1919

2020
from hypothesis import given, settings
21-
from hypothesis.internal.compat import PYPY
2221
from hypothesis.strategies import dates, datetimes, timedeltas, times
2322

2423
from tests.common.debug import find_any, minimal
@@ -139,13 +138,10 @@ def test_naive_times_are_naive(dt):
139138
assert dt.tzinfo is None
140139

141140

142-
# pypy3.6 seems to canonicalise fold to 0 for non-ambiguous times?
143-
@pytest.mark.skipif(PYPY, reason="see comment")
144141
def test_can_generate_datetime_with_fold_1():
145142
find_any(datetimes(), lambda d: d.fold)
146143

147144

148-
@pytest.mark.skipif(PYPY, reason="see comment")
149145
def test_can_generate_time_with_fold_1():
150146
find_any(times(), lambda d: d.fold)
151147

0 commit comments

Comments
 (0)