|
1
|
|
|
#!/usr/bin/env python |
|
2
|
|
|
# coding=utf-8 |
|
3
|
|
|
from __future__ import division, print_function, unicode_literals |
|
4
|
|
|
|
|
5
|
|
|
import functools |
|
6
|
|
|
import hashlib |
|
7
|
|
|
import os.path |
|
8
|
|
|
import re |
|
9
|
|
|
import sys |
|
10
|
|
|
|
|
11
|
|
|
import pkg_resources |
|
12
|
|
|
|
|
13
|
|
|
import sacred.optional as opt |
|
14
|
|
|
from sacred import SETTINGS |
|
15
|
|
|
from sacred.utils import is_subdir, iter_prefixes, basestring |
|
16
|
|
|
|
|
17
|
|
|
MB = 1048576 |
|
18
|
|
|
MODULE_BLACKLIST = set(sys.builtin_module_names) |
|
19
|
|
|
# sadly many builtins are missing from the above, so we list them manually: |
|
20
|
|
|
MODULE_BLACKLIST |= { |
|
21
|
|
|
None, '__future__', '_abcoll', '_bootlocale', '_bsddb', '_bz2', |
|
22
|
|
|
'_codecs_cn', '_codecs_hk', '_codecs_iso2022', '_codecs_jp', '_codecs_kr', |
|
23
|
|
|
'_codecs_tw', '_collections_abc', '_compat_pickle', '_compression', |
|
24
|
|
|
'_crypt', '_csv', '_ctypes', '_ctypes_test', '_curses', '_curses_panel', |
|
25
|
|
|
'_dbm', '_decimal', '_dummy_thread', '_elementtree', '_gdbm', '_hashlib', |
|
26
|
|
|
'_hotshot', '_json', '_lsprof', '_LWPCookieJar', '_lzma', '_markupbase', |
|
27
|
|
|
'_MozillaCookieJar', '_multibytecodec', '_multiprocessing', '_opcode', |
|
28
|
|
|
'_osx_support', '_pydecimal', '_pyio', '_sitebuiltins', '_sqlite3', |
|
29
|
|
|
'_ssl', '_strptime', '_sysconfigdata', '_sysconfigdata_m', |
|
30
|
|
|
'_sysconfigdata_nd', '_testbuffer', '_testcapi', '_testimportmultiple', |
|
31
|
|
|
'_testmultiphase', '_threading_local', '_tkinter', '_weakrefset', 'abc', |
|
32
|
|
|
'aifc', 'antigravity', 'anydbm', 'argparse', 'ast', 'asynchat', 'asyncio', |
|
33
|
|
|
'asyncore', 'atexit', 'audiodev', 'audioop', 'base64', 'BaseHTTPServer', |
|
34
|
|
|
'Bastion', 'bdb', 'binhex', 'bisect', 'bsddb', 'bz2', 'calendar', |
|
35
|
|
|
'Canvas', 'CDROM', 'cgi', 'CGIHTTPServer', 'cgitb', 'chunk', 'cmath', |
|
36
|
|
|
'cmd', 'code', 'codecs', 'codeop', 'collections', 'colorsys', 'commands', |
|
37
|
|
|
'compileall', 'compiler', 'concurrent', 'ConfigParser', 'configparser', |
|
38
|
|
|
'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg', 'copyreg', |
|
39
|
|
|
'cProfile', 'crypt', 'csv', 'ctypes', 'curses', 'datetime', 'dbhash', |
|
40
|
|
|
'dbm', 'decimal', 'Dialog', 'difflib', 'dircache', 'dis', 'distutils', |
|
41
|
|
|
'DLFCN', 'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread', |
|
42
|
|
|
'dummy_threading', 'easy_install', 'email', 'encodings', 'ensurepip', |
|
43
|
|
|
'enum', 'filecmp', 'FileDialog', 'fileinput', 'FixTk', 'fnmatch', |
|
44
|
|
|
'formatter', 'fpectl', 'fpformat', 'fractions', 'ftplib', 'functools', |
|
45
|
|
|
'future_builtins', 'genericpath', 'getopt', 'getpass', 'gettext', 'glob', |
|
46
|
|
|
'gzip', 'hashlib', 'heapq', 'hmac', 'hotshot', 'html', 'htmlentitydefs', |
|
47
|
|
|
'htmllib', 'HTMLParser', 'http', 'httplib', 'idlelib', 'ihooks', |
|
48
|
|
|
'imaplib', 'imghdr', 'imp', 'importlib', 'imputil', 'IN', 'inspect', 'io', |
|
49
|
|
|
'ipaddress', 'json', 'keyword', 'lib2to3', 'linecache', 'linuxaudiodev', |
|
50
|
|
|
'locale', 'logging', 'lzma', 'macpath', 'macurl2path', 'mailbox', |
|
51
|
|
|
'mailcap', 'markupbase', 'md5', 'mhlib', 'mimetools', 'mimetypes', |
|
52
|
|
|
'MimeWriter', 'mimify', 'mmap', 'modulefinder', 'multifile', |
|
53
|
|
|
'multiprocessing', 'mutex', 'netrc', 'new', 'nis', 'nntplib', 'ntpath', |
|
54
|
|
|
'nturl2path', 'numbers', 'opcode', 'operator', 'optparse', 'os', |
|
55
|
|
|
'os2emxpath', 'ossaudiodev', 'parser', 'pathlib', 'pdb', 'pickle', |
|
56
|
|
|
'pickletools', 'pip', 'pipes', 'pkg_resources', 'pkgutil', 'platform', |
|
57
|
|
|
'plistlib', 'popen2', 'poplib', 'posixfile', 'posixpath', 'pprint', |
|
58
|
|
|
'profile', 'pstats', 'pty', 'py_compile', 'pyclbr', 'pydoc', 'pydoc_data', |
|
59
|
|
|
'pyexpat', 'Queue', 'queue', 'quopri', 'random', 're', 'readline', 'repr', |
|
60
|
|
|
'reprlib', 'resource', 'rexec', 'rfc822', 'rlcompleter', 'robotparser', |
|
61
|
|
|
'runpy', 'sched', 'ScrolledText', 'selectors', 'sets', 'setuptools', |
|
62
|
|
|
'sgmllib', 'sha', 'shelve', 'shlex', 'shutil', 'signal', 'SimpleDialog', |
|
63
|
|
|
'SimpleHTTPServer', 'SimpleXMLRPCServer', 'site', 'sitecustomize', |
|
64
|
|
|
'smtpd', 'smtplib', 'sndhdr', 'socket', 'SocketServer', 'socketserver', |
|
65
|
|
|
'sqlite3', 'sre', 'sre_compile', 'sre_constants', 'sre_parse', 'ssl', |
|
66
|
|
|
'stat', 'statistics', 'statvfs', 'string', 'StringIO', 'stringold', |
|
67
|
|
|
'stringprep', 'struct', 'subprocess', 'sunau', 'sunaudio', 'symbol', |
|
68
|
|
|
'symtable', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', |
|
69
|
|
|
'termios', 'test', 'textwrap', 'this', 'threading', 'timeit', 'Tix', |
|
70
|
|
|
'tkColorChooser', 'tkCommonDialog', 'Tkconstants', 'Tkdnd', |
|
71
|
|
|
'tkFileDialog', 'tkFont', 'tkinter', 'Tkinter', 'tkMessageBox', |
|
72
|
|
|
'tkSimpleDialog', 'toaiff', 'token', 'tokenize', 'trace', 'traceback', |
|
73
|
|
|
'tracemalloc', 'ttk', 'tty', 'turtle', 'types', 'TYPES', 'typing', |
|
74
|
|
|
'unittest', 'urllib', 'urllib2', 'urlparse', 'user', 'UserDict', |
|
75
|
|
|
'UserList', 'UserString', 'uu', 'uuid', 'venv', 'warnings', 'wave', |
|
76
|
|
|
'weakref', 'webbrowser', 'wheel', 'whichdb', 'wsgiref', 'xdrlib', 'xml', |
|
77
|
|
|
'xmllib', 'xmlrpc', 'xmlrpclib', 'xxlimited', 'zipapp', 'zipfile'} |
|
78
|
|
|
|
|
79
|
|
|
module = type(sys) |
|
80
|
|
|
PEP440_VERSION_PATTERN = re.compile(r""" |
|
81
|
|
|
^ |
|
82
|
|
|
(\d+!)? # epoch |
|
83
|
|
|
(\d[.\d]*(?<= \d)) # release |
|
84
|
|
|
((?:[abc]|rc)\d+)? # pre-release |
|
85
|
|
|
(?:(\.post\d+))? # post-release |
|
86
|
|
|
(?:(\.dev\d+))? # development release |
|
87
|
|
|
$ |
|
88
|
|
|
""", flags=re.VERBOSE) |
|
89
|
|
|
|
|
90
|
|
|
|
|
91
|
|
|
def get_py_file_if_possible(pyc_name): |
|
92
|
|
|
"""Try to retrieve a X.py file for a given X.py[c] file.""" |
|
93
|
|
|
if pyc_name.endswith('.py'): |
|
94
|
|
|
return pyc_name |
|
95
|
|
|
assert pyc_name.endswith('.pyc') |
|
96
|
|
|
non_compiled_file = pyc_name[:-1] |
|
97
|
|
|
if os.path.exists(non_compiled_file): |
|
98
|
|
|
return non_compiled_file |
|
99
|
|
|
return pyc_name |
|
100
|
|
|
|
|
101
|
|
|
|
|
102
|
|
|
def get_digest(filename): |
|
103
|
|
|
"""Compute the MD5 hash for a given file.""" |
|
104
|
|
|
h = hashlib.md5() |
|
105
|
|
|
with open(filename, 'rb') as f: |
|
106
|
|
|
data = f.read(1 * MB) |
|
107
|
|
|
while data: |
|
108
|
|
|
h.update(data) |
|
109
|
|
|
data = f.read(1 * MB) |
|
110
|
|
|
return h.hexdigest() |
|
111
|
|
|
|
|
112
|
|
|
|
|
113
|
|
|
def get_commit_if_possible(filename): |
|
114
|
|
|
"""Try to retrieve VCS information for a given file. |
|
115
|
|
|
|
|
116
|
|
|
Currently only supports git using the gitpython package. |
|
117
|
|
|
|
|
118
|
|
|
Parameters |
|
119
|
|
|
---------- |
|
120
|
|
|
filename : str |
|
121
|
|
|
|
|
122
|
|
|
Returns |
|
123
|
|
|
------- |
|
124
|
|
|
path: str |
|
125
|
|
|
The base path of the repository |
|
126
|
|
|
commit: str |
|
127
|
|
|
The commit hash |
|
128
|
|
|
is_dirty: bool |
|
129
|
|
|
True if there are uncommitted changes in the repository |
|
130
|
|
|
""" |
|
131
|
|
|
# git |
|
132
|
|
|
if opt.has_gitpython: |
|
133
|
|
|
from git import Repo, InvalidGitRepositoryError |
|
134
|
|
|
try: |
|
135
|
|
|
directory = os.path.dirname(filename) |
|
136
|
|
|
repo = Repo(directory, search_parent_directories=True) |
|
137
|
|
|
try: |
|
138
|
|
|
path = repo.remote().url |
|
139
|
|
|
except ValueError: |
|
140
|
|
|
path = 'git:/' + repo.working_dir |
|
141
|
|
|
is_dirty = repo.is_dirty() |
|
142
|
|
|
commit = repo.head.commit.hexsha |
|
143
|
|
|
return path, commit, is_dirty |
|
144
|
|
|
except (InvalidGitRepositoryError, ValueError): |
|
145
|
|
|
pass |
|
146
|
|
|
return None, None, None |
|
147
|
|
|
|
|
148
|
|
|
|
|
149
|
|
|
@functools.total_ordering |
|
150
|
|
|
class Source(object): |
|
151
|
|
|
def __init__(self, filename, digest, repo, commit, isdirty): |
|
152
|
|
|
self.filename = filename |
|
153
|
|
|
self.digest = digest |
|
154
|
|
|
self.repo = repo |
|
155
|
|
|
self.commit = commit |
|
156
|
|
|
self.is_dirty = isdirty |
|
157
|
|
|
|
|
158
|
|
|
@staticmethod |
|
159
|
|
|
def create(filename): |
|
160
|
|
|
if not filename or not os.path.exists(filename): |
|
161
|
|
|
raise ValueError('invalid filename or file not found "{}"' |
|
162
|
|
|
.format(filename)) |
|
163
|
|
|
|
|
164
|
|
|
main_file = get_py_file_if_possible(os.path.abspath(filename)) |
|
165
|
|
|
repo, commit, is_dirty = get_commit_if_possible(main_file) |
|
166
|
|
|
return Source(main_file, get_digest(main_file), repo, commit, is_dirty) |
|
167
|
|
|
|
|
168
|
|
|
def to_json(self, base_dir=None): |
|
169
|
|
|
if base_dir: |
|
170
|
|
|
return os.path.relpath(self.filename, base_dir), self.digest |
|
171
|
|
|
else: |
|
172
|
|
|
return self.filename, self.digest |
|
173
|
|
|
|
|
174
|
|
|
def __hash__(self): |
|
175
|
|
|
return hash(self.filename) |
|
176
|
|
|
|
|
177
|
|
|
def __eq__(self, other): |
|
178
|
|
|
if isinstance(other, Source): |
|
179
|
|
|
return self.filename == other.filename |
|
180
|
|
|
elif isinstance(other, basestring): |
|
181
|
|
|
return self.filename == other |
|
182
|
|
|
else: |
|
183
|
|
|
return False |
|
184
|
|
|
|
|
185
|
|
|
def __le__(self, other): |
|
186
|
|
|
return self.filename.__le__(other.filename) |
|
187
|
|
|
|
|
188
|
|
|
def __repr__(self): |
|
189
|
|
|
return '<Source: {}>'.format(self.filename) |
|
190
|
|
|
|
|
191
|
|
|
|
|
192
|
|
|
@functools.total_ordering |
|
193
|
|
|
class PackageDependency(object): |
|
194
|
|
|
modname_to_dist = {} |
|
195
|
|
|
|
|
196
|
|
|
def __init__(self, name, version): |
|
197
|
|
|
self.name = name |
|
198
|
|
|
self.version = version |
|
199
|
|
|
|
|
200
|
|
|
def fill_missing_version(self): |
|
201
|
|
|
if self.version is not None: |
|
202
|
|
|
return |
|
203
|
|
|
dist = pkg_resources.working_set.by_key.get(self.name) |
|
204
|
|
|
self.version = dist.version if dist else None |
|
205
|
|
|
|
|
206
|
|
|
def to_json(self): |
|
207
|
|
|
return '{}=={}'.format(self.name, self.version or '<unknown>') |
|
208
|
|
|
|
|
209
|
|
|
def __hash__(self): |
|
210
|
|
|
return hash(self.name) |
|
211
|
|
|
|
|
212
|
|
|
def __eq__(self, other): |
|
213
|
|
|
if isinstance(other, PackageDependency): |
|
214
|
|
|
return self.name == other.name |
|
215
|
|
|
else: |
|
216
|
|
|
return False |
|
217
|
|
|
|
|
218
|
|
|
def __le__(self, other): |
|
219
|
|
|
return self.name.__le__(other.name) |
|
220
|
|
|
|
|
221
|
|
|
def __repr__(self): |
|
222
|
|
|
return '<PackageDependency: {}={}>'.format(self.name, self.version) |
|
223
|
|
|
|
|
224
|
|
|
@staticmethod |
|
225
|
|
|
def get_version_heuristic(mod): |
|
226
|
|
|
possible_version_attributes = ['__version__', 'VERSION', 'version'] |
|
227
|
|
|
for vattr in possible_version_attributes: |
|
228
|
|
|
if hasattr(mod, vattr): |
|
229
|
|
|
version = getattr(mod, vattr) |
|
230
|
|
|
if isinstance(version, basestring) and \ |
|
231
|
|
|
PEP440_VERSION_PATTERN.match(version): |
|
232
|
|
|
return version |
|
233
|
|
|
if isinstance(version, tuple): |
|
234
|
|
|
version = '.'.join([str(n) for n in version]) |
|
235
|
|
|
if PEP440_VERSION_PATTERN.match(version): |
|
236
|
|
|
return version |
|
237
|
|
|
|
|
238
|
|
|
return None |
|
239
|
|
|
|
|
240
|
|
|
@classmethod |
|
241
|
|
|
def create(cls, mod): |
|
242
|
|
|
if not cls.modname_to_dist: |
|
243
|
|
|
# some packagenames don't match the module names (e.g. PyYAML) |
|
244
|
|
|
# so we set up a dict to map from module name to package name |
|
245
|
|
|
for dist in pkg_resources.working_set: |
|
246
|
|
|
try: |
|
247
|
|
|
toplevel_names = dist._get_metadata('top_level.txt') |
|
248
|
|
|
for tln in toplevel_names: |
|
249
|
|
|
cls.modname_to_dist[ |
|
250
|
|
|
tln] = dist.project_name, dist.version |
|
251
|
|
|
except: |
|
252
|
|
|
pass |
|
253
|
|
|
|
|
254
|
|
|
# version = PackageDependency.get_version_heuristic(mod) |
|
255
|
|
|
name, version = cls.modname_to_dist.get(mod.__name__, |
|
256
|
|
|
(mod.__name__, None)) |
|
257
|
|
|
|
|
258
|
|
|
return PackageDependency(name, version) |
|
259
|
|
|
|
|
260
|
|
|
|
|
261
|
|
|
def splitall(path): |
|
262
|
|
|
"""Split a path into a list of directory names (and optionally a filename). |
|
263
|
|
|
|
|
264
|
|
|
Parameters |
|
265
|
|
|
---------- |
|
266
|
|
|
path: str |
|
267
|
|
|
The path (absolute or relative). |
|
268
|
|
|
|
|
269
|
|
|
Returns |
|
270
|
|
|
------- |
|
271
|
|
|
allparts: list[str] |
|
272
|
|
|
List of directory names (and optionally a filename) |
|
273
|
|
|
|
|
274
|
|
|
Example |
|
275
|
|
|
------- |
|
276
|
|
|
"foo/bar/baz.py" => ["foo", "bar", "baz.py"] |
|
277
|
|
|
"/absolute/path.py" => ["/", "absolute", "baz.py"] |
|
278
|
|
|
|
|
279
|
|
|
Notes |
|
280
|
|
|
----- |
|
281
|
|
|
Credit to Trent Mick. Taken from |
|
282
|
|
|
https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch04s16.html |
|
283
|
|
|
""" |
|
284
|
|
|
allparts = [] |
|
285
|
|
|
while True: |
|
286
|
|
|
parts = os.path.split(path) |
|
287
|
|
|
if parts[0] == path: # sentinel for absolute paths |
|
288
|
|
|
allparts.insert(0, parts[0]) |
|
289
|
|
|
break |
|
290
|
|
|
elif parts[1] == path: # sentinel for relative paths |
|
291
|
|
|
allparts.insert(0, parts[1]) |
|
292
|
|
|
break |
|
293
|
|
|
else: |
|
294
|
|
|
path = parts[0] |
|
295
|
|
|
allparts.insert(0, parts[1]) |
|
296
|
|
|
return allparts |
|
297
|
|
|
|
|
298
|
|
|
|
|
299
|
|
|
def convert_path_to_module_parts(path): |
|
300
|
|
|
"""Convert path to a python file into list of module names.""" |
|
301
|
|
|
module_parts = splitall(path) |
|
302
|
|
|
if module_parts[-1] in ['__init__.py', '__init__.pyc']: |
|
303
|
|
|
# remove trailing __init__.py |
|
304
|
|
|
module_parts = module_parts[:-1] |
|
305
|
|
|
else: |
|
306
|
|
|
# remove file extension |
|
307
|
|
|
module_parts[-1], _ = os.path.splitext(module_parts[-1]) |
|
308
|
|
|
return module_parts |
|
309
|
|
|
|
|
310
|
|
|
|
|
311
|
|
|
def is_local_source(filename, modname, experiment_path): |
|
312
|
|
|
"""Check if a module comes from the given experiment path. |
|
313
|
|
|
|
|
314
|
|
|
Check if a module, given by name and filename, is from (a subdirectory of ) |
|
315
|
|
|
the given experiment path. |
|
316
|
|
|
This is used to determine if the module is a local source file, or rather |
|
317
|
|
|
a package dependency. |
|
318
|
|
|
|
|
319
|
|
|
Parameters |
|
320
|
|
|
---------- |
|
321
|
|
|
filename: str |
|
322
|
|
|
The absolute filename of the module in question. |
|
323
|
|
|
(Usually module.__file__) |
|
324
|
|
|
modname: str |
|
325
|
|
|
The full name of the module including parent namespaces. |
|
326
|
|
|
experiment_path: str |
|
327
|
|
|
The base path of the experiment. |
|
328
|
|
|
|
|
329
|
|
|
Returns |
|
330
|
|
|
------- |
|
331
|
|
|
bool: |
|
332
|
|
|
True if the module was imported locally from (a subdir of) the |
|
333
|
|
|
experiment_path, and False otherwise. |
|
334
|
|
|
""" |
|
335
|
|
|
if not is_subdir(filename, experiment_path): |
|
336
|
|
|
return False |
|
337
|
|
|
rel_path = os.path.relpath(filename, experiment_path) |
|
338
|
|
|
path_parts = convert_path_to_module_parts(rel_path) |
|
339
|
|
|
|
|
340
|
|
|
mod_parts = modname.split('.') |
|
341
|
|
|
if path_parts == mod_parts: |
|
342
|
|
|
return True |
|
343
|
|
|
if len(path_parts) > len(mod_parts): |
|
344
|
|
|
return False |
|
345
|
|
|
abs_path_parts = convert_path_to_module_parts(os.path.abspath(filename)) |
|
346
|
|
|
return all([p == m for p, m in zip(reversed(abs_path_parts), |
|
347
|
|
|
reversed(mod_parts))]) |
|
348
|
|
|
|
|
349
|
|
|
|
|
350
|
|
|
def get_main_file(globs): |
|
351
|
|
|
filename = globs.get('__file__') |
|
352
|
|
|
|
|
353
|
|
|
if filename is None: |
|
354
|
|
|
experiment_path = os.path.abspath(os.path.curdir) |
|
355
|
|
|
main = None |
|
356
|
|
|
else: |
|
357
|
|
|
main = Source.create(globs.get('__file__')) |
|
358
|
|
|
experiment_path = os.path.dirname(main.filename) |
|
359
|
|
|
return experiment_path, main |
|
360
|
|
|
|
|
361
|
|
|
|
|
362
|
|
|
def iterate_imported_modules(globs): |
|
363
|
|
|
checked_modules = set(MODULE_BLACKLIST) |
|
364
|
|
|
for glob in globs.values(): |
|
365
|
|
|
if isinstance(glob, module): |
|
366
|
|
|
mod_path = glob.__name__ |
|
367
|
|
|
elif hasattr(glob, '__module__'): |
|
368
|
|
|
mod_path = glob.__module__ |
|
369
|
|
|
else: |
|
370
|
|
|
continue # pragma: no cover |
|
371
|
|
|
|
|
372
|
|
|
if not mod_path: |
|
373
|
|
|
continue |
|
374
|
|
|
|
|
375
|
|
|
for modname in iter_prefixes(mod_path): |
|
376
|
|
|
if modname in checked_modules: |
|
377
|
|
|
continue |
|
378
|
|
|
checked_modules.add(modname) |
|
379
|
|
|
mod = sys.modules.get(modname) |
|
380
|
|
|
if mod is not None: |
|
381
|
|
|
yield modname, mod |
|
382
|
|
|
|
|
383
|
|
|
|
|
384
|
|
|
def iterate_all_python_files(base_path): |
|
385
|
|
|
# TODO support ignored directories/files |
|
386
|
|
|
for dirname, subdirlist, filelist in os.walk(base_path): |
|
387
|
|
|
if '__pycache__' in dirname: |
|
388
|
|
|
continue |
|
389
|
|
|
for filename in filelist: |
|
390
|
|
|
if filename.endswith('.py'): |
|
391
|
|
|
yield os.path.join(base_path, dirname, filename) |
|
392
|
|
|
|
|
393
|
|
|
|
|
394
|
|
|
def iterate_sys_modules(): |
|
395
|
|
|
items = list(sys.modules.items()) |
|
396
|
|
|
for modname, mod in items: |
|
397
|
|
|
if modname not in MODULE_BLACKLIST and mod is not None: |
|
398
|
|
|
yield modname, mod |
|
399
|
|
|
|
|
400
|
|
|
|
|
401
|
|
|
def get_sources_from_modules(module_iterator, base_path): |
|
402
|
|
|
sources = set() |
|
403
|
|
|
for modname, mod in module_iterator: |
|
404
|
|
|
if not hasattr(mod, '__file__'): |
|
405
|
|
|
continue |
|
406
|
|
|
|
|
407
|
|
|
filename = os.path.abspath(mod.__file__) |
|
408
|
|
|
if filename not in sources and \ |
|
409
|
|
|
is_local_source(filename, modname, base_path): |
|
410
|
|
|
s = Source.create(filename) |
|
411
|
|
|
sources.add(s) |
|
412
|
|
|
return sources |
|
413
|
|
|
|
|
414
|
|
|
|
|
415
|
|
|
def get_dependencies_from_modules(module_iterator, base_path): |
|
416
|
|
|
dependencies = set() |
|
417
|
|
|
for modname, mod in module_iterator: |
|
418
|
|
|
if hasattr(mod, '__file__') and is_local_source( |
|
419
|
|
|
os.path.abspath(mod.__file__), modname, base_path): |
|
420
|
|
|
continue |
|
421
|
|
|
if modname.startswith('_') or '.' in modname: |
|
422
|
|
|
continue |
|
423
|
|
|
|
|
424
|
|
|
try: |
|
425
|
|
|
pdep = PackageDependency.create(mod) |
|
426
|
|
|
if pdep.version is not None: |
|
427
|
|
|
dependencies.add(pdep) |
|
428
|
|
|
except AttributeError: |
|
429
|
|
|
pass |
|
430
|
|
|
return dependencies |
|
431
|
|
|
|
|
432
|
|
|
|
|
433
|
|
|
def get_sources_from_sys_modules(globs, base_path): |
|
434
|
|
|
return get_sources_from_modules(iterate_sys_modules(), base_path) |
|
435
|
|
|
|
|
436
|
|
|
|
|
437
|
|
|
def get_sources_from_imported_modules(globs, base_path): |
|
438
|
|
|
return get_sources_from_modules(iterate_imported_modules(globs), base_path) |
|
439
|
|
|
|
|
440
|
|
|
|
|
441
|
|
|
def get_sources_from_local_dir(globs, base_path): |
|
442
|
|
|
return {Source.create(filename) |
|
443
|
|
|
for filename in iterate_all_python_files(base_path)} |
|
444
|
|
|
|
|
445
|
|
|
|
|
446
|
|
|
def get_dependencies_from_sys_modules(globs, base_path): |
|
447
|
|
|
return get_dependencies_from_modules(iterate_sys_modules(), base_path) |
|
448
|
|
|
|
|
449
|
|
|
|
|
450
|
|
|
def get_dependencies_from_imported_modules(globs, base_path): |
|
451
|
|
|
return get_dependencies_from_modules(iterate_imported_modules(globs), |
|
452
|
|
|
base_path) |
|
453
|
|
|
|
|
454
|
|
|
|
|
455
|
|
|
def get_dependencies_from_pkg(globs, base_path): |
|
456
|
|
|
dependencies = set() |
|
457
|
|
|
for dist in pkg_resources.working_set: |
|
458
|
|
|
if dist.version == '0.0.0': |
|
459
|
|
|
continue # ugly hack to deal with pkg-resource version bug |
|
460
|
|
|
dependencies.add(PackageDependency(dist.project_name, dist.version)) |
|
461
|
|
|
return dependencies |
|
462
|
|
|
|
|
463
|
|
|
|
|
464
|
|
|
source_discovery_strategies = { |
|
465
|
|
|
'none': lambda globs, path: set(), |
|
466
|
|
|
'imported': get_sources_from_imported_modules, |
|
467
|
|
|
'sys': get_sources_from_sys_modules, |
|
468
|
|
|
'dir': get_sources_from_local_dir |
|
469
|
|
|
} |
|
470
|
|
|
|
|
471
|
|
|
dependency_discovery_strategies = { |
|
472
|
|
|
'none': lambda globs, path: set(), |
|
473
|
|
|
'imported': get_dependencies_from_imported_modules, |
|
474
|
|
|
'sys': get_dependencies_from_sys_modules, |
|
475
|
|
|
'pkg': get_dependencies_from_pkg |
|
476
|
|
|
} |
|
477
|
|
|
|
|
478
|
|
|
|
|
479
|
|
|
def gather_sources_and_dependencies(globs, base_dir=None): |
|
480
|
|
|
"""Scan the given globals for modules and return them as dependencies.""" |
|
481
|
|
|
|
|
482
|
|
|
experiment_path, main = get_main_file(globs) |
|
483
|
|
|
|
|
484
|
|
|
base_dir = base_dir or experiment_path |
|
485
|
|
|
|
|
486
|
|
|
gather_sources = source_discovery_strategies[SETTINGS['DISCOVER_SOURCES']] |
|
487
|
|
|
sources = gather_sources(globs, base_dir) |
|
488
|
|
|
if main is not None: |
|
489
|
|
|
sources.add(main) |
|
490
|
|
|
|
|
491
|
|
|
gather_dependencies = dependency_discovery_strategies[ |
|
492
|
|
|
SETTINGS['DISCOVER_DEPENDENCIES']] |
|
493
|
|
|
dependencies = gather_dependencies(globs, base_dir) |
|
494
|
|
|
|
|
495
|
|
|
if opt.has_numpy: |
|
496
|
|
|
# Add numpy as a dependency because it might be used for randomness |
|
497
|
|
|
dependencies.add(PackageDependency.create(opt.np)) |
|
498
|
|
|
|
|
499
|
|
|
return main, sources, dependencies |
|
500
|
|
|
|