Skip to content

Commit f69db08

Browse files
Merge pull request #38 from r1chardj0n3s/nightly-ci
Run tests nightly with the latest pip version
2 parents f2277bf + e977994 commit f69db08

File tree

9 files changed

+149
-58
lines changed

9 files changed

+149
-58
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
branches: [master]
88
pull_request:
99
branches: [master]
10+
schedule:
11+
# * is a special character in YAML so you have to quote this string
12+
# Run at 1:00 every day
13+
- cron: '0 1 * * *'
1014

1115
jobs:
1216
build:
@@ -16,8 +20,9 @@ jobs:
1620
strategy:
1721
matrix:
1822
# These versions match the minimum and maximum versions of pip in
19-
# requirements.txt
20-
pip-version: ['==10.0.1', '<= 19']
23+
# requirements.txt.
24+
# An empty string here represents the latest version.
25+
pip-version: ['==10.0.1', '']
2126
python-version: [3.6, 3.7, 3.8]
2227

2328
steps:

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ Release History
77
- Remove support for Python 2.
88
Please use an older version of this tool if you require that support.
99
- Remove requirement for setuptools.
10-
- Add restriction on the pip version to install to match the pip versions which work.
10+
- Support newer versions of pip, including the current version, for more features (20.1.1).
11+
Thanks to @Czaki for important parts of this change.
1112

1213
2.0.1
1314

pip_check_reqs/common.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import re
77

88
from packaging.utils import canonicalize_name
9-
from pip._internal.download import PipSession
9+
# Between different versions of pip the location of PipSession has changed.
10+
try:
11+
from pip._internal.network.session import PipSession
12+
except ImportError: # pragma: no cover
13+
from pip._internal.download import PipSession
1014
from pip._internal.req.req_file import parse_requirements
1115

1216
log = logging.getLogger(__name__)
@@ -121,15 +125,26 @@ def find_imported_modules(options):
121125
return vis.finalise()
122126

123127

124-
def find_required_modules(options):
128+
def find_required_modules(options, requirements_filename: str):
125129
explicit = set()
126-
for requirement in parse_requirements('requirements.txt',
130+
for requirement in parse_requirements(requirements_filename,
127131
session=PipSession()):
132+
try:
133+
requirement_name = requirement.name
134+
# The type of "requirement" changed between pip versions.
135+
# We exclude the "except" from coverage so that on any pip version we
136+
# can report 100% coverage.
137+
except AttributeError: # pragma: no cover
138+
from pip._internal.req.constructors import install_req_from_line
139+
requirement_name = install_req_from_line(
140+
requirement.requirement,
141+
).name
142+
128143
if options.ignore_reqs(requirement):
129-
log.debug('ignoring requirement: %s', requirement.name)
144+
log.debug('ignoring requirement: %s', requirement_name)
130145
else:
131-
log.debug('found requirement: %s', requirement.name)
132-
explicit.add(canonicalize_name(requirement.name))
146+
log.debug('found requirement: %s', requirement_name)
147+
explicit.add(canonicalize_name(requirement_name))
133148
return explicit
134149

135150

@@ -149,9 +164,22 @@ def ignorer(ignore_cfg):
149164

150165
def f(candidate, ignore_cfg=ignore_cfg):
151166
for ignore in ignore_cfg:
152-
if fnmatch.fnmatch(candidate, ignore):
167+
try:
168+
from pip._internal.req.constructors import (
169+
install_req_from_line,
170+
)
171+
candidate_path = install_req_from_line( # pragma: no cover
172+
candidate.requirement,
173+
).name
174+
except (ImportError, AttributeError):
175+
try:
176+
candidate_path = candidate.name
177+
except AttributeError:
178+
candidate_path = candidate
179+
180+
if fnmatch.fnmatch(candidate_path, ignore):
153181
return True
154-
elif fnmatch.fnmatch(os.path.relpath(candidate), ignore):
182+
elif fnmatch.fnmatch(os.path.relpath(candidate_path), ignore):
155183
return True
156184
return False
157185

pip_check_reqs/find_extra_reqs.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
log = logging.getLogger(__name__)
1313

1414

15-
def find_extra_reqs(options):
15+
def find_extra_reqs(options, requirements_filename):
1616
# 1. find files used by imports in the code (as best we can without
1717
# executing)
1818
used_modules = common.find_imported_modules(options)
@@ -50,7 +50,10 @@ def find_extra_reqs(options):
5050
modname, info.filename)
5151

5252
# 4. compare with requirements.txt
53-
explicit = common.find_required_modules(options)
53+
explicit = common.find_required_modules(
54+
options=options,
55+
requirements_filename=requirements_filename,
56+
)
5457

5558
return [name for name in explicit if name not in used]
5659

@@ -121,12 +124,20 @@ def main():
121124

122125
log.info('using pip_check_reqs-%s from %s', __version__, __file__)
123126

124-
extras = find_extra_reqs(options)
127+
requirements_filename = 'requirements.txt'
128+
extras = find_extra_reqs(
129+
options=options,
130+
requirements_filename=requirements_filename,
131+
)
125132

126133
if extras:
127134
log.warning('Extra requirements:')
128135
for name in extras:
129-
log.warning('%s in requirements.txt' % name)
136+
message = '{name} in {requirements_filename}'.format(
137+
name=name,
138+
requirements_filename=requirements_filename,
139+
)
140+
log.warning(message)
130141

131142
if extras:
132143
sys.exit(1)

pip_check_reqs/find_missing_reqs.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
from packaging.utils import canonicalize_name
88
from pip._internal.commands.show import search_packages_info
9-
from pip._internal.download import PipSession
9+
# Between different versions of pip the location of PipSession has changed.
10+
try:
11+
from pip._internal.network.session import PipSession
12+
except ImportError: # pragma: no cover
13+
from pip._internal.download import PipSession
1014
from pip._internal.req.req_file import parse_requirements
1115
from pip._internal.utils.misc import get_installed_distributions
1216

@@ -15,7 +19,7 @@
1519
log = logging.getLogger(__name__)
1620

1721

18-
def find_missing_reqs(options):
22+
def find_missing_reqs(options, requirements_filename):
1923
# 1. find files used by imports in the code (as best we can without
2024
# executing)
2125
used_modules = common.find_imported_modules(options)
@@ -54,10 +58,23 @@ def find_missing_reqs(options):
5458

5559
# 4. compare with requirements.txt
5660
explicit = set()
57-
for requirement in parse_requirements('requirements.txt',
58-
session=PipSession()):
59-
log.debug('found requirement: %s', requirement.name)
60-
explicit.add(canonicalize_name(requirement.name))
61+
for requirement in parse_requirements(
62+
requirements_filename,
63+
session=PipSession(),
64+
):
65+
try:
66+
requirement_name = requirement.name
67+
# The type of "requirement" changed between pip versions.
68+
# We exclude the "except" from coverage so that on any pip version we
69+
# can report 100% coverage.
70+
except AttributeError: # pragma: no cover
71+
from pip._internal.req.constructors import install_req_from_line
72+
requirement_name = install_req_from_line(
73+
requirement.requirement,
74+
).name
75+
76+
log.debug('found requirement: %s', requirement_name)
77+
explicit.add(canonicalize_name(requirement_name))
6178

6279
return [(name, used[name]) for name in used if name not in explicit]
6380

@@ -121,7 +138,11 @@ def main():
121138

122139
log.info('using pip_check_reqs-%s from %s', __version__, __file__)
123140

124-
missing = find_missing_reqs(options)
141+
requirements_filename = 'requirements.txt'
142+
missing = find_missing_reqs(
143+
options=options,
144+
requirements_filename=requirements_filename,
145+
)
125146

126147
if missing:
127148
log.warning('Missing requirements:')

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
packaging
2-
# These versions are matched in the GitHub workflows CI.
3-
pip >= 10.0.1, <= 19
2+
# Pinned pip versions are matched in the GitHub workflows CI matrix.
3+
pip >= 10.0.1

tests/test_common.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import absolute_import
22

33
import ast
4-
import collections
54
import logging
65
import os.path
6+
from pathlib import Path
77

88
import pytest
99
import pretend
@@ -157,25 +157,23 @@ def ignore_mods(module):
157157
(['spam*'], 'eggs', False),
158158
(['spam'], '/spam', True),
159159
])
160-
def test_ignorer(monkeypatch, ignore_cfg, candidate, result):
160+
def test_ignorer(monkeypatch, tmp_path: Path, ignore_cfg, candidate, result):
161161
monkeypatch.setattr(os.path, 'relpath', lambda s: s.lstrip('/'))
162162
ignorer = common.ignorer(ignore_cfg)
163163
assert ignorer(candidate) == result
164164

165165

166-
def test_find_required_modules(monkeypatch):
166+
def test_find_required_modules(monkeypatch, tmp_path: Path):
167167
class options:
168-
@staticmethod
169-
def ignore_reqs(req):
170-
if req.name == 'barfoo':
171-
return True
172-
return False
168+
pass
169+
170+
options.ignore_reqs = common.ignorer(ignore_cfg=['barfoo'])
173171

174-
FakeReq = collections.namedtuple('FakeReq', ['name'])
175-
requirements = [FakeReq('foobar'), FakeReq('barfoo')]
176-
monkeypatch.setattr(
177-
common, 'parse_requirements',
178-
pretend.call_recorder(lambda a, session=None: requirements))
172+
fake_requirements_file = tmp_path / 'requirements.txt'
173+
fake_requirements_file.write_text('foobar==1\nbarfoo==2')
179174

180-
reqs = common.find_required_modules(options)
175+
reqs = common.find_required_modules(
176+
options=options,
177+
requirements_filename=str(fake_requirements_file),
178+
)
181179
assert reqs == set(['foobar'])

tests/test_find_extra_reqs.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import collections
44
import logging
55
import optparse
6+
from pathlib import Path
67

78
import pytest
89
import pretend
@@ -37,7 +38,7 @@ def parse_args(self):
3738
return FakeOptParse
3839

3940

40-
def test_find_extra_reqs(monkeypatch):
41+
def test_find_extra_reqs(monkeypatch, tmp_path: Path):
4142
imported_modules = dict(spam=common.FoundModule('spam',
4243
'site-spam/spam.py',
4344
[('ham.py', 1)]),
@@ -64,19 +65,19 @@ def test_find_extra_reqs(monkeypatch):
6465
monkeypatch.setattr(find_extra_reqs, 'search_packages_info',
6566
pretend.call_recorder(lambda x: packages_info))
6667

67-
FakeReq = collections.namedtuple('FakeReq', ['name'])
68-
requirements = [FakeReq('foobar')]
69-
monkeypatch.setattr(
70-
common, 'parse_requirements',
71-
pretend.call_recorder(lambda a, session=None: requirements))
68+
fake_requirements_file = tmp_path / 'requirements.txt'
69+
fake_requirements_file.write_text('foobar==1')
7270

7371
class options:
7472
def ignore_reqs(x, y):
7573
return False
7674

7775
options = options()
7876

79-
result = find_extra_reqs.find_extra_reqs(options)
77+
result = find_extra_reqs.find_extra_reqs(
78+
options=options,
79+
requirements_filename=str(fake_requirements_file),
80+
)
8081
assert result == ['foobar']
8182

8283

@@ -86,7 +87,7 @@ def test_main_failure(monkeypatch, caplog, fake_opts):
8687
caplog.set_level(logging.WARN)
8788

8889
monkeypatch.setattr(find_extra_reqs, 'find_extra_reqs',
89-
lambda x: ['extra'])
90+
lambda options, requirements_filename: ['extra'])
9091

9192
with pytest.raises(SystemExit) as excinfo:
9293
find_extra_reqs.main()
@@ -145,7 +146,11 @@ def parse_args(self):
145146

146147
monkeypatch.setattr(optparse, 'OptionParser', FakeOptParse)
147148

148-
monkeypatch.setattr(find_extra_reqs, 'find_extra_reqs', lambda x: [])
149+
monkeypatch.setattr(
150+
find_extra_reqs,
151+
'find_extra_reqs',
152+
lambda options, requirements_filename: [],
153+
)
149154
find_extra_reqs.main()
150155

151156
for event in [(logging.DEBUG, 'debug'), (logging.INFO, 'info'),

0 commit comments

Comments
 (0)