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()
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
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()
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()
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()