1
|
|
|
# -*- coding: utf-8 -*- |
2
|
14 |
|
# Copyright (C) 2012 Anaconda, Inc |
3
|
|
|
# SPDX-License-Identifier: BSD-3-Clause |
4
|
14 |
|
from __future__ import absolute_import, division, print_function, unicode_literals |
5
|
14 |
|
|
6
|
14 |
|
from datetime import timedelta |
7
|
|
|
from errno import ENOSPC |
8
|
14 |
|
import json |
9
|
14 |
|
from logging import getLogger |
10
|
|
|
import os |
11
|
|
|
import sys |
12
|
14 |
|
from traceback import format_exception, format_exception_only |
13
|
14 |
|
|
14
|
7 |
|
from . import CondaError, CondaExitZero, CondaMultiError, text_type |
15
|
7 |
|
from ._vendor.auxlib.entity import EntityEncoder |
16
|
|
|
from ._vendor.auxlib.ish import dals |
17
|
|
|
from ._vendor.auxlib.type_coercion import boolify |
18
|
14 |
|
from .base.constants import COMPATIBLE_SHELLS, PathConflict, SafetyChecks |
19
|
14 |
|
from .common.compat import PY2, ensure_text_type, input, iteritems, iterkeys, on_win, string_types |
20
|
7 |
|
from .common.io import dashlist, timeout |
21
|
|
|
from .common.signals import get_signal_name |
22
|
|
|
from .common.url import maybe_unquote |
23
|
14 |
|
|
24
|
14 |
|
try: |
25
|
7 |
|
from cytoolz.itertoolz import groupby |
26
|
7 |
|
except ImportError: # pragma: no cover |
27
|
|
|
from ._vendor.toolz.itertoolz import groupby # NOQA |
28
|
|
|
|
29
|
14 |
|
log = getLogger(__name__) |
30
|
14 |
|
|
31
|
|
|
|
32
|
|
|
# TODO: for conda-build compatibility only |
33
|
|
|
# remove in conda 4.4 |
34
|
|
|
class ResolvePackageNotFound(CondaError): |
35
|
|
|
def __init__(self, bad_deps): |
36
|
|
|
# bad_deps is a list of lists |
37
|
14 |
|
self.bad_deps = tuple(dep for deps in bad_deps for dep in deps if dep) |
38
|
14 |
|
message = '\n' + '\n'.join((' - %s' % dep) for dep in self.bad_deps) |
39
|
|
|
super(ResolvePackageNotFound, self).__init__(message) |
40
|
|
|
NoPackagesFound = NoPackagesFoundError = ResolvePackageNotFound # NOQA |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
class LockError(CondaError): |
44
|
|
|
def __init__(self, message): |
45
|
|
|
msg = "%s" % message |
46
|
|
|
super(LockError, self).__init__(msg) |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
class ArgumentError(CondaError): |
50
|
|
|
def __init__(self, message, **kwargs): |
51
|
14 |
|
super(ArgumentError, self).__init__(message, **kwargs) |
52
|
14 |
|
|
53
|
|
|
|
54
|
|
|
class CommandArgumentError(ArgumentError): |
55
|
|
|
def __init__(self, message, **kwargs): |
56
|
|
|
command = ' '.join(ensure_text_type(s) for s in sys.argv) |
57
|
|
|
super(CommandArgumentError, self).__init__(message, command=command, **kwargs) |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
class Help(CondaError): |
61
|
|
|
pass |
62
|
14 |
|
|
63
|
14 |
|
|
64
|
|
|
class ActivateHelp(Help): |
65
|
|
|
|
66
|
|
|
def __init__(self): |
67
|
|
|
message = dals(""" |
68
|
|
|
usage: conda activate [-h] [--stack] [env_name_or_prefix] |
69
|
|
|
|
70
|
14 |
|
Activate a conda environment. |
71
|
14 |
|
|
72
|
|
|
Options: |
73
|
|
|
|
74
|
|
|
positional arguments: |
75
|
|
|
env_name_or_prefix The environment name or prefix to activate. If the |
76
|
|
|
prefix is a relative path, it must start with './' |
77
|
14 |
|
(or '.\\' on Windows). |
78
|
14 |
|
|
79
|
7 |
|
optional arguments: |
80
|
7 |
|
-h, --help Show this help message and exit. |
81
|
7 |
|
--stack Stack the environment being activated on top of the |
82
|
|
|
previous active environment, rather replacing the |
83
|
|
|
current active environment with a new one. Currently, |
84
|
14 |
|
only the PATH environment variable is stacked. |
85
|
14 |
|
""") |
86
|
|
|
super(ActivateHelp, self).__init__(message) |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
class DeactivateHelp(Help): |
90
|
|
|
|
91
|
14 |
|
def __init__(self): |
92
|
|
|
message = dals(""" |
93
|
|
|
usage: conda deactivate [-h] |
94
|
|
|
|
95
|
|
|
Deactivate the current active conda environment. |
96
|
|
|
|
97
|
14 |
|
Options: |
98
|
7 |
|
|
99
|
|
|
optional arguments: |
100
|
|
|
-h, --help Show this help message and exit. |
101
|
7 |
|
""") |
102
|
7 |
|
super(DeactivateHelp, self).__init__(message) |
103
|
|
|
|
104
|
|
|
|
105
|
14 |
|
class GenericHelp(Help): |
106
|
14 |
|
|
107
|
|
|
def __init__(self, command): |
108
|
|
|
message = "help requested for %s" % command |
109
|
|
|
super(GenericHelp, self).__init__(message) |
110
|
|
|
|
111
|
14 |
|
|
112
|
14 |
|
class CondaSignalInterrupt(CondaError): |
113
|
|
|
def __init__(self, signum): |
114
|
|
|
signal_name = get_signal_name(signum) |
115
|
|
|
super(CondaSignalInterrupt, self).__init__("Signal interrupt %(signal_name)s", |
116
|
|
|
signal_name=signal_name, |
117
|
14 |
|
signum=signum) |
118
|
14 |
|
|
119
|
|
|
|
120
|
|
|
class TooManyArgumentsError(ArgumentError): |
121
|
|
|
def __init__(self, expected, received, offending_arguments, optional_message='', |
122
|
|
|
*args): |
123
|
14 |
|
self.expected = expected |
124
|
14 |
|
self.received = received |
125
|
|
|
self.offending_arguments = offending_arguments |
126
|
|
|
self.optional_message = optional_message |
127
|
|
|
|
128
|
14 |
|
suffix = 's' if received - expected > 1 else '' |
129
|
14 |
|
msg = ('%s Got %s argument%s (%s) but expected %s.' % |
130
|
7 |
|
(optional_message, received, suffix, ', '.join(offending_arguments), expected)) |
131
|
7 |
|
super(TooManyArgumentsError, self).__init__(msg, *args) |
132
|
|
|
|
133
|
|
|
|
134
|
14 |
|
class TooFewArgumentsError(ArgumentError): |
135
|
14 |
|
def __init__(self, expected, received, optional_message='', *args): |
136
|
|
|
self.expected = expected |
137
|
|
|
self.received = received |
138
|
|
|
self.optional_message = optional_message |
139
|
|
|
|
140
|
14 |
|
msg = ('%s Got %s arguments but expected %s.' % |
141
|
14 |
|
(optional_message, received, expected)) |
142
|
|
|
super(TooFewArgumentsError, self).__init__(msg, *args) |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
class ClobberError(CondaError): |
146
|
14 |
|
def __init__(self, message, path_conflict, **kwargs): |
147
|
14 |
|
self.path_conflict = path_conflict |
148
|
|
|
super(ClobberError, self).__init__(message, **kwargs) |
149
|
|
|
|
150
|
|
|
def __repr__(self): |
151
|
|
|
clz_name = "ClobberWarning" if self.path_conflict == PathConflict.warn else "ClobberError" |
152
|
14 |
|
return '%s: %s\n' % (clz_name, text_type(self)) |
153
|
14 |
|
|
154
|
|
|
|
155
|
|
|
class BasicClobberError(ClobberError): |
156
|
|
|
def __init__(self, source_path, target_path, context): |
157
|
|
|
message = dals(""" |
158
|
14 |
|
Conda was asked to clobber an existing path. |
159
|
14 |
|
source path: %(source_path)s |
160
|
|
|
target path: %(target_path)s |
161
|
|
|
""") |
162
|
|
|
if context.path_conflict == PathConflict.prevent: |
163
|
|
|
message += ("Conda no longer clobbers existing paths without the use of the " |
164
|
|
|
"--clobber option\n.") |
165
|
|
|
super(BasicClobberError, self).__init__(message, context.path_conflict, |
166
|
14 |
|
target_path=target_path, |
167
|
14 |
|
source_path=source_path) |
168
|
7 |
|
|
169
|
|
|
|
170
|
7 |
|
class KnownPackageClobberError(ClobberError): |
171
|
7 |
|
def __init__(self, target_path, colliding_dist_being_linked, colliding_linked_dist, context): |
172
|
|
|
message = dals(""" |
173
|
|
|
The package '%(colliding_dist_being_linked)s' cannot be installed due to a |
174
|
14 |
|
path collision for '%(target_path)s'. |
175
|
14 |
|
This path already exists in the target prefix, and it won't be removed by |
176
|
|
|
an uninstall action in this transaction. The path appears to be coming from |
177
|
|
|
the package '%(colliding_linked_dist)s', which is already installed in the prefix. |
178
|
|
|
""") |
179
|
|
|
if context.path_conflict == PathConflict.prevent: |
180
|
14 |
|
message += ("If you'd like to proceed anyway, re-run the command with " |
181
|
14 |
|
"the `--clobber` flag.\n.") |
182
|
|
|
super(KnownPackageClobberError, self).__init__( |
183
|
|
|
message, context.path_conflict, |
184
|
|
|
target_path=target_path, |
185
|
|
|
colliding_dist_being_linked=colliding_dist_being_linked, |
186
|
14 |
|
colliding_linked_dist=colliding_linked_dist, |
187
|
14 |
|
) |
188
|
|
|
|
189
|
|
|
|
190
|
|
|
class UnknownPackageClobberError(ClobberError): |
191
|
|
|
def __init__(self, target_path, colliding_dist_being_linked, context): |
192
|
14 |
|
message = dals(""" |
193
|
14 |
|
The package '%(colliding_dist_being_linked)s' cannot be installed due to a |
194
|
|
|
path collision for '%(target_path)s'. |
195
|
|
|
This path already exists in the target prefix, and it won't be removed |
196
|
|
|
by an uninstall action in this transaction. The path is one that conda |
197
|
|
|
doesn't recognize. It may have been created by another package manager. |
198
|
14 |
|
""") |
199
|
14 |
|
if context.path_conflict == PathConflict.prevent: |
200
|
|
|
message += ("If you'd like to proceed anyway, re-run the command with " |
201
|
|
|
"the `--clobber` flag.\n.") |
202
|
|
|
super(UnknownPackageClobberError, self).__init__( |
203
|
|
|
message, context.path_conflict, |
204
|
14 |
|
target_path=target_path, |
205
|
14 |
|
colliding_dist_being_linked=colliding_dist_being_linked, |
206
|
|
|
) |
207
|
|
|
|
208
|
|
|
|
209
|
|
|
class SharedLinkPathClobberError(ClobberError): |
210
|
14 |
|
def __init__(self, target_path, incompatible_package_dists, context): |
211
|
14 |
|
message = dals(""" |
212
|
|
|
This transaction has incompatible packages due to a shared path. |
213
|
|
|
packages: %(incompatible_packages)s |
214
|
|
|
path: '%(target_path)s' |
215
|
|
|
""") |
216
|
|
|
if context.path_conflict == PathConflict.prevent: |
217
|
14 |
|
message += ("If you'd like to proceed anyway, re-run the command with " |
218
|
14 |
|
"the `--clobber` flag.\n.") |
219
|
|
|
super(SharedLinkPathClobberError, self).__init__( |
220
|
|
|
message, context.path_conflict, |
221
|
|
|
target_path=target_path, |
222
|
|
|
incompatible_packages=', '.join(text_type(d) for d in incompatible_package_dists), |
223
|
14 |
|
) |
224
|
14 |
|
|
225
|
|
|
|
226
|
|
|
class CommandNotFoundError(CondaError): |
227
|
|
|
def __init__(self, command): |
228
|
|
|
activate_commands = { |
229
|
14 |
|
'activate', |
230
|
14 |
|
'deactivate', |
231
|
|
|
} |
232
|
|
|
conda_commands = { |
233
|
14 |
|
'clean', |
234
|
|
|
'config', |
235
|
|
|
'create', |
236
|
|
|
'help', |
237
|
|
|
'info', |
238
|
|
|
'install', |
239
|
|
|
'list', |
240
|
|
|
'package', |
241
|
|
|
'remove', |
242
|
|
|
'search', |
243
|
|
|
'uninstall', |
244
|
14 |
|
'update', |
245
|
7 |
|
'upgrade', |
246
|
7 |
|
} |
247
|
|
|
build_commands = { |
248
|
7 |
|
'build', |
249
|
7 |
|
'convert', |
250
|
7 |
|
'develop', |
251
|
7 |
|
'index', |
252
|
7 |
|
'inspect', |
253
|
|
|
'metapackage', |
254
|
7 |
|
'render', |
255
|
7 |
|
'skeleton', |
256
|
7 |
|
} |
257
|
7 |
|
from .base.context import context |
258
|
7 |
|
from .cli.main import init_loggers |
259
|
|
|
init_loggers(context) |
260
|
|
|
if command in activate_commands: |
261
|
14 |
|
# TODO: Point users to a page at conda-docs, which explains this context in more detail |
262
|
|
|
builder = ["Your shell has not been properly configured to use 'conda %(command)s'."] |
263
|
|
|
if on_win: |
264
|
|
|
builder.append(dals(""" |
265
|
|
|
If using 'conda %(command)s' from a batch script, change your |
266
|
|
|
invocation to 'CALL conda.bat %(command)s'. |
267
|
|
|
""")) |
268
|
|
|
builder.append(dals(""" |
269
|
|
|
To initialize your shell, run |
270
|
|
|
|
271
|
|
|
$ conda init <SHELL_NAME> |
272
|
|
|
|
273
|
|
|
Currently supported shells are:%(supported_shells)s |
274
|
14 |
|
|
275
|
7 |
|
See 'conda init --help' for more information and options. |
276
|
|
|
|
277
|
7 |
|
IMPORTANT: You may need to close and restart your shell after running 'conda init'. |
278
|
7 |
|
""") % { |
279
|
7 |
|
'supported_shells': dashlist(COMPATIBLE_SHELLS), |
280
|
7 |
|
}) |
281
|
7 |
|
message = '\n'.join(builder) |
282
|
7 |
|
elif command in build_commands: |
283
|
7 |
|
message = "To use 'conda %(command)s', install conda-build." |
284
|
7 |
|
else: |
285
|
7 |
|
from difflib import get_close_matches |
286
|
7 |
|
from .cli.find_commands import find_commands |
287
|
7 |
|
message = "No command 'conda %(command)s'." |
288
|
7 |
|
choices = activate_commands | conda_commands | build_commands | set(find_commands()) |
289
|
7 |
|
close = get_close_matches(command, choices) |
290
|
7 |
|
if close: |
291
|
7 |
|
message += "\nDid you mean 'conda %s'?" % close[0] |
292
|
7 |
|
super(CommandNotFoundError, self).__init__(message, command=command) |
293
|
7 |
|
|
294
|
7 |
|
|
295
|
7 |
|
class PathNotFoundError(CondaError, OSError): |
296
|
7 |
|
def __init__(self, path): |
297
|
7 |
|
message = "%(path)s" |
298
|
7 |
|
super(PathNotFoundError, self).__init__(message, path=path) |
299
|
7 |
|
|
300
|
|
|
|
301
|
|
|
class DirectoryNotFoundError(CondaError): |
302
|
|
|
def __init__(self, path): |
303
|
7 |
|
message = "%(path)s" |
304
|
|
|
super(DirectoryNotFoundError, self).__init__(message, path=path) |
305
|
7 |
|
|
306
|
7 |
|
|
307
|
7 |
|
class EnvironmentLocationNotFound(CondaError): |
308
|
7 |
|
def __init__(self, location): |
309
|
|
|
message = "Not a conda environment: %(location)s" |
310
|
|
|
super(EnvironmentLocationNotFound, self).__init__(message, location=location) |
311
|
|
|
|
312
|
|
|
|
313
|
|
|
class EnvironmentNameNotFound(CondaError): |
314
|
|
|
def __init__(self, environment_name): |
315
|
|
|
message = dals(""" |
316
|
7 |
|
Could not find conda environment: %(environment_name)s |
317
|
7 |
|
You can list all discoverable environments with `conda info --envs`. |
318
|
|
|
""") |
319
|
|
|
super(EnvironmentNameNotFound, self).__init__(message, environment_name=environment_name) |
320
|
14 |
|
|
321
|
14 |
|
|
322
|
7 |
|
class NoBaseEnvironmentError(CondaError): |
323
|
7 |
|
|
324
|
|
|
def __init__(self): |
325
|
|
|
message = dals(""" |
326
|
14 |
|
This conda installation has no default base environment. Use |
327
|
14 |
|
'conda create' to create new environments and 'conda activate' to |
328
|
|
|
activate environments. |
329
|
|
|
""") |
330
|
|
|
super(NoBaseEnvironmentError, self).__init__(message) |
331
|
|
|
|
332
|
14 |
|
|
333
|
14 |
|
class DirectoryNotACondaEnvironmentError(CondaError): |
334
|
|
|
|
335
|
|
|
def __init__(self, target_directory): |
336
|
|
|
message = dals(""" |
337
|
|
|
The target directory exists, but it is not a conda environment. |
338
|
14 |
|
Use 'conda create' to convert the directory to a conda environment. |
339
|
14 |
|
target directory: %(target_directory)s |
340
|
7 |
|
""") |
341
|
7 |
|
super(DirectoryNotACondaEnvironmentError, self).__init__(message, |
342
|
|
|
target_directory=target_directory) |
343
|
|
|
|
344
|
14 |
|
|
345
|
14 |
|
class CondaEnvironmentError(CondaError, EnvironmentError): |
346
|
7 |
|
def __init__(self, message, *args): |
347
|
7 |
|
msg = '%s' % message |
348
|
|
|
super(CondaEnvironmentError, self).__init__(msg, *args) |
349
|
|
|
|
350
|
14 |
|
|
351
|
14 |
|
class DryRunExit(CondaExitZero): |
352
|
|
|
def __init__(self): |
353
|
|
|
msg = 'Dry run. Exiting.' |
354
|
|
|
super(DryRunExit, self).__init__(msg) |
355
|
|
|
|
356
|
14 |
|
|
357
|
14 |
|
class CondaSystemExit(CondaExitZero, SystemExit): |
358
|
|
|
def __init__(self, *args): |
359
|
|
|
msg = ' '.join(text_type(arg) for arg in self.args) |
360
|
|
|
super(CondaSystemExit, self).__init__(msg) |
361
|
|
|
|
362
|
14 |
|
|
363
|
14 |
|
class PaddingError(CondaError): |
364
|
|
|
def __init__(self, dist, placeholder, placeholder_length): |
365
|
|
|
msg = ("Placeholder of length '%d' too short in package %s.\n" |
366
|
|
|
"The package must be rebuilt with conda-build > 2.0." % (placeholder_length, dist)) |
367
|
|
|
super(PaddingError, self).__init__(msg) |
368
|
14 |
|
|
369
|
14 |
|
|
370
|
|
|
class LinkError(CondaError): |
371
|
|
|
def __init__(self, message): |
372
|
|
|
super(LinkError, self).__init__(message) |
373
|
|
|
|
374
|
14 |
|
|
375
|
7 |
|
class CondaOSError(CondaError, OSError): |
376
|
|
|
def __init__(self, message, **kwargs): |
377
|
7 |
|
msg = '%s' % message |
378
|
7 |
|
super(CondaOSError, self).__init__(msg, **kwargs) |
379
|
|
|
|
380
|
7 |
|
|
381
|
7 |
|
class ProxyError(CondaError): |
382
|
|
|
def __init__(self, message): |
383
|
|
|
msg = '%s' % message |
384
|
|
|
super(ProxyError, self).__init__(msg) |
385
|
|
|
|
386
|
|
|
|
387
|
7 |
|
class CondaIOError(CondaError, IOError): |
388
|
|
|
def __init__(self, message, *args): |
389
|
7 |
|
msg = '%s' % message |
390
|
|
|
super(CondaIOError, self).__init__(msg) |
391
|
14 |
|
|
392
|
|
|
|
393
|
|
|
class CondaFileIOError(CondaIOError): |
394
|
|
|
def __init__(self, filepath, message, *args): |
395
|
|
|
self.filepath = filepath |
396
|
|
|
|
397
|
|
|
msg = "'%s'. %s" % (filepath, message) |
398
|
|
|
super(CondaFileIOError, self).__init__(msg, *args) |
399
|
|
|
|
400
|
|
|
|
401
|
|
|
class CondaKeyError(CondaError, KeyError): |
402
|
|
|
def __init__(self, key, message, *args): |
403
|
|
|
self.key = key |
404
|
|
|
self.msg = "'%s': %s" % (key, message) |
405
|
|
|
super(CondaKeyError, self).__init__(self.msg, *args) |
406
|
|
|
|
407
|
14 |
|
|
408
|
|
|
class ChannelError(CondaError): |
409
|
|
|
def __init__(self, message, *args): |
410
|
|
|
msg = '%s' % message |
411
|
|
|
super(ChannelError, self).__init__(msg) |
412
|
7 |
|
|
413
|
|
|
|
414
|
7 |
|
class ChannelNotAllowed(ChannelError): |
415
|
|
|
def __init__(self, message, *args): |
416
|
7 |
|
msg = '%s' % message |
417
|
7 |
|
super(ChannelNotAllowed, self).__init__(msg, *args) |
418
|
7 |
|
|
419
|
7 |
|
|
420
|
|
|
class OperationNotAllowed(CondaError): |
421
|
|
|
|
422
|
|
|
def __init__(self, message): |
423
|
|
|
super(OperationNotAllowed, self).__init__(message) |
424
|
|
|
|
425
|
|
|
|
426
|
|
|
class CondaImportError(CondaError, ImportError): |
427
|
|
|
def __init__(self, message): |
428
|
|
|
msg = '%s' % message |
429
|
|
|
super(CondaImportError, self).__init__(msg) |
430
|
|
|
|
431
|
|
|
|
432
|
|
|
class ParseError(CondaError): |
433
|
|
|
def __init__(self, message): |
434
|
|
|
msg = '%s' % message |
435
|
|
|
super(ParseError, self).__init__(msg) |
436
|
|
|
|
437
|
|
|
|
438
|
|
|
class CouldntParseError(ParseError): |
439
|
|
|
def __init__(self, reason): |
440
|
14 |
|
self.reason = reason |
441
|
14 |
|
super(CouldntParseError, self).__init__(self.args[0]) |
442
|
14 |
|
|
443
|
14 |
|
|
444
|
7 |
|
class MD5MismatchError(CondaError): |
445
|
14 |
|
def __init__(self, url, target_full_path, expected_md5sum, actual_md5sum): |
446
|
|
|
message = dals(""" |
447
|
|
|
Conda detected a mismatch between the expected content and downloaded content |
448
|
14 |
|
for url '%(url)s'. |
449
|
7 |
|
download saved to: %(target_full_path)s |
450
|
7 |
|
expected md5 sum: %(expected_md5sum)s |
451
|
|
|
actual md5 sum: %(actual_md5sum)s |
452
|
|
|
""") |
453
|
7 |
|
url = maybe_unquote(url) |
454
|
7 |
|
super(MD5MismatchError, self).__init__(message, url=url, target_full_path=target_full_path, |
455
|
14 |
|
expected_md5sum=expected_md5sum, |
456
|
7 |
|
actual_md5sum=actual_md5sum) |
457
|
7 |
|
|
458
|
|
|
|
459
|
|
|
class PackageNotInstalledError(CondaError): |
460
|
|
|
|
461
|
|
|
def __init__(self, prefix, package_name): |
462
|
|
|
message = dals(""" |
463
|
|
|
Package is not installed in prefix. |
464
|
|
|
prefix: %(prefix)s |
465
|
|
|
package name: %(package_name)s |
466
|
|
|
""") |
467
|
|
|
super(PackageNotInstalledError, self).__init__(message, prefix=prefix, |
468
|
|
|
package_name=package_name) |
469
|
|
|
|
470
|
|
|
|
471
|
|
|
class CondaHTTPError(CondaError): |
472
|
|
|
def __init__(self, message, url, status_code, reason, elapsed_time, response=None, |
473
|
|
|
caused_by=None): |
474
|
|
|
_message = dals(""" |
475
|
|
|
HTTP %(status_code)s %(reason)s for url <%(url)s> |
476
|
|
|
Elapsed: %(elapsed_time)s |
477
|
|
|
""") |
478
|
|
|
cf_ray = getattr(response, 'headers', {}).get('CF-RAY') |
479
|
|
|
_message += "CF-RAY: %s\n\n" % cf_ray if cf_ray else "\n" |
480
|
|
|
message = _message + message |
481
|
|
|
|
482
|
|
|
status_code = status_code or '000' |
483
|
|
|
reason = reason or 'CONNECTION FAILED' |
484
|
|
|
elapsed_time = elapsed_time or '-' |
485
|
|
|
|
486
|
|
|
from ._vendor.auxlib.logz import stringify |
487
|
|
|
response_details = (stringify(response, content_max_len=1024) or '') if response else '' |
488
|
|
|
|
489
|
|
|
url = maybe_unquote(url) |
490
|
|
|
if isinstance(elapsed_time, timedelta): |
491
|
|
|
elapsed_time = text_type(elapsed_time).split(':', 1)[-1] |
492
|
|
|
if isinstance(reason, string_types): |
493
|
|
|
reason = reason.upper() |
494
|
|
|
super(CondaHTTPError, self).__init__(message, url=url, status_code=status_code, |
495
|
|
|
reason=reason, elapsed_time=elapsed_time, |
496
|
|
|
response_details=response_details, |
497
|
|
|
caused_by=caused_by) |
498
|
|
|
|
499
|
|
|
|
500
|
|
|
class CondaRevisionError(CondaError): |
501
|
|
|
def __init__(self, message): |
502
|
|
|
msg = "%s." % message |
503
|
|
|
super(CondaRevisionError, self).__init__(msg) |
504
|
|
|
|
505
|
|
|
|
506
|
|
|
class AuthenticationError(CondaError): |
507
|
|
|
pass |
508
|
|
|
|
509
|
|
|
|
510
|
|
|
class PackagesNotFoundError(CondaError): |
511
|
|
|
|
512
|
|
|
def __init__(self, packages, channel_urls=()): |
513
|
|
|
|
514
|
|
|
format_list = lambda iterable: ' - ' + '\n - '.join(text_type(x) for x in iterable) |
515
|
|
|
|
516
|
|
|
if channel_urls: |
517
|
|
|
message = dals(""" |
518
|
|
|
The following packages are not available from current channels: |
519
|
|
|
|
520
|
|
|
%(packages_formatted)s |
521
|
|
|
|
522
|
|
|
Current channels: |
523
|
|
|
|
524
|
|
|
%(channels_formatted)s |
525
|
|
|
|
526
|
|
|
To search for alternate channels that may provide the conda package you're |
527
|
|
|
looking for, navigate to |
528
|
|
|
|
529
|
|
|
https://anaconda.org |
530
|
|
|
|
531
|
|
|
and use the search bar at the top of the page. |
532
|
|
|
""") |
533
|
|
|
packages_formatted = format_list(packages) |
534
|
|
|
channels_formatted = format_list(channel_urls) |
535
|
|
|
else: |
536
|
|
|
message = dals(""" |
537
|
|
|
The following packages are missing from the target environment: |
538
|
|
|
%(packages_formatted)s |
539
|
|
|
""") |
540
|
|
|
packages_formatted = format_list(packages) |
541
|
|
|
channels_formatted = () |
542
|
|
|
|
543
|
|
|
super(PackagesNotFoundError, self).__init__( |
544
|
|
|
message, packages=packages, packages_formatted=packages_formatted, |
545
|
|
|
channel_urls=channel_urls, channels_formatted=channels_formatted |
546
|
|
|
) |
547
|
|
|
|
548
|
|
|
|
549
|
|
|
class UnsatisfiableError(CondaError): |
550
|
|
|
"""An exception to report unsatisfiable dependencies. |
551
|
|
|
|
552
|
|
|
Args: |
553
|
|
|
bad_deps: a list of tuples of objects (likely MatchSpecs). |
554
|
|
|
chains: (optional) if True, the tuples are interpreted as chains |
555
|
|
|
of dependencies, from top level to bottom. If False, the tuples |
556
|
|
|
are interpreted as simple lists of conflicting specs. |
557
|
|
|
|
558
|
|
|
Returns: |
559
|
|
|
Raises an exception with a formatted message detailing the |
560
|
|
|
unsatisfiable specifications. |
561
|
|
|
""" |
562
|
|
|
|
563
|
|
|
def __init__(self, bad_deps, chains=True): |
564
|
|
|
from .models.match_spec import MatchSpec |
565
|
|
|
from .resolve import dashlist |
566
|
|
|
|
567
|
|
|
# Remove any target values from the MatchSpecs, convert to strings |
568
|
|
|
bad_deps = [list(map(lambda x: str(MatchSpec(x, target=None)), dep)) for dep in bad_deps] |
569
|
|
|
if chains: |
570
|
|
|
chains = {} |
571
|
|
|
for dep in sorted(bad_deps, key=len, reverse=True): |
572
|
|
|
dep1 = [s.partition(' ') for s in dep[1:]] |
573
|
|
|
key = (dep[0],) + tuple(v[0] for v in dep1) |
574
|
|
|
vals = ('',) + tuple(v[2] for v in dep1) |
575
|
|
|
found = False |
576
|
|
|
for key2, csets in iteritems(chains): |
577
|
|
|
if key2[:len(key)] == key: |
578
|
|
|
for cset, val in zip(csets, vals): |
579
|
|
|
cset.add(val) |
580
|
|
|
found = True |
581
|
|
|
if not found: |
582
|
|
|
chains[key] = [{val} for val in vals] |
583
|
|
|
for key, csets in iteritems(chains): |
584
|
|
|
deps = [] |
585
|
|
|
for name, cset in zip(key, csets): |
586
|
|
|
if '' not in cset: |
587
|
|
|
pass |
588
|
|
|
elif len(cset) == 1: |
589
|
|
|
cset.clear() |
590
|
|
|
else: |
591
|
|
|
cset.remove('') |
592
|
|
|
cset.add('*') |
593
|
|
|
if name[0] == '@': |
594
|
|
|
name = 'feature:' + name[1:] |
595
|
|
|
deps.append('%s %s' % (name, '|'.join(sorted(cset))) if cset else name) |
596
|
|
|
chains[key] = ' -> '.join(deps) |
597
|
|
|
bad_deps = [chains[key] for key in sorted(iterkeys(chains))] |
598
|
|
|
msg = '''The following specifications were found to be in conflict:%s |
599
|
|
|
Use "conda search <package> --info" to see the dependencies for each package.''' |
600
|
|
|
else: |
601
|
|
|
bad_deps = [sorted(dep) for dep in bad_deps] |
602
|
|
|
bad_deps = [', '.join(dep) for dep in sorted(bad_deps)] |
603
|
|
|
msg = '''The following specifications were found to be incompatible with the |
604
|
|
|
others, or with the existing package set:%s |
605
|
|
|
Use "conda search <package> --info" to see the dependencies for each package.''' |
606
|
|
|
msg = msg % dashlist(bad_deps) |
607
|
|
|
super(UnsatisfiableError, self).__init__(msg) |
608
|
|
|
|
609
|
|
|
|
610
|
|
|
class InstallError(CondaError): |
611
|
|
|
def __init__(self, message): |
612
|
|
|
msg = '%s' % message |
613
|
|
|
super(InstallError, self).__init__(msg) |
614
|
|
|
|
615
|
|
|
|
616
|
|
|
class RemoveError(CondaError): |
617
|
|
|
def __init__(self, message): |
618
|
|
|
msg = '%s' % message |
619
|
|
|
super(RemoveError, self).__init__(msg) |
620
|
|
|
|
621
|
|
|
|
622
|
|
|
class DisallowedPackageError(CondaError): |
623
|
|
|
def __init__(self, package_ref, **kwargs): |
624
|
|
|
from .models.records import PackageRef |
625
|
|
|
package_ref = PackageRef.from_objects(package_ref) |
626
|
|
|
message = ("The package '%(dist_str)s' is disallowed by configuration.\n" |
627
|
|
|
"See 'conda config --show disallowed_packages'.") |
628
|
|
|
super(DisallowedPackageError, self).__init__(message, package_ref=package_ref, |
629
|
|
|
dist_str=package_ref.dist_str(), **kwargs) |
630
|
|
|
|
631
|
|
|
|
632
|
|
|
class CondaIndexError(CondaError, IndexError): |
633
|
|
|
def __init__(self, message): |
634
|
|
|
msg = '%s' % message |
635
|
|
|
super(CondaIndexError, self).__init__(msg) |
636
|
|
|
|
637
|
|
|
|
638
|
|
|
class CondaValueError(CondaError, ValueError): |
639
|
|
|
def __init__(self, message, *args): |
640
|
|
|
msg = '%s' % message |
641
|
|
|
super(CondaValueError, self).__init__(msg) |
642
|
|
|
|
643
|
|
|
|
644
|
|
|
class CondaTypeError(CondaError, TypeError): |
645
|
|
|
def __init__(self, expected_type, received_type, optional_message): |
646
|
|
|
msg = "Expected type '%s' and got type '%s'. %s" |
647
|
|
|
super(CondaTypeError, self).__init__(msg) |
648
|
|
|
|
649
|
|
|
|
650
|
|
|
class CyclicalDependencyError(CondaError, ValueError): |
651
|
|
|
def __init__(self, packages_with_cycles, **kwargs): |
652
|
|
|
from .resolve import dashlist |
653
|
|
|
from .models.records import PackageRef |
654
|
|
|
packages_with_cycles = tuple(PackageRef.from_objects(p) for p in packages_with_cycles) |
655
|
|
|
message = "Cyclic dependencies exist among these items: %s" % dashlist( |
656
|
|
|
p.dist_str() for p in packages_with_cycles |
657
|
|
|
) |
658
|
|
|
super(CyclicalDependencyError, self).__init__( |
659
|
|
|
message, packages_with_cycles=packages_with_cycles, **kwargs |
660
|
|
|
) |
661
|
|
|
|
662
|
|
|
|
663
|
|
|
class CorruptedEnvironmentError(CondaError): |
664
|
|
|
def __init__(self, environment_location, corrupted_file, **kwargs): |
665
|
|
|
message = dals(""" |
666
|
|
|
The target environment has been corrupted. Corrupted environments most commonly |
667
|
|
|
occur when the conda process is force-terminated while in an unlink-link |
668
|
|
|
transaction. |
669
|
|
|
environment location: %(environment_location)s |
670
|
|
|
corrupted file: %(corrupted_file)s |
671
|
|
|
""") |
672
|
|
|
super(CorruptedEnvironmentError, self).__init__( |
673
|
|
|
message, |
674
|
|
|
environment_location=environment_location, |
675
|
|
|
corrupted_file=corrupted_file, |
676
|
|
|
**kwargs |
677
|
|
|
) |
678
|
|
|
|
679
|
|
|
|
680
|
|
|
class CondaHistoryError(CondaError): |
681
|
|
|
def __init__(self, message): |
682
|
|
|
msg = '%s' % message |
683
|
|
|
super(CondaHistoryError, self).__init__(msg) |
684
|
|
|
|
685
|
|
|
|
686
|
|
|
class CondaUpgradeError(CondaError): |
687
|
|
|
def __init__(self, message): |
688
|
|
|
msg = "%s" % message |
689
|
|
|
super(CondaUpgradeError, self).__init__(msg) |
690
|
|
|
|
691
|
|
|
|
692
|
|
|
class CaseInsensitiveFileSystemError(CondaError): |
693
|
|
|
def __init__(self, package_location, extract_location, **kwargs): |
694
|
|
|
message = dals(""" |
695
|
|
|
Cannot extract package to a case-insensitive file system. |
696
|
|
|
package location: %(package_location)s |
697
|
|
|
extract location: %(extract_location)s |
698
|
|
|
""") |
699
|
|
|
super(CaseInsensitiveFileSystemError, self).__init__( |
700
|
|
|
message, |
701
|
|
|
package_location=package_location, |
702
|
|
|
extract_location=extract_location, |
703
|
|
|
**kwargs |
704
|
|
|
) |
705
|
|
|
|
706
|
|
|
|
707
|
|
|
class CondaVerificationError(CondaError): |
708
|
|
|
def __init__(self, message): |
709
|
|
|
super(CondaVerificationError, self).__init__(message) |
710
|
|
|
|
711
|
|
|
|
712
|
|
|
class SafetyError(CondaError): |
713
|
|
|
def __init__(self, message): |
714
|
|
|
super(SafetyError, self).__init__(message) |
715
|
|
|
|
716
|
|
|
|
717
|
|
|
class CondaMemoryError(CondaError, MemoryError): |
718
|
|
|
def __init__(self, caused_by, **kwargs): |
719
|
|
|
message = "The conda process ran out of memory. Increase system memory and/or try again." |
720
|
|
|
super(CondaMemoryError, self).__init__(message, caused_by=caused_by, **kwargs) |
721
|
|
|
|
722
|
|
|
|
723
|
|
|
class NotWritableError(CondaError, OSError): |
724
|
|
|
|
725
|
|
|
def __init__(self, path, errno, **kwargs): |
726
|
|
|
kwargs.update({ |
727
|
|
|
'path': path, |
728
|
|
|
'errno': errno, |
729
|
|
|
}) |
730
|
|
|
if on_win: |
731
|
|
|
message = dals(""" |
732
|
|
|
The current user does not have write permissions to a required path. |
733
|
|
|
path: %(path)s |
734
|
|
|
""") |
735
|
|
|
else: |
736
|
|
|
message = dals(""" |
737
|
|
|
The current user does not have write permissions to a required path. |
738
|
|
|
path: %(path)s |
739
|
|
|
uid: %(uid)s |
740
|
|
|
gid: %(gid)s |
741
|
|
|
|
742
|
|
|
If you feel that permissions on this path are set incorrectly, you can manually |
743
|
|
|
change them by executing |
744
|
|
|
|
745
|
|
|
$ sudo chown %(uid)s:%(gid)s %(path)s |
746
|
|
|
|
747
|
|
|
In general, it's not advisable to use 'sudo conda'. |
748
|
|
|
""") |
749
|
|
|
import os |
750
|
|
|
kwargs.update({ |
751
|
|
|
'uid': os.geteuid(), |
752
|
|
|
'gid': os.getegid(), |
753
|
|
|
}) |
754
|
|
|
super(NotWritableError, self).__init__(message, **kwargs) |
755
|
|
|
|
756
|
|
|
|
757
|
|
|
class NoWritableEnvsDirError(CondaError): |
758
|
|
|
|
759
|
|
|
def __init__(self, envs_dirs, **kwargs): |
760
|
|
|
message = "No writeable envs directories configured.%s" % dashlist(envs_dirs) |
761
|
|
|
super(NoWritableEnvsDirError, self).__init__(message, envs_dirs=envs_dirs, **kwargs) |
762
|
|
|
|
763
|
|
|
|
764
|
|
|
class NoWritablePkgsDirError(CondaError): |
765
|
|
|
|
766
|
|
|
def __init__(self, pkgs_dirs, **kwargs): |
767
|
|
|
message = "No writeable pkgs directories configured.%s" % dashlist(pkgs_dirs) |
768
|
|
|
super(NoWritablePkgsDirError, self).__init__(message, pkgs_dirs=pkgs_dirs, **kwargs) |
769
|
|
|
|
770
|
|
|
|
771
|
|
|
class CondaDependencyError(CondaError): |
772
|
|
|
def __init__(self, message): |
773
|
|
|
super(CondaDependencyError, self).__init__(message) |
774
|
|
|
|
775
|
|
|
|
776
|
|
|
class BinaryPrefixReplacementError(CondaError): |
777
|
|
|
def __init__(self, path, placeholder, new_prefix, original_data_length, new_data_length): |
778
|
|
|
message = dals(""" |
779
|
|
|
Refusing to replace mismatched data length in binary file. |
780
|
|
|
path: %(path)s |
781
|
|
|
placeholder: %(placeholder)s |
782
|
|
|
new prefix: %(new_prefix)s |
783
|
|
|
original data Length: %(original_data_length)d |
784
|
|
|
new data length: %(new_data_length)d |
785
|
|
|
""") |
786
|
|
|
kwargs = { |
787
|
|
|
'path': path, |
788
|
|
|
'placeholder': placeholder, |
789
|
|
|
'new_prefix': new_prefix, |
790
|
|
|
'original_data_length': original_data_length, |
791
|
|
|
'new_data_length': new_data_length, |
792
|
|
|
} |
793
|
|
|
super(BinaryPrefixReplacementError, self).__init__(message, **kwargs) |
794
|
|
|
|
795
|
|
|
|
796
|
|
|
class InvalidVersionSpecError(CondaError): |
797
|
|
|
def __init__(self, invalid_spec): |
798
|
|
|
message = "Invalid version spec: %(invalid_spec)s" |
799
|
|
|
super(InvalidVersionSpecError, self).__init__(message, invalid_spec=invalid_spec) |
800
|
|
|
|
801
|
|
|
|
802
|
|
|
class EncodingError(CondaError): |
803
|
|
|
|
804
|
|
|
def __init__(self, caused_by, **kwargs): |
805
|
|
|
message = dals(""" |
806
|
|
|
A unicode encoding or decoding error has occurred. |
807
|
|
|
Python 2 is the interpreter under which conda is running in your base environment. |
808
|
|
|
Replacing your base environment with one having Python 3 may help resolve this issue. |
809
|
|
|
If you still have a need for Python 2 environments, consider using 'conda create' |
810
|
|
|
and 'conda activate'. For example: |
811
|
|
|
|
812
|
|
|
$ conda create -n py2 python=2 |
813
|
|
|
$ conda activate py2 |
814
|
|
|
|
815
|
|
|
Error details: %r |
816
|
|
|
|
817
|
|
|
""") % caused_by |
818
|
|
|
super(EncodingError, self).__init__(message, caused_by=caused_by, **kwargs) |
819
|
|
|
|
820
|
|
|
|
821
|
|
|
class NoSpaceLeftError(CondaError): |
822
|
|
|
|
823
|
|
|
def __init__(self, caused_by, **kwargs): |
824
|
|
|
message = "No space left on devices." |
825
|
|
|
super(NoSpaceLeftError, self).__init__(message, caused_by=caused_by, **kwargs) |
826
|
|
|
|
827
|
|
|
|
828
|
|
|
def maybe_raise(error, context): |
829
|
|
|
if isinstance(error, CondaMultiError): |
830
|
|
|
groups = groupby(lambda e: isinstance(e, ClobberError), error.errors) |
831
|
|
|
clobber_errors = groups.get(True, ()) |
832
|
|
|
groups = groupby(lambda e: isinstance(e, SafetyError), groups.get(False, ())) |
833
|
|
|
safety_errors = groups.get(True, ()) |
834
|
|
|
other_errors = groups.get(False, ()) |
835
|
|
|
|
836
|
|
|
if ((safety_errors and context.safety_checks == SafetyChecks.enabled) |
837
|
|
|
or (clobber_errors and context.path_conflict == PathConflict.prevent |
838
|
|
|
and not context.clobber) |
839
|
|
|
or other_errors): |
840
|
|
|
raise error |
841
|
|
|
elif ((safety_errors and context.safety_checks == SafetyChecks.warn) |
842
|
|
|
or (clobber_errors and context.path_conflict == PathConflict.warn |
843
|
|
|
and not context.clobber)): |
844
|
|
|
print_conda_exception(error) |
845
|
|
|
|
846
|
|
|
elif isinstance(error, ClobberError): |
847
|
|
|
if context.path_conflict == PathConflict.prevent and not context.clobber: |
848
|
|
|
raise error |
849
|
|
|
elif context.path_conflict == PathConflict.warn and not context.clobber: |
850
|
|
|
print_conda_exception(error) |
851
|
|
|
|
852
|
|
|
elif isinstance(error, SafetyError): |
853
|
|
|
if context.safety_checks == SafetyChecks.enabled: |
854
|
|
|
raise error |
855
|
|
|
elif context.safety_checks == SafetyChecks.warn: |
856
|
|
|
print_conda_exception(error) |
857
|
|
|
|
858
|
|
|
else: |
859
|
|
|
raise error |
860
|
|
|
|
861
|
|
|
|
862
|
|
|
def print_conda_exception(exc_val, exc_tb=None): |
863
|
|
|
from .base.context import context |
864
|
|
|
rc = getattr(exc_val, 'return_code', None) |
865
|
|
|
if context.debug or context.verbosity > 0: |
866
|
|
|
print(_format_exc(exc_val, exc_tb), file=sys.stderr) |
867
|
|
|
elif context.json: |
868
|
|
|
if rc == 0: |
869
|
|
|
# suppress DryRunExit and CondaSystemExit messages |
870
|
|
|
pass |
871
|
|
|
else: |
872
|
|
|
import json |
873
|
|
|
stdoutlog = getLogger('conda.stdout') |
874
|
|
|
exc_json = json.dumps(exc_val.dump_map(), indent=2, sort_keys=True, cls=EntityEncoder) |
875
|
|
|
stdoutlog.error("%s\n" % exc_json) |
876
|
|
|
else: |
877
|
|
|
stderrlog = getLogger('conda.stderr') |
878
|
|
|
if rc == 0: |
879
|
|
|
stderrlog.error("\n%s\n", exc_val) |
880
|
|
|
else: |
881
|
|
|
stderrlog.error("\n%r\n", exc_val) |
882
|
|
|
|
883
|
|
|
|
884
|
|
|
def _format_exc(exc_val=None, exc_tb=None): |
885
|
|
|
if exc_val is None: |
886
|
|
|
exc_type, exc_val, exc_tb = sys.exc_info() |
887
|
|
|
else: |
888
|
|
|
exc_type = type(exc_val) |
889
|
|
|
if exc_tb: |
890
|
|
|
formatted_exception = format_exception(exc_type, exc_val, exc_tb) |
891
|
|
|
else: |
892
|
|
|
formatted_exception = format_exception_only(exc_type, exc_val) |
893
|
|
|
return ''.join(formatted_exception) |
894
|
|
|
|
895
|
|
|
|
896
|
|
|
class ExceptionHandler(object): |
897
|
|
|
|
898
|
|
|
def __call__(self, func, *args, **kwargs): |
899
|
|
|
try: |
900
|
|
|
return func(*args, **kwargs) |
901
|
|
|
except: |
902
|
|
|
_, exc_val, exc_tb = sys.exc_info() |
903
|
|
|
return self.handle_exception(exc_val, exc_tb) |
904
|
|
|
|
905
|
|
|
@property |
906
|
|
|
def out_stream(self): |
907
|
|
|
from .base.context import context |
908
|
|
|
return sys.stdout if context.json else sys.stderr |
909
|
|
|
|
910
|
|
|
@property |
911
|
|
|
def http_timeout(self): |
912
|
|
|
from .base.context import context |
913
|
|
|
return context.remote_connect_timeout_secs, context.remote_read_timeout_secs |
914
|
|
|
|
915
|
|
|
@property |
916
|
|
|
def user_agent(self): |
917
|
|
|
from .base.context import context |
918
|
|
|
return context.user_agent |
919
|
|
|
|
920
|
|
|
@property |
921
|
|
|
def error_upload_url(self): |
922
|
|
|
from .base.context import context |
923
|
|
|
return context.error_upload_url |
924
|
|
|
|
925
|
|
|
def handle_exception(self, exc_val, exc_tb): |
926
|
|
|
if isinstance(exc_val, CondaHTTPError): |
927
|
|
|
return self.handle_reportable_application_exception(exc_val, exc_tb) |
928
|
|
|
if isinstance(exc_val, CondaError): |
929
|
|
|
return self.handle_application_exception(exc_val, exc_tb) |
930
|
|
|
if isinstance(exc_val, UnicodeError) and PY2: |
931
|
|
|
return self.handle_application_exception(EncodingError(exc_val), exc_tb) |
932
|
|
|
if isinstance(exc_val, EnvironmentError): |
933
|
|
|
if getattr(exc_val, 'errno', None) == ENOSPC: |
934
|
|
|
return self.handle_application_exception(NoSpaceLeftError(exc_val), exc_tb) |
935
|
|
|
if isinstance(exc_val, MemoryError): |
936
|
|
|
return self.handle_application_exception(CondaMemoryError(exc_val), exc_tb) |
937
|
|
|
if isinstance(exc_val, KeyboardInterrupt): |
938
|
|
|
self._print_conda_exception(CondaError("KeyboardInterrupt"), _format_exc()) |
939
|
|
|
return 1 |
940
|
|
|
if isinstance(exc_val, SystemExit): |
941
|
|
|
return exc_val.code |
942
|
|
|
return self.handle_unexpected_exception(exc_val, exc_tb) |
943
|
|
|
|
944
|
|
|
def handle_application_exception(self, exc_val, exc_tb): |
945
|
|
|
self._print_conda_exception(exc_val, exc_tb) |
946
|
|
|
rc = getattr(exc_val, 'return_code', None) |
947
|
|
|
return rc if rc is not None else 1 |
948
|
|
|
|
949
|
|
|
def _print_conda_exception(self, exc_val, exc_tb): |
950
|
|
|
print_conda_exception(exc_val, exc_tb) |
951
|
|
|
|
952
|
|
|
def handle_unexpected_exception(self, exc_val, exc_tb): |
953
|
|
|
error_report = self.get_error_report(exc_val, exc_tb) |
954
|
|
|
self.print_unexpected_error_report(error_report) |
955
|
|
|
ask_for_upload, do_upload = self._calculate_ask_do_upload() |
956
|
|
|
do_upload, ask_response = self.ask_for_upload() if ask_for_upload else (do_upload, None) |
957
|
|
|
if do_upload: |
958
|
|
|
self._execute_upload(error_report) |
959
|
|
|
self.print_upload_confirm(do_upload, ask_for_upload, ask_response) |
960
|
|
|
rc = getattr(exc_val, 'return_code', None) |
961
|
|
|
return rc if rc is not None else 1 |
962
|
|
|
|
963
|
|
|
def handle_reportable_application_exception(self, exc_val, exc_tb): |
964
|
|
|
error_report = self.get_error_report(exc_val, exc_tb) |
965
|
|
|
from .base.context import context |
966
|
|
|
if context.json: |
967
|
|
|
error_report.update(exc_val.dump_map()) |
968
|
|
|
self.print_expected_error_report(error_report) |
969
|
|
|
ask_for_upload, do_upload = self._calculate_ask_do_upload() |
970
|
|
|
do_upload, ask_response = self.ask_for_upload() if ask_for_upload else (do_upload, None) |
971
|
|
|
if do_upload: |
972
|
|
|
self._execute_upload(error_report) |
973
|
|
|
self.print_upload_confirm(do_upload, ask_for_upload, ask_response) |
974
|
|
|
rc = getattr(exc_val, 'return_code', None) |
975
|
|
|
return rc if rc is not None else 1 |
976
|
|
|
|
977
|
|
|
def get_error_report(self, exc_val, exc_tb): |
978
|
|
|
command = ' '.join(ensure_text_type(s) for s in sys.argv) |
979
|
|
|
info_dict = {} |
980
|
|
|
if ' info' not in command: |
981
|
|
|
# get info_dict, but if we get an exception here too, record it without trampling |
982
|
|
|
# the original exception |
983
|
|
|
try: |
984
|
|
|
from .cli.main_info import get_info_dict |
985
|
|
|
info_dict = get_info_dict() |
986
|
|
|
except Exception as info_e: |
987
|
|
|
info_traceback = _format_exc() |
988
|
|
|
info_dict = { |
989
|
|
|
'error': repr(info_e), |
990
|
|
|
'exception_name': info_e.__class__.__name__, |
991
|
|
|
'exception_type': text_type(exc_val.__class__), |
992
|
|
|
'traceback': info_traceback, |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
error_report = { |
996
|
|
|
'error': repr(exc_val), |
997
|
|
|
'exception_name': exc_val.__class__.__name__, |
998
|
|
|
'exception_type': text_type(exc_val.__class__), |
999
|
|
|
'command': command, |
1000
|
|
|
'traceback': _format_exc(exc_val, exc_tb), |
1001
|
|
|
'conda_info': info_dict, |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
if isinstance(exc_val, CondaError): |
1005
|
|
|
error_report['conda_error_components'] = exc_val.dump_map() |
1006
|
|
|
|
1007
|
|
|
return error_report |
1008
|
|
|
|
1009
|
|
|
def print_unexpected_error_report(self, error_report): |
1010
|
|
|
from .base.context import context |
1011
|
|
|
if context.json: |
1012
|
|
|
from .cli.common import stdout_json |
1013
|
|
|
stdout_json(error_report) |
1014
|
|
|
else: |
1015
|
|
|
message_builder = [] |
1016
|
|
|
message_builder.append('') |
1017
|
|
|
message_builder.append('# >>>>>>>>>>>>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<<<<<<<<<<<<') |
1018
|
|
|
message_builder.append('') |
1019
|
|
|
message_builder.extend(' ' + line |
1020
|
|
|
for line in error_report['traceback'].splitlines()) |
1021
|
|
|
message_builder.append('') |
1022
|
|
|
message_builder.append('`$ %s`' % error_report['command']) |
1023
|
|
|
message_builder.append('') |
1024
|
|
|
if error_report['conda_info']: |
1025
|
|
|
from .cli.main_info import get_env_vars_str, get_main_info_str |
1026
|
|
|
try: |
1027
|
|
|
# TODO: Sanitize env vars to remove secrets (e.g credentials for PROXY) |
1028
|
|
|
message_builder.append(get_env_vars_str(error_report['conda_info'])) |
1029
|
|
|
message_builder.append(get_main_info_str(error_report['conda_info'])) |
1030
|
|
|
except Exception as e: |
1031
|
|
|
log.warn("%r", e, exc_info=True) |
1032
|
|
|
message_builder.append('conda info could not be constructed.') |
1033
|
|
|
message_builder.append('%r' % e) |
1034
|
|
|
message_builder.append('') |
1035
|
|
|
message_builder.append( |
1036
|
|
|
"An unexpected error has occurred. Conda has prepared the above report." |
1037
|
|
|
) |
1038
|
|
|
message_builder.append('') |
1039
|
|
|
self.out_stream.write('\n'.join(message_builder)) |
1040
|
|
|
|
1041
|
|
|
def print_expected_error_report(self, error_report): |
1042
|
|
|
from .base.context import context |
1043
|
|
|
if context.json: |
1044
|
|
|
from .cli.common import stdout_json |
1045
|
|
|
stdout_json(error_report) |
1046
|
|
|
else: |
1047
|
|
|
message_builder = [] |
1048
|
|
|
message_builder.append('') |
1049
|
|
|
message_builder.append('# >>>>>>>>>>>>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<<<<<<<<<<<<') |
1050
|
|
|
message_builder.append('') |
1051
|
|
|
message_builder.append('`$ %s`' % error_report['command']) |
1052
|
|
|
message_builder.append('') |
1053
|
|
|
if error_report['conda_info']: |
1054
|
|
|
from .cli.main_info import get_env_vars_str, get_main_info_str |
1055
|
|
|
try: |
1056
|
|
|
# TODO: Sanitize env vars to remove secrets (e.g credentials for PROXY) |
1057
|
|
|
message_builder.append(get_env_vars_str(error_report['conda_info'])) |
1058
|
|
|
message_builder.append(get_main_info_str(error_report['conda_info'])) |
1059
|
|
|
except Exception as e: |
1060
|
|
|
log.warn("%r", e, exc_info=True) |
1061
|
|
|
message_builder.append('conda info could not be constructed.') |
1062
|
|
|
message_builder.append('%r' % e) |
1063
|
|
|
message_builder.append('') |
1064
|
|
|
message_builder.append('V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V') |
1065
|
|
|
message_builder.append('') |
1066
|
|
|
|
1067
|
|
|
message_builder.extend(error_report['error'].splitlines()) |
1068
|
|
|
message_builder.append('') |
1069
|
|
|
|
1070
|
|
|
message_builder.append( |
1071
|
|
|
"A reportable application error has occurred. Conda has prepared the above report." |
1072
|
|
|
) |
1073
|
|
|
message_builder.append('') |
1074
|
|
|
self.out_stream.write('\n'.join(message_builder)) |
1075
|
|
|
|
1076
|
|
|
def _calculate_ask_do_upload(self): |
1077
|
|
|
from .base.context import context |
1078
|
|
|
|
1079
|
|
|
try: |
1080
|
|
|
isatty = os.isatty(0) or on_win |
1081
|
|
|
except Exception as e: |
1082
|
|
|
log.debug('%r', e) |
1083
|
|
|
# given how the rest of this function is constructed, better to assume True here |
1084
|
|
|
isatty = True |
1085
|
|
|
|
1086
|
|
|
if context.report_errors is False: |
1087
|
|
|
ask_for_upload = False |
1088
|
|
|
do_upload = False |
1089
|
|
|
elif context.report_errors is True or context.always_yes: |
1090
|
|
|
ask_for_upload = False |
1091
|
|
|
do_upload = True |
1092
|
|
|
elif context.json or context.quiet: |
1093
|
|
|
ask_for_upload = False |
1094
|
|
|
do_upload = not context.offline and context.always_yes |
1095
|
|
|
elif not isatty: |
1096
|
|
|
ask_for_upload = False |
1097
|
|
|
do_upload = not context.offline and context.always_yes |
1098
|
|
|
else: |
1099
|
|
|
ask_for_upload = True |
1100
|
|
|
do_upload = False |
1101
|
|
|
|
1102
|
|
|
return ask_for_upload, do_upload |
1103
|
|
|
|
1104
|
|
|
def ask_for_upload(self): |
1105
|
|
|
message_builder = [ |
1106
|
|
|
"If submitted, this report will be used by core maintainers to improve", |
1107
|
|
|
"future releases of conda.", |
1108
|
|
|
"Would you like conda to send this report to the core maintainers?", |
1109
|
|
|
] |
1110
|
|
|
message_builder.append( |
1111
|
|
|
"[y/N]: " |
1112
|
|
|
) |
1113
|
|
|
self.out_stream.write('\n'.join(message_builder)) |
1114
|
|
|
ask_response = None |
1115
|
|
|
try: |
1116
|
|
|
ask_response = timeout(40, input) |
1117
|
|
|
do_upload = ask_response and boolify(ask_response) |
1118
|
|
|
except Exception as e: # pragma: no cover |
1119
|
|
|
log.debug('%r', e) |
1120
|
|
|
do_upload = False |
1121
|
|
|
return do_upload, ask_response |
1122
|
|
|
|
1123
|
|
|
def _execute_upload(self, error_report): |
1124
|
|
|
headers = { |
1125
|
|
|
'User-Agent': self.user_agent, |
1126
|
|
|
} |
1127
|
|
|
_timeout = self.http_timeout |
1128
|
|
|
data = json.dumps(error_report, sort_keys=True, cls=EntityEncoder) + '\n' |
1129
|
|
|
response = None |
1130
|
|
|
try: |
1131
|
|
|
# requests does not follow HTTP standards for redirects of non-GET methods |
1132
|
|
|
# That is, when following a 301 or 302, it turns a POST into a GET. |
1133
|
|
|
# And no way to disable. WTF |
1134
|
|
|
import requests |
1135
|
|
|
redirect_counter = 0 |
1136
|
|
|
url = self.error_upload_url |
1137
|
|
|
response = requests.post(url, headers=headers, timeout=_timeout, data=data, |
1138
|
|
|
allow_redirects=False) |
1139
|
|
|
response.raise_for_status() |
1140
|
|
|
while response.status_code in (301, 302) and response.headers.get('Location'): |
1141
|
|
|
url = response.headers['Location'] |
1142
|
|
|
response = requests.post(url, headers=headers, timeout=_timeout, data=data, |
1143
|
|
|
allow_redirects=False) |
1144
|
|
|
response.raise_for_status() |
1145
|
|
|
redirect_counter += 1 |
1146
|
|
|
if redirect_counter > 15: |
1147
|
|
|
raise CondaError("Redirect limit exceeded") |
1148
|
|
|
log.debug("upload response status: %s", response and response.status_code) |
1149
|
|
|
except Exception as e: # pragma: no cover |
1150
|
|
|
log.info('%r', e) |
1151
|
|
|
try: |
1152
|
|
|
if response and response.ok: |
1153
|
|
|
self.out_stream.write("Upload successful.\n") |
1154
|
|
|
else: |
1155
|
|
|
self.out_stream.write("Upload did not complete.") |
1156
|
|
|
if response and response.status_code: |
1157
|
|
|
self.out_stream.write(" HTTP %s" % response.status_code) |
1158
|
|
|
self.out_stream.write("\n") |
1159
|
|
|
except Exception as e: |
1160
|
|
|
log.debug("%r" % e) |
1161
|
|
|
|
1162
|
|
|
def print_upload_confirm(self, do_upload, ask_for_upload, ask_response): |
1163
|
|
|
if ask_response and do_upload: |
1164
|
|
|
self.out_stream.write( |
1165
|
|
|
"\n" |
1166
|
|
|
"Thank you for helping to improve conda.\n" |
1167
|
|
|
"Opt-in to always sending reports (and not see this message again)\n" |
1168
|
|
|
"by running\n" |
1169
|
|
|
"\n" |
1170
|
|
|
" $ conda config --set report_errors true\n" |
1171
|
|
|
"\n" |
1172
|
|
|
) |
1173
|
|
|
elif ask_for_upload: |
1174
|
|
|
self.out_stream.write( |
1175
|
|
|
"\n" |
1176
|
|
|
"No report sent. To permanently opt-out, use\n" |
1177
|
|
|
"\n" |
1178
|
|
|
" $ conda config --set report_errors false\n" |
1179
|
|
|
"\n" |
1180
|
|
|
) |
1181
|
|
|
elif ask_response is None and ask_for_upload: |
1182
|
|
|
# means timeout was reached for `input` |
1183
|
|
|
self.out_stream.write( # lgtm [py/unreachable-statement] |
1184
|
|
|
'\nTimeout reached. No report sent.\n' |
1185
|
|
|
) |
1186
|
|
|
|
1187
|
|
|
|
1188
|
|
|
def conda_exception_handler(func, *args, **kwargs): |
1189
|
|
|
exception_handler = ExceptionHandler() |
1190
|
|
|
return_value = exception_handler(func, *args, **kwargs) |
1191
|
|
|
if isinstance(return_value, int): |
1192
|
|
|
return return_value |
1193
|
|
|
|