summaryrefslogtreecommitdiff
diff options
-rw-r--r--.flake83
-rw-r--r--CHANGELOG.rst14
-rw-r--r--MANIFEST.in1
-rw-r--r--PKG-INFO17
-rw-r--r--debian/changelog6
-rw-r--r--debian/control2
-rw-r--r--docs/conf.py33
-rw-r--r--docs/development/getting-started.rst8
-rw-r--r--docs/development/index.rst1
-rw-r--r--docs/development/release-process.rst26
-rw-r--r--docs/development/submitting-patches.rst9
-rw-r--r--docs/markers.rst2
-rw-r--r--packaging.egg-info/PKG-INFO17
-rw-r--r--packaging.egg-info/SOURCES.txt2
-rw-r--r--packaging/__about__.py14
-rw-r--r--packaging/__init__.py20
-rw-r--r--packaging/_compat.py7
-rw-r--r--packaging/_structures.py2
-rw-r--r--packaging/markers.py89
-rw-r--r--packaging/requirements.py34
-rw-r--r--packaging/specifiers.py67
-rw-r--r--packaging/utils.py8
-rw-r--r--packaging/version.py49
-rw-r--r--setup.py20
-rw-r--r--tests/test_markers.py130
-rw-r--r--tests/test_requirements.py66
-rw-r--r--tests/test_specifiers.py239
-rw-r--r--tests/test_utils.py24
-rw-r--r--tests/test_version.py198
-rw-r--r--tox.ini27
30 files changed, 558 insertions, 577 deletions
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..b5a35be
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 88
+ignore = E203,W503,W504
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0fe202a..dcdf54c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,20 @@
Changelog
---------
+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
~~~~~~~~~~~~~~~~~
diff --git a/MANIFEST.in b/MANIFEST.in
index c713aff..36fcbd8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,6 +2,7 @@ include CHANGELOG.rst CONTRIBUTING.rst README.rst
include LICENSE LICENSE.APACHE LICENSE.BSD
include .coveragerc
+include .flake8
include tox.ini
recursive-include docs *
diff --git a/PKG-INFO b/PKG-INFO
index 84ec8c2..aa89b89 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: packaging
-Version: 18.0
+Version: 19.0
Summary: Core utilities for Python packages
Home-page: https://github.com/pypa/packaging
Author: Donald Stufft and individual contributors
@@ -41,6 +41,20 @@ Description: packaging
Changelog
---------
+ 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
~~~~~~~~~~~~~~~~~
@@ -247,4 +261,5 @@ Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
diff --git a/debian/changelog b/debian/changelog
index 82689e1..7e17109 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-packaging (19.0-1) unstable; urgency=medium
+
+ * New upstream version.
+
+ -- Matthias Klose <doko@debian.org> Fri, 25 Jan 2019 13:11:43 +0100
+
python-packaging (18.0-1) unstable; urgency=medium
* New upstream version.
diff --git a/debian/control b/debian/control
index ea87465..97b98d2 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 9),
python-six, python3-six, pypy-six,
python-pytest, python3-pytest,
python-setuptools, python3-setuptools, pypy-setuptools,
-Standards-Version: 4.2.1
+Standards-Version: 4.3.0
Homepage: https://pypi.python.org/pypi/packaging
Package: python-packaging
diff --git a/docs/conf.py b/docs/conf.py
index cf1d01e..48f2354 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -40,7 +40,6 @@ master_doc = "index"
# General information about the project.
project = "Packaging"
-copyright = "2014 Donald Stufft"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -53,6 +52,7 @@ with open(os.path.join(base_dir, "packaging", "__about__.py")) as f:
exec(f.read(), about)
version = release = about["__version__"]
+copyright = about["__copyright__"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -62,8 +62,8 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx"
extlinks = {
- 'issue': ('https://github.com/pypa/packaging/issues/%s', '#'),
- 'pull': ('https://github.com/pypa/packaging/pull/%s', 'PR #'),
+ "issue": ("https://github.com/pypa/packaging/issues/%s", "#"),
+ "pull": ("https://github.com/pypa/packaging/pull/%s", "PR #"),
}
# -- Options for HTML output --------------------------------------------------
@@ -87,34 +87,19 @@ htmlhelp_basename = "packagingdoc"
# -- Options for LaTeX output -------------------------------------------------
-latex_elements = {
-}
+latex_elements = {}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual])
latex_documents = [
- (
- "index",
- "packaging.tex",
- "Packaging Documentation",
- "Donald Stufft",
- "manual",
- ),
+ ("index", "packaging.tex", "Packaging Documentation", "Donald Stufft", "manual")
]
# -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (
- "index",
- "packaging",
- "Packaging Documentation",
- ["Donald Stufft"],
- 1,
- )
-]
+man_pages = [("index", "packaging", "Packaging Documentation", ["Donald Stufft"], 1)]
# -- Options for Texinfo output -----------------------------------------------
@@ -130,12 +115,10 @@ texinfo_documents = [
"packaging",
"Core utilities for Python packages",
"Miscellaneous",
- ),
+ )
]
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {
- "https://docs.python.org/": None,
-}
+intersphinx_mapping = {"https://docs.python.org/": None}
epub_theme = "epub"
diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst
index 7e6b45d..aa47fe6 100644
--- a/docs/development/getting-started.rst
+++ b/docs/development/getting-started.rst
@@ -9,8 +9,8 @@ dependencies, install packaging in ``editable`` mode. For example:
.. code-block:: console
$ # Create a virtualenv and activate it
- $ pip install --requirement dev-requirements.txt
- $ pip install --editable .
+ $ python -m pip install --requirement dev-requirements.txt
+ $ python -m pip install --editable .
You are now ready to run the tests and build the documentation.
@@ -23,7 +23,7 @@ automatically, so all you have to do is:
.. code-block:: console
- $ py.test
+ $ python -m pytest
...
62746 passed in 220.43 seconds
@@ -73,4 +73,4 @@ The HTML documentation index can now be found at
.. _`virtualenv`: https://pypi.org/project/virtualenv/
.. _`pip`: https://pypi.org/project/pip/
.. _`sphinx`: https://pypi.org/project/Sphinx/
-.. _`reStructured Text`: http://sphinx-doc.org/rest.html \ No newline at end of file
+.. _`reStructured Text`: http://sphinx-doc.org/rest.html
diff --git a/docs/development/index.rst b/docs/development/index.rst
index f81799a..c0aea8a 100644
--- a/docs/development/index.rst
+++ b/docs/development/index.rst
@@ -13,6 +13,7 @@ bug check out `what to put in your bug report`_.
getting-started
submitting-patches
reviewing-patches
+ release-process
.. _`GitHub`: https://github.com/pypa/packaging
.. _`what to put in your bug report`: http://www.contribution-guide.org/#what-to-put-in-your-bug-report
diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst
new file mode 100644
index 0000000..95375b2
--- /dev/null
+++ b/docs/development/release-process.rst
@@ -0,0 +1,26 @@
+Release Process
+===============
+
+#. Checkout the current ``master`` branch, with a clean working directory.
+#. Modify the ``CHANGELOG.rst`` to include changes made since the last release.
+#. Bump the version in ``packaging/__about__.py``
+
+#. Install the latest ``setuptools``, ``wheel`` and ``twine`` packages
+ from PyPI::
+
+ $ pip install --upgrade setuptools wheel twine
+
+#. Ensure no ``dist/`` folder exists and then create the distribution files::
+
+ $ python setup.py sdist bdist_wheel
+
+#. Check the built distribution files with ``twine``::
+
+ $ twine check dist/*
+
+#. If all goes well, upload the build distribution files::
+
+ $ twine upload dist/*
+
+#. Bump the version for development in ``packaging/__about__.py`` and
+ ``CHANGELOG.rst``.
diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst
index a2edde7..875b790 100644
--- a/docs/development/submitting-patches.rst
+++ b/docs/development/submitting-patches.rst
@@ -18,10 +18,9 @@ follow the directions on the :doc:`security page </security>`.
Code
----
-When in doubt, refer to :pep:`8` for Python code. You can check if your code
-meets our automated requirements by running ``flake8`` against it. If you've
-installed the development requirements this will automatically use our
-configuration. You can also run the ``tox`` job with ``tox -e pep8``.
+This project's source is auto-formatted with |black|. You can check if your
+code meets our requirements by running our linters against it with ``tox -e
+lint``.
`Write comments as complete sentences.`_
@@ -75,6 +74,8 @@ So, specifically:
* Use Sphinx parameter/attribute documentation `syntax`_.
+.. |black| replace:: ``black``
+.. _black: https://pypi.org/project/black/
.. _`Write comments as complete sentences.`: https://nedbatchelder.com/blog/201401/comments_should_be_sentences.html
.. _`syntax`: http://sphinx-doc.org/domains.html#info-field-lists
.. _`Studies have shown`: http://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/
diff --git a/docs/markers.rst b/docs/markers.rst
index 7613dae..ad25361 100644
--- a/docs/markers.rst
+++ b/docs/markers.rst
@@ -16,7 +16,7 @@ Usage
>>> marker = Marker("python_version>'2'")
>>> marker
<Marker('python_version > "2"')>
- >>> # We can evaluate a marker to see if the dependency is required
+ >>> # We can evaluate the marker to see if it is satisfied
>>> marker.evaluate()
True
>>> # We can also override the environment
diff --git a/packaging.egg-info/PKG-INFO b/packaging.egg-info/PKG-INFO
index 84ec8c2..aa89b89 100644
--- a/packaging.egg-info/PKG-INFO
+++ b/packaging.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: packaging
-Version: 18.0
+Version: 19.0
Summary: Core utilities for Python packages
Home-page: https://github.com/pypa/packaging
Author: Donald Stufft and individual contributors
@@ -41,6 +41,20 @@ Description: packaging
Changelog
---------
+ 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
~~~~~~~~~~~~~~~~~
@@ -247,4 +261,5 @@ Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
diff --git a/packaging.egg-info/SOURCES.txt b/packaging.egg-info/SOURCES.txt
index cda2869..cdf2c43 100644
--- a/packaging.egg-info/SOURCES.txt
+++ b/packaging.egg-info/SOURCES.txt
@@ -1,4 +1,5 @@
.coveragerc
+.flake8
CHANGELOG.rst
CONTRIBUTING.rst
LICENSE
@@ -22,6 +23,7 @@ docs/version.rst
docs/_static/.empty
docs/development/getting-started.rst
docs/development/index.rst
+docs/development/release-process.rst
docs/development/reviewing-patches.rst
docs/development/submitting-patches.rst
packaging/__about__.py
diff --git a/packaging/__about__.py b/packaging/__about__.py
index 21fc6ce..7481c9e 100644
--- a/packaging/__about__.py
+++ b/packaging/__about__.py
@@ -4,18 +4,24 @@
from __future__ import absolute_import, division, print_function
__all__ = [
- "__title__", "__summary__", "__uri__", "__version__", "__author__",
- "__email__", "__license__", "__copyright__",
+ "__title__",
+ "__summary__",
+ "__uri__",
+ "__version__",
+ "__author__",
+ "__email__",
+ "__license__",
+ "__copyright__",
]
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
-__version__ = "18.0"
+__version__ = "19.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
-__copyright__ = "Copyright 2014-2018 %s" % __author__
+__copyright__ = "Copyright 2014-2019 %s" % __author__
diff --git a/packaging/__init__.py b/packaging/__init__.py
index 5ee6220..a0cf67d 100644
--- a/packaging/__init__.py
+++ b/packaging/__init__.py
@@ -4,11 +4,23 @@
from __future__ import absolute_import, division, print_function
from .__about__ import (
- __author__, __copyright__, __email__, __license__, __summary__, __title__,
- __uri__, __version__
+ __author__,
+ __copyright__,
+ __email__,
+ __license__,
+ __summary__,
+ __title__,
+ __uri__,
+ __version__,
)
__all__ = [
- "__title__", "__summary__", "__uri__", "__version__", "__author__",
- "__email__", "__license__", "__copyright__",
+ "__title__",
+ "__summary__",
+ "__uri__",
+ "__version__",
+ "__author__",
+ "__email__",
+ "__license__",
+ "__copyright__",
]
diff --git a/packaging/_compat.py b/packaging/_compat.py
index 210bb80..25da473 100644
--- a/packaging/_compat.py
+++ b/packaging/_compat.py
@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
- string_types = str,
+ string_types = (str,)
else:
- string_types = basestring,
+ string_types = (basestring,)
def with_metaclass(meta, *bases):
@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
- return type.__new__(metaclass, 'temporary_class', (), {})
+
+ return type.__new__(metaclass, "temporary_class", (), {})
diff --git a/packaging/_structures.py b/packaging/_structures.py
index e9fc4a0..68dcca6 100644
--- a/packaging/_structures.py
+++ b/packaging/_structures.py
@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function
class Infinity(object):
-
def __repr__(self):
return "Infinity"
@@ -38,7 +37,6 @@ Infinity = Infinity()
class NegativeInfinity(object):
-
def __repr__(self):
return "-Infinity"
diff --git a/packaging/markers.py b/packaging/markers.py
index 5fdf510..eff5abb 100644
--- a/packaging/markers.py
+++ b/packaging/markers.py
@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier
__all__ = [
- "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
- "Marker", "default_environment",
+ "InvalidMarker",
+ "UndefinedComparison",
+ "UndefinedEnvironmentName",
+ "Marker",
+ "default_environment",
]
@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError):
class Node(object):
-
def __init__(self, value):
self.value = value
@@ -57,62 +59,52 @@ class Node(object):
class Variable(Node):
-
def serialize(self):
return str(self)
class Value(Node):
-
def serialize(self):
return '"{0}"'.format(self)
class Op(Node):
-
def serialize(self):
return str(self)
VARIABLE = (
- L("implementation_version") |
- L("platform_python_implementation") |
- L("implementation_name") |
- L("python_full_version") |
- L("platform_release") |
- L("platform_version") |
- L("platform_machine") |
- L("platform_system") |
- L("python_version") |
- L("sys_platform") |
- L("os_name") |
- L("os.name") | # PEP-345
- L("sys.platform") | # PEP-345
- L("platform.version") | # PEP-345
- L("platform.machine") | # PEP-345
- L("platform.python_implementation") | # PEP-345
- L("python_implementation") | # undocumented setuptools legacy
- L("extra")
+ L("implementation_version")
+ | L("platform_python_implementation")
+ | L("implementation_name")
+ | L("python_full_version")
+ | L("platform_release")
+ | L("platform_version")
+ | L("platform_machine")
+ | L("platform_system")
+ | L("python_version")
+ | L("sys_platform")
+ | L("os_name")
+ | L("os.name")
+ | L("sys.platform") # PEP-345
+ | L("platform.version") # PEP-345
+ | L("platform.machine") # PEP-345
+ | L("platform.python_implementation") # PEP-345
+ | L("python_implementation") # PEP-345
+ | L("extra") # undocumented setuptools legacy
)
ALIASES = {
- 'os.name': 'os_name',
- 'sys.platform': 'sys_platform',
- 'platform.version': 'platform_version',
- 'platform.machine': 'platform_machine',
- 'platform.python_implementation': 'platform_python_implementation',
- 'python_implementation': 'platform_python_implementation'
+ "os.name": "os_name",
+ "sys.platform": "sys_platform",
+ "platform.version": "platform_version",
+ "platform.machine": "platform_machine",
+ "platform.python_implementation": "platform_python_implementation",
+ "python_implementation": "platform_python_implementation",
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
- L("===") |
- L("==") |
- L(">=") |
- L("<=") |
- L("!=") |
- L("~=") |
- L(">") |
- L("<")
+ L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
@@ -152,8 +144,11 @@ def _format_marker(marker, first=True):
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
- if (isinstance(marker, list) and len(marker) == 1 and
- isinstance(marker[0], (list, tuple))):
+ if (
+ isinstance(marker, list)
+ and len(marker) == 1
+ and isinstance(marker[0], (list, tuple))
+ ):
return _format_marker(marker[0])
if isinstance(marker, list):
@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment):
def format_full_version(info):
- version = '{0.major}.{0.minor}.{0.micro}'.format(info)
+ version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
- if kind != 'final':
+ if kind != "final":
version += kind[0] + str(info.serial)
return version
def default_environment():
- if hasattr(sys, 'implementation'):
+ if hasattr(sys, "implementation"):
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
else:
- iver = '0'
- implementation_name = ''
+ iver = "0"
+ implementation_name = ""
return {
"implementation_name": implementation_name,
@@ -270,13 +265,13 @@ def default_environment():
class Marker(object):
-
def __init__(self, marker):
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])
+ marker, marker[e.loc : e.loc + 8]
+ )
raise InvalidMarker(err_str)
def __str__(self):
diff --git a/packaging/requirements.py b/packaging/requirements.py
index e8008a6..4d9688b 100644
--- a/packaging/requirements.py
+++ b/packaging/requirements.py
@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER
-URI = Regex(r'[^ ]+')("url")
-URL = (AT + URI)
+URI = Regex(r"[^ ]+")("url")
+URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
@@ -48,17 +48,18 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
-VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE),
- joinString=",", adjacent=False)("_raw_spec")
+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.setParseAction(lambda s, l, t: t._raw_spec or '')
+_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
- lambda s, l, t: Marker(s[t._original_start:t._original_end])
+ lambda s, l, t: Marker(s[t._original_start : t._original_end])
)
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
@@ -66,8 +67,7 @@ MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
-NAMED_REQUIREMENT = \
- NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
+NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
# pyparsing isn't thread safe during initialization, so we do it eagerly, see
@@ -92,15 +92,21 @@ class Requirement(object):
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
- ))
+ raise InvalidRequirement(
+ 'Parse error at "{0!r}": {1}'.format(
+ requirement_string[e.loc : e.loc + 8], e.msg
+ )
+ )
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
- if not (parsed_url.scheme and parsed_url.netloc) or (
- not parsed_url.scheme and not parsed_url.netloc):
+ if parsed_url.scheme == "file":
+ if urlparse.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
else:
@@ -120,6 +126,8 @@ class Requirement(object):
if self.url:
parts.append("@ {0}".format(self.url))
+ if self.marker:
+ parts.append(" ")
if self.marker:
parts.append("; {0}".format(self.marker))
diff --git a/packaging/specifiers.py b/packaging/specifiers.py
index 4c79899..743576a 100644
--- a/packaging/specifiers.py
+++ b/packaging/specifiers.py
@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError):
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
-
@abc.abstractmethod
def __str__(self):
"""
@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier):
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
- self._spec = (
- match.group("operator").strip(),
- match.group("version").strip(),
- )
+ self._spec = (match.group("operator").strip(), match.group("version").strip())
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier):
else ""
)
- return "<{0}({1!r}{2})>".format(
- self.__class__.__name__,
- str(self),
- pre,
- )
+ return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
return "{0}{1}".format(*self._spec)
@@ -194,8 +186,9 @@ class _IndividualSpecifier(BaseSpecifier):
# If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing
# else matches this specifier.
- if (parsed_version.is_prerelease and not
- (prereleases or self.prereleases)):
+ if parsed_version.is_prerelease and not (
+ prereleases or self.prereleases
+ ):
found_prereleases.append(version)
# Either this is not a prerelease, or we should have been
# accepting prereleases from the beginning.
@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier):
class LegacySpecifier(_IndividualSpecifier):
- _regex_str = (
- r"""
+ _regex_str = r"""
(?P<operator>(==|!=|<=|>=|<|>))
\s*
(?P<version>
@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier):
# them, and a comma since it's a version separator.
)
"""
- )
- _regex = re.compile(
- r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
@@ -269,13 +259,13 @@ def _require_version_compare(fn):
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
+
return wrapped
class Specifier(_IndividualSpecifier):
- _regex_str = (
- r"""
+ _regex_str = r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version>
(?:
@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier):
)
)
"""
- )
- _regex = re.compile(
- r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier):
prefix = ".".join(
list(
itertools.takewhile(
- lambda x: (not x.startswith("post") and not
- x.startswith("dev")),
+ lambda x: (not x.startswith("post") and not x.startswith("dev")),
_version_split(spec),
)
)[:-1]
@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier):
# Add the prefix notation to the end of our string
prefix += ".*"
- return (self._get_operator(">=")(prospective, spec) and
- self._get_operator("==")(prospective, prefix))
+ return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
+ prospective, prefix
+ )
@_require_version_compare
def _compare_equal(self, prospective, spec):
@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier):
# Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the
# prospective version or not.
- prospective = prospective[:len(spec)]
+ prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same
# length.
@@ -567,27 +555,17 @@ def _pad_version(left, right):
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
- left_split.append(left[len(left_split[0]):])
- right_split.append(right[len(right_split[0]):])
+ left_split.append(left[len(left_split[0]) :])
+ right_split.append(right[len(right_split[0]) :])
# Insert our padding
- left_split.insert(
- 1,
- ["0"] * max(0, len(right_split[0]) - len(left_split[0])),
- )
- right_split.insert(
- 1,
- ["0"] * max(0, len(left_split[0]) - len(right_split[0])),
- )
+ left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
+ right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
- return (
- list(itertools.chain(*left_split)),
- list(itertools.chain(*right_split)),
- )
+ return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
-
def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier):
# given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision.
- return all(
- s.contains(item, prereleases=prereleases)
- for s in self._specs
- )
+ return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing
diff --git a/packaging/utils.py b/packaging/utils.py
index 4b94a82..8841878 100644
--- a/packaging/utils.py
+++ b/packaging/utils.py
@@ -36,13 +36,7 @@ def canonicalize_version(version):
# 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 version.release)))
# Pre-release
if version.pre is not None:
diff --git a/packaging/version.py b/packaging/version.py
index 6ed5cbb..95157a1 100644
--- a/packaging/version.py
+++ b/packaging/version.py
@@ -10,14 +10,11 @@ import re
from ._structures import Infinity
-__all__ = [
- "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
-]
+__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
_Version = collections.namedtuple(
- "_Version",
- ["epoch", "release", "dev", "pre", "post", "local"],
+ "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)
@@ -40,7 +37,6 @@ class InvalidVersion(ValueError):
class _BaseVersion(object):
-
def __hash__(self):
return hash(self._key)
@@ -70,7 +66,6 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion):
-
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
@@ -126,12 +121,14 @@ class LegacyVersion(_BaseVersion):
return False
-_legacy_version_component_re = re.compile(
- r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
-)
+_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_replacement_map = {
- "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
+ "pre": "c",
+ "preview": "c",
+ "-": "final-",
+ "rc": "c",
+ "dev": "@",
}
@@ -215,10 +212,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion):
- _regex = re.compile(
- r"^\s*" + VERSION_PATTERN + r"\s*$",
- re.VERBOSE | re.IGNORECASE,
- )
+ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
def __init__(self, version):
# Validate the version and parse it into pieces
@@ -230,18 +224,11 @@ class Version(_BaseVersion):
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
- pre=_parse_letter_version(
- match.group("pre_l"),
- match.group("pre_n"),
- ),
+ pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
- match.group("post_l"),
- match.group("post_n1") or match.group("post_n2"),
- ),
- dev=_parse_letter_version(
- match.group("dev_l"),
- match.group("dev_n"),
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
+ dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
@@ -395,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(
- reversed(list(
- itertools.dropwhile(
- lambda x: x == 0,
- reversed(release),
- )
- ))
+ reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
@@ -433,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
- local = tuple(
- (i, "") if isinstance(i, int) else (-Infinity, i)
- for i in local
- )
+ local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
return epoch, release, pre, post, dev, local
diff --git a/setup.py b/setup.py
index 7a1462b..23007c7 100644
--- a/setup.py
+++ b/setup.py
@@ -41,29 +41,19 @@ with open(os.path.join(base_dir, "CHANGELOG.rst")) as f:
setup(
name=about["__title__"],
version=about["__version__"],
-
description=about["__summary__"],
long_description=long_description,
license=about["__license__"],
url=about["__uri__"],
-
author=about["__author__"],
author_email=about["__email__"],
-
- python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
-
- install_requires=[
- "pyparsing>=2.0.2", # Needed to avoid issue #91
- "six",
- ],
-
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+ install_requires=["pyparsing>=2.0.2", "six"], # Needed to avoid issue #91
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
-
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: BSD License",
-
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
@@ -71,9 +61,7 @@ setup(
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
],
-
- packages=[
- "packaging",
- ],
+ packages=["packaging"],
)
diff --git a/tests/test_markers.py b/tests/test_markers.py
index 0e2d96a..e271ad6 100644
--- a/tests/test_markers.py
+++ b/tests/test_markers.py
@@ -13,39 +13,54 @@ import pretend
import pytest
from packaging.markers import (
- Node, InvalidMarker, UndefinedComparison, UndefinedEnvironmentName, Marker,
- default_environment, format_full_version,
+ Node,
+ InvalidMarker,
+ UndefinedComparison,
+ UndefinedEnvironmentName,
+ Marker,
+ default_environment,
+ format_full_version,
)
VARIABLES = [
- "extra", "implementation_name", "implementation_version", "os_name",
- "platform_machine", "platform_release", "platform_system",
- "platform_version", "python_full_version", "python_version",
- "platform_python_implementation", "sys_platform",
+ "extra",
+ "implementation_name",
+ "implementation_version",
+ "os_name",
+ "platform_machine",
+ "platform_release",
+ "platform_system",
+ "platform_version",
+ "python_full_version",
+ "python_version",
+ "platform_python_implementation",
+ "sys_platform",
]
PEP_345_VARIABLES = [
- "os.name", "sys.platform", "platform.version", "platform.machine",
+ "os.name",
+ "sys.platform",
+ "platform.version",
+ "platform.machine",
"platform.python_implementation",
]
-SETUPTOOLS_VARIABLES = [
- "python_implementation",
-]
+SETUPTOOLS_VARIABLES = ["python_implementation"]
-OPERATORS = [
- "===", "==", ">=", "<=", "!=", "~=", ">", "<", "in", "not in",
-]
+OPERATORS = ["===", "==", ">=", "<=", "!=", "~=", ">", "<", "in", "not in"]
VALUES = [
- "1.0", "5.6a0", "dog", "freebsd", "literally any string can go here",
+ "1.0",
+ "5.6a0",
+ "dog",
+ "freebsd",
+ "literally any string can go here",
"things @#4 dsfd (((",
]
class TestNode:
-
@pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []])
def test_accepts_value(self, value):
assert Node(value).value == value
@@ -64,12 +79,11 @@ class TestNode:
class TestOperatorEvaluation:
-
def test_prefers_pep440(self):
- assert Marker('"2.7.9" < "foo"').evaluate(dict(foo='2.7.10'))
+ assert Marker('"2.7.9" < "foo"').evaluate(dict(foo="2.7.10"))
def test_falls_back_to_python(self):
- assert Marker('"b" > "a"').evaluate(dict(a='a'))
+ assert Marker('"b" > "a"').evaluate(dict(a="a"))
def test_fails_when_undefined(self):
with pytest.raises(UndefinedComparison):
@@ -77,15 +91,14 @@ class TestOperatorEvaluation:
FakeVersionInfo = collections.namedtuple(
- "FakeVersionInfo",
- ["major", "minor", "micro", "releaselevel", "serial"],
+ "FakeVersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]
)
class TestDefaultEnvironment:
-
- @pytest.mark.skipif(hasattr(sys, 'implementation'),
- reason='sys.implementation does exist')
+ @pytest.mark.skipif(
+ hasattr(sys, "implementation"), reason="sys.implementation does exist"
+ )
def test_matches_expected_no_sys_implementation(self):
environment = default_environment()
@@ -103,8 +116,9 @@ class TestDefaultEnvironment:
"sys_platform": sys.platform,
}
- @pytest.mark.skipif(not hasattr(sys, 'implementation'),
- reason='sys.implementation does not exist')
+ @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")
@@ -124,14 +138,13 @@ class TestDefaultEnvironment:
"sys_platform": sys.platform,
}
- @pytest.mark.skipif(not hasattr(sys, 'implementation'),
- reason='sys.implementation does not exist')
+ @pytest.mark.skipif(
+ not hasattr(sys, "implementation"), reason="sys.implementation does not exist"
+ )
def test_matches_expected(self):
environment = default_environment()
- iver = "{0.major}.{0.minor}.{0.micro}".format(
- sys.implementation.version
- )
+ iver = "{0.major}.{0.minor}.{0.micro}".format(sys.implementation.version)
if sys.implementation.version.releaselevel != "final":
iver = "{0}{1[0]}{2}".format(
iver,
@@ -153,14 +166,16 @@ class TestDefaultEnvironment:
"sys_platform": sys.platform,
}
- @pytest.mark.skipif(hasattr(sys, 'implementation'),
- reason='sys.implementation does exist')
+ @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)
+ sys,
+ "implementation",
+ pretend.stub(version=FakeVersionInfo(3, 4, 2, "final", 0), name="linux"),
+ raising=False,
+ )
environment = default_environment()
assert environment == {
@@ -179,21 +194,21 @@ class TestDefaultEnvironment:
def tests_when_releaselevel_final(self):
v = FakeVersionInfo(3, 4, 2, "final", 0)
- assert format_full_version(v) == '3.4.2'
+ assert format_full_version(v) == "3.4.2"
def tests_when_releaselevel_not_final(self):
v = FakeVersionInfo(3, 4, 2, "beta", 4)
- assert format_full_version(v) == '3.4.2b4'
+ assert format_full_version(v) == "3.4.2b4"
class TestMarker:
-
@pytest.mark.parametrize(
"marker_string",
[
"{0} {1} {2!r}".format(*i)
for i in itertools.product(VARIABLES, OPERATORS, VALUES)
- ] + [
+ ]
+ + [
"{2!r} {1} {0}".format(*i)
for i in itertools.product(VARIABLES, OPERATORS, VALUES)
],
@@ -220,7 +235,6 @@ class TestMarker:
# Test the different quoting rules
("python_version == '2.7'", 'python_version == "2.7"'),
('python_version == "2.7"', 'python_version == "2.7"'),
-
# Test and/or expressions
(
'python_version == "2.7" and os_name == "linux"',
@@ -236,7 +250,6 @@ class TestMarker:
'python_version == "2.7" and os_name == "linux" or '
'sys_platform == "win32"',
),
-
# Test nested expressions and grouping with ()
('(python_version == "2.7")', 'python_version == "2.7"'),
(
@@ -293,16 +306,8 @@ class TestMarker:
{"os_name": "other", "python_version": "2.7.4"},
False,
),
- (
- "extra == 'security'",
- {"extra": "quux"},
- False,
- ),
- (
- "extra == 'security'",
- {"extra": "security"},
- True,
- ),
+ ("extra == 'security'", {"extra": "quux"}, False),
+ ("extra == 'security'", {"extra": "security"}, True),
],
)
def test_evaluates(self, marker_string, environment, expected):
@@ -314,7 +319,8 @@ class TestMarker:
[
"{0} {1} {2!r}".format(*i)
for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES)
- ] + [
+ ]
+ + [
"{2!r} {1} {0}".format(*i)
for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES)
],
@@ -327,16 +333,8 @@ class TestMarker:
[
("os.name == '{0}'".format(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,
- ),
+ ("platform.version in 'Ubuntu'", {"platform_version": "#39"}, False),
+ ("platform.machine=='x86_64'", {"platform_machine": "x86_64"}, True),
(
"platform.python_implementation=='Jython'",
{"platform_python_implementation": "CPython"},
@@ -350,8 +348,7 @@ class TestMarker:
),
],
)
- def test_evaluate_pep345_markers(self, marker_string, environment,
- expected):
+ def test_evaluate_pep345_markers(self, marker_string, environment, expected):
args = [] if environment is None else [environment]
assert Marker(marker_string).evaluate(*args) == expected
@@ -360,7 +357,8 @@ class TestMarker:
[
"{0} {1} {2!r}".format(*i)
for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES)
- ] + [
+ ]
+ + [
"{2!r} {1} {0}".format(*i)
for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES)
],
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index cd42aa9..0e56cd9 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -12,23 +12,28 @@ from packaging.specifiers import SpecifierSet
class TestRequirements:
-
def test_string_specifier_marker(self):
requirement = 'name[bar]>=3; python_version == "2.7"'
req = Requirement(requirement)
assert str(req) == requirement
def test_string_url(self):
- requirement = 'name@ http://foo.com'
+ requirement = "name@ http://foo.com"
+ req = Requirement(requirement)
+ assert str(req) == requirement
+
+ def test_string_url_with_marker(self):
+ requirement = 'name@ http://foo.com ; extra == "feature"'
req = Requirement(requirement)
assert str(req) == requirement
def test_repr(self):
- req = Requirement('name')
+ req = Requirement("name")
assert repr(req) == "<Requirement('name')>"
- def _assert_requirement(self, req, name, url=None, extras=[],
- specifier='', marker=None):
+ def _assert_requirement(
+ self, req, name, url=None, extras=[], specifier="", marker=None
+ ):
assert req.name == name
assert req.url == url
assert sorted(req.extras) == sorted(extras)
@@ -62,8 +67,9 @@ class TestRequirements:
def test_with_legacy_version_and_marker(self):
req = Requirement("name>=1.x.y;python_version=='2.6'")
- self._assert_requirement(req, "name", specifier=">=1.x.y",
- marker='python_version == "2.6"')
+ self._assert_requirement(
+ req, "name", specifier=">=1.x.y", marker='python_version == "2.6"'
+ )
def test_version_with_parens_and_whitespace(self):
req = Requirement("name (==4)")
@@ -88,12 +94,12 @@ class TestRequirements:
def test_url(self):
url_section = "@ http://example.com"
parsed = URL.parseString(url_section)
- assert parsed.url == 'http://example.com'
+ assert parsed.url == "http://example.com"
def test_url_and_marker(self):
instring = "@ http://example.com ; os_name=='a'"
parsed = URL_AND_MARKER.parseString(instring)
- assert parsed.url == 'http://example.com'
+ assert parsed.url == "http://example.com"
assert str(parsed.marker) == 'os_name == "a"'
def test_invalid_url(self):
@@ -102,30 +108,42 @@ class TestRequirements:
assert "Invalid URL: " in str(e)
assert "gopher:/foo/com" in str(e)
+ def test_file_url(self):
+ req = Requirement("name @ file:///absolute/path")
+ self._assert_requirement(req, "name", "file:///absolute/path")
+ req = Requirement("name @ file://.")
+ self._assert_requirement(req, "name", "file://.")
+
+ def test_invalid_file_urls(self):
+ with pytest.raises(InvalidRequirement):
+ Requirement("name @ file:.")
+ with pytest.raises(InvalidRequirement):
+ Requirement("name @ file:/.")
+
def test_extras_and_url_and_marker(self):
- req = Requirement(
- "name [fred,bar] @ http://foo.com ; python_version=='2.7'")
- self._assert_requirement(req, "name", extras=["bar", "fred"],
- url="http://foo.com",
- marker='python_version == "2.7"')
+ req = Requirement("name [fred,bar] @ http://foo.com ; python_version=='2.7'")
+ self._assert_requirement(
+ req,
+ "name",
+ extras=["bar", "fred"],
+ url="http://foo.com",
+ marker='python_version == "2.7"',
+ )
def test_complex_url_and_marker(self):
url = "https://example.com/name;v=1.1/?query=foo&bar=baz#blah"
req = Requirement("foo @ %s ; python_version=='3.4'" % url)
- self._assert_requirement(req, "foo", url=url,
- marker='python_version == "3.4"')
+ self._assert_requirement(req, "foo", url=url, marker='python_version == "3.4"')
def test_multiple_markers(self):
req = Requirement(
- "name[quux, strange];python_version<'2.7' and "
- "platform_version=='2'")
+ "name[quux, strange];python_version<'2.7' and " "platform_version=='2'"
+ )
marker = 'python_version < "2.7" and platform_version == "2"'
- self._assert_requirement(req, "name", extras=["strange", "quux"],
- marker=marker)
+ self._assert_requirement(req, "name", extras=["strange", "quux"], marker=marker)
def test_multiple_comparsion_markers(self):
- req = Requirement(
- "name; os_name=='a' and os_name=='b' or os_name=='c'")
+ 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)
@@ -160,7 +178,7 @@ class TestRequirements:
def test_sys_platform_linux_equal(self):
req = Requirement('something>=1.2.3; sys_platform == "foo"')
- assert req.name == 'something'
+ assert req.name == "something"
assert req.marker is not None
assert req.marker.evaluate(dict(sys_platform="foo")) is True
assert req.marker.evaluate(dict(sys_platform="bar")) is False
@@ -168,7 +186,7 @@ class TestRequirements:
def test_sys_platform_linux_in(self):
req = Requirement("aviato>=1.2.3; 'f' in sys_platform")
- assert req.name == 'aviato'
+ assert req.name == "aviato"
assert req.marker is not None
assert req.marker.evaluate(dict(sys_platform="foo")) is True
assert req.marker.evaluate(dict(sys_platform="bar")) is False
diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py
index adc10e5..a3feecc 100644
--- a/tests/test_specifiers.py
+++ b/tests/test_specifiers.py
@@ -9,7 +9,10 @@ import operator
import pytest
from packaging.specifiers import (
- InvalidSpecifier, LegacySpecifier, Specifier, SpecifierSet,
+ InvalidSpecifier,
+ LegacySpecifier,
+ Specifier,
+ SpecifierSet,
)
from packaging.version import LegacyVersion, Version, parse
@@ -17,17 +20,29 @@ from .test_version import VERSIONS, LEGACY_VERSIONS
LEGACY_SPECIFIERS = [
- "==2.1.0.3", "!=2.2.0.5", "<=5", ">=7.9a1", "<1.0.dev1", ">2.0.post1",
+ "==2.1.0.3",
+ "!=2.2.0.5",
+ "<=5",
+ ">=7.9a1",
+ "<1.0.dev1",
+ ">2.0.post1",
]
SPECIFIERS = [
- "~=2.0", "==2.1.*", "==2.1.0.3", "!=2.2.*", "!=2.2.0.5", "<=5", ">=7.9a1",
- "<1.0.dev1", ">2.0.post1", "===lolwat",
+ "~=2.0",
+ "==2.1.*",
+ "==2.1.0.3",
+ "!=2.2.*",
+ "!=2.2.0.5",
+ "<=5",
+ ">=7.9a1",
+ "<1.0.dev1",
+ ">2.0.post1",
+ "===lolwat",
]
class TestSpecifier:
-
@pytest.mark.parametrize("specifier", SPECIFIERS)
def test_specifiers_valid(self, specifier):
Specifier(specifier)
@@ -37,42 +52,33 @@ class TestSpecifier:
[
# Operator-less specifier
"2.0",
-
# Invalid operator
"=>2.0",
-
# Version-less specifier
"==",
-
# Local segment on operators which don't support them
"~=1.0+5",
">=1.0+deadbeef",
"<=1.0+abc123",
">1.0+watwat",
"<1.0+1.0",
-
# Prefix matching on operators which don't support them
"~=1.0.*",
">=1.0.*",
"<=1.0.*",
">1.0.*",
"<1.0.*",
-
# Combination of local and prefix matching on operators which do
# support one or the other
"==1.0.*+5",
"!=1.0.*+deadbeef",
-
# Prefix matching cannot be used inside of a local version
"==1.0+5.*",
"!=1.0+deadbeef.*",
-
# Prefix matching must appear at the end
"==1.0.*.5",
-
# Compatible operator requires 2 digits in the release operator
"~=1",
-
# Cannot use a prefix matching after a .devN version
"==1.0.dev1.*",
"!=1.0.dev1.*",
@@ -99,7 +105,6 @@ class TestSpecifier:
"1.0.DEV1",
"1.0-DEV",
"1.0-DEV1",
-
# Various alpha incarnations
"1.0a",
"1.0.a",
@@ -121,7 +126,6 @@ class TestSpecifier:
"1.0.ALPHA1",
"1.0-ALPHA",
"1.0-ALPHA1",
-
# Various beta incarnations
"1.0b",
"1.0.b",
@@ -143,7 +147,6 @@ class TestSpecifier:
"1.0.BETA1",
"1.0-BETA",
"1.0-BETA1",
-
# Various release candidate incarnations
"1.0c",
"1.0.c",
@@ -165,7 +168,6 @@ class TestSpecifier:
"1.0.RC1",
"1.0-RC",
"1.0-RC1",
-
# Various post release incarnations
"1.0post",
"1.0.post",
@@ -181,10 +183,8 @@ class TestSpecifier:
"1.0-POST",
"1.0-POST1",
"1.0-5",
-
# Local version case insensitivity
"1.0+AbC"
-
# Integer Normalization
"1.01",
"1.0a05",
@@ -195,7 +195,6 @@ class TestSpecifier:
"1.1.dev09000",
"00!1.2",
"0100!0.0",
-
# Various other normalizations
"v1.0",
" \r \f \v v1.0\t\n",
@@ -221,7 +220,6 @@ class TestSpecifier:
(">2.0", ">2.0"),
(">=2.0", ">=2.0"),
("~=2.0", "~=2.0"),
-
# Spaces should be removed
("< 2", "<2"),
],
@@ -241,19 +239,14 @@ class TestSpecifier:
itertools.chain(
*
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.eq) for x in SPECIFIERS]
- ] +
+ [[(x, x, operator.eq) for x in SPECIFIERS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
- [
- (x, y, operator.ne)
- for j, y in enumerate(SPECIFIERS)
- if i != j
- ]
+ [(x, y, operator.ne) for j, y in enumerate(SPECIFIERS) if i != j]
for i, x in enumerate(SPECIFIERS)
]
- )
+ ),
)
def test_comparison_true(self, left, right, op):
assert op(Specifier(left), Specifier(right))
@@ -265,19 +258,14 @@ class TestSpecifier:
itertools.chain(
*
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.ne) for x in SPECIFIERS]
- ] +
+ [[(x, x, operator.ne) for x in SPECIFIERS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
- [
- (x, y, operator.eq)
- for j, y in enumerate(SPECIFIERS)
- if i != j
- ]
+ [(x, y, operator.eq) for j, y in enumerate(SPECIFIERS) if i != j]
for i, x in enumerate(SPECIFIERS)
]
- )
+ ),
)
def test_comparison_false(self, left, right, op):
assert not op(Specifier(left), Specifier(right))
@@ -306,7 +294,6 @@ class TestSpecifier:
("2.0+deadbeef", "==2.0+deadbeef"),
("2.0+deadbeef", "==2.0.0+deadbeef"),
("2.0+deadbeef.0", "==2.0.0+deadbeef.00"),
-
# Test the equality operation with a prefix
("2.dev1", "==2.*"),
("2a1", "==2.*"),
@@ -322,7 +309,6 @@ class TestSpecifier:
("2.0.post1", "==2.0.post1.*"),
("2.0.post1.dev1", "==2.0.post1.*"),
("2.1+local.version", "==2.1.*"),
-
# Test the in-equality operation
("2.1", "!=2"),
("2.1", "!=2.0"),
@@ -330,11 +316,9 @@ class TestSpecifier:
("2.0.1", "!=2.0"),
("2.0.1", "!=2.0.0"),
("2.0", "!=2.0+deadbeef"),
-
# Test the in-equality operation with a prefix
("2.0", "!=3.*"),
("2.1", "!=2.0.*"),
-
# Test the greater than equal operation
("2.0", ">=2"),
("2.0", ">=2.0"),
@@ -342,7 +326,6 @@ class TestSpecifier:
("2.0.post1", ">=2"),
("2.0.post1.dev1", ">=2"),
("3", ">=2"),
-
# Test the less than equal operation
("2.0", "<=2"),
("2.0", "<=2.0"),
@@ -356,25 +339,21 @@ class TestSpecifier:
("2.0c1.post1.dev1", "<=2"),
("2.0rc1", "<=2"),
("1", "<=2"),
-
# Test the greater than operation
("3", ">2"),
("2.1", ">2.0"),
("2.0.1", ">2"),
("2.1.post1", ">2"),
("2.1+local.version", ">2"),
-
# Test the less than operation
("1", "<2"),
("2.0", "<2.1"),
("2.0.dev0", "<2.1"),
-
# Test the compatibility operation
("1", "~=1.0"),
("1.0.1", "~=1.0"),
("1.1", "~=1.0"),
("1.9999999", "~=1.0"),
-
# Test that epochs are handled sanely
("2!1.0", "~=2!1.0"),
("2!1.0", "==2!1.*"),
@@ -385,11 +364,11 @@ class TestSpecifier:
("2!1.0", ">=2.0"),
("1.0", "<2!0.1"),
("2!1.0", ">2.0"),
-
# Test some normalization rules
("2.0.5", ">2.0dev"),
]
- ] + [
+ ]
+ + [
(v, s, False)
for v, s in [
# Test the equality operation
@@ -397,11 +376,9 @@ class TestSpecifier:
("2.1", "==2.0"),
("2.1", "==2.0.0"),
("2.0", "==2.0+deadbeef"),
-
# Test the equality operation with a prefix
("2.0", "==3.*"),
("2.1", "==2.0.*"),
-
# Test the in-equality operation
("2.0", "!=2"),
("2.0", "!=2.0"),
@@ -413,7 +390,6 @@ class TestSpecifier:
("2.0+deadbeef", "!=2.0+deadbeef"),
("2.0+deadbeef", "!=2.0.0+deadbeef"),
("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"),
-
# Test the in-equality operation with a prefix
("2.dev1", "!=2.*"),
("2a1", "!=2.*"),
@@ -428,7 +404,6 @@ class TestSpecifier:
("2.0.0", "!=2.*"),
("2.0.post1", "!=2.0.post1.*"),
("2.0.post1.dev1", "!=2.0.post1.*"),
-
# Test the greater than equal operation
("2.0.dev1", ">=2"),
("2.0a1", ">=2"),
@@ -439,12 +414,10 @@ class TestSpecifier:
("2.0c1.post1.dev1", ">=2"),
("2.0rc1", ">=2"),
("1", ">=2"),
-
# Test the less than equal operation
("2.0.post1", "<=2"),
("2.0.post1.dev1", "<=2"),
("3", "<=2"),
-
# Test the greater than operation
("1", ">2"),
("2.0.dev1", ">2"),
@@ -459,7 +432,6 @@ class TestSpecifier:
("2.0.post1", ">2"),
("2.0.post1.dev1", ">2"),
("2.0+local.version", ">2"),
-
# Test the less than operation
("2.0.dev1", "<2"),
("2.0a1", "<2"),
@@ -473,12 +445,10 @@ class TestSpecifier:
("2.post1", "<2"),
("2.post1.dev1", "<2"),
("3", "<2"),
-
# Test the compatibility operation
("2.0", "~=1.0"),
("1.1.0", "~=1.0.0"),
("1.1.post1", "~=1.0.0"),
-
# Test that epochs are handled sanely
("1.0", "~=2!1.0"),
("2!1.0", "~=1.0"),
@@ -587,9 +557,7 @@ class TestSpecifier:
def test_specifier_filter(self, specifier, prereleases, input, expected):
spec = Specifier(specifier)
- kwargs = (
- {"prereleases": prereleases} if prereleases is not None else {}
- )
+ kwargs = {"prereleases": prereleases} if prereleases is not None else {}
assert list(spec.filter(input, **kwargs)) == expected
@@ -598,50 +566,44 @@ class TestSpecifier:
assert Specifier("==1.0").contains(LegacyVersion("1.0"))
@pytest.mark.parametrize(
- ('spec', 'op'),
+ ("spec", "op"),
[
- ('~=2.0', '~='),
- ('==2.1.*', '=='),
- ('==2.1.0.3', '=='),
- ('!=2.2.*', '!='),
- ('!=2.2.0.5', '!='),
- ('<=5', '<='),
- ('>=7.9a1', '>='),
- ('<1.0.dev1', '<'),
- ('>2.0.post1', '>'),
- ('===lolwat', '==='),
- ]
+ ("~=2.0", "~="),
+ ("==2.1.*", "=="),
+ ("==2.1.0.3", "=="),
+ ("!=2.2.*", "!="),
+ ("!=2.2.0.5", "!="),
+ ("<=5", "<="),
+ (">=7.9a1", ">="),
+ ("<1.0.dev1", "<"),
+ (">2.0.post1", ">"),
+ ("===lolwat", "==="),
+ ],
)
def test_specifier_operator_property(self, spec, op):
assert Specifier(spec).operator == op
@pytest.mark.parametrize(
- ('spec', 'version'),
+ ("spec", "version"),
[
- ('~=2.0', '2.0'),
- ('==2.1.*', '2.1.*'),
- ('==2.1.0.3', '2.1.0.3'),
- ('!=2.2.*', '2.2.*'),
- ('!=2.2.0.5', '2.2.0.5'),
- ('<=5', '5'),
- ('>=7.9a1', '7.9a1'),
- ('<1.0.dev1', '1.0.dev1'),
- ('>2.0.post1', '2.0.post1'),
- ('===lolwat', 'lolwat'),
- ]
+ ("~=2.0", "2.0"),
+ ("==2.1.*", "2.1.*"),
+ ("==2.1.0.3", "2.1.0.3"),
+ ("!=2.2.*", "2.2.*"),
+ ("!=2.2.0.5", "2.2.0.5"),
+ ("<=5", "5"),
+ (">=7.9a1", "7.9a1"),
+ ("<1.0.dev1", "1.0.dev1"),
+ (">2.0.post1", "2.0.post1"),
+ ("===lolwat", "lolwat"),
+ ],
)
def test_specifier_version_property(self, spec, version):
assert Specifier(spec).version == version
@pytest.mark.parametrize(
("spec", "expected_length"),
- [
- ("", 0),
- ("==2.0", 1),
- (">=2.0", 1),
- (">=2.0,<3", 2),
- (">=2.0,<3,==2.4", 3),
- ],
+ [("", 0), ("==2.0", 1), (">=2.0", 1), (">=2.0,<3", 2), (">=2.0,<3,==2.4", 3)],
)
def test_length(self, spec, expected_length):
spec = SpecifierSet(spec)
@@ -664,7 +626,6 @@ class TestSpecifier:
class TestLegacySpecifier:
-
@pytest.mark.parametrize(
("version", "spec", "expected"),
[
@@ -674,14 +635,12 @@ class TestLegacySpecifier:
("2.0", "==2"),
("2.0", "==2.0"),
("2.0", "==2.0.0"),
-
# Test the in-equality operation
("2.1", "!=2"),
("2.1", "!=2.0"),
("2.0.1", "!=2"),
("2.0.1", "!=2.0"),
("2.0.1", "!=2.0.0"),
-
# Test the greater than equal operation
("2.0", ">=2"),
("2.0", ">=2.0"),
@@ -689,7 +648,6 @@ class TestLegacySpecifier:
("2.0.post1", ">=2"),
("2.0.post1.dev1", ">=2"),
("3", ">=2"),
-
# Test the less than equal operation
("2.0", "<=2"),
("2.0", "<=2.0"),
@@ -703,28 +661,25 @@ class TestLegacySpecifier:
("2.0c1.post1.dev1", "<=2"),
("2.0rc1", "<=2"),
("1", "<=2"),
-
# Test the greater than operation
("3", ">2"),
("2.1", ">2.0"),
-
# Test the less than operation
("1", "<2"),
("2.0", "<2.1"),
]
- ] + [
+ ]
+ + [
(v, s, False)
for v, s in [
# Test the equality operation
("2.1", "==2"),
("2.1", "==2.0"),
("2.1", "==2.0.0"),
-
# Test the in-equality operation
("2.0", "!=2"),
("2.0", "!=2.0"),
("2.0", "!=2.0.0"),
-
# Test the greater than equal operation
("2.0.dev1", ">=2"),
("2.0a1", ">=2"),
@@ -735,12 +690,10 @@ class TestLegacySpecifier:
("2.0c1.post1.dev1", ">=2"),
("2.0rc1", ">=2"),
("1", ">=2"),
-
# Test the less than equal operation
("2.0.post1", "<=2"),
("2.0.post1.dev1", "<=2"),
("3", "<=2"),
-
# Test the greater than operation
("1", ">2"),
("2.0.dev1", ">2"),
@@ -752,7 +705,6 @@ class TestLegacySpecifier:
("2.0c1.post1.dev1", ">2"),
("2.0rc1", ">2"),
("2.0", ">2"),
-
# Test the less than operation
("3", "<2"),
]
@@ -801,11 +753,7 @@ class TestLegacySpecifier:
class TestSpecifierSet:
-
- @pytest.mark.parametrize(
- "version",
- VERSIONS + LEGACY_VERSIONS,
- )
+ @pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS)
def test_empty_specifier(self, version):
spec = SpecifierSet(prereleases=True)
@@ -854,10 +802,7 @@ class TestSpecifierSet:
assert not spec.contains("1.0.dev1", prereleases=False)
@pytest.mark.parametrize(
- (
- "specifier", "specifier_prereleases", "prereleases", "input",
- "expected",
- ),
+ ("specifier", "specifier_prereleases", "prereleases", "input", "expected"),
[
# General test of the filter method
("", None, None, ["1.0", "2.0a1"], ["1.0"]),
@@ -865,12 +810,10 @@ class TestSpecifierSet:
("", None, None, ["1.0a1"], ["1.0a1"]),
("", None, None, ["1.0", Version("2.0")], ["1.0", Version("2.0")]),
("", None, None, ["2.0dog", "1.0"], ["1.0"]),
-
# Test overriding with the prereleases parameter on filter
("", None, False, ["1.0a1"], []),
(">=1.0.dev1", None, False, ["1.0", "2.0a1"], ["1.0"]),
("", None, True, ["1.0", "2.0a1"], ["1.0", "2.0a1"]),
-
# Test overriding with the overall specifier
("", True, None, ["1.0", "2.0a1"], ["1.0", "2.0a1"]),
("", False, None, ["1.0", "2.0a1"], ["1.0"]),
@@ -880,16 +823,15 @@ class TestSpecifierSet:
("", False, None, ["1.0a1"], []),
],
)
- def test_specifier_filter(self, specifier_prereleases, specifier,
- prereleases, input, expected):
+ def test_specifier_filter(
+ self, specifier_prereleases, specifier, prereleases, input, expected
+ ):
if specifier_prereleases is None:
spec = SpecifierSet(specifier)
else:
spec = SpecifierSet(specifier, prereleases=specifier_prereleases)
- kwargs = (
- {"prereleases": prereleases} if prereleases is not None else {}
- )
+ kwargs = {"prereleases": prereleases} if prereleases is not None else {}
assert list(spec.filter(input, **kwargs)) == expected
@@ -908,10 +850,8 @@ class TestSpecifierSet:
(">2.0", ">2.0"),
(">=2.0", ">=2.0"),
("~=2.0", "~=2.0"),
-
# Spaces should be removed
("< 2", "<2"),
-
# Multiple item specifiers should work
("!=2.0,>1.0", "!=2.0,>1.0"),
("!=2.0 ,>1.0", "!=2.0,>1.0"),
@@ -928,10 +868,7 @@ class TestSpecifierSet:
assert hash(SpecifierSet(specifier)) == hash(SpecifierSet(specifier))
@pytest.mark.parametrize(
- ("left", "right", "expected"),
- [
- (">2.0", "<5.0", ">2.0,<5.0"),
- ],
+ ("left", "right", "expected"), [(">2.0", "<5.0", ">2.0,<5.0")]
)
def test_specifiers_combine(self, left, right, expected):
result = SpecifierSet(left) & SpecifierSet(right)
@@ -956,30 +893,26 @@ class TestSpecifierSet:
assert result == SpecifierSet(expected)
assert not result.prereleases
- result = (
- SpecifierSet(left, prereleases=True) &
- SpecifierSet(right, prereleases=True)
+ result = SpecifierSet(left, prereleases=True) & SpecifierSet(
+ right, prereleases=True
)
assert result == SpecifierSet(expected)
assert result.prereleases
- result = (
- SpecifierSet(left, prereleases=False) &
- SpecifierSet(right, prereleases=False)
+ result = SpecifierSet(left, prereleases=False) & SpecifierSet(
+ right, prereleases=False
)
assert result == SpecifierSet(expected)
assert not result.prereleases
with pytest.raises(ValueError):
- result = (
- SpecifierSet(left, prereleases=True) &
- SpecifierSet(right, prereleases=False)
+ result = SpecifierSet(left, prereleases=True) & SpecifierSet(
+ right, prereleases=False
)
with pytest.raises(ValueError):
- result = (
- SpecifierSet(left, prereleases=False) &
- SpecifierSet(right, prereleases=True)
+ result = SpecifierSet(left, prereleases=False) & SpecifierSet(
+ right, prereleases=True
)
def test_specifiers_combine_not_implemented(self):
@@ -991,19 +924,14 @@ class TestSpecifierSet:
itertools.chain(
*
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.eq) for x in SPECIFIERS]
- ] +
+ [[(x, x, operator.eq) for x in SPECIFIERS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
- [
- (x, y, operator.ne)
- for j, y in enumerate(SPECIFIERS)
- if i != j
- ]
+ [(x, y, operator.ne) for j, y in enumerate(SPECIFIERS) if i != j]
for i, x in enumerate(SPECIFIERS)
]
- )
+ ),
)
def test_comparison_true(self, left, right, op):
assert op(SpecifierSet(left), SpecifierSet(right))
@@ -1017,19 +945,14 @@ class TestSpecifierSet:
itertools.chain(
*
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.ne) for x in SPECIFIERS]
- ] +
+ [[(x, x, operator.ne) for x in SPECIFIERS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
- [
- (x, y, operator.eq)
- for j, y in enumerate(SPECIFIERS)
- if i != j
- ]
+ [(x, y, operator.eq) for j, y in enumerate(SPECIFIERS) if i != j]
for i, x in enumerate(SPECIFIERS)
]
- )
+ ),
)
def test_comparison_false(self, left, right, op):
assert not op(SpecifierSet(left), SpecifierSet(right))
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 2fcb30a..8458ffb 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -30,18 +30,18 @@ def test_canonicalize_name(name, expected):
@pytest.mark.parametrize(
("version", "expected"),
[
- ('1.4.0', '1.4'),
- ('1.40.0', '1.40'),
- ('1.4.0.0.00.000.0000', '1.4'),
- ('1.0', '1'),
- ('1.0+abc', '1+abc'),
- ('1.0.dev0', '1.dev0'),
- ('1.0.post0', '1.post0'),
- ('1.0a0', '1a0'),
- ('1.0rc0', '1rc0'),
- ('100!0.0', '100!0'),
- ('1.0.1-test7', '1.0.1-test7'), # LegacyVersion is unchanged
- ]
+ ("1.4.0", "1.4"),
+ ("1.40.0", "1.40"),
+ ("1.4.0.0.00.000.0000", "1.4"),
+ ("1.0", "1"),
+ ("1.0+abc", "1+abc"),
+ ("1.0.dev0", "1.dev0"),
+ ("1.0.post0", "1.post0"),
+ ("1.0a0", "1a0"),
+ ("1.0rc0", "1rc0"),
+ ("100!0.0", "100!0"),
+ ("1.0.1-test7", "1.0.1-test7"), # LegacyVersion is unchanged
+ ],
)
def test_canonicalize_version(version, expected):
assert canonicalize_version(version) == expected
diff --git a/tests/test_version.py b/tests/test_version.py
index ba4a11d..4e8079d 100644
--- a/tests/test_version.py
+++ b/tests/test_version.py
@@ -13,11 +13,7 @@ from packaging.version import Version, LegacyVersion, InvalidVersion, parse
@pytest.mark.parametrize(
- ("version", "klass"),
- [
- ("1.0", Version),
- ("1-1-1", LegacyVersion),
- ],
+ ("version", "klass"), [("1.0", Version), ("1-1-1", LegacyVersion)]
)
def test_parse(version, klass):
assert isinstance(parse(version), klass)
@@ -26,26 +22,65 @@ def test_parse(version, klass):
# This list must be in the correct sorting order
VERSIONS = [
# Implicit epoch of 0
- "1.0.dev456", "1.0a1", "1.0a2.dev456", "1.0a12.dev456", "1.0a12",
- "1.0b1.dev456", "1.0b2", "1.0b2.post345.dev456", "1.0b2.post345",
- "1.0b2-346", "1.0c1.dev456", "1.0c1", "1.0rc2", "1.0c3", "1.0",
- "1.0.post456.dev34", "1.0.post456", "1.1.dev1", "1.2+123abc",
- "1.2+123abc456", "1.2+abc", "1.2+abc123", "1.2+abc123def", "1.2+1234.abc",
- "1.2+123456", "1.2.r32+123456", "1.2.rev33+123456",
-
+ "1.0.dev456",
+ "1.0a1",
+ "1.0a2.dev456",
+ "1.0a12.dev456",
+ "1.0a12",
+ "1.0b1.dev456",
+ "1.0b2",
+ "1.0b2.post345.dev456",
+ "1.0b2.post345",
+ "1.0b2-346",
+ "1.0c1.dev456",
+ "1.0c1",
+ "1.0rc2",
+ "1.0c3",
+ "1.0",
+ "1.0.post456.dev34",
+ "1.0.post456",
+ "1.1.dev1",
+ "1.2+123abc",
+ "1.2+123abc456",
+ "1.2+abc",
+ "1.2+abc123",
+ "1.2+abc123def",
+ "1.2+1234.abc",
+ "1.2+123456",
+ "1.2.r32+123456",
+ "1.2.rev33+123456",
# Explicit epoch of 1
- "1!1.0.dev456", "1!1.0a1", "1!1.0a2.dev456", "1!1.0a12.dev456", "1!1.0a12",
- "1!1.0b1.dev456", "1!1.0b2", "1!1.0b2.post345.dev456", "1!1.0b2.post345",
- "1!1.0b2-346", "1!1.0c1.dev456", "1!1.0c1", "1!1.0rc2", "1!1.0c3", "1!1.0",
- "1!1.0.post456.dev34", "1!1.0.post456", "1!1.1.dev1", "1!1.2+123abc",
- "1!1.2+123abc456", "1!1.2+abc", "1!1.2+abc123", "1!1.2+abc123def",
- "1!1.2+1234.abc", "1!1.2+123456", "1!1.2.r32+123456", "1!1.2.rev33+123456",
-
+ "1!1.0.dev456",
+ "1!1.0a1",
+ "1!1.0a2.dev456",
+ "1!1.0a12.dev456",
+ "1!1.0a12",
+ "1!1.0b1.dev456",
+ "1!1.0b2",
+ "1!1.0b2.post345.dev456",
+ "1!1.0b2.post345",
+ "1!1.0b2-346",
+ "1!1.0c1.dev456",
+ "1!1.0c1",
+ "1!1.0rc2",
+ "1!1.0c3",
+ "1!1.0",
+ "1!1.0.post456.dev34",
+ "1!1.0.post456",
+ "1!1.1.dev1",
+ "1!1.2+123abc",
+ "1!1.2+123abc456",
+ "1!1.2+abc",
+ "1!1.2+abc123",
+ "1!1.2+abc123def",
+ "1!1.2+1234.abc",
+ "1!1.2+123456",
+ "1!1.2.r32+123456",
+ "1!1.2.rev33+123456",
]
class TestVersion:
-
@pytest.mark.parametrize("version", VERSIONS)
def test_valid_versions(self, version):
Version(version)
@@ -55,14 +90,13 @@ class TestVersion:
[
# Non sensical versions should be invalid
"french toast",
-
# Versions with invalid local versions
"1.0+a+",
"1.0++",
"1.0+_foobar",
"1.0+foo&asd",
"1.0+1+1",
- ]
+ ],
)
def test_invalid_versions(self, version):
with pytest.raises(InvalidVersion):
@@ -85,7 +119,6 @@ class TestVersion:
("1.0.DEV1", "1.0.dev1"),
("1.0-DEV", "1.0.dev0"),
("1.0-DEV1", "1.0.dev1"),
-
# Various alpha incarnations
("1.0a", "1.0a0"),
("1.0.a", "1.0a0"),
@@ -107,7 +140,6 @@ class TestVersion:
("1.0.ALPHA1", "1.0a1"),
("1.0-ALPHA", "1.0a0"),
("1.0-ALPHA1", "1.0a1"),
-
# Various beta incarnations
("1.0b", "1.0b0"),
("1.0.b", "1.0b0"),
@@ -129,7 +161,6 @@ class TestVersion:
("1.0.BETA1", "1.0b1"),
("1.0-BETA", "1.0b0"),
("1.0-BETA1", "1.0b1"),
-
# Various release candidate incarnations
("1.0c", "1.0rc0"),
("1.0.c", "1.0rc0"),
@@ -151,7 +182,6 @@ class TestVersion:
("1.0.RC1", "1.0rc1"),
("1.0-RC", "1.0rc0"),
("1.0-RC1", "1.0rc1"),
-
# Various post release incarnations
("1.0post", "1.0.post0"),
("1.0.post", "1.0.post0"),
@@ -173,10 +203,8 @@ class TestVersion:
("1.0-5", "1.0.post5"),
("1.0-r5", "1.0.post5"),
("1.0-rev5", "1.0.post5"),
-
# Local version case insensitivity
("1.0+AbC", "1.0+abc"),
-
# Integer Normalization
("1.01", "1.1"),
("1.0a05", "1.0a5"),
@@ -187,7 +215,6 @@ class TestVersion:
("1.1.dev09000", "1.1.dev9000"),
("00!1.2", "1.2"),
("0100!0.0", "100!0.0"),
-
# Various other normalizations
("v1.0", "1.0"),
(" v1.0\t\n", "1.0"),
@@ -250,8 +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({0})>".format(repr(expected))
def test_version_rc_and_c_equals(self):
assert Version("1.0rc1") == Version("1.0c1")
@@ -451,31 +477,31 @@ class TestVersion:
("1.0", None),
("1.0.dev0", None),
("1.0.dev6", None),
- ("1.0a1", ('a', 1)),
- ("1.0a1.post5", ('a', 1)),
- ("1.0a1.post5.dev6", ('a', 1)),
- ("1.0rc4", ('rc', 4)),
+ ("1.0a1", ("a", 1)),
+ ("1.0a1.post5", ("a", 1)),
+ ("1.0a1.post5.dev6", ("a", 1)),
+ ("1.0rc4", ("rc", 4)),
("1.0.post5", None),
("1!1.0", None),
("1!1.0.dev6", None),
- ("1!1.0a1", ('a', 1)),
- ("1!1.0a1.post5", ('a', 1)),
- ("1!1.0a1.post5.dev6", ('a', 1)),
- ("1!1.0rc4", ('rc', 4)),
+ ("1!1.0a1", ("a", 1)),
+ ("1!1.0a1.post5", ("a", 1)),
+ ("1!1.0a1.post5.dev6", ("a", 1)),
+ ("1!1.0rc4", ("rc", 4)),
("1!1.0.post5", None),
("1.0+deadbeef", None),
("1.0.dev6+deadbeef", None),
- ("1.0a1+deadbeef", ('a', 1)),
- ("1.0a1.post5+deadbeef", ('a', 1)),
- ("1.0a1.post5.dev6+deadbeef", ('a', 1)),
- ("1.0rc4+deadbeef", ('rc', 4)),
+ ("1.0a1+deadbeef", ("a", 1)),
+ ("1.0a1.post5+deadbeef", ("a", 1)),
+ ("1.0a1.post5.dev6+deadbeef", ("a", 1)),
+ ("1.0rc4+deadbeef", ("rc", 4)),
("1.0.post5+deadbeef", None),
("1!1.0+deadbeef", None),
("1!1.0.dev6+deadbeef", None),
- ("1!1.0a1+deadbeef", ('a', 1)),
- ("1!1.0a1.post5+deadbeef", ('a', 1)),
- ("1!1.0a1.post5.dev6+deadbeef", ('a', 1)),
- ("1!1.0rc4+deadbeef", ('rc', 4)),
+ ("1!1.0a1+deadbeef", ("a", 1)),
+ ("1!1.0a1.post5+deadbeef", ("a", 1)),
+ ("1!1.0a1.post5.dev6+deadbeef", ("a", 1)),
+ ("1!1.0rc4+deadbeef", ("rc", 4)),
("1!1.0.post5+deadbeef", None),
],
)
@@ -630,7 +656,7 @@ class TestVersion:
("1.0", False),
("1.0+foo", False),
("1.0.post1.dev1", True),
- ("1.0.post1", True)
+ ("1.0.post1", True),
],
)
def test_version_is_postrelease(self, version, expected):
@@ -644,34 +670,37 @@ class TestVersion:
*
# Verify that the less than (<) operator works correctly
[
- [(x, y, operator.lt) for y in VERSIONS[i + 1:]]
+ [(x, y, operator.lt) for y in VERSIONS[i + 1 :]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the less than equal (<=) operator works correctly
[
[(x, y, operator.le) for y in VERSIONS[i:]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.eq) for x in VERSIONS]
- ] +
+ [[(x, x, operator.eq) for x in VERSIONS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
[(x, y, operator.ne) for j, y in enumerate(VERSIONS) if i != j]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the greater than equal (>=) operator works correctly
[
- [(x, y, operator.ge) for y in VERSIONS[:i + 1]]
+ [(x, y, operator.ge) for y in VERSIONS[: i + 1]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the greater than (>) operator works correctly
[
[(x, y, operator.gt) for y in VERSIONS[:i]]
for i, x in enumerate(VERSIONS)
]
- )
+ ),
)
def test_comparison_true(self, left, right, op):
assert op(Version(left), Version(right))
@@ -684,43 +713,44 @@ class TestVersion:
*
# Verify that the less than (<) operator works correctly
[
- [(x, y, operator.lt) for y in VERSIONS[:i + 1]]
+ [(x, y, operator.lt) for y in VERSIONS[: i + 1]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the less than equal (<=) operator works correctly
[
[(x, y, operator.le) for y in VERSIONS[:i]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the equal (==) operator works correctly
[
[(x, y, operator.eq) for j, y in enumerate(VERSIONS) if i != j]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the not equal (!=) operator works correctly
- [
- [(x, x, operator.ne) for x in VERSIONS]
- ] +
+ [[(x, x, operator.ne) for x in VERSIONS]]
+ +
# Verify that the greater than equal (>=) operator works correctly
[
- [(x, y, operator.ge) for y in VERSIONS[i + 1:]]
+ [(x, y, operator.ge) for y in VERSIONS[i + 1 :]]
for i, x in enumerate(VERSIONS)
- ] +
+ ]
+ +
# Verify that the greater than (>) operator works correctly
[
[(x, y, operator.gt) for y in VERSIONS[i:]]
for i, x in enumerate(VERSIONS)
]
- )
+ ),
)
def test_comparison_false(self, left, right, op):
assert not op(Version(left), Version(right))
@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(**{"__{0}__".format(op): lambda other: NotImplemented})
assert getattr(operator, op)(Version("1"), other) is expected
@@ -733,7 +763,6 @@ LEGACY_VERSIONS = ["foobar", "a cat is fine too", "lolwut", "1-0", "2.0-a1"]
class TestLegacyVersion:
-
@pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS)
def test_valid_legacy_versions(self, version):
LegacyVersion(version)
@@ -741,8 +770,9 @@ 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(repr(version)))
+ assert repr(LegacyVersion(version)) == "<LegacyVersion({0})>".format(
+ repr(version)
+ )
@pytest.mark.parametrize("version", VERSIONS + LEGACY_VERSIONS)
def test_legacy_version_hash(self, version):
@@ -799,9 +829,8 @@ class TestLegacyVersion:
itertools.chain(
*
# Verify that the equal (==) operator works correctly
- [
- [(x, x, operator.eq) for x in VERSIONS + LEGACY_VERSIONS]
- ] +
+ [[(x, x, operator.eq) for x in VERSIONS + LEGACY_VERSIONS]]
+ +
# Verify that the not equal (!=) operator works correctly
[
[
@@ -811,7 +840,7 @@ class TestLegacyVersion:
]
for i, x in enumerate(VERSIONS + LEGACY_VERSIONS)
]
- )
+ ),
)
def test_comparison_true(self, left, right, op):
assert op(LegacyVersion(left), LegacyVersion(right))
@@ -831,20 +860,17 @@ class TestLegacyVersion:
if i != j
]
for i, x in enumerate(VERSIONS + LEGACY_VERSIONS)
- ] +
- # Verify that the not equal (!=) operator works correctly
- [
- [(x, x, operator.ne) for x in VERSIONS + LEGACY_VERSIONS]
]
- )
+ +
+ # Verify that the not equal (!=) operator works correctly
+ [[(x, x, operator.ne) for x in VERSIONS + LEGACY_VERSIONS]]
+ ),
)
def test_comparison_false(self, left, right, op):
assert not op(LegacyVersion(left), LegacyVersion(right))
@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(**{"__{0}__".format(op): lambda other: NotImplemented})
assert getattr(operator, op)(LegacyVersion("1"), other) is expected
diff --git a/tox.ini b/tox.ini
index 5af0a58..c44acad 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,16 +1,12 @@
[tox]
-envlist = py27,pypy,py34,py35,py36,py37,docs,pep8,py2pep8
+envlist = py27,pypy,pypy3,py34,py35,py36,py37,docs,lint
[testenv]
deps =
coverage
pretend
pytest
- https://github.com/pypa/pip/archive/master.zip#egg=pip
-# The --ignore-installed install options is needed to install pip url
-# (needed to issue #95). This can be removed once pip 10.0 is out.
-install_command =
- pip install {opts} {packages} --ignore-installed
+ pip>=9.0.2
commands =
python -m coverage run --source packaging/ -m pytest --strict {posargs}
python -m coverage report -m --fail-under 100
@@ -20,7 +16,7 @@ commands =
py.test --capture=no --strict {posargs}
[testenv:docs]
-basepython = python3.6
+basepython = python3
deps =
sphinx
sphinx_rtd_theme
@@ -29,19 +25,15 @@ commands =
sphinx-build -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex
sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
-[testenv:pep8]
-basepython = python3.4
+[testenv:lint]
+basepython = python3
deps =
flake8
pep8-naming
-commands = flake8 .
-
-[testenv:py2pep8]
-basepython = python2.7
-deps =
- flake8
- pep8-naming
-commands = flake8 .
+ black
+commands =
+ flake8 .
+ black --check .
[testenv:packaging]
deps =
@@ -54,3 +46,4 @@ commands =
[flake8]
exclude = .tox,*.egg
select = E,W,F,N
+ignore = W504