conda config
and context#
The context
object is central to many parts of the conda
codebase. It serves as a centralized
repository of settings. You normally import the singleton and access its (many) attributes directly:
from conda.base.context import context
context.quiet
# False
This singleton is initialized from a cascade of different possible sources. From lower to higher precedence:
Default values hardcoded in the
Context
class. These are defined via class attributes.Values defined in the configuration files (
.condarc
), which have their own precedence.Values set by the corresponding command line arguments, if any.
Values defined by their corresponding
CONDA_*
environment variables, if present.
The mechanism implementing this behavior is an elaborate object with several types of objects involved.
Anatomy of the Context
class#
conda.base.context.Context
is an conda-specific subclass of the application-agnostic
conda.common.configuration.Configuration
class. This class implements the precedence order
for the instantiation of each defined attribute, as well as the overall validation logic and help
message reporting. But that’s it, it’s merely a storage of ParameterLoader
objects which, in
turn, instantiate the relevant Parameter
subclasses in each attribute. Roughly:
class MyConfiguration(Configuration):
string_field = ParameterLoader(PrimitiveParameter("default", str))
list_of_int_field = ParameterLoader(SequenceParameter([1, 2, 3], int))
map_of_foat_values_field = ParameterLoader(MapParameter({"key": 1.0}, float))
When MyConfiguration
is instantiated, those class attributes are populated by the .raw_data
dicionary that has been filled in with the values coming from the precedence chain stated
above. The raw_data
dictionary contains RawParameter
objects, subclassed to deal with the
specifics of their origin (YAML file, environment variable, command line flag). Each
ParameterLoader
object will pass the RawParameter
object to the .load()
method of its relevant
Parameter
subclass, which are designed to return their corresponding LoadedParameter
object
counterpart.
It’s a bit confusing, but the delegation happens like this:
The
Configuration
subclass parses the raw values of the possible origins and stores them as the relevantRawParameter
objects, which can be:EnvRawParameter
: for those coming from an environment variableArgParseRawParameter
: for those coming from a command line flagYamlRawParameter
: for those coming from a configuration fileDefaultValueRawParameter
: for those coming from the default value given toParameterLoader
Each
Configuration
attribute is aParameterLoader
, which implements theproperty
protocol via__get__
. This means that, upon attribute access (e.g.MyConfiguration.string_field
), theParameterLoader
can execute the loading logic. This means finding potential type matches in the raw data, loading them asLoadedParameter
objects and merging them with the adequate precedence order.
The merging policy depends on the (Loaded)Parameter
subtype. Below is a list of available
subtypes:
PrimitiveParameter
: holds a single scalar value of typestr
,int
,float
,complex
,bool
orNoneType
.SequenceParameter
: holds an iterable (list
) of otherParameter
objects.MapParameter
: holds a mapping (dict
) of otherParameter
objects.ObjectParameter
: holds an object with attributes set toParameter
objects.
The main goal of the Parameter
objects is to implement how to typify and turn the raw values into
their Loaded
counterparts. These implement the validation routines and define how parameters for
the same key should be merged:
PrimitiveLoadedParameter
: value with highest precedence replaces the existing one.SequenceLoadedParameter
: extends with no duplication, keeping precedence.MapLoadedParameter
: cascading updates, highest precedence kept.ObjectLoadedParameter
: same asMap
.
After all of this, the LoadedParameter
objects are typified: this is when type validation is
performed. If everything goes well, you obtain your values just fine. If not, the validation errors
are raised.
Take into account that the result is cached for faster subsequent access. This means that even
if you change the value of the environment variables responsible for a given setting, this won’t be
reflected in the context
object until you refresh it with conda.base.context.reset_context()
.
Do not modify the Context object!
ParameterLoader
does not implement the __set__
method of the property
protocol, so you
can freely override an attribute defined in a Configuration
subclass. You might think that
this will redefine the value after passing through the validation machinery, but that’s not true.
You will simply overwrite it entirely with the raw value and that’s probably not what you want.
Instead, consider the context
object immutable. If you need to change a setting at runtime, it is
probably A Bad Idea. The only situation where this is acceptable is during testing.
Setting values in the different origins#
There’s some magic behind the possible origins for the settings values. How these are tied to the
final Configuration
object might not be obvious at first. This is different for each
RawParameter
subclass:
DefaultValueRawParameter
: Users will never see this one. It only wraps the default value passed to theParameterLoader
class. Safe to ignore.YamlRawParameter
: This one takes a YAML file and parses it as a dictionary. The keys in this file must match the attribute names in theConfiguration
class exactly (or one of their aliases). Matching happens automatically once this is properly set up. How the values are parsed depends on the YAML Loader, set internally byconda
.EnvRawParameter
: Values coming from certain environment variables can make it to theConfiguration
instance, provided they are formatted as<APP_NAME>_<PARAMETER_NAME>
, all uppercase. The app name is defined by theConfiguration
subclass. The parameter name is defined by the attribute name in the class, transformed to upper case. For example,context.ignore_pinned
can be set withCONDA_IGNORE_PINNED
. The value of the variable is parsed in different ways depending on the type:PrimitiveParameter
is the easy one. The environment variable string is parsed as the expected type. Booleans are a bit different since several strings are recognized as such, and in a case-insensitive way:True
can be set withtrue
,yes
,on
andy
.False
can be set withfalse
,off
,n
,no
,non
,none
and""
(empty string).
SequenceParameter
can specify their own delimiter (e.g.,
), so the environment variable string is processed into a list.MapParameter
andObjectParameter
do not support being set with environment variables.
ArgParseRawParameter
: These are a bit different because there is no automated mechanism that ties a given command line flag to the context object. This means that if you add a new setting to theContext
class and you want that available in the CLI as a command line flag, you have to add it yourself. If that’s the case, refer toconda.cli.conda_argparse
and make sure that thedest
value of yourargparse.Argument
matches the attribute name inContext
. This way,Configuration.__init__
can take theargparse.Namespace
object, turn it into a dictionary, and make it pass through the loading machinery.