Skip to content

Commit

Permalink
Use **kwargs instead of OrderedDict for `_Activator.get_export_un…
Browse files Browse the repository at this point in the history
…set_vars` (conda#11649)
  • Loading branch information
kenodegard authored Aug 1, 2022
1 parent 9bf4c17 commit 818a2d1
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 128 deletions.
184 changes: 84 additions & 100 deletions conda/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import absolute_import, division, print_function, unicode_literals

from collections import OrderedDict
from errno import ENOENT
from itertools import chain
import json
import os
from os.path import abspath, basename, dirname, expanduser, expandvars, isdir, join, exists
Expand Down Expand Up @@ -65,58 +63,51 @@ def __init__(self, arguments=None):
self._raw_arguments = arguments
self.environ = os.environ.copy()

# Once Python2 dies odargs can become kwargs again since dicts are ordered since 3.6.
def get_export_unset_vars(self, odargs):
def get_export_unset_vars(self, export_metavars=True, **kwargs):
"""
:param kwargs: environment variables to export. The `conda_exe_vars` meta
variables are also exported by default. If you do not want
this to happen then pass:
conda_exe_vars=None
:param export_metavars: whether to export `conda_exe_vars` meta variables.
:param kwargs: environment variables to export.
.. if you pass and set any other variable to None, then it
emits it to the dict with a value of None.
:return: A OrderedDict of env vars to export ordered the same way as kwargs.
:return: A dict of env vars to export ordered the same way as kwargs.
And a list of env vars to unset.
"""
kwargs = odargs
conda_exe_vars_None = False if ('conda_exe_vars' not in kwargs or
kwargs['conda_exe_vars'] is not None) else True
conda_exe_unset_vars = []
unset_vars = []
# conda_exe_vars = context.conda_exe_vars_dict.copy()
from collections import OrderedDict
conda_exe_vars_export = OrderedDict()
for k, v in context.conda_exe_vars_dict.items():
if v is None or conda_exe_vars_None:
conda_exe_unset_vars.append(k)
else:
conda_exe_vars_export[k] = self.path_conversion(v) if v else v
from collections import OrderedDict
export_vars = OrderedDict()
for k, v in kwargs.items():
if k == 'conda_exe_vars':
continue
elif v is None:
unset_vars.append(k.upper())
export_vars = {}

# split provided environment variables into exports vs unsets
for name, value in kwargs.items():
if value is None:
unset_vars.append(name.upper())
else:
export_vars[k.upper()] = v
# Just to make writing tests a bit more ergonomic we add these to the end.
unset_vars += conda_exe_unset_vars
export_vars = OrderedDict(chain(export_vars.items(), conda_exe_vars_export.items()))
export_vars[name.upper()] = value

if export_metavars:
# split meta variables into exports vs unsets
for name, value in context.conda_exe_vars_dict.items():
if value is None:
unset_vars.append(name.upper())
else:
export_vars[name.upper()] = self.path_conversion(value) if value else value
else:
# unset all meta variables
unset_vars.extend(context.conda_exe_vars_dict)

return export_vars, unset_vars

# Used in tests only.
def add_export_unset_vars(self, export_vars, unset_vars, **kwargs):
new_export_vars, new_unset_vars = self.get_export_unset_vars(odargs=OrderedDict(kwargs))
new_export_vars, new_unset_vars = self.get_export_unset_vars(**kwargs)
if export_vars is not None:
export_vars = OrderedDict(chain(export_vars.items(), new_export_vars.items()))
export_vars = {**export_vars, **new_export_vars}
if unset_vars is not None:
unset_vars += new_unset_vars
unset_vars = [*unset_vars, *new_unset_vars]
return export_vars, unset_vars

# Used in tests only.
def get_scripts_export_unset_vars(self, **kwargs):
export_vars, unset_vars = self.get_export_unset_vars(odargs=OrderedDict(kwargs))
export_vars, unset_vars = self.get_export_unset_vars(**kwargs)
script_export_vars = script_unset_vars = None
if export_vars:
script_export_vars = self.command_join.join(
Expand Down Expand Up @@ -336,49 +327,42 @@ def _build_activate_stack(self, env_name_or_prefix, stack):

unset_vars = []
if old_conda_shlvl == 0:
new_path = self.pathsep_join(self._add_prefix_to_path(prefix))
env_vars_to_export = OrderedDict((
('path', new_path),
('conda_prefix', prefix),
('conda_shlvl', new_conda_shlvl),
('conda_default_env', conda_default_env),
('conda_prompt_modifier', conda_prompt_modifier)))
for k, v in conda_environment_env_vars.items():
env_vars_to_export[k] = v
export_vars, unset_vars = self.get_export_unset_vars(odargs=env_vars_to_export)
export_vars, unset_vars = self.get_export_unset_vars(
path=self.pathsep_join(self._add_prefix_to_path(prefix)),
conda_prefix=prefix,
conda_shlvl=new_conda_shlvl,
conda_default_env=conda_default_env,
conda_prompt_modifier=conda_prompt_modifier,
**conda_environment_env_vars,
)
deactivate_scripts = ()
else:
if self.environ.get('CONDA_PREFIX_%s' % (old_conda_shlvl - 1)) == prefix:
# in this case, user is attempting to activate the previous environment,
# i.e. step back down
return self.build_deactivate()
if stack:
new_path = self.pathsep_join(self._add_prefix_to_path(prefix))
deactivate_scripts = ()
env_vars_to_export = OrderedDict((
('path', new_path),
('conda_prefix', prefix),
('conda_shlvl', new_conda_shlvl),
('conda_default_env', conda_default_env),
('conda_prompt_modifier', conda_prompt_modifier)))
for k, v in conda_environment_env_vars.items():
env_vars_to_export[k] = v
export_vars, unset_vars = self.get_export_unset_vars(odargs=env_vars_to_export)
export_vars, unset_vars = self.get_export_unset_vars(
path=self.pathsep_join(self._add_prefix_to_path(prefix)),
conda_prefix=prefix,
conda_shlvl=new_conda_shlvl,
conda_default_env=conda_default_env,
conda_prompt_modifier=conda_prompt_modifier,
**conda_environment_env_vars,
)
export_vars['CONDA_PREFIX_%d' % old_conda_shlvl] = old_conda_prefix
export_vars['CONDA_STACKED_%d' % new_conda_shlvl] = 'true'
else:
new_path = self.pathsep_join(
self._replace_prefix_in_path(old_conda_prefix, prefix))
deactivate_scripts = self._get_deactivate_scripts(old_conda_prefix)
env_vars_to_export = OrderedDict((
('path', new_path),
('conda_prefix', prefix),
('conda_shlvl', new_conda_shlvl),
('conda_default_env', conda_default_env),
('conda_prompt_modifier', conda_prompt_modifier)))
for k, v in conda_environment_env_vars.items():
env_vars_to_export[k] = v
export_vars, unset_vars = self.get_export_unset_vars(odargs=env_vars_to_export)
export_vars, unset_vars = self.get_export_unset_vars(
path=self.pathsep_join(self._replace_prefix_in_path(old_conda_prefix, prefix)),
conda_prefix=prefix,
conda_shlvl=new_conda_shlvl,
conda_default_env=conda_default_env,
conda_prompt_modifier=conda_prompt_modifier,
**conda_environment_env_vars,
)
export_vars['CONDA_PREFIX_%d' % old_conda_shlvl] = old_conda_prefix

set_vars = {}
Expand All @@ -403,11 +387,11 @@ def build_deactivate(self):
if not old_conda_prefix or old_conda_shlvl < 1:
# no active environment, so cannot deactivate; do nothing
return {
'unset_vars': (),
'set_vars': OrderedDict(),
'export_vars': OrderedDict(),
'deactivate_scripts': (),
'activate_scripts': (),
"unset_vars": (),
"set_vars": {},
"export_vars": {},
"deactivate_scripts": (),
"activate_scripts": (),
}
deactivate_scripts = self._get_deactivate_scripts(old_conda_prefix)
old_conda_environment_env_vars = self._get_environment_env_vars(old_conda_prefix)
Expand All @@ -416,18 +400,19 @@ def build_deactivate(self):
set_vars = {}
if old_conda_shlvl == 1:
new_path = self.pathsep_join(self._remove_prefix_from_path(old_conda_prefix))
# You might think that you can remove the CONDA_EXE vars by passing conda_exe_vars=None
# You might think that you can remove the CONDA_EXE vars with export_metavars=False
# here so that "deactivate means deactivate" but you cannot since the conda shell
# scripts still refer to them and they only set them once at the top. We could change
# that though, the conda() shell function could set them instead of doing it at the
# top. This would be *much* cleaner. I personally cannot abide that I have
# deactivated conda and anything at all in my env still references it (apart from the
# shell script, we need something I suppose!)
export_vars, unset_vars = self.get_export_unset_vars(odargs=OrderedDict((
('conda_prefix', None),
('conda_shlvl', new_conda_shlvl),
('conda_default_env', None),
('conda_prompt_modifier', None))))
export_vars, unset_vars = self.get_export_unset_vars(
conda_prefix=None,
conda_shlvl=new_conda_shlvl,
conda_default_env=None,
conda_prompt_modifier=None,
)
conda_prompt_modifier = ''
activate_scripts = ()
export_path = {'PATH': new_path, }
Expand All @@ -450,14 +435,13 @@ def build_deactivate(self):
self._replace_prefix_in_path(old_conda_prefix, new_prefix)
)

env_vars_to_export = OrderedDict((
('conda_prefix', new_prefix),
('conda_shlvl', new_conda_shlvl),
('conda_default_env', conda_default_env),
('conda_prompt_modifier', conda_prompt_modifier)))
for k, v in new_conda_environment_env_vars.items():
env_vars_to_export[k] = v
export_vars, unset_vars2 = self.get_export_unset_vars(odargs=env_vars_to_export)
export_vars, unset_vars2 = self.get_export_unset_vars(
conda_prefix=new_prefix,
conda_shlvl=new_conda_shlvl,
conda_default_env=conda_default_env,
conda_prompt_modifier=conda_prompt_modifier,
**new_conda_environment_env_vars,
)
unset_vars += unset_vars2
export_path = {'PATH': new_path, }
activate_scripts = self._get_activate_scripts(new_prefix)
Expand Down Expand Up @@ -486,11 +470,11 @@ def build_reactivate(self):
if not conda_prefix or conda_shlvl < 1:
# no active environment, so cannot reactivate; do nothing
return {
'unset_vars': (),
'set_vars': OrderedDict(),
'export_vars': OrderedDict(),
'deactivate_scripts': (),
'activate_scripts': (),
"unset_vars": (),
"set_vars": {},
"export_vars": {},
"deactivate_scripts": (),
"activate_scripts": (),
}
conda_default_env = self.environ.get('CONDA_DEFAULT_ENV', self._default_env(conda_prefix))
new_path = self.pathsep_join(self._replace_prefix_in_path(conda_prefix, conda_prefix))
Expand All @@ -500,11 +484,11 @@ def build_reactivate(self):
self._update_prompt(set_vars, conda_prompt_modifier)

env_vars_to_unset = ()
env_vars_to_export = OrderedDict([
('PATH', new_path),
('CONDA_SHLVL', conda_shlvl),
('CONDA_PROMPT_MODIFIER', self._prompt_modifier(conda_prefix, conda_default_env)),
])
env_vars_to_export = {
"PATH": new_path,
"CONDA_SHLVL": conda_shlvl,
"CONDA_PROMPT_MODIFIER": self._prompt_modifier(conda_prefix, conda_default_env),
}
conda_environment_env_vars = self._get_environment_env_vars(conda_prefix)
for k, v in conda_environment_env_vars.items():
if v == CONDA_ENV_VARS_UNSET_VAR:
Expand Down Expand Up @@ -722,18 +706,18 @@ def _get_deactivate_scripts(self, prefix):
def _get_environment_env_vars(self, prefix):
env_vars_file = join(prefix, PREFIX_STATE_FILE)
pkg_env_var_dir = join(prefix, PACKAGE_ENV_VARS_DIR)
env_vars = OrderedDict()
env_vars = {}

# First get env vars from packages
if exists(pkg_env_var_dir):
for pkg_env_var_path in sorted(entry.path for entry in os.scandir(pkg_env_var_dir)):
with open(pkg_env_var_path, 'r') as f:
env_vars.update(json.loads(f.read(), object_pairs_hook=OrderedDict))
env_vars.update(json.loads(f.read()))

# Then get env vars from environment specification
if exists(env_vars_file):
with open(env_vars_file, 'r') as f:
prefix_state = json.loads(f.read(), object_pairs_hook=OrderedDict)
prefix_state = json.loads(f.read())
prefix_state_env_vars = prefix_state.get('env_vars', {})
dup_vars = [ev for ev in env_vars.keys() if ev in prefix_state_env_vars.keys()]
for dup in dup_vars:
Expand Down Expand Up @@ -1115,7 +1099,7 @@ def _hook_preamble(self):
}

def get_scripts_export_unset_vars(self, **kwargs):
export_vars, unset_vars = self.get_export_unset_vars(odargs=OrderedDict(kwargs))
export_vars, unset_vars = self.get_export_unset_vars(**kwargs)
script_export_vars = script_unset_vars = None
if export_vars:
script_export_vars = dict(export_vars.items())
Expand Down
19 changes: 19 additions & 0 deletions news/11649-refactor-get_export_unset_vars
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* <news item>

### Bug fixes

* <news item>

### Deprecations

* Refactor `conda.activate._Activator.get_export_unset_vars` to use `**kwargs` instead of `OrderedDict`. (#11649)

### Docs

* <news item>

### Other

* <news item>
51 changes: 23 additions & 28 deletions tests/test_activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,32 +930,28 @@ def test_build_deactivate_shlvl_1(self):
original_path = tuple(activator._get_starting_path_list())
builder = activator.build_deactivate()

unset_vars = [
'CONDA_PREFIX',
'CONDA_DEFAULT_ENV',
'CONDA_PROMPT_MODIFIER',
'PKG_A_ENV',
'PKG_B_ENV',
'ENV_ONE',
'ENV_TWO',
'ENV_THREE'
]

new_path = activator.pathsep_join(activator.path_conversion(original_path))
assert builder['set_vars'] == {
'PS1': os.environ.get('PS1', ''),
}
export_vars = OrderedDict((
('CONDA_SHLVL', 0),
))
export_path = {'PATH': new_path,}
export_vars, unset_vars = activator.add_export_unset_vars(export_vars, unset_vars,
conda_exe_vars=True)
assert builder['export_vars'] == export_vars
assert builder['unset_vars'] == unset_vars
assert builder['export_path'] == export_path
assert builder['activate_scripts'] == ()
assert builder['deactivate_scripts'] == (activator.path_conversion(deactivate_d_1),)
export_vars, unset_vars = activator.add_export_unset_vars(
{"CONDA_SHLVL": 0},
[
"CONDA_PREFIX",
"CONDA_DEFAULT_ENV",
"CONDA_PROMPT_MODIFIER",
"PKG_A_ENV",
"PKG_B_ENV",
"ENV_ONE",
"ENV_TWO",
"ENV_THREE",
],
)
assert builder["set_vars"] == {"PS1": os.environ.get("PS1", "")}
assert builder["export_vars"] == export_vars
assert builder["unset_vars"] == unset_vars
assert builder["export_path"] == {"PATH": new_path}
assert builder["activate_scripts"] == ()
assert builder["deactivate_scripts"] == (
activator.path_conversion(deactivate_d_1),
)

def test_get_env_vars_big_whitespace(self):
with tempdir() as td:
Expand Down Expand Up @@ -1216,7 +1212,7 @@ def test_posix_basic(self):
deactivate_data = c.stdout

new_path = activator.pathsep_join(activator._remove_prefix_from_path(self.prefix))
conda_exe_export, conda_exe_unset = activator.get_scripts_export_unset_vars(conda_exe_vars=True)
conda_exe_export, conda_exe_unset = activator.get_scripts_export_unset_vars()

e_deactivate_data = dals("""
export PATH='%(new_path)s'
Expand Down Expand Up @@ -1397,9 +1393,8 @@ def test_csh_basic(self):
deactivate_data = c.stdout

new_path = activator.pathsep_join(activator._remove_prefix_from_path(self.prefix))
conda_exe_vars = ';\n'.join([activator.export_var_tmpl % (k, v) for k, v in context.conda_exe_vars_dict.items()])

conda_exe_export, conda_exe_unset = activator.get_scripts_export_unset_vars(conda_exe_vars=True)
conda_exe_export, conda_exe_unset = activator.get_scripts_export_unset_vars()

e_deactivate_data = dals("""
setenv PATH "%(new_path)s";
Expand Down

0 comments on commit 818a2d1

Please sign in to comment.