Imported Upstream version 19.0 upstream/19.0
authorJinWang An <jinwang.an@samsung.com>
Mon, 14 Dec 2020 06:38:43 +0000 (15:38 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 14 Dec 2020 06:38:43 +0000 (15:38 +0900)
29 files changed:
.flake8 [new file with mode: 0644]
.gitignore
.travis.yml
CHANGELOG.rst
MANIFEST.in
dev-requirements.txt
docs/conf.py
docs/development/getting-started.rst
docs/development/index.rst
docs/development/release-process.rst [new file with mode: 0644]
docs/development/submitting-patches.rst
docs/markers.rst
packaging/__about__.py
packaging/__init__.py
packaging/_compat.py
packaging/_structures.py
packaging/markers.py
packaging/requirements.py
packaging/specifiers.py
packaging/utils.py
packaging/version.py
setup.py
tasks/check.py
tests/test_markers.py
tests/test_requirements.py
tests/test_specifiers.py
tests/test_utils.py
tests/test_version.py
tox.ini

diff --git a/.flake8 b/.flake8
new file mode 100644 (file)
index 0000000..b5a35be
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 88
+ignore = E203,W503,W504
index 608bf48..126d348 100644 (file)
@@ -6,9 +6,10 @@
 .cache/
 .coverage
 .idea
+.venv
 
 __pycache__/
 _build/
 build/
 dist/
-htmlcov/
\ No newline at end of file
+htmlcov/
index f4b2ced..f6c835e 100644 (file)
@@ -1,28 +1,26 @@
 language: python
 cache: pip
+python: 3.6
+env: TOXENV=py
 
 matrix:
   include:
     - python: 2.7
-      env: TOXENV=py27
+    - python: pypy
+    - python: pypy3
     - python: 3.4
-      env: TOXENV=py34
     - python: 3.5
-      env: TOXENV=py35
-    - python: 3.6
-      env: TOXENV=py36
-    - python: nightly
-      env: TOXENV=py37
-    - python: pypy
-      env: TOXENV=pypy
-    - env: TOXENV=pep8
-    - env: TOXENV=py2pep8
     - python: 3.6
-      env: TOXENV=docs
+    - python: 3.7
+      dist: xenial
+    - python: 3.8-dev
+      dist: xenial
+    - env: TOXENV=lint
+    - env: TOXENV=docs
     - env: TOXENV=packaging
 
   allow_failures:
-    - python: nightly
+    - python: 3.8-dev
 
 install:
   - pip install tox
index 0fe202a..dcdf54c 100644 (file)
@@ -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
 ~~~~~~~~~~~~~~~~~
 
index c713aff..36fcbd8 100644 (file)
@@ -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 *
index dd93cc2..e4cc498 100644 (file)
@@ -1,4 +1,6 @@
 # Install our development requirements
+pretend
+pytest
 tox
 
 # Install packaging itself
index cf1d01e..48f2354 100644 (file)
@@ -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"
index 7e6b45d..aa47fe6 100644 (file)
@@ -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
index f81799a..c0aea8a 100644 (file)
@@ -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 (file)
index 0000000..95375b2
--- /dev/null
@@ -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``.
index a2edde7..875b790 100644 (file)
@@ -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/
index 7613dae..ad25361 100644 (file)
@@ -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
index 21fc6ce..7481c9e 100644 (file)
@@ -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__
index 5ee6220..a0cf67d 100644 (file)
@@ -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__",
 ]
index 210bb80..25da473 100644 (file)
@@ -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", (), {})
index e9fc4a0..68dcca6 100644 (file)
@@ -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"
 
index 5fdf510..eff5abb 100644 (file)
@@ -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):
index e8008a6..4d9688b 100644 (file)
@@ -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))
index 4c79899..743576a 100644 (file)
@@ -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
index 4b94a82..8841878 100644 (file)
@@ -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:
index 6ed5cbb..95157a1 100644 (file)
@@ -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
index 7a1462b..23007c7 100644 (file)
--- 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"],
 )
index 6138f41..930ab49 100644 (file)
@@ -47,10 +47,12 @@ def pep440(cached=False):
         bar = progress.bar.ShadyBar("Fetching Versions")
         client = xmlrpc_client.Server("https://pypi.python.org/pypi")
 
-        data = dict([
-            (project, client.package_releases(project, True))
-            for project in bar.iter(client.list_packages())
-        ])
+        data = dict(
+            [
+                (project, client.package_releases(project, True))
+                for project in bar.iter(client.list_packages())
+            ]
+        )
 
         os.makedirs(os.path.dirname(cache_path), exist_ok=True)
         with open(cache_path, "w") as fp:
@@ -62,17 +64,18 @@ def pep440(cached=False):
     # Determine the total number of versions which are compatible with the
     # current routine
     parsed_versions = [
-        _parse_version(v)
-        for v in all_versions
-        if _parse_version(v) is not None
+        _parse_version(v) for v in all_versions if _parse_version(v) is not None
     ]
 
     # Determine a list of projects that sort exactly the same between
     # pkg_resources and PEP 440
     compatible_sorting = [
-        project for project, versions in data.items()
-        if (sorted(versions, key=pkg_resources.parse_version) ==
-            sorted((x for x in versions if _parse_version(x)), key=Version))
+        project
+        for project, versions in data.items()
+        if (
+            sorted(versions, key=pkg_resources.parse_version)
+            == sorted((x for x in versions if _parse_version(x)), key=Version)
+        )
     ]
 
     # Determine a list of projects that sort exactly the same between
@@ -83,25 +86,29 @@ def pep440(cached=False):
             (p, [v for v in vs if _parse_version(v) is not None])
             for p, vs in data.items()
         )
-        if (sorted(versions, key=pkg_resources.parse_version) ==
-            sorted(versions, key=Version))
+        if (
+            sorted(versions, key=pkg_resources.parse_version)
+            == sorted(versions, key=Version)
+        )
     ]
 
     # Determine a list of projects which do not have any versions that are
     # valid with PEP 440 and which have any versions registered
     only_invalid_versions = [
-        project for project, versions in data.items()
-        if (versions and not
-            [v for v in versions if _parse_version(v) is not None])
+        project
+        for project, versions in data.items()
+        if (versions and not [v for v in versions if _parse_version(v) is not None])
     ]
 
     # Determine a list of projects which have matching latest versions between
     # pkg_resources and PEP 440
     differing_latest_versions = [
-        project for project, versions in data.items()
-        if (sorted(versions, key=pkg_resources.parse_version)[-1:] !=
-            sorted(
-                (x for x in versions if _parse_version(x)), key=Version)[-1:])
+        project
+        for project, versions in data.items()
+        if (
+            sorted(versions, key=pkg_resources.parse_version)[-1:]
+            != sorted((x for x in versions if _parse_version(x)), key=Version)[-1:]
+        )
     ]
 
     # Print out our findings
@@ -114,9 +121,7 @@ def pep440(cached=False):
     )
     print(
         "Total Sorting Compatibility (Unfiltered): {}/{} ({:.2%})".format(
-            len(compatible_sorting),
-            len(data),
-            len(compatible_sorting) / len(data),
+            len(compatible_sorting), len(data), len(compatible_sorting) / len(data)
         )
     )
     print(
index 0e2d96a..e271ad6 100644 (file)
@@ -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)
         ],
index cd42aa9..0e56cd9 100644 (file)
@@ -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
index adc10e5..a3feecc 100644 (file)
@@ -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))
index 2fcb30a..8458ffb 100644 (file)
@@ -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
index ba4a11d..4e8079d 100644 (file)
@@ -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 (file)
--- 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