Issues (107)

conda/exceptions.py (2 issues)

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