diff options
| author | Matthias Klose <doko@debian.org> | 2021-10-11 14:28:40 +0200 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2021-10-11 16:38:36 +0000 |
| commit | 3ff6fce1972b87cc992bccba5a42f68ace3298d9 (patch) | |
| tree | d2a7fb7b83ab1f8d18950cc6b092c049e42869fa | |
| parent | bfea0c3a68a68cd435cb06cb300bc11c7557296b (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.yaml | 14 | ||||
| -rw-r--r-- | CHANGELOG.rst | 12 | ||||
| -rw-r--r-- | MANIFEST.in | 7 | ||||
| -rw-r--r-- | PKG-INFO | 798 | ||||
| -rw-r--r-- | debian/changelog | 7 | ||||
| -rw-r--r-- | debian/control | 2 | ||||
| -rw-r--r-- | debian/patches/get_platform.diff | 181 | ||||
| -rw-r--r-- | debian/patches/series | 1 | ||||
| -rw-r--r-- | docs/conf.py | 2 | ||||
| -rw-r--r-- | docs/development/getting-started.rst | 5 | ||||
| -rw-r--r-- | docs/development/release-process.rst | 4 | ||||
| -rw-r--r-- | docs/development/submitting-patches.rst | 7 | ||||
| -rw-r--r-- | docs/specifiers.rst | 2 | ||||
| -rw-r--r-- | mypy.ini | 5 | ||||
| -rw-r--r-- | packaging.egg-info/PKG-INFO | 798 | ||||
| -rw-r--r-- | packaging.egg-info/SOURCES.txt | 32 | ||||
| -rw-r--r-- | packaging/__about__.py | 3 | ||||
| -rw-r--r-- | packaging/__init__.py | 1 | ||||
| -rw-r--r-- | packaging/_compat.py | 38 | ||||
| -rw-r--r-- | packaging/_manylinux.py | 301 | ||||
| -rw-r--r-- | packaging/_musllinux.py | 136 | ||||
| -rw-r--r-- | packaging/_structures.py | 59 | ||||
| -rw-r--r-- | packaging/_typing.py | 48 | ||||
| -rw-r--r-- | packaging/markers.py | 118 | ||||
| -rw-r--r-- | packaging/requirements.py | 60 | ||||
| -rw-r--r-- | packaging/specifiers.py | 296 | ||||
| -rw-r--r-- | packaging/tags.py | 544 | ||||
| -rw-r--r-- | packaging/utils.py | 76 | ||||
| -rw-r--r-- | packaging/version.py | 236 | ||||
| -rw-r--r-- | setup.cfg | 3 | ||||
| -rw-r--r-- | setup.py | 8 | ||||
| -rw-r--r-- | tests/__init__.py | 1 | ||||
| -rwxr-xr-x | tests/manylinux/hello-world-armv7l-armel (renamed from tests/hello-world-armv7l-armel) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-armv7l-armhf (renamed from tests/hello-world-armv7l-armhf) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-invalid-class (renamed from tests/hello-world-invalid-class) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-invalid-data (renamed from tests/hello-world-invalid-data) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-invalid-magic (renamed from tests/hello-world-invalid-magic) | bin | 52 -> 52 bytes | |||
| -rw-r--r-- | tests/manylinux/hello-world-s390x-s390x (renamed from tests/hello-world-s390x-s390x) | bin | 64 -> 64 bytes | |||
| -rw-r--r-- | tests/manylinux/hello-world-too-short (renamed from tests/hello-world-too-short) | bin | 40 -> 40 bytes | |||
| -rw-r--r-- | tests/manylinux/hello-world-x86_64-amd64 (renamed from tests/hello-world-x86_64-amd64) | bin | 64 -> 64 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-x86_64-i386 (renamed from tests/hello-world-x86_64-i386) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/manylinux/hello-world-x86_64-x32 (renamed from tests/hello-world-x86_64-x32) | bin | 52 -> 52 bytes | |||
| -rwxr-xr-x | tests/musllinux/glibc-x86_64 | bin | 0 -> 1024 bytes | |||
| -rwxr-xr-x | tests/musllinux/musl-aarch64 | bin | 0 -> 1024 bytes | |||
| -rwxr-xr-x | tests/musllinux/musl-i386 | bin | 0 -> 1024 bytes | |||
| -rwxr-xr-x | tests/musllinux/musl-x86_64 | bin | 0 -> 1024 bytes | |||
| -rw-r--r-- | tests/test_manylinux.py | 253 | ||||
| -rw-r--r-- | tests/test_markers.py | 87 | ||||
| -rw-r--r-- | tests/test_musllinux.py | 146 | ||||
| -rw-r--r-- | tests/test_requirements.py | 3 | ||||
| -rw-r--r-- | tests/test_specifiers.py | 8 | ||||
| -rw-r--r-- | tests/test_structures.py | 3 | ||||
| -rw-r--r-- | tests/test_tags.py | 399 | ||||
| -rw-r--r-- | tests/test_utils.py | 13 | ||||
| -rw-r--r-- | tests/test_version.py | 13 |
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 @@ -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 @@ -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. @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [isort] profile = black combine_as_imports = true @@ -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 Binary files differindex 1dfd23f..1dfd23f 100755 --- a/tests/hello-world-armv7l-armel +++ b/tests/manylinux/hello-world-armv7l-armel diff --git a/tests/hello-world-armv7l-armhf b/tests/manylinux/hello-world-armv7l-armhf Binary files differindex 965ab30..965ab30 100755 --- a/tests/hello-world-armv7l-armhf +++ b/tests/manylinux/hello-world-armv7l-armhf diff --git a/tests/hello-world-invalid-class b/tests/manylinux/hello-world-invalid-class Binary files differindex 5e9899f..5e9899f 100755 --- a/tests/hello-world-invalid-class +++ b/tests/manylinux/hello-world-invalid-class diff --git a/tests/hello-world-invalid-data b/tests/manylinux/hello-world-invalid-data Binary files differindex 2659b8e..2659b8e 100755 --- a/tests/hello-world-invalid-data +++ b/tests/manylinux/hello-world-invalid-data diff --git a/tests/hello-world-invalid-magic b/tests/manylinux/hello-world-invalid-magic Binary files differindex 46066ad..46066ad 100755 --- a/tests/hello-world-invalid-magic +++ b/tests/manylinux/hello-world-invalid-magic diff --git a/tests/hello-world-s390x-s390x b/tests/manylinux/hello-world-s390x-s390x Binary files differindex c4e9578..c4e9578 100644 --- a/tests/hello-world-s390x-s390x +++ b/tests/manylinux/hello-world-s390x-s390x diff --git a/tests/hello-world-too-short b/tests/manylinux/hello-world-too-short Binary files differindex 4e5c039..4e5c039 100644 --- a/tests/hello-world-too-short +++ b/tests/manylinux/hello-world-too-short diff --git a/tests/hello-world-x86_64-amd64 b/tests/manylinux/hello-world-x86_64-amd64 Binary files differindex c7f5b0b..c7f5b0b 100644 --- a/tests/hello-world-x86_64-amd64 +++ b/tests/manylinux/hello-world-x86_64-amd64 diff --git a/tests/hello-world-x86_64-i386 b/tests/manylinux/hello-world-x86_64-i386 Binary files differindex ff1d540..ff1d540 100755 --- a/tests/hello-world-x86_64-i386 +++ b/tests/manylinux/hello-world-x86_64-i386 diff --git a/tests/hello-world-x86_64-x32 b/tests/manylinux/hello-world-x86_64-x32 Binary files differindex daf85d3..daf85d3 100755 --- a/tests/hello-world-x86_64-x32 +++ b/tests/manylinux/hello-world-x86_64-x32 diff --git a/tests/musllinux/glibc-x86_64 b/tests/musllinux/glibc-x86_64 Binary files differnew file mode 100755 index 0000000..59996e2 --- /dev/null +++ b/tests/musllinux/glibc-x86_64 diff --git a/tests/musllinux/musl-aarch64 b/tests/musllinux/musl-aarch64 Binary files differnew file mode 100755 index 0000000..f6bcd38 --- /dev/null +++ b/tests/musllinux/musl-aarch64 diff --git a/tests/musllinux/musl-i386 b/tests/musllinux/musl-i386 Binary files differnew file mode 100755 index 0000000..2bbe495 --- /dev/null +++ b/tests/musllinux/musl-i386 diff --git a/tests/musllinux/musl-x86_64 b/tests/musllinux/musl-x86_64 Binary files differnew file mode 100755 index 0000000..d70261b --- /dev/null +++ b/tests/musllinux/musl-x86_64 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 |
