This document is for developers, detailing how to extend and modify the DDNS configuration system.
The DDNS configuration system uses a layered architecture supporting multiple configuration sources:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Configuration Sources (Priority) β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β CLI Args > JSON File > Env Vars > Defaults β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β Config Class β
β Unified Configuration Interface β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β Providers and App Modules β
βββββββββββββββββββββββββββββββββββββββββββββββ
Core Modules:
ddns/config/cli.py
- Command line argument parsingddns/config/json.py
- JSON configuration file handlingddns/config/env.py
- Environment variable parsingddns/config/config.py
- Config class implementationThe Config class is the core of the configuration system, responsible for merging multiple configuration sources:
class Config(object):
def __init__(self, cli_config=None, json_config=None, env_config=None):
self._cli_config = cli_config or {}
self._json_config = json_config or {}
self._env_config = env_config or {}
# Configuration attribute initialization
self.dns = self._get("dns", "debug")
self.endpoint = self._get("endpoint") # New endpoint configuration
def _get(self, key, default=None):
"""Get configuration value by priority: CLI > JSON > ENV > default"""
return (
self._cli_config.get(key) or
self._json_config.get(key) or
self._env_config.get(key) or
default
)
First, add the new parameter to the JSON schema files:
// schema/v4.0.json
{
"properties": {
"new_param": {
"type": "string",
"description": "Description of new parameter"
}
}
}
Add command line parameter support in ddns/config/cli.py
:
def load_config(description, doc, version, date):
parser = ArgumentParser(...)
# Add new parameter
parser.add_argument(
"--new-param",
dest="new_param",
help="Help text for new parameter"
)
Add environment variable parsing in ddns/config/env.py
:
def load_config():
config = {}
# Add new parameter
config["new_param"] = os.getenv("DDNS_NEW_PARAM")
return config
Add the new attribute to the Config class in ddns/config/config.py
:
class Config(object):
def __init__(self, ...):
# ... existing code ...
self.new_param = self._get("new_param")
Update the relevant documentation files:
doc/config/cli.en.md
- Add CLI parameter documentationdoc/config/env.en.md
- Add environment variable documentationdoc/config/json.en.md
- Add JSON configuration documentationThe configuration system provides several utility functions for type conversion:
from ddns.config.cli import str_bool
# Boolean conversion
debug = str_bool(config.get("debug", False))
# List conversion
domains = config.get("ipv4", [])
if isinstance(domains, str):
domains = [d.strip() for d in domains.split(",") if d.strip()]
Add validation logic in the Config class constructor:
class Config(object):
def __init__(self, ...):
# ... existing code ...
self.new_param = self._get("new_param")
# Validation
if self.new_param and not self._validate_new_param():
raise ValueError("Invalid new_param value")
def _validate_new_param(self):
"""Validate new parameter value"""
return True # Implement validation logic
The complete configuration loading process:
def load_all_config():
# 1. Load command line arguments
cli_config = cli.load_config(...)
# 2. Load JSON configuration file
json_config = file.load_config(cli_config.get("config"))
# 3. Load environment variables
env_config = env.load_config()
# 4. Create Config instance
config = Config(cli_config, json_config, env_config)
return config
Create comprehensive tests for each configuration module:
# tests/test_config_new_param.py
import unittest
from ddns.config.config import Config
class TestConfigNewParam(unittest.TestCase):
def test_new_param_from_cli(self):
cli_config = {"new_param": "cli_value"}
config = Config(cli_config=cli_config)
self.assertEqual(config.new_param, "cli_value")
def test_new_param_priority(self):
cli_config = {"new_param": "cli_value"}
json_config = {"new_param": "json_value"}
env_config = {"new_param": "env_value"}
config = Config(cli_config, json_config, env_config)
self.assertEqual(config.new_param, "cli_value") # CLI has highest priority
Test the complete configuration loading process:
def test_full_config_loading():
# Test with actual configuration files and environment variables
import os
os.environ["DDNS_NEW_PARAM"] = "test_value"
config = load_all_config()
assert config.new_param == "test_value"
When adding new parameters, ensure backward compatibility:
# Support both old and new parameter names
self.new_param = self._get("new_param") or self._get("old_param")
Provide sensible defaults for all parameters:
self.new_param = self._get("new_param", "default_value")
Use appropriate type conversion and validation:
# Convert string to integer with validation
try:
self.timeout = int(self._get("timeout", 30))
except ValueError:
raise ValueError("Invalid timeout value")
Always update documentation when adding new parameters:
The DDNS configuration system uses JSON Schema for validation:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"dns": {
"type": "string",
"enum": ["dnspod", "cloudflare", "alidns", ...]
}
},
"required": ["dns"]
}
Add validation for new parameters:
{
"properties": {
"new_param": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$",
"minLength": 1,
"maxLength": 100,
"description": "New parameter description"
}
}
}
Implement proper error handling for configuration issues:
class ConfigError(Exception):
"""Configuration-related errors"""
pass
class Config(object):
def __init__(self, ...):
try:
self._validate_config()
except Exception as e:
raise ConfigError("Configuration validation failed: {}".format(e))
def _validate_config(self):
"""Validate complete configuration"""
if not self.dns:
raise ValueError("DNS provider is required")
Provide clear error messages for common configuration mistakes:
def _validate_dns_provider(self):
valid_providers = ["dnspod", "cloudflare", "alidns", ...]
if self.dns not in valid_providers:
raise ValueError(
"Invalid DNS provider '{}'. Valid options: {}".format(
self.dns, ", ".join(valid_providers)
)
)
Implement lazy loading for expensive configuration operations:
class Config(object):
def __init__(self, ...):
self._domains_cache = None
@property
def domains(self):
if self._domains_cache is None:
self._domains_cache = self._parse_domains()
return self._domains_cache
Cache parsed configuration to avoid repeated processing:
import functools
@functools.lru_cache(maxsize=1)
def load_json_config(config_file):
"""Load and cache JSON configuration"""
return json.load(open(config_file))
Add debug output for configuration troubleshooting:
def debug_config(self):
"""Print configuration debug information"""
print("Configuration Sources:")
print(" CLI: {}".format(self._cli_config))
print(" JSON: {}".format(self._json_config))
print(" ENV: {}".format(self._env_config))
print("Final Configuration:")
for attr in dir(self):
if not attr.startswith("_"):
print(" {}: {}".format(attr, getattr(self, attr)))
Add validation checks that can be enabled in debug mode:
def validate_all(self):
"""Comprehensive configuration validation"""
errors = []
if not self.dns:
errors.append("DNS provider not specified")
if not self.token:
errors.append("Authentication token not provided")
if errors:
raise ConfigError("Configuration errors: {}".format("; ".join(errors)))
The DDNS configuration system is designed to be:
When extending the configuration system, always follow these principles and update all relevant documentation and tests.