standard_config.py
Standard Config. A module to load tooling config from a users project space.
Inspired from frustrations that some tools have a tool.ini, .tool.ini,
setup.cfg, or pyproject.toml. Some allow for global configs, some don't. Some
properly follow the users home directory, others end up in a weird temp
directory. Windows home directory is only more confusing. Some will even
respect the users $XDG_HOME
directory.
This file is for any project that can be configured in plain text such as ini
or toml
and not requiring a .py file. Just name your tool and let users put
config where it makes sense to them, no need to figure out resolution order.
Usage:
from standard_config import load # Retrieve any overrides from the user overrides = {'setting': True} config = load('my_tool', overrides)
Resolution Order
- First global file with a tool key
- First local file with a tool key
- Environment variables prefixed with
TOOL
- Overrides
Tool Specific Ini files
Ini file formats must include a <tool>
key.
[my_tool] setting = True
pyproject.toml
Toml files must include a tool.<tool>
key
[tool.my_tool] setting = True
setup.cfg
setup.cfg files must include a tool:<tool>
key
[tool:my_tool] setting = True
global files to consider
/tool.ini /.tool /.tool.ini /.config/tool.ini /.config/.tool /.config/.tool.ini
local files to consider
- <project_home>/tool.ini
- <project_home>/.tool
- <project_home>/.tool.ini
- <project_home>/pyproject.toml
- <project_home>/setup.cfg
Markata's standard configuration system.
Configuration Overview
Markata uses a hierarchical configuration system based on Pydantic models. Configuration can be set through:
- TOML files
- Environment variables
- Command line arguments
Basic Configuration
Minimal markata.toml
:
[markata] # Site info title = "My Site" url = "https://example.com" description = "Site description" # Content locations content_dir = "content" output_dir = "markout" assets_dir = "static" # Plugin management hooks = ["default"]
Environment Variables
All settings can be overridden with environment variables:
# Override site URL export MARKATA_URL="https://staging.example.com" # Override output directory export MARKATA_OUTPUT_DIR="dist" # Enable debug mode export MARKATA_DEBUG=1
Detailed Configuration
Core Settings
[markata] # Site information title = "My Site" # Site title url = "https://example.com" # Base URL description = "Site description" # Meta description author_name = "Author Name" # Author name author_email = "[email protected]" # Author email icon = "favicon.ico" # Site icon lang = "en" # Site language # Content locations content_dir = "content" # Source content location output_dir = "markout" # Build output location assets_dir = "static" # Static assets location template_dir = "templates" # Template location # Plugin management hooks = ["default"] # Active plugins disabled_hooks = [] # Disabled plugins
Cache Settings
[markata] # Cache configuration default_cache_expire = 3600 # Default TTL (1 hour) template_cache_expire = 86400 # Template TTL (24 hours) markdown_cache_expire = 21600 # Markdown TTL (6 hours) dynamic_cache_expire = 3600 # Dynamic TTL (1 hour)
Development Settings
[markata] # Development server dev_server_port = 8000 # Local server port dev_server_host = "localhost" # Local server host debug = false # Debug mode # Performance parallel = true # Enable parallel processing workers = 4 # Number of worker threads
Content Settings
[markata] # Content processing default_template = "post.html" # Default template markdown_extensions = [ # Markdown extensions "fenced_code", "tables", "footnotes" ] # Content filtering draft = false # Include drafts future = false # Include future posts
Plugin Configuration
Each plugin can define its own configuration section:
# RSS feed configuration [markata.feeds] rss = { output = "rss.xml" } atom = { output = "atom.xml" } json = { output = "feed.json" } # Template configuration [markata.template] engine = "jinja2" cache_size = 100 autoescape = true # Markdown configuration [markata.markdown] highlight_theme = "monokai" line_numbers = true
Configuration Validation
The configuration is validated using Pydantic models:
from pydantic import BaseModel, Field class MarkataConfig(BaseModel): """Core configuration model.""" # Site info title: str = Field(..., description="Site title") url: str = Field(..., description="Site base URL") # Directories content_dir: Path = Field("content", description="Content directory") output_dir: Path = Field("markout", description="Output directory") # Features debug: bool = Field(False, description="Enable debug mode") parallel: bool = Field(True, description="Enable parallel processing") model_config = ConfigDict( validate_assignment=True, arbitrary_types_allowed=True, extra="allow", str_strip_whitespace=True, validate_default=True, populate_by_name=True, )
Usage Example
from markata import Markata # Load config from file markata = Markata.from_file("markata.toml") # Access configuration print(markata.config.title) # Site title print(markata.config.url) # Site URL print(markata.config.content_dir) # Content directory # Access plugin config print(markata.config.feeds.rss) # RSS feed config print(markata.config.template) # Template config # Override config markata.config.debug = True markata.config.parallel = False
See hookspec.py for plugin development and lifecycle.py for build process details.
Function
_get_global_path_specs function
Generate a list of standard pathspecs for global config files.
Args: tool (str): name of the tool to configure
_get_global_path_specs source
def _get_global_path_specs(tool: str) -> path_spec_type: """ Generate a list of standard pathspecs for global config files. Args: tool (str): name of the tool to configure """ try: home = Path(os.environ["XDG_HOME"]) except KeyError: home = Path.home() return [ {"path_specs": home / f"{tool}.ini", "parser": "ini", "keys": [tool]}, {"path_specs": home / f".{tool}", "parser": "ini", "keys": [tool]}, {"path_specs": home / f".{tool}.ini", "parser": "ini", "keys": [tool]}, { "path_specs": home / ".config" / f"{tool}.ini", "parser": "ini", "keys": [tool], }, { "path_specs": home / ".config" / f".{tool}", "parser": "ini", "keys": [tool], }, { "path_specs": home / ".config" / f".{tool}.ini", "parser": "ini", "keys": [tool], }, ]
Function
_get_local_path_specs function
Generate a list of standard pathspecs for local, project directory config files.
Args: tool (str): name of the tool to configure
_get_local_path_specs source
def _get_local_path_specs(tool: str, project_home: Union[str, Path]) -> path_spec_type: """ Generate a list of standard pathspecs for local, project directory config files. Args: tool (str): name of the tool to configure """ return [ { "path_specs": Path(project_home) / f"{tool}.ini", "parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}", "parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.ini", "parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f"{tool}.yml", "parser": "yaml", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.yml", "parser": "yaml", "keys": [tool], }, { "path_specs": Path(project_home) / f"{tool}.toml", "parser": "toml", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.toml", "parser": "toml", "keys": [tool], }, { "path_specs": Path(project_home) / "pyproject.toml", "parser": "toml", "keys": ["tool", tool], }, { "path_specs": Path(project_home) / "setup.cfg", "parser": "ini", "keys": [f"tool.{tool}"], }, ]
Function
_get_attrs function
Get nested config data from a list of keys.
specifically written for pyproject.toml which needs to get tool
then <tool>
_get_attrs source
def _get_attrs(attrs: list, config: Dict) -> Dict: """Get nested config data from a list of keys. specifically written for pyproject.toml which needs to get `tool` then `<tool>` """ for attr in attrs: config = config[attr] return config
Function
_load_config_file function
Load a configuration file using the appropriate parser.
Args: file_spec: Dictionary containing path_specs, parser, and keys information
Returns: Optional[Dict[str, Any]]: Parsed configuration or None if file doesn't exist
_load_config_file source
def _load_config_file(file_spec: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Load a configuration file using the appropriate parser. Args: file_spec: Dictionary containing path_specs, parser, and keys information Returns: Optional[Dict[str, Any]]: Parsed configuration or None if file doesn't exist """ path = file_spec["path_specs"] if not path.exists(): return None try: if file_spec["parser"] == "toml": with open(path, "rb") as f: config = tomli.load(f) elif file_spec["parser"] == "yaml": with open(path, "r") as f: config = yaml.safe_load(f) elif file_spec["parser"] == "ini": config = configparser.ConfigParser() config.read(path) # Convert ConfigParser to dict config = {s: dict(config.items(s)) for s in config.sections()} else: return None return _get_attrs(file_spec["keys"], config) except ( KeyError, TypeError, yaml.YAMLError, tomli.TOMLDecodeError, configparser.Error, ): return None
Function
_load_files function
Load config files stopping at the first one that exists and can be parsed.
Args: config_path_specs: List of path specifications to try
Returns: Dict[str, Any]: Configuration dictionary
_load_files source
def _load_files(config_path_specs: path_spec_type) -> Dict[str, Any]: """Load config files stopping at the first one that exists and can be parsed. Args: config_path_specs: List of path specifications to try Returns: Dict[str, Any]: Configuration dictionary """ for file_spec in config_path_specs: config = _load_config_file(file_spec) if config: return config return {}
Function
_load_env function
Load config from environment variables.
Args: tool (str): name of the tool to configure
_load_env source
def _load_env(tool: str) -> Dict[str, Any]: """Load config from environment variables. Args: tool (str): name of the tool to configure """ env_prefix = tool.upper() env_config = { key.replace(f"{env_prefix}_", "").lower(): value for key, value in os.environ.items() if key.startswith(f"{env_prefix}_") } return env_config
Function
load function
Load tool config from standard config files.
Resolution Order
- First global file with a tool key
- First local file with a tool key
- Environment variables prefixed with
TOOL
- Overrides
Args: tool (str): name of the tool to configure project_home (Union[Path, str], optional): Project directory to search for config files. Defaults to ".". overrides (Dict, optional): Override values to apply last. Defaults to None.
Returns: Dict[str, Any]: Configuration object
load source
def load( tool: str, project_home: Union[Path, str] = ".", overrides: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Load tool config from standard config files. Resolution Order * First global file with a tool key * First local file with a tool key * Environment variables prefixed with `TOOL` * Overrides Args: tool (str): name of the tool to configure project_home (Union[Path, str], optional): Project directory to search for config files. Defaults to ".". overrides (Dict, optional): Override values to apply last. Defaults to None. Returns: Dict[str, Any]: Configuration object """ overrides = overrides or {} config = {} # Load from files in order of precedence config.update(_load_files(_get_global_path_specs(tool)) or {}) config.update(_load_files(_get_local_path_specs(tool, project_home)) or {}) config.update(_load_env(tool)) config.update(overrides) # If no settings class is provided, return the raw dict return config