fix
This commit is contained in:
3720
venv/lib/python3.11/site-packages/pkg_resources/__init__.py
Normal file
3720
venv/lib/python3.11/site-packages/pkg_resources/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
424
venv/lib/python3.11/site-packages/pkg_resources/api_tests.txt
Normal file
424
venv/lib/python3.11/site-packages/pkg_resources/api_tests.txt
Normal file
@@ -0,0 +1,424 @@
|
||||
Pluggable Distributions of Python Software
|
||||
==========================================
|
||||
|
||||
Distributions
|
||||
-------------
|
||||
|
||||
A "Distribution" is a collection of files that represent a "Release" of a
|
||||
"Project" as of a particular point in time, denoted by a
|
||||
"Version"::
|
||||
|
||||
>>> import sys, pkg_resources
|
||||
>>> from pkg_resources import Distribution
|
||||
>>> Distribution(project_name="Foo", version="1.2")
|
||||
Foo 1.2
|
||||
|
||||
Distributions have a location, which can be a filename, URL, or really anything
|
||||
else you care to use::
|
||||
|
||||
>>> dist = Distribution(
|
||||
... location="http://example.com/something",
|
||||
... project_name="Bar", version="0.9"
|
||||
... )
|
||||
|
||||
>>> dist
|
||||
Bar 0.9 (http://example.com/something)
|
||||
|
||||
|
||||
Distributions have various introspectable attributes::
|
||||
|
||||
>>> dist.location
|
||||
'http://example.com/something'
|
||||
|
||||
>>> dist.project_name
|
||||
'Bar'
|
||||
|
||||
>>> dist.version
|
||||
'0.9'
|
||||
|
||||
>>> dist.py_version == '{}.{}'.format(*sys.version_info)
|
||||
True
|
||||
|
||||
>>> print(dist.platform)
|
||||
None
|
||||
|
||||
Including various computed attributes::
|
||||
|
||||
>>> from pkg_resources import parse_version
|
||||
>>> dist.parsed_version == parse_version(dist.version)
|
||||
True
|
||||
|
||||
>>> dist.key # case-insensitive form of the project name
|
||||
'bar'
|
||||
|
||||
Distributions are compared (and hashed) by version first::
|
||||
|
||||
>>> Distribution(version='1.0') == Distribution(version='1.0')
|
||||
True
|
||||
>>> Distribution(version='1.0') == Distribution(version='1.1')
|
||||
False
|
||||
>>> Distribution(version='1.0') < Distribution(version='1.1')
|
||||
True
|
||||
|
||||
but also by project name (case-insensitive), platform, Python version,
|
||||
location, etc.::
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="Foo",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="foo",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(project_name="Foo",version="1.0") == \
|
||||
... Distribution(project_name="Foo",version="1.1")
|
||||
False
|
||||
|
||||
>>> Distribution(project_name="Foo",py_version="2.3",version="1.0") == \
|
||||
... Distribution(project_name="Foo",py_version="2.4",version="1.0")
|
||||
False
|
||||
|
||||
>>> Distribution(location="spam",version="1.0") == \
|
||||
... Distribution(location="spam",version="1.0")
|
||||
True
|
||||
|
||||
>>> Distribution(location="spam",version="1.0") == \
|
||||
... Distribution(location="baz",version="1.0")
|
||||
False
|
||||
|
||||
|
||||
|
||||
Hash and compare distribution by prio/plat
|
||||
|
||||
Get version from metadata
|
||||
provider capabilities
|
||||
egg_name()
|
||||
as_requirement()
|
||||
from_location, from_filename (w/path normalization)
|
||||
|
||||
Releases may have zero or more "Requirements", which indicate
|
||||
what releases of another project the release requires in order to
|
||||
function. A Requirement names the other project, expresses some criteria
|
||||
as to what releases of that project are acceptable, and lists any "Extras"
|
||||
that the requiring release may need from that project. (An Extra is an
|
||||
optional feature of a Release, that can only be used if its additional
|
||||
Requirements are satisfied.)
|
||||
|
||||
|
||||
|
||||
The Working Set
|
||||
---------------
|
||||
|
||||
A collection of active distributions is called a Working Set. Note that a
|
||||
Working Set can contain any importable distribution, not just pluggable ones.
|
||||
For example, the Python standard library is an importable distribution that
|
||||
will usually be part of the Working Set, even though it is not pluggable.
|
||||
Similarly, when you are doing development work on a project, the files you are
|
||||
editing are also a Distribution. (And, with a little attention to the
|
||||
directory names used, and including some additional metadata, such a
|
||||
"development distribution" can be made pluggable as well.)
|
||||
|
||||
>>> from pkg_resources import WorkingSet
|
||||
|
||||
A working set's entries are the sys.path entries that correspond to the active
|
||||
distributions. By default, the working set's entries are the items on
|
||||
``sys.path``::
|
||||
|
||||
>>> ws = WorkingSet()
|
||||
>>> ws.entries == sys.path
|
||||
True
|
||||
|
||||
But you can also create an empty working set explicitly, and add distributions
|
||||
to it::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(dist)
|
||||
>>> ws.entries
|
||||
['http://example.com/something']
|
||||
>>> dist in ws
|
||||
True
|
||||
>>> Distribution('foo',version="") in ws
|
||||
False
|
||||
|
||||
And you can iterate over its distributions::
|
||||
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
Adding the same distribution more than once is a no-op::
|
||||
|
||||
>>> ws.add(dist)
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
For that matter, adding multiple distributions for the same project also does
|
||||
nothing, because a working set can only hold one active distribution per
|
||||
project -- the first one added to it::
|
||||
|
||||
>>> ws.add(
|
||||
... Distribution(
|
||||
... 'http://example.com/something', project_name="Bar",
|
||||
... version="7.2"
|
||||
... )
|
||||
... )
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
You can append a path entry to a working set using ``add_entry()``::
|
||||
|
||||
>>> ws.entries
|
||||
['http://example.com/something']
|
||||
>>> ws.add_entry(pkg_resources.__file__)
|
||||
>>> ws.entries
|
||||
['http://example.com/something', '...pkg_resources...']
|
||||
|
||||
Multiple additions result in multiple entries, even if the entry is already in
|
||||
the working set (because ``sys.path`` can contain the same entry more than
|
||||
once)::
|
||||
|
||||
>>> ws.add_entry(pkg_resources.__file__)
|
||||
>>> ws.entries
|
||||
['...example.com...', '...pkg_resources...', '...pkg_resources...']
|
||||
|
||||
And you can specify the path entry a distribution was found under, using the
|
||||
optional second parameter to ``add()``::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(dist,"foo")
|
||||
>>> ws.entries
|
||||
['foo']
|
||||
|
||||
But even if a distribution is found under multiple path entries, it still only
|
||||
shows up once when iterating the working set:
|
||||
|
||||
>>> ws.add_entry(ws.entries[0])
|
||||
>>> list(ws)
|
||||
[Bar 0.9 (http://example.com/something)]
|
||||
|
||||
You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
|
||||
|
||||
>>> from pkg_resources import Requirement
|
||||
>>> print(ws.find(Requirement.parse("Foo==1.0"))) # no match, return None
|
||||
None
|
||||
|
||||
>>> ws.find(Requirement.parse("Bar==0.9")) # match, return distribution
|
||||
Bar 0.9 (http://example.com/something)
|
||||
|
||||
Note that asking for a conflicting version of a distribution already in a
|
||||
working set triggers a ``pkg_resources.VersionConflict`` error:
|
||||
|
||||
>>> try:
|
||||
... ws.find(Requirement.parse("Bar==1.0"))
|
||||
... except pkg_resources.VersionConflict as exc:
|
||||
... print(str(exc))
|
||||
... else:
|
||||
... raise AssertionError("VersionConflict was not raised")
|
||||
(Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0'))
|
||||
|
||||
You can subscribe a callback function to receive notifications whenever a new
|
||||
distribution is added to a working set. The callback is immediately invoked
|
||||
once for each existing distribution in the working set, and then is called
|
||||
again for new distributions added thereafter::
|
||||
|
||||
>>> def added(dist): print("Added %s" % dist)
|
||||
>>> ws.subscribe(added)
|
||||
Added Bar 0.9
|
||||
>>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12")
|
||||
>>> ws.add(foo12)
|
||||
Added Foo 1.2
|
||||
|
||||
Note, however, that only the first distribution added for a given project name
|
||||
will trigger a callback, even during the initial ``subscribe()`` callback::
|
||||
|
||||
>>> foo14 = Distribution(project_name="Foo", version="1.4", location="f14")
|
||||
>>> ws.add(foo14) # no callback, because Foo 1.2 is already active
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.add(foo12)
|
||||
>>> ws.add(foo14)
|
||||
>>> ws.subscribe(added)
|
||||
Added Foo 1.2
|
||||
|
||||
And adding a callback more than once has no effect, either::
|
||||
|
||||
>>> ws.subscribe(added) # no callbacks
|
||||
|
||||
# and no double-callbacks on subsequent additions, either
|
||||
>>> just_a_test = Distribution(project_name="JustATest", version="0.99")
|
||||
>>> ws.add(just_a_test)
|
||||
Added JustATest 0.99
|
||||
|
||||
|
||||
Finding Plugins
|
||||
---------------
|
||||
|
||||
``WorkingSet`` objects can be used to figure out what plugins in an
|
||||
``Environment`` can be loaded without any resolution errors::
|
||||
|
||||
>>> from pkg_resources import Environment
|
||||
|
||||
>>> plugins = Environment([]) # normally, a list of plugin directories
|
||||
>>> plugins.add(foo12)
|
||||
>>> plugins.add(foo14)
|
||||
>>> plugins.add(just_a_test)
|
||||
|
||||
In the simplest case, we just get the newest version of each distribution in
|
||||
the plugin environment::
|
||||
|
||||
>>> ws = WorkingSet([])
|
||||
>>> ws.find_plugins(plugins)
|
||||
([JustATest 0.99, Foo 1.4 (f14)], {})
|
||||
|
||||
But if there's a problem with a version conflict or missing requirements, the
|
||||
method falls back to older versions, and the error info dict will contain an
|
||||
exception instance for each unloadable plugin::
|
||||
|
||||
>>> ws.add(foo12) # this will conflict with Foo 1.4
|
||||
>>> ws.find_plugins(plugins)
|
||||
([JustATest 0.99, Foo 1.2 (f12)], {Foo 1.4 (f14): VersionConflict(...)})
|
||||
|
||||
But if you disallow fallbacks, the failed plugin will be skipped instead of
|
||||
trying older versions::
|
||||
|
||||
>>> ws.find_plugins(plugins, fallback=False)
|
||||
([JustATest 0.99], {Foo 1.4 (f14): VersionConflict(...)})
|
||||
|
||||
|
||||
|
||||
Platform Compatibility Rules
|
||||
----------------------------
|
||||
|
||||
On the Mac, there are potential compatibility issues for modules compiled
|
||||
on newer versions of macOS than what the user is running. Additionally,
|
||||
macOS will soon have two platforms to contend with: Intel and PowerPC.
|
||||
|
||||
Basic equality works as on other platforms::
|
||||
|
||||
>>> from pkg_resources import compatible_platforms as cp
|
||||
>>> reqd = 'macosx-10.4-ppc'
|
||||
>>> cp(reqd, reqd)
|
||||
True
|
||||
>>> cp("win32", reqd)
|
||||
False
|
||||
|
||||
Distributions made on other machine types are not compatible::
|
||||
|
||||
>>> cp("macosx-10.4-i386", reqd)
|
||||
False
|
||||
|
||||
Distributions made on earlier versions of the OS are compatible, as
|
||||
long as they are from the same top-level version. The patchlevel version
|
||||
number does not matter::
|
||||
|
||||
>>> cp("macosx-10.4-ppc", reqd)
|
||||
True
|
||||
>>> cp("macosx-10.3-ppc", reqd)
|
||||
True
|
||||
>>> cp("macosx-10.5-ppc", reqd)
|
||||
False
|
||||
>>> cp("macosx-9.5-ppc", reqd)
|
||||
False
|
||||
|
||||
Backwards compatibility for packages made via earlier versions of
|
||||
setuptools is provided as well::
|
||||
|
||||
>>> cp("darwin-8.2.0-Power_Macintosh", reqd)
|
||||
True
|
||||
>>> cp("darwin-7.2.0-Power_Macintosh", reqd)
|
||||
True
|
||||
>>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc")
|
||||
False
|
||||
|
||||
|
||||
Environment Markers
|
||||
-------------------
|
||||
|
||||
>>> from pkg_resources import invalid_marker as im, evaluate_marker as em
|
||||
>>> import os
|
||||
|
||||
>>> print(im("sys_platform"))
|
||||
Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
|
||||
sys_platform
|
||||
^
|
||||
|
||||
>>> print(im("sys_platform=="))
|
||||
Expected a marker variable or quoted string
|
||||
sys_platform==
|
||||
^
|
||||
|
||||
>>> print(im("sys_platform=='win32'"))
|
||||
False
|
||||
|
||||
>>> print(im("sys=='x'"))
|
||||
Expected a marker variable or quoted string
|
||||
sys=='x'
|
||||
^
|
||||
|
||||
>>> print(im("(extra)"))
|
||||
Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
|
||||
(extra)
|
||||
^
|
||||
|
||||
>>> print(im("(extra"))
|
||||
Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
|
||||
(extra
|
||||
^
|
||||
|
||||
>>> print(im("os.open('foo')=='y'"))
|
||||
Expected a marker variable or quoted string
|
||||
os.open('foo')=='y'
|
||||
^
|
||||
|
||||
>>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit!
|
||||
Expected a marker variable or quoted string
|
||||
'x'=='y' and os.open('foo')=='y'
|
||||
^
|
||||
|
||||
>>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit!
|
||||
Expected a marker variable or quoted string
|
||||
'x'=='x' or os.open('foo')=='y'
|
||||
^
|
||||
|
||||
>>> print(im("r'x'=='x'"))
|
||||
Expected a marker variable or quoted string
|
||||
r'x'=='x'
|
||||
^
|
||||
|
||||
>>> print(im("'''x'''=='x'"))
|
||||
Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
|
||||
'''x'''=='x'
|
||||
^
|
||||
|
||||
>>> print(im('"""x"""=="x"'))
|
||||
Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in
|
||||
"""x"""=="x"
|
||||
^
|
||||
|
||||
>>> print(im(r"x\n=='x'"))
|
||||
Expected a marker variable or quoted string
|
||||
x\n=='x'
|
||||
^
|
||||
|
||||
>>> print(im("os.open=='y'"))
|
||||
Expected a marker variable or quoted string
|
||||
os.open=='y'
|
||||
^
|
||||
|
||||
>>> em("sys_platform=='win32'") == (sys.platform=='win32')
|
||||
True
|
||||
|
||||
>>> em("python_version >= '2.7'")
|
||||
True
|
||||
|
||||
>>> em("python_version > '2.6'")
|
||||
True
|
||||
|
||||
>>> im("implementation_name=='cpython'")
|
||||
False
|
||||
|
||||
>>> im("platform_python_implementation=='CPython'")
|
||||
False
|
||||
|
||||
>>> im("implementation_version=='3.5.1'")
|
||||
False
|
||||
@@ -0,0 +1,56 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import pkg_resources
|
||||
|
||||
TESTS_DATA_DIR = Path(__file__).parent / 'data'
|
||||
|
||||
|
||||
class TestFindDistributions:
|
||||
@pytest.fixture
|
||||
def target_dir(self, tmpdir):
|
||||
target_dir = tmpdir.mkdir('target')
|
||||
# place a .egg named directory in the target that is not an egg:
|
||||
target_dir.mkdir('not.an.egg')
|
||||
return target_dir
|
||||
|
||||
def test_non_egg_dir_named_egg(self, target_dir):
|
||||
dists = pkg_resources.find_distributions(str(target_dir))
|
||||
assert not list(dists)
|
||||
|
||||
def test_standalone_egg_directory(self, target_dir):
|
||||
shutil.copytree(
|
||||
TESTS_DATA_DIR / 'my-test-package_unpacked-egg',
|
||||
target_dir,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
dists = pkg_resources.find_distributions(str(target_dir))
|
||||
assert [dist.project_name for dist in dists] == ['my-test-package']
|
||||
dists = pkg_resources.find_distributions(str(target_dir), only=True)
|
||||
assert not list(dists)
|
||||
|
||||
def test_zipped_egg(self, target_dir):
|
||||
shutil.copytree(
|
||||
TESTS_DATA_DIR / 'my-test-package_zipped-egg',
|
||||
target_dir,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
dists = pkg_resources.find_distributions(str(target_dir))
|
||||
assert [dist.project_name for dist in dists] == ['my-test-package']
|
||||
dists = pkg_resources.find_distributions(str(target_dir), only=True)
|
||||
assert not list(dists)
|
||||
|
||||
def test_zipped_sdist_one_level_removed(self, target_dir):
|
||||
shutil.copytree(
|
||||
TESTS_DATA_DIR / 'my-test-package-zip', target_dir, dirs_exist_ok=True
|
||||
)
|
||||
dists = pkg_resources.find_distributions(
|
||||
str(target_dir / "my-test-package.zip")
|
||||
)
|
||||
assert [dist.project_name for dist in dists] == ['my-test-package']
|
||||
dists = pkg_resources.find_distributions(
|
||||
str(target_dir / "my-test-package.zip"), only=True
|
||||
)
|
||||
assert not list(dists)
|
||||
@@ -0,0 +1,54 @@
|
||||
import platform
|
||||
from inspect import cleandoc
|
||||
|
||||
import jaraco.path
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
# For the sake of simplicity this test uses fixtures defined in
|
||||
# `setuptools.test.fixtures`,
|
||||
# and it also exercise conditions considered deprecated...
|
||||
# So if needed this test can be deleted.
|
||||
@pytest.mark.skipif(
|
||||
platform.system() != "Linux",
|
||||
reason="only demonstrated to fail on Linux in #4399",
|
||||
)
|
||||
def test_interop_pkg_resources_iter_entry_points(tmp_path, venv):
|
||||
"""
|
||||
Importing pkg_resources.iter_entry_points on console_scripts
|
||||
seems to cause trouble with zope-interface, when deprecates installation method
|
||||
is used. See #4399.
|
||||
"""
|
||||
project = {
|
||||
"pkg": {
|
||||
"foo.py": cleandoc(
|
||||
"""
|
||||
from pkg_resources import iter_entry_points
|
||||
|
||||
def bar():
|
||||
print("Print me if you can")
|
||||
"""
|
||||
),
|
||||
"setup.py": cleandoc(
|
||||
"""
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
install_requires=["zope-interface==6.4.post2"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"foo=foo:bar",
|
||||
],
|
||||
},
|
||||
)
|
||||
"""
|
||||
),
|
||||
}
|
||||
}
|
||||
jaraco.path.build(project, prefix=tmp_path)
|
||||
cmd = ["pip", "install", "-e", ".", "--no-use-pep517"]
|
||||
venv.run(cmd, cwd=tmp_path / "pkg") # Needs this version of pkg_resources installed
|
||||
out = venv.run(["foo"])
|
||||
assert "Print me if you can" in out
|
||||
@@ -0,0 +1,8 @@
|
||||
from unittest import mock
|
||||
|
||||
from pkg_resources import evaluate_marker
|
||||
|
||||
|
||||
@mock.patch('platform.python_version', return_value='2.7.10')
|
||||
def test_ordering(python_version_mock):
|
||||
assert evaluate_marker("python_full_version > '2.7.3'") is True
|
||||
@@ -0,0 +1,427 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import datetime
|
||||
import os
|
||||
import plistlib
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
import pkg_resources
|
||||
from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
|
||||
|
||||
import distutils.command.install_egg_info
|
||||
import distutils.dist
|
||||
|
||||
|
||||
class EggRemover(str):
|
||||
def __call__(self):
|
||||
if self in sys.path:
|
||||
sys.path.remove(self)
|
||||
if os.path.exists(self):
|
||||
os.remove(self)
|
||||
|
||||
|
||||
class TestZipProvider:
|
||||
finalizers: list[EggRemover] = []
|
||||
|
||||
ref_time = datetime.datetime(2013, 5, 12, 13, 25, 0)
|
||||
"A reference time for a file modification"
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"create a zip egg and add it to sys.path"
|
||||
egg = tempfile.NamedTemporaryFile(suffix='.egg', delete=False)
|
||||
zip_egg = zipfile.ZipFile(egg, 'w')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'mod.py'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'x = 3\n')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'data.dat'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'hello, world!')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'subdir/mod2.py'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'x = 6\n')
|
||||
zip_info = zipfile.ZipInfo()
|
||||
zip_info.filename = 'subdir/data2.dat'
|
||||
zip_info.date_time = cls.ref_time.timetuple()
|
||||
zip_egg.writestr(zip_info, 'goodbye, world!')
|
||||
zip_egg.close()
|
||||
egg.close()
|
||||
|
||||
sys.path.append(egg.name)
|
||||
subdir = os.path.join(egg.name, 'subdir')
|
||||
sys.path.append(subdir)
|
||||
cls.finalizers.append(EggRemover(subdir))
|
||||
cls.finalizers.append(EggRemover(egg.name))
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
for finalizer in cls.finalizers:
|
||||
finalizer()
|
||||
|
||||
def test_resource_listdir(self):
|
||||
import mod # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
|
||||
zp = pkg_resources.ZipProvider(mod)
|
||||
|
||||
expected_root = ['data.dat', 'mod.py', 'subdir']
|
||||
assert sorted(zp.resource_listdir('')) == expected_root
|
||||
|
||||
expected_subdir = ['data2.dat', 'mod2.py']
|
||||
assert sorted(zp.resource_listdir('subdir')) == expected_subdir
|
||||
assert sorted(zp.resource_listdir('subdir/')) == expected_subdir
|
||||
|
||||
assert zp.resource_listdir('nonexistent') == []
|
||||
assert zp.resource_listdir('nonexistent/') == []
|
||||
|
||||
import mod2 # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
|
||||
zp2 = pkg_resources.ZipProvider(mod2)
|
||||
|
||||
assert sorted(zp2.resource_listdir('')) == expected_subdir
|
||||
|
||||
assert zp2.resource_listdir('subdir') == []
|
||||
assert zp2.resource_listdir('subdir/') == []
|
||||
|
||||
def test_resource_filename_rewrites_on_change(self):
|
||||
"""
|
||||
If a previous call to get_resource_filename has saved the file, but
|
||||
the file has been subsequently mutated with different file of the
|
||||
same size and modification time, it should not be overwritten on a
|
||||
subsequent call to get_resource_filename.
|
||||
"""
|
||||
import mod # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
|
||||
manager = pkg_resources.ResourceManager()
|
||||
zp = pkg_resources.ZipProvider(mod)
|
||||
filename = zp.get_resource_filename(manager, 'data.dat')
|
||||
actual = datetime.datetime.fromtimestamp(os.stat(filename).st_mtime)
|
||||
assert actual == self.ref_time
|
||||
f = open(filename, 'w', encoding="utf-8")
|
||||
f.write('hello, world?')
|
||||
f.close()
|
||||
ts = self.ref_time.timestamp()
|
||||
os.utime(filename, (ts, ts))
|
||||
filename = zp.get_resource_filename(manager, 'data.dat')
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
assert f.read() == 'hello, world!'
|
||||
manager.cleanup_resources()
|
||||
|
||||
|
||||
class TestResourceManager:
|
||||
def test_get_cache_path(self):
|
||||
mgr = pkg_resources.ResourceManager()
|
||||
path = mgr.get_cache_path('foo')
|
||||
type_ = str(type(path))
|
||||
message = "Unexpected type from get_cache_path: " + type_
|
||||
assert isinstance(path, str), message
|
||||
|
||||
def test_get_cache_path_race(self, tmpdir):
|
||||
# Patch to os.path.isdir to create a race condition
|
||||
def patched_isdir(dirname, unpatched_isdir=pkg_resources.isdir):
|
||||
patched_isdir.dirnames.append(dirname)
|
||||
|
||||
was_dir = unpatched_isdir(dirname)
|
||||
if not was_dir:
|
||||
os.makedirs(dirname)
|
||||
return was_dir
|
||||
|
||||
patched_isdir.dirnames = []
|
||||
|
||||
# Get a cache path with a "race condition"
|
||||
mgr = pkg_resources.ResourceManager()
|
||||
mgr.set_extraction_path(str(tmpdir))
|
||||
|
||||
archive_name = os.sep.join(('foo', 'bar', 'baz'))
|
||||
with mock.patch.object(pkg_resources, 'isdir', new=patched_isdir):
|
||||
mgr.get_cache_path(archive_name)
|
||||
|
||||
# Because this test relies on the implementation details of this
|
||||
# function, these assertions are a sentinel to ensure that the
|
||||
# test suite will not fail silently if the implementation changes.
|
||||
called_dirnames = patched_isdir.dirnames
|
||||
assert len(called_dirnames) == 2
|
||||
assert called_dirnames[0].split(os.sep)[-2:] == ['foo', 'bar']
|
||||
assert called_dirnames[1].split(os.sep)[-1:] == ['foo']
|
||||
|
||||
"""
|
||||
Tests to ensure that pkg_resources runs independently from setuptools.
|
||||
"""
|
||||
|
||||
def test_setuptools_not_imported(self):
|
||||
"""
|
||||
In a separate Python environment, import pkg_resources and assert
|
||||
that action doesn't cause setuptools to be imported.
|
||||
"""
|
||||
lines = (
|
||||
'import pkg_resources',
|
||||
'import sys',
|
||||
('assert "setuptools" not in sys.modules, "setuptools was imported"'),
|
||||
)
|
||||
cmd = [sys.executable, '-c', '; '.join(lines)]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def make_test_distribution(metadata_path, metadata):
|
||||
"""
|
||||
Make a test Distribution object, and return it.
|
||||
|
||||
:param metadata_path: the path to the metadata file that should be
|
||||
created. This should be inside a distribution directory that should
|
||||
also be created. For example, an argument value might end with
|
||||
"<project>.dist-info/METADATA".
|
||||
:param metadata: the desired contents of the metadata file, as bytes.
|
||||
"""
|
||||
dist_dir = os.path.dirname(metadata_path)
|
||||
os.mkdir(dist_dir)
|
||||
with open(metadata_path, 'wb') as f:
|
||||
f.write(metadata)
|
||||
dists = list(pkg_resources.distributions_from_metadata(dist_dir))
|
||||
(dist,) = dists
|
||||
|
||||
return dist
|
||||
|
||||
|
||||
def test_get_metadata__bad_utf8(tmpdir):
|
||||
"""
|
||||
Test a metadata file with bytes that can't be decoded as utf-8.
|
||||
"""
|
||||
filename = 'METADATA'
|
||||
# Convert the tmpdir LocalPath object to a string before joining.
|
||||
metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename)
|
||||
# Encode a non-ascii string with the wrong encoding (not utf-8).
|
||||
metadata = 'née'.encode('iso-8859-1')
|
||||
dist = make_test_distribution(metadata_path, metadata=metadata)
|
||||
|
||||
with pytest.raises(UnicodeDecodeError) as excinfo:
|
||||
dist.get_metadata(filename)
|
||||
|
||||
exc = excinfo.value
|
||||
actual = str(exc)
|
||||
expected = (
|
||||
# The error message starts with "'utf-8' codec ..." However, the
|
||||
# spelling of "utf-8" can vary (e.g. "utf8") so we don't include it
|
||||
"codec can't decode byte 0xe9 in position 1: "
|
||||
'invalid continuation byte in METADATA file at path: '
|
||||
)
|
||||
assert expected in actual, 'actual: {}'.format(actual)
|
||||
assert actual.endswith(metadata_path), 'actual: {}'.format(actual)
|
||||
|
||||
|
||||
def make_distribution_no_version(tmpdir, basename):
|
||||
"""
|
||||
Create a distribution directory with no file containing the version.
|
||||
"""
|
||||
dist_dir = tmpdir / basename
|
||||
dist_dir.ensure_dir()
|
||||
# Make the directory non-empty so distributions_from_metadata()
|
||||
# will detect it and yield it.
|
||||
dist_dir.join('temp.txt').ensure()
|
||||
|
||||
dists = list(pkg_resources.distributions_from_metadata(dist_dir))
|
||||
assert len(dists) == 1
|
||||
(dist,) = dists
|
||||
|
||||
return dist, dist_dir
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("suffix", "expected_filename", "expected_dist_type"),
|
||||
[
|
||||
('egg-info', 'PKG-INFO', EggInfoDistribution),
|
||||
('dist-info', 'METADATA', DistInfoDistribution),
|
||||
],
|
||||
)
|
||||
@pytest.mark.xfail(
|
||||
sys.version_info[:2] == (3, 12) and sys.version_info.releaselevel != 'final',
|
||||
reason="https://github.com/python/cpython/issues/103632",
|
||||
)
|
||||
def test_distribution_version_missing(
|
||||
tmpdir, suffix, expected_filename, expected_dist_type
|
||||
):
|
||||
"""
|
||||
Test Distribution.version when the "Version" header is missing.
|
||||
"""
|
||||
basename = 'foo.{}'.format(suffix)
|
||||
dist, dist_dir = make_distribution_no_version(tmpdir, basename)
|
||||
|
||||
expected_text = ("Missing 'Version:' header and/or {} file at path: ").format(
|
||||
expected_filename
|
||||
)
|
||||
metadata_path = os.path.join(dist_dir, expected_filename)
|
||||
|
||||
# Now check the exception raised when the "version" attribute is accessed.
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
dist.version
|
||||
|
||||
err = str(excinfo.value)
|
||||
# Include a string expression after the assert so the full strings
|
||||
# will be visible for inspection on failure.
|
||||
assert expected_text in err, str((expected_text, err))
|
||||
|
||||
# Also check the args passed to the ValueError.
|
||||
msg, dist = excinfo.value.args
|
||||
assert expected_text in msg
|
||||
# Check that the message portion contains the path.
|
||||
assert metadata_path in msg, str((metadata_path, msg))
|
||||
assert type(dist) is expected_dist_type
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
sys.version_info[:2] == (3, 12) and sys.version_info.releaselevel != 'final',
|
||||
reason="https://github.com/python/cpython/issues/103632",
|
||||
)
|
||||
def test_distribution_version_missing_undetected_path():
|
||||
"""
|
||||
Test Distribution.version when the "Version" header is missing and
|
||||
the path can't be detected.
|
||||
"""
|
||||
# Create a Distribution object with no metadata argument, which results
|
||||
# in an empty metadata provider.
|
||||
dist = Distribution('/foo')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
dist.version
|
||||
|
||||
msg, dist = excinfo.value.args
|
||||
expected = (
|
||||
"Missing 'Version:' header and/or PKG-INFO file at path: [could not detect]"
|
||||
)
|
||||
assert msg == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('only', [False, True])
|
||||
def test_dist_info_is_not_dir(tmp_path, only):
|
||||
"""Test path containing a file with dist-info extension."""
|
||||
dist_info = tmp_path / 'foobar.dist-info'
|
||||
dist_info.touch()
|
||||
assert not pkg_resources.dist_factory(str(tmp_path), str(dist_info), only)
|
||||
|
||||
|
||||
def test_macos_vers_fallback(monkeypatch, tmp_path):
|
||||
"""Regression test for pkg_resources._macos_vers"""
|
||||
orig_open = builtins.open
|
||||
|
||||
# Pretend we need to use the plist file
|
||||
monkeypatch.setattr('platform.mac_ver', mock.Mock(return_value=('', (), '')))
|
||||
|
||||
# Create fake content for the fake plist file
|
||||
with open(tmp_path / 'fake.plist', 'wb') as fake_file:
|
||||
plistlib.dump({"ProductVersion": "11.4"}, fake_file)
|
||||
|
||||
# Pretend the fake file exists
|
||||
monkeypatch.setattr('os.path.exists', mock.Mock(return_value=True))
|
||||
|
||||
def fake_open(file, *args, **kwargs):
|
||||
return orig_open(tmp_path / 'fake.plist', *args, **kwargs)
|
||||
|
||||
# Ensure that the _macos_vers works correctly
|
||||
with mock.patch('builtins.open', mock.Mock(side_effect=fake_open)) as m:
|
||||
pkg_resources._macos_vers.cache_clear()
|
||||
assert pkg_resources._macos_vers() == ["11", "4"]
|
||||
pkg_resources._macos_vers.cache_clear()
|
||||
|
||||
m.assert_called()
|
||||
|
||||
|
||||
class TestDeepVersionLookupDistutils:
|
||||
@pytest.fixture
|
||||
def env(self, tmpdir):
|
||||
"""
|
||||
Create a package environment, similar to a virtualenv,
|
||||
in which packages are installed.
|
||||
"""
|
||||
|
||||
class Environment(str):
|
||||
pass
|
||||
|
||||
env = Environment(tmpdir)
|
||||
tmpdir.chmod(stat.S_IRWXU)
|
||||
subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
|
||||
env.paths = dict((dirname, str(tmpdir / dirname)) for dirname in subs)
|
||||
list(map(os.mkdir, env.paths.values()))
|
||||
return env
|
||||
|
||||
def create_foo_pkg(self, env, version):
|
||||
"""
|
||||
Create a foo package installed (distutils-style) to env.paths['lib']
|
||||
as version.
|
||||
"""
|
||||
ld = "This package has unicode metadata! ❄"
|
||||
attrs = dict(name='foo', version=version, long_description=ld)
|
||||
dist = distutils.dist.Distribution(attrs)
|
||||
iei_cmd = distutils.command.install_egg_info.install_egg_info(dist)
|
||||
iei_cmd.initialize_options()
|
||||
iei_cmd.install_dir = env.paths['lib']
|
||||
iei_cmd.finalize_options()
|
||||
iei_cmd.run()
|
||||
|
||||
def test_version_resolved_from_egg_info(self, env):
|
||||
version = '1.11.0.dev0+2329eae'
|
||||
self.create_foo_pkg(env, version)
|
||||
|
||||
# this requirement parsing will raise a VersionConflict unless the
|
||||
# .egg-info file is parsed (see #419 on BitBucket)
|
||||
req = pkg_resources.Requirement.parse('foo>=1.9')
|
||||
dist = pkg_resources.WorkingSet([env.paths['lib']]).find(req)
|
||||
assert dist.version == version
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("unnormalized", "normalized"),
|
||||
[
|
||||
('foo', 'foo'),
|
||||
('foo/', 'foo'),
|
||||
('foo/bar', 'foo/bar'),
|
||||
('foo/bar/', 'foo/bar'),
|
||||
],
|
||||
)
|
||||
def test_normalize_path_trailing_sep(self, unnormalized, normalized):
|
||||
"""Ensure the trailing slash is cleaned for path comparison.
|
||||
|
||||
See pypa/setuptools#1519.
|
||||
"""
|
||||
result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
|
||||
result_from_normalized = pkg_resources.normalize_path(normalized)
|
||||
assert result_from_unnormalized == result_from_normalized
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.path.normcase('A') != os.path.normcase('a'),
|
||||
reason='Testing case-insensitive filesystems.',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("unnormalized", "normalized"),
|
||||
[
|
||||
('MiXeD/CasE', 'mixed/case'),
|
||||
],
|
||||
)
|
||||
def test_normalize_path_normcase(self, unnormalized, normalized):
|
||||
"""Ensure mixed case is normalized on case-insensitive filesystems."""
|
||||
result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
|
||||
result_from_normalized = pkg_resources.normalize_path(normalized)
|
||||
assert result_from_unnormalized == result_from_normalized
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.path.sep != '\\',
|
||||
reason='Testing systems using backslashes as path separators.',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("unnormalized", "expected"),
|
||||
[
|
||||
('forward/slash', 'forward\\slash'),
|
||||
('forward/slash/', 'forward\\slash'),
|
||||
('backward\\slash\\', 'backward\\slash'),
|
||||
],
|
||||
)
|
||||
def test_normalize_path_backslash_sep(self, unnormalized, expected):
|
||||
"""Ensure path seps are cleaned on backslash path sep systems."""
|
||||
result = pkg_resources.normalize_path(unnormalized)
|
||||
assert result.endswith(expected)
|
||||
@@ -0,0 +1,869 @@
|
||||
import itertools
|
||||
import os
|
||||
import platform
|
||||
import string
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from packaging.specifiers import SpecifierSet
|
||||
|
||||
import pkg_resources
|
||||
from pkg_resources import (
|
||||
Distribution,
|
||||
EntryPoint,
|
||||
Requirement,
|
||||
VersionConflict,
|
||||
WorkingSet,
|
||||
parse_requirements,
|
||||
parse_version,
|
||||
safe_name,
|
||||
safe_version,
|
||||
)
|
||||
|
||||
|
||||
# from Python 3.6 docs. Available from itertools on Python 3.10
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = itertools.tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
class Metadata(pkg_resources.EmptyProvider):
|
||||
"""Mock object to return metadata as if from an on-disk distribution"""
|
||||
|
||||
def __init__(self, *pairs) -> None:
|
||||
self.metadata = dict(pairs)
|
||||
|
||||
def has_metadata(self, name) -> bool:
|
||||
return name in self.metadata
|
||||
|
||||
def get_metadata(self, name):
|
||||
return self.metadata[name]
|
||||
|
||||
def get_metadata_lines(self, name):
|
||||
return pkg_resources.yield_lines(self.get_metadata(name))
|
||||
|
||||
|
||||
dist_from_fn = pkg_resources.Distribution.from_filename
|
||||
|
||||
|
||||
class TestDistro:
|
||||
def testCollection(self):
|
||||
# empty path should produce no distributions
|
||||
ad = pkg_resources.Environment([], platform=None, python=None)
|
||||
assert list(ad) == []
|
||||
assert ad['FooPkg'] == []
|
||||
ad.add(dist_from_fn("FooPkg-1.3_1.egg"))
|
||||
ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg"))
|
||||
ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg"))
|
||||
|
||||
# Name is in there now
|
||||
assert ad['FooPkg']
|
||||
# But only 1 package
|
||||
assert list(ad) == ['foopkg']
|
||||
|
||||
# Distributions sort by version
|
||||
expected = ['1.4', '1.3-1', '1.2']
|
||||
assert [dist.version for dist in ad['FooPkg']] == expected
|
||||
|
||||
# Removing a distribution leaves sequence alone
|
||||
ad.remove(ad['FooPkg'][1])
|
||||
assert [dist.version for dist in ad['FooPkg']] == ['1.4', '1.2']
|
||||
|
||||
# And inserting adds them in order
|
||||
ad.add(dist_from_fn("FooPkg-1.9.egg"))
|
||||
assert [dist.version for dist in ad['FooPkg']] == ['1.9', '1.4', '1.2']
|
||||
|
||||
ws = WorkingSet([])
|
||||
foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg")
|
||||
foo14 = dist_from_fn("FooPkg-1.4-py2.4-win32.egg")
|
||||
(req,) = parse_requirements("FooPkg>=1.3")
|
||||
|
||||
# Nominal case: no distros on path, should yield all applicable
|
||||
assert ad.best_match(req, ws).version == '1.9'
|
||||
# If a matching distro is already installed, should return only that
|
||||
ws.add(foo14)
|
||||
assert ad.best_match(req, ws).version == '1.4'
|
||||
|
||||
# If the first matching distro is unsuitable, it's a version conflict
|
||||
ws = WorkingSet([])
|
||||
ws.add(foo12)
|
||||
ws.add(foo14)
|
||||
with pytest.raises(VersionConflict):
|
||||
ad.best_match(req, ws)
|
||||
|
||||
# If more than one match on the path, the first one takes precedence
|
||||
ws = WorkingSet([])
|
||||
ws.add(foo14)
|
||||
ws.add(foo12)
|
||||
ws.add(foo14)
|
||||
assert ad.best_match(req, ws).version == '1.4'
|
||||
|
||||
def checkFooPkg(self, d):
|
||||
assert d.project_name == "FooPkg"
|
||||
assert d.key == "foopkg"
|
||||
assert d.version == "1.3.post1"
|
||||
assert d.py_version == "2.4"
|
||||
assert d.platform == "win32"
|
||||
assert d.parsed_version == parse_version("1.3-1")
|
||||
|
||||
def testDistroBasics(self):
|
||||
d = Distribution(
|
||||
"/some/path",
|
||||
project_name="FooPkg",
|
||||
version="1.3-1",
|
||||
py_version="2.4",
|
||||
platform="win32",
|
||||
)
|
||||
self.checkFooPkg(d)
|
||||
|
||||
d = Distribution("/some/path")
|
||||
assert d.py_version == f'{sys.version_info.major}.{sys.version_info.minor}'
|
||||
assert d.platform is None
|
||||
|
||||
def testDistroParse(self):
|
||||
d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg")
|
||||
self.checkFooPkg(d)
|
||||
d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg-info")
|
||||
self.checkFooPkg(d)
|
||||
|
||||
def testDistroMetadata(self):
|
||||
d = Distribution(
|
||||
"/some/path",
|
||||
project_name="FooPkg",
|
||||
py_version="2.4",
|
||||
platform="win32",
|
||||
metadata=Metadata(('PKG-INFO', "Metadata-Version: 1.0\nVersion: 1.3-1\n")),
|
||||
)
|
||||
self.checkFooPkg(d)
|
||||
|
||||
def distRequires(self, txt):
|
||||
return Distribution("/foo", metadata=Metadata(('depends.txt', txt)))
|
||||
|
||||
def checkRequires(self, dist, txt, extras=()):
|
||||
assert list(dist.requires(extras)) == list(parse_requirements(txt))
|
||||
|
||||
def testDistroDependsSimple(self):
|
||||
for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0":
|
||||
self.checkRequires(self.distRequires(v), v)
|
||||
|
||||
needs_object_dir = pytest.mark.skipif(
|
||||
not hasattr(object, '__dir__'),
|
||||
reason='object.__dir__ necessary for self.__dir__ implementation',
|
||||
)
|
||||
|
||||
def test_distribution_dir(self):
|
||||
d = pkg_resources.Distribution()
|
||||
dir(d)
|
||||
|
||||
@needs_object_dir
|
||||
def test_distribution_dir_includes_provider_dir(self):
|
||||
d = pkg_resources.Distribution()
|
||||
before = d.__dir__()
|
||||
assert 'test_attr' not in before
|
||||
d._provider.test_attr = None
|
||||
after = d.__dir__()
|
||||
assert len(after) == len(before) + 1
|
||||
assert 'test_attr' in after
|
||||
|
||||
@needs_object_dir
|
||||
def test_distribution_dir_ignores_provider_dir_leading_underscore(self):
|
||||
d = pkg_resources.Distribution()
|
||||
before = d.__dir__()
|
||||
assert '_test_attr' not in before
|
||||
d._provider._test_attr = None
|
||||
after = d.__dir__()
|
||||
assert len(after) == len(before)
|
||||
assert '_test_attr' not in after
|
||||
|
||||
def testResolve(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
# Resolving no requirements -> nothing to install
|
||||
assert list(ws.resolve([], ad)) == []
|
||||
# Request something not in the collection -> DistributionNotFound
|
||||
with pytest.raises(pkg_resources.DistributionNotFound):
|
||||
ws.resolve(parse_requirements("Foo"), ad)
|
||||
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.egg",
|
||||
metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0")),
|
||||
)
|
||||
ad.add(Foo)
|
||||
ad.add(Distribution.from_filename("Foo-0.9.egg"))
|
||||
|
||||
# Request thing(s) that are available -> list to activate
|
||||
for i in range(3):
|
||||
targets = list(ws.resolve(parse_requirements("Foo"), ad))
|
||||
assert targets == [Foo]
|
||||
list(map(ws.add, targets))
|
||||
with pytest.raises(VersionConflict):
|
||||
ws.resolve(parse_requirements("Foo==0.9"), ad)
|
||||
ws = WorkingSet([]) # reset
|
||||
|
||||
# Request an extra that causes an unresolved dependency for "Baz"
|
||||
with pytest.raises(pkg_resources.DistributionNotFound):
|
||||
ws.resolve(parse_requirements("Foo[bar]"), ad)
|
||||
Baz = Distribution.from_filename(
|
||||
"/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo"))
|
||||
)
|
||||
ad.add(Baz)
|
||||
|
||||
# Activation list now includes resolved dependency
|
||||
assert list(ws.resolve(parse_requirements("Foo[bar]"), ad)) == [Foo, Baz]
|
||||
# Requests for conflicting versions produce VersionConflict
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.resolve(parse_requirements("Foo==1.2\nFoo!=1.2"), ad)
|
||||
|
||||
msg = 'Foo 0.9 is installed but Foo==1.2 is required'
|
||||
assert vc.value.report() == msg
|
||||
|
||||
def test_environment_marker_evaluation_negative(self):
|
||||
"""Environment markers are evaluated at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
res = ws.resolve(parse_requirements("Foo;python_version<'2'"), ad)
|
||||
assert list(res) == []
|
||||
|
||||
def test_environment_marker_evaluation_positive(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.2.dist-info")
|
||||
ad.add(Foo)
|
||||
res = ws.resolve(parse_requirements("Foo;python_version>='2'"), ad)
|
||||
assert list(res) == [Foo]
|
||||
|
||||
def test_environment_marker_evaluation_called(self):
|
||||
"""
|
||||
If one package foo requires bar without any extras,
|
||||
markers should pass for bar without extras.
|
||||
"""
|
||||
(parent_req,) = parse_requirements("foo")
|
||||
(req,) = parse_requirements("bar;python_version>='2'")
|
||||
req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
|
||||
assert req_extras.markers_pass(req)
|
||||
|
||||
(parent_req,) = parse_requirements("foo[]")
|
||||
(req,) = parse_requirements("bar;python_version>='2'")
|
||||
req_extras = pkg_resources._ReqExtras({req: parent_req.extras})
|
||||
assert req_extras.markers_pass(req)
|
||||
|
||||
def test_marker_evaluation_with_extras(self):
|
||||
"""Extras are also evaluated as markers at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata((
|
||||
"METADATA",
|
||||
"Provides-Extra: baz\nRequires-Dist: quux; extra=='baz'",
|
||||
)),
|
||||
)
|
||||
ad.add(Foo)
|
||||
assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz]"), ad))
|
||||
assert res == [Foo, quux]
|
||||
|
||||
def test_marker_evaluation_with_extras_normlized(self):
|
||||
"""Extras are also evaluated as markers at resolution time."""
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata((
|
||||
"METADATA",
|
||||
"Provides-Extra: baz-lightyear\n"
|
||||
"Requires-Dist: quux; extra=='baz-lightyear'",
|
||||
)),
|
||||
)
|
||||
ad.add(Foo)
|
||||
assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz-lightyear]"), ad))
|
||||
assert res == [Foo, quux]
|
||||
|
||||
def test_marker_evaluation_with_multiple_extras(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename(
|
||||
"/foo_dir/Foo-1.2.dist-info",
|
||||
metadata=Metadata((
|
||||
"METADATA",
|
||||
"Provides-Extra: baz\n"
|
||||
"Requires-Dist: quux; extra=='baz'\n"
|
||||
"Provides-Extra: bar\n"
|
||||
"Requires-Dist: fred; extra=='bar'\n",
|
||||
)),
|
||||
)
|
||||
ad.add(Foo)
|
||||
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
|
||||
ad.add(quux)
|
||||
fred = Distribution.from_filename("/foo_dir/fred-0.1.dist-info")
|
||||
ad.add(fred)
|
||||
res = list(ws.resolve(parse_requirements("Foo[baz,bar]"), ad))
|
||||
assert sorted(res) == [fred, quux, Foo]
|
||||
|
||||
def test_marker_evaluation_with_extras_loop(self):
|
||||
ad = pkg_resources.Environment([])
|
||||
ws = WorkingSet([])
|
||||
a = Distribution.from_filename(
|
||||
"/foo_dir/a-0.2.dist-info",
|
||||
metadata=Metadata(("METADATA", "Requires-Dist: c[a]")),
|
||||
)
|
||||
b = Distribution.from_filename(
|
||||
"/foo_dir/b-0.3.dist-info",
|
||||
metadata=Metadata(("METADATA", "Requires-Dist: c[b]")),
|
||||
)
|
||||
c = Distribution.from_filename(
|
||||
"/foo_dir/c-1.0.dist-info",
|
||||
metadata=Metadata((
|
||||
"METADATA",
|
||||
"Provides-Extra: a\n"
|
||||
"Requires-Dist: b;extra=='a'\n"
|
||||
"Provides-Extra: b\n"
|
||||
"Requires-Dist: foo;extra=='b'",
|
||||
)),
|
||||
)
|
||||
foo = Distribution.from_filename("/foo_dir/foo-0.1.dist-info")
|
||||
for dist in (a, b, c, foo):
|
||||
ad.add(dist)
|
||||
res = list(ws.resolve(parse_requirements("a"), ad))
|
||||
assert res == [a, c, b, foo]
|
||||
|
||||
@pytest.mark.xfail(
|
||||
sys.version_info[:2] == (3, 12) and sys.version_info.releaselevel != 'final',
|
||||
reason="https://github.com/python/cpython/issues/103632",
|
||||
)
|
||||
def testDistroDependsOptions(self):
|
||||
d = self.distRequires(
|
||||
"""
|
||||
Twisted>=1.5
|
||||
[docgen]
|
||||
ZConfig>=2.0
|
||||
docutils>=0.3
|
||||
[fastcgi]
|
||||
fcgiapp>=0.1"""
|
||||
)
|
||||
self.checkRequires(d, "Twisted>=1.5")
|
||||
self.checkRequires(
|
||||
d, "Twisted>=1.5 ZConfig>=2.0 docutils>=0.3".split(), ["docgen"]
|
||||
)
|
||||
self.checkRequires(d, "Twisted>=1.5 fcgiapp>=0.1".split(), ["fastcgi"])
|
||||
self.checkRequires(
|
||||
d,
|
||||
"Twisted>=1.5 ZConfig>=2.0 docutils>=0.3 fcgiapp>=0.1".split(),
|
||||
["docgen", "fastcgi"],
|
||||
)
|
||||
self.checkRequires(
|
||||
d,
|
||||
"Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(),
|
||||
["fastcgi", "docgen"],
|
||||
)
|
||||
with pytest.raises(pkg_resources.UnknownExtra):
|
||||
d.requires(["foo"])
|
||||
|
||||
|
||||
class TestWorkingSet:
|
||||
def test_find_conflicting(self):
|
||||
ws = WorkingSet([])
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.2.egg")
|
||||
ws.add(Foo)
|
||||
|
||||
# create a requirement that conflicts with Foo 1.2
|
||||
req = next(parse_requirements("Foo<1.2"))
|
||||
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.find(req)
|
||||
|
||||
msg = 'Foo 1.2 is installed but Foo<1.2 is required'
|
||||
assert vc.value.report() == msg
|
||||
|
||||
def test_resolve_conflicts_with_prior(self):
|
||||
"""
|
||||
A ContextualVersionConflict should be raised when a requirement
|
||||
conflicts with a prior requirement for a different package.
|
||||
"""
|
||||
# Create installation where Foo depends on Baz 1.0 and Bar depends on
|
||||
# Baz 2.0.
|
||||
ws = WorkingSet([])
|
||||
md = Metadata(('depends.txt', "Baz==1.0"))
|
||||
Foo = Distribution.from_filename("/foo_dir/Foo-1.0.egg", metadata=md)
|
||||
ws.add(Foo)
|
||||
md = Metadata(('depends.txt', "Baz==2.0"))
|
||||
Bar = Distribution.from_filename("/foo_dir/Bar-1.0.egg", metadata=md)
|
||||
ws.add(Bar)
|
||||
Baz = Distribution.from_filename("/foo_dir/Baz-1.0.egg")
|
||||
ws.add(Baz)
|
||||
Baz = Distribution.from_filename("/foo_dir/Baz-2.0.egg")
|
||||
ws.add(Baz)
|
||||
|
||||
with pytest.raises(VersionConflict) as vc:
|
||||
ws.resolve(parse_requirements("Foo\nBar\n"))
|
||||
|
||||
msg = "Baz 1.0 is installed but Baz==2.0 is required by "
|
||||
msg += repr(set(['Bar']))
|
||||
assert vc.value.report() == msg
|
||||
|
||||
|
||||
class TestEntryPoints:
|
||||
def assertfields(self, ep):
|
||||
assert ep.name == "foo"
|
||||
assert ep.module_name == "pkg_resources.tests.test_resources"
|
||||
assert ep.attrs == ("TestEntryPoints",)
|
||||
assert ep.extras == ("x",)
|
||||
assert ep.load() is TestEntryPoints
|
||||
expect = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]"
|
||||
assert str(ep) == expect
|
||||
|
||||
def setup_method(self, method):
|
||||
self.dist = Distribution.from_filename(
|
||||
"FooPkg-1.2-py2.4.egg", metadata=Metadata(('requires.txt', '[x]'))
|
||||
)
|
||||
|
||||
def testBasics(self):
|
||||
ep = EntryPoint(
|
||||
"foo",
|
||||
"pkg_resources.tests.test_resources",
|
||||
["TestEntryPoints"],
|
||||
["x"],
|
||||
self.dist,
|
||||
)
|
||||
self.assertfields(ep)
|
||||
|
||||
def testParse(self):
|
||||
s = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]"
|
||||
ep = EntryPoint.parse(s, self.dist)
|
||||
self.assertfields(ep)
|
||||
|
||||
ep = EntryPoint.parse("bar baz= spammity[PING]")
|
||||
assert ep.name == "bar baz"
|
||||
assert ep.module_name == "spammity"
|
||||
assert ep.attrs == ()
|
||||
assert ep.extras == ("ping",)
|
||||
|
||||
ep = EntryPoint.parse(" fizzly = wocka:foo")
|
||||
assert ep.name == "fizzly"
|
||||
assert ep.module_name == "wocka"
|
||||
assert ep.attrs == ("foo",)
|
||||
assert ep.extras == ()
|
||||
|
||||
# plus in the name
|
||||
spec = "html+mako = mako.ext.pygmentplugin:MakoHtmlLexer"
|
||||
ep = EntryPoint.parse(spec)
|
||||
assert ep.name == 'html+mako'
|
||||
|
||||
reject_specs = "foo", "x=a:b:c", "q=x/na", "fez=pish:tush-z", "x=f[a]>2"
|
||||
|
||||
@pytest.mark.parametrize("reject_spec", reject_specs)
|
||||
def test_reject_spec(self, reject_spec):
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse(reject_spec)
|
||||
|
||||
def test_printable_name(self):
|
||||
"""
|
||||
Allow any printable character in the name.
|
||||
"""
|
||||
# Create a name with all printable characters; strip the whitespace.
|
||||
name = string.printable.strip()
|
||||
spec = "{name} = module:attr".format(**locals())
|
||||
ep = EntryPoint.parse(spec)
|
||||
assert ep.name == name
|
||||
|
||||
def checkSubMap(self, m):
|
||||
assert len(m) == len(self.submap_expect)
|
||||
for key, ep in self.submap_expect.items():
|
||||
assert m.get(key).name == ep.name
|
||||
assert m.get(key).module_name == ep.module_name
|
||||
assert sorted(m.get(key).attrs) == sorted(ep.attrs)
|
||||
assert sorted(m.get(key).extras) == sorted(ep.extras)
|
||||
|
||||
submap_expect = dict(
|
||||
feature1=EntryPoint('feature1', 'somemodule', ['somefunction']),
|
||||
feature2=EntryPoint(
|
||||
'feature2', 'another.module', ['SomeClass'], ['extra1', 'extra2']
|
||||
),
|
||||
feature3=EntryPoint('feature3', 'this.module', extras=['something']),
|
||||
)
|
||||
submap_str = """
|
||||
# define features for blah blah
|
||||
feature1 = somemodule:somefunction
|
||||
feature2 = another.module:SomeClass [extra1,extra2]
|
||||
feature3 = this.module [something]
|
||||
"""
|
||||
|
||||
def testParseList(self):
|
||||
self.checkSubMap(EntryPoint.parse_group("xyz", self.submap_str))
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_group("x a", "foo=bar")
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_group("x", ["foo=baz", "foo=bar"])
|
||||
|
||||
def testParseMap(self):
|
||||
m = EntryPoint.parse_map({'xyz': self.submap_str})
|
||||
self.checkSubMap(m['xyz'])
|
||||
assert list(m.keys()) == ['xyz']
|
||||
m = EntryPoint.parse_map("[xyz]\n" + self.submap_str)
|
||||
self.checkSubMap(m['xyz'])
|
||||
assert list(m.keys()) == ['xyz']
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_map(["[xyz]", "[xyz]"])
|
||||
with pytest.raises(ValueError):
|
||||
EntryPoint.parse_map(self.submap_str)
|
||||
|
||||
def testDeprecationWarnings(self):
|
||||
ep = EntryPoint(
|
||||
"foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"], ["x"]
|
||||
)
|
||||
with pytest.warns(pkg_resources.PkgResourcesDeprecationWarning):
|
||||
ep.load(require=False)
|
||||
|
||||
|
||||
class TestRequirements:
|
||||
def testBasics(self):
|
||||
r = Requirement.parse("Twisted>=1.2")
|
||||
assert str(r) == "Twisted>=1.2"
|
||||
assert repr(r) == "Requirement.parse('Twisted>=1.2')"
|
||||
assert r == Requirement("Twisted>=1.2")
|
||||
assert r == Requirement("twisTed>=1.2")
|
||||
assert r != Requirement("Twisted>=2.0")
|
||||
assert r != Requirement("Zope>=1.2")
|
||||
assert r != Requirement("Zope>=3.0")
|
||||
assert r != Requirement("Twisted[extras]>=1.2")
|
||||
|
||||
def testOrdering(self):
|
||||
r1 = Requirement("Twisted==1.2c1,>=1.2")
|
||||
r2 = Requirement("Twisted>=1.2,==1.2c1")
|
||||
assert r1 == r2
|
||||
assert str(r1) == str(r2)
|
||||
assert str(r2) == "Twisted==1.2c1,>=1.2"
|
||||
assert Requirement("Twisted") != Requirement(
|
||||
"Twisted @ https://localhost/twisted.zip"
|
||||
)
|
||||
|
||||
def testBasicContains(self):
|
||||
r = Requirement("Twisted>=1.2")
|
||||
foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg")
|
||||
twist11 = Distribution.from_filename("Twisted-1.1.egg")
|
||||
twist12 = Distribution.from_filename("Twisted-1.2.egg")
|
||||
assert parse_version('1.2') in r
|
||||
assert parse_version('1.1') not in r
|
||||
assert '1.2' in r
|
||||
assert '1.1' not in r
|
||||
assert foo_dist not in r
|
||||
assert twist11 not in r
|
||||
assert twist12 in r
|
||||
|
||||
def testOptionsAndHashing(self):
|
||||
r1 = Requirement.parse("Twisted[foo,bar]>=1.2")
|
||||
r2 = Requirement.parse("Twisted[bar,FOO]>=1.2")
|
||||
assert r1 == r2
|
||||
assert set(r1.extras) == set(("foo", "bar"))
|
||||
assert set(r2.extras) == set(("foo", "bar"))
|
||||
assert hash(r1) == hash(r2)
|
||||
assert hash(r1) == hash((
|
||||
"twisted",
|
||||
None,
|
||||
SpecifierSet(">=1.2"),
|
||||
frozenset(["foo", "bar"]),
|
||||
None,
|
||||
))
|
||||
assert hash(
|
||||
Requirement.parse("Twisted @ https://localhost/twisted.zip")
|
||||
) == hash((
|
||||
"twisted",
|
||||
"https://localhost/twisted.zip",
|
||||
SpecifierSet(),
|
||||
frozenset(),
|
||||
None,
|
||||
))
|
||||
|
||||
def testVersionEquality(self):
|
||||
r1 = Requirement.parse("foo==0.3a2")
|
||||
r2 = Requirement.parse("foo!=0.3a4")
|
||||
d = Distribution.from_filename
|
||||
|
||||
assert d("foo-0.3a4.egg") not in r1
|
||||
assert d("foo-0.3a1.egg") not in r1
|
||||
assert d("foo-0.3a4.egg") not in r2
|
||||
|
||||
assert d("foo-0.3a2.egg") in r1
|
||||
assert d("foo-0.3a2.egg") in r2
|
||||
assert d("foo-0.3a3.egg") in r2
|
||||
assert d("foo-0.3a5.egg") in r2
|
||||
|
||||
def testSetuptoolsProjectName(self):
|
||||
"""
|
||||
The setuptools project should implement the setuptools package.
|
||||
"""
|
||||
|
||||
assert Requirement.parse('setuptools').project_name == 'setuptools'
|
||||
# setuptools 0.7 and higher means setuptools.
|
||||
assert Requirement.parse('setuptools == 0.7').project_name == 'setuptools'
|
||||
assert Requirement.parse('setuptools == 0.7a1').project_name == 'setuptools'
|
||||
assert Requirement.parse('setuptools >= 0.7').project_name == 'setuptools'
|
||||
|
||||
|
||||
class TestParsing:
|
||||
def testEmptyParse(self):
|
||||
assert list(parse_requirements('')) == []
|
||||
|
||||
def testYielding(self):
|
||||
for inp, out in [
|
||||
([], []),
|
||||
('x', ['x']),
|
||||
([[]], []),
|
||||
(' x\n y', ['x', 'y']),
|
||||
(['x\n\n', 'y'], ['x', 'y']),
|
||||
]:
|
||||
assert list(pkg_resources.yield_lines(inp)) == out
|
||||
|
||||
def testSplitting(self):
|
||||
sample = """
|
||||
x
|
||||
[Y]
|
||||
z
|
||||
|
||||
a
|
||||
[b ]
|
||||
# foo
|
||||
c
|
||||
[ d]
|
||||
[q]
|
||||
v
|
||||
"""
|
||||
assert list(pkg_resources.split_sections(sample)) == [
|
||||
(None, ["x"]),
|
||||
("Y", ["z", "a"]),
|
||||
("b", ["c"]),
|
||||
("d", []),
|
||||
("q", ["v"]),
|
||||
]
|
||||
with pytest.raises(ValueError):
|
||||
list(pkg_resources.split_sections("[foo"))
|
||||
|
||||
def testSafeName(self):
|
||||
assert safe_name("adns-python") == "adns-python"
|
||||
assert safe_name("WSGI Utils") == "WSGI-Utils"
|
||||
assert safe_name("WSGI Utils") == "WSGI-Utils"
|
||||
assert safe_name("Money$$$Maker") == "Money-Maker"
|
||||
assert safe_name("peak.web") != "peak-web"
|
||||
|
||||
def testSafeVersion(self):
|
||||
assert safe_version("1.2-1") == "1.2.post1"
|
||||
assert safe_version("1.2 alpha") == "1.2.alpha"
|
||||
assert safe_version("2.3.4 20050521") == "2.3.4.20050521"
|
||||
assert safe_version("Money$$$Maker") == "Money-Maker"
|
||||
assert safe_version("peak.web") == "peak.web"
|
||||
|
||||
def testSimpleRequirements(self):
|
||||
assert list(parse_requirements('Twis-Ted>=1.2-1')) == [
|
||||
Requirement('Twis-Ted>=1.2-1')
|
||||
]
|
||||
assert list(parse_requirements('Twisted >=1.2, \\ # more\n<2.0')) == [
|
||||
Requirement('Twisted>=1.2,<2.0')
|
||||
]
|
||||
assert Requirement.parse("FooBar==1.99a3") == Requirement("FooBar==1.99a3")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse(">=2.3")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("x\\")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("x==2 q")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("X==1\nY==2")
|
||||
with pytest.raises(ValueError):
|
||||
Requirement.parse("#")
|
||||
|
||||
def test_requirements_with_markers(self):
|
||||
assert Requirement.parse("foobar;os_name=='a'") == Requirement.parse(
|
||||
"foobar;os_name=='a'"
|
||||
)
|
||||
assert Requirement.parse(
|
||||
"name==1.1;python_version=='2.7'"
|
||||
) != Requirement.parse("name==1.1;python_version=='3.6'")
|
||||
assert Requirement.parse(
|
||||
"name==1.0;python_version=='2.7'"
|
||||
) != Requirement.parse("name==1.2;python_version=='2.7'")
|
||||
assert Requirement.parse(
|
||||
"name[foo]==1.0;python_version=='3.6'"
|
||||
) != Requirement.parse("name[foo,bar]==1.0;python_version=='3.6'")
|
||||
|
||||
def test_local_version(self):
|
||||
parse_requirements('foo==1.0+org1')
|
||||
|
||||
def test_spaces_between_multiple_versions(self):
|
||||
parse_requirements('foo>=1.0, <3')
|
||||
parse_requirements('foo >= 1.0, < 3')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("lower", "upper"),
|
||||
[
|
||||
('1.2-rc1', '1.2rc1'),
|
||||
('0.4', '0.4.0'),
|
||||
('0.4.0.0', '0.4.0'),
|
||||
('0.4.0-0', '0.4-0'),
|
||||
('0post1', '0.0post1'),
|
||||
('0pre1', '0.0c1'),
|
||||
('0.0.0preview1', '0c1'),
|
||||
('0.0c1', '0-rc1'),
|
||||
('1.2a1', '1.2.a.1'),
|
||||
('1.2.a', '1.2a'),
|
||||
],
|
||||
)
|
||||
def testVersionEquality(self, lower, upper):
|
||||
assert parse_version(lower) == parse_version(upper)
|
||||
|
||||
torture = """
|
||||
0.80.1-3 0.80.1-2 0.80.1-1 0.79.9999+0.80.0pre4-1
|
||||
0.79.9999+0.80.0pre2-3 0.79.9999+0.80.0pre2-2
|
||||
0.77.2-1 0.77.1-1 0.77.0-1
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("lower", "upper"),
|
||||
[
|
||||
('2.1', '2.1.1'),
|
||||
('2a1', '2b0'),
|
||||
('2a1', '2.1'),
|
||||
('2.3a1', '2.3'),
|
||||
('2.1-1', '2.1-2'),
|
||||
('2.1-1', '2.1.1'),
|
||||
('2.1', '2.1post4'),
|
||||
('2.1a0-20040501', '2.1'),
|
||||
('1.1', '02.1'),
|
||||
('3.2', '3.2.post0'),
|
||||
('3.2post1', '3.2post2'),
|
||||
('0.4', '4.0'),
|
||||
('0.0.4', '0.4.0'),
|
||||
('0post1', '0.4post1'),
|
||||
('2.1.0-rc1', '2.1.0'),
|
||||
('2.1dev', '2.1a0'),
|
||||
]
|
||||
+ list(pairwise(reversed(torture.split()))),
|
||||
)
|
||||
def testVersionOrdering(self, lower, upper):
|
||||
assert parse_version(lower) < parse_version(upper)
|
||||
|
||||
def testVersionHashable(self):
|
||||
"""
|
||||
Ensure that our versions stay hashable even though we've subclassed
|
||||
them and added some shim code to them.
|
||||
"""
|
||||
assert hash(parse_version("1.0")) == hash(parse_version("1.0"))
|
||||
|
||||
|
||||
class TestNamespaces:
|
||||
ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
|
||||
|
||||
@pytest.fixture
|
||||
def symlinked_tmpdir(self, tmpdir):
|
||||
"""
|
||||
Where available, return the tempdir as a symlink,
|
||||
which as revealed in #231 is more fragile than
|
||||
a natural tempdir.
|
||||
"""
|
||||
if not hasattr(os, 'symlink'):
|
||||
yield str(tmpdir)
|
||||
return
|
||||
|
||||
link_name = str(tmpdir) + '-linked'
|
||||
os.symlink(str(tmpdir), link_name)
|
||||
try:
|
||||
yield type(tmpdir)(link_name)
|
||||
finally:
|
||||
os.unlink(link_name)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patched_path(self, tmpdir):
|
||||
"""
|
||||
Patch sys.path to include the 'site-pkgs' dir. Also
|
||||
restore pkg_resources._namespace_packages to its
|
||||
former state.
|
||||
"""
|
||||
saved_ns_pkgs = pkg_resources._namespace_packages.copy()
|
||||
saved_sys_path = sys.path[:]
|
||||
site_pkgs = tmpdir.mkdir('site-pkgs')
|
||||
sys.path.append(str(site_pkgs))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
pkg_resources._namespace_packages = saved_ns_pkgs
|
||||
sys.path = saved_sys_path
|
||||
|
||||
issue591 = pytest.mark.xfail(platform.system() == 'Windows', reason="#591")
|
||||
|
||||
@issue591
|
||||
def test_two_levels_deep(self, symlinked_tmpdir):
|
||||
"""
|
||||
Test nested namespace packages
|
||||
Create namespace packages in the following tree :
|
||||
site-packages-1/pkg1/pkg2
|
||||
site-packages-2/pkg1/pkg2
|
||||
Check both are in the _namespace_packages dict and that their __path__
|
||||
is correct
|
||||
"""
|
||||
real_tmpdir = symlinked_tmpdir.realpath()
|
||||
tmpdir = symlinked_tmpdir
|
||||
sys.path.append(str(tmpdir / 'site-pkgs2'))
|
||||
site_dirs = tmpdir / 'site-pkgs', tmpdir / 'site-pkgs2'
|
||||
for site in site_dirs:
|
||||
pkg1 = site / 'pkg1'
|
||||
pkg2 = pkg1 / 'pkg2'
|
||||
pkg2.ensure_dir()
|
||||
(pkg1 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
(pkg2 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
with pytest.warns(DeprecationWarning, match="pkg_resources.declare_namespace"):
|
||||
import pkg1 # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
assert "pkg1" in pkg_resources._namespace_packages
|
||||
# attempt to import pkg2 from site-pkgs2
|
||||
with pytest.warns(DeprecationWarning, match="pkg_resources.declare_namespace"):
|
||||
import pkg1.pkg2 # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
# check the _namespace_packages dict
|
||||
assert "pkg1.pkg2" in pkg_resources._namespace_packages
|
||||
assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"]
|
||||
# check the __path__ attribute contains both paths
|
||||
expected = [
|
||||
str(real_tmpdir / "site-pkgs" / "pkg1" / "pkg2"),
|
||||
str(real_tmpdir / "site-pkgs2" / "pkg1" / "pkg2"),
|
||||
]
|
||||
assert pkg1.pkg2.__path__ == expected
|
||||
|
||||
@issue591
|
||||
def test_path_order(self, symlinked_tmpdir):
|
||||
"""
|
||||
Test that if multiple versions of the same namespace package subpackage
|
||||
are on different sys.path entries, that only the one earliest on
|
||||
sys.path is imported, and that the namespace package's __path__ is in
|
||||
the correct order.
|
||||
|
||||
Regression test for https://github.com/pypa/setuptools/issues/207
|
||||
"""
|
||||
|
||||
tmpdir = symlinked_tmpdir
|
||||
site_dirs = (
|
||||
tmpdir / "site-pkgs",
|
||||
tmpdir / "site-pkgs2",
|
||||
tmpdir / "site-pkgs3",
|
||||
)
|
||||
|
||||
vers_str = "__version__ = %r"
|
||||
|
||||
for number, site in enumerate(site_dirs, 1):
|
||||
if number > 1:
|
||||
sys.path.append(str(site))
|
||||
nspkg = site / 'nspkg'
|
||||
subpkg = nspkg / 'subpkg'
|
||||
subpkg.ensure_dir()
|
||||
(nspkg / '__init__.py').write_text(self.ns_str, encoding='utf-8')
|
||||
(subpkg / '__init__.py').write_text(vers_str % number, encoding='utf-8')
|
||||
|
||||
with pytest.warns(DeprecationWarning, match="pkg_resources.declare_namespace"):
|
||||
import nspkg # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
import nspkg.subpkg # pyright: ignore[reportMissingImports] # Temporary package for test
|
||||
expected = [str(site.realpath() / 'nspkg') for site in site_dirs]
|
||||
assert nspkg.__path__ == expected
|
||||
assert nspkg.subpkg.__version__ == 1
|
||||
@@ -0,0 +1,501 @@
|
||||
import functools
|
||||
import inspect
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .test_resources import Metadata
|
||||
|
||||
|
||||
def strip_comments(s):
|
||||
return '\n'.join(
|
||||
line
|
||||
for line in s.split('\n')
|
||||
if line.strip() and not line.strip().startswith('#')
|
||||
)
|
||||
|
||||
|
||||
def parse_distributions(s):
|
||||
"""
|
||||
Parse a series of distribution specs of the form:
|
||||
{project_name}-{version}
|
||||
[optional, indented requirements specification]
|
||||
|
||||
Example:
|
||||
|
||||
foo-0.2
|
||||
bar-1.0
|
||||
foo>=3.0
|
||||
[feature]
|
||||
baz
|
||||
|
||||
yield 2 distributions:
|
||||
- project_name=foo, version=0.2
|
||||
- project_name=bar, version=1.0,
|
||||
requires=['foo>=3.0', 'baz; extra=="feature"']
|
||||
"""
|
||||
s = s.strip()
|
||||
for spec in re.split(r'\n(?=[^\s])', s):
|
||||
if not spec:
|
||||
continue
|
||||
fields = spec.split('\n', 1)
|
||||
assert 1 <= len(fields) <= 2
|
||||
name, version = fields.pop(0).rsplit('-', 1)
|
||||
if fields:
|
||||
requires = textwrap.dedent(fields.pop(0))
|
||||
metadata = Metadata(('requires.txt', requires))
|
||||
else:
|
||||
metadata = None
|
||||
dist = pkg_resources.Distribution(
|
||||
project_name=name, version=version, metadata=metadata
|
||||
)
|
||||
yield dist
|
||||
|
||||
|
||||
class FakeInstaller:
|
||||
def __init__(self, installable_dists) -> None:
|
||||
self._installable_dists = installable_dists
|
||||
|
||||
def __call__(self, req):
|
||||
return next(
|
||||
iter(filter(lambda dist: dist in req, self._installable_dists)), None
|
||||
)
|
||||
|
||||
|
||||
def parametrize_test_working_set_resolve(*test_list):
|
||||
idlist = []
|
||||
argvalues = []
|
||||
for test in test_list:
|
||||
(
|
||||
name,
|
||||
installed_dists,
|
||||
installable_dists,
|
||||
requirements,
|
||||
expected1,
|
||||
expected2,
|
||||
) = (
|
||||
strip_comments(s.lstrip())
|
||||
for s in textwrap.dedent(test).lstrip().split('\n\n', 5)
|
||||
)
|
||||
installed_dists = list(parse_distributions(installed_dists))
|
||||
installable_dists = list(parse_distributions(installable_dists))
|
||||
requirements = list(pkg_resources.parse_requirements(requirements))
|
||||
for id_, replace_conflicting, expected in (
|
||||
(name, False, expected1),
|
||||
(name + '_replace_conflicting', True, expected2),
|
||||
):
|
||||
idlist.append(id_)
|
||||
expected = strip_comments(expected.strip())
|
||||
if re.match(r'\w+$', expected):
|
||||
expected = getattr(pkg_resources, expected)
|
||||
assert issubclass(expected, Exception)
|
||||
else:
|
||||
expected = list(parse_distributions(expected))
|
||||
argvalues.append(
|
||||
pytest.param(
|
||||
installed_dists,
|
||||
installable_dists,
|
||||
requirements,
|
||||
replace_conflicting,
|
||||
expected,
|
||||
)
|
||||
)
|
||||
return pytest.mark.parametrize(
|
||||
'installed_dists,installable_dists,'
|
||||
'requirements,replace_conflicting,'
|
||||
'resolved_dists_or_exception',
|
||||
argvalues,
|
||||
ids=idlist,
|
||||
)
|
||||
|
||||
|
||||
@parametrize_test_working_set_resolve(
|
||||
"""
|
||||
# id
|
||||
noop
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
|
||||
# resolved
|
||||
|
||||
# resolved [replace conflicting]
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
already_installed
|
||||
|
||||
# installed
|
||||
foo-3.0
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
foo-3.0
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.0
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installable_not_installed
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.0
|
||||
foo-4.0
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
foo-3.0
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.0
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
not_installable
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
DistributionNotFound
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
no_matching_version
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.1
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
DistributionNotFound
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installable_with_installed_conflict
|
||||
|
||||
# installed
|
||||
foo-3.1
|
||||
|
||||
# installable
|
||||
foo-3.5
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.5
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
not_installable_with_installed_conflict
|
||||
|
||||
# installed
|
||||
foo-3.1
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installed_with_installed_require
|
||||
|
||||
# installed
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installed_with_conflicting_installed_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
DistributionNotFound
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installed_with_installable_conflicting_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
baz-0.1
|
||||
foo-2.9
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installed_with_installable_require
|
||||
|
||||
# installed
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# installable
|
||||
foo-3.9
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installable_with_installed_require
|
||||
|
||||
# installed
|
||||
foo-3.9
|
||||
|
||||
# installable
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installable_with_installable_require
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo-3.9
|
||||
baz-0.1
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installable_with_conflicting_installable_require
|
||||
|
||||
# installed
|
||||
foo-5
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
baz-0.1
|
||||
foo>=2.1,!=3.1,<4
|
||||
|
||||
# wanted
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
baz-0.1
|
||||
foo-2.9
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
conflicting_installables
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
foo-5.0
|
||||
|
||||
# wanted
|
||||
foo>=2.1,!=3.1,<4
|
||||
foo>=4
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installables_with_conflicting_requires
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
dep==1.0
|
||||
baz-5.0
|
||||
dep==2.0
|
||||
dep-1.0
|
||||
dep-2.0
|
||||
|
||||
# wanted
|
||||
foo
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
installables_with_conflicting_nested_requires
|
||||
|
||||
# installed
|
||||
|
||||
# installable
|
||||
foo-2.9
|
||||
dep1
|
||||
dep1-1.0
|
||||
subdep<1.0
|
||||
baz-5.0
|
||||
dep2
|
||||
dep2-1.0
|
||||
subdep>1.0
|
||||
subdep-0.9
|
||||
subdep-1.1
|
||||
|
||||
# wanted
|
||||
foo
|
||||
baz
|
||||
|
||||
# resolved
|
||||
VersionConflict
|
||||
|
||||
# resolved [replace conflicting]
|
||||
VersionConflict
|
||||
""",
|
||||
"""
|
||||
# id
|
||||
wanted_normalized_name_installed_canonical
|
||||
|
||||
# installed
|
||||
foo.bar-3.6
|
||||
|
||||
# installable
|
||||
|
||||
# wanted
|
||||
foo-bar==3.6
|
||||
|
||||
# resolved
|
||||
foo.bar-3.6
|
||||
|
||||
# resolved [replace conflicting]
|
||||
foo.bar-3.6
|
||||
""",
|
||||
)
|
||||
def test_working_set_resolve(
|
||||
installed_dists,
|
||||
installable_dists,
|
||||
requirements,
|
||||
replace_conflicting,
|
||||
resolved_dists_or_exception,
|
||||
):
|
||||
ws = pkg_resources.WorkingSet([])
|
||||
list(map(ws.add, installed_dists))
|
||||
resolve_call = functools.partial(
|
||||
ws.resolve,
|
||||
requirements,
|
||||
installer=FakeInstaller(installable_dists),
|
||||
replace_conflicting=replace_conflicting,
|
||||
)
|
||||
if inspect.isclass(resolved_dists_or_exception):
|
||||
with pytest.raises(resolved_dists_or_exception):
|
||||
resolve_call()
|
||||
else:
|
||||
assert sorted(resolve_call()) == sorted(resolved_dists_or_exception)
|
||||
Reference in New Issue
Block a user