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
Contextclass. 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
Configurationsubclass parses the raw values of the possible origins and stores them as the relevantRawParameterobjects, 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
Configurationattribute is aParameterLoader, which implements thepropertyprotocol via__get__. This means that, upon attribute access (e.g.MyConfiguration.string_field), theParameterLoadercan execute the loading logic. This means finding potential type matches in the raw data, loading them asLoadedParameterobjects 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,boolorNoneType.SequenceParameter: holds an iterable (list) of otherParameterobjects.MapParameter: holds a mapping (dict) of otherParameterobjects.ObjectParameter: holds an object with attributes set toParameterobjects.
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 theParameterLoaderclass. 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 theConfigurationclass 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 theConfigurationinstance, provided they are formatted as<APP_NAME>_<PARAMETER_NAME>, all uppercase. The app name is defined by theConfigurationsubclass. The parameter name is defined by the attribute name in the class, transformed to upper case. For example,context.ignore_pinnedcan be set withCONDA_IGNORE_PINNED. The value of the variable is parsed in different ways depending on the type:PrimitiveParameteris 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:Truecan be set withtrue,yes,onandy.Falsecan be set withfalse,off,n,no,non,noneand""(empty string).
SequenceParametercan specify their own delimiter (e.g.,), so the environment variable string is processed into a list.MapParameterandObjectParameterdo 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 theContextclass 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_argparseand make sure that thedestvalue of yourargparse.Argumentmatches the attribute name inContext. This way,Configuration.__init__can take theargparse.Namespaceobject, turn it into a dictionary, and make it pass through the loading machinery.