Browse Source

Drop support for python 2.7, 3.4, and 3.5 (#376)

pull/378/head
Jon Dufresne 2 years ago committed by GitHub
parent
commit
4d60352aaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      .github/workflows/test.yml
  2. 4
      .pre-commit-config.yaml
  3. 8
      .travis.yml
  4. 2
      CHANGELOG.rst
  5. 2
      docs/conf.py
  6. 5
      docs/development/getting-started.rst
  7. 2
      noxfile.py
  8. 1
      packaging/__about__.py
  9. 1
      packaging/__init__.py
  10. 38
      packaging/_compat.py
  11. 5
      packaging/_structures.py
  12. 42
      packaging/markers.py
  13. 34
      packaging/requirements.py
  14. 45
      packaging/specifiers.py
  15. 55
      packaging/tags.py
  16. 21
      packaging/utils.py
  17. 19
      packaging/version.py
  18. 3
      setup.cfg
  19. 8
      setup.py
  20. 1
      tasks/__init__.py
  21. 20
      tasks/check.py
  22. 1
      tests/__init__.py
  23. 87
      tests/test_markers.py
  24. 1
      tests/test_requirements.py
  25. 7
      tests/test_specifiers.py
  26. 1
      tests/test_structures.py
  27. 74
      tests/test_tags.py
  28. 1
      tests/test_utils.py
  29. 13
      tests/test_version.py

23
.github/workflows/test.yml

@ -18,9 +18,8 @@ jobs:
fail-fast: false
matrix:
os: [Ubuntu, Windows, macOS]
# Python 3.4 is not available from actions/setup-python@v2.
python_version:
["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy2", "pypy3"]
["3.6", "3.7", "3.8", "3.9", "pypy3"]
exclude:
# This is failing due to pip not being in the virtual environment.
# https://github.com/pypa/packaging/runs/424785871#step:7:9
@ -35,26 +34,6 @@ jobs:
with:
python-version: ${{ matrix.python_version }}
# Set `python` to a recent 3.x version if we're not testing Python 3.6+.
# Why? Nox needs Python 3.5+ and everyone likes f-strings.
- uses: actions/setup-python@v2
name: Install Python 3.x
with:
python-version: 3.x
if: >
(
matrix.python_version == '2.7' ||
matrix.python_version == 'pypy2' ||
matrix.python_version == '3.5'
)
# Workaround https://github.com/theacodes/nox/issues/250
- name: Workaround for Windows Python 2.7
# This is in PATH, so nox resolves to it - but then subsequent steps fail.
run: rm C:/ProgramData/Chocolatey/bin/python2.7.exe
shell: bash
if: runner.os == 'Windows' && matrix.python_version == '2.7'
- name: Install dependencies
run: |
python -m pip install --upgrade pip

4
.pre-commit-config.yaml

@ -13,10 +13,6 @@ repos:
- id: mypy
exclude: '^(docs|tasks|tests)|setup\.py'
args: []
- id: mypy
name: mypy for Python 2
exclude: '^(docs|tasks|tests)|setup\.py|noxfile\.py'
args: ["--py2"]
- repo: https://github.com/psf/black
rev: 19.3b0

8
.travis.yml

@ -4,16 +4,8 @@ python: 3.8
matrix:
include:
- python: 2.7
env: NOXSESSION=tests-2.7
- python: pypy
env: NOXSESSION=tests-pypy
- python: pypy3
env: NOXSESSION=tests-pypy3
- python: 3.4
env: NOXSESSION=tests-3.4
- python: 3.5
env: NOXSESSION=tests-3.5
- python: 3.6
env: NOXSESSION=tests-3.6
- python: 3.7

2
CHANGELOG.rst

@ -4,7 +4,7 @@ Changelog
*unreleased*
~~~~~~~~~~~~
No unreleased changes.
* `packaging` is now only compatible with Python 3.6 and above.
20.9 - 2021-01-29
~~~~~~~~~~~~~~~~~

2
docs/conf.py

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import os
import sys

5
docs/development/getting-started.rst

@ -30,13 +30,10 @@ each supported Python version and run the tests. For example:
$ nox -s tests
...
nox > Ran multiple sessions:
nox > * tests-2.7: success
nox > * tests-3.4: skipped
nox > * tests-3.5: success
nox > * tests-3.6: success
nox > * tests-3.7: success
nox > * tests-3.8: success
nox > * tests-pypy: skipped
nox > * tests-3.9: success
nox > * tests-pypy3: skipped
You may not have all the required Python versions installed, in which case you

2
noxfile.py

@ -20,7 +20,7 @@ nox.options.sessions = ["lint"]
nox.options.reuse_existing_virtualenvs = True
@nox.session(python=["2.7", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy", "pypy3"])
@nox.session(python=["3.6", "3.7", "3.8", "3.9", "pypy3"])
def tests(session):
def coverage(*args):
session.run("python", "-m", "coverage", *args)

1
packaging/__about__.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
__all__ = [
"__title__",

1
packaging/__init__.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
from .__about__ import (
__author__,

38
packaging/_compat.py

@ -1,38 +0,0 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import sys
from ._typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Dict, Tuple, Type
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
string_types = (str,)
else:
string_types = (basestring,)
def with_metaclass(meta, *bases):
# type: (Type[Any], Tuple[Type[Any], ...]) -> Any
"""
Create a base class with a metaclass.
"""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta): # type: ignore
def __new__(cls, name, this_bases, d):
# type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any
return meta(name, bases, d)
return type.__new__(metaclass, "temporary_class", (), {})

5
packaging/_structures.py

@ -1,10 +1,9 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
class InfinityType(object):
class InfinityType:
def __repr__(self):
# type: () -> str
return "Infinity"
@ -45,7 +44,7 @@ class InfinityType(object):
Infinity = InfinityType()
class NegativeInfinityType(object):
class NegativeInfinityType:
def __repr__(self):
# type: () -> str
return "-Infinity"

42
packaging/markers.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import operator
import os
@ -20,7 +19,6 @@ from pyparsing import ( # noqa: N817
stringStart,
)
from ._compat import string_types
from ._typing import TYPE_CHECKING
from .specifiers import InvalidSpecifier, Specifier
@ -58,7 +56,7 @@ class UndefinedEnvironmentName(ValueError):
"""
class Node(object):
class Node:
def __init__(self, value):
# type: (Any) -> None
self.value = value
@ -69,7 +67,7 @@ class Node(object):
def __repr__(self):
# type: () -> str
return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
return f"<{self.__class__.__name__}('{self}')>"
def serialize(self):
# type: () -> str
@ -85,7 +83,7 @@ class Variable(Node):
class Value(Node):
def serialize(self):
# type: () -> str
return '"{0}"'.format(self)
return f'"{self}"'
class Op(Node):
@ -162,7 +160,7 @@ def _coerce_parse_result(results):
def _format_marker(marker, first=True):
# type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str
assert isinstance(marker, (list, tuple, string_types))
assert isinstance(marker, (list, tuple, str))
# Sometimes we have a structure like [[...]] which is a single item list
# where the single item is itself it's own list. In that case we want skip
@ -210,14 +208,12 @@ def _eval_op(lhs, op, rhs):
oper = _operators.get(op.serialize()) # type: Optional[Operator]
if oper is None:
raise UndefinedComparison(
"Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
)
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
return oper(lhs, rhs)
class Undefined(object):
class Undefined:
pass
@ -230,7 +226,7 @@ def _get_env(environment, name):
if isinstance(value, Undefined):
raise UndefinedEnvironmentName(
"{0!r} does not exist in evaluation environment.".format(name)
f"{name!r} does not exist in evaluation environment."
)
return value
@ -241,7 +237,7 @@ def _evaluate_markers(markers, environment):
groups = [[]] # type: List[List[bool]]
for marker in markers:
assert isinstance(marker, (list, tuple, string_types))
assert isinstance(marker, (list, tuple, str))
if isinstance(marker, list):
groups[-1].append(_evaluate_markers(marker, environment))
@ -275,16 +271,8 @@ def format_full_version(info):
def default_environment():
# type: () -> Dict[str, str]
if hasattr(sys, "implementation"):
# Ignoring the `sys.implementation` reference for type checking due to
# mypy not liking that the attribute doesn't exist in Python 2.7 when
# run with the `--py27` flag.
iver = format_full_version(sys.implementation.version) # type: ignore
implementation_name = sys.implementation.name # type: ignore
else:
iver = "0"
implementation_name = ""
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
return {
"implementation_name": implementation_name,
"implementation_version": iver,
@ -300,16 +288,16 @@ def default_environment():
}
class Marker(object):
class Marker:
def __init__(self, marker):
# type: (str) -> None
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc : e.loc + 8]
raise InvalidMarker(
f"Invalid marker: {marker!r}, parse error at "
f"{marker[e.loc : e.loc + 8]!r}"
)
raise InvalidMarker(err_str)
def __str__(self):
# type: () -> str
@ -317,7 +305,7 @@ class Marker(object):
def __repr__(self):
# type: () -> str
return "<Marker({0!r})>".format(str(self))
return f"<Marker('{self}')>"
def evaluate(self, environment=None):
# type: (Optional[Dict[str, str]]) -> bool

34
packaging/requirements.py

@ -1,13 +1,12 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import re
import string
import sys
import urllib.parse
from pyparsing import ( # noqa: N817
from pyparsing import ( # noqa
Combine,
Literal as L,
Optional,
@ -24,12 +23,6 @@ from ._typing import TYPE_CHECKING
from .markers import MARKER_EXPR, Marker
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
if sys.version_info[0] >= 3:
from urllib import parse as urlparse # pragma: no cover
else: # pragma: no cover
import urlparse
if TYPE_CHECKING: # pragma: no cover
from typing import List, Optional as TOptional, Set
@ -70,7 +63,7 @@ VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
@ -94,7 +87,7 @@ REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
REQUIREMENT.parseString("x[]")
class Requirement(object):
class Requirement:
"""Parse a requirement.
Parse a given requirement string into its parts, such as name, specifier,
@ -113,21 +106,19 @@ class Requirement(object):
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
'Parse error at "{0!r}": {1}'.format(
requirement_string[e.loc : e.loc + 8], e.msg
)
f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}'
)
self.name = req.name # type: str
if req.url:
parsed_url = urlparse.urlparse(req.url)
parsed_url = urllib.parse.urlparse(req.url)
if parsed_url.scheme == "file":
if urlparse.urlunparse(parsed_url) != req.url:
if urllib.parse.urlunparse(parsed_url) != req.url:
raise InvalidRequirement("Invalid URL given")
elif not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
raise InvalidRequirement(f"Invalid URL: {req.url}")
self.url = req.url # type: TOptional[str]
else:
self.url = None
@ -140,21 +131,22 @@ class Requirement(object):
parts = [self.name] # type: List[str]
if self.extras:
parts.append("[{0}]".format(",".join(sorted(self.extras))))
formatted_extras = ",".join(sorted(self.extras))
parts.append(f"[{formatted_extras}]")
if self.specifier:
parts.append(str(self.specifier))
if self.url:
parts.append("@ {0}".format(self.url))
parts.append(f"@ {self.url}")
if self.marker:
parts.append(" ")
if self.marker:
parts.append("; {0}".format(self.marker))
parts.append(f"; {self.marker}")
return "".join(parts)
def __repr__(self):
# type: () -> str
return "<Requirement({0!r})>".format(str(self))
return f"<Requirement('{self}')>"

45
packaging/specifiers.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import abc
import functools
@ -9,13 +8,22 @@ import itertools
import re
import warnings
from ._compat import string_types, with_metaclass
from ._typing import TYPE_CHECKING
from .utils import canonicalize_version
from .version import LegacyVersion, Version, parse
if TYPE_CHECKING: # pragma: no cover
from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
from typing import (
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Set,
Tuple,
Union,
)
ParsedVersion = Union[Version, LegacyVersion]
UnparsedVersion = Union[Version, LegacyVersion, str]
@ -28,7 +36,7 @@ class InvalidSpecifier(ValueError):
"""
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
class BaseSpecifier(metaclass=abc.ABCMeta):
@abc.abstractmethod
def __str__(self):
# type: () -> str
@ -95,12 +103,13 @@ class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
class _IndividualSpecifier(BaseSpecifier):
_operators = {} # type: Dict[str, str]
_regex = None # type: re.Pattern[str]
def __init__(self, spec="", prereleases=None):
# type: (str, Optional[bool]) -> None
match = self._regex.search(spec)
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
self._spec = (
match.group("operator").strip(),
@ -113,16 +122,16 @@ class _IndividualSpecifier(BaseSpecifier):
def __repr__(self):
# type: () -> str
pre = (
", prereleases={0!r}".format(self.prereleases)
f", prereleases={self.prereleases!r}"
if self._prereleases is not None
else ""
)
return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
# type: () -> str
return "{0}{1}".format(*self._spec)
return "{}{}".format(*self._spec)
@property
def _canonical_spec(self):
@ -135,7 +144,7 @@ class _IndividualSpecifier(BaseSpecifier):
def __eq__(self, other):
# type: (object) -> bool
if isinstance(other, string_types):
if isinstance(other, str):
try:
other = self.__class__(str(other))
except InvalidSpecifier:
@ -147,7 +156,7 @@ class _IndividualSpecifier(BaseSpecifier):
def __ne__(self, other):
# type: (object) -> bool
if isinstance(other, string_types):
if isinstance(other, str):
try:
other = self.__class__(str(other))
except InvalidSpecifier:
@ -160,7 +169,7 @@ class _IndividualSpecifier(BaseSpecifier):
def _get_operator(self, op):
# type: (str) -> CallableOperator
operator_callable = getattr(
self, "_compare_{0}".format(self._operators[op])
self, f"_compare_{self._operators[op]}"
) # type: CallableOperator
return operator_callable
@ -278,7 +287,7 @@ class LegacySpecifier(_IndividualSpecifier):
def __init__(self, spec="", prereleases=None):
# type: (str, Optional[bool]) -> None
super(LegacySpecifier, self).__init__(spec, prereleases)
super().__init__(spec, prereleases)
warnings.warn(
"Creating a LegacyVersion has been deprecated and will be "
@ -672,7 +681,7 @@ class SpecifierSet(BaseSpecifier):
# Parsed each individual specifier, attempting first to make it a
# Specifier and falling back to a LegacySpecifier.
parsed = set()
parsed = set() # type: Set[_IndividualSpecifier]
for specifier in split_specifiers:
try:
parsed.add(Specifier(specifier))
@ -689,12 +698,12 @@ class SpecifierSet(BaseSpecifier):
def __repr__(self):
# type: () -> str
pre = (
", prereleases={0!r}".format(self.prereleases)
f", prereleases={self.prereleases!r}"
if self._prereleases is not None
else ""
)
return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
return "<SpecifierSet({!r}{})>".format(str(self), pre)
def __str__(self):
# type: () -> str
@ -706,7 +715,7 @@ class SpecifierSet(BaseSpecifier):
def __and__(self, other):
# type: (Union[SpecifierSet, str]) -> SpecifierSet
if isinstance(other, string_types):
if isinstance(other, str):
other = SpecifierSet(other)
elif not isinstance(other, SpecifierSet):
return NotImplemented
@ -730,7 +739,7 @@ class SpecifierSet(BaseSpecifier):
def __eq__(self, other):
# type: (object) -> bool
if isinstance(other, (string_types, _IndividualSpecifier)):
if isinstance(other, (str, _IndividualSpecifier)):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented
@ -739,7 +748,7 @@ class SpecifierSet(BaseSpecifier):
def __ne__(self, other):
# type: (object) -> bool
if isinstance(other, (string_types, _IndividualSpecifier)):
if isinstance(other, (str, _IndividualSpecifier)):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented

55
packaging/tags.py

@ -2,18 +2,8 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import
import distutils.util
try:
from importlib.machinery import EXTENSION_SUFFIXES
except ImportError: # pragma: no cover
import imp
EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
del imp
import collections
import distutils.util
import logging
import os
import platform
@ -22,6 +12,7 @@ import struct
import sys
import sysconfig
import warnings
from importlib.machinery import EXTENSION_SUFFIXES
from ._typing import TYPE_CHECKING, cast
@ -76,7 +67,7 @@ _LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int]
glibcVersion = collections.namedtuple("Version", ["major", "minor"])
class Tag(object):
class Tag:
"""
A representation of the tag triple for a wheel.
@ -130,7 +121,7 @@ class Tag(object):
def __str__(self):
# type: () -> str
return "{}-{}-{}".format(self._interpreter, self._abi, self._platform)
return f"{self._interpreter}-{self._abi}-{self._platform}"
def __repr__(self):
# type: () -> str
@ -164,9 +155,7 @@ def _warn_keyword_parameter(func_name, kwargs):
elif len(kwargs) > 1 or "warn" not in kwargs:
kwargs.pop("warn", None)
arg = next(iter(kwargs.keys()))
raise TypeError(
"{}() got an unexpected keyword argument {!r}".format(func_name, arg)
)
raise TypeError(f"{func_name}() got an unexpected keyword argument {arg!r}")
return kwargs["warn"]
@ -222,7 +211,7 @@ def _cpython_abis(py_version, warn=False):
elif debug:
# Debug builds can also load "normal" extension modules.
# We can also assume no UCS-4 or pymalloc requirement.
abis.append("cp{version}".format(version=version))
abis.append(f"cp{version}")
abis.insert(
0,
"cp{version}{debug}{pymalloc}{ucs4}".format(
@ -236,7 +225,7 @@ def cpython_tags(
python_version=None, # type: Optional[PythonVersion]
abis=None, # type: Optional[Iterable[str]]
platforms=None, # type: Optional[Iterable[str]]
**kwargs # type: bool
**kwargs, # type: bool
):
# type: (...) -> Iterator[Tag]
"""
@ -278,10 +267,8 @@ def cpython_tags(
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
if _abi3_applies(python_version):
for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms):
yield tag
for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms):
yield tag
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
if _abi3_applies(python_version):
for minor_version in range(python_version[1] - 1, 1, -1):
@ -303,7 +290,7 @@ def generic_tags(
interpreter=None, # type: Optional[str]
abis=None, # type: Optional[Iterable[str]]
platforms=None, # type: Optional[Iterable[str]]
**kwargs # type: bool
**kwargs, # type: bool
):
# type: (...) -> Iterator[Tag]
"""
@ -630,7 +617,7 @@ def _get_glibc_version():
# identify the architecture of the running executable in some cases, so we
# determine it dynamically by reading the information from the running
# process. This only applies on Linux, which uses the ELF format.
class _ELFFileHeader(object):
class _ELFFileHeader:
# https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
class _InvalidELFFileHeader(ValueError):
"""
@ -699,7 +686,7 @@ def _get_elf_header():
try:
with open(sys.executable, "rb") as f:
elf_header = _ELFFileHeader(f)
except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
return None
return elf_header
@ -789,8 +776,7 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
linux = "linux_armv7l"
_, arch = linux.split("_", 1)
if _have_compatible_manylinux_abi(arch):
for tag in _manylinux_tags(linux, arch):
yield tag
yield from _manylinux_tags(linux, arch)
yield linux
@ -817,11 +803,7 @@ def interpreter_name():
"""
Returns the name of the running interpreter.
"""
try:
name = sys.implementation.name # type: ignore
except AttributeError: # pragma: no cover
# Python 2.7 compatibility.
name = platform.python_implementation().lower()
name = sys.implementation.name
return INTERPRETER_SHORT_NAMES.get(name) or name
@ -856,11 +838,8 @@ def sys_tags(**kwargs):
interp_name = interpreter_name()
if interp_name == "cp":
for tag in cpython_tags(warn=warn):
yield tag
yield from cpython_tags(warn=warn)
else:
for tag in generic_tags():
yield tag
yield from generic_tags()
for tag in compatible_tags():
yield tag
yield from compatible_tags()

21
packaging/utils.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import re
@ -60,7 +59,7 @@ def canonicalize_version(version):
# Epoch
if version.epoch != 0:
parts.append("{0}!".format(version.epoch))
parts.append(f"{version.epoch}!")
# Release segment
# NB: This strips trailing '.0's to normalize
@ -72,15 +71,15 @@ def canonicalize_version(version):
# Post-release
if version.post is not None:
parts.append(".post{0}".format(version.post))
parts.append(f".post{version.post}")
# Development release
if version.dev is not None:
parts.append(".dev{0}".format(version.dev))
parts.append(f".dev{version.dev}")
# Local version segment
if version.local is not None:
parts.append("+{0}".format(version.local))
parts.append(f"+{version.local}")
return "".join(parts)
@ -89,21 +88,21 @@ def parse_wheel_filename(filename):
# type: (str) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]
if not filename.endswith(".whl"):
raise InvalidWheelFilename(
"Invalid wheel filename (extension must be '.whl'): {0}".format(filename)
f"Invalid wheel filename (extension must be '.whl'): {filename}"
)
filename = filename[:-4]
dashes = filename.count("-")
if dashes not in (4, 5):
raise InvalidWheelFilename(
"Invalid wheel filename (wrong number of parts): {0}".format(filename)
f"Invalid wheel filename (wrong number of parts): {filename}"
)
parts = filename.split("-", dashes - 2)
name_part = parts[0]
# See PEP 427 for the rules on escaping the project name
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
raise InvalidWheelFilename("Invalid project name: {0}".format(filename))
raise InvalidWheelFilename(f"Invalid project name: {filename}")
name = canonicalize_name(name_part)
version = Version(parts[1])
if dashes == 5:
@ -111,7 +110,7 @@ def parse_wheel_filename(filename):
build_match = _build_tag_regex.match(build_part)
if build_match is None:
raise InvalidWheelFilename(
"Invalid build number: {0} in '{1}'".format(build_part, filename)
f"Invalid build number: {build_part} in '{filename}'"
)
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
else:
@ -124,14 +123,14 @@ def parse_sdist_filename(filename):
# type: (str) -> Tuple[NormalizedName, Version]
if not filename.endswith(".tar.gz"):
raise InvalidSdistFilename(
"Invalid sdist filename (extension must be '.tar.gz'): {0}".format(filename)
f"Invalid sdist filename (extension must be '.tar.gz'): {filename}"
)
# We are requiring a PEP 440 version, which cannot contain dashes,
# so we split on the last dash.
name_part, sep, version_part = filename[:-7].rpartition("-")
if not sep:
raise InvalidSdistFilename("Invalid sdist filename: {0}".format(filename))
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
name = canonicalize_name(name_part)
version = Version(version_part)

19
packaging/version.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import collections
import itertools
@ -65,7 +64,7 @@ class InvalidVersion(ValueError):
"""
class _BaseVersion(object):
class _BaseVersion:
_key = None # type: Union[CmpKey, LegacyCmpKey]
def __hash__(self):
@ -136,7 +135,7 @@ class LegacyVersion(_BaseVersion):
def __repr__(self):
# type: () -> str
return "<LegacyVersion({0})>".format(repr(str(self)))
return f"<LegacyVersion('{self}')>"
@property
def public(self):
@ -295,7 +294,7 @@ class Version(_BaseVersion):
# Validate the version and parse it into pieces
match = self._regex.search(version)
if not match:
raise InvalidVersion("Invalid version: '{0}'".format(version))
raise InvalidVersion(f"Invalid version: '{version}'")
# Store the parsed out pieces of the version
self._version = _Version(
@ -321,7 +320,7 @@ class Version(_BaseVersion):
def __repr__(self):
# type: () -> str
return "<Version({0})>".format(repr(str(self)))
return f"<Version('{self}')>"
def __str__(self):
# type: () -> str
@ -329,7 +328,7 @@ class Version(_BaseVersion):
# Epoch
if self.epoch != 0:
parts.append("{0}!".format(self.epoch))
parts.append(f"{self.epoch}!")
# Release segment
parts.append(".".join(str(x) for x in self.release))
@ -340,15 +339,15 @@ class Version(_BaseVersion):
# Post-release
if self.post is not None:
parts.append(".post{0}".format(self.post))
parts.append(f".post{self.post}")
# Development release
if self.dev is not None:
parts.append(".dev{0}".format(self.dev))
parts.append(f".dev{self.dev}")
# Local version segment
if self.local is not None:
parts.append("+{0}".format(self.local))
parts.append(f"+{self.local}")
return "".join(parts)
@ -400,7 +399,7 @@ class Version(_BaseVersion):
# Epoch
if self.epoch != 0:
parts.append("{0}!".format(self.epoch))
parts.append(f"{self.epoch}!")
# Release segment
parts.append(".".join(str(x) for x in self.release))

3
setup.cfg

@ -1,6 +1,3 @@
[bdist_wheel]
universal=1
[isort]
profile = black
combine_as_imports = true

8
setup.py

@ -2,7 +2,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import os
import re
@ -48,7 +47,7 @@ setup(
url=about["__uri__"],
author=about["__author__"],
author_email=about["__email__"],
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
python_requires=">=3.6",
install_requires=["pyparsing>=2.0.2"], # Needed to avoid issue #91
classifiers=[
"Development Status :: 5 - Production/Stable",
@ -56,11 +55,8 @@ setup(
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3 :: only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",

1
tasks/__init__.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import invoke

20
tasks/check.py

@ -5,11 +5,7 @@
import itertools
import json
import os.path
try:
import xmlrpc.client as xmlrpc_client
except ImportError:
import xmlrpclib as xmlrpc_client
import xmlrpc.client
import invoke
import pkg_resources
@ -35,7 +31,7 @@ def pep440(cached=False):
# possible
if cached:
try:
with open(cache_path, "r") as fp:
with open(cache_path) as fp:
data = json.load(fp)
except Exception:
data = None
@ -45,14 +41,12 @@ def pep440(cached=False):
# If we don't have data, then let's go fetch it from PyPI
if data is None:
bar = progress.bar.ShadyBar("Fetching Versions")
client = xmlrpc_client.Server("https://pypi.python.org/pypi")
client = xmlrpc.client.Server("https://pypi.python.org/pypi")
data = dict(
[
(project, client.package_releases(project, True))
for project in bar.iter(client.list_packages())
]
)
data = {
project: client.package_releases(project, True)
for project in bar.iter(client.list_packages())
}
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
with open(cache_path, "w") as fp:

1
tests/__init__.py

@ -1,4 +1,3 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function

87
tests/test_markers.py

@ -1,7 +1,6 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import collections
import itertools
@ -9,7 +8,6 @@ import os
import platform
import sys
import pretend
import pytest
from packaging.markers import (
@ -70,7 +68,7 @@ class TestNode:
@pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []])
def test_repr(self, value):
assert repr(Node(value)) == "<Node({0!r})>".format(str(value))
assert repr(Node(value)) == "<Node({!r})>".format(str(value))
def test_base_class(self):
with pytest.raises(NotImplementedError):
@ -95,51 +93,6 @@ FakeVersionInfo = collections.namedtuple(
class TestDefaultEnvironment:
@pytest.mark.skipif(
hasattr(sys, "implementation"), reason="sys.implementation does exist"
)
def test_matches_expected_no_sys_implementation(self):
environment = default_environment()
assert environment == {
"implementation_name": "",
"implementation_version": "0",
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
@pytest.mark.skipif(
not hasattr(sys, "implementation"), reason="sys.implementation does not exist"
)
def test_matches_expected_deleted_sys_implementation(self, monkeypatch):
monkeypatch.delattr(sys, "implementation")
environment = default_environment()
assert environment == {
"implementation_name": "",
"implementation_version": "0",
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
@pytest.mark.skipif(
not hasattr(sys, "implementation"), reason="sys.implementation does not exist"
)
def test_matches_expected(self):
environment = default_environment()
@ -165,32 +118,6 @@ class TestDefaultEnvironment:
"sys_platform": sys.platform,
}
@pytest.mark.skipif(
hasattr(sys, "implementation"), reason="sys.implementation does exist"
)
def test_monkeypatch_sys_implementation(self, monkeypatch):
monkeypatch.setattr(
sys,
"implementation",
pretend.stub(version=FakeVersionInfo(3, 4, 2, "final", 0), name="linux"),
raising=False,
)
environment = default_environment()
assert environment == {
"implementation_name": "linux",
"implementation_version": "3.4.2",
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
def test_multidigit_minor_version(self, monkeypatch):
version_info = (3, 10, 0, "final", 0)
monkeypatch.setattr(sys, "version_info", version_info, raising=False)
@ -216,7 +143,7 @@ class TestMarker:
@pytest.mark.parametrize(
"marker_string",
[
"{0} {1} {2!r}".format(*i)
"{} {} {!r}".format(*i)
for i in itertools.product(VARIABLES, OPERATORS, VALUES)
]
+ [
@ -278,7 +205,7 @@ class TestMarker:
def test_str_and_repr(self, marker_string, expected):
m = Marker(marker_string)
assert str(m) == expected
assert repr(m) == "<Marker({0!r})>".format(str(m))
assert repr(m) == "<Marker({!r})>".format(str(m))
def test_extra_with_no_extra_in_environment(self):
# We can't evaluate an extra if no extra is passed into the environment
@ -289,7 +216,7 @@ class TestMarker:
@pytest.mark.parametrize(
("marker_string", "environment", "expected"),
[
("os_name == '{0}'".format(os.name), None, True),
(f"os_name == '{os.name}'", None, True),
("os_name == 'foo'", {"os_name": "foo"}, True),
("os_name == 'foo'", {"os_name": "bar"}, False),
("'2.7' in python_version", {"python_version": "2.7.5"}, True),
@ -328,7 +255,7 @@ class TestMarker:
@pytest.mark.parametrize(
"marker_string",
[
"{0} {1} {2!r}".format(*i)
"{} {} {!r}".format(*i)
for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES)