Skip to content

Commit afe11da

Browse files
asottilegaborbernat
authored andcommitted
Move to importlib-metadata for performance improvements (#1324)
1 parent 8c0275f commit afe11da

File tree

9 files changed

+44
-30
lines changed

9 files changed

+44
-30
lines changed

docs/changelog/1324.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Replace ``pkg_resources`` with ``importlib_metadata`` for speed - by :user:`asottile`.

setup.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ classifiers =
3838
packages = find:
3939
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
4040
install_requires =
41-
setuptools >= 30.0.0
42-
pluggy >= 0.3.0, <1
41+
importlib-metadata
42+
packaging
43+
pluggy >= 0.12.0, <1
4344
py >= 1.4.17, <2
4445
six >= 1.0.0, <2
4546
virtualenv >= 14.0.0

src/tox/config/__init__.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
from subprocess import list2cmdline
1616
from threading import Thread
1717

18-
import pkg_resources
18+
import importlib_metadata
1919
import pluggy
2020
import py
2121
import toml
22+
from packaging import requirements
23+
from packaging.utils import canonicalize_name
2224

2325
import tox
2426
from tox.constants import INFO
27+
from tox.exception import MissingDependency
2528
from tox.interpreters import Interpreters, NoInterpreterInfo
2629
from tox.reporter import (
2730
REPORTER_TIMESTAMP_ON_ENV,
@@ -192,10 +195,10 @@ def _cut_off_dep_comment(name):
192195
@classmethod
193196
def _is_same_dep(cls, dep1, dep2):
194197
"""Definitions are the same if they refer to the same package, even if versions differ."""
195-
dep1_name = pkg_resources.Requirement.parse(dep1).project_name
198+
dep1_name = canonicalize_name(requirements.Requirement(dep1).name)
196199
try:
197-
dep2_name = pkg_resources.Requirement.parse(dep2).project_name
198-
except pkg_resources.RequirementParseError:
200+
dep2_name = canonicalize_name(requirements.Requirement(dep2).name)
201+
except requirements.InvalidRequirement:
199202
# we couldn't parse a version, probably a URL
200203
return False
201204
return dep1_name == dep2_name
@@ -1133,17 +1136,20 @@ def ensure_requires_satisfied(config, requires, min_version):
11331136
for require in requires + [min_version]:
11341137
# noinspection PyBroadException
11351138
try:
1136-
package = pkg_resources.Requirement.parse(require)
1137-
if package.project_name not in exists:
1139+
package = requirements.Requirement(require)
1140+
package_name = canonicalize_name(package.name)
1141+
if package_name not in exists:
11381142
deps.append(DepConfig(require, None))
1139-
exists.add(package.project_name)
1140-
pkg_resources.get_distribution(package)
1141-
except pkg_resources.RequirementParseError as exception:
1143+
exists.add(package_name)
1144+
dist = importlib_metadata.distribution(package_name)
1145+
if not package.specifier.contains(dist.version, prereleases=True):
1146+
raise MissingDependency(package)
1147+
except requirements.InvalidRequirement as exception:
11421148
failed_to_parse = True
11431149
error("failed to parse {!r}".format(exception))
11441150
except Exception as exception:
11451151
verbosity1("could not satisfy requires {!r}".format(exception))
1146-
missing_requirements.append(str(pkg_resources.Requirement(require)))
1152+
missing_requirements.append(str(requirements.Requirement(require)))
11471153
if failed_to_parse:
11481154
raise tox.exception.BadRequirement()
11491155
config.run_provision = bool(len(missing_requirements))

src/tox/package/builder/isolated.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import json
44
from collections import namedtuple
55

6-
import pkg_resources
76
import six
7+
from packaging.requirements import Requirement
8+
from packaging.utils import canonicalize_name
89

910
from tox import reporter
1011
from tox.config import DepConfig, get_py_project_toml
@@ -31,11 +32,11 @@ def build(config, session):
3132

3233
build_requires = get_build_requires(build_info, package_venv, config.setupdir)
3334
# we need to filter out requirements already specified in pyproject.toml or user deps
34-
base_build_deps = {pkg_resources.Requirement(r.name).key for r in package_venv.envconfig.deps}
35+
base_build_deps = {canonicalize_name(r.name) for r in package_venv.envconfig.deps}
3536
build_requires_dep = [
3637
DepConfig(r, None)
3738
for r in build_requires
38-
if pkg_resources.Requirement(r).key not in base_build_deps
39+
if canonicalize_name(Requirement(r).name) not in base_build_deps
3940
]
4041
if build_requires_dep:
4142
with package_venv.new_action("build_requires", package_venv.envconfig.envdir) as action:

src/tox/package/local.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import re
33

4-
import pkg_resources
4+
import packaging.version
55
import py
66

77
import tox
@@ -58,7 +58,6 @@ def get_version_from_filename(basename):
5858
return None
5959
version = m.group(1)
6060
try:
61-
62-
return pkg_resources.packaging.version.Version(version)
63-
except pkg_resources.packaging.version.InvalidVersion:
61+
return packaging.version.Version(version)
62+
except packaging.version.InvalidVersion:
6463
return None

src/tox/session/commands/show_config.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import sys
22
from collections import OrderedDict
33

4+
import importlib_metadata
5+
from packaging.requirements import Requirement
6+
from packaging.utils import canonicalize_name
47
from six import StringIO
58
from six.moves import configparser
69

@@ -57,17 +60,21 @@ def tox_info(config, parser):
5760

5861

5962
def version_info(parser):
60-
import pkg_resources
61-
6263
versions = OrderedDict()
63-
visited = set()
6464
to_visit = {"tox"}
6565
while to_visit:
6666
current = to_visit.pop()
67-
visited.add(current)
68-
current_dist = pkg_resources.get_distribution(current)
69-
to_visit.update(i.name for i in current_dist.requires() if i.name not in visited)
70-
versions[current] = current_dist.version
67+
current_dist = importlib_metadata.distribution(current)
68+
current_name = canonicalize_name(current_dist.metadata["name"])
69+
versions[current_name] = current_dist.version
70+
if current_dist.requires is not None:
71+
for require in current_dist.requires:
72+
pkg = Requirement(require)
73+
pkg_name = canonicalize_name(pkg.name)
74+
if (
75+
pkg.marker is None or pkg.marker.evaluate({"extra": ""})
76+
) and pkg_name not in versions:
77+
to_visit.add(pkg_name)
7178
set_section(parser, "tox:versions", versions)
7279

7380

src/tox/venv.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from itertools import chain
88

99
import py
10-
from pkg_resources import to_filename
1110

1211
import tox
1312
from tox import reporter
@@ -324,7 +323,7 @@ def _needs_reinstall(self, setupdir, action):
324323
sys_path = json.loads(out)
325324
except ValueError:
326325
sys_path = []
327-
egg_info_fname = ".".join((to_filename(name), "egg-info"))
326+
egg_info_fname = ".".join((name.replace("-", "_"), "egg-info"))
328327
for d in reversed(sys_path):
329328
egg_info = py.path.local(d).join(egg_info_fname)
330329
if egg_info.check():

tests/unit/session/test_provision.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def test_provision_bad_requires(newconfig, capsys, monkeypatch):
9595
""",
9696
)
9797
out, err = capsys.readouterr()
98-
assert "ERROR: failed to parse RequirementParseError" in out
98+
assert "ERROR: failed to parse InvalidRequirement" in out
9999
assert not err
100100

101101

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ include_trailing_comma = True
139139
force_grid_wrap = 0
140140
line_length = 99
141141
known_first_party = tox,tests
142-
known_third_party = apiclient,docutils,filelock,flaky,freezegun,git,httplib2,oauth2client,packaging,pathlib2,pkg_resources,pluggy,py,pytest,setuptools,six,sphinx,toml
142+
known_third_party = apiclient,docutils,filelock,flaky,freezegun,git,httplib2,importlib_metadata,oauth2client,packaging,pathlib2,pluggy,py,pytest,setuptools,six,sphinx,toml
143143
144144
[testenv:release]
145145
description = do a release, required posarg of the version number

0 commit comments

Comments
 (0)