Skip to content

Commit 757e617

Browse files
authored
Deprecate Python 3.6. (#192)
1 parent 95b0089 commit 757e617

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+504
-529
lines changed

.pre-commit-config.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ repos:
3333
rev: v2.31.0
3434
hooks:
3535
- id: pyupgrade
36-
args: [--py36-plus]
36+
args: [--py37-plus]
3737
- repo: https://github.com/asottile/reorder_python_imports
3838
rev: v2.6.0
3939
hooks:
4040
- id: reorder-python-imports
41+
args: [--py37-plus, --add-import, 'from __future__ import annotations']
4142
- repo: https://github.com/asottile/setup-cfg-fmt
4243
rev: v1.20.0
4344
hooks:

README.rst

+2-3
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ Installation
8585

8686
.. start-installation
8787
88-
pytask is available on `PyPI <https://pypi.org/project/pytask>`_ for Python >= 3.6.1 and
89-
on `Anaconda.org <https://anaconda.org/conda-forge/pytask>`_ for Python >= 3.7. Install
90-
the package with
88+
pytask is available on `PyPI <https://pypi.org/project/pytask>`_ and on `Anaconda.org
89+
<https://anaconda.org/conda-forge/pytask>`_. Install the package with
9190

9291
.. code-block:: console
9392

docs/source/changes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ all releases are available on `PyPI <https://pypi.org/project/pytask>`_ and
1111
------------------
1212

1313
- :gh:`191` adds a guide on how to profile pytask to the developer's guide.
14+
- :gh:`192` deprecates Python 3.6.
1415
- :gh:`193` adds more figures to the documentation.
1516
- :gh:`194` updates the ``README.rst``.
1617
- :gh:`196` references the two new cookiecutters for projects and plugins.

docs/source/conf.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# If extensions (or modules to document with autodoc) are in another directory, add
66
# these directories to sys.path here. If the directory is relative to the documentation
77
# root, use os.path.abspath to make it absolute, like shown here.
8+
from __future__ import annotations
9+
810
from importlib.metadata import version
911

1012
import sphinx
@@ -137,7 +139,7 @@
137139
}
138140

139141

140-
def setup(app: "sphinx.application.Sphinx") -> None:
142+
def setup(app: sphinx.application.Sphinx) -> None:
141143
app.add_object_type(
142144
"confval",
143145
"confval",

setup.cfg

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ classifiers =
1919
Operating System :: POSIX
2020
Programming Language :: Python :: 3
2121
Programming Language :: Python :: 3 :: Only
22-
Programming Language :: Python :: 3.6
2322
Programming Language :: Python :: 3.7
2423
Programming Language :: Python :: 3.8
2524
Programming Language :: Python :: 3.9
@@ -43,7 +42,7 @@ install_requires =
4342
pluggy
4443
pony>=0.7.13
4544
rich
46-
python_requires = >=3.6.1
45+
python_requires = >=3.7
4746
include_package_data = True
4847
package_dir =
4948
=src

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from setuptools import setup
24

35

src/_pytask/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
try:
24
from ._version import version as __version__
35
except ImportError:

src/_pytask/build.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Implement the build command."""
2+
from __future__ import annotations
3+
24
import sys
35
from typing import Any
4-
from typing import Dict
56
from typing import TYPE_CHECKING
67

78
import click
@@ -28,7 +29,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None:
2829
cli.add_command(build)
2930

3031

31-
def main(config_from_cli: Dict[str, Any]) -> Session:
32+
def main(config_from_cli: dict[str, Any]) -> Session:
3233
"""Run pytask.
3334
3435
This is the main command to run pytask which usually receives kwargs from the
@@ -117,7 +118,7 @@ def main(config_from_cli: Dict[str, Any]) -> Session:
117118
type=click.Choice(["yes", "no"]),
118119
help="Choose whether tracebacks should be displayed or not. [default: yes]",
119120
)
120-
def build(**config_from_cli: Any) -> "NoReturn":
121+
def build(**config_from_cli: Any) -> NoReturn:
121122
"""Collect and execute tasks and report the results.
122123
123124
This is the default command of pytask which searches given paths or the current

src/_pytask/capture.py

+23-87
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
<https://github.com/pytest-dev/pytest/blob/master/src/_pytest/debugging.py>`_.
2424
2525
"""
26+
from __future__ import annotations
27+
2628
import contextlib
2729
import functools
2830
import io
@@ -31,15 +33,11 @@
3133
from tempfile import TemporaryFile
3234
from typing import Any
3335
from typing import AnyStr
34-
from typing import Dict
3536
from typing import Generator
3637
from typing import Generic
3738
from typing import Iterator
38-
from typing import Optional
3939
from typing import TextIO
40-
from typing import Tuple
4140
from typing import TYPE_CHECKING
42-
from typing import Union
4341

4442
import click
4543
from _pytask.config import hookimpl
@@ -92,9 +90,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None:
9290

9391
@hookimpl
9492
def pytask_parse_config(
95-
config: Dict[str, Any],
96-
config_from_cli: Dict[str, Any],
97-
config_from_file: Dict[str, Any],
93+
config: dict[str, Any],
94+
config_from_cli: dict[str, Any],
95+
config_from_file: dict[str, Any],
9896
) -> None:
9997
"""Parse configuration.
10098
@@ -122,10 +120,8 @@ def pytask_parse_config(
122120

123121

124122
@hookimpl
125-
def pytask_post_parse(config: Dict[str, Any]) -> None:
123+
def pytask_post_parse(config: dict[str, Any]) -> None:
126124
"""Initialize the CaptureManager."""
127-
if config["capture"] == "fd":
128-
_py36_windowsconsoleio_workaround(sys.stdout)
129125
_colorama_workaround()
130126

131127
pluginmanager = config["pm"]
@@ -136,7 +132,7 @@ def pytask_post_parse(config: Dict[str, Any]) -> None:
136132
capman.suspend()
137133

138134

139-
def _capture_callback(x: "Optional[_CaptureMethod]") -> "Optional[_CaptureMethod]":
135+
def _capture_callback(x: _CaptureMethod | None) -> _CaptureMethod | None:
140136
"""Validate the passed options for capturing output."""
141137
if x in [None, "None", "none"]:
142138
x = None
@@ -148,8 +144,8 @@ def _capture_callback(x: "Optional[_CaptureMethod]") -> "Optional[_CaptureMethod
148144

149145

150146
def _show_capture_callback(
151-
x: "Optional[_CaptureCallback]",
152-
) -> "Optional[_CaptureCallback]":
147+
x: _CaptureCallback | None,
148+
) -> _CaptureCallback | None:
153149
"""Validate the passed options for showing captured output."""
154150
if x in [None, "None", "none"]:
155151
x = None
@@ -181,66 +177,6 @@ def _colorama_workaround() -> None:
181177
pass
182178

183179

184-
def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
185-
"""Workaround for Windows Unicode console handling on Python>=3.6.
186-
187-
Python 3.6 implemented Unicode console handling for Windows. This works by
188-
reading/writing to the raw console handle using ``{Read,Write}ConsoleW``.
189-
190-
The problem is that we are going to ``dup2`` over the stdio file descriptors when
191-
doing ``FDCapture`` and this will ``CloseHandle`` the handles used by Python to
192-
write to the console. Though there is still some weirdness and the console handle
193-
seems to only be closed randomly and not on the first call to ``CloseHandle``, or
194-
maybe it gets reopened with the same handle value when we suspend capturing.
195-
196-
The workaround in this case will reopen stdio with a different fd which also means a
197-
different handle by replicating the logic in
198-
"Py_lifecycle.c:initstdio/create_stdio".
199-
200-
Parameters
201-
---------
202-
stream
203-
In practice ``sys.stdout`` or ``sys.stderr``, but given here as parameter for
204-
unit testing purposes.
205-
206-
See https://github.com/pytest-dev/py/issues/103.
207-
208-
"""
209-
if not sys.platform.startswith("win32") or hasattr(sys, "pypy_version_info"):
210-
return
211-
212-
# Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666).
213-
if not hasattr(stream, "buffer"):
214-
return
215-
216-
buffered = hasattr(stream.buffer, "raw")
217-
# ``getattr`` hack since ``buffer`` might not have an attribute ``raw``.
218-
raw_stdout = getattr(stream.buffer, "raw", stream.buffer)
219-
220-
# ``getattr`` hack since ``_WindowsConsoleIO`` is not defined in stubs.
221-
windowsconsoleio = getattr(io, "_WindowsConsoleIO", None)
222-
if windowsconsoleio is not None and not isinstance(raw_stdout, windowsconsoleio):
223-
return
224-
225-
def _reopen_stdio(f: TextIO, mode: str) -> TextIO:
226-
if not buffered and mode[0] == "w":
227-
buffering = 0
228-
else:
229-
buffering = -1
230-
231-
return io.TextIOWrapper(
232-
open(os.dup(f.fileno()), mode, buffering),
233-
f.encoding,
234-
f.errors,
235-
f.newlines,
236-
bool(f.line_buffering),
237-
)
238-
239-
sys.stdin = _reopen_stdio(sys.stdin, "rb")
240-
sys.stdout = _reopen_stdio(sys.stdout, "wb")
241-
sys.stderr = _reopen_stdio(sys.stderr, "wb")
242-
243-
244180
# IO Helpers.
245181

246182

@@ -292,7 +228,7 @@ def read(self, *_args: Any) -> None: # noqa: U101
292228
readlines = read
293229
__next__ = read
294230

295-
def __iter__(self) -> "DontReadFromInput":
231+
def __iter__(self) -> DontReadFromInput:
296232
return self
297233

298234
def fileno(self) -> int:
@@ -305,7 +241,7 @@ def close(self) -> None:
305241
pass
306242

307243
@property
308-
def buffer(self) -> "DontReadFromInput":
244+
def buffer(self) -> DontReadFromInput:
309245
return self
310246

311247

@@ -368,7 +304,7 @@ def __repr__(self) -> str:
368304
self.tmpfile,
369305
)
370306

371-
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
307+
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
372308
assert (
373309
self._state in states
374310
), "cannot {} in state {!r}: expected one of {}".format(
@@ -463,7 +399,7 @@ def __init__(self, targetfd: int) -> None:
463399
# Further complications are the need to support suspend() and the
464400
# possibility of FD reuse (e.g. the tmpfile getting the very same target
465401
# FD). The following approach is robust, I believe.
466-
self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR)
402+
self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR)
467403
os.dup2(self.targetfd_invalid, targetfd)
468404
else:
469405
self.targetfd_invalid = None
@@ -496,7 +432,7 @@ def __repr__(self) -> str:
496432
self.tmpfile,
497433
)
498434

499-
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
435+
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
500436
assert (
501437
self._state in states
502438
), "cannot {} in state {!r}: expected one of {}".format(
@@ -614,8 +550,8 @@ def __getitem__(self, item: int) -> AnyStr:
614550
return tuple(self)[item]
615551

616552
def _replace(
617-
self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None
618-
) -> "CaptureResult[AnyStr]":
553+
self, *, out: AnyStr | None = None, err: AnyStr | None = None
554+
) -> CaptureResult[AnyStr]:
619555
return CaptureResult(
620556
out=self.out if out is None else out, err=self.err if err is None else err
621557
)
@@ -657,9 +593,9 @@ class MultiCapture(Generic[AnyStr]):
657593

658594
def __init__(
659595
self,
660-
in_: Optional[Union[FDCapture, SysCapture]],
661-
out: Optional[Union[FDCapture, SysCapture]],
662-
err: Optional[Union[FDCapture, SysCapture]],
596+
in_: FDCapture | SysCapture | None,
597+
out: FDCapture | SysCapture | None,
598+
err: FDCapture | SysCapture | None,
663599
) -> None:
664600
self.in_ = in_
665601
self.out = out
@@ -686,7 +622,7 @@ def start_capturing(self) -> None:
686622
if self.err:
687623
self.err.start()
688624

689-
def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
625+
def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]:
690626
"""Pop current snapshot out/err capture and flush to orig streams."""
691627
out, err = self.readouterr()
692628
if out:
@@ -743,7 +679,7 @@ def readouterr(self) -> CaptureResult[AnyStr]:
743679
return CaptureResult(out, err) # type: ignore
744680

745681

746-
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]:
682+
def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]:
747683
"""Set up the MultiCapture class with the passed method.
748684
749685
For each valid method, the function instantiates the :class:`MultiCapture` class
@@ -779,9 +715,9 @@ class CaptureManager:
779715
780716
"""
781717

782-
def __init__(self, method: "_CaptureMethod") -> None:
718+
def __init__(self, method: _CaptureMethod) -> None:
783719
self._method = method
784-
self._capturing: Optional[MultiCapture[str]] = None
720+
self._capturing: MultiCapture[str] | None = None
785721

786722
def __repr__(self) -> str:
787723
return ("<CaptureManager _method={!r} _capturing={!r}>").format(

0 commit comments

Comments
 (0)