Browse Source

Remove support for end-of-life Pythons

Match pip support Pythons and remove support for end-of-life Pythons.
pip has not support Python 2 since 21.0 (2021-01-23). For dates on when
these environments went EOL, see:

https://devguide.python.org/devcycle/#end-of-life-branches

Removing support for Python2 allows for several simplifications:

- Remove use of toml package in favor of tomli
- Remove compat.py
- Replace io.open with builtin open
- Replace tempdir function with tempfile.TemporaryDirectory
- Replace mkdir_p function with os.makedirs
- Remove use of mock package in favor of unittest.mock
- Remove import from future module
- Remove unnecessary inheritance from object

Refs #90
pull/131/head
Jon Dufresne 11 months ago
parent
commit
3a8e996841
  1. 2
      .github/workflows/tests.yml
  2. 5
      dev-requirements.txt
  3. 6
      doc/changelog.rst
  4. 3
      doc/requirements.txt
  5. 14
      pep517/build.py
  6. 10
      pep517/check.py
  7. 2
      pep517/colorlog.py
  8. 50
      pep517/compat.py
  9. 25
      pep517/dirtools.py
  10. 10
      pep517/envbuild.py
  11. 30
      pep517/in_process/_in_process.py
  12. 9
      pep517/meta.py
  13. 46
      pep517/wrappers.py
  14. 6
      pyproject.toml
  15. 18
      tests/test_call_hooks.py
  16. 9
      tests/test_envbuild.py
  17. 7
      tests/test_hook_fallbacks.py
  18. 7
      tests/test_inplace_hooks.py
  19. 9
      tests/test_meta.py
  20. 2
      tox.ini

2
.github/workflows/tests.yml

@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:

5
dev-requirements.txt

@ -1,11 +1,8 @@
pytest
pytest-flake8
flake8 < 4 # https://github.com/tholo/pytest-flake8/issues/81
pytest-forward-compatibility; python_version<'3'
mock ; python_version<'3.6'
testpath
toml ; python_version<'3.6'
tomli ; python_version>='3.6'
tomli
setuptools>=30
importlib_metadata ; python_version<'3.8'
zipp ; python_version<'3.8'

6
doc/changelog.rst

@ -1,6 +1,12 @@
Changelog
=========
UNRELEASED
----------
- Remove support for end-of-life Pythons. Now requires Python3.6+.
- Remove support for ``toml`` package. Now requires ``tomli``.
0.12
----

3
doc/requirements.txt

@ -1,2 +1 @@
toml ; python_version<'3.6'
tomli ; python_version>='3.6'
tomli

14
pep517/build.py

@ -1,13 +1,13 @@
"""Build a project using PEP 517 hooks.
"""
import argparse
import io
import logging
import os
import shutil
import tempfile
import tomli
from .compat import FileNotFoundError, toml_load
from .dirtools import mkdir_p, tempdir
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
@ -31,8 +31,8 @@ def load_system(source_dir):
Load the build system from a source dir (pyproject.toml).
"""
pyproject = os.path.join(source_dir, 'pyproject.toml')
with io.open(pyproject, 'rb') as f:
pyproject_data = toml_load(f)
with open(pyproject, 'rb') as f:
pyproject_data = tomli.load(f)
return pyproject_data['build-system']
@ -64,7 +64,7 @@ def _do_build(hooks, env, dist, dest):
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
with tempdir() as td:
with tempfile.TemporaryDirectory() as td:
log.info('Trying to build %s in %s', dist, td)
build_name = 'build_{dist}'.format(**locals())
build = getattr(hooks, build_name)
@ -76,7 +76,7 @@ def _do_build(hooks, env, dist, dest):
def build(source_dir, dist, dest=None, system=None):
system = system or load_system(source_dir)
dest = os.path.join(source_dir, dest or 'dist')
mkdir_p(dest)
os.makedirs(dest, exist_ok=True)
validate_system(system)
hooks = Pep517HookCaller(

10
pep517/check.py

@ -1,7 +1,6 @@
"""Check a project and backend by attempting to build using PEP 517 hooks.
"""
import argparse
import io
import logging
import os
import shutil
@ -13,8 +12,9 @@ from os.path import join as pjoin
from subprocess import CalledProcessError
from tempfile import mkdtemp
import tomli
from .colorlog import enable_colourful_output
from .compat import TOMLDecodeError, toml_load
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
@ -143,15 +143,15 @@ def check(source_dir):
return False
try:
with io.open(pyproject, 'rb') as f:
pyproject_data = toml_load(f)
with open(pyproject, 'rb') as f:
pyproject_data = tomli.load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
backend_path = buildsys.get('backend-path')
log.info('Loaded pyproject.toml')
except (TOMLDecodeError, KeyError):
except (tomli.TOMLDecodeError, KeyError):
log.error("Invalid pyproject.toml", exc_info=True)
return False

2
pep517/colorlog.py

@ -73,8 +73,6 @@ class LogFormatter(logging.Formatter):
# right conversion in python 3.
fg_color = (curses.tigetstr("setaf") or
curses.tigetstr("setf") or "")
if (3, 0) < sys.version_info < (3, 2, 3):
fg_color = str(fg_color, "ascii")
for levelno, code in self.DEFAULT_COLORS.items():
self._colors[levelno] = str(

50
pep517/compat.py

@ -1,50 +0,0 @@
"""Python 2/3 compatibility"""
import io
import json
import sys
# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
# FileNotFoundError
try:
FileNotFoundError = FileNotFoundError
except NameError:
FileNotFoundError = IOError
if sys.version_info < (3, 6):
from toml import load as _toml_load # noqa: F401
def toml_load(f):
w = io.TextIOWrapper(f, encoding="utf8", newline="")
try:
return _toml_load(w)
finally:
w.detach()
from toml import TomlDecodeError as TOMLDecodeError # noqa: F401
else:
from tomli import TOMLDecodeError # noqa: F401
from tomli import load as toml_load # noqa: F401

25
pep517/dirtools.py

@ -1,33 +1,8 @@
import contextlib
import errno
import io
import os
import shutil
import tempfile
import zipfile
@contextlib.contextmanager
def tempdir():
"""Create a temporary directory in a context manager."""
td = tempfile.mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
def mkdir_p(*args, **kwargs):
"""Like `mkdir`, but does not raise an exception if the
directory already exists.
"""
try:
return os.mkdir(*args, **kwargs)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def dir_to_zipfile(root):
"""Construct an in-memory zip file for a directory."""
buffer = io.BytesIO()

10
pep517/envbuild.py

@ -1,7 +1,6 @@
"""Build wheels/sdists by installing build deps to a temporary environment.
"""
import io
import logging
import os
import shutil
@ -10,18 +9,19 @@ from subprocess import check_call
from sysconfig import get_paths
from tempfile import mkdtemp
from .compat import toml_load
import tomli
from .wrappers import LoggerWrapper, Pep517HookCaller
log = logging.getLogger(__name__)
def _load_pyproject(source_dir):
with io.open(
with open(
os.path.join(source_dir, 'pyproject.toml'),
'rb',
) as f:
pyproject_data = toml_load(f)
pyproject_data = tomli.load(f)
buildsys = pyproject_data['build-system']
return (
buildsys['requires'],
@ -30,7 +30,7 @@ def _load_pyproject(source_dir):
)
class BuildEnvironment(object):
class BuildEnvironment:
"""Context manager to install build deps in a simple temporary environment
Based on code I wrote for pip, which is MIT licensed.

30
pep517/in_process/_in_process.py

@ -23,30 +23,18 @@ from glob import glob
from importlib import import_module
from os.path import join as pjoin
# This file is run as a script, and `import compat` is not zip-safe, so we
# include write_json() and read_json() from compat.py.
#
# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
# This file is run as a script, and `import wrappers` is not zip-safe, so we
# include write_json() and read_json() from wrappers.py.
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
def read_json(path):
with open(path, encoding='utf-8') as f:
return json.load(f)
class BackendUnavailable(Exception):
@ -64,7 +52,7 @@ class BackendInvalid(Exception):
class HookMissing(Exception):
"""Raised if a hook is missing and we are not executing the fallback"""
def __init__(self, hook_name=None):
super(HookMissing, self).__init__(hook_name)
super().__init__(hook_name)
self.hook_name = hook_name

9
pep517/meta.py

@ -5,6 +5,7 @@ import functools
import logging
import os
import shutil
import tempfile
try:
import importlib.metadata as imp_meta
@ -17,7 +18,7 @@ except ImportError:
from zipp import Path
from .build import compat_system, load_system, validate_system
from .dirtools import dir_to_zipfile, mkdir_p, tempdir
from .dirtools import dir_to_zipfile
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller, quiet_subprocess_runner
@ -31,7 +32,7 @@ def _prep_meta(hooks, env, dest):
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
with tempdir() as td:
with tempfile.TemporaryDirectory() as td:
log.info('Trying to build metadata in %s', td)
filename = hooks.prepare_metadata_for_build_wheel(td, {})
source = os.path.join(td, filename)
@ -41,7 +42,7 @@ def _prep_meta(hooks, env, dest):
def build(source_dir='.', dest=None, system=None):
system = system or load_system(source_dir)
dest = os.path.join(source_dir, dest or 'dist')
mkdir_p(dest)
os.makedirs(dest, exist_ok=True)
validate_system(system)
hooks = Pep517HookCaller(
source_dir, system['build-backend'], system.get('backend-path')
@ -54,7 +55,7 @@ def build(source_dir='.', dest=None, system=None):
def build_as_zip(builder=build):
with tempdir() as out_dir:
with tempfile.TemporaryDirectory() as out_dir:
builder(dest=out_dir)
return dir_to_zipfile(out_dir)

46
pep517/wrappers.py

@ -1,14 +1,13 @@
import json
import os
import shutil
import sys
import tempfile
import threading
from contextlib import contextmanager
from os.path import abspath
from os.path import join as pjoin
from subprocess import STDOUT, check_call, check_output
from tempfile import mkdtemp
from . import compat
from .in_process import _in_proc_script_path
__all__ = [
@ -22,13 +21,14 @@ __all__ = [
]
@contextmanager
def tempdir():
td = mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, encoding='utf-8') as f:
return json.load(f)
class BackendUnavailable(Exception):
@ -48,7 +48,7 @@ class BackendInvalid(Exception):
class HookMissing(Exception):
"""Will be raised on missing hooks."""
def __init__(self, hook_name):
super(HookMissing, self).__init__(hook_name)
super().__init__(hook_name)
self.hook_name = hook_name
@ -100,7 +100,7 @@ def norm_and_check(source_tree, requested):
return abs_requested
class Pep517HookCaller(object):
class Pep517HookCaller:
"""A wrapper around a source directory to be built with a PEP 517 backend.
:param source_dir: The path to the source directory, containing
@ -293,29 +293,15 @@ class Pep517HookCaller(object):
})
def _call_hook(self, hook_name, kwargs):
# On Python 2, pytoml returns Unicode values (which is correct) but the
# environment passed to check_call needs to contain string values. We
# convert here by encoding using ASCII (the backend can only contain
# letters, digits and _, . and : characters, and will be used as a
# Python identifier, so non-ASCII content is wrong on Python 2 in
# any case).
# For backend_path, we use sys.getfilesystemencoding.
if sys.version_info[0] == 2:
build_backend = self.build_backend.encode('ASCII')
else:
build_backend = self.build_backend
extra_environ = {'PEP517_BUILD_BACKEND': build_backend}
extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend}
if self.backend_path:
backend_path = os.pathsep.join(self.backend_path)
if sys.version_info[0] == 2:
backend_path = backend_path.encode(sys.getfilesystemencoding())
extra_environ['PEP517_BACKEND_PATH'] = backend_path
with tempdir() as td:
with tempfile.TemporaryDirectory() as td:
hook_input = {'kwargs': kwargs}
compat.write_json(hook_input, pjoin(td, 'input.json'),
indent=2)
write_json(hook_input, pjoin(td, 'input.json'), indent=2)
# Run the hook in a subprocess
with _in_proc_script_path() as script:
@ -326,7 +312,7 @@ class Pep517HookCaller(object):
extra_environ=extra_environ
)
data = compat.read_json(pjoin(td, 'output.json'))
data = read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation(data.get('traceback', ''))
if data.get('no_backend'):

6
pyproject.toml

@ -9,15 +9,15 @@ author-email = "thomas@kluyver.me.uk"
home-page = "https://github.com/pypa/pep517"
description-file = "README.rst"
requires = [
"toml;python_version<'3.6'",
"tomli >=1.1.0;python_version>='3.6'",
"tomli >=1.1.0",
"importlib_metadata;python_version<'3.8'",
"zipp;python_version<'3.8'",
]
requires-python = ">=3.6"
classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
]
[tool.isort]

18
tests/test_call_hooks.py

@ -1,22 +1,16 @@
import io
import json
import os
import sys
import tarfile
import zipfile
from os.path import abspath, dirname
from os.path import join as pjoin
from unittest.mock import Mock
import pytest
import tomli
from testpath import assert_isfile, modified_env
from testpath.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
try:
from mock import Mock # Prefer the backport below python 3.6
except ImportError:
from unittest.mock import Mock
from pep517.compat import toml_load
from pep517.wrappers import (
BackendUnavailable,
Pep517HookCaller,
@ -24,18 +18,14 @@ from pep517.wrappers import (
default_subprocess_runner,
)
if sys.version_info[0] == 2:
FileNotFoundError = IOError
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples')
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, 'buildsys_pkgs')
def get_hooks(pkg, **kwargs):
source_dir = pjoin(SAMPLES_DIR, pkg)
with io.open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = toml_load(f)
with open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = tomli.load(f)
return Pep517HookCaller(
source_dir, data['build-system']['build-backend'], **kwargs
)

9
tests/test_envbuild.py

@ -1,17 +1,12 @@
import tarfile
import zipfile
from os.path import abspath, dirname
from os.path import join as pjoin
from unittest.mock import call, patch
from testpath import assert_isfile, modified_env
from testpath.tempdir import TemporaryDirectory
try:
from unittest.mock import call, patch
except ImportError:
from mock import patch, call # Python 2 fallback
import zipfile
from pep517.envbuild import BuildEnvironment, build_sdist, build_wheel
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples')

7
tests/test_hook_fallbacks.py

@ -1,12 +1,11 @@
import io
from os.path import abspath, dirname
from os.path import join as pjoin
import pytest
import tomli
from testpath import assert_isfile, modified_env
from testpath.tempdir import TemporaryDirectory
from pep517.compat import toml_load
from pep517.wrappers import HookMissing, Pep517HookCaller
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples')
@ -15,8 +14,8 @@ BUILDSYS_PKGS = pjoin(SAMPLES_DIR, 'buildsys_pkgs')
def get_hooks(pkg):
source_dir = pjoin(SAMPLES_DIR, pkg)
with io.open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = toml_load(f)
with open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = tomli.load(f)
return Pep517HookCaller(source_dir, data['build-system']['build-backend'])

7
tests/test_inplace_hooks.py

@ -1,11 +1,10 @@
import io
from os.path import abspath, dirname
from os.path import join as pjoin
import pytest
import tomli
from testpath import modified_env
from pep517.compat import toml_load
from pep517.wrappers import BackendInvalid, Pep517HookCaller
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples')
@ -15,8 +14,8 @@ SOURCE_DIR = pjoin(SAMPLES_DIR, 'pkg1')
def get_hooks(pkg, backend=None, path=None):
source_dir = pjoin(SAMPLES_DIR, pkg)
with io.open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = toml_load(f)
with open(pjoin(source_dir, 'pyproject.toml'), 'rb') as f:
data = tomli.load(f)
if backend is None:
backend = data['build-system']['build-backend']
if path is None:

9
tests/test_meta.py

@ -1,16 +1,7 @@
from __future__ import absolute_import, division, unicode_literals
import re
import pytest
from pep517 import meta
pep517_needs_python_3 = pytest.mark.xfail(
'sys.version_info < (3,)',
reason="pep517 cannot be built on Python 2",
)
def test_meta_for_this_package():
dist = meta.load('.')

2
tox.ini

@ -1,5 +1,5 @@
[tox]
envlist = py27, py34, py35, py36, py37, py38, py39, py310, pypy, pypy3, isort
envlist = py36, py37, py38, py39, py310, pypy3, isort
skipsdist = true
[testenv]

Loading…
Cancel
Save