def test_use_non_default_conda_manager():
    called = dict()

    class MyCondaManager(CondaManager):
        def __init__(self, frontend):
            pass

        def resolve_dependencies(self, package_specs, channels, platforms):
            return CondaLockSet({}, platforms=[])

        def find_environment_deviations(self, *args):
            called['find_environment_deviations'] = args

        def fix_environment_deviations(self, *args):
            called['fix_environment_deviations'] = args

        def remove_packages(self, *args):
            called['remove_packages'] = args

    push_conda_manager_class(MyCondaManager)
    try:
        manager = new_conda_manager()
        manager.find_environment_deviations(None, None)
        manager.fix_environment_deviations(None, None)
        manager.remove_packages(None, None)
        assert dict(find_environment_deviations=(None, None),
                    fix_environment_deviations=(None, None),
                    remove_packages=(None, None)) == called
    finally:
        pop_conda_manager_class()
Exemple #2
0
    def provide(self, requirement, context):
        """Override superclass to create or update our environment."""
        assert 'PATH' in context.environ
        conda = new_conda_manager(context.frontend)

        # set from the inherited vale if necessary
        if context.status.analysis.config['source'] == 'inherited':
            context.environ[
                requirement.env_var] = context.status.analysis.config['value']

        # set the env var (but not PATH, etc. to fully activate, that's done below)
        super_result = super(CondaBootstrapEnvProvider,
                             self).provide(requirement, context)

        project_dir = context.environ['PROJECT_DIR']

        env_name = context.status.analysis.config.get(
            'env_name', context.default_env_spec_name)
        env_spec = requirement.env_specs.get(env_name)

        prefix = os.path.join(project_dir, 'envs', 'bootstrap-env')

        # if the value has changed, choose the matching env spec
        # (something feels wrong here; should this be in read_config?
        # or not at all?)
        for env in requirement.env_specs.values():
            if env.path(project_dir) == prefix:
                env_spec = env
                break

        if context.mode != PROVIDE_MODE_CHECK:
            # we update the environment in both prod and dev mode

            # TODO if not creating a named env, we could use the
            # shared packages, but for now we leave it alone
            assert env_spec is not None
            try:
                conda.fix_environment_deviations(prefix, env_spec, create=True)
            except CondaManagerError as e:
                return super_result.copy_with_additions(errors=[str(e)])

        conda_api.environ_set_prefix(context.environ,
                                     prefix,
                                     varname=requirement.env_var)

        path = context.environ.get("PATH", "")

        context.environ["PATH"] = conda_api.set_conda_env_in_path(path, prefix)
        # Some stuff can only be done when a shell is launched:
        #  - we can't set PS1 because it shouldn't be exported.
        #  - we can't run conda activate scripts because they are sourced.
        # We can do these in the output of our activate command, but not here.

        return super_result
Exemple #3
0
    def __init__(self, registry, env_specs=None):
        """Extend superclass to default to CONDA_PREFIX and carry environment information.

        Args:
            registry (RequirementsRegistry): plugin registry
            env_specs (dict): dict from env name to ``CondaEnvironment``
        """
        super(CondaBootstrapEnvRequirement,
              self).__init__(registry=registry, env_var="BOOTSTRAP_ENV_PREFIX")
        self.env_specs = env_specs
        self._conda = new_conda_manager()
Exemple #4
0
    def __init__(self, registry, env_specs=None):
        """Extend superclass to default to CONDA_PREFIX and carry environment information.

        Args:
            registry (PluginRegistry): plugin registry
            env_specs (dict): dict from env name to ``CondaEnvironment``
        """
        super(CondaEnvRequirement,
              self).__init__(registry=registry,
                             env_var=conda_api.conda_prefix_variable())
        self.env_specs = env_specs
        self._conda = new_conda_manager()
    def __init__(self, registry, env_specs=None, env_var=None, bootstrap=False):
        """Extend superclass to default to CONDA_PREFIX and carry environment information.

        Args:
            registry (RequirementsRegistry): plugin registry
            env_specs (dict): dict from env name to ``CondaEnvironment``
        """
        if env_var is None:
            env_var = conda_api.conda_prefix_variable()
        super(CondaEnvRequirement, self).__init__(registry=registry, env_var=env_var)
        self.env_specs = env_specs
        self._bootstrap = env_var == 'BOOTSTRAP_ENV_PREFIX'
        self._conda = new_conda_manager()
def test_use_non_default_conda_manager():
    called = dict()

    class MyCondaManager(CondaManager):
        def find_environment_deviations(self, *args):
            called['find_environment_deviations'] = args

        def fix_environment_deviations(self, *args):
            called['fix_environment_deviations'] = args

        def remove_packages(self, *args):
            called['remove_packages'] = args

    push_conda_manager_class(MyCondaManager)
    try:
        manager = new_conda_manager()
        manager.find_environment_deviations(None, None)
        manager.fix_environment_deviations(None, None)
        manager.remove_packages(None, None)
        assert dict(find_environment_deviations=(None, None),
                    fix_environment_deviations=(None, None),
                    remove_packages=(None, None)) == called
    finally:
        pop_conda_manager_class()
def remove_packages(project, env_spec_name, packages):
    """Attempt to remove packages from an environment in anaconda-project.yml.

    If the env_spec_name is None rather than an env name,
    packages are removed from the global packages section
    (from all environments).

    The returned ``Status`` should be a ``RequirementStatus`` for
    the environment requirement if it evaluates to True (on success),
    but may be another subtype of ``Status`` on failure. A False
    status will have an ``errors`` property with a list of error
    strings.

    Args:
        project (Project): the project
        env_spec_name (str): environment spec name or None for all environment specs
        packages (list of str): packages to remove

    Returns:
        ``Status`` instance
    """
    # This is sort of one big ugly. What we SHOULD be able to do
    # is simply remove the package from anaconda-project.yml then re-run
    # prepare, and if the packages aren't pulled in as deps of
    # something else, they get removed. This would work if our
    # approach was to always force the env to exactly the env
    # we'd have created from scratch, given our env config.
    # But that isn't our approach right now.
    #
    # So what we do right now is remove the package from the env,
    # and then remove it from anaconda-project.yml, and then see if we can
    # still prepare the project.

    # TODO this should handle env spec inheritance in the same way
    # it handles the global packages list, that is, removing a package
    # from a spec should also remove it from its ancestors (and possibly
    # add it to other children of those ancestors).
    # Not doing it at the time of writing this comment because it might
    # be nicer to rewrite this whole thing when we add version pinning
    # anyway.

    failed = project.problems_status()
    if failed is not None:
        return failed

    assert packages is not None
    assert len(packages) > 0

    if env_spec_name is None:
        envs = project.env_specs.values()
        unaffected_envs = []
    else:
        env = project.env_specs.get(env_spec_name, None)
        if env is None:
            problem = "Environment spec {} doesn't exist.".format(
                env_spec_name)
            return SimpleStatus(success=False, description=problem)
        else:
            envs = [env]
            unaffected_envs = list(project.env_specs.values())
            unaffected_envs.remove(env)
            assert len(unaffected_envs) == (len(project.env_specs) - 1)

    assert len(envs) > 0

    conda = conda_manager.new_conda_manager()

    for env in envs:
        prefix = env.path(project.directory_path)
        try:
            if os.path.isdir(prefix):
                conda.remove_packages(prefix, packages)
        except conda_manager.CondaManagerError:
            pass  # ignore errors; not all the envs will exist or have the package installed perhaps

    def envs_to_their_dicts(envs):
        env_dicts = []
        for env in envs:
            env_dict = project.project_file.get_value(['env_specs', env.name])
            if env_dict is not None:  # it can be None for the default environment (which doesn't have to be listed)
                env_dicts.append(env_dict)
        return env_dicts

    env_dicts = envs_to_their_dicts(envs)
    env_dicts.append(project.project_file.root)

    unaffected_env_dicts = envs_to_their_dicts(unaffected_envs)

    assert len(env_dicts) > 0

    previous_global_deps = set(project.project_file.root.get('packages', []))

    for env_dict in env_dicts:
        # packages may be a "CommentedSeq" and we don't want to lose the comments,
        # so don't convert this thing to a regular list.
        old_packages = env_dict.get('packages', [])
        removed_set = set(packages)
        _filter_inplace(lambda dep: dep not in removed_set, old_packages)
        env_dict['packages'] = old_packages

    # if we removed any deps from global, add them to the
    # individual envs that were not supposed to be affected.
    new_global_deps = set(project.project_file.root.get('packages', []))
    removed_from_global = (previous_global_deps - new_global_deps)
    for env_dict in unaffected_env_dicts:
        # old_packages may be a "CommentedSeq" and we don't want to lose the comments,
        # so don't convert this thing to a regular list.
        old_packages = env_dict.get('packages', [])
        old_packages.extend(list(removed_from_global))
        env_dict['packages'] = old_packages

    status = _commit_requirement_if_it_works(project,
                                             CondaEnvRequirement,
                                             env_spec_name=env_spec_name)

    return status
    def provide(self, requirement, context):
        """Override superclass to create or update our environment."""
        assert 'PATH' in context.environ

        conda = new_conda_manager(context.frontend)

        # set from the inherited vale if necessary
        if context.status.analysis.config['source'] == 'inherited':
            context.environ[
                requirement.env_var] = context.status.analysis.config['value']

        # set the env var (but not PATH, etc. to fully activate, that's done below)
        super_result = super(CondaEnvProvider,
                             self).provide(requirement, context)

        project_dir = context.environ['PROJECT_DIR']

        env_name = context.status.analysis.config.get(
            'env_name', context.default_env_spec_name)
        env_spec = requirement.env_specs.get(env_name)

        if env_name == 'bootstrap-env':
            # The bootstrap environment is always stored in the project directory
            # TODO: have this respect ANACONDA_PROJECT_ENVS_PATH
            prefix = os.path.join(project_dir, 'envs', 'bootstrap-env')
        elif context.status.analysis.config['source'] == 'inherited':
            prefix = context.environ.get(requirement.env_var, None)
            inherited = True
        else:
            prefix = None
            inherited = False

        if prefix is None:
            # use the default environment
            prefix = env_spec.path(project_dir)

        assert prefix is not None

        # if the value has changed, choose the matching env spec
        # (something feels wrong here; should this be in read_config?
        # or not at all?)
        for env in requirement.env_specs.values():
            if env.path(project_dir) == prefix:
                env_spec = env
                break

        if context.mode != PROVIDE_MODE_CHECK:
            # we update the environment in both prod and dev mode
            # TODO if not creating a named env, we could use the
            # shared packages, but for now we leave it alone
            assert env_spec is not None

            deviations = conda.find_environment_deviations(prefix, env_spec)

            readonly_policy = os.environ.get(
                'ANACONDA_PROJECT_READONLY_ENVS_POLICY', 'fail').lower()

            if deviations.unfixable and readonly_policy in ('clone',
                                                            'replace'):
                # scan for writable path
                destination = env_spec.path(project_dir,
                                            reset=True,
                                            force_writable=True)
                if destination != prefix:
                    if readonly_policy == 'replace':
                        print('Replacing the readonly environment {}'.format(
                            prefix))
                        deviations = conda.find_environment_deviations(
                            destination, env_spec)
                    else:
                        print('Cloning the readonly environment {}'.format(
                            prefix))
                        conda_api.clone(
                            destination,
                            prefix,
                            stdout_callback=context.frontend.partial_info,
                            stderr_callback=context.frontend.partial_error)
                    prefix = destination

            try:
                conda.fix_environment_deviations(prefix,
                                                 env_spec,
                                                 create=(not inherited))
            except CondaManagerError as e:
                return super_result.copy_with_additions(errors=[str(e)])

        conda_api.environ_set_prefix(context.environ,
                                     prefix,
                                     varname=requirement.env_var)

        path = context.environ.get("PATH", "")

        context.environ["PATH"] = conda_api.set_conda_env_in_path(path, prefix)
        # Some stuff can only be done when a shell is launched:
        #  - we can't set PS1 because it shouldn't be exported.
        #  - we can't run conda activate scripts because they are sourced.
        # We can do these in the output of our activate command, but not here.

        return super_result
 def __init__(self):
     """Override to create our CondaManager."""
     super(CondaEnvProvider, self).__init__()
     self._conda = new_conda_manager()
Exemple #10
0
    def __init__(self,
                 name,
                 conda_packages,
                 channels,
                 pip_packages=(),
                 description=None,
                 inherit_from_names=(),
                 inherit_from=(),
                 platforms=(),
                 lock_set=None):
        """Construct a package set with the given name and packages.

        Args:
            name (str): name of the package set
            conda_packages (list): list of package specs to pass to conda install
            channels (list): list of channel names
            pip_packages (list): list of pip package specs to pass to pip
            description (str or None): one-sentence-ish summary of what this env is
            inherit_from_name (str or None): name of what we inherit from
            inherit_from (EnvSpec or None): pull in packages and channels from
            lock_set (CondaLockSet): locked packages or None
        """
        assert inherit_from_names is not None
        assert inherit_from is not None

        self._name = name
        self._path = None
        self._readonly = None
        self._conda_packages = tuple(conda_packages)
        self._channels = tuple(channels)
        self._pip_packages = tuple(pip_packages)
        self._description = description
        self._logical_hash = None
        self._locked_hash = None
        self._import_hash = None
        self._inherit_from_names = inherit_from_names
        self._inherit_from = inherit_from
        self._lock_set = lock_set
        self._platforms = tuple(conda_api.sort_platform_list(platforms))

        # inherit_from must be a subset of inherit_from_names
        # except that we can have an anonymous base env spec for
        # the global packages/channels sections; if there was an
        # error that kept us from creating one of the specs we
        # name as a parent, then self._inherit_from would be a
        # subset rather than equal.
        for name in tuple([spec.name for spec in self._inherit_from]):
            assert name is None or name in self._inherit_from_names

        conda_specs_by_name = dict()
        for spec in self.conda_packages_for_create:
            # we quietly skip invalid specs here and let them fail
            # somewhere we can more easily report an error message.
            parsed = conda_api.parse_spec(spec)
            if parsed is not None:
                conda_specs_by_name[parsed.name] = spec
        self._conda_specs_for_create_by_name = conda_specs_by_name

        name_set = set()
        conda_constrained_packages = []
        for spec in self.conda_packages:
            parsed = conda_api.parse_spec(spec)
            if parsed is not None:
                name_set.add(parsed.name)
                if parsed.conda_constraint is not None or parsed.pip_constraint is not None:
                    conda_constrained_packages.append(spec)
        self._conda_logical_specs_name_set = name_set
        self.conda_constrained_packages = sorted(conda_constrained_packages)

        pip_specs_by_name = dict()
        for spec in self.pip_packages_for_create:
            # we quietly skip invalid specs here and let them fail
            # somewhere we can more easily report an error message.
            parsed = pip_api.parse_spec(spec)
            if parsed is not None:
                pip_specs_by_name[parsed.name] = spec
        self._pip_specs_for_create_by_name = pip_specs_by_name

        name_set = set()
        for spec in self.pip_packages:
            parsed = pip_api.parse_spec(spec)
            if parsed is not None:
                name_set.add(parsed.name)
        self._pip_logical_specs_name_set = name_set

        self._conda = conda_manager.new_conda_manager()