(deep_dive_config_management)=
# Programmatic `.condarc` File API
This guide explains how to programmatically read and write conda configuration files (`.condarc`)
using the `ConfigurationFile` class. This is useful for tools that need to modify conda settings
without shelling out to `conda config` commands.
## Overview
The `conda.cli.condarc.ConfigurationFile` class provides a high-level interface for reading,
modifying, and writing conda configuration files (`.condarc`). It handles:
- Configuration file validation
- Type checking and conversion
- Parameter existence checking
- Atomic file operations via context manager
- Support for sequence, map, and primitive parameters
## Basic Usage
### Reading Configuration
```python
from conda.cli.condarc import ConfigurationFile
# Read user's .condarc file
config = ConfigurationFile.from_user_condarc()
# Access the configuration content
print(config.content)
# {'channels': ['defaults', 'conda-forge'], 'auto_update_conda': False}
# Get a specific key
key, value = config.get_key("channels")
print(f"{key}: {value}")
# channels: ['defaults', 'conda-forge']
```
### Writing Configuration
```python
from conda.cli.condarc import ConfigurationFile
# Create a configuration file instance
config = ConfigurationFile.from_user_condarc()
# Set a primitive parameter
config.set_key("auto_update_conda", False)
# Add to a sequence parameter
config.add("channels", "conda-forge", prepend=True)
# Set a map parameter
config.set_key("proxy_servers.http", "http://proxy.example.com")
# Write changes to file
config.write()
```
### Using Context Manager
For atomic operations, use the context manager pattern:
```python
from conda.cli.condarc import ConfigurationFile
# Changes are automatically written on successful exit
with ConfigurationFile.from_user_condarc() as config:
config.set_key("channels", ["conda-forge", "defaults"])
config.set_key("auto_update_conda", False)
# File is written here automatically
```
If an exception occurs within the context, changes are not written:
```python
try:
with ConfigurationFile.from_user_condarc() as config:
config.set_key("channels", ["conda-forge"])
raise ValueError("Something went wrong")
except ValueError:
pass
# File was NOT modified because of the exception
```
## Factory Methods
The `ConfigurationFile` class provides several factory methods for common configuration file locations:
### User Configuration
```python
from conda.cli.condarc import ConfigurationFile
# User's .condarc file (typically ~/.condarc)
config = ConfigurationFile.from_user_condarc()
```
### System Configuration
```python
from conda.cli.condarc import ConfigurationFile
# System-wide .condarc file
config = ConfigurationFile.from_system_condarc()
```
### Environment Configuration
```python
from conda.cli.condarc import ConfigurationFile
# Environment-specific .condarc file at {prefix}/.condarc
config = ConfigurationFile.from_env_condarc(prefix="/path/to/env")
# Or use CONDA_PREFIX environment variable
config = ConfigurationFile.from_env_condarc()
```
### Custom Path
```python
from pathlib import Path
from conda.cli.condarc import ConfigurationFile
# Custom configuration file path
config = ConfigurationFile(path=Path("/custom/path/.condarc"))
```
## Parameter Types
Conda configuration parameters come in three types, and each supports different operations:
### Primitive Parameters
Single scalar values (strings, numbers, booleans):
```python
config.set_key("auto_update_conda", False)
config.set_key("channel_priority", "strict")
config.set_key("rollback_enabled", True)
```
### Sequence Parameters
Lists of values:
```python
# Add to end of list
config.add("channels", "conda-forge", prepend=False)
# Add to beginning of list
config.add("channels", "defaults", prepend=True)
# Remove from list
config.remove_item("channels", "conda-forge")
```
### Map Parameters
Dictionaries with nested values:
```python
# Set a map entry
config.set_key("proxy_servers.http", "http://proxy.example.com")
config.set_key("proxy_servers.https", "https://proxy.example.com")
# Add to a nested sequence within a map
config.add("conda_build.config_file", "/path/to/config.yaml")
```
## Key Validation
The `ConfigurationFile` class validates keys against the current conda context:
```python
from conda.cli.condarc import ConfigurationFile
config = ConfigurationFile.from_user_condarc()
# Check if a key exists
if config.key_exists("channels"):
print("channels is a valid parameter")
# Attempting to set an invalid key raises an error
try:
config.set_key("invalid_key", "value")
except Exception as e:
print(f"Error: {e}")
# Error: CondaKeyError: 'invalid_key': unknown parameter
```
## Handling Missing Keys
When getting a key that doesn't exist, the method returns a sentinel value:
```python
from conda.cli.condarc import ConfigurationFile, MISSING
config = ConfigurationFile(content={})
key, value = config.get_key("undefined_key")
if value is MISSING:
print(f"Key '{key}' not found in config")
```
## Working with Plugin Configuration
Plugin parameters use a `plugins.` prefix:
```python
# Set a plugin-specific parameter
config.set_key("plugins.custom_solver.enabled", True)
# Add to a plugin sequence parameter
config.add("plugins.custom_reporters.backends", "custom_backend")
```
## Advanced Usage
### Custom Context
You can provide a custom context instance for testing or specialized configurations:
```python
from conda.base.context import Context
from conda.cli.condarc import ConfigurationFile
# Create a custom context
custom_context = Context()
# Use it with ConfigurationFile
config = ConfigurationFile(path="/path/to/config", context=custom_context)
```
### Warning Handlers
Customize how warnings are reported:
```python
warnings = []
def collect_warnings(msg):
warnings.append(msg)
config = ConfigurationFile(path="/path/to/config", warning_handler=collect_warnings)
config.add("channels", "defaults", prepend=False)
# If "defaults" already exists, a warning is collected
print(warnings)
```
### Manual Read/Write Control
For more control over when files are read or written:
```python
from conda.cli.condarc import ConfigurationFile
config = ConfigurationFile(path="/path/to/.condarc")
# Explicitly read
config.read()
# Make changes
config.content["channels"] = ["conda-forge"]
# Explicitly write
config.write()
# Or write to a different location
config.write(path="/different/path/.condarc")
```
### Working with Content Directly
The `content` property provides direct access to the underlying configuration dictionary:
```python
config = ConfigurationFile.from_user_condarc()
# Access content
current_channels = config.content.get("channels", [])
# Modify content directly (use with caution)
config.content["channels"] = ["conda-forge", "defaults"]
config.write()
```
**Note**: Direct content manipulation bypasses validation. Use the provided methods
(`set_key`, `add`, `remove_item`, etc.) for safer operations.
## Complete Example
Here's a complete example that demonstrates multiple operations:
```python
from conda.cli.condarc import ConfigurationFile
# Use context manager for atomic operations
with ConfigurationFile.from_user_condarc() as config:
# Set primitive parameters
config.set_key("auto_update_conda", False)
config.set_key("channel_priority", "strict")
# Configure channels
config.set_key("channels", []) # Clear existing
config.add("channels", "conda-forge", prepend=False)
config.add("channels", "defaults", prepend=False)
# Set proxy servers
config.set_key("proxy_servers.http", "http://proxy.example.com:8080")
config.set_key("proxy_servers.https", "https://proxy.example.com:8080")
# Configure conda-build
config.add("conda_build.config_file", "/path/to/conda_build_config.yaml")
# Print current configuration
print("Current configuration:")
for key, value in config.content.items():
print(f" {key}: {value}")
# File is automatically written when exiting the context manager
print("Configuration saved!")
```
## Migration Guide
If you were previously using the private functions from `conda.cli.main_config`, here's how to migrate:
### Before (deprecated)
```python
from conda.cli.main_config import (
_read_rc,
_write_rc,
_set_key,
_get_key,
_remove_key,
)
# Old way
rc_config = _read_rc("/path/to/.condarc")
_set_key("auto_update_conda", False, rc_config)
_write_rc("/path/to/.condarc", rc_config)
```
### After (recommended)
```python
from conda.cli.condarc import ConfigurationFile
# New way
config = ConfigurationFile(path="/path/to/.condarc")
config.set_key("auto_update_conda", False)
config.write()
# Or even simpler with context manager
with ConfigurationFile(path="/path/to/.condarc") as config:
config.set_key("auto_update_conda", False)
```
## Error Handling
The `ConfigurationFile` class raises specific exceptions for different error conditions:
```python
from conda.cli.condarc import ConfigurationFile
from conda.exceptions import CondaKeyError, CondaValueError, CouldntParseError
config = ConfigurationFile.from_user_condarc()
try:
# Unknown parameter
config.set_key("unknown_param", "value")
except CondaKeyError as e:
print(f"Invalid key: {e}")
try:
# Invalid operation for parameter type
config.set_key("channels", "not-a-list")
except CondaKeyError as e:
print(f"Invalid operation: {e}")
try:
# Adding to a non-sequence parameter
config.add("auto_update_conda", "value")
except CondaValueError as e:
print(f"Type error: {e}")
```
## Thread Safety
The `ConfigurationFile` class is not thread-safe. If you need to access configuration from multiple
threads, consider using locks or creating separate instances for each thread.
## Best Practices
1. **Use context managers**: For atomic operations, always use the context manager pattern to ensure
changes are only written on success.
2. **Validate keys**: Use `key_exists()` to check parameter validity before attempting operations.
3. **Use factory methods**: Prefer `from_user_condarc()`, `from_system_condarc()`, and
`from_env_condarc()` over manual path construction.
4. **Handle missing keys**: Always check for `MISSING` when using `get_key()` to handle undefined
parameters gracefully.
5. **Avoid direct content manipulation**: Use the provided methods (`set_key`, `add`, etc.) instead
of modifying `content` directly to ensure proper validation.
6. **Custom warning handlers**: For library code, provide custom warning handlers to integrate with
your logging system.
## See Also
- {ref}`deep_dive_context` - Understanding the conda context object
- {doc}`/configuration` - User-facing configuration documentation
- {doc}`/dev-guide/api/conda/cli/index` - CLI API documentation