Modulo Configurazione (structum.config)¶
Facade e Interfacce per il sistema di gestione della configurazione unificata.
Feature |
Dettagli |
|---|---|
Namespace |
|
Ruolo |
Configuration Facade |
Indice¶
1. Architettura e Pattern¶
1.1 Principio: Dependency Injection per la Configurazione¶
Structum implementa il Strategy Pattern per disaccoppiare la business logic dalla sorgente di configurazione:
┌─────────────────────────────────────────┐
│ Application Code │
│ config.get("database.host") │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ ConfigFacade (Singleton) │
│ • Risolve dot-notation (a.b.c) │
│ • Gestisce layer priority │
│ • Cache e validazione │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ ConfigProvider (Interface) │
│ ┌───────────┬──────────┬────────────┐ │
│ │ JSON │ Dynaconf │ Custom │ │
│ │ Provider │ Provider │ Provider │ │
│ └───────────┴──────────┴────────────┘ │
└─────────────────────────────────────────┘
1.2 Vantaggi Architetturali¶
Beneficio |
Descrizione |
|---|---|
Testabilità |
Mock del provider senza modificare application code |
Zero Refactoring |
Switch da file locali a Vault/Consul senza code changes |
Ambienti Multipli |
Provider diversi per dev/staging/production |
Type Safety |
Validazione opzionale con Pydantic (provider-specific) |
1.3 Layer di Configurazione¶
I valori vengono risolti con precedenza (dal più alto al più basso):
Runtime Layer →
config.set()chiamato dall’applicazioneEnvironment Layer → Variabili d’ambiente (
STRUCTUM_*)User Layer → File persistente utente (
~/.structum/config.json)Application Layer → File di progetto (
config/settings.toml)Defaults Layer → Valori hard-coded nel codice
2. API Pubblica¶
2.1 Inizializzazione¶
from structum_lab.config import get_config, set_config_provider, JSONConfigProvider
# Setup del provider (una sola volta all'avvio)
provider = JSONConfigProvider() # Default: usa ~/.structum/config.json
set_config_provider(provider)
# Accesso al singleton
config = get_config()
2.2 Lettura con Dot-Notation¶
Il metodo get() supporta la navigazione di strutture nested:
# Struttura dati ipotetica:
# {
# "database": {
# "primary": {
# "host": "db.example.com",
# "port": 5432
# }
# }
# }
# Accesso flat
log_level = config.get("log_level", default="INFO")
# Accesso nested (dot-notation)
db_host = config.get("database.primary.host", default="localhost")
db_port = config.get("database.primary.port", default=5432)
# Type hints (opzionale, dipende dall'uso)
timeout: int = config.get("network.timeout", default=30)
⚠️ Nota sui Default:
Il valore default viene restituito se:
La chiave non esiste
Un nodo intermedio nel path non è un dict
Si verifica un errore di tipo (es.
"database"è una stringa invece di un dict)
2.3 Scrittura e Persistenza¶
# Modifica semplice
config.set("ui.theme", "dark_mode")
# Modifica nested
config.set("database.primary.max_connections", 100)
# IMPORTANTE: Salvataggio esplicito
config.save() # Persiste su disco (~/.structum/config.json)
⚠️ Comportamento di set():
set()modifica solo la cache in-memorysave()è richiesto per rendere le modifiche persistenti (nel Core JSONProvider)Solo i valori modificati via
set()vengono salvati nel layer utente
Esempio completo con gestione errori:
from structum_lab.config import get_config
config = get_config()
try:
# Lettura sicura
db_host = config.get("database.host", default="localhost")
# Modifica
config.set("database.max_connections", 150)
# Commit su disco
config.save()
except Exception as e:
# Errore di I/O durante save()
print(f"Impossibile salvare: {e}")
2.4 Metodi di Utilità¶
# Verifica esistenza chiave
if config.has("feature_flags.new_ui"):
# ...
# Ricarica da sorgente (utile se il file cambia su disco)
config.reload()
3. Provider Implementati¶
3.1 Confronto Provider¶
Feature |
JSONConfigProvider |
DynaconfProvider |
|---|---|---|
Sorgente Dati |
File JSON singolo ( |
TOML Project Files + Env Vars |
Validazione |
❌ Nessuna (dict libero) |
✅ Pydantic (Auto o Strict) |
Layer Support |
Single layer (User Persistence) |
Multi-layer (5 livelli) |
Hot Reload |
❌ No |
✅ Watchdog (Opzionale) |
Produzione |
⚠️ Solo User Prefs |
✅ Raccomandato |
Dipendenze |
Core built-in |
|
3.2 JSON Provider (Built-in)¶
Caso d’Uso: Salvare preferenze utente persistenti (tema, ultimi file aperti, dimensioni finestre) senza dipendenze esterne.
Limiti:
Non supporta file di progetto (es.
config/app/settings.toml)Nessuna separazione tra layer (tutto appiattito)
Esempio:
from structum_lab.config import set_config_provider, get_config, JSONConfigProvider
# Setup
provider = JSONConfigProvider() # Path default: ~/.structum/config.json
set_config_provider(provider)
# Uso
config = get_config()
config.set("window.width", 1920)
config.save() # Scrive su file
3.3 Dynaconf Provider (Raccomandato)¶
Caso d’Uso: Applicazioni di produzione con configurazione complessa su più ambienti.
Setup completo: Vedi Plugin Dynaconf Documentation.
Quick Start (Zero-Config):
from structum_lab.plugins.dynaconf import DynaconfConfigProvider
from structum_lab.config import set_config_provider
provider = DynaconfConfigProvider()
provider.auto_discover() # Scans config/app/*.toml
set_config_provider(provider)
4. Setup e Configurazione Raccomandata¶
4.1 Struttura Directory “Industry Standard”¶
my_project/
├── config/
│ ├── app/
│ │ ├── setting.toml # Configurazione applicazione
│ │ └── database.toml
│ └── models/
│ └── database.py # Pydantic models (opzionale)
├── src/
│ └── main.py
└── .env # Secrets locali (NON committare)
5. Gestione Errori¶
5.1 Eccezioni¶
L’interfaccia definisce contratti, ma i metodi possono sollevare eccezioni standard o specifiche del provider.
Il metodo get_config() solleva RuntimeError se il provider non è inizializzato.
5.2 Edge Cases della Dot-Notation¶
config_data = {
"database": "not_a_dict", # Errore: ci aspettiamo un dict
"app.name": "MyApp" # Chiave con punto letterale
}
# ❌ Fallisce: "database" non è un dict
config.get("database.host") # Restituisce default (o errore a seconda del provider)
# ✅ Corretto: usa default per gestire l'errore
config.get("database.host", default="localhost")
6. Sicurezza e Best Practices¶
6.1 Gestione Secrets¶
❌ MAI fare questo:
# settings.toml (versionato in Git)
[production.database]
password = "SuperSecret123" # ❌ ESPOSTO IN VERSION CONTROL
✅ Approcci corretti:
Opzione 1: Environment Variables
export STRUCTUM_DATABASE__PASSWORD=SuperSecret123
Opzione 2: File .secrets (Dynaconf)
Configura Dynaconf per caricare .secrets.toml (aggiunto a .gitignore).
6.2 Permessi File¶
I file di configurazione contenenti segreti devono avere permessi 600 (rw——-).
7. API Reference Completa¶
7.1 Interfaccia ConfigProviderInterface¶
class ConfigProviderInterface(Protocol):
def get(self, key: str, default: Any = None) -> Any:
...
def set(self, key: str, value: Any) -> None:
"""
Imposta un valore (solo in-memory).
Richiede save() per persistenza.
"""
...
def has(self, key: str) -> bool:
...
def save(self) -> None:
"""
Persiste le modifiche allo storage sottostante.
"""
...
def reload(self) -> None:
...
7.2 Funzioni Pubbliche¶
def get_config() -> ConfigProviderInterface:
"""
Ottiene il singleton della configurazione.
Raises RuntimeError se set_config_provider() non è stato chiamato.
"""
...
def set_config_provider(provider: ConfigProviderInterface | type[ConfigProviderInterface]) -> None:
"""
Imposta il provider globale (chiamare una sola volta all'avvio).
"""
...
8. Estendere il Sistema¶
8.1 Creare un Custom Provider¶
Basta creare una classe che rispetti il protocollo ConfigProviderInterface.
class InMemoryProvider:
def __init__(self): self._data = {}
def get(self, key, default=None): return self._data.get(key, default)
def set(self, key, value): self._data[key] = value
def has(self, key): return key in self._data
def save(self): pass
def reload(self): pass