Skip to content

Commit 48afe2e

Browse files
committed
Merge pull request #3 from jhesketh/master
Refactor and add support for pip-extra-reqs
2 parents 31a9ff0 + 246b9e0 commit 48afe2e

13 files changed

+745
-445
lines changed

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
recursive-include pip_missing_reqs
1+
recursive-include pip_check_reqs
22
include *.rst
33
include LICENSE
44
include setup.*

README.rst

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
pip-missing-reqs
2-
================
1+
pip-check-reqs
2+
==============
33

44
It happens: you start using a module in your project and it works and you
55
don't realise that it's only being included in your `virtualenv`_ because
@@ -28,15 +28,24 @@ Basic usage, running in your project directory::
2828
This will find all imports in the code in "sample" and check that the
2929
packages those modules belong to are in the requirements.txt file.
3030

31+
Additionally it is possible to check that there are no dependencies in
32+
requirements.txt that are then unused in the project::
33+
<activate virtualenv for your project>
34+
pip-extra-reqs --ignore-file=sample/tests/* sample
35+
36+
This would find anything that is listed in requirements.txt but that is not
37+
imported by sample.
3138

3239
Sample tox.ini configuration
3340
----------------------------
3441

3542
To make your life easier, copy something like this into your tox.ini::
3643

37-
[testenv:pip-missing-reqs]
44+
[testenv:pip-check-reqs]
3845
deps=-rrequirements.txt
39-
commands=pip-missing-reqs --ignore-file=sample/tests/* sample
46+
commands=
47+
pip-missing-reqs --ignore-file=sample/tests/* sample
48+
pip-extra-reqs --ignore-file=sample/tests/* sample
4049

4150

4251
Excluding test files (or others) from this check
File renamed without changes.

pip_missing_reqs/find_missing_reqs.py renamed to pip_check_reqs/common.py

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import ast
2-
import collections
32
import fnmatch
43
import imp
54
import logging
6-
import optparse
75
import os
86
import re
9-
import sys
10-
11-
from pip.commands.show import search_packages_info
12-
from pip.download import PipSession
13-
from pip.req import parse_requirements
14-
from pip.utils import get_installed_distributions, normalize_name
157

168
log = logging.getLogger(__name__)
179

@@ -135,52 +127,6 @@ def is_package_file(path):
135127
return ''
136128

137129

138-
def find_missing_reqs(options):
139-
# 1. find files used by imports in the code (as best we can without
140-
# executing)
141-
used_modules = find_imported_modules(options)
142-
143-
# 2. find which packages provide which files
144-
installed_files = {}
145-
all_pkgs = (pkg.project_name for pkg in get_installed_distributions())
146-
for package in search_packages_info(all_pkgs):
147-
log.debug('installed package: %s (at %s)', package['name'],
148-
package['location'])
149-
for file in package['files'] or []:
150-
path = os.path.realpath(os.path.join(package['location'], file))
151-
installed_files[path] = package['name']
152-
package_path = is_package_file(path)
153-
if package_path:
154-
# we've seen a package file so add the bare package directory
155-
# to the installed list as well as we might want to look up
156-
# a package by its directory path later
157-
installed_files[package_path] = package['name']
158-
159-
# 3. match imported modules against those packages
160-
used = collections.defaultdict(list)
161-
for modname, info in used_modules.items():
162-
# probably standard library if it's not in the files list
163-
if info.filename in installed_files:
164-
used_name = normalize_name(installed_files[info.filename])
165-
log.debug('used module: %s (from package %s)', modname,
166-
installed_files[info.filename])
167-
used[used_name].append(info)
168-
else:
169-
log.debug(
170-
'used module: %s (from file %s, assuming stdlib or local)',
171-
modname, info.filename)
172-
173-
# 4. compare with requirements.txt
174-
explicit = set()
175-
for requirement in parse_requirements('requirements.txt',
176-
session=PipSession()):
177-
log.debug('found requirement: %s', requirement.name)
178-
explicit.add(normalize_name(requirement.name))
179-
180-
return [(name, used[name]) for name in used
181-
if name not in explicit]
182-
183-
184130
def ignorer(ignore_cfg):
185131
if not ignore_cfg:
186132
return lambda candidate: False
@@ -193,63 +139,3 @@ def f(candidate, ignore_cfg=ignore_cfg):
193139
return True
194140
return False
195141
return f
196-
197-
198-
def main():
199-
from pip_missing_reqs import __version__
200-
201-
usage = 'usage: %prog [options] files or directories'
202-
parser = optparse.OptionParser(usage)
203-
parser.add_option("-f", "--ignore-file", dest="ignore_files",
204-
action="append", default=[],
205-
help="file paths globs to ignore")
206-
parser.add_option("-m", "--ignore-module", dest="ignore_mods",
207-
action="append", default=[],
208-
help="used module names (globs are ok) to ignore")
209-
parser.add_option("-v", "--verbose", dest="verbose",
210-
action="store_true", default=False, help="be more verbose")
211-
parser.add_option("-d", "--debug", dest="debug",
212-
action="store_true", default=False, help="be *really* verbose")
213-
parser.add_option("--version", dest="version",
214-
action="store_true", default=False, help="display version information")
215-
216-
(options, args) = parser.parse_args()
217-
218-
if options.version:
219-
sys.exit(__version__)
220-
221-
if not args:
222-
parser.error("no source files or directories specified")
223-
sys.exit(2)
224-
225-
options.ignore_files = ignorer(options.ignore_files)
226-
options.ignore_mods = ignorer(options.ignore_mods)
227-
228-
options.paths = args
229-
230-
logging.basicConfig(format='%(message)s')
231-
if options.debug:
232-
log.setLevel(logging.DEBUG)
233-
elif options.verbose:
234-
log.setLevel(logging.INFO)
235-
else:
236-
log.setLevel(logging.WARN)
237-
238-
log.info('using pip_missing_reqs-%s from %s', __version__, __file__)
239-
240-
missing = find_missing_reqs(options)
241-
242-
if missing:
243-
log.warning('Missing requirements:')
244-
for name, uses in missing:
245-
for use in uses:
246-
for filename, lineno in use.locations:
247-
log.warning('%s:%s dist=%s module=%s',
248-
os.path.relpath(filename), lineno, name, use.modname)
249-
250-
if missing:
251-
sys.exit(1)
252-
253-
254-
if __name__ == '__main__': # pragma: no cover
255-
main()

pip_check_reqs/find_extra_reqs.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import collections
2+
import logging
3+
import optparse
4+
import os
5+
import sys
6+
7+
from pip.commands.show import search_packages_info
8+
from pip.download import PipSession
9+
from pip.req import parse_requirements
10+
from pip.utils import get_installed_distributions, normalize_name
11+
12+
from pip_check_reqs import common
13+
14+
log = logging.getLogger(__name__)
15+
16+
17+
def find_extra_reqs(options):
18+
# 1. find files used by imports in the code (as best we can without
19+
# executing)
20+
used_modules = common.find_imported_modules(options)
21+
22+
# 2. find which packages provide which files
23+
installed_files = {}
24+
all_pkgs = (pkg.project_name for pkg in get_installed_distributions())
25+
for package in search_packages_info(all_pkgs):
26+
log.debug('installed package: %s (at %s)', package['name'],
27+
package['location'])
28+
for f in package['files'] or []:
29+
path = os.path.realpath(os.path.join(package['location'], f))
30+
installed_files[path] = package['name']
31+
package_path = common.is_package_file(path)
32+
if package_path:
33+
# we've seen a package file so add the bare package directory
34+
# to the installed list as well as we might want to look up
35+
# a package by its directory path later
36+
installed_files[package_path] = package['name']
37+
38+
# 3. match imported modules against those packages
39+
used = collections.defaultdict(list)
40+
for modname, info in used_modules.items():
41+
# probably standard library if it's not in the files list
42+
if info.filename in installed_files:
43+
used_name = normalize_name(installed_files[info.filename])
44+
log.debug('used module: %s (from package %s)', modname,
45+
installed_files[info.filename])
46+
used[used_name].append(info)
47+
else:
48+
log.debug(
49+
'used module: %s (from file %s, assuming stdlib or local)',
50+
modname, info.filename)
51+
52+
# 4. compare with requirements.txt
53+
explicit = set()
54+
for requirement in parse_requirements('requirements.txt',
55+
session=PipSession()):
56+
log.debug('found requirement: %s', requirement.name)
57+
explicit.add(normalize_name(requirement.name))
58+
59+
return [name for name in explicit if name not in used]
60+
61+
62+
def main():
63+
from pip_check_reqs import __version__
64+
65+
usage = 'usage: %prog [options] files or directories'
66+
parser = optparse.OptionParser(usage)
67+
parser.add_option("-f", "--ignore-file", dest="ignore_files",
68+
action="append", default=[],
69+
help="file paths globs to ignore")
70+
parser.add_option("-m", "--ignore-module", dest="ignore_mods",
71+
action="append", default=[],
72+
help="used module names (globs are ok) to ignore")
73+
parser.add_option("-v", "--verbose", dest="verbose",
74+
action="store_true", default=False, help="be more verbose")
75+
parser.add_option("-d", "--debug", dest="debug",
76+
action="store_true", default=False, help="be *really* verbose")
77+
parser.add_option("--version", dest="version",
78+
action="store_true", default=False, help="display version information")
79+
80+
(options, args) = parser.parse_args()
81+
82+
if options.version:
83+
sys.exit(__version__)
84+
85+
if not args:
86+
parser.error("no source files or directories specified")
87+
sys.exit(2)
88+
89+
options.ignore_files = common.ignorer(options.ignore_files)
90+
options.ignore_mods = common.ignorer(options.ignore_mods)
91+
92+
options.paths = args
93+
94+
logging.basicConfig(format='%(message)s')
95+
if options.debug:
96+
log.setLevel(logging.DEBUG)
97+
elif options.verbose:
98+
log.setLevel(logging.INFO)
99+
else:
100+
log.setLevel(logging.WARN)
101+
102+
log.info('using pip_check_reqs-%s from %s', __version__, __file__)
103+
104+
extras = find_extra_reqs(options)
105+
106+
if extras:
107+
log.warning('Extra requirements:')
108+
for name in extras:
109+
log.warning('%s in requirements.txt' % name)
110+
111+
if extras:
112+
sys.exit(1)
113+
114+
115+
if __name__ == '__main__': # pragma: no cover
116+
main()

0 commit comments

Comments
 (0)