Configuration package for RegressionLab.
The config package centralizes all application configuration, constants, and settings. It is split into submodules; the main package re-exports everything so that from config import PLOT_CONFIG, from config import __version__, etc. continue to work.
Package structure:
config/color_utils.py– Pure hex color utilities (no config/env dependencies):lighten_hex,muted_from_hex. Used by Streamlit theme.config/env.py– Environment variables,.envloading, validation,get_env,get_env_from_schema,get_current_env_values,write_env_file,initialize_and_validate_config,ENV_SCHEMA,DEFAULT_LOG_FILE,DEFAULT_LOG_LEVEL,DONATIONS_URLconfig/theme.py–UI_STYLE,PLOT_CONFIG,FONT_CONFIG,SPINBOX_STYLE,setup_fonts,get_entry_font,configure_ttk_styles,apply_hover_to_childrenconfig/paths.py–FILE_CONFIG,get_project_root,ensure_output_directory,get_output_pathconfig/constants.py–__version__,EQUATIONS,AVAILABLE_EQUATION_TYPES,EXIT_SIGNAL,MATH_FUNCTION_REPLACEMENTS_COMPILED,SUPPORTED_LANGUAGE_CODES,LANGUAGE_ALIASES,DEFAULT_LANGUAGE,DATA_FILE_TYPESconfig/equations.yaml– Single source of truth for equation definitions (function name, formula, format, param_names). Loaded byconstants.pyintoEQUATIONS.
Usage remains the same: import from config (e.g. from config import PLOT_CONFIG, get_project_root, from config import lighten_hex, get_env_from_schema).
__version__ = "1.1.2"
__author__ = "Alejandro Mata Ali"
__email__ = "alejandro.mata.ali@gmail.com"Equations are defined in config/equations.yaml. Each entry has:
function: Name of the fitting function (e.g.fit_linear_function_with_n)formula: Display formula for the UI (e.g."y = mx + n")format: Template with{param}placeholders for the fitted equation string (e.g."y={m}x+{n}")param_names: List of parameter names for the fit
constants.py loads this file into the EQUATIONS dictionary. The keys of EQUATIONS are the equation IDs; AVAILABLE_EQUATION_TYPES is an immutable tuple of those keys in the same order as the YAML file.
# EQUATIONS structure (from equations.yaml)
EQUATIONS = {
'linear_function_with_n': {
'function': 'fit_linear_function_with_n',
'formula': 'y = mx + n',
'format': 'y={m}x+{n}',
'param_names': ['n', 'm'],
},
'linear_function': { 'function': 'fit_linear_function', 'formula': 'y = mx', 'format': 'y={m}x', 'param_names': ['m'] },
# ...
}
AVAILABLE_EQUATION_TYPES = tuple(EQUATIONS.keys()) # Same order as in YAML; immutableThis defines which equations appear in the UI and how they are invoked.
The module provides configuration dictionaries that are loaded from environment variables:
Dictionary containing plot configuration settings:
PLOT_CONFIG = {
'figsize': (12, 6), # Figure size (width, height) in inches
'dpi': 100, # Resolution
'line_color': 'black', # Fitted curve color
'line_width': 1.0, # Fitted curve width
'line_style': '-', # Line style ('-', '--', '-.', ':')
'marker_format': 'o', # Marker style ('o', 's', '^', 'd')
'marker_size': 5, # Marker size
'error_color': 'crimson', # Error bar color
'marker_face_color': 'crimson', # Marker fill color
'marker_edge_color': 'crimson', # Marker edge color
'show_title': False, # Show plot title
'show_grid': False # Show background grid on plot
}Dictionary containing UI theme configuration for Tkinter:
UI_STYLE = {
'background': 'midnight blue',
'foreground': 'snow',
'button_fg': 'lime green',
'button_fg_cancel': 'red2',
'active_bg': 'navy',
'active_fg': 'snow',
'border_width': 8,
'relief': 'ridge',
'padding_x': 8,
'padding_y': 8,
'button_width': 12,
'button_width_wide': 28,
'font_size': 16,
'font_size_large': 20,
'font_family': 'Menlo',
'spinbox_width': 10,
'entry_width': 25
}Dictionary containing font configuration for plots:
FONT_CONFIG = {
'family': 'serif',
'title_size': 'xx-large',
'title_weight': 'semibold',
'axis_size': 30,
'axis_style': 'italic',
'tick_size': 16,
'param_font': ('Courier', 10)
}Get environment variable with type casting, validation, and default value.
def get_env(
key: str,
default: Any,
cast_type: Type[Union[str, int, float, bool]] = str
) -> Union[str, int, float, bool]:
"""
Get environment variable with type casting, validation, and default value.
This function validates the value according to ENV_SCHEMA rules. If validation
fails, the default value is returned.
Args:
key: Environment variable name
default: Default value if variable not found or invalid
cast_type: Type to cast the value to (str, int, float, bool)
Returns:
The environment variable value cast to the specified type, validated,
or default if invalid or missing
"""Get environment variable using ENV_SCHEMA: default and cast_type come from the schema.
def get_env_from_schema(key: str) -> Any:
"""
Get environment variable using ENV_SCHEMA: default and cast_type come from
the schema. Use this when the key is defined in ENV_SCHEMA to avoid
duplicating defaults.
Args:
key: Environment variable name (must exist in ENV_SCHEMA)
Returns:
The validated value from get_env(key, default, cast_type)
Raises:
KeyError: If key is not in ENV_SCHEMA
"""Collect current environment values for all keys defined in ENV_SCHEMA.
def get_current_env_values() -> dict[str, str]:
"""
Collect current environment values for all keys defined in ENV_SCHEMA.
Values are read using get_env so casting, defaults and boolean
handling are applied consistently. Booleans are converted to the strings
"true" or "false" so they can be written back to .env files.
Returns:
Dictionary mapping environment keys to their string representation
"""Write a .env file with the given key=value pairs.
def write_env_file(env_path: Path, values: dict[str, str]) -> None:
"""
Write a .env file with the given key=value pairs.
Only keys present in ENV_SCHEMA are written, and values are quoted
when they contain spaces, # or line breaks.
Args:
env_path: Destination path for the .env file
values: Mapping from environment keys to their desired string values
"""Initialize configuration and validate all environment values. Should be called at application startup.
def initialize_and_validate_config() -> None:
"""
Initialize configuration and validate all environment values.
This function should be called at application startup to ensure all
configuration values are valid. Invalid values are automatically corrected
to their defaults, and warnings are logged if any corrections were made.
"""Setup and return font properties for plots.
def setup_fonts() -> Tuple[FontProperties, FontProperties]:
"""
Setup and return font properties for plots.
Uses caching to avoid recreating fonts on every call.
Returns:
Tuple of (title_font, axis_font) FontProperties objects
"""Get font tuple for ttk Entry and Combobox (unified with UI base font).
def get_entry_font() -> tuple[str, int]:
"""
Font tuple for ttk Entry and Combobox (unified with UI base font).
Returns:
Tuple of (font_family, font_size)
"""Configure ttk styles from the unified UI_STYLE. Call once after creating the Tk root.
def configure_ttk_styles(root: Any) -> None:
"""
Configure ttk styles from the unified UI_STYLE. Call once after creating
the Tk root. Uses 'clam' theme for consistent field colors.
Args:
root: Tkinter root window
"""Apply hover effects to all children widgets of a parent widget.
def apply_hover_to_children(parent: Any) -> None:
"""
Apply hover effects to all children widgets of a parent widget.
Args:
parent: Parent Tkinter widget
"""Get the project root directory.
def get_project_root() -> Path:
"""
Get the project root directory.
Returns:
Path to the project root (parent of src/)
"""Create output directory if it doesn't exist.
def ensure_output_directory(output_dir: str = None) -> str:
"""
Create output directory if it doesn't exist.
Args:
output_dir: Optional directory path. If None, uses FILE_CONFIG['output_dir']
Returns:
The output directory path (absolute path from project root)
Raises:
OSError: If directory cannot be created
"""Get the full output path for a plot.
def get_output_path(fit_name: str, output_dir: str = None) -> str:
"""
Get the full output path for a plot.
Args:
fit_name: Name of the fit/adjustment (used in filename)
output_dir: Optional directory path. If None, uses FILE_CONFIG['output_dir']
Returns:
Full path to the output file
"""Configuration is loaded from .env file using python-dotenv.
# Language
LANGUAGE="es"
# Plot settings
PLOT_FIGSIZE_WIDTH=12
PLOT_FIGSIZE_HEIGHT=6
DPI=100
PLOT_LINE_COLOR="black"
# UI settings (Tkinter)
UI_BACKGROUND="midnight blue"
UI_FOREGROUND="snow"
UI_FONT_SIZE=16
# File paths
FILE_INPUT_DIR="input"
FILE_OUTPUT_DIR="output"
# Logging
LOG_LEVEL=INFO
LOG_FILE=regressionlab.logSee Configuration Guide for complete documentation.
EXIT_SIGNAL = "Exit" # Returned when user cancels operationFILE_CONFIG = {
'input_dir': 'input', # Input directory for data files
'output_dir': 'output', # Output directory for plots
'filename_template': 'fit_{}', # Filename template ({} replaced by fit name)
'plot_format': 'png' # Output plot format (png, jpg, or pdf)
}Values are read from .env (FILE_INPUT_DIR, FILE_OUTPUT_DIR, FILE_FILENAME_TEMPLATE, FILE_PLOT_FORMAT). The Tkinter Configure dialog edits these and all other keys defined in ENV_SCHEMA (see Configuration Guide); optional keys such as DONATIONS_URL, CHECK_UPDATES, CHECK_UPDATES_FORCE, UPDATE_CHECK_URL, and UI theme/text preview options are also in the schema.
Equation-to-function mapping is provided by the function field of each entry in EQUATIONS (loaded from equations.yaml). The workflow controller and UI use EQUATIONS[eq_id]['function'] to resolve the fitting function name.
from config import (
PLOT_CONFIG, UI_STYLE, FONT_CONFIG, __version__, get_project_root,
get_current_env_values
)
# Application version
print(f"RegressionLab v{__version__}")
# Plot configuration (dictionary, not function)
print(f"Figure size: {PLOT_CONFIG['figsize']}")
print(f"DPI: {PLOT_CONFIG['dpi']}")
# UI theme configuration
print(f"Background: {UI_STYLE['background']}")
print(f"Font size: {UI_STYLE['font_size']}")
# Font configuration
print(f"Font family: {FONT_CONFIG['family']}")
# Project root
root = get_project_root()
print(f"Project root: {root}")
# Get all current environment values
env_values = get_current_env_values()
print(f"Current language: {env_values['LANGUAGE']}")from config import AVAILABLE_EQUATION_TYPES, EQUATIONS
# List all equations
print(f"Available equations: {len(AVAILABLE_EQUATION_TYPES)}")
for eq_id in AVAILABLE_EQUATION_TYPES:
info = EQUATIONS[eq_id]
print(f" - {eq_id}: {info['formula']} -> {info['function']}")
# Check if equation exists
if 'linear_function' in AVAILABLE_EQUATION_TYPES:
print("Linear fitting available")To add a new equation to the system:
- Implement the mathematical and fitting functions in the appropriate module under
fitting/functions/(e.g.special.py,polynomials.py). - Add an entry to
config/equations.yamlwithfunction,formula,format, andparam_names. The key is the equation ID (e.g.my_equation). - Add translations for the equation ID in
src/locales/en.json,src/locales/es.json, andsrc/locales/de.jsonunder theequationskey.
-
Copy template: Start with
.env.examplecp .env.example .env
-
Modify gradually: Change one setting at a time
-
Restart app: Changes require restart
-
Keep backup: Save working configuration
-
Add defaults: Always provide fallback values
line_width = float(os.getenv('PLOT_LINE_WIDTH', 1.0))
-
Validate input: Check types and ranges
dpi = int(os.getenv('DPI', 100)) if dpi < 50 or dpi > 1200: dpi = 100 # Reset to default
-
Document new settings: Update
.env.exampleand docs -
Type hints: Use proper types in getters
def get_dpi() -> int: return int(os.getenv('DPI', 100))
- Environment variables loaded from
.env(seeconfig/env.py) - Defaults applied for missing values
- Validation performed
- Configuration exposed via package; values are effectively immutable during runtime
Configuration is loaded once at startup. Changes to .env during runtime are not reflected until restart.
- Configuration loaded once per module.
- Values cached in memory.
- No disk I/O after initial load.
- Theme color conversion (named colors to RGB/hex) is cached via
lru_cacheso repeated lookups (e.g. same color for multiple buttons) avoid redundant Tk/matplotlib calls.
For complete configuration options, see Configuration Guide.