Completed
Push — master ( 117db0...24da64 )
by Gonzalo
153:14 queued 88:17
created

NoBaseEnvironmentError.__init__()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
ccs 2
cts 4
cp 0.5
crap 1.125
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