summaryrefslogtreecommitdiff
diff options
authorMatthias Klose <doko@debian.org>2021-10-11 14:28:40 +0200
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2021-10-11 16:38:36 +0000
commit3ff6fce1972b87cc992bccba5a42f68ace3298d9 (patch)
treed2a7fb7b83ab1f8d18950cc6b092c049e42869fa
parentbfea0c3a68a68cd435cb06cb300bc11c7557296b (diff)
21.0-1 (patches unapplied)import/21.0-1
Imported using git-ubuntu import.
Notes
Notes: * New upstream version. * New standards version.
-rw-r--r--.pre-commit-config.yaml14
-rw-r--r--CHANGELOG.rst12
-rw-r--r--MANIFEST.in7
-rw-r--r--PKG-INFO798
-rw-r--r--debian/changelog7
-rw-r--r--debian/control2
-rw-r--r--debian/patches/get_platform.diff181
-rw-r--r--debian/patches/series1
-rw-r--r--docs/conf.py2
-rw-r--r--docs/development/getting-started.rst5
-rw-r--r--docs/development/release-process.rst4
-rw-r--r--docs/development/submitting-patches.rst7
-rw-r--r--docs/specifiers.rst2
-rw-r--r--mypy.ini5
-rw-r--r--packaging.egg-info/PKG-INFO798
-rw-r--r--packaging.egg-info/SOURCES.txt32
-rw-r--r--packaging/__about__.py3
-rw-r--r--packaging/__init__.py1
-rw-r--r--packaging/_compat.py38
-rw-r--r--packaging/_manylinux.py301
-rw-r--r--packaging/_musllinux.py136
-rw-r--r--packaging/_structures.py59
-rw-r--r--packaging/_typing.py48
-rw-r--r--packaging/markers.py118
-rw-r--r--packaging/requirements.py60
-rw-r--r--packaging/specifiers.py296
-rw-r--r--packaging/tags.py544
-rw-r--r--packaging/utils.py76
-rw-r--r--packaging/version.py236
-rw-r--r--setup.cfg3
-rw-r--r--setup.py8
-rw-r--r--tests/__init__.py1
-rwxr-xr-xtests/manylinux/hello-world-armv7l-armel (renamed from tests/hello-world-armv7l-armel)bin52 -> 52 bytes
-rwxr-xr-xtests/manylinux/hello-world-armv7l-armhf (renamed from tests/hello-world-armv7l-armhf)bin52 -> 52 bytes
-rwxr-xr-xtests/manylinux/hello-world-invalid-class (renamed from tests/hello-world-invalid-class)bin52 -> 52 bytes
-rwxr-xr-xtests/manylinux/hello-world-invalid-data (renamed from tests/hello-world-invalid-data)bin52 -> 52 bytes
-rwxr-xr-xtests/manylinux/hello-world-invalid-magic (renamed from tests/hello-world-invalid-magic)bin52 -> 52 bytes
-rw-r--r--tests/manylinux/hello-world-s390x-s390x (renamed from tests/hello-world-s390x-s390x)bin64 -> 64 bytes
-rw-r--r--tests/manylinux/hello-world-too-short (renamed from tests/hello-world-too-short)bin40 -> 40 bytes
-rw-r--r--tests/manylinux/hello-world-x86_64-amd64 (renamed from tests/hello-world-x86_64-amd64)bin64 -> 64 bytes
-rwxr-xr-xtests/manylinux/hello-world-x86_64-i386 (renamed from tests/hello-world-x86_64-i386)bin52 -> 52 bytes
-rwxr-xr-xtests/manylinux/hello-world-x86_64-x32 (renamed from tests/hello-world-x86_64-x32)bin52 -> 52 bytes
-rwxr-xr-xtests/musllinux/glibc-x86_64bin0 -> 1024 bytes
-rwxr-xr-xtests/musllinux/musl-aarch64bin0 -> 1024 bytes
-rwxr-xr-xtests/musllinux/musl-i386bin0 -> 1024 bytes
-rwxr-xr-xtests/musllinux/musl-x86_64bin0 -> 1024 bytes
-rw-r--r--tests/test_manylinux.py253
-rw-r--r--tests/test_markers.py87
-rw-r--r--tests/test_musllinux.py146
-rw-r--r--tests/test_requirements.py3
-rw-r--r--tests/test_specifiers.py8
-rw-r--r--tests/test_structures.py3
-rw-r--r--tests/test_tags.py399
-rw-r--r--tests/test_utils.py13
-rw-r--r--tests/test_version.py13
55 files changed, 2259 insertions, 2471 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5d8ba1f..df47a8a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.3.0
+ rev: v3.4.0
hooks:
- id: check-toml
- id: check-yaml
@@ -8,28 +8,24 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.790
+ rev: v0.812
hooks:
- 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
+ rev: 20.8b1
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
- rev: 5.6.4
+ rev: 5.8.0
hooks:
- id: isort
- repo: https://gitlab.com/PyCQA/flake8
- rev: "3.7.8"
+ rev: "3.9.0"
hooks:
- id: flake8
additional_dependencies: ["pep8-naming"]
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f368c45..7017c78 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,10 +1,16 @@
Changelog
---------
+21.0 - 2021-07-03
+~~~~~~~~~~~~~~~~~
+
+* `packaging` is now only compatible with Python 3.6 and above.
+* Add support for zip files in ``parse_sdist_filename`` (:issue:`429`)
+
20.9 - 2021-01-29
~~~~~~~~~~~~~~~~~
-* Run [isort](https://pypi.org/project/isort/) over the code base (:issue:`377`)
+* Run `isort <https://pypi.org/project/isort/>`_ over the code base (:issue:`377`)
* Add support for the ``macosx_10_*_universal2`` platform tags (:issue:`379`)
* Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
(:issue:`387` and :issue:`389`)
@@ -74,7 +80,7 @@ No unreleased changes.
* Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (:issue:`193`)
-* Expand upon the API provded by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (:issue:`187`)
+* Expand upon the API provided by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (:issue:`187`)
* Officially support Python 3.8 (:issue:`232`)
@@ -225,7 +231,7 @@ No unreleased changes.
15.2 - 2015-05-13
~~~~~~~~~~~~~~~~~
-* Fix an error where the arbitary specifier (``===``) was not correctly
+* Fix an error where the arbitrary specifier (``===``) was not correctly
allowing pre-releases when it was being used.
* Expose the specifier and version parts through properties on the
diff --git a/MANIFEST.in b/MANIFEST.in
index 82485c2..a078133 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,13 +8,16 @@ include mypy.ini
recursive-include docs *
recursive-include tests *.py
-recursive-include tests hello-world-*
+recursive-include tests/manylinux hello-world-*
+recursive-include tests/musllinux glibc-*
+recursive-include tests/musllinux musl-*
exclude noxfile.py
exclude .readthedocs.yml
exclude .travis.yml
exclude dev-requirements.txt
-exclude tests/build-hello-world.sh
+exclude tests/manylinux/build-hello-world.sh
+exclude tests/musllinux/build.sh
exclude tests/hello-world.c
prune docs/_build
diff --git a/PKG-INFO b/PKG-INFO
index 7865ede..017764a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,416 +1,424 @@
Metadata-Version: 2.1
Name: packaging
-Version: 20.9
+Version: 21.0
Summary: Core utilities for Python packages
Home-page: https://github.com/pypa/packaging
Author: Donald Stufft and individual contributors
Author-email: donald@stufft.io
License: BSD-2-Clause or Apache-2.0
-Description: packaging
- =========
-
- .. start-intro
-
- Reusable core utilities for various Python Packaging
- `interoperability specifications <https://packaging.python.org/specifications/>`_.
-
- This library provides utilities that implement the interoperability
- specifications which have clearly one correct behaviour (eg: :pep:`440`)
- or benefit greatly from having a single shared implementation (eg: :pep:`425`).
-
- .. end-intro
-
- The ``packaging`` project includes the following: version handling, specifiers,
- markers, requirements, tags, utilities.
-
- Documentation
- -------------
-
- The `documentation`_ provides information and the API for the following:
-
- - Version Handling
- - Specifiers
- - Markers
- - Requirements
- - Tags
- - Utilities
-
- Installation
- ------------
-
- Use ``pip`` to install these utilities::
-
- pip install packaging
-
- Discussion
- ----------
-
- If you run into bugs, you can file them in our `issue tracker`_.
-
- You can also join ``#pypa`` on Freenode to ask questions or get involved.
-
-
- .. _`documentation`: https://packaging.pypa.io/
- .. _`issue tracker`: https://github.com/pypa/packaging/issues
-
-
- Code of Conduct
- ---------------
-
- Everyone interacting in the packaging project's codebases, issue trackers, chat
- rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
-
- .. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
-
- Contributing
- ------------
-
- The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
- well as how to report a potential security issue. The documentation for this
- project also covers information about `project development`_ and `security`_.
-
- .. _`project development`: https://packaging.pypa.io/en/latest/development/
- .. _`security`: https://packaging.pypa.io/en/latest/security/
-
- Project History
- ---------------
-
- Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
- recent changes and project history.
-
- .. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
-
- Changelog
- ---------
-
- 20.9 - 2021-01-29
- ~~~~~~~~~~~~~~~~~
-
- * Run [isort](https://pypi.org/project/isort/) over the code base (`#377 <https://github.com/pypa/packaging/issues/377>`__)
- * Add support for the ``macosx_10_*_universal2`` platform tags (`#379 <https://github.com/pypa/packaging/issues/379>`__)
- * Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
- (`#387 <https://github.com/pypa/packaging/issues/387>`__ and `#389 <https://github.com/pypa/packaging/issues/389>`__)
-
- 20.8 - 2020-12-11
- ~~~~~~~~~~~~~~~~~
-
- * Revert back to setuptools for compatibility purposes for some Linux distros (`#363 <https://github.com/pypa/packaging/issues/363>`__)
- * Do not insert an underscore in wheel tags when the interpreter version number
- is more than 2 digits (`#372 <https://github.com/pypa/packaging/issues/372>`__)
-
- 20.7 - 2020-11-28
- ~~~~~~~~~~~~~~~~~
-
- No unreleased changes.
-
- 20.6 - 2020-11-28
- ~~~~~~~~~~~~~~~~~
-
- .. note:: This release was subsequently yanked, and these changes were included in 20.7.
-
- * Fix flit configuration, to include LICENSE files (`#357 <https://github.com/pypa/packaging/issues/357>`__)
- * Make `intel` a recognized CPU architecture for the `universal` macOS platform tag (`#361 <https://github.com/pypa/packaging/issues/361>`__)
- * Add some missing type hints to `packaging.requirements` (issue:`350`)
-
- 20.5 - 2020-11-27
- ~~~~~~~~~~~~~~~~~
-
- * Officially support Python 3.9 (`#343 <https://github.com/pypa/packaging/issues/343>`__)
- * Deprecate the ``LegacyVersion`` and ``LegacySpecifier`` classes (`#321 <https://github.com/pypa/packaging/issues/321>`__)
- * Handle ``OSError`` on non-dynamic executables when attempting to resolve
- the glibc version string.
-
- 20.4 - 2020-05-19
- ~~~~~~~~~~~~~~~~~
-
- * Canonicalize version before comparing specifiers. (`#282 <https://github.com/pypa/packaging/issues/282>`__)
- * Change type hint for ``canonicalize_name`` to return
- ``packaging.utils.NormalizedName``.
- This enables the use of static typing tools (like mypy) to detect mixing of
- normalized and un-normalized names.
-
- 20.3 - 2020-03-05
- ~~~~~~~~~~~~~~~~~
-
- * Fix changelog for 20.2.
-
- 20.2 - 2020-03-05
- ~~~~~~~~~~~~~~~~~
-
- * Fix a bug that caused a 32-bit OS that runs on a 64-bit ARM CPU (e.g. ARM-v8,
- aarch64), to report the wrong bitness.
-
- 20.1 - 2020-01-24
- ~~~~~~~~~~~~~~~~~~~
-
- * Fix a bug caused by reuse of an exhausted iterator. (`#257 <https://github.com/pypa/packaging/issues/257>`__)
-
- 20.0 - 2020-01-06
- ~~~~~~~~~~~~~~~~~
-
- * Add type hints (`#191 <https://github.com/pypa/packaging/issues/191>`__)
-
- * Add proper trove classifiers for PyPy support (`#198 <https://github.com/pypa/packaging/issues/198>`__)
-
- * Scale back depending on ``ctypes`` for manylinux support detection (`#171 <https://github.com/pypa/packaging/issues/171>`__)
-
- * Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (`#193 <https://github.com/pypa/packaging/issues/193>`__)
-
- * Expand upon the API provded by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (`#187 <https://github.com/pypa/packaging/issues/187>`__)
-
- * Officially support Python 3.8 (`#232 <https://github.com/pypa/packaging/issues/232>`__)
-
- * Add ``major``, ``minor``, and ``micro`` aliases to ``packaging.version.Version`` (`#226 <https://github.com/pypa/packaging/issues/226>`__)
-
- * Properly mark ``packaging`` has being fully typed by adding a `py.typed` file (`#226 <https://github.com/pypa/packaging/issues/226>`__)
-
- 19.2 - 2019-09-18
- ~~~~~~~~~~~~~~~~~
-
- * Remove dependency on ``attrs`` (`#178 <https://github.com/pypa/packaging/issues/178>`__, `#179 <https://github.com/pypa/packaging/issues/179>`__)
-
- * Use appropriate fallbacks for CPython ABI tag (`#181 <https://github.com/pypa/packaging/issues/181>`__, `#185 <https://github.com/pypa/packaging/issues/185>`__)
-
- * Add manylinux2014 support (`#186 <https://github.com/pypa/packaging/issues/186>`__)
-
- * Improve ABI detection (`#181 <https://github.com/pypa/packaging/issues/181>`__)
-
- * Properly handle debug wheels for Python 3.8 (`#172 <https://github.com/pypa/packaging/issues/172>`__)
-
- * Improve detection of debug builds on Windows (`#194 <https://github.com/pypa/packaging/issues/194>`__)
-
- 19.1 - 2019-07-30
- ~~~~~~~~~~~~~~~~~
-
- * Add the ``packaging.tags`` module. (`#156 <https://github.com/pypa/packaging/issues/156>`__)
-
- * Correctly handle two-digit versions in ``python_version`` (`#119 <https://github.com/pypa/packaging/issues/119>`__)
-
-
- 19.0 - 2019-01-20
- ~~~~~~~~~~~~~~~~~
-
- * Fix string representation of PEP 508 direct URL requirements with markers.
-
- * Better handling of file URLs
-
- This allows for using ``file:///absolute/path``, which was previously
- prevented due to the missing ``netloc``.
-
- This allows for all file URLs that ``urlunparse`` turns back into the
- original URL to be valid.
-
-
- 18.0 - 2018-09-26
- ~~~~~~~~~~~~~~~~~
-
- * Improve error messages when invalid requirements are given. (`#129 <https://github.com/pypa/packaging/issues/129>`__)
-
-
- 17.1 - 2017-02-28
- ~~~~~~~~~~~~~~~~~
-
- * Fix ``utils.canonicalize_version`` when supplying non PEP 440 versions.
-
-
- 17.0 - 2017-02-28
- ~~~~~~~~~~~~~~~~~
-
- * Drop support for python 2.6, 3.2, and 3.3.
-
- * Define minimal pyparsing version to 2.0.2 (`#91 <https://github.com/pypa/packaging/issues/91>`__).
-
- * Add ``epoch``, ``release``, ``pre``, ``dev``, and ``post`` attributes to
- ``Version`` and ``LegacyVersion`` (`#34 <https://github.com/pypa/packaging/issues/34>`__).
-
- * Add ``Version().is_devrelease`` and ``LegacyVersion().is_devrelease`` to
- make it easy to determine if a release is a development release.
-
- * Add ``utils.canonicalize_version`` to canonicalize version strings or
- ``Version`` instances (`#121 <https://github.com/pypa/packaging/issues/121>`__).
-
-
- 16.8 - 2016-10-29
- ~~~~~~~~~~~~~~~~~
-
- * Fix markers that utilize ``in`` so that they render correctly.
-
- * Fix an erroneous test on Python RC releases.
-
-
- 16.7 - 2016-04-23
- ~~~~~~~~~~~~~~~~~
-
- * Add support for the deprecated ``python_implementation`` marker which was
- an undocumented setuptools marker in addition to the newer markers.
-
-
- 16.6 - 2016-03-29
- ~~~~~~~~~~~~~~~~~
-
- * Add support for the deprecated, PEP 345 environment markers in addition to
- the newer markers.
-
-
- 16.5 - 2016-02-26
- ~~~~~~~~~~~~~~~~~
-
- * Fix a regression in parsing requirements with whitespaces between the comma
- separators.
-
-
- 16.4 - 2016-02-22
- ~~~~~~~~~~~~~~~~~
-
- * Fix a regression in parsing requirements like ``foo (==4)``.
-
-
- 16.3 - 2016-02-21
- ~~~~~~~~~~~~~~~~~
-
- * Fix a bug where ``packaging.requirements:Requirement`` was overly strict when
- matching legacy requirements.
-
-
- 16.2 - 2016-02-09
- ~~~~~~~~~~~~~~~~~
-
- * Add a function that implements the name canonicalization from PEP 503.
-
-
- 16.1 - 2016-02-07
- ~~~~~~~~~~~~~~~~~
-
- * Implement requirement specifiers from PEP 508.
-
-
- 16.0 - 2016-01-19
- ~~~~~~~~~~~~~~~~~
-
- * Relicense so that packaging is available under *either* the Apache License,
- Version 2.0 or a 2 Clause BSD license.
-
- * Support installation of packaging when only distutils is available.
-
- * Fix ``==`` comparison when there is a prefix and a local version in play.
- (`#41 <https://github.com/pypa/packaging/issues/41>`__).
-
- * Implement environment markers from PEP 508.
-
-
- 15.3 - 2015-08-01
- ~~~~~~~~~~~~~~~~~
-
- * Normalize post-release spellings for rev/r prefixes. `#35 <https://github.com/pypa/packaging/issues/35>`__
-
-
- 15.2 - 2015-05-13
- ~~~~~~~~~~~~~~~~~
-
- * Fix an error where the arbitary specifier (``===``) was not correctly
- allowing pre-releases when it was being used.
-
- * Expose the specifier and version parts through properties on the
- ``Specifier`` classes.
-
- * Allow iterating over the ``SpecifierSet`` to get access to all of the
- ``Specifier`` instances.
-
- * Allow testing if a version is contained within a specifier via the ``in``
- operator.
-
-
- 15.1 - 2015-04-13
- ~~~~~~~~~~~~~~~~~
-
- * Fix a logic error that was causing inconsistent answers about whether or not
- a pre-release was contained within a ``SpecifierSet`` or not.
-
-
- 15.0 - 2015-01-02
- ~~~~~~~~~~~~~~~~~
-
- * Add ``Version().is_postrelease`` and ``LegacyVersion().is_postrelease`` to
- make it easy to determine if a release is a post release.
-
- * Add ``Version().base_version`` and ``LegacyVersion().base_version`` to make
- it easy to get the public version without any pre or post release markers.
-
- * Support the update to PEP 440 which removed the implied ``!=V.*`` when using
- either ``>V`` or ``<V`` and which instead special cased the handling of
- pre-releases, post-releases, and local versions when using ``>V`` or ``<V``.
-
-
- 14.5 - 2014-12-17
- ~~~~~~~~~~~~~~~~~
-
- * Normalize release candidates as ``rc`` instead of ``c``.
-
- * Expose the ``VERSION_PATTERN`` constant, a regular expression matching
- a valid version.
-
-
- 14.4 - 2014-12-15
- ~~~~~~~~~~~~~~~~~
-
- * Ensure that versions are normalized before comparison when used in a
- specifier with a less than (``<``) or greater than (``>``) operator.
-
-
- 14.3 - 2014-11-19
- ~~~~~~~~~~~~~~~~~
-
- * **BACKWARDS INCOMPATIBLE** Refactor specifier support so that it can sanely
- handle legacy specifiers as well as PEP 440 specifiers.
-
- * **BACKWARDS INCOMPATIBLE** Move the specifier support out of
- ``packaging.version`` into ``packaging.specifiers``.
-
-
- 14.2 - 2014-09-10
- ~~~~~~~~~~~~~~~~~
-
- * Add prerelease support to ``Specifier``.
- * Remove the ability to do ``item in Specifier()`` and replace it with
- ``Specifier().contains(item)`` in order to allow flags that signal if a
- prerelease should be accepted or not.
- * Add a method ``Specifier().filter()`` which will take an iterable and returns
- an iterable with items that do not match the specifier filtered out.
-
-
- 14.1 - 2014-09-08
- ~~~~~~~~~~~~~~~~~
-
- * Allow ``LegacyVersion`` and ``Version`` to be sorted together.
- * Add ``packaging.version.parse()`` to enable easily parsing a version string
- as either a ``Version`` or a ``LegacyVersion`` depending on it's PEP 440
- validity.
-
-
- 14.0 - 2014-09-05
- ~~~~~~~~~~~~~~~~~
-
- * Initial release.
-
-
- .. _`master`: https://github.com/pypa/packaging/
-
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=3.6
Description-Content-Type: text/x-rst
+License-File: LICENSE
+License-File: LICENSE.APACHE
+License-File: LICENSE.BSD
+
+packaging
+=========
+
+.. start-intro
+
+Reusable core utilities for various Python Packaging
+`interoperability specifications <https://packaging.python.org/specifications/>`_.
+
+This library provides utilities that implement the interoperability
+specifications which have clearly one correct behaviour (eg: :pep:`440`)
+or benefit greatly from having a single shared implementation (eg: :pep:`425`).
+
+.. end-intro
+
+The ``packaging`` project includes the following: version handling, specifiers,
+markers, requirements, tags, utilities.
+
+Documentation
+-------------
+
+The `documentation`_ provides information and the API for the following:
+
+- Version Handling
+- Specifiers
+- Markers
+- Requirements
+- Tags
+- Utilities
+
+Installation
+------------
+
+Use ``pip`` to install these utilities::
+
+ pip install packaging
+
+Discussion
+----------
+
+If you run into bugs, you can file them in our `issue tracker`_.
+
+You can also join ``#pypa`` on Freenode to ask questions or get involved.
+
+
+.. _`documentation`: https://packaging.pypa.io/
+.. _`issue tracker`: https://github.com/pypa/packaging/issues
+
+
+Code of Conduct
+---------------
+
+Everyone interacting in the packaging project's codebases, issue trackers, chat
+rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
+
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+
+Contributing
+------------
+
+The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
+well as how to report a potential security issue. The documentation for this
+project also covers information about `project development`_ and `security`_.
+
+.. _`project development`: https://packaging.pypa.io/en/latest/development/
+.. _`security`: https://packaging.pypa.io/en/latest/security/
+
+Project History
+---------------
+
+Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
+recent changes and project history.
+
+.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
+
+Changelog
+---------
+
+21.0 - 2021-07-03
+~~~~~~~~~~~~~~~~~
+
+* `packaging` is now only compatible with Python 3.6 and above.
+* Add support for zip files in ``parse_sdist_filename`` (`#429 <https://github.com/pypa/packaging/issues/429>`__)
+
+20.9 - 2021-01-29
+~~~~~~~~~~~~~~~~~
+
+* Run `isort <https://pypi.org/project/isort/>`_ over the code base (`#377 <https://github.com/pypa/packaging/issues/377>`__)
+* Add support for the ``macosx_10_*_universal2`` platform tags (`#379 <https://github.com/pypa/packaging/issues/379>`__)
+* Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
+ (`#387 <https://github.com/pypa/packaging/issues/387>`__ and `#389 <https://github.com/pypa/packaging/issues/389>`__)
+
+20.8 - 2020-12-11
+~~~~~~~~~~~~~~~~~
+
+* Revert back to setuptools for compatibility purposes for some Linux distros (`#363 <https://github.com/pypa/packaging/issues/363>`__)
+* Do not insert an underscore in wheel tags when the interpreter version number
+ is more than 2 digits (`#372 <https://github.com/pypa/packaging/issues/372>`__)
+
+20.7 - 2020-11-28
+~~~~~~~~~~~~~~~~~
+
+No unreleased changes.
+
+20.6 - 2020-11-28
+~~~~~~~~~~~~~~~~~
+
+.. note:: This release was subsequently yanked, and these changes were included in 20.7.
+
+* Fix flit configuration, to include LICENSE files (`#357 <https://github.com/pypa/packaging/issues/357>`__)
+* Make `intel` a recognized CPU architecture for the `universal` macOS platform tag (`#361 <https://github.com/pypa/packaging/issues/361>`__)
+* Add some missing type hints to `packaging.requirements` (issue:`350`)
+
+20.5 - 2020-11-27
+~~~~~~~~~~~~~~~~~
+
+* Officially support Python 3.9 (`#343 <https://github.com/pypa/packaging/issues/343>`__)
+* Deprecate the ``LegacyVersion`` and ``LegacySpecifier`` classes (`#321 <https://github.com/pypa/packaging/issues/321>`__)
+* Handle ``OSError`` on non-dynamic executables when attempting to resolve
+ the glibc version string.
+
+20.4 - 2020-05-19
+~~~~~~~~~~~~~~~~~
+
+* Canonicalize version before comparing specifiers. (`#282 <https://github.com/pypa/packaging/issues/282>`__)
+* Change type hint for ``canonicalize_name`` to return
+ ``packaging.utils.NormalizedName``.
+ This enables the use of static typing tools (like mypy) to detect mixing of
+ normalized and un-normalized names.
+
+20.3 - 2020-03-05
+~~~~~~~~~~~~~~~~~
+
+* Fix changelog for 20.2.
+
+20.2 - 2020-03-05
+~~~~~~~~~~~~~~~~~
+
+* Fix a bug that caused a 32-bit OS that runs on a 64-bit ARM CPU (e.g. ARM-v8,
+ aarch64), to report the wrong bitness.
+
+20.1 - 2020-01-24
+~~~~~~~~~~~~~~~~~~~
+
+* Fix a bug caused by reuse of an exhausted iterator. (`#257 <https://github.com/pypa/packaging/issues/257>`__)
+
+20.0 - 2020-01-06
+~~~~~~~~~~~~~~~~~
+
+* Add type hints (`#191 <https://github.com/pypa/packaging/issues/191>`__)
+
+* Add proper trove classifiers for PyPy support (`#198 <https://github.com/pypa/packaging/issues/198>`__)
+
+* Scale back depending on ``ctypes`` for manylinux support detection (`#171 <https://github.com/pypa/packaging/issues/171>`__)
+
+* Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (`#193 <https://github.com/pypa/packaging/issues/193>`__)
+
+* Expand upon the API provided by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (`#187 <https://github.com/pypa/packaging/issues/187>`__)
+
+* Officially support Python 3.8 (`#232 <https://github.com/pypa/packaging/issues/232>`__)
+
+* Add ``major``, ``minor``, and ``micro`` aliases to ``packaging.version.Version`` (`#226 <https://github.com/pypa/packaging/issues/226>`__)
+
+* Properly mark ``packaging`` has being fully typed by adding a `py.typed` file (`#226 <https://github.com/pypa/packaging/issues/226>`__)
+
+19.2 - 2019-09-18
+~~~~~~~~~~~~~~~~~
+
+* Remove dependency on ``attrs`` (`#178 <https://github.com/pypa/packaging/issues/178>`__, `#179 <https://github.com/pypa/packaging/issues/179>`__)
+
+* Use appropriate fallbacks for CPython ABI tag (`#181 <https://github.com/pypa/packaging/issues/181>`__, `#185 <https://github.com/pypa/packaging/issues/185>`__)
+
+* Add manylinux2014 support (`#186 <https://github.com/pypa/packaging/issues/186>`__)
+
+* Improve ABI detection (`#181 <https://github.com/pypa/packaging/issues/181>`__)
+
+* Properly handle debug wheels for Python 3.8 (`#172 <https://github.com/pypa/packaging/issues/172>`__)
+
+* Improve detection of debug builds on Windows (`#194 <https://github.com/pypa/packaging/issues/194>`__)
+
+19.1 - 2019-07-30
+~~~~~~~~~~~~~~~~~
+
+* Add the ``packaging.tags`` module. (`#156 <https://github.com/pypa/packaging/issues/156>`__)
+
+* Correctly handle two-digit versions in ``python_version`` (`#119 <https://github.com/pypa/packaging/issues/119>`__)
+
+
+19.0 - 2019-01-20
+~~~~~~~~~~~~~~~~~
+
+* Fix string representation of PEP 508 direct URL requirements with markers.
+
+* Better handling of file URLs
+
+ This allows for using ``file:///absolute/path``, which was previously
+ prevented due to the missing ``netloc``.
+
+ This allows for all file URLs that ``urlunparse`` turns back into the
+ original URL to be valid.
+
+
+18.0 - 2018-09-26
+~~~~~~~~~~~~~~~~~
+
+* Improve error messages when invalid requirements are given. (`#129 <https://github.com/pypa/packaging/issues/129>`__)
+
+
+17.1 - 2017-02-28
+~~~~~~~~~~~~~~~~~
+
+* Fix ``utils.canonicalize_version`` when supplying non PEP 440 versions.
+
+
+17.0 - 2017-02-28
+~~~~~~~~~~~~~~~~~
+
+* Drop support for python 2.6, 3.2, and 3.3.
+
+* Define minimal pyparsing version to 2.0.2 (`#91 <https://github.com/pypa/packaging/issues/91>`__).
+
+* Add ``epoch``, ``release``, ``pre``, ``dev``, and ``post`` attributes to
+ ``Version`` and ``LegacyVersion`` (`#34 <https://github.com/pypa/packaging/issues/34>`__).
+
+* Add ``Version().is_devrelease`` and ``LegacyVersion().is_devrelease`` to
+ make it easy to determine if a release is a development release.
+
+* Add ``utils.canonicalize_version`` to canonicalize version strings or
+ ``Version`` instances (`#121 <https://github.com/pypa/packaging/issues/121>`__).
+
+
+16.8 - 2016-10-29
+~~~~~~~~~~~~~~~~~
+
+* Fix markers that utilize ``in`` so that they render correctly.
+
+* Fix an erroneous test on Python RC releases.
+
+
+16.7 - 2016-04-23
+~~~~~~~~~~~~~~~~~
+
+* Add support for the deprecated ``python_implementation`` marker which was
+ an undocumented setuptools marker in addition to the newer markers.
+
+
+16.6 - 2016-03-29
+~~~~~~~~~~~~~~~~~
+
+* Add support for the deprecated, PEP 345 environment markers in addition to
+ the newer markers.
+
+
+16.5 - 2016-02-26
+~~~~~~~~~~~~~~~~~
+
+* Fix a regression in parsing requirements with whitespaces between the comma
+ separators.
+
+
+16.4 - 2016-02-22
+~~~~~~~~~~~~~~~~~
+
+* Fix a regression in parsing requirements like ``foo (==4)``.
+
+
+16.3 - 2016-02-21
+~~~~~~~~~~~~~~~~~
+
+* Fix a bug where ``packaging.requirements:Requirement`` was overly strict when
+ matching legacy requirements.
+
+
+16.2 - 2016-02-09
+~~~~~~~~~~~~~~~~~
+
+* Add a function that implements the name canonicalization from PEP 503.
+
+
+16.1 - 2016-02-07
+~~~~~~~~~~~~~~~~~
+
+* Implement requirement specifiers from PEP 508.
+
+
+16.0 - 2016-01-19
+~~~~~~~~~~~~~~~~~
+
+* Relicense so that packaging is available under *either* the Apache License,
+ Version 2.0 or a 2 Clause BSD license.
+
+* Support installation of packaging when only distutils is available.
+
+* Fix ``==`` comparison when there is a prefix and a local version in play.
+ (`#41 <https://github.com/pypa/packaging/issues/41>`__).
+
+* Implement environment markers from PEP 508.
+
+
+15.3 - 2015-08-01
+~~~~~~~~~~~~~~~~~
+
+* Normalize post-release spellings for rev/r prefixes. `#35 <https://github.com/pypa/packaging/issues/35>`__
+
+
+15.2 - 2015-05-13
+~~~~~~~~~~~~~~~~~
+
+* Fix an error where the arbitrary specifier (``===``) was not correctly
+ allowing pre-releases when it was being used.
+
+* Expose the specifier and version parts through properties on the
+ ``Specifier`` classes.
+
+* Allow iterating over the ``SpecifierSet`` to get access to all of the
+ ``Specifier`` instances.
+
+* Allow testing if a version is contained within a specifier via the ``in``
+ operator.
+
+
+15.1 - 2015-04-13
+~~~~~~~~~~~~~~~~~
+
+* Fix a logic error that was causing inconsistent answers about whether or not
+ a pre-release was contained within a ``SpecifierSet`` or not.
+
+
+15.0 - 2015-01-02
+~~~~~~~~~~~~~~~~~
+
+* Add ``Version().is_postrelease`` and ``LegacyVersion().is_postrelease`` to
+ make it easy to determine if a release is a post release.
+
+* Add ``Version().base_version`` and ``LegacyVersion().base_version`` to make
+ it easy to get the public version without any pre or post release markers.
+
+* Support the update to PEP 440 which removed the implied ``!=V.*`` when using
+ either ``>V`` or ``<V`` and which instead special cased the handling of
+ pre-releases, post-releases, and local versions when using ``>V`` or ``<V``.
+
+
+14.5 - 2014-12-17
+~~~~~~~~~~~~~~~~~
+
+* Normalize release candidates as ``rc`` instead of ``c``.
+
+* Expose the ``VERSION_PATTERN`` constant, a regular expression matching
+ a valid version.
+
+
+14.4 - 2014-12-15
+~~~~~~~~~~~~~~~~~
+
+* Ensure that versions are normalized before comparison when used in a
+ specifier with a less than (``<``) or greater than (``>``) operator.
+
+
+14.3 - 2014-11-19
+~~~~~~~~~~~~~~~~~
+
+* **BACKWARDS INCOMPATIBLE** Refactor specifier support so that it can sanely
+ handle legacy specifiers as well as PEP 440 specifiers.
+
+* **BACKWARDS INCOMPATIBLE** Move the specifier support out of
+ ``packaging.version`` into ``packaging.specifiers``.
+
+
+14.2 - 2014-09-10
+~~~~~~~~~~~~~~~~~
+
+* Add prerelease support to ``Specifier``.
+* Remove the ability to do ``item in Specifier()`` and replace it with
+ ``Specifier().contains(item)`` in order to allow flags that signal if a
+ prerelease should be accepted or not.
+* Add a method ``Specifier().filter()`` which will take an iterable and returns
+ an iterable with items that do not match the specifier filtered out.
+
+
+14.1 - 2014-09-08
+~~~~~~~~~~~~~~~~~
+
+* Allow ``LegacyVersion`` and ``Version`` to be sorted together.
+* Add ``packaging.version.parse()`` to enable easily parsing a version string
+ as either a ``Version`` or a ``LegacyVersion`` depending on it's PEP 440
+ validity.
+
+
+14.0 - 2014-09-05
+~~~~~~~~~~~~~~~~~
+
+* Initial release.
+
+
+.. _`master`: https://github.com/pypa/packaging/
+
+
diff --git a/debian/changelog b/debian/changelog
index 49b2eac..7e3ee24 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-packaging (21.0-1) unstable; urgency=medium
+
+ * New upstream version.
+ * New standards version.
+
+ -- Matthias Klose <doko@debian.org> Mon, 11 Oct 2021 14:28:40 +0200
+
python-packaging (20.9-2) unstable; urgency=medium
* Use sysconfig.get_platform instead of distutils.util.get_platform.
diff --git a/debian/control b/debian/control
index 313b6d6..e3c99d8 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 11),
python3-six, pypy-six,
python3-pytest,
python3-setuptools, pypy-setuptools,
-Standards-Version: 4.5.1
+Standards-Version: 4.6.0
Homepage: https://pypi.python.org/pypi/packaging
Package: python3-packaging
diff --git a/debian/patches/get_platform.diff b/debian/patches/get_platform.diff
deleted file mode 100644
index 9d22941..0000000
--- a/debian/patches/get_platform.diff
+++ /dev/null
@@ -1,181 +0,0 @@
---- a/packaging/tags.py
-+++ b/packaging/tags.py
-@@ -4,8 +4,6 @@
-
- from __future__ import absolute_import
-
--import distutils.util
--
- try:
- from importlib.machinery import EXTENSION_SUFFIXES
- except ImportError: # pragma: no cover
-@@ -781,7 +779,7 @@ def _manylinux_tags(linux, arch):
-
- def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
- # type: (bool) -> Iterator[str]
-- linux = _normalize_string(distutils.util.get_platform())
-+ linux = _normalize_string(sysconfig.get_platform())
- if is_32bit:
- if linux == "linux_x86_64":
- linux = "linux_i686"
-@@ -796,7 +794,7 @@ def _linux_platforms(is_32bit=_32_BIT_IN
-
- def _generic_platforms():
- # type: () -> Iterator[str]
-- yield _normalize_string(distutils.util.get_platform())
-+ yield _normalize_string(sysconfig.get_platform())
-
-
- def _platform_tags():
---- a/tests/test_tags.py
-+++ b/tests/test_tags.py
-@@ -11,7 +11,6 @@ try:
- import ctypes
- except ImportError:
- ctypes = None
--import distutils.util
- import os
- import platform
- import re
-@@ -492,14 +491,14 @@ class TestManylinuxPlatform:
- def test_linux_platforms_32_64bit_on_64bit_os(
- self, arch, is_32bit, expected, monkeypatch
- ):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: arch)
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: arch)
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
- linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[-1]
- assert linux_platform == expected
-
- def test_linux_platforms_manylinux_unsupported(self, monkeypatch):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
- linux_platform = list(tags._linux_platforms(is_32bit=False))
-@@ -509,7 +508,7 @@ class TestManylinuxPlatform:
- monkeypatch.setattr(
- tags, "_is_manylinux_compatible", lambda name, *args: name == "manylinux1"
- )
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(platform, "machine", lambda: "x86_64")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- platforms = list(tags._linux_platforms(is_32bit=False))
-@@ -517,7 +516,7 @@ class TestManylinuxPlatform:
- assert platforms == ["manylinux1_" + arch, "linux_" + arch]
-
- def test_linux_platforms_manylinux2010(self, monkeypatch):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(platform, "machine", lambda: "x86_64")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.12", raising=False)
- platforms = list(tags._linux_platforms(is_32bit=False))
-@@ -538,7 +537,7 @@ class TestManylinuxPlatform:
- assert platforms == expected
-
- def test_linux_platforms_manylinux2014(self, monkeypatch):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(platform, "machine", lambda: "x86_64")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.17", raising=False)
- platforms = list(tags._linux_platforms(is_32bit=False))
-@@ -571,7 +570,7 @@ class TestManylinuxPlatform:
- "_is_manylinux_compatible",
- lambda name, *args: name == "manylinux2014",
- )
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv7l")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv7l")
- monkeypatch.setattr(
- sys,
- "executable",
-@@ -583,7 +582,7 @@ class TestManylinuxPlatform:
-
- def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch):
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.17")
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(
- sys,
- "executable",
-@@ -615,7 +614,7 @@ class TestManylinuxPlatform:
- # test for a future glic 3.x version
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "3.2")
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, *args: True)
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_aarch64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_aarch64")
- monkeypatch.setattr(
- sys,
- "executable",
-@@ -633,7 +632,7 @@ class TestManylinuxPlatform:
- monkeypatch.setattr(
- tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
- )
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv6l")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- platforms = list(tags._linux_platforms(is_32bit=True))
- expected = ["linux_armv6l"]
-@@ -648,7 +647,7 @@ class TestManylinuxPlatform:
- ):
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: False)
- monkeypatch.setattr(
-- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
-+ sysconfig, "get_platform", lambda: "linux_{}".format(machine)
- )
- monkeypatch.setattr(
- sys,
-@@ -995,7 +994,7 @@ class TestGenericTags:
- assert not list(tags._generic_abi())
-
- def test_generic_platforms(self):
-- platform = distutils.util.get_platform().replace("-", "_")
-+ platform = sysconfig.get_platform().replace("-", "_")
- platform = platform.replace(".", "_")
- assert list(tags._generic_platforms()) == [platform]
-
-@@ -1269,14 +1268,14 @@ class TestSysTags:
- assert result[-1] == expected
-
- def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch, manylinux_module):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv6l")
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- platforms = list(tags._linux_platforms(is_32bit=True))
- expected = ["linux_armv6l"]
- assert platforms == expected
-
- def test_skip_manylinux_2014(self, monkeypatch, manylinux_module):
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_ppc64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_ppc64")
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 20))
- monkeypatch.setattr(
- manylinux_module, "manylinux2014_compatible", False, raising=False
-@@ -1300,7 +1299,7 @@ class TestSysTags:
- self, monkeypatch, manylinux_module, machine, abi, alt_machine
- ):
- monkeypatch.setattr(
-- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
-+ sysconfig, "get_platform", lambda: "linux_{}".format(machine)
- )
- monkeypatch.setattr(
- sys,
-@@ -1326,7 +1325,7 @@ class TestSysTags:
-
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (major, minor))
- monkeypatch.setattr(
-- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
-+ sysconfig, "get_platform", lambda: "linux_{}".format(machine)
- )
- monkeypatch.setattr(
- manylinux_module,
-@@ -1349,7 +1348,7 @@ class TestSysTags:
- return None
-
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 30))
-- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
-+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
- monkeypatch.setattr(
- manylinux_module,
- "manylinux_compatible",
diff --git a/debian/patches/series b/debian/patches/series
index 43ef63f..1bb8bf6 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1 @@
# empty
-get_platform.diff
diff --git a/docs/conf.py b/docs/conf.py
index cdd1ac1..edd8dd5 100644
--- a/docs/conf.py
+++ b/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
diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst
index c8e9487..8bd42ac 100644
--- a/docs/development/getting-started.rst
+++ b/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
diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst
index f634ac4..66f6c64 100644
--- a/docs/development/release-process.rst
+++ b/docs/development/release-process.rst
@@ -10,6 +10,10 @@ Release Process
$ nox -s release -- YY.N
+ You will need the password for your GPG key as well as an API token for PyPI.
+
+#. Add a `release on GitHub <https://github.com/pypa/packaging/releases>`__.
+
#. Notify the other project owners of the release.
.. note::
diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst
index cf433c4..fbdb5a4 100644
--- a/docs/development/submitting-patches.rst
+++ b/docs/development/submitting-patches.rst
@@ -32,13 +32,6 @@ Every code file must start with the boilerplate licensing notice:
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
-Additionally, every Python code file must contain
-
-.. code-block:: python
-
- from __future__ import absolute_import, division, print_function
-
-
Tests
-----
diff --git a/docs/specifiers.rst b/docs/specifiers.rst
index e1e458b..83299a8 100644
--- a/docs/specifiers.rst
+++ b/docs/specifiers.rst
@@ -48,7 +48,7 @@ Usage
Reference
---------
-.. class:: SpecifierSet(specifiers, prereleases=None)
+.. class:: SpecifierSet(specifiers="", prereleases=None)
This class abstracts handling specifying the dependencies of a project. It
can be passed a single specifier (``>=3.0``), a comma-separated list of
diff --git a/mypy.ini b/mypy.ini
index e197348..d88ab8f 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -2,9 +2,6 @@
ignore_missing_imports = True
# The following are the flags enabled by --strict
-#
-# Note: warn_unused_ignores is False due to incorrect typeshed annotations for
-# platform.mac_ver()
warn_unused_configs = True
disallow_subclassing_any = True
disallow_any_generics = True
@@ -15,6 +12,6 @@ check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
-warn_unused_ignores = False
+warn_unused_ignores = True
warn_return_any = True
no_implicit_reexport = True
diff --git a/packaging.egg-info/PKG-INFO b/packaging.egg-info/PKG-INFO
index 7865ede..017764a 100644
--- a/packaging.egg-info/PKG-INFO
+++ b/packaging.egg-info/PKG-INFO
@@ -1,416 +1,424 @@
Metadata-Version: 2.1
Name: packaging
-Version: 20.9
+Version: 21.0
Summary: Core utilities for Python packages
Home-page: https://github.com/pypa/packaging
Author: Donald Stufft and individual contributors
Author-email: donald@stufft.io
License: BSD-2-Clause or Apache-2.0
-Description: packaging
- =========
-
- .. start-intro
-
- Reusable core utilities for various Python Packaging
- `interoperability specifications <https://packaging.python.org/specifications/>`_.
-
- This library provides utilities that implement the interoperability
- specifications which have clearly one correct behaviour (eg: :pep:`440`)
- or benefit greatly from having a single shared implementation (eg: :pep:`425`).
-
- .. end-intro
-
- The ``packaging`` project includes the following: version handling, specifiers,
- markers, requirements, tags, utilities.
-
- Documentation
- -------------
-
- The `documentation`_ provides information and the API for the following:
-
- - Version Handling
- - Specifiers
- - Markers
- - Requirements
- - Tags
- - Utilities
-
- Installation
- ------------
-
- Use ``pip`` to install these utilities::
-
- pip install packaging
-
- Discussion
- ----------
-
- If you run into bugs, you can file them in our `issue tracker`_.
-
- You can also join ``#pypa`` on Freenode to ask questions or get involved.
-
-
- .. _`documentation`: https://packaging.pypa.io/
- .. _`issue tracker`: https://github.com/pypa/packaging/issues
-
-
- Code of Conduct
- ---------------
-
- Everyone interacting in the packaging project's codebases, issue trackers, chat
- rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
-
- .. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
-
- Contributing
- ------------
-
- The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
- well as how to report a potential security issue. The documentation for this
- project also covers information about `project development`_ and `security`_.
-
- .. _`project development`: https://packaging.pypa.io/en/latest/development/
- .. _`security`: https://packaging.pypa.io/en/latest/security/
-
- Project History
- ---------------
-
- Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
- recent changes and project history.
-
- .. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
-
- Changelog
- ---------
-
- 20.9 - 2021-01-29
- ~~~~~~~~~~~~~~~~~
-
- * Run [isort](https://pypi.org/project/isort/) over the code base (`#377 <https://github.com/pypa/packaging/issues/377>`__)
- * Add support for the ``macosx_10_*_universal2`` platform tags (`#379 <https://github.com/pypa/packaging/issues/379>`__)
- * Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
- (`#387 <https://github.com/pypa/packaging/issues/387>`__ and `#389 <https://github.com/pypa/packaging/issues/389>`__)
-
- 20.8 - 2020-12-11
- ~~~~~~~~~~~~~~~~~
-
- * Revert back to setuptools for compatibility purposes for some Linux distros (`#363 <https://github.com/pypa/packaging/issues/363>`__)
- * Do not insert an underscore in wheel tags when the interpreter version number
- is more than 2 digits (`#372 <https://github.com/pypa/packaging/issues/372>`__)
-
- 20.7 - 2020-11-28
- ~~~~~~~~~~~~~~~~~
-
- No unreleased changes.
-
- 20.6 - 2020-11-28
- ~~~~~~~~~~~~~~~~~
-
- .. note:: This release was subsequently yanked, and these changes were included in 20.7.
-
- * Fix flit configuration, to include LICENSE files (`#357 <https://github.com/pypa/packaging/issues/357>`__)
- * Make `intel` a recognized CPU architecture for the `universal` macOS platform tag (`#361 <https://github.com/pypa/packaging/issues/361>`__)
- * Add some missing type hints to `packaging.requirements` (issue:`350`)
-
- 20.5 - 2020-11-27
- ~~~~~~~~~~~~~~~~~
-
- * Officially support Python 3.9 (`#343 <https://github.com/pypa/packaging/issues/343>`__)
- * Deprecate the ``LegacyVersion`` and ``LegacySpecifier`` classes (`#321 <https://github.com/pypa/packaging/issues/321>`__)
- * Handle ``OSError`` on non-dynamic executables when attempting to resolve
- the glibc version string.
-
- 20.4 - 2020-05-19
- ~~~~~~~~~~~~~~~~~
-
- * Canonicalize version before comparing specifiers. (`#282 <https://github.com/pypa/packaging/issues/282>`__)
- * Change type hint for ``canonicalize_name`` to return
- ``packaging.utils.NormalizedName``.
- This enables the use of static typing tools (like mypy) to detect mixing of
- normalized and un-normalized names.
-
- 20.3 - 2020-03-05
- ~~~~~~~~~~~~~~~~~
-
- * Fix changelog for 20.2.
-
- 20.2 - 2020-03-05
- ~~~~~~~~~~~~~~~~~
-
- * Fix a bug that caused a 32-bit OS that runs on a 64-bit ARM CPU (e.g. ARM-v8,
- aarch64), to report the wrong bitness.
-
- 20.1 - 2020-01-24
- ~~~~~~~~~~~~~~~~~~~
-
- * Fix a bug caused by reuse of an exhausted iterator. (`#257 <https://github.com/pypa/packaging/issues/257>`__)
-
- 20.0 - 2020-01-06
- ~~~~~~~~~~~~~~~~~
-
- * Add type hints (`#191 <https://github.com/pypa/packaging/issues/191>`__)
-
- * Add proper trove classifiers for PyPy support (`#198 <https://github.com/pypa/packaging/issues/198>`__)
-
- * Scale back depending on ``ctypes`` for manylinux support detection (`#171 <https://github.com/pypa/packaging/issues/171>`__)
-
- * Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (`#193 <https://github.com/pypa/packaging/issues/193>`__)
-
- * Expand upon the API provded by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (`#187 <https://github.com/pypa/packaging/issues/187>`__)
-
- * Officially support Python 3.8 (`#232 <https://github.com/pypa/packaging/issues/232>`__)
-
- * Add ``major``, ``minor``, and ``micro`` aliases to ``packaging.version.Version`` (`#226 <https://github.com/pypa/packaging/issues/226>`__)
-
- * Properly mark ``packaging`` has being fully typed by adding a `py.typed` file (`#226 <https://github.com/pypa/packaging/issues/226>`__)
-
- 19.2 - 2019-09-18
- ~~~~~~~~~~~~~~~~~
-
- * Remove dependency on ``attrs`` (`#178 <https://github.com/pypa/packaging/issues/178>`__, `#179 <https://github.com/pypa/packaging/issues/179>`__)
-
- * Use appropriate fallbacks for CPython ABI tag (`#181 <https://github.com/pypa/packaging/issues/181>`__, `#185 <https://github.com/pypa/packaging/issues/185>`__)
-
- * Add manylinux2014 support (`#186 <https://github.com/pypa/packaging/issues/186>`__)
-
- * Improve ABI detection (`#181 <https://github.com/pypa/packaging/issues/181>`__)
-
- * Properly handle debug wheels for Python 3.8 (`#172 <https://github.com/pypa/packaging/issues/172>`__)
-
- * Improve detection of debug builds on Windows (`#194 <https://github.com/pypa/packaging/issues/194>`__)
-
- 19.1 - 2019-07-30
- ~~~~~~~~~~~~~~~~~
-
- * Add the ``packaging.tags`` module. (`#156 <https://github.com/pypa/packaging/issues/156>`__)
-
- * Correctly handle two-digit versions in ``python_version`` (`#119 <https://github.com/pypa/packaging/issues/119>`__)
-
-
- 19.0 - 2019-01-20
- ~~~~~~~~~~~~~~~~~
-
- * Fix string representation of PEP 508 direct URL requirements with markers.
-
- * Better handling of file URLs
-
- This allows for using ``file:///absolute/path``, which was previously
- prevented due to the missing ``netloc``.
-
- This allows for all file URLs that ``urlunparse`` turns back into the
- original URL to be valid.
-
-
- 18.0 - 2018-09-26
- ~~~~~~~~~~~~~~~~~
-
- * Improve error messages when invalid requirements are given. (`#129 <https://github.com/pypa/packaging/issues/129>`__)
-
-
- 17.1 - 2017-02-28
- ~~~~~~~~~~~~~~~~~
-
- * Fix ``utils.canonicalize_version`` when supplying non PEP 440 versions.
-
-
- 17.0 - 2017-02-28
- ~~~~~~~~~~~~~~~~~
-
- * Drop support for python 2.6, 3.2, and 3.3.
-
- * Define minimal pyparsing version to 2.0.2 (`#91 <https://github.com/pypa/packaging/issues/91>`__).
-
- * Add ``epoch``, ``release``, ``pre``, ``dev``, and ``post`` attributes to
- ``Version`` and ``LegacyVersion`` (`#34 <https://github.com/pypa/packaging/issues/34>`__).
-
- * Add ``Version().is_devrelease`` and ``LegacyVersion().is_devrelease`` to
- make it easy to determine if a release is a development release.
-
- * Add ``utils.canonicalize_version`` to canonicalize version strings or
- ``Version`` instances (`#121 <https://github.com/pypa/packaging/issues/121>`__).
-
-
- 16.8 - 2016-10-29
- ~~~~~~~~~~~~~~~~~
-
- * Fix markers that utilize ``in`` so that they render correctly.
-
- * Fix an erroneous test on Python RC releases.
-
-
- 16.7 - 2016-04-23
- ~~~~~~~~~~~~~~~~~
-
- * Add support for the deprecated ``python_implementation`` marker which was
- an undocumented setuptools marker in addition to the newer markers.
-
-
- 16.6 - 2016-03-29
- ~~~~~~~~~~~~~~~~~
-
- * Add support for the deprecated, PEP 345 environment markers in addition to
- the newer markers.
-
-
- 16.5 - 2016-02-26
- ~~~~~~~~~~~~~~~~~
-
- * Fix a regression in parsing requirements with whitespaces between the comma
- separators.
-
-
- 16.4 - 2016-02-22
- ~~~~~~~~~~~~~~~~~
-
- * Fix a regression in parsing requirements like ``foo (==4)``.
-
-
- 16.3 - 2016-02-21
- ~~~~~~~~~~~~~~~~~
-
- * Fix a bug where ``packaging.requirements:Requirement`` was overly strict when
- matching legacy requirements.
-
-
- 16.2 - 2016-02-09
- ~~~~~~~~~~~~~~~~~
-
- * Add a function that implements the name canonicalization from PEP 503.
-
-
- 16.1 - 2016-02-07
- ~~~~~~~~~~~~~~~~~
-
- * Implement requirement specifiers from PEP 508.
-
-
- 16.0 - 2016-01-19
- ~~~~~~~~~~~~~~~~~
-
- * Relicense so that packaging is available under *either* the Apache License,
- Version 2.0 or a 2 Clause BSD license.
-
- * Support installation of packaging when only distutils is available.
-
- * Fix ``==`` comparison when there is a prefix and a local version in play.
- (`#41 <https://github.com/pypa/packaging/issues/41>`__).
-
- * Implement environment markers from PEP 508.
-
-
- 15.3 - 2015-08-01
- ~~~~~~~~~~~~~~~~~
-
- * Normalize post-release spellings for rev/r prefixes. `#35 <https://github.com/pypa/packaging/issues/35>`__
-
-
- 15.2 - 2015-05-13
- ~~~~~~~~~~~~~~~~~
-
- * Fix an error where the arbitary specifier (``===``) was not correctly
- allowing pre-releases when it was being used.
-
- * Expose the specifier and version parts through properties on the
- ``Specifier`` classes.
-
- * Allow iterating over the ``SpecifierSet`` to get access to all of the
- ``Specifier`` instances.
-
- * Allow testing if a version is contained within a specifier via the ``in``
- operator.
-
-
- 15.1 - 2015-04-13
- ~~~~~~~~~~~~~~~~~
-
- * Fix a logic error that was causing inconsistent answers about whether or not
- a pre-release was contained within a ``SpecifierSet`` or not.
-
-
- 15.0 - 2015-01-02
- ~~~~~~~~~~~~~~~~~
-
- * Add ``Version().is_postrelease`` and ``LegacyVersion().is_postrelease`` to
- make it easy to determine if a release is a post release.
-
- * Add ``Version().base_version`` and ``LegacyVersion().base_version`` to make
- it easy to get the public version without any pre or post release markers.
-
- * Support the update to PEP 440 which removed the implied ``!=V.*`` when using
- either ``>V`` or ``<V`` and which instead special cased the handling of
- pre-releases, post-releases, and local versions when using ``>V`` or ``<V``.
-
-
- 14.5 - 2014-12-17
- ~~~~~~~~~~~~~~~~~
-
- * Normalize release candidates as ``rc`` instead of ``c``.
-
- * Expose the ``VERSION_PATTERN`` constant, a regular expression matching
- a valid version.
-
-
- 14.4 - 2014-12-15
- ~~~~~~~~~~~~~~~~~
-
- * Ensure that versions are normalized before comparison when used in a
- specifier with a less than (``<``) or greater than (``>``) operator.
-
-
- 14.3 - 2014-11-19
- ~~~~~~~~~~~~~~~~~
-
- * **BACKWARDS INCOMPATIBLE** Refactor specifier support so that it can sanely
- handle legacy specifiers as well as PEP 440 specifiers.
-
- * **BACKWARDS INCOMPATIBLE** Move the specifier support out of
- ``packaging.version`` into ``packaging.specifiers``.
-
-
- 14.2 - 2014-09-10
- ~~~~~~~~~~~~~~~~~
-
- * Add prerelease support to ``Specifier``.
- * Remove the ability to do ``item in Specifier()`` and replace it with
- ``Specifier().contains(item)`` in order to allow flags that signal if a
- prerelease should be accepted or not.
- * Add a method ``Specifier().filter()`` which will take an iterable and returns
- an iterable with items that do not match the specifier filtered out.
-
-
- 14.1 - 2014-09-08
- ~~~~~~~~~~~~~~~~~
-
- * Allow ``LegacyVersion`` and ``Version`` to be sorted together.
- * Add ``packaging.version.parse()`` to enable easily parsing a version string
- as either a ``Version`` or a ``LegacyVersion`` depending on it's PEP 440
- validity.
-
-
- 14.0 - 2014-09-05
- ~~~~~~~~~~~~~~~~~
-
- * Initial release.
-
-
- .. _`master`: https://github.com/pypa/packaging/
-
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=3.6
Description-Content-Type: text/x-rst
+License-File: LICENSE
+License-File: LICENSE.APACHE
+License-File: LICENSE.BSD
+
+packaging
+=========
+
+.. start-intro
+
+Reusable core utilities for various Python Packaging
+`interoperability specifications <https://packaging.python.org/specifications/>`_.
+
+This library provides utilities that implement the interoperability
+specifications which have clearly one correct behaviour (eg: :pep:`440`)
+or benefit greatly from having a single shared implementation (eg: :pep:`425`).
+
+.. end-intro
+
+The ``packaging`` project includes the following: version handling, specifiers,
+markers, requirements, tags, utilities.
+
+Documentation
+-------------
+
+The `documentation`_ provides information and the API for the following:
+
+- Version Handling
+- Specifiers
+- Markers
+- Requirements
+- Tags
+- Utilities
+
+Installation
+------------
+
+Use ``pip`` to install these utilities::
+
+ pip install packaging
+
+Discussion
+----------
+
+If you run into bugs, you can file them in our `issue tracker`_.
+
+You can also join ``#pypa`` on Freenode to ask questions or get involved.
+
+
+.. _`documentation`: https://packaging.pypa.io/
+.. _`issue tracker`: https://github.com/pypa/packaging/issues
+
+
+Code of Conduct
+---------------
+
+Everyone interacting in the packaging project's codebases, issue trackers, chat
+rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
+
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+
+Contributing
+------------
+
+The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
+well as how to report a potential security issue. The documentation for this
+project also covers information about `project development`_ and `security`_.
+
+.. _`project development`: https://packaging.pypa.io/en/latest/development/
+.. _`security`: https://packaging.pypa.io/en/latest/security/
+
+Project History
+---------------
+
+Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
+recent changes and project history.
+
+.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
+
+Changelog
+---------
+
+21.0 - 2021-07-03
+~~~~~~~~~~~~~~~~~
+
+* `packaging` is now only compatible with Python 3.6 and above.
+* Add support for zip files in ``parse_sdist_filename`` (`#429 <https://github.com/pypa/packaging/issues/429>`__)
+
+20.9 - 2021-01-29
+~~~~~~~~~~~~~~~~~
+
+* Run `isort <https://pypi.org/project/isort/>`_ over the code base (`#377 <https://github.com/pypa/packaging/issues/377>`__)
+* Add support for the ``macosx_10_*_universal2`` platform tags (`#379 <https://github.com/pypa/packaging/issues/379>`__)
+* Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
+ (`#387 <https://github.com/pypa/packaging/issues/387>`__ and `#389 <https://github.com/pypa/packaging/issues/389>`__)
+
+20.8 - 2020-12-11
+~~~~~~~~~~~~~~~~~
+
+* Revert back to setuptools for compatibility purposes for some Linux distros (`#363 <https://github.com/pypa/packaging/issues/363>`__)
+* Do not insert an underscore in wheel tags when the interpreter version number
+ is more than 2 digits (`#372 <https://github.com/pypa/packaging/issues/372>`__)
+
+20.7 - 2020-11-28
+~~~~~~~~~~~~~~~~~
+
+No unreleased changes.
+
+20.6 - 2020-11-28
+~~~~~~~~~~~~~~~~~
+
+.. note:: This release was subsequently yanked, and these changes were included in 20.7.
+
+* Fix flit configuration, to include LICENSE files (`#357 <https://github.com/pypa/packaging/issues/357>`__)
+* Make `intel` a recognized CPU architecture for the `universal` macOS platform tag (`#361 <https://github.com/pypa/packaging/issues/361>`__)
+* Add some missing type hints to `packaging.requirements` (issue:`350`)
+
+20.5 - 2020-11-27
+~~~~~~~~~~~~~~~~~
+
+* Officially support Python 3.9 (`#343 <https://github.com/pypa/packaging/issues/343>`__)
+* Deprecate the ``LegacyVersion`` and ``LegacySpecifier`` classes (`#321 <https://github.com/pypa/packaging/issues/321>`__)
+* Handle ``OSError`` on non-dynamic executables when attempting to resolve
+ the glibc version string.
+
+20.4 - 2020-05-19
+~~~~~~~~~~~~~~~~~
+
+* Canonicalize version before comparing specifiers. (`#282 <https://github.com/pypa/packaging/issues/282>`__)
+* Change type hint for ``canonicalize_name`` to return
+ ``packaging.utils.NormalizedName``.
+ This enables the use of static typing tools (like mypy) to detect mixing of
+ normalized and un-normalized names.
+
+20.3 - 2020-03-05
+~~~~~~~~~~~~~~~~~
+
+* Fix changelog for 20.2.
+
+20.2 - 2020-03-05
+~~~~~~~~~~~~~~~~~
+
+* Fix a bug that caused a 32-bit OS that runs on a 64-bit ARM CPU (e.g. ARM-v8,
+ aarch64), to report the wrong bitness.
+
+20.1 - 2020-01-24
+~~~~~~~~~~~~~~~~~~~
+
+* Fix a bug caused by reuse of an exhausted iterator. (`#257 <https://github.com/pypa/packaging/issues/257>`__)
+
+20.0 - 2020-01-06
+~~~~~~~~~~~~~~~~~
+
+* Add type hints (`#191 <https://github.com/pypa/packaging/issues/191>`__)
+
+* Add proper trove classifiers for PyPy support (`#198 <https://github.com/pypa/packaging/issues/198>`__)
+
+* Scale back depending on ``ctypes`` for manylinux support detection (`#171 <https://github.com/pypa/packaging/issues/171>`__)
+
+* Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (`#193 <https://github.com/pypa/packaging/issues/193>`__)
+
+* Expand upon the API provided by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (`#187 <https://github.com/pypa/packaging/issues/187>`__)
+
+* Officially support Python 3.8 (`#232 <https://github.com/pypa/packaging/issues/232>`__)
+
+* Add ``major``, ``minor``, and ``micro`` aliases to ``packaging.version.Version`` (`#226 <https://github.com/pypa/packaging/issues/226>`__)
+
+* Properly mark ``packaging`` has being fully typed by adding a `py.typed` file (`#226 <https://github.com/pypa/packaging/issues/226>`__)
+
+19.2 - 2019-09-18
+~~~~~~~~~~~~~~~~~
+
+* Remove dependency on ``attrs`` (`#178 <https://github.com/pypa/packaging/issues/178>`__, `#179 <https://github.com/pypa/packaging/issues/179>`__)
+
+* Use appropriate fallbacks for CPython ABI tag (`#181 <https://github.com/pypa/packaging/issues/181>`__, `#185 <https://github.com/pypa/packaging/issues/185>`__)
+
+* Add manylinux2014 support (`#186 <https://github.com/pypa/packaging/issues/186>`__)
+
+* Improve ABI detection (`#181 <https://github.com/pypa/packaging/issues/181>`__)
+
+* Properly handle debug wheels for Python 3.8 (`#172 <https://github.com/pypa/packaging/issues/172>`__)
+
+* Improve detection of debug builds on Windows (`#194 <https://github.com/pypa/packaging/issues/194>`__)
+
+19.1 - 2019-07-30
+~~~~~~~~~~~~~~~~~
+
+* Add the ``packaging.tags`` module. (`#156 <https://github.com/pypa/packaging/issues/156>`__)
+
+* Correctly handle two-digit versions in ``python_version`` (`#119 <https://github.com/pypa/packaging/issues/119>`__)
+
+
+19.0 - 2019-01-20
+~~~~~~~~~~~~~~~~~
+
+* Fix string representation of PEP 508 direct URL requirements with markers.
+
+* Better handling of file URLs
+
+ This allows for using ``file:///absolute/path``, which was previously
+ prevented due to the missing ``netloc``.
+
+ This allows for all file URLs that ``urlunparse`` turns back into the
+ original URL to be valid.
+
+
+18.0 - 2018-09-26
+~~~~~~~~~~~~~~~~~
+
+* Improve error messages when invalid requirements are given. (`#129 <https://github.com/pypa/packaging/issues/129>`__)
+
+
+17.1 - 2017-02-28
+~~~~~~~~~~~~~~~~~
+
+* Fix ``utils.canonicalize_version`` when supplying non PEP 440 versions.
+
+
+17.0 - 2017-02-28
+~~~~~~~~~~~~~~~~~
+
+* Drop support for python 2.6, 3.2, and 3.3.
+
+* Define minimal pyparsing version to 2.0.2 (`#91 <https://github.com/pypa/packaging/issues/91>`__).
+
+* Add ``epoch``, ``release``, ``pre``, ``dev``, and ``post`` attributes to
+ ``Version`` and ``LegacyVersion`` (`#34 <https://github.com/pypa/packaging/issues/34>`__).
+
+* Add ``Version().is_devrelease`` and ``LegacyVersion().is_devrelease`` to
+ make it easy to determine if a release is a development release.
+
+* Add ``utils.canonicalize_version`` to canonicalize version strings or
+ ``Version`` instances (`#121 <https://github.com/pypa/packaging/issues/121>`__).
+
+
+16.8 - 2016-10-29
+~~~~~~~~~~~~~~~~~
+
+* Fix markers that utilize ``in`` so that they render correctly.
+
+* Fix an erroneous test on Python RC releases.
+
+
+16.7 - 2016-04-23
+~~~~~~~~~~~~~~~~~
+
+* Add support for the deprecated ``python_implementation`` marker which was
+ an undocumented setuptools marker in addition to the newer markers.
+
+
+16.6 - 2016-03-29
+~~~~~~~~~~~~~~~~~
+
+* Add support for the deprecated, PEP 345 environment markers in addition to
+ the newer markers.
+
+
+16.5 - 2016-02-26
+~~~~~~~~~~~~~~~~~
+
+* Fix a regression in parsing requirements with whitespaces between the comma
+ separators.
+
+
+16.4 - 2016-02-22
+~~~~~~~~~~~~~~~~~
+
+* Fix a regression in parsing requirements like ``foo (==4)``.
+
+
+16.3 - 2016-02-21
+~~~~~~~~~~~~~~~~~
+
+* Fix a bug where ``packaging.requirements:Requirement`` was overly strict when
+ matching legacy requirements.
+
+
+16.2 - 2016-02-09
+~~~~~~~~~~~~~~~~~
+
+* Add a function that implements the name canonicalization from PEP 503.
+
+
+16.1 - 2016-02-07
+~~~~~~~~~~~~~~~~~
+
+* Implement requirement specifiers from PEP 508.
+
+
+16.0 - 2016-01-19
+~~~~~~~~~~~~~~~~~
+
+* Relicense so that packaging is available under *either* the Apache License,
+ Version 2.0 or a 2 Clause BSD license.
+
+* Support installation of packaging when only distutils is available.
+
+* Fix ``==`` comparison when there is a prefix and a local version in play.
+ (`#41 <https://github.com/pypa/packaging/issues/41>`__).
+
+* Implement environment markers from PEP 508.
+
+
+15.3 - 2015-08-01
+~~~~~~~~~~~~~~~~~
+
+* Normalize post-release spellings for rev/r prefixes. `#35 <https://github.com/pypa/packaging/issues/35>`__
+
+
+15.2 - 2015-05-13
+~~~~~~~~~~~~~~~~~
+
+* Fix an error where the arbitrary specifier (``===``) was not correctly
+ allowing pre-releases when it was being used.
+
+* Expose the specifier and version parts through properties on the
+ ``Specifier`` classes.
+
+* Allow iterating over the ``SpecifierSet`` to get access to all of the
+ ``Specifier`` instances.
+
+* Allow testing if a version is contained within a specifier via the ``in``
+ operator.
+
+
+15.1 - 2015-04-13
+~~~~~~~~~~~~~~~~~
+
+* Fix a logic error that was causing inconsistent answers about whether or not
+ a pre-release was contained within a ``SpecifierSet`` or not.
+
+
+15.0 - 2015-01-02
+~~~~~~~~~~~~~~~~~
+
+* Add ``Version().is_postrelease`` and ``LegacyVersion().is_postrelease`` to
+ make it easy to determine if a release is a post release.
+
+* Add ``Version().base_version`` and ``LegacyVersion().base_version`` to make
+ it easy to get the public version without any pre or post release markers.
+
+* Support the update to PEP 440 which removed the implied ``!=V.*`` when using
+ either ``>V`` or ``<V`` and which instead special cased the handling of
+ pre-releases, post-releases, and local versions when using ``>V`` or ``<V``.
+
+
+14.5 - 2014-12-17
+~~~~~~~~~~~~~~~~~
+
+* Normalize release candidates as ``rc`` instead of ``c``.
+
+* Expose the ``VERSION_PATTERN`` constant, a regular expression matching
+ a valid version.
+
+
+14.4 - 2014-12-15
+~~~~~~~~~~~~~~~~~
+
+* Ensure that versions are normalized before comparison when used in a
+ specifier with a less than (``<``) or greater than (``>``) operator.
+
+
+14.3 - 2014-11-19
+~~~~~~~~~~~~~~~~~
+
+* **BACKWARDS INCOMPATIBLE** Refactor specifier support so that it can sanely
+ handle legacy specifiers as well as PEP 440 specifiers.
+
+* **BACKWARDS INCOMPATIBLE** Move the specifier support out of
+ ``packaging.version`` into ``packaging.specifiers``.
+
+
+14.2 - 2014-09-10
+~~~~~~~~~~~~~~~~~
+
+* Add prerelease support to ``Specifier``.
+* Remove the ability to do ``item in Specifier()`` and replace it with
+ ``Specifier().contains(item)`` in order to allow flags that signal if a
+ prerelease should be accepted or not.
+* Add a method ``Specifier().filter()`` which will take an iterable and returns
+ an iterable with items that do not match the specifier filtered out.
+
+
+14.1 - 2014-09-08
+~~~~~~~~~~~~~~~~~
+
+* Allow ``LegacyVersion`` and ``Version`` to be sorted together.
+* Add ``packaging.version.parse()`` to enable easily parsing a version string
+ as either a ``Version`` or a ``LegacyVersion`` depending on it's PEP 440
+ validity.
+
+
+14.0 - 2014-09-05
+~~~~~~~~~~~~~~~~~
+
+* Initial release.
+
+
+.. _`master`: https://github.com/pypa/packaging/
+
+
diff --git a/packaging.egg-info/SOURCES.txt b/packaging.egg-info/SOURCES.txt
index 5fd1af0..645c647 100644
--- a/packaging.egg-info/SOURCES.txt
+++ b/packaging.egg-info/SOURCES.txt
@@ -32,9 +32,9 @@ docs/development/reviewing-patches.rst
docs/development/submitting-patches.rst
packaging/__about__.py
packaging/__init__.py
-packaging/_compat.py
+packaging/_manylinux.py
+packaging/_musllinux.py
packaging/_structures.py
-packaging/_typing.py
packaging/markers.py
packaging/py.typed
packaging/requirements.py
@@ -48,20 +48,26 @@ packaging.egg-info/dependency_links.txt
packaging.egg-info/requires.txt
packaging.egg-info/top_level.txt
tests/__init__.py
-tests/hello-world-armv7l-armel
-tests/hello-world-armv7l-armhf
-tests/hello-world-invalid-class
-tests/hello-world-invalid-data
-tests/hello-world-invalid-magic
-tests/hello-world-s390x-s390x
-tests/hello-world-too-short
-tests/hello-world-x86_64-amd64
-tests/hello-world-x86_64-i386
-tests/hello-world-x86_64-x32
+tests/test_manylinux.py
tests/test_markers.py
+tests/test_musllinux.py
tests/test_requirements.py
tests/test_specifiers.py
tests/test_structures.py
tests/test_tags.py
tests/test_utils.py
-tests/test_version.py \ No newline at end of file
+tests/test_version.py
+tests/manylinux/hello-world-armv7l-armel
+tests/manylinux/hello-world-armv7l-armhf
+tests/manylinux/hello-world-invalid-class
+tests/manylinux/hello-world-invalid-data
+tests/manylinux/hello-world-invalid-magic
+tests/manylinux/hello-world-s390x-s390x
+tests/manylinux/hello-world-too-short
+tests/manylinux/hello-world-x86_64-amd64
+tests/manylinux/hello-world-x86_64-i386
+tests/manylinux/hello-world-x86_64-x32
+tests/musllinux/glibc-x86_64
+tests/musllinux/musl-aarch64
+tests/musllinux/musl-i386
+tests/musllinux/musl-x86_64 \ No newline at end of file
diff --git a/packaging/__about__.py b/packaging/__about__.py
index 4c43a96..e70d692 100644
--- a/packaging/__about__.py
+++ b/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__",
@@ -18,7 +17,7 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
-__version__ = "20.9"
+__version__ = "21.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
diff --git a/packaging/__init__.py b/packaging/__init__.py
index a0cf67d..3c50c5d 100644
--- a/packaging/__init__.py
+++ b/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__,
diff --git a/packaging/_compat.py b/packaging/_compat.py
deleted file mode 100644
index e54bd4e..0000000
--- a/packaging/_compat.py
+++ /dev/null
@@ -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", (), {})
diff --git a/packaging/_manylinux.py b/packaging/_manylinux.py
new file mode 100644
index 0000000..4c379aa
--- /dev/null
+++ b/packaging/_manylinux.py
@@ -0,0 +1,301 @@
+import collections
+import functools
+import os
+import re
+import struct
+import sys
+import warnings
+from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple
+
+
+# Python does not provide platform information at sufficient granularity to
+# 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:
+ # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
+ class _InvalidELFFileHeader(ValueError):
+ """
+ An invalid ELF file header was found.
+ """
+
+ ELF_MAGIC_NUMBER = 0x7F454C46
+ ELFCLASS32 = 1
+ ELFCLASS64 = 2
+ ELFDATA2LSB = 1
+ ELFDATA2MSB = 2
+ EM_386 = 3
+ EM_S390 = 22
+ EM_ARM = 40
+ EM_X86_64 = 62
+ EF_ARM_ABIMASK = 0xFF000000
+ EF_ARM_ABI_VER5 = 0x05000000
+ EF_ARM_ABI_FLOAT_HARD = 0x00000400
+
+ def __init__(self, file: IO[bytes]) -> None:
+ def unpack(fmt: str) -> int:
+ try:
+ data = file.read(struct.calcsize(fmt))
+ result: Tuple[int, ...] = struct.unpack(fmt, data)
+ except struct.error:
+ raise _ELFFileHeader._InvalidELFFileHeader()
+ return result[0]
+
+ self.e_ident_magic = unpack(">I")
+ if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
+ raise _ELFFileHeader._InvalidELFFileHeader()
+ self.e_ident_class = unpack("B")
+ if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
+ raise _ELFFileHeader._InvalidELFFileHeader()
+ self.e_ident_data = unpack("B")
+ if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
+ raise _ELFFileHeader._InvalidELFFileHeader()
+ self.e_ident_version = unpack("B")
+ self.e_ident_osabi = unpack("B")
+ self.e_ident_abiversion = unpack("B")
+ self.e_ident_pad = file.read(7)
+ format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
+ format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
+ format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
+ format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
+ self.e_type = unpack(format_h)
+ self.e_machine = unpack(format_h)
+ self.e_version = unpack(format_i)
+ self.e_entry = unpack(format_p)
+ self.e_phoff = unpack(format_p)
+ self.e_shoff = unpack(format_p)
+ self.e_flags = unpack(format_i)
+ self.e_ehsize = unpack(format_h)
+ self.e_phentsize = unpack(format_h)
+ self.e_phnum = unpack(format_h)
+ self.e_shentsize = unpack(format_h)
+ self.e_shnum = unpack(format_h)
+ self.e_shstrndx = unpack(format_h)
+
+
+def _get_elf_header() -> Optional[_ELFFileHeader]:
+ try:
+ with open(sys.executable, "rb") as f:
+ elf_header = _ELFFileHeader(f)
+ except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
+ return None
+ return elf_header
+
+
+def _is_linux_armhf() -> bool:
+ # hard-float ABI can be detected from the ELF header of the running
+ # process
+ # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
+ elf_header = _get_elf_header()
+ if elf_header is None:
+ return False
+ result = elf_header.e_ident_class == elf_header.ELFCLASS32
+ result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
+ result &= elf_header.e_machine == elf_header.EM_ARM
+ result &= (
+ elf_header.e_flags & elf_header.EF_ARM_ABIMASK
+ ) == elf_header.EF_ARM_ABI_VER5
+ result &= (
+ elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD
+ ) == elf_header.EF_ARM_ABI_FLOAT_HARD
+ return result
+
+
+def _is_linux_i686() -> bool:
+ elf_header = _get_elf_header()
+ if elf_header is None:
+ return False
+ result = elf_header.e_ident_class == elf_header.ELFCLASS32
+ result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
+ result &= elf_header.e_machine == elf_header.EM_386
+ return result
+
+
+def _have_compatible_abi(arch: str) -> bool:
+ if arch == "armv7l":
+ return _is_linux_armhf()
+ if arch == "i686":
+ return _is_linux_i686()
+ return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
+
+
+# If glibc ever changes its major version, we need to know what the last
+# minor version was, so we can build the complete list of all versions.
+# For now, guess what the highest minor version might be, assume it will
+# be 50 for testing. Once this actually happens, update the dictionary
+# with the actual value.
+_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50)
+
+
+class _GLibCVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _glibc_version_string_confstr() -> Optional[str]:
+ """
+ Primary implementation of glibc_version_string using os.confstr.
+ """
+ # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
+ # to be broken or missing. This strategy is used in the standard library
+ # platform module.
+ # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
+ try:
+ # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
+ version_string = os.confstr("CS_GNU_LIBC_VERSION")
+ assert version_string is not None
+ _, version = version_string.split()
+ except (AssertionError, AttributeError, OSError, ValueError):
+ # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
+ return None
+ return version
+
+
+def _glibc_version_string_ctypes() -> Optional[str]:
+ """
+ Fallback implementation of glibc_version_string using ctypes.
+ """
+ try:
+ import ctypes
+ except ImportError:
+ return None
+
+ # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
+ # manpage says, "If filename is NULL, then the returned handle is for the
+ # main program". This way we can let the linker do the work to figure out
+ # which libc our process is actually using.
+ #
+ # We must also handle the special case where the executable is not a
+ # dynamically linked executable. This can occur when using musl libc,
+ # for example. In this situation, dlopen() will error, leading to an
+ # OSError. Interestingly, at least in the case of musl, there is no
+ # errno set on the OSError. The single string argument used to construct
+ # OSError comes from libc itself and is therefore not portable to
+ # hard code here. In any case, failure to call dlopen() means we
+ # can proceed, so we bail on our attempt.
+ try:
+ process_namespace = ctypes.CDLL(None)
+ except OSError:
+ return None
+
+ try:
+ gnu_get_libc_version = process_namespace.gnu_get_libc_version
+ except AttributeError:
+ # Symbol doesn't exist -> therefore, we are not linked to
+ # glibc.
+ return None
+
+ # Call gnu_get_libc_version, which returns a string like "2.5"
+ gnu_get_libc_version.restype = ctypes.c_char_p
+ version_str: str = gnu_get_libc_version()
+ # py2 / py3 compatibility:
+ if not isinstance(version_str, str):
+ version_str = version_str.decode("ascii")
+
+ return version_str
+
+
+def _glibc_version_string() -> Optional[str]:
+ """Returns glibc version string, or None if not using glibc."""
+ return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
+
+
+def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
+ """Parse glibc version.
+
+ We use a regexp instead of str.split because we want to discard any
+ random junk that might come after the minor version -- this might happen
+ in patched/forked versions of glibc (e.g. Linaro's version of glibc
+ uses version strings like "2.20-2014.11"). See gh-3588.
+ """
+ m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
+ if not m:
+ warnings.warn(
+ "Expected glibc version with 2 components major.minor,"
+ " got: %s" % version_str,
+ RuntimeWarning,
+ )
+ return -1, -1
+ return int(m.group("major")), int(m.group("minor"))
+
+
+@functools.lru_cache()
+def _get_glibc_version() -> Tuple[int, int]:
+ version_str = _glibc_version_string()
+ if version_str is None:
+ return (-1, -1)
+ return _parse_glibc_version(version_str)
+
+
+# From PEP 513, PEP 600
+def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool:
+ sys_glibc = _get_glibc_version()
+ if sys_glibc < version:
+ return False
+ # Check for presence of _manylinux module.
+ try:
+ import _manylinux # noqa
+ except ImportError:
+ return True
+ if hasattr(_manylinux, "manylinux_compatible"):
+ result = _manylinux.manylinux_compatible(version[0], version[1], arch)
+ if result is not None:
+ return bool(result)
+ return True
+ if version == _GLibCVersion(2, 5):
+ if hasattr(_manylinux, "manylinux1_compatible"):
+ return bool(_manylinux.manylinux1_compatible)
+ if version == _GLibCVersion(2, 12):
+ if hasattr(_manylinux, "manylinux2010_compatible"):
+ return bool(_manylinux.manylinux2010_compatible)
+ if version == _GLibCVersion(2, 17):
+ if hasattr(_manylinux, "manylinux2014_compatible"):
+ return bool(_manylinux.manylinux2014_compatible)
+ return True
+
+
+_LEGACY_MANYLINUX_MAP = {
+ # CentOS 7 w/ glibc 2.17 (PEP 599)
+ (2, 17): "manylinux2014",
+ # CentOS 6 w/ glibc 2.12 (PEP 571)
+ (2, 12): "manylinux2010",
+ # CentOS 5 w/ glibc 2.5 (PEP 513)
+ (2, 5): "manylinux1",
+}
+
+
+def platform_tags(linux: str, arch: str) -> Iterator[str]:
+ if not _have_compatible_abi(arch):
+ return
+ # Oldest glibc to be supported regardless of architecture is (2, 17).
+ too_old_glibc2 = _GLibCVersion(2, 16)
+ if arch in {"x86_64", "i686"}:
+ # On x86/i686 also oldest glibc to be supported is (2, 5).
+ too_old_glibc2 = _GLibCVersion(2, 4)
+ current_glibc = _GLibCVersion(*_get_glibc_version())
+ glibc_max_list = [current_glibc]
+ # We can assume compatibility across glibc major versions.
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
+ #
+ # Build a list of maximum glibc versions so that we can
+ # output the canonical list of all glibc from current_glibc
+ # down to too_old_glibc2, including all intermediary versions.
+ for glibc_major in range(current_glibc.major - 1, 1, -1):
+ glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
+ glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
+ for glibc_max in glibc_max_list:
+ if glibc_max.major == too_old_glibc2.major:
+ min_minor = too_old_glibc2.minor
+ else:
+ # For other glibc major versions oldest supported is (x, 0).
+ min_minor = -1
+ for glibc_minor in range(glibc_max.minor, min_minor, -1):
+ glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
+ tag = "manylinux_{}_{}".format(*glibc_version)
+ if _is_compatible(tag, arch, glibc_version):
+ yield linux.replace("linux", tag)
+ # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
+ if glibc_version in _LEGACY_MANYLINUX_MAP:
+ legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
+ if _is_compatible(legacy_tag, arch, glibc_version):
+ yield linux.replace("linux", legacy_tag)
diff --git a/packaging/_musllinux.py b/packaging/_musllinux.py
new file mode 100644
index 0000000..85450fa
--- /dev/null
+++ b/packaging/_musllinux.py
@@ -0,0 +1,136 @@
+"""PEP 656 support.
+
+This module implements logic to detect if the currently running Python is
+linked against musl, and what musl version is used.
+"""
+
+import contextlib
+import functools
+import operator
+import os
+import re
+import struct
+import subprocess
+import sys
+from typing import IO, Iterator, NamedTuple, Optional, Tuple
+
+
+def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]:
+ return struct.unpack(fmt, f.read(struct.calcsize(fmt)))
+
+
+def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
+ """Detect musl libc location by parsing the Python executable.
+
+ Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
+ ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
+ """
+ f.seek(0)
+ try:
+ ident = _read_unpacked(f, "16B")
+ except struct.error:
+ return None
+ if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF.
+ return None
+ f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version.
+
+ try:
+ # e_fmt: Format for program header.
+ # p_fmt: Format for section header.
+ # p_idx: Indexes to find p_type, p_offset, and p_filesz.
+ e_fmt, p_fmt, p_idx = {
+ 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit.
+ 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit.
+ }[ident[4]]
+ except KeyError:
+ return None
+ else:
+ p_get = operator.itemgetter(*p_idx)
+
+ # Find the interpreter section and return its content.
+ try:
+ _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt)
+ except struct.error:
+ return None
+ for i in range(e_phnum + 1):
+ f.seek(e_phoff + e_phentsize * i)
+ try:
+ p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt))
+ except struct.error:
+ return None
+ if p_type != 3: # Not PT_INTERP.
+ continue
+ f.seek(p_offset)
+ interpreter = os.fsdecode(f.read(p_filesz)).strip("\0")
+ if "musl" not in interpreter:
+ return None
+ return interpreter
+ return None
+
+
+class _MuslVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
+ lines = [n for n in (n.strip() for n in output.splitlines()) if n]
+ if len(lines) < 2 or lines[0][:4] != "musl":
+ return None
+ m = re.match(r"Version (\d+)\.(\d+)", lines[1])
+ if not m:
+ return None
+ return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
+
+
+@functools.lru_cache()
+def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
+ """Detect currently-running musl runtime version.
+
+ This is done by checking the specified executable's dynamic linking
+ information, and invoking the loader to parse its output for a version
+ string. If the loader is musl, the output would be something like::
+
+ musl libc (x86_64)
+ Version 1.2.2
+ Dynamic Program Loader
+ """
+ with contextlib.ExitStack() as stack:
+ try:
+ f = stack.enter_context(open(executable, "rb"))
+ except IOError:
+ return None
+ ld = _parse_ld_musl_from_elf(f)
+ if not ld:
+ return None
+ proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
+ return _parse_musl_version(proc.stderr)
+
+
+def platform_tags(arch: str) -> Iterator[str]:
+ """Generate musllinux tags compatible to the current platform.
+
+ :param arch: Should be the part of platform tag after the ``linux_``
+ prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a
+ prerequisite for the current platform to be musllinux-compatible.
+
+ :returns: An iterator of compatible musllinux tags.
+ """
+ sys_musl = _get_musl_version(sys.executable)
+ if sys_musl is None: # Python not dynamically linked against musl.
+ return
+ for minor in range(sys_musl.minor, -1, -1):
+ yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
+
+
+if __name__ == "__main__": # pragma: no cover
+ import sysconfig
+
+ plat = sysconfig.get_platform()
+ assert plat.startswith("linux-"), "not linux"
+
+ print("plat:", plat)
+ print("musl:", _get_musl_version(sys.executable))
+ print("tags:", end=" ")
+ for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
+ print(t, end="\n ")
diff --git a/packaging/_structures.py b/packaging/_structures.py
index 800d5c5..9515497 100644
--- a/packaging/_structures.py
+++ b/packaging/_structures.py
@@ -1,85 +1,66 @@
# 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):
- def __repr__(self):
- # type: () -> str
+class InfinityType:
+ def __repr__(self) -> str:
return "Infinity"
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(repr(self))
- def __lt__(self, other):
- # type: (object) -> bool
+ def __lt__(self, other: object) -> bool:
return False
- def __le__(self, other):
- # type: (object) -> bool
+ def __le__(self, other: object) -> bool:
return False
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
- def __ne__(self, other):
- # type: (object) -> bool
+ def __ne__(self, other: object) -> bool:
return not isinstance(other, self.__class__)
- def __gt__(self, other):
- # type: (object) -> bool
+ def __gt__(self, other: object) -> bool:
return True
- def __ge__(self, other):
- # type: (object) -> bool
+ def __ge__(self, other: object) -> bool:
return True
- def __neg__(self):
- # type: (object) -> NegativeInfinityType
+ def __neg__(self: object) -> "NegativeInfinityType":
return NegativeInfinity
Infinity = InfinityType()
-class NegativeInfinityType(object):
- def __repr__(self):
- # type: () -> str
+class NegativeInfinityType:
+ def __repr__(self) -> str:
return "-Infinity"
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(repr(self))
- def __lt__(self, other):
- # type: (object) -> bool
+ def __lt__(self, other: object) -> bool:
return True
- def __le__(self, other):
- # type: (object) -> bool
+ def __le__(self, other: object) -> bool:
return True
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
- def __ne__(self, other):
- # type: (object) -> bool
+ def __ne__(self, other: object) -> bool:
return not isinstance(other, self.__class__)
- def __gt__(self, other):
- # type: (object) -> bool
+ def __gt__(self, other: object) -> bool:
return False
- def __ge__(self, other):
- # type: (object) -> bool
+ def __ge__(self, other: object) -> bool:
return False
- def __neg__(self):
- # type: (object) -> InfinityType
+ def __neg__(self: object) -> InfinityType:
return Infinity
diff --git a/packaging/_typing.py b/packaging/_typing.py
deleted file mode 100644
index 77a8b91..0000000
--- a/packaging/_typing.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""For neatly implementing static typing in packaging.
-
-`mypy` - the static type analysis tool we use - uses the `typing` module, which
-provides core functionality fundamental to mypy's functioning.
-
-Generally, `typing` would be imported at runtime and used in that fashion -
-it acts as a no-op at runtime and does not have any run-time overhead by
-design.
-
-As it turns out, `typing` is not vendorable - it uses separate sources for
-Python 2/Python 3. Thus, this codebase can not expect it to be present.
-To work around this, mypy allows the typing import to be behind a False-y
-optional to prevent it from running at runtime and type-comments can be used
-to remove the need for the types to be accessible directly during runtime.
-
-This module provides the False-y guard in a nicely named fashion so that a
-curious maintainer can reach here to read this.
-
-In packaging, all static-typing related imports should be guarded as follows:
-
- from packaging._typing import TYPE_CHECKING
-
- if TYPE_CHECKING:
- from typing import ...
-
-Ref: https://github.com/python/mypy/issues/3216
-"""
-
-__all__ = ["TYPE_CHECKING", "cast"]
-
-# The TYPE_CHECKING constant defined by the typing module is False at runtime
-# but True while type checking.
-if False: # pragma: no cover
- from typing import TYPE_CHECKING
-else:
- TYPE_CHECKING = False
-
-# typing's cast syntax requires calling typing.cast at runtime, but we don't
-# want to import typing at runtime. Here, we inform the type checkers that
-# we're importing `typing.cast` as `cast` and re-implement typing.cast's
-# runtime behavior in a block that is ignored by type checkers.
-if TYPE_CHECKING: # pragma: no cover
- # not executed at runtime
- from typing import cast
-else:
- # executed at runtime
- def cast(type_, value): # noqa
- return value
diff --git a/packaging/markers.py b/packaging/markers.py
index e0330ab..cb640e8 100644
--- a/packaging/markers.py
+++ b/packaging/markers.py
@@ -1,12 +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 operator
import os
import platform
import sys
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from pyparsing import ( # noqa: N817
Forward,
@@ -20,16 +20,8 @@ from pyparsing import ( # noqa: N817
stringStart,
)
-from ._compat import string_types
-from ._typing import TYPE_CHECKING
from .specifiers import InvalidSpecifier, Specifier
-if TYPE_CHECKING: # pragma: no cover
- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
-
- Operator = Callable[[str, str], bool]
-
-
__all__ = [
"InvalidMarker",
"UndefinedComparison",
@@ -38,6 +30,8 @@ __all__ = [
"default_environment",
]
+Operator = Callable[[str, str], bool]
+
class InvalidMarker(ValueError):
"""
@@ -58,39 +52,32 @@ class UndefinedEnvironmentName(ValueError):
"""
-class Node(object):
- def __init__(self, value):
- # type: (Any) -> None
+class Node:
+ def __init__(self, value: Any) -> None:
self.value = value
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
return str(self.value)
- def __repr__(self):
- # type: () -> str
- return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__}('{self}')>"
- def serialize(self):
- # type: () -> str
+ def serialize(self) -> str:
raise NotImplementedError
class Variable(Node):
- def serialize(self):
- # type: () -> str
+ def serialize(self) -> str:
return str(self)
class Value(Node):
- def serialize(self):
- # type: () -> str
- return '"{0}"'.format(self)
+ def serialize(self) -> str:
+ return f'"{self}"'
class Op(Node):
- def serialize(self):
- # type: () -> str
+ def serialize(self) -> str:
return str(self)
@@ -151,18 +138,18 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
MARKER = stringStart + MARKER_EXPR + stringEnd
-def _coerce_parse_result(results):
- # type: (Union[ParseResults, List[Any]]) -> List[Any]
+def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]:
if isinstance(results, ParseResults):
return [_coerce_parse_result(i) for i in results]
else:
return results
-def _format_marker(marker, first=True):
- # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str
+def _format_marker(
+ marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True
+) -> 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
@@ -187,7 +174,7 @@ def _format_marker(marker, first=True):
return marker
-_operators = {
+_operators: Dict[str, Operator] = {
"in": lambda lhs, rhs: lhs in rhs,
"not in": lambda lhs, rhs: lhs not in rhs,
"<": operator.lt,
@@ -196,11 +183,10 @@ _operators = {
"!=": operator.ne,
">=": operator.ge,
">": operator.gt,
-} # type: Dict[str, Operator]
+}
-def _eval_op(lhs, op, rhs):
- # type: (str, Op, str) -> bool
+def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
try:
spec = Specifier("".join([op.serialize(), rhs]))
except InvalidSpecifier:
@@ -208,40 +194,36 @@ def _eval_op(lhs, op, rhs):
else:
return spec.contains(lhs)
- oper = _operators.get(op.serialize()) # type: Optional[Operator]
+ oper: Optional[Operator] = _operators.get(op.serialize())
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
_undefined = Undefined()
-def _get_env(environment, name):
- # type: (Dict[str, str], str) -> str
- value = environment.get(name, _undefined) # type: Union[str, Undefined]
+def _get_env(environment: Dict[str, str], name: str) -> str:
+ value: Union[str, Undefined] = environment.get(name, _undefined)
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
-def _evaluate_markers(markers, environment):
- # type: (List[Any], Dict[str, str]) -> bool
- groups = [[]] # type: List[List[bool]]
+def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
+ groups: 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))
@@ -264,8 +246,7 @@ def _evaluate_markers(markers, environment):
return any(all(item) for item in groups)
-def format_full_version(info):
- # type: (sys._version_info) -> str
+def format_full_version(info: "sys._version_info") -> str:
version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != "final":
@@ -273,18 +254,9 @@ def format_full_version(info):
return version
-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 = ""
-
+def default_environment() -> Dict[str, str]:
+ iver = format_full_version(sys.implementation.version)
+ implementation_name = sys.implementation.name
return {
"implementation_name": implementation_name,
"implementation_version": iver,
@@ -300,27 +272,23 @@ def default_environment():
}
-class Marker(object):
- def __init__(self, marker):
- # type: (str) -> None
+class Marker:
+ def __init__(self, marker: 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
+ def __str__(self) -> str:
return _format_marker(self._markers)
- def __repr__(self):
- # type: () -> str
- return "<Marker({0!r})>".format(str(self))
+ def __repr__(self) -> str:
+ return f"<Marker('{self}')>"
- def evaluate(self, environment=None):
- # type: (Optional[Dict[str, str]]) -> bool
+ def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
"""Evaluate a marker.
Return the boolean from evaluating the given marker against the
diff --git a/packaging/requirements.py b/packaging/requirements.py
index aa69d50..53f9a3a 100644
--- a/packaging/requirements.py
+++ b/packaging/requirements.py
@@ -1,13 +1,13 @@
# 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 typing import List, Optional as TOptional, Set
-from pyparsing import ( # noqa: N817
+from pyparsing import ( # noqa
Combine,
Literal as L,
Optional,
@@ -20,19 +20,9 @@ from pyparsing import ( # noqa: N817
stringStart,
)
-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
-
class InvalidRequirement(ValueError):
"""
@@ -70,7 +60,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 +84,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,
@@ -107,54 +97,50 @@ class Requirement(object):
# the thing as well as the version? What about the markers?
# TODO: Can we normalize the name and extra name?
- def __init__(self, requirement_string):
- # type: (str) -> None
+ def __init__(self, requirement_string: str) -> None:
try:
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
+ self.name: str = req.name
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))
- self.url = req.url # type: TOptional[str]
+ raise InvalidRequirement(f"Invalid URL: {req.url}")
+ self.url: TOptional[str] = req.url
else:
self.url = None
- self.extras = set(req.extras.asList() if req.extras else []) # type: Set[str]
- self.specifier = SpecifierSet(req.specifier) # type: SpecifierSet
- self.marker = req.marker if req.marker else None # type: TOptional[Marker]
+ self.extras: Set[str] = set(req.extras.asList() if req.extras else [])
+ self.specifier: SpecifierSet = SpecifierSet(req.specifier)
+ self.marker: TOptional[Marker] = req.marker if req.marker else None
- def __str__(self):
- # type: () -> str
- parts = [self.name] # type: List[str]
+ def __str__(self) -> str:
+ parts: List[str] = [self.name]
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))
+ def __repr__(self) -> str:
+ return f"<Requirement('{self}')>"
diff --git a/packaging/specifiers.py b/packaging/specifiers.py
index a6a83c1..ce66bd4 100644
--- a/packaging/specifiers.py
+++ b/packaging/specifiers.py
@@ -1,25 +1,33 @@
# 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
import itertools
import re
import warnings
+from typing import (
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Pattern,
+ Set,
+ Tuple,
+ TypeVar,
+ Union,
+)
-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
-
- ParsedVersion = Union[Version, LegacyVersion]
- UnparsedVersion = Union[Version, LegacyVersion, str]
- CallableOperator = Callable[[ParsedVersion, str], bool]
+ParsedVersion = Union[Version, LegacyVersion]
+UnparsedVersion = Union[Version, LegacyVersion, str]
+VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion)
+CallableOperator = Callable[[ParsedVersion, str], bool]
class InvalidSpecifier(ValueError):
@@ -28,64 +36,58 @@ class InvalidSpecifier(ValueError):
"""
-class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
+class BaseSpecifier(metaclass=abc.ABCMeta):
@abc.abstractmethod
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
"""
Returns the str representation of this Specifier like object. This
should be representative of the Specifier itself.
"""
@abc.abstractmethod
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
"""
Returns a hash value for this Specifier like object.
"""
@abc.abstractmethod
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
"""
Returns a boolean representing whether or not the two Specifier like
objects are equal.
"""
@abc.abstractmethod
- def __ne__(self, other):
- # type: (object) -> bool
+ def __ne__(self, other: object) -> bool:
"""
Returns a boolean representing whether or not the two Specifier like
objects are not equal.
"""
@abc.abstractproperty
- def prereleases(self):
- # type: () -> Optional[bool]
+ def prereleases(self) -> Optional[bool]:
"""
Returns whether or not pre-releases as a whole are allowed by this
specifier.
"""
@prereleases.setter
- def prereleases(self, value):
- # type: (bool) -> None
+ def prereleases(self, value: bool) -> None:
"""
Sets whether or not pre-releases as a whole are allowed by this
specifier.
"""
@abc.abstractmethod
- def contains(self, item, prereleases=None):
- # type: (str, Optional[bool]) -> bool
+ def contains(self, item: str, prereleases: Optional[bool] = None) -> bool:
"""
Determines if the given item is contained within this specifier.
"""
@abc.abstractmethod
- def filter(self, iterable, prereleases=None):
- # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
+ def filter(
+ self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
+ ) -> Iterable[VersionTypeVar]:
"""
Takes an iterable of items and filters them so that only items which
are contained within this specifier are allowed in it.
@@ -94,48 +96,43 @@ class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
class _IndividualSpecifier(BaseSpecifier):
- _operators = {} # type: Dict[str, str]
+ _operators: Dict[str, str] = {}
+ _regex: Pattern[str]
- def __init__(self, spec="", prereleases=None):
- # type: (str, Optional[bool]) -> None
+ def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
match = self._regex.search(spec)
if not match:
- raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
+ raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
- self._spec = (
+ self._spec: Tuple[str, str] = (
match.group("operator").strip(),
match.group("version").strip(),
- ) # type: Tuple[str, str]
+ )
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
- def __repr__(self):
- # type: () -> str
+ def __repr__(self) -> 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)
+ def __str__(self) -> str:
+ return "{}{}".format(*self._spec)
@property
- def _canonical_spec(self):
- # type: () -> Tuple[str, Union[Version, str]]
+ def _canonical_spec(self) -> Tuple[str, str]:
return self._spec[0], canonicalize_version(self._spec[1])
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(self._canonical_spec)
- def __eq__(self, other):
- # type: (object) -> bool
- if isinstance(other, string_types):
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, str):
try:
other = self.__class__(str(other))
except InvalidSpecifier:
@@ -145,9 +142,8 @@ class _IndividualSpecifier(BaseSpecifier):
return self._canonical_spec == other._canonical_spec
- def __ne__(self, other):
- # type: (object) -> bool
- if isinstance(other, string_types):
+ def __ne__(self, other: object) -> bool:
+ if isinstance(other, str):
try:
other = self.__class__(str(other))
except InvalidSpecifier:
@@ -157,45 +153,39 @@ class _IndividualSpecifier(BaseSpecifier):
return self._spec != other._spec
- def _get_operator(self, op):
- # type: (str) -> CallableOperator
- operator_callable = getattr(
- self, "_compare_{0}".format(self._operators[op])
- ) # type: CallableOperator
+ def _get_operator(self, op: str) -> CallableOperator:
+ operator_callable: CallableOperator = getattr(
+ self, f"_compare_{self._operators[op]}"
+ )
return operator_callable
- def _coerce_version(self, version):
- # type: (UnparsedVersion) -> ParsedVersion
+ def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion:
if not isinstance(version, (LegacyVersion, Version)):
version = parse(version)
return version
@property
- def operator(self):
- # type: () -> str
+ def operator(self) -> str:
return self._spec[0]
@property
- def version(self):
- # type: () -> str
+ def version(self) -> str:
return self._spec[1]
@property
- def prereleases(self):
- # type: () -> Optional[bool]
+ def prereleases(self) -> Optional[bool]:
return self._prereleases
@prereleases.setter
- def prereleases(self, value):
- # type: (bool) -> None
+ def prereleases(self, value: bool) -> None:
self._prereleases = value
- def __contains__(self, item):
- # type: (str) -> bool
+ def __contains__(self, item: str) -> bool:
return self.contains(item)
- def contains(self, item, prereleases=None):
- # type: (UnparsedVersion, Optional[bool]) -> bool
+ def contains(
+ self, item: UnparsedVersion, prereleases: Optional[bool] = None
+ ) -> bool:
# Determine if prereleases are to be allowed or not.
if prereleases is None:
@@ -213,11 +203,12 @@ class _IndividualSpecifier(BaseSpecifier):
# Actually do the comparison to determine if this item is contained
# within this Specifier or not.
- operator_callable = self._get_operator(self.operator) # type: CallableOperator
+ operator_callable: CallableOperator = self._get_operator(self.operator)
return operator_callable(normalized_item, self.version)
- def filter(self, iterable, prereleases=None):
- # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
+ def filter(
+ self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
+ ) -> Iterable[VersionTypeVar]:
yielded = False
found_prereleases = []
@@ -231,7 +222,7 @@ class _IndividualSpecifier(BaseSpecifier):
if self.contains(parsed_version, **kw):
# If our version is a prerelease, and we were not set to allow
- # prereleases, then we'll store it for later incase nothing
+ # prereleases, then we'll store it for later in case nothing
# else matches this specifier.
if parsed_version.is_prerelease and not (
prereleases or self.prereleases
@@ -276,9 +267,8 @@ class LegacySpecifier(_IndividualSpecifier):
">": "greater_than",
}
- def __init__(self, spec="", prereleases=None):
- # type: (str, Optional[bool]) -> None
- super(LegacySpecifier, self).__init__(spec, prereleases)
+ def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
+ super().__init__(spec, prereleases)
warnings.warn(
"Creating a LegacyVersion has been deprecated and will be "
@@ -286,44 +276,37 @@ class LegacySpecifier(_IndividualSpecifier):
DeprecationWarning,
)
- def _coerce_version(self, version):
- # type: (Union[ParsedVersion, str]) -> LegacyVersion
+ def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion:
if not isinstance(version, LegacyVersion):
version = LegacyVersion(str(version))
return version
- def _compare_equal(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool:
return prospective == self._coerce_version(spec)
- def _compare_not_equal(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool:
return prospective != self._coerce_version(spec)
- def _compare_less_than_equal(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool:
return prospective <= self._coerce_version(spec)
- def _compare_greater_than_equal(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_greater_than_equal(
+ self, prospective: LegacyVersion, spec: str
+ ) -> bool:
return prospective >= self._coerce_version(spec)
- def _compare_less_than(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool:
return prospective < self._coerce_version(spec)
- def _compare_greater_than(self, prospective, spec):
- # type: (LegacyVersion, str) -> bool
+ def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool:
return prospective > self._coerce_version(spec)
def _require_version_compare(
- fn, # type: (Callable[[Specifier, ParsedVersion, str], bool])
-):
- # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool]
+ fn: Callable[["Specifier", ParsedVersion, str], bool]
+) -> Callable[["Specifier", ParsedVersion, str], bool]:
@functools.wraps(fn)
- def wrapped(self, prospective, spec):
- # type: (Specifier, ParsedVersion, str) -> bool
+ def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool:
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
@@ -440,8 +423,7 @@ class Specifier(_IndividualSpecifier):
}
@_require_version_compare
- def _compare_compatible(self, prospective, spec):
- # type: (ParsedVersion, str) -> bool
+ def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool:
# Compatible releases have an equivalent combination of >= and ==. That
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
@@ -450,15 +432,9 @@ class Specifier(_IndividualSpecifier):
# the other specifiers.
# We want everything but the last item in the version, but we want to
- # ignore post and dev releases and we want to treat the pre-release as
- # it's own separate segment.
+ # ignore suffix segments.
prefix = ".".join(
- list(
- itertools.takewhile(
- lambda x: (not x.startswith("post") and not x.startswith("dev")),
- _version_split(spec),
- )
- )[:-1]
+ list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
)
# Add the prefix notation to the end of our string
@@ -469,8 +445,7 @@ class Specifier(_IndividualSpecifier):
)
@_require_version_compare
- def _compare_equal(self, prospective, spec):
- # type: (ParsedVersion, str) -> bool
+ def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool:
# We need special logic to handle prefix matching
if spec.endswith(".*"):
@@ -510,13 +485,11 @@ class Specifier(_IndividualSpecifier):
return prospective == spec_version
@_require_version_compare
- def _compare_not_equal(self, prospective, spec):
- # type: (ParsedVersion, str) -> bool
+ def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool:
return not self._compare_equal(prospective, spec)
@_require_version_compare
- def _compare_less_than_equal(self, prospective, spec):
- # type: (ParsedVersion, str) -> bool
+ def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool:
# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
@@ -524,8 +497,9 @@ class Specifier(_IndividualSpecifier):
return Version(prospective.public) <= Version(spec)
@_require_version_compare
- def _compare_greater_than_equal(self, prospective, spec):
- # type: (ParsedVersion, str) -> bool
+ def _compare_greater_than_equal(
+ self, prospective: ParsedVersion, spec: str
+ ) -> bool:
# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
@@ -533,8 +507,7 @@ class Specifier(_IndividualSpecifier):
return Version(prospective.public) >= Version(spec)
@_require_version_compare
- def _compare_less_than(self, prospective, spec_str):
- # type: (ParsedVersion, str) -> bool
+ def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool:
# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
@@ -560,8 +533,7 @@ class Specifier(_IndividualSpecifier):
return True
@_require_version_compare
- def _compare_greater_than(self, prospective, spec_str):
- # type: (ParsedVersion, str) -> bool
+ def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool:
# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
@@ -592,13 +564,11 @@ class Specifier(_IndividualSpecifier):
# same version in the spec.
return True
- def _compare_arbitrary(self, prospective, spec):
- # type: (Version, str) -> bool
+ def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
return str(prospective).lower() == str(spec).lower()
@property
- def prereleases(self):
- # type: () -> bool
+ def prereleases(self) -> bool:
# If there is an explicit prereleases set for this, then we'll just
# blindly use that.
@@ -623,17 +593,15 @@ class Specifier(_IndividualSpecifier):
return False
@prereleases.setter
- def prereleases(self, value):
- # type: (bool) -> None
+ def prereleases(self, value: bool) -> None:
self._prereleases = value
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
-def _version_split(version):
- # type: (str) -> List[str]
- result = [] # type: List[str]
+def _version_split(version: str) -> List[str]:
+ result: List[str] = []
for item in version.split("."):
match = _prefix_regex.search(item)
if match:
@@ -643,8 +611,13 @@ def _version_split(version):
return result
-def _pad_version(left, right):
- # type: (List[str], List[str]) -> Tuple[List[str], List[str]]
+def _is_not_suffix(segment: str) -> bool:
+ return not any(
+ segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
+ )
+
+
+def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
left_split, right_split = [], []
# Get the release segment of our versions
@@ -663,8 +636,9 @@ def _pad_version(left, right):
class SpecifierSet(BaseSpecifier):
- def __init__(self, specifiers="", prereleases=None):
- # type: (str, Optional[bool]) -> None
+ def __init__(
+ self, specifiers: str = "", prereleases: Optional[bool] = None
+ ) -> None:
# Split on , to break each individual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
@@ -672,7 +646,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[_IndividualSpecifier] = set()
for specifier in split_specifiers:
try:
parsed.add(Specifier(specifier))
@@ -686,27 +660,23 @@ class SpecifierSet(BaseSpecifier):
# we accept prereleases or not.
self._prereleases = prereleases
- def __repr__(self):
- # type: () -> str
+ def __repr__(self) -> 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
+ def __str__(self) -> str:
return ",".join(sorted(str(s) for s in self._specs))
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(self._specs)
- def __and__(self, other):
- # type: (Union[SpecifierSet, str]) -> SpecifierSet
- if isinstance(other, string_types):
+ def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
+ if isinstance(other, str):
other = SpecifierSet(other)
elif not isinstance(other, SpecifierSet):
return NotImplemented
@@ -728,35 +698,30 @@ class SpecifierSet(BaseSpecifier):
return specifier
- def __eq__(self, other):
- # type: (object) -> bool
- if isinstance(other, (string_types, _IndividualSpecifier)):
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, (str, _IndividualSpecifier)):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented
return self._specs == other._specs
- def __ne__(self, other):
- # type: (object) -> bool
- if isinstance(other, (string_types, _IndividualSpecifier)):
+ def __ne__(self, other: object) -> bool:
+ if isinstance(other, (str, _IndividualSpecifier)):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented
return self._specs != other._specs
- def __len__(self):
- # type: () -> int
+ def __len__(self) -> int:
return len(self._specs)
- def __iter__(self):
- # type: () -> Iterator[_IndividualSpecifier]
+ def __iter__(self) -> Iterator[_IndividualSpecifier]:
return iter(self._specs)
@property
- def prereleases(self):
- # type: () -> Optional[bool]
+ def prereleases(self) -> Optional[bool]:
# If we have been given an explicit prerelease modifier, then we'll
# pass that through here.
@@ -774,16 +739,15 @@ class SpecifierSet(BaseSpecifier):
return any(s.prereleases for s in self._specs)
@prereleases.setter
- def prereleases(self, value):
- # type: (bool) -> None
+ def prereleases(self, value: bool) -> None:
self._prereleases = value
- def __contains__(self, item):
- # type: (Union[ParsedVersion, str]) -> bool
+ def __contains__(self, item: UnparsedVersion) -> bool:
return self.contains(item)
- def contains(self, item, prereleases=None):
- # type: (Union[ParsedVersion, str], Optional[bool]) -> bool
+ def contains(
+ self, item: UnparsedVersion, prereleases: Optional[bool] = None
+ ) -> bool:
# Ensure that our item is a Version or LegacyVersion instance.
if not isinstance(item, (LegacyVersion, Version)):
@@ -811,11 +775,8 @@ class SpecifierSet(BaseSpecifier):
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(
- self,
- iterable, # type: Iterable[Union[ParsedVersion, str]]
- prereleases=None, # type: Optional[bool]
- ):
- # type: (...) -> Iterable[Union[ParsedVersion, str]]
+ self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
+ ) -> Iterable[VersionTypeVar]:
# Determine if we're forcing a prerelease or not, if we're not forcing
# one for this particular filter call, then we'll use whatever the
@@ -834,8 +795,11 @@ class SpecifierSet(BaseSpecifier):
# which will filter out any pre-releases, unless there are no final
# releases, and which will filter out LegacyVersion in general.
else:
- filtered = [] # type: List[Union[ParsedVersion, str]]
- found_prereleases = [] # type: List[Union[ParsedVersion, str]]
+ filtered: List[VersionTypeVar] = []
+ found_prereleases: List[VersionTypeVar] = []
+
+ item: UnparsedVersion
+ parsed_version: Union[Version, LegacyVersion]
for item in iterable:
# Ensure that we some kind of Version class for this item.
diff --git a/packaging/tags.py b/packaging/tags.py
index d637f1b..82a47cd 100644
--- a/packaging/tags.py
+++ b/packaging/tags.py
@@ -2,81 +2,44 @@
# 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 logging
-import os
import platform
-import re
-import struct
import sys
import sysconfig
-import warnings
-
-from ._typing import TYPE_CHECKING, cast
-
-if TYPE_CHECKING: # pragma: no cover
- from typing import (
- IO,
- Dict,
- FrozenSet,
- Iterable,
- Iterator,
- List,
- Optional,
- Sequence,
- Tuple,
- Union,
- )
-
- PythonVersion = Sequence[int]
- MacVersion = Tuple[int, int]
- GlibcVersion = Tuple[int, int]
-
+from importlib.machinery import EXTENSION_SUFFIXES
+from typing import (
+ Dict,
+ FrozenSet,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+ cast,
+)
+
+from . import _manylinux, _musllinux
logger = logging.getLogger(__name__)
-INTERPRETER_SHORT_NAMES = {
+PythonVersion = Sequence[int]
+MacVersion = Tuple[int, int]
+
+INTERPRETER_SHORT_NAMES: Dict[str, str] = {
"python": "py", # Generic.
"cpython": "cp",
"pypy": "pp",
"ironpython": "ip",
"jython": "jy",
-} # type: Dict[str, str]
+}
_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
-_LEGACY_MANYLINUX_MAP = {
- # CentOS 7 w/ glibc 2.17 (PEP 599)
- (2, 17): "manylinux2014",
- # CentOS 6 w/ glibc 2.12 (PEP 571)
- (2, 12): "manylinux2010",
- # CentOS 5 w/ glibc 2.5 (PEP 513)
- (2, 5): "manylinux1",
-}
-
-# If glibc ever changes its major version, we need to know what the last
-# minor version was, so we can build the complete list of all versions.
-# For now, guess what the highest minor version might be, assume it will
-# be 50 for testing. Once this actually happens, update the dictionary
-# with the actual value.
-_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.
@@ -86,8 +49,7 @@ class Tag(object):
__slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
- def __init__(self, interpreter, abi, platform):
- # type: (str, str, str) -> None
+ def __init__(self, interpreter: str, abi: str, platform: str) -> None:
self._interpreter = interpreter.lower()
self._abi = abi.lower()
self._platform = platform.lower()
@@ -99,46 +61,39 @@ class Tag(object):
self._hash = hash((self._interpreter, self._abi, self._platform))
@property
- def interpreter(self):
- # type: () -> str
+ def interpreter(self) -> str:
return self._interpreter
@property
- def abi(self):
- # type: () -> str
+ def abi(self) -> str:
return self._abi
@property
- def platform(self):
- # type: () -> str
+ def platform(self) -> str:
return self._platform
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
if not isinstance(other, Tag):
return NotImplemented
return (
- (self.platform == other.platform)
- and (self.abi == other.abi)
- and (self.interpreter == other.interpreter)
+ (self._hash == other._hash) # Short-circuit ASAP for perf reasons.
+ and (self._platform == other._platform)
+ and (self._abi == other._abi)
+ and (self._interpreter == other._interpreter)
)
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return self._hash
- def __str__(self):
- # type: () -> str
- return "{}-{}-{}".format(self._interpreter, self._abi, self._platform)
+ def __str__(self) -> str:
+ return f"{self._interpreter}-{self._abi}-{self._platform}"
- def __repr__(self):
- # type: () -> str
+ def __repr__(self) -> str:
return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
-def parse_tag(tag):
- # type: (str) -> FrozenSet[Tag]
+def parse_tag(tag: str) -> FrozenSet[Tag]:
"""
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
@@ -154,24 +109,7 @@ def parse_tag(tag):
return frozenset(tags)
-def _warn_keyword_parameter(func_name, kwargs):
- # type: (str, Dict[str, bool]) -> bool
- """
- Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only.
- """
- if not kwargs:
- return False
- 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)
- )
- return kwargs["warn"]
-
-
-def _get_config_var(name, warn=False):
- # type: (str, bool) -> Union[int, str, None]
+def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
value = sysconfig.get_config_var(name)
if value is None and warn:
logger.debug(
@@ -180,13 +118,11 @@ def _get_config_var(name, warn=False):
return value
-def _normalize_string(string):
- # type: (str) -> str
+def _normalize_string(string: str) -> str:
return string.replace(".", "_").replace("-", "_")
-def _abi3_applies(python_version):
- # type: (PythonVersion) -> bool
+def _abi3_applies(python_version: PythonVersion) -> bool:
"""
Determine if the Python version supports abi3.
@@ -195,8 +131,7 @@ def _abi3_applies(python_version):
return len(python_version) > 1 and tuple(python_version) >= (3, 2)
-def _cpython_abis(py_version, warn=False):
- # type: (PythonVersion, bool) -> List[str]
+def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
py_version = tuple(py_version) # To allow for version comparison.
abis = []
version = _version_nodot(py_version[:2])
@@ -222,7 +157,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(
@@ -233,12 +168,12 @@ def _cpython_abis(py_version, warn=False):
def cpython_tags(
- python_version=None, # type: Optional[PythonVersion]
- abis=None, # type: Optional[Iterable[str]]
- platforms=None, # type: Optional[Iterable[str]]
- **kwargs # type: bool
-):
- # type: (...) -> Iterator[Tag]
+ python_version: Optional[PythonVersion] = None,
+ abis: Optional[Iterable[str]] = None,
+ platforms: Optional[Iterable[str]] = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
"""
Yields the tags for a CPython interpreter.
@@ -254,7 +189,6 @@ def cpython_tags(
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
their normal position and not at the beginning.
"""
- warn = _warn_keyword_parameter("cpython_tags", kwargs)
if not python_version:
python_version = sys.version_info[:2]
@@ -278,10 +212,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):
@@ -292,20 +224,19 @@ def cpython_tags(
yield Tag(interpreter, "abi3", platform_)
-def _generic_abi():
- # type: () -> Iterator[str]
+def _generic_abi() -> Iterator[str]:
abi = sysconfig.get_config_var("SOABI")
if abi:
yield _normalize_string(abi)
def generic_tags(
- interpreter=None, # type: Optional[str]
- abis=None, # type: Optional[Iterable[str]]
- platforms=None, # type: Optional[Iterable[str]]
- **kwargs # type: bool
-):
- # type: (...) -> Iterator[Tag]
+ interpreter: Optional[str] = None,
+ abis: Optional[Iterable[str]] = None,
+ platforms: Optional[Iterable[str]] = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
"""
Yields the tags for a generic interpreter.
@@ -314,7 +245,6 @@ def generic_tags(
The "none" ABI will be added if it was not explicitly provided.
"""
- warn = _warn_keyword_parameter("generic_tags", kwargs)
if not interpreter:
interp_name = interpreter_name()
interp_version = interpreter_version(warn=warn)
@@ -330,8 +260,7 @@ def generic_tags(
yield Tag(interpreter, abi, platform_)
-def _py_interpreter_range(py_version):
- # type: (PythonVersion) -> Iterator[str]
+def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
"""
Yields Python versions in descending order.
@@ -347,11 +276,10 @@ def _py_interpreter_range(py_version):
def compatible_tags(
- python_version=None, # type: Optional[PythonVersion]
- interpreter=None, # type: Optional[str]
- platforms=None, # type: Optional[Iterable[str]]
-):
- # type: (...) -> Iterator[Tag]
+ python_version: Optional[PythonVersion] = None,
+ interpreter: Optional[str] = None,
+ platforms: Optional[Iterable[str]] = None,
+) -> Iterator[Tag]:
"""
Yields the sequence of tags that are compatible with a specific version of Python.
@@ -372,8 +300,7 @@ def compatible_tags(
yield Tag(version, "none", "any")
-def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
- # type: (str, bool) -> str
+def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
if not is_32bit:
return arch
@@ -383,8 +310,7 @@ def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
return "i386"
-def _mac_binary_formats(version, cpu_arch):
- # type: (MacVersion, str) -> List[str]
+def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
formats = [cpu_arch]
if cpu_arch == "x86_64":
if version < (10, 4):
@@ -416,8 +342,9 @@ def _mac_binary_formats(version, cpu_arch):
return formats
-def mac_platforms(version=None, arch=None):
- # type: (Optional[MacVersion], Optional[str]) -> Iterator[str]
+def mac_platforms(
+ version: Optional[MacVersion] = None, arch: Optional[str] = None
+) -> Iterator[str]:
"""
Yields the platform tags for a macOS system.
@@ -426,7 +353,7 @@ def mac_platforms(version=None, arch=None):
generate platform tags for. Both parameters default to the appropriate value
for the current system.
"""
- version_str, _, cpu_arch = platform.mac_ver() # type: ignore
+ version_str, _, cpu_arch = platform.mac_ver()
if version is None:
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
else:
@@ -487,320 +414,24 @@ def mac_platforms(version=None, arch=None):
)
-# From PEP 513, PEP 600
-def _is_manylinux_compatible(name, arch, glibc_version):
- # type: (str, str, GlibcVersion) -> bool
- sys_glibc = _get_glibc_version()
- if sys_glibc < glibc_version:
- return False
- # Check for presence of _manylinux module.
- try:
- import _manylinux # noqa
- except ImportError:
- pass
- else:
- if hasattr(_manylinux, "manylinux_compatible"):
- result = _manylinux.manylinux_compatible(
- glibc_version[0], glibc_version[1], arch
- )
- if result is not None:
- return bool(result)
- else:
- if glibc_version == (2, 5):
- if hasattr(_manylinux, "manylinux1_compatible"):
- return bool(_manylinux.manylinux1_compatible)
- if glibc_version == (2, 12):
- if hasattr(_manylinux, "manylinux2010_compatible"):
- return bool(_manylinux.manylinux2010_compatible)
- if glibc_version == (2, 17):
- if hasattr(_manylinux, "manylinux2014_compatible"):
- return bool(_manylinux.manylinux2014_compatible)
- return True
-
-
-def _glibc_version_string():
- # type: () -> Optional[str]
- # Returns glibc version string, or None if not using glibc.
- return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
-
-
-def _glibc_version_string_confstr():
- # type: () -> Optional[str]
- """
- Primary implementation of glibc_version_string using os.confstr.
- """
- # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
- # to be broken or missing. This strategy is used in the standard library
- # platform module.
- # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
- try:
- # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
- version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821
- "CS_GNU_LIBC_VERSION"
- )
- assert version_string is not None
- _, version = version_string.split() # type: Tuple[str, str]
- except (AssertionError, AttributeError, OSError, ValueError):
- # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
- return None
- return version
-
-
-def _glibc_version_string_ctypes():
- # type: () -> Optional[str]
- """
- Fallback implementation of glibc_version_string using ctypes.
- """
- try:
- import ctypes
- except ImportError:
- return None
-
- # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
- # manpage says, "If filename is NULL, then the returned handle is for the
- # main program". This way we can let the linker do the work to figure out
- # which libc our process is actually using.
- #
- # We must also handle the special case where the executable is not a
- # dynamically linked executable. This can occur when using musl libc,
- # for example. In this situation, dlopen() will error, leading to an
- # OSError. Interestingly, at least in the case of musl, there is no
- # errno set on the OSError. The single string argument used to construct
- # OSError comes from libc itself and is therefore not portable to
- # hard code here. In any case, failure to call dlopen() means we
- # can proceed, so we bail on our attempt.
- try:
- # Note: typeshed is wrong here so we are ignoring this line.
- process_namespace = ctypes.CDLL(None) # type: ignore
- except OSError:
- return None
-
- try:
- gnu_get_libc_version = process_namespace.gnu_get_libc_version
- except AttributeError:
- # Symbol doesn't exist -> therefore, we are not linked to
- # glibc.
- return None
-
- # Call gnu_get_libc_version, which returns a string like "2.5"
- gnu_get_libc_version.restype = ctypes.c_char_p
- version_str = gnu_get_libc_version() # type: str
- # py2 / py3 compatibility:
- if not isinstance(version_str, str):
- version_str = version_str.decode("ascii")
-
- return version_str
-
-
-def _parse_glibc_version(version_str):
- # type: (str) -> Tuple[int, int]
- # Parse glibc version.
- #
- # We use a regexp instead of str.split because we want to discard any
- # random junk that might come after the minor version -- this might happen
- # in patched/forked versions of glibc (e.g. Linaro's version of glibc
- # uses version strings like "2.20-2014.11"). See gh-3588.
- m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
- if not m:
- warnings.warn(
- "Expected glibc version with 2 components major.minor,"
- " got: %s" % version_str,
- RuntimeWarning,
- )
- return -1, -1
- return (int(m.group("major")), int(m.group("minor")))
-
-
-_glibc_version = [] # type: List[Tuple[int, int]]
-
-
-def _get_glibc_version():
- # type: () -> Tuple[int, int]
- if _glibc_version:
- return _glibc_version[0]
- version_str = _glibc_version_string()
- if version_str is None:
- _glibc_version.append((-1, -1))
- else:
- _glibc_version.append(_parse_glibc_version(version_str))
- return _glibc_version[0]
-
-
-# Python does not provide platform information at sufficient granularity to
-# 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):
- # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
- class _InvalidELFFileHeader(ValueError):
- """
- An invalid ELF file header was found.
- """
-
- ELF_MAGIC_NUMBER = 0x7F454C46
- ELFCLASS32 = 1
- ELFCLASS64 = 2
- ELFDATA2LSB = 1
- ELFDATA2MSB = 2
- EM_386 = 3
- EM_S390 = 22
- EM_ARM = 40
- EM_X86_64 = 62
- EF_ARM_ABIMASK = 0xFF000000
- EF_ARM_ABI_VER5 = 0x05000000
- EF_ARM_ABI_FLOAT_HARD = 0x00000400
-
- def __init__(self, file):
- # type: (IO[bytes]) -> None
- def unpack(fmt):
- # type: (str) -> int
- try:
- (result,) = struct.unpack(
- fmt, file.read(struct.calcsize(fmt))
- ) # type: (int, )
- except struct.error:
- raise _ELFFileHeader._InvalidELFFileHeader()
- return result
-
- self.e_ident_magic = unpack(">I")
- if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
- raise _ELFFileHeader._InvalidELFFileHeader()
- self.e_ident_class = unpack("B")
- if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
- raise _ELFFileHeader._InvalidELFFileHeader()
- self.e_ident_data = unpack("B")
- if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
- raise _ELFFileHeader._InvalidELFFileHeader()
- self.e_ident_version = unpack("B")
- self.e_ident_osabi = unpack("B")
- self.e_ident_abiversion = unpack("B")
- self.e_ident_pad = file.read(7)
- format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
- format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
- format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
- format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
- self.e_type = unpack(format_h)
- self.e_machine = unpack(format_h)
- self.e_version = unpack(format_i)
- self.e_entry = unpack(format_p)
- self.e_phoff = unpack(format_p)
- self.e_shoff = unpack(format_p)
- self.e_flags = unpack(format_i)
- self.e_ehsize = unpack(format_h)
- self.e_phentsize = unpack(format_h)
- self.e_phnum = unpack(format_h)
- self.e_shentsize = unpack(format_h)
- self.e_shnum = unpack(format_h)
- self.e_shstrndx = unpack(format_h)
-
-
-def _get_elf_header():
- # type: () -> Optional[_ELFFileHeader]
- try:
- with open(sys.executable, "rb") as f:
- elf_header = _ELFFileHeader(f)
- except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
- return None
- return elf_header
-
-
-def _is_linux_armhf():
- # type: () -> bool
- # hard-float ABI can be detected from the ELF header of the running
- # process
- # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
- elf_header = _get_elf_header()
- if elf_header is None:
- return False
- result = elf_header.e_ident_class == elf_header.ELFCLASS32
- result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
- result &= elf_header.e_machine == elf_header.EM_ARM
- result &= (
- elf_header.e_flags & elf_header.EF_ARM_ABIMASK
- ) == elf_header.EF_ARM_ABI_VER5
- result &= (
- elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD
- ) == elf_header.EF_ARM_ABI_FLOAT_HARD
- return result
-
-
-def _is_linux_i686():
- # type: () -> bool
- elf_header = _get_elf_header()
- if elf_header is None:
- return False
- result = elf_header.e_ident_class == elf_header.ELFCLASS32
- result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
- result &= elf_header.e_machine == elf_header.EM_386
- return result
-
-
-def _have_compatible_manylinux_abi(arch):
- # type: (str) -> bool
- if arch == "armv7l":
- return _is_linux_armhf()
- if arch == "i686":
- return _is_linux_i686()
- return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
-
-
-def _manylinux_tags(linux, arch):
- # type: (str, str) -> Iterator[str]
- # Oldest glibc to be supported regardless of architecture is (2, 17).
- too_old_glibc2 = glibcVersion(2, 16)
- if arch in {"x86_64", "i686"}:
- # On x86/i686 also oldest glibc to be supported is (2, 5).
- too_old_glibc2 = glibcVersion(2, 4)
- current_glibc = glibcVersion(*_get_glibc_version())
- glibc_max_list = [current_glibc]
- # We can assume compatibility across glibc major versions.
- # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
- #
- # Build a list of maximum glibc versions so that we can
- # output the canonical list of all glibc from current_glibc
- # down to too_old_glibc2, including all intermediary versions.
- for glibc_major in range(current_glibc.major - 1, 1, -1):
- glibc_max_list.append(glibcVersion(glibc_major, _LAST_GLIBC_MINOR[glibc_major]))
- for glibc_max in glibc_max_list:
- if glibc_max.major == too_old_glibc2.major:
- min_minor = too_old_glibc2.minor
- else:
- # For other glibc major versions oldest supported is (x, 0).
- min_minor = -1
- for glibc_minor in range(glibc_max.minor, min_minor, -1):
- glibc_version = (glibc_max.major, glibc_minor)
- tag = "manylinux_{}_{}".format(*glibc_version)
- if _is_manylinux_compatible(tag, arch, glibc_version):
- yield linux.replace("linux", tag)
- # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
- if glibc_version in _LEGACY_MANYLINUX_MAP:
- legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
- if _is_manylinux_compatible(legacy_tag, arch, glibc_version):
- yield linux.replace("linux", legacy_tag)
-
-
-def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
- # type: (bool) -> Iterator[str]
- linux = _normalize_string(distutils.util.get_platform())
+def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
+ linux = _normalize_string(sysconfig.get_platform())
if is_32bit:
if linux == "linux_x86_64":
linux = "linux_i686"
elif linux == "linux_aarch64":
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.platform_tags(linux, arch)
+ yield from _musllinux.platform_tags(arch)
yield linux
-def _generic_platforms():
- # type: () -> Iterator[str]
- yield _normalize_string(distutils.util.get_platform())
+def _generic_platforms() -> Iterator[str]:
+ yield _normalize_string(sysconfig.get_platform())
-def _platform_tags():
- # type: () -> Iterator[str]
+def _platform_tags() -> Iterator[str]:
"""
Provides the platform tags for this installation.
"""
@@ -812,25 +443,18 @@ def _platform_tags():
return _generic_platforms()
-def interpreter_name():
- # type: () -> str
+def interpreter_name() -> str:
"""
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
-def interpreter_version(**kwargs):
- # type: (bool) -> str
+def interpreter_version(*, warn: bool = False) -> str:
"""
Returns the version of the running interpreter.
"""
- warn = _warn_keyword_parameter("interpreter_version", kwargs)
version = _get_config_var("py_version_nodot", warn=warn)
if version:
version = str(version)
@@ -839,28 +463,22 @@ def interpreter_version(**kwargs):
return version
-def _version_nodot(version):
- # type: (PythonVersion) -> str
+def _version_nodot(version: PythonVersion) -> str:
return "".join(map(str, version))
-def sys_tags(**kwargs):
- # type: (bool) -> Iterator[Tag]
+def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
"""
Returns the sequence of tag triples for the running interpreter.
The order of the sequence corresponds to priority order for the
interpreter, from most to least important.
"""
- warn = _warn_keyword_parameter("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()
diff --git a/packaging/utils.py b/packaging/utils.py
index 6e8c2a3..bab11b8 100644
--- a/packaging/utils.py
+++ b/packaging/utils.py
@@ -1,22 +1,15 @@
# 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
+from typing import FrozenSet, NewType, Tuple, Union, cast
-from ._typing import TYPE_CHECKING, cast
from .tags import Tag, parse_tag
from .version import InvalidVersion, Version
-if TYPE_CHECKING: # pragma: no cover
- from typing import FrozenSet, NewType, Tuple, Union
-
- BuildTag = Union[Tuple[()], Tuple[int, str]]
- NormalizedName = NewType("NormalizedName", str)
-else:
- BuildTag = tuple
- NormalizedName = str
+BuildTag = Union[Tuple[()], Tuple[int, str]]
+NormalizedName = NewType("NormalizedName", str)
class InvalidWheelFilename(ValueError):
@@ -36,74 +29,75 @@ _canonicalize_regex = re.compile(r"[-_.]+")
_build_tag_regex = re.compile(r"(\d+)(.*)")
-def canonicalize_name(name):
- # type: (str) -> NormalizedName
+def canonicalize_name(name: str) -> NormalizedName:
# This is taken from PEP 503.
value = _canonicalize_regex.sub("-", name).lower()
return cast(NormalizedName, value)
-def canonicalize_version(version):
- # type: (Union[Version, str]) -> Union[Version, str]
+def canonicalize_version(version: Union[Version, str]) -> str:
"""
This is very similar to Version.__str__, but has one subtle difference
with the way it handles the release segment.
"""
- if not isinstance(version, Version):
+ if isinstance(version, str):
try:
- version = Version(version)
+ parsed = Version(version)
except InvalidVersion:
# Legacy versions cannot be normalized
return version
+ else:
+ parsed = version
parts = []
# Epoch
- if version.epoch != 0:
- parts.append("{0}!".format(version.epoch))
+ if parsed.epoch != 0:
+ parts.append(f"{parsed.epoch}!")
# Release segment
# NB: This strips trailing '.0's to normalize
- parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
+ parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release)))
# Pre-release
- if version.pre is not None:
- parts.append("".join(str(x) for x in version.pre))
+ if parsed.pre is not None:
+ parts.append("".join(str(x) for x in parsed.pre))
# Post-release
- if version.post is not None:
- parts.append(".post{0}".format(version.post))
+ if parsed.post is not None:
+ parts.append(f".post{parsed.post}")
# Development release
- if version.dev is not None:
- parts.append(".dev{0}".format(version.dev))
+ if parsed.dev is not None:
+ parts.append(f".dev{parsed.dev}")
# Local version segment
- if version.local is not None:
- parts.append("+{0}".format(version.local))
+ if parsed.local is not None:
+ parts.append(f"+{parsed.local}")
return "".join(parts)
-def parse_wheel_filename(filename):
- # type: (str) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]
+def parse_wheel_filename(
+ filename: 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 +105,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:
@@ -120,18 +114,22 @@ def parse_wheel_filename(filename):
return (name, version, build, tags)
-def parse_sdist_filename(filename):
- # type: (str) -> Tuple[NormalizedName, Version]
- if not filename.endswith(".tar.gz"):
+def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:
+ if filename.endswith(".tar.gz"):
+ file_stem = filename[: -len(".tar.gz")]
+ elif filename.endswith(".zip"):
+ file_stem = filename[: -len(".zip")]
+ else:
raise InvalidSdistFilename(
- "Invalid sdist filename (extension must be '.tar.gz'): {0}".format(filename)
+ f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
+ f" {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("-")
+ name_part, sep, version_part = file_stem.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)
diff --git a/packaging/version.py b/packaging/version.py
index 517d91f..de9a09a 100644
--- a/packaging/version.py
+++ b/packaging/version.py
@@ -1,53 +1,45 @@
# 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
import re
import warnings
+from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
-from ._structures import Infinity, NegativeInfinity
-from ._typing import TYPE_CHECKING
-
-if TYPE_CHECKING: # pragma: no cover
- from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
-
- from ._structures import InfinityType, NegativeInfinityType
-
- InfiniteTypes = Union[InfinityType, NegativeInfinityType]
- PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
- SubLocalType = Union[InfiniteTypes, int, str]
- LocalType = Union[
- NegativeInfinityType,
- Tuple[
- Union[
- SubLocalType,
- Tuple[SubLocalType, str],
- Tuple[NegativeInfinityType, SubLocalType],
- ],
- ...,
- ],
- ]
- CmpKey = Tuple[
- int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
- ]
- LegacyCmpKey = Tuple[int, Tuple[str, ...]]
- VersionComparisonMethod = Callable[
- [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
- ]
+from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
+InfiniteTypes = Union[InfinityType, NegativeInfinityType]
+PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
+SubLocalType = Union[InfiniteTypes, int, str]
+LocalType = Union[
+ NegativeInfinityType,
+ Tuple[
+ Union[
+ SubLocalType,
+ Tuple[SubLocalType, str],
+ Tuple[NegativeInfinityType, SubLocalType],
+ ],
+ ...,
+ ],
+]
+CmpKey = Tuple[
+ int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
+]
+LegacyCmpKey = Tuple[int, Tuple[str, ...]]
+VersionComparisonMethod = Callable[
+ [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
+]
_Version = collections.namedtuple(
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)
-def parse(version):
- # type: (str) -> Union[LegacyVersion, Version]
+def parse(version: str) -> Union["LegacyVersion", "Version"]:
"""
Parse the given version string and return either a :class:`Version` object
or a :class:`LegacyVersion` object depending on if the given version is
@@ -65,53 +57,46 @@ class InvalidVersion(ValueError):
"""
-class _BaseVersion(object):
- _key = None # type: Union[CmpKey, LegacyCmpKey]
+class _BaseVersion:
+ _key: Union[CmpKey, LegacyCmpKey]
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(self._key)
# Please keep the duplicated `isinstance` check
# in the six comparisons hereunder
# unless you find a way to avoid adding overhead function calls.
- def __lt__(self, other):
- # type: (_BaseVersion) -> bool
+ def __lt__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key < other._key
- def __le__(self, other):
- # type: (_BaseVersion) -> bool
+ def __le__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key <= other._key
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key == other._key
- def __ge__(self, other):
- # type: (_BaseVersion) -> bool
+ def __ge__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key >= other._key
- def __gt__(self, other):
- # type: (_BaseVersion) -> bool
+ def __gt__(self, other: "_BaseVersion") -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key > other._key
- def __ne__(self, other):
- # type: (object) -> bool
+ def __ne__(self, other: object) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
@@ -119,8 +104,7 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion):
- def __init__(self, version):
- # type: (str) -> None
+ def __init__(self, version: str) -> None:
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
@@ -130,67 +114,54 @@ class LegacyVersion(_BaseVersion):
DeprecationWarning,
)
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
return self._version
- def __repr__(self):
- # type: () -> str
- return "<LegacyVersion({0})>".format(repr(str(self)))
+ def __repr__(self) -> str:
+ return f"<LegacyVersion('{self}')>"
@property
- def public(self):
- # type: () -> str
+ def public(self) -> str:
return self._version
@property
- def base_version(self):
- # type: () -> str
+ def base_version(self) -> str:
return self._version
@property
- def epoch(self):
- # type: () -> int
+ def epoch(self) -> int:
return -1
@property
- def release(self):
- # type: () -> None
+ def release(self) -> None:
return None
@property
- def pre(self):
- # type: () -> None
+ def pre(self) -> None:
return None
@property
- def post(self):
- # type: () -> None
+ def post(self) -> None:
return None
@property
- def dev(self):
- # type: () -> None
+ def dev(self) -> None:
return None
@property
- def local(self):
- # type: () -> None
+ def local(self) -> None:
return None
@property
- def is_prerelease(self):
- # type: () -> bool
+ def is_prerelease(self) -> bool:
return False
@property
- def is_postrelease(self):
- # type: () -> bool
+ def is_postrelease(self) -> bool:
return False
@property
- def is_devrelease(self):
- # type: () -> bool
+ def is_devrelease(self) -> bool:
return False
@@ -205,8 +176,7 @@ _legacy_version_replacement_map = {
}
-def _parse_version_parts(s):
- # type: (str) -> Iterator[str]
+def _parse_version_parts(s: str) -> Iterator[str]:
for part in _legacy_version_component_re.split(s):
part = _legacy_version_replacement_map.get(part, part)
@@ -223,8 +193,7 @@ def _parse_version_parts(s):
yield "*final"
-def _legacy_cmpkey(version):
- # type: (str) -> LegacyCmpKey
+def _legacy_cmpkey(version: str) -> LegacyCmpKey:
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
# greater than or equal to 0. This will effectively put the LegacyVersion,
@@ -234,7 +203,7 @@ def _legacy_cmpkey(version):
# This scheme is taken from pkg_resources.parse_version setuptools prior to
# it's adoption of the packaging library.
- parts = [] # type: List[str]
+ parts: List[str] = []
for part in _parse_version_parts(version.lower()):
if part.startswith("*"):
# remove "-" before a prerelease tag
@@ -289,13 +258,12 @@ class Version(_BaseVersion):
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
- def __init__(self, version):
- # type: (str) -> None
+ def __init__(self, version: str) -> None:
# 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(
@@ -319,17 +287,15 @@ class Version(_BaseVersion):
self._version.local,
)
- def __repr__(self):
- # type: () -> str
- return "<Version({0})>".format(repr(str(self)))
+ def __repr__(self) -> str:
+ return f"<Version('{self}')>"
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
parts = []
# 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,67 +306,59 @@ 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)
@property
- def epoch(self):
- # type: () -> int
- _epoch = self._version.epoch # type: int
+ def epoch(self) -> int:
+ _epoch: int = self._version.epoch
return _epoch
@property
- def release(self):
- # type: () -> Tuple[int, ...]
- _release = self._version.release # type: Tuple[int, ...]
+ def release(self) -> Tuple[int, ...]:
+ _release: Tuple[int, ...] = self._version.release
return _release
@property
- def pre(self):
- # type: () -> Optional[Tuple[str, int]]
- _pre = self._version.pre # type: Optional[Tuple[str, int]]
+ def pre(self) -> Optional[Tuple[str, int]]:
+ _pre: Optional[Tuple[str, int]] = self._version.pre
return _pre
@property
- def post(self):
- # type: () -> Optional[Tuple[str, int]]
+ def post(self) -> Optional[int]:
return self._version.post[1] if self._version.post else None
@property
- def dev(self):
- # type: () -> Optional[Tuple[str, int]]
+ def dev(self) -> Optional[int]:
return self._version.dev[1] if self._version.dev else None
@property
- def local(self):
- # type: () -> Optional[str]
+ def local(self) -> Optional[str]:
if self._version.local:
return ".".join(str(x) for x in self._version.local)
else:
return None
@property
- def public(self):
- # type: () -> str
+ def public(self) -> str:
return str(self).split("+", 1)[0]
@property
- def base_version(self):
- # type: () -> str
+ def base_version(self) -> str:
parts = []
# 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))
@@ -408,41 +366,33 @@ class Version(_BaseVersion):
return "".join(parts)
@property
- def is_prerelease(self):
- # type: () -> bool
+ def is_prerelease(self) -> bool:
return self.dev is not None or self.pre is not None
@property
- def is_postrelease(self):
- # type: () -> bool
+ def is_postrelease(self) -> bool:
return self.post is not None
@property
- def is_devrelease(self):
- # type: () -> bool
+ def is_devrelease(self) -> bool:
return self.dev is not None
@property
- def major(self):
- # type: () -> int
+ def major(self) -> int:
return self.release[0] if len(self.release) >= 1 else 0
@property
- def minor(self):
- # type: () -> int
+ def minor(self) -> int:
return self.release[1] if len(self.release) >= 2 else 0
@property
- def micro(self):
- # type: () -> int
+ def micro(self) -> int:
return self.release[2] if len(self.release) >= 3 else 0
def _parse_letter_version(
- letter, # type: str
- number, # type: Union[str, bytes, SupportsInt]
-):
- # type: (...) -> Optional[Tuple[str, int]]
+ letter: str, number: Union[str, bytes, SupportsInt]
+) -> Optional[Tuple[str, int]]:
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
@@ -479,8 +429,7 @@ def _parse_letter_version(
_local_version_separators = re.compile(r"[\._-]")
-def _parse_local_version(local):
- # type: (str) -> Optional[LocalType]
+def _parse_local_version(local: str) -> Optional[LocalType]:
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
@@ -493,14 +442,13 @@ def _parse_local_version(local):
def _cmpkey(
- epoch, # type: int
- release, # type: Tuple[int, ...]
- pre, # type: Optional[Tuple[str, int]]
- post, # type: Optional[Tuple[str, int]]
- dev, # type: Optional[Tuple[str, int]]
- local, # type: Optional[Tuple[SubLocalType]]
-):
- # type: (...) -> CmpKey
+ epoch: int,
+ release: Tuple[int, ...],
+ pre: Optional[Tuple[str, int]],
+ post: Optional[Tuple[str, int]],
+ dev: Optional[Tuple[str, int]],
+ local: Optional[Tuple[SubLocalType]],
+) -> CmpKey:
# When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now
@@ -516,7 +464,7 @@ def _cmpkey(
# if there is not a pre or a post segment. If we have one of those then
# the normal sorting rules will handle this case correctly.
if pre is None and post is None and dev is not None:
- _pre = NegativeInfinity # type: PrePostDevType
+ _pre: PrePostDevType = NegativeInfinity
# Versions without a pre-release (except as noted above) should sort after
# those with one.
elif pre is None:
@@ -526,21 +474,21 @@ def _cmpkey(
# Versions without a post segment should sort before those with one.
if post is None:
- _post = NegativeInfinity # type: PrePostDevType
+ _post: PrePostDevType = NegativeInfinity
else:
_post = post
# Versions without a development segment should sort after those with one.
if dev is None:
- _dev = Infinity # type: PrePostDevType
+ _dev: PrePostDevType = Infinity
else:
_dev = dev
if local is None:
# Versions without a local segment should sort before those with one.
- _local = NegativeInfinity # type: LocalType
+ _local: LocalType = NegativeInfinity
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
diff --git a/setup.cfg b/setup.cfg
index e69dd12..22b4de2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal = 1
-
[isort]
profile = black
combine_as_imports = true
diff --git a/setup.py b/setup.py
index fd85620..4705191 100644
--- a/setup.py
+++ b/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",
diff --git a/tests/__init__.py b/tests/__init__.py
index 0e11301..b509336 100644
--- a/tests/__init__.py
+++ b/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
diff --git a/tests/hello-world-armv7l-armel b/tests/manylinux/hello-world-armv7l-armel
index 1dfd23f..1dfd23f 100755
--- a/tests/hello-world-armv7l-armel
+++ b/tests/manylinux/hello-world-armv7l-armel
Binary files differ
diff --git a/tests/hello-world-armv7l-armhf b/tests/manylinux/hello-world-armv7l-armhf
index 965ab30..965ab30 100755
--- a/tests/hello-world-armv7l-armhf
+++ b/tests/manylinux/hello-world-armv7l-armhf
Binary files differ
diff --git a/tests/hello-world-invalid-class b/tests/manylinux/hello-world-invalid-class
index 5e9899f..5e9899f 100755
--- a/tests/hello-world-invalid-class
+++ b/tests/manylinux/hello-world-invalid-class
Binary files differ
diff --git a/tests/hello-world-invalid-data b/tests/manylinux/hello-world-invalid-data
index 2659b8e..2659b8e 100755
--- a/tests/hello-world-invalid-data
+++ b/tests/manylinux/hello-world-invalid-data
Binary files differ
diff --git a/tests/hello-world-invalid-magic b/tests/manylinux/hello-world-invalid-magic
index 46066ad..46066ad 100755
--- a/tests/hello-world-invalid-magic
+++ b/tests/manylinux/hello-world-invalid-magic
Binary files differ
diff --git a/tests/hello-world-s390x-s390x b/tests/manylinux/hello-world-s390x-s390x
index c4e9578..c4e9578 100644
--- a/tests/hello-world-s390x-s390x
+++ b/tests/manylinux/hello-world-s390x-s390x
Binary files differ
diff --git a/tests/hello-world-too-short b/tests/manylinux/hello-world-too-short
index 4e5c039..4e5c039 100644
--- a/tests/hello-world-too-short
+++ b/tests/manylinux/hello-world-too-short
Binary files differ
diff --git a/tests/hello-world-x86_64-amd64 b/tests/manylinux/hello-world-x86_64-amd64
index c7f5b0b..c7f5b0b 100644
--- a/tests/hello-world-x86_64-amd64
+++ b/tests/manylinux/hello-world-x86_64-amd64
Binary files differ
diff --git a/tests/hello-world-x86_64-i386 b/tests/manylinux/hello-world-x86_64-i386
index ff1d540..ff1d540 100755
--- a/tests/hello-world-x86_64-i386
+++ b/tests/manylinux/hello-world-x86_64-i386
Binary files differ
diff --git a/tests/hello-world-x86_64-x32 b/tests/manylinux/hello-world-x86_64-x32
index daf85d3..daf85d3 100755
--- a/tests/hello-world-x86_64-x32
+++ b/tests/manylinux/hello-world-x86_64-x32
Binary files differ
diff --git a/tests/musllinux/glibc-x86_64 b/tests/musllinux/glibc-x86_64
new file mode 100755
index 0000000..59996e2
--- /dev/null
+++ b/tests/musllinux/glibc-x86_64
Binary files differ
diff --git a/tests/musllinux/musl-aarch64 b/tests/musllinux/musl-aarch64
new file mode 100755
index 0000000..f6bcd38
--- /dev/null
+++ b/tests/musllinux/musl-aarch64
Binary files differ
diff --git a/tests/musllinux/musl-i386 b/tests/musllinux/musl-i386
new file mode 100755
index 0000000..2bbe495
--- /dev/null
+++ b/tests/musllinux/musl-i386
Binary files differ
diff --git a/tests/musllinux/musl-x86_64 b/tests/musllinux/musl-x86_64
new file mode 100755
index 0000000..d70261b
--- /dev/null
+++ b/tests/musllinux/musl-x86_64
Binary files differ
diff --git a/tests/test_manylinux.py b/tests/test_manylinux.py
new file mode 100644
index 0000000..a04db15
--- /dev/null
+++ b/tests/test_manylinux.py
@@ -0,0 +1,253 @@
+try:
+ import ctypes
+except ImportError:
+ ctypes = None
+import os
+import platform
+import sys
+import types
+import warnings
+
+import pretend
+import pytest
+
+from packaging import _manylinux
+from packaging._manylinux import (
+ _ELFFileHeader,
+ _get_elf_header,
+ _get_glibc_version,
+ _glibc_version_string,
+ _glibc_version_string_confstr,
+ _glibc_version_string_ctypes,
+ _is_compatible,
+ _is_linux_armhf,
+ _is_linux_i686,
+ _parse_glibc_version,
+)
+
+
+@pytest.fixture(autouse=True)
+def clear_lru_cache():
+ yield
+ _get_glibc_version.cache_clear()
+
+
+@pytest.fixture
+def manylinux_module(monkeypatch):
+ monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda *args: (2, 20))
+ module_name = "_manylinux"
+ module = types.ModuleType(module_name)
+ monkeypatch.setitem(sys.modules, module_name, module)
+ return module
+
+
+@pytest.mark.parametrize("tf", (True, False))
+@pytest.mark.parametrize(
+ "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
+)
+def test_module_declaration(monkeypatch, manylinux_module, attribute, glibc, tf):
+ manylinux = f"manylinux{attribute}_compatible"
+ monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False)
+ res = _is_compatible(manylinux, "x86_64", glibc)
+ assert tf is res
+
+
+@pytest.mark.parametrize(
+ "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
+)
+def test_module_declaration_missing_attribute(
+ monkeypatch, manylinux_module, attribute, glibc
+):
+ manylinux = f"manylinux{attribute}_compatible"
+ monkeypatch.delattr(manylinux_module, manylinux, raising=False)
+ assert _is_compatible(manylinux, "x86_64", glibc)
+
+
+@pytest.mark.parametrize(
+ "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False))
+)
+def test_is_manylinux_compatible_glibc_support(version, compatible, monkeypatch):
+ monkeypatch.setitem(sys.modules, "_manylinux", None)
+ monkeypatch.setattr(_manylinux, "_get_glibc_version", lambda: (2, 5))
+ assert bool(_is_compatible("manylinux1", "any", version)) == compatible
+
+
+@pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"])
+def test_check_glibc_version_warning(version_str):
+ with warnings.catch_warnings(record=True) as w:
+ _parse_glibc_version(version_str)
+ assert len(w) == 1
+ assert issubclass(w[0].category, RuntimeWarning)
+
+
+@pytest.mark.skipif(not ctypes, reason="requires ctypes")
+@pytest.mark.parametrize(
+ "version_str,expected",
+ [
+ # Be very explicit about bytes and Unicode for Python 2 testing.
+ (b"2.4", "2.4"),
+ ("2.4", "2.4"),
+ ],
+)
+def test_glibc_version_string(version_str, expected, monkeypatch):
+ class LibcVersion:
+ def __init__(self, version_str):
+ self.version_str = version_str
+
+ def __call__(self):
+ return version_str
+
+ class ProcessNamespace:
+ def __init__(self, libc_version):
+ self.gnu_get_libc_version = libc_version
+
+ process_namespace = ProcessNamespace(LibcVersion(version_str))
+ monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace)
+ monkeypatch.setattr(_manylinux, "_glibc_version_string_confstr", lambda: False)
+
+ assert _glibc_version_string() == expected
+
+ del process_namespace.gnu_get_libc_version
+ assert _glibc_version_string() is None
+
+
+def test_glibc_version_string_confstr(monkeypatch):
+ monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
+ assert _glibc_version_string_confstr() == "2.20"
+
+
+def test_glibc_version_string_fail(monkeypatch):
+ monkeypatch.setattr(os, "confstr", lambda x: None, raising=False)
+ monkeypatch.setitem(sys.modules, "ctypes", None)
+ assert _glibc_version_string() is None
+ assert _get_glibc_version() == (-1, -1)
+
+
+@pytest.mark.parametrize(
+ "failure",
+ [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"],
+)
+def test_glibc_version_string_confstr_fail(monkeypatch, failure):
+ monkeypatch.setattr(os, "confstr", failure, raising=False)
+ assert _glibc_version_string_confstr() is None
+
+
+def test_glibc_version_string_confstr_missing(monkeypatch):
+ monkeypatch.delattr(os, "confstr", raising=False)
+ assert _glibc_version_string_confstr() is None
+
+
+def test_glibc_version_string_ctypes_missing(monkeypatch):
+ monkeypatch.setitem(sys.modules, "ctypes", None)
+ assert _glibc_version_string_ctypes() is None
+
+
+def test_glibc_version_string_ctypes_raise_oserror(monkeypatch):
+ def patched_cdll(name):
+ raise OSError("Dynamic loading not supported")
+
+ monkeypatch.setattr(ctypes, "CDLL", patched_cdll)
+ assert _glibc_version_string_ctypes() is None
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux")
+def test_is_manylinux_compatible_old():
+ # Assuming no one is running this test with a version of glibc released in
+ # 1997.
+ assert _is_compatible("any", "any", (2, 0))
+
+
+def test_is_manylinux_compatible(monkeypatch):
+ monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: "2.4")
+ assert _is_compatible("", "any", (2, 4))
+
+
+def test_glibc_version_string_none(monkeypatch):
+ monkeypatch.setattr(_manylinux, "_glibc_version_string", lambda: None)
+ assert not _is_compatible("any", "any", (2, 4))
+
+
+def test_is_linux_armhf_not_elf(monkeypatch):
+ monkeypatch.setattr(_manylinux, "_get_elf_header", lambda: None)
+ assert not _is_linux_armhf()
+
+
+def test_is_linux_i686_not_elf(monkeypatch):
+ monkeypatch.setattr(_manylinux, "_get_elf_header", lambda: None)
+ assert not _is_linux_i686()
+
+
+@pytest.mark.parametrize(
+ "machine, abi, elf_class, elf_data, elf_machine",
+ [
+ (
+ "x86_64",
+ "x32",
+ _ELFFileHeader.ELFCLASS32,
+ _ELFFileHeader.ELFDATA2LSB,
+ _ELFFileHeader.EM_X86_64,
+ ),
+ (
+ "x86_64",
+ "i386",
+ _ELFFileHeader.ELFCLASS32,
+ _ELFFileHeader.ELFDATA2LSB,
+ _ELFFileHeader.EM_386,
+ ),
+ (
+ "x86_64",
+ "amd64",
+ _ELFFileHeader.ELFCLASS64,
+ _ELFFileHeader.ELFDATA2LSB,
+ _ELFFileHeader.EM_X86_64,
+ ),
+ (
+ "armv7l",
+ "armel",
+ _ELFFileHeader.ELFCLASS32,
+ _ELFFileHeader.ELFDATA2LSB,
+ _ELFFileHeader.EM_ARM,
+ ),
+ (
+ "armv7l",
+ "armhf",
+ _ELFFileHeader.ELFCLASS32,
+ _ELFFileHeader.ELFDATA2LSB,
+ _ELFFileHeader.EM_ARM,
+ ),
+ (
+ "s390x",
+ "s390x",
+ _ELFFileHeader.ELFCLASS64,
+ _ELFFileHeader.ELFDATA2MSB,
+ _ELFFileHeader.EM_S390,
+ ),
+ ],
+)
+def test_get_elf_header(monkeypatch, machine, abi, elf_class, elf_data, elf_machine):
+ path = os.path.join(
+ os.path.dirname(__file__),
+ "manylinux",
+ f"hello-world-{machine}-{abi}",
+ )
+ monkeypatch.setattr(sys, "executable", path)
+ elf_header = _get_elf_header()
+ assert elf_header.e_ident_class == elf_class
+ assert elf_header.e_ident_data == elf_data
+ assert elf_header.e_machine == elf_machine
+
+
+@pytest.mark.parametrize(
+ "content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
+)
+def test_get_elf_header_bad_executable(monkeypatch, content):
+ if content:
+ path = os.path.join(
+ os.path.dirname(__file__),
+ "manylinux",
+ f"hello-world-{content}",
+ )
+ else:
+ path = None
+ monkeypatch.setattr(sys, "executable", path)
+ assert _get_elf_header() is None
diff --git a/tests/test_markers.py b/tests/test_markers.py
index 3bdbffa..64573a9 100644
--- a/tests/test_markers.py
+++ b/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)
]
+ [
@@ -342,7 +269,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),
("sys.platform == 'win32'", {"sys_platform": "linux2"}, False),
("platform.version in 'Ubuntu'", {"platform_version": "#39"}, False),
("platform.machine=='x86_64'", {"platform_machine": "x86_64"}, True),
@@ -366,7 +293,7 @@ class TestMarker:
@pytest.mark.parametrize(
"marker_string",
[
- "{0} {1} {2!r}".format(*i)
+ "{} {} {!r}".format(*i)
for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES)
]
+ [
diff --git a/tests/test_musllinux.py b/tests/test_musllinux.py
new file mode 100644
index 0000000..d2c87ca
--- /dev/null
+++ b/tests/test_musllinux.py
@@ -0,0 +1,146 @@
+import collections
+import io
+import pathlib
+import struct
+import subprocess
+
+import pretend
+import pytest
+
+from packaging import _musllinux
+from packaging._musllinux import (
+ _get_musl_version,
+ _MuslVersion,
+ _parse_ld_musl_from_elf,
+ _parse_musl_version,
+)
+
+MUSL_AMD64 = "musl libc (x86_64)\nVersion 1.2.2\n"
+MUSL_I386 = "musl libc (i386)\nVersion 1.2.1\n"
+MUSL_AARCH64 = "musl libc (aarch64)\nVersion 1.1.24\n"
+MUSL_INVALID = "musl libc (invalid)\n"
+MUSL_UNKNOWN = "musl libc (unknown)\nVersion unknown\n"
+
+MUSL_DIR = pathlib.Path(__file__).with_name("musllinux").resolve()
+
+BIN_GLIBC_X86_64 = MUSL_DIR.joinpath("glibc-x86_64")
+BIN_MUSL_X86_64 = MUSL_DIR.joinpath("musl-x86_64")
+BIN_MUSL_I386 = MUSL_DIR.joinpath("musl-i386")
+BIN_MUSL_AARCH64 = MUSL_DIR.joinpath("musl-aarch64")
+
+LD_MUSL_X86_64 = "/lib/ld-musl-x86_64.so.1"
+LD_MUSL_I386 = "/lib/ld-musl-i386.so.1"
+LD_MUSL_AARCH64 = "/lib/ld-musl-aarch64.so.1"
+
+
+@pytest.fixture(autouse=True)
+def clear_lru_cache():
+ yield
+ _get_musl_version.cache_clear()
+
+
+@pytest.mark.parametrize(
+ "output, version",
+ [
+ (MUSL_AMD64, _MuslVersion(1, 2)),
+ (MUSL_I386, _MuslVersion(1, 2)),
+ (MUSL_AARCH64, _MuslVersion(1, 1)),
+ (MUSL_INVALID, None),
+ (MUSL_UNKNOWN, None),
+ ],
+ ids=["amd64-1.2.2", "i386-1.2.1", "aarch64-1.1.24", "invalid", "unknown"],
+)
+def test_parse_musl_version(output, version):
+ assert _parse_musl_version(output) == version
+
+
+@pytest.mark.parametrize(
+ "executable, location",
+ [
+ (BIN_GLIBC_X86_64, None),
+ (BIN_MUSL_X86_64, LD_MUSL_X86_64),
+ (BIN_MUSL_I386, LD_MUSL_I386),
+ (BIN_MUSL_AARCH64, LD_MUSL_AARCH64),
+ ],
+ ids=["glibc", "x86_64", "i386", "aarch64"],
+)
+def test_parse_ld_musl_from_elf(executable, location):
+ with executable.open("rb") as f:
+ assert _parse_ld_musl_from_elf(f) == location
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ # Too short for magic.
+ b"\0",
+ # Enough for magic, but not ELF.
+ b"#!/bin/bash" + b"\0" * 16,
+ # ELF, but unknown byte declaration.
+ b"\x7fELF\3" + b"\0" * 16,
+ ],
+ ids=["no-magic", "wrong-magic", "unknown-format"],
+)
+def test_parse_ld_musl_from_elf_invalid(data):
+ assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
+
+
+@pytest.mark.parametrize(
+ "head",
+ [
+ 25, # Enough for magic, but not the section definitions.
+ 58, # Enough for section definitions, but not the actual sections.
+ ],
+)
+def test_parse_ld_musl_from_elf_invalid_section(head):
+ data = BIN_MUSL_X86_64.read_bytes()[:head]
+ assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
+
+
+def test_parse_ld_musl_from_elf_no_interpreter_section():
+ with BIN_MUSL_X86_64.open("rb") as f:
+ data = f.read()
+
+ # Change all sections to *not* PT_INTERP.
+ unpacked = struct.unpack("16BHHIQQQIHHH", data[:58])
+ *_, e_phoff, _, _, _, e_phentsize, e_phnum = unpacked
+ for i in range(e_phnum + 1):
+ sb = e_phoff + e_phentsize * i
+ se = sb + 56
+ section = struct.unpack("IIQQQQQQ", data[sb:se])
+ data = data[:sb] + struct.pack("IIQQQQQQ", 0, *section[1:]) + data[se:]
+
+ assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
+
+
+@pytest.mark.parametrize(
+ "executable, output, version, ld_musl",
+ [
+ (MUSL_DIR.joinpath("does-not-exist"), "error", None, None),
+ (BIN_GLIBC_X86_64, "error", None, None),
+ (BIN_MUSL_X86_64, MUSL_AMD64, _MuslVersion(1, 2), LD_MUSL_X86_64),
+ (BIN_MUSL_I386, MUSL_I386, _MuslVersion(1, 2), LD_MUSL_I386),
+ (BIN_MUSL_AARCH64, MUSL_AARCH64, _MuslVersion(1, 1), LD_MUSL_AARCH64),
+ ],
+ ids=["does-not-exist", "glibc", "x86_64", "i386", "aarch64"],
+)
+def test_get_musl_version(monkeypatch, executable, output, version, ld_musl):
+ def mock_run(*args, **kwargs):
+ return collections.namedtuple("Proc", "stderr")(output)
+
+ run_recorder = pretend.call_recorder(mock_run)
+ monkeypatch.setattr(_musllinux.subprocess, "run", run_recorder)
+
+ assert _get_musl_version(str(executable)) == version
+
+ if ld_musl is not None:
+ expected_calls = [
+ pretend.call(
+ [ld_musl],
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ ]
+ else:
+ expected_calls = []
+ assert run_recorder.calls == expected_calls
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index 0213d6d..8cc4568 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.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 pytest
@@ -141,7 +140,7 @@ class TestRequirements:
marker = 'python_version < "2.7" and platform_version == "2"'
self._assert_requirement(req, "name", extras=["strange", "quux"], marker=marker)
- def test_multiple_comparsion_markers(self):
+ def test_multiple_comparison_markers(self):
req = Requirement("name; os_name=='a' and os_name=='b' or os_name=='c'")
marker = 'os_name == "a" and os_name == "b" or os_name == "c"'
self._assert_requirement(req, "name", marker=marker)
diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py
index 41e93fe..0b8e742 100644
--- a/tests/test_specifiers.py
+++ b/tests/test_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 itertools
import operator
@@ -228,7 +227,7 @@ class TestSpecifier:
spec = Specifier(specifier)
assert str(spec) == expected
- assert repr(spec) == "<Specifier({0})>".format(repr(expected))
+ assert repr(spec) == "<Specifier({!r})>".format(expected)
@pytest.mark.parametrize("specifier", SPECIFIERS)
def test_specifiers_hash(self, specifier):
@@ -360,6 +359,7 @@ class TestSpecifier:
("1.0.1", "~=1.0"),
("1.1", "~=1.0"),
("1.9999999", "~=1.0"),
+ ("1.1", "~=1.0a1"),
# Test that epochs are handled sanely
("2!1.0", "~=2!1.0"),
("2!1.0", "==2!1.*"),
@@ -627,7 +627,7 @@ class TestSpecifier:
)
def test_iteration(self, spec, expected_items):
spec = SpecifierSet(spec)
- items = set(str(item) for item in spec)
+ items = {str(item) for item in spec}
assert items == set(expected_items)
@@ -873,7 +873,7 @@ class TestSpecifierSet:
spec = SpecifierSet(specifier)
assert str(spec) == expected
- assert repr(spec) == "<SpecifierSet({0})>".format(repr(expected))
+ assert repr(spec) == "<SpecifierSet({!r})>".format(expected)
@pytest.mark.parametrize("specifier", SPECIFIERS + LEGACY_SPECIFIERS)
def test_specifiers_hash(self, specifier):
diff --git a/tests/test_structures.py b/tests/test_structures.py
index 33b1896..f8115e5 100644
--- a/tests/test_structures.py
+++ b/tests/test_structures.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 pytest
@@ -44,7 +43,7 @@ def test_negative_infinity_lesser(left):
assert left >= NegativeInfinity
-def test_infinty_equal():
+def test_infinity_equal():
assert Infinity == Infinity
diff --git a/tests/test_tags.py b/tests/test_tags.py
index 6b0e5b6..aa922f2 100644
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -2,28 +2,25 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
-try:
- import collections.abc as collections_abc
-except ImportError:
- import collections as collections_abc
+
+import collections.abc
try:
import ctypes
except ImportError:
ctypes = None
-import distutils.util
import os
+import pathlib
import platform
-import re
import sys
import sysconfig
import types
-import warnings
import pretend
import pytest
from packaging import tags
+from packaging._musllinux import _MuslVersion
@pytest.fixture
@@ -33,7 +30,7 @@ def example_tag():
@pytest.fixture
def manylinux_module(monkeypatch):
- monkeypatch.setattr(tags, "_get_glibc_version", lambda *args: (2, 20))
+ monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda *args: (2, 20))
module_name = "_manylinux"
module = types.ModuleType(module_name)
monkeypatch.setitem(sys.modules, module_name, module)
@@ -43,11 +40,9 @@ def manylinux_module(monkeypatch):
@pytest.fixture
def mock_interpreter_name(monkeypatch):
def mock(name):
- if hasattr(sys, "implementation") and sys.implementation.name != name.lower():
- monkeypatch.setattr(sys.implementation, "name", name.lower())
- return True
- elif platform.python_implementation() != name:
- monkeypatch.setattr(platform, "python_implementation", lambda: name)
+ name = name.lower()
+ if sys.implementation.name != name:
+ monkeypatch.setattr(sys.implementation, "name", name)
return True
return False
@@ -91,31 +86,6 @@ class TestTag:
assert example_tag.platform == "any"
-class TestWarnKeywordOnlyParameter:
- def test_no_argument(self):
- assert not tags._warn_keyword_parameter("test_warn_keyword_parameters", {})
-
- def test_false(self):
- assert not tags._warn_keyword_parameter(
- "test_warn_keyword_parameters", {"warn": False}
- )
-
- def test_true(self):
- assert tags._warn_keyword_parameter(
- "test_warn_keyword_parameters", {"warn": True}
- )
-
- def test_too_many_arguments(self):
- message_re = re.compile(r"too_many.+{!r}".format("whatever"))
- with pytest.raises(TypeError, match=message_re):
- tags._warn_keyword_parameter("too_many", {"warn": True, "whatever": True})
-
- def test_wrong_argument(self):
- message_re = re.compile(r"missing.+{!r}".format("unexpected"))
- with pytest.raises(TypeError, match=message_re):
- tags._warn_keyword_parameter("missing", {"unexpected": True})
-
-
class TestParseTag:
def test_simple(self, example_tag):
parsed_tags = tags.parse_tag(str(example_tag))
@@ -146,7 +116,7 @@ class TestParseTag:
class TestInterpreterName:
def test_sys_implementation_name(self, monkeypatch):
- class MockImplementation(object):
+ class MockImplementation:
pass
mock_implementation = MockImplementation()
@@ -154,12 +124,6 @@ class TestInterpreterName:
monkeypatch.setattr(sys, "implementation", mock_implementation, raising=False)
assert tags.interpreter_name() == "sillywalk"
- def test_platform(self, monkeypatch):
- monkeypatch.delattr(sys, "implementation", raising=False)
- name = "SillyWalk"
- monkeypatch.setattr(platform, "python_implementation", lambda: name)
- assert tags.interpreter_name() == name.lower()
-
def test_interpreter_short_names(self, mock_interpreter_name, monkeypatch):
mock_interpreter_name("cpython")
assert tags.interpreter_name() == "cp"
@@ -167,7 +131,7 @@ class TestInterpreterName:
class TestInterpreterVersion:
def test_warn(self, monkeypatch):
- class MockConfigVar(object):
+ class MockConfigVar:
def __init__(self, return_):
self.warn = None
self._return = return_
@@ -342,112 +306,7 @@ class TestMacOSPlatforms:
class TestManylinuxPlatform:
def teardown_method(self):
# Clear the version cache
- tags._glibc_version = []
-
- @pytest.mark.parametrize("tf", (True, False))
- @pytest.mark.parametrize(
- "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
- )
- def test_module_declaration(
- self, monkeypatch, manylinux_module, attribute, glibc, tf
- ):
- manylinux = "manylinux{}_compatible".format(attribute)
- monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False)
- res = tags._is_manylinux_compatible(manylinux, "x86_64", glibc)
- assert tf is res
-
- @pytest.mark.parametrize(
- "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17)))
- )
- def test_module_declaration_missing_attribute(
- self, monkeypatch, manylinux_module, attribute, glibc
- ):
- manylinux = "manylinux{}_compatible".format(attribute)
- monkeypatch.delattr(manylinux_module, manylinux, raising=False)
- assert tags._is_manylinux_compatible(manylinux, "x86_64", glibc)
-
- @pytest.mark.parametrize(
- "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False))
- )
- def test_is_manylinux_compatible_glibc_support(
- self, version, compatible, monkeypatch
- ):
- monkeypatch.setitem(sys.modules, "_manylinux", None)
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 5))
- assert (
- bool(tags._is_manylinux_compatible("manylinux1", "any", version))
- == compatible
- )
-
- @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"])
- def test_check_glibc_version_warning(self, version_str):
- with warnings.catch_warnings(record=True) as w:
- tags._parse_glibc_version(version_str)
- assert len(w) == 1
- assert issubclass(w[0].category, RuntimeWarning)
-
- @pytest.mark.skipif(not ctypes, reason="requires ctypes")
- @pytest.mark.parametrize(
- "version_str,expected",
- [
- # Be very explicit about bytes and Unicode for Python 2 testing.
- (b"2.4", "2.4"),
- (u"2.4", "2.4"),
- ],
- )
- def test_glibc_version_string(self, version_str, expected, monkeypatch):
- class LibcVersion:
- def __init__(self, version_str):
- self.version_str = version_str
-
- def __call__(self):
- return version_str
-
- class ProcessNamespace:
- def __init__(self, libc_version):
- self.gnu_get_libc_version = libc_version
-
- process_namespace = ProcessNamespace(LibcVersion(version_str))
- monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace)
- monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False)
-
- assert tags._glibc_version_string() == expected
-
- del process_namespace.gnu_get_libc_version
- assert tags._glibc_version_string() is None
-
- def test_glibc_version_string_confstr(self, monkeypatch):
- monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- assert tags._glibc_version_string_confstr() == "2.20"
-
- def test_glibc_version_string_fail(self, monkeypatch):
- monkeypatch.setattr(os, "confstr", lambda x: None, raising=False)
- monkeypatch.setitem(sys.modules, "ctypes", None)
- assert tags._glibc_version_string() is None
- assert tags._get_glibc_version() == (-1, -1)
-
- @pytest.mark.parametrize(
- "failure",
- [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"],
- )
- def test_glibc_version_string_confstr_fail(self, monkeypatch, failure):
- monkeypatch.setattr(os, "confstr", failure, raising=False)
- assert tags._glibc_version_string_confstr() is None
-
- def test_glibc_version_string_confstr_missing(self, monkeypatch):
- monkeypatch.delattr(os, "confstr", raising=False)
- assert tags._glibc_version_string_confstr() is None
-
- def test_glibc_version_string_ctypes_missing(self, monkeypatch):
- monkeypatch.setitem(sys.modules, "ctypes", None)
- assert tags._glibc_version_string_ctypes() is None
-
- def test_glibc_version_string_ctypes_raise_oserror(self, monkeypatch):
- def patched_cdll(name):
- raise OSError("Dynamic loading not supported")
-
- monkeypatch.setattr(ctypes, "CDLL", patched_cdll)
- assert tags._glibc_version_string_ctypes() is None
+ tags._manylinux._get_glibc_version.cache_clear()
def test_get_config_var_does_not_log(self, monkeypatch):
debug = pretend.call_recorder(lambda *a: None)
@@ -466,20 +325,6 @@ class TestManylinuxPlatform:
)
]
- @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux")
- def test_is_manylinux_compatible_old(self):
- # Assuming no one is running this test with a version of glibc released in
- # 1997.
- assert tags._is_manylinux_compatible("any", "any", (2, 0))
-
- def test_is_manylinux_compatible(self, monkeypatch):
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4")
- assert tags._is_manylinux_compatible("", "any", (2, 4))
-
- def test_glibc_version_string_none(self, monkeypatch):
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: None)
- assert not tags._is_manylinux_compatible("any", "any", (2, 4))
-
@pytest.mark.parametrize(
"arch,is_32bit,expected",
[
@@ -492,24 +337,24 @@ class TestManylinuxPlatform:
def test_linux_platforms_32_64bit_on_64bit_os(
self, arch, is_32bit, expected, monkeypatch
):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: arch)
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: arch)
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
+ monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False)
linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[-1]
assert linux_platform == expected
def test_linux_platforms_manylinux_unsupported(self, monkeypatch):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
+ monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda *args: False)
linux_platform = list(tags._linux_platforms(is_32bit=False))
assert linux_platform == ["linux_x86_64"]
def test_linux_platforms_manylinux1(self, monkeypatch):
monkeypatch.setattr(
- tags, "_is_manylinux_compatible", lambda name, *args: name == "manylinux1"
+ tags._manylinux, "_is_compatible", lambda name, *args: name == "manylinux1"
)
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
platforms = list(tags._linux_platforms(is_32bit=False))
@@ -517,7 +362,7 @@ class TestManylinuxPlatform:
assert platforms == ["manylinux1_" + arch, "linux_" + arch]
def test_linux_platforms_manylinux2010(self, monkeypatch):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.12", raising=False)
platforms = list(tags._linux_platforms(is_32bit=False))
@@ -538,7 +383,7 @@ class TestManylinuxPlatform:
assert platforms == expected
def test_linux_platforms_manylinux2014(self, monkeypatch):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.17", raising=False)
platforms = list(tags._linux_platforms(is_32bit=False))
@@ -565,29 +410,37 @@ class TestManylinuxPlatform:
assert platforms == expected
def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch):
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.30")
+ monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.30")
monkeypatch.setattr(
- tags,
- "_is_manylinux_compatible",
+ tags._manylinux,
+ "_is_compatible",
lambda name, *args: name == "manylinux2014",
)
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv7l")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv7l")
monkeypatch.setattr(
sys,
"executable",
- os.path.join(os.path.dirname(__file__), "hello-world-armv7l-armhf"),
+ os.path.join(
+ os.path.dirname(__file__),
+ "manylinux",
+ "hello-world-armv7l-armhf",
+ ),
)
platforms = list(tags._linux_platforms(is_32bit=True))
expected = ["manylinux2014_armv7l", "linux_armv7l"]
assert platforms == expected
def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch):
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.17")
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "2.17")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(
sys,
"executable",
- os.path.join(os.path.dirname(__file__), "hello-world-x86_64-i386"),
+ os.path.join(
+ os.path.dirname(__file__),
+ "manylinux",
+ "hello-world-x86_64-i386",
+ ),
)
platforms = list(tags._linux_platforms(is_32bit=True))
expected = [
@@ -613,27 +466,65 @@ class TestManylinuxPlatform:
def test_linux_platforms_manylinux_glibc3(self, monkeypatch):
# test for a future glic 3.x version
- monkeypatch.setattr(tags, "_glibc_version_string", lambda: "3.2")
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, *args: True)
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_aarch64")
+ monkeypatch.setattr(tags._manylinux, "_glibc_version_string", lambda: "3.2")
+ monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda name, *args: True)
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_aarch64")
monkeypatch.setattr(
sys,
"executable",
- os.path.join(os.path.dirname(__file__), "hello-world-aarch64"),
+ os.path.join(
+ os.path.dirname(__file__),
+ "manylinux",
+ "hello-world-aarch64",
+ ),
)
platforms = list(tags._linux_platforms(is_32bit=False))
expected = (
["manylinux_3_2_aarch64", "manylinux_3_1_aarch64", "manylinux_3_0_aarch64"]
- + ["manylinux_2_{}_aarch64".format(i) for i in range(50, 16, -1)]
+ + [f"manylinux_2_{i}_aarch64" for i in range(50, 16, -1)]
+ ["manylinux2014_aarch64", "linux_aarch64"]
)
assert platforms == expected
+ @pytest.mark.parametrize(
+ "native_arch, cross32_arch, musl_version",
+ [
+ ("aarch64", "armv7l", _MuslVersion(1, 1)),
+ ("i386", "i386", _MuslVersion(1, 2)),
+ ("x86_64", "i686", _MuslVersion(1, 2)),
+ ],
+ )
+ @pytest.mark.parametrize("cross32", [True, False], ids=["cross", "native"])
+ def test_linux_platforms_musllinux(
+ self, monkeypatch, native_arch, cross32_arch, musl_version, cross32
+ ):
+ fake_executable = str(
+ pathlib.Path(__file__)
+ .parent.joinpath("musllinux", f"musl-{native_arch}")
+ .resolve()
+ )
+ monkeypatch.setattr(tags._musllinux.sys, "executable", fake_executable)
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{native_arch}")
+ monkeypatch.setattr(tags._manylinux, "platform_tags", lambda *_: ())
+
+ recorder = pretend.call_recorder(lambda _: musl_version)
+ monkeypatch.setattr(tags._musllinux, "_get_musl_version", recorder)
+
+ platforms = list(tags._linux_platforms(is_32bit=cross32))
+ target_arch = cross32_arch if cross32 else native_arch
+ expected = [
+ f"musllinux_{musl_version[0]}_{minor}_{target_arch}"
+ for minor in range(musl_version[1], -1, -1)
+ ] + [f"linux_{target_arch}"]
+ assert platforms == expected
+
+ assert recorder.calls == [pretend.call(fake_executable)]
+
def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch):
monkeypatch.setattr(
- tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
+ tags._manylinux, "_is_compatible", lambda name, _: name == "manylinux2014"
)
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv6l")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
platforms = list(tags._linux_platforms(is_32bit=True))
expected = ["linux_armv6l"]
@@ -646,101 +537,21 @@ class TestManylinuxPlatform:
def test_linux_platforms_not_manylinux_abi(
self, monkeypatch, machine, abi, alt_machine
):
- monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: False)
- monkeypatch.setattr(
- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
- )
+ monkeypatch.setattr(tags._manylinux, "_is_compatible", lambda name, _: False)
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{machine}")
monkeypatch.setattr(
sys,
"executable",
os.path.join(
- os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
+ os.path.dirname(__file__),
+ "manylinux",
+ f"hello-world-{machine}-{abi}",
),
)
platforms = list(tags._linux_platforms(is_32bit=True))
- expected = ["linux_{}".format(alt_machine)]
+ expected = [f"linux_{alt_machine}"]
assert platforms == expected
- @pytest.mark.parametrize(
- "machine, abi, elf_class, elf_data, elf_machine",
- [
- (
- "x86_64",
- "x32",
- tags._ELFFileHeader.ELFCLASS32,
- tags._ELFFileHeader.ELFDATA2LSB,
- tags._ELFFileHeader.EM_X86_64,
- ),
- (
- "x86_64",
- "i386",
- tags._ELFFileHeader.ELFCLASS32,
- tags._ELFFileHeader.ELFDATA2LSB,
- tags._ELFFileHeader.EM_386,
- ),
- (
- "x86_64",
- "amd64",
- tags._ELFFileHeader.ELFCLASS64,
- tags._ELFFileHeader.ELFDATA2LSB,
- tags._ELFFileHeader.EM_X86_64,
- ),
- (
- "armv7l",
- "armel",
- tags._ELFFileHeader.ELFCLASS32,
- tags._ELFFileHeader.ELFDATA2LSB,
- tags._ELFFileHeader.EM_ARM,
- ),
- (
- "armv7l",
- "armhf",
- tags._ELFFileHeader.ELFCLASS32,
- tags._ELFFileHeader.ELFDATA2LSB,
- tags._ELFFileHeader.EM_ARM,
- ),
- (
- "s390x",
- "s390x",
- tags._ELFFileHeader.ELFCLASS64,
- tags._ELFFileHeader.ELFDATA2MSB,
- tags._ELFFileHeader.EM_S390,
- ),
- ],
- )
- def test_get_elf_header(
- self, monkeypatch, machine, abi, elf_class, elf_data, elf_machine
- ):
- path = os.path.join(
- os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
- )
- monkeypatch.setattr(sys, "executable", path)
- elf_header = tags._get_elf_header()
- assert elf_header.e_ident_class == elf_class
- assert elf_header.e_ident_data == elf_data
- assert elf_header.e_machine == elf_machine
-
- @pytest.mark.parametrize(
- "content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
- )
- def test_get_elf_header_bad_excutable(self, monkeypatch, content):
- if content:
- path = os.path.join(
- os.path.dirname(__file__), "hello-world-{}".format(content)
- )
- else:
- path = None
- monkeypatch.setattr(sys, "executable", path)
- assert tags._get_elf_header() is None
-
- def test_is_linux_armhf_not_elf(self, monkeypatch):
- monkeypatch.setattr(tags, "_get_elf_header", lambda: None)
- assert not tags._is_linux_armhf()
-
- def test_is_linux_i686_not_elf(self, monkeypatch):
- monkeypatch.setattr(tags, "_get_elf_header", lambda: None)
- assert not tags._is_linux_i686()
-
@pytest.mark.parametrize(
"platform_name,dispatch_func",
@@ -825,7 +636,7 @@ class TestCPythonTags:
result_iterator = tags.cpython_tags(
(3, 8), ["cp38d", "cp38"], ["plat1", "plat2"]
)
- assert isinstance(result_iterator, collections_abc.Iterator)
+ assert isinstance(result_iterator, collections.abc.Iterator)
def test_all_args(self):
result_iterator = tags.cpython_tags(
@@ -995,13 +806,13 @@ class TestGenericTags:
assert not list(tags._generic_abi())
def test_generic_platforms(self):
- platform = distutils.util.get_platform().replace("-", "_")
+ platform = sysconfig.get_platform().replace("-", "_")
platform = platform.replace(".", "_")
assert list(tags._generic_platforms()) == [platform]
def test_iterator_returned(self):
result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])
- assert isinstance(result_iterator, collections_abc.Iterator)
+ assert isinstance(result_iterator, collections.abc.Iterator)
def test_all_args(self):
result_iterator = tags.generic_tags("sillywalk33", ["abi"], ["plat1", "plat2"])
@@ -1204,7 +1015,7 @@ class TestSysTags:
assert tags.interpreter_name() == expected
def test_iterator(self):
- assert isinstance(tags.sys_tags(), collections_abc.Iterator)
+ assert isinstance(tags.sys_tags(), collections.abc.Iterator)
def test_mac_cpython(self, mock_interpreter_name, monkeypatch):
if mock_interpreter_name("CPython"):
@@ -1269,15 +1080,15 @@ class TestSysTags:
assert result[-1] == expected
def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch, manylinux_module):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_armv6l")
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
platforms = list(tags._linux_platforms(is_32bit=True))
expected = ["linux_armv6l"]
assert platforms == expected
def test_skip_manylinux_2014(self, monkeypatch, manylinux_module):
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_ppc64")
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 20))
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_ppc64")
+ monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 20))
monkeypatch.setattr(
manylinux_module, "manylinux2014_compatible", False, raising=False
)
@@ -1299,18 +1110,18 @@ class TestSysTags:
def test_linux_platforms_not_manylinux_abi(
self, monkeypatch, manylinux_module, machine, abi, alt_machine
):
- monkeypatch.setattr(
- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
- )
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{machine}")
monkeypatch.setattr(
sys,
"executable",
os.path.join(
- os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi)
+ os.path.dirname(__file__),
+ "manylinux",
+ f"hello-world-{machine}-{abi}",
),
)
platforms = list(tags._linux_platforms(is_32bit=True))
- expected = ["linux_{}".format(alt_machine)]
+ expected = [f"linux_{alt_machine}"]
assert platforms == expected
@pytest.mark.parametrize(
@@ -1324,10 +1135,12 @@ class TestSysTags:
return tag_arch == "s390x"
return False
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (major, minor))
monkeypatch.setattr(
- distutils.util, "get_platform", lambda: "linux_{}".format(machine)
+ tags._manylinux,
+ "_get_glibc_version",
+ lambda: (major, minor),
)
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: f"linux_{machine}")
monkeypatch.setattr(
manylinux_module,
"manylinux_compatible",
@@ -1336,10 +1149,10 @@ class TestSysTags:
)
platforms = list(tags._linux_platforms(is_32bit=False))
if tf:
- expected = ["manylinux_2_22_{}".format(machine)]
+ expected = [f"manylinux_2_22_{machine}"]
else:
expected = []
- expected.append("linux_{}".format(machine))
+ expected.append(f"linux_{machine}")
assert platforms == expected
def test_linux_use_manylinux_compatible_none(self, monkeypatch, manylinux_module):
@@ -1348,8 +1161,8 @@ class TestSysTags:
return False
return None
- monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 30))
- monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
+ monkeypatch.setattr(tags._manylinux, "_get_glibc_version", lambda: (2, 30))
+ monkeypatch.setattr(sysconfig, "get_platform", lambda: "linux_x86_64")
monkeypatch.setattr(
manylinux_module,
"manylinux_compatible",
diff --git a/tests/test_utils.py b/tests/test_utils.py
index d2a7c91..be52d67 100644
--- a/tests/test_utils.py
+++ b/tests/test_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 pytest
@@ -68,6 +67,13 @@ def test_canonicalize_version(version, expected):
{Tag("py3", "none", "any")},
),
(
+ "some_PACKAGE-1.0-py3-none-any.whl",
+ "some-package",
+ Version("1.0"),
+ (),
+ {Tag("py3", "none", "any")},
+ ),
+ (
"foo-1.0-1000-py3-none-any.whl",
"foo",
Version("1.0"),
@@ -105,13 +111,14 @@ def test_parse_wheel_invalid_filename(filename):
@pytest.mark.parametrize(
- ("filename", "name", "version"), [("foo-1.0.tar.gz", "foo", Version("1.0"))]
+ ("filename", "name", "version"),
+ [("foo-1.0.tar.gz", "foo", Version("1.0")), ("foo-1.0.zip", "foo", Version("1.0"))],
)
def test_parse_sdist_filename(filename, name, version):
assert parse_sdist_filename(filename) == (name, version)
-@pytest.mark.parametrize(("filename"), [("foo-1.0.zip"), ("foo1.0.tar.gz")])
+@pytest.mark.parametrize(("filename"), [("foo-1.0.xz"), ("foo1.0.tar.gz")])
def test_parse_sdist_invalid_filename(filename):
with pytest.raises(InvalidSdistFilename):
parse_sdist_filename(filename)
diff --git a/tests/test_version.py b/tests/test_version.py
index 3d72281..8fe5632 100644
--- a/tests/test_version.py
+++ b/tests/test_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 itertools
import operator
@@ -278,7 +277,7 @@ class TestVersion:
)
def test_version_str_repr(self, version, expected):
assert str(Version(version)) == expected
- assert repr(Version(version)) == "<Version({0})>".format(repr(expected))
+ assert repr(Version(version)) == "<Version({!r})>".format(expected)
def test_version_rc_and_c_equals(self):
assert Version("1.0rc1") == Version("1.0c1")
@@ -751,12 +750,12 @@ class TestVersion:
@pytest.mark.parametrize("op", ["lt", "le", "eq", "ge", "gt", "ne"])
def test_dunder_op_returns_notimplemented(self, op):
- method = getattr(Version, "__{0}__".format(op))
+ method = getattr(Version, f"__{op}__")
assert method(Version("1"), 1) is NotImplemented
@pytest.mark.parametrize(("op", "expected"), [("eq", False), ("ne", True)])
def test_compare_other(self, op, expected):
- other = pretend.stub(**{"__{0}__".format(op): lambda other: NotImplemented})
+ other = pretend.stub(**{f"__{op}__": lambda other: NotImplemented})
assert getattr(operator, op)(Version("1"), other) is expected
@@ -794,7 +793,7 @@ class TestLegacyVersion:
@pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS)
def test_legacy_version_str_repr(self, version):
assert str(LegacyVersion(version)) == version
- assert repr(LegacyVersion(version)) == "<LegacyVersion({0})>".format(
+ assert repr(LegacyVersion(version)) == "<LegacyVersion({})>".format(
repr(version)
)
@@ -895,11 +894,11 @@ class TestLegacyVersion:
@pytest.mark.parametrize("op", ["lt", "le", "eq", "ge", "gt", "ne"])
def test_dunder_op_returns_notimplemented(self, op):
- method = getattr(LegacyVersion, "__{0}__".format(op))
+ method = getattr(LegacyVersion, f"__{op}__")
assert method(LegacyVersion("1"), 1) is NotImplemented
@pytest.mark.parametrize(("op", "expected"), [("eq", False), ("ne", True)])
def test_compare_other(self, op, expected):
- other = pretend.stub(**{"__{0}__".format(op): lambda other: NotImplemented})
+ other = pretend.stub(**{f"__{op}__": lambda other: NotImplemented})
assert getattr(operator, op)(LegacyVersion("1"), other) is expected