venv added, updated
This commit is contained in:
3725
myenv/lib/python3.12/site-packages/pkg_resources/__init__.py
Normal file
3725
myenv/lib/python3.12/site-packages/pkg_resources/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
424
myenv/lib/python3.12/site-packages/pkg_resources/api_tests.txt
Normal file
424
myenv/lib/python3.12/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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
name="my-test-package",
|
||||
version="1.0",
|
||||
zip_safe=True,
|
||||
)
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: my-test-package
|
||||
Version: 1.0
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
@@ -0,0 +1,7 @@
|
||||
setup.cfg
|
||||
setup.py
|
||||
my_test_package.egg-info/PKG-INFO
|
||||
my_test_package.egg-info/SOURCES.txt
|
||||
my_test_package.egg-info/dependency_links.txt
|
||||
my_test_package.egg-info/top_level.txt
|
||||
my_test_package.egg-info/zip-safe
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Binary file not shown.
@@ -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):
|
||||
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 == '{}.{}'.format(*sys.version_info)
|
||||
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):
|
||||
(req,) = parse_requirements('foo==1.0+org1')
|
||||
|
||||
def test_spaces_between_multiple_versions(self):
|
||||
(req,) = parse_requirements('foo>=1.0, <3')
|
||||
(req,) = 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):
|
||||
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