Namespace Mounting & Logging Config

Two changes to lythonic compose engine

namespace.py engine.py cached.py lyth.py

Problem

Before: imperative wiring

register_cached_callable(
    ns, db_path, gref, nsref,
    min_ttl=0.5, max_ttl=2.0
)
  • Conflates resolution, DDL, wrapping, registration
  • Requires db_path at definition time
  • No way to detect "does this NS need storage?"

Before: logging

_setup_file_logging(storage.log_file)
# hardcoded DEBUG level
# no per-category control
# only triggered by CLI start
  • Level hardcoded
  • No per-logger overrides
  • Only works via lyth start

Solution: Define Mount

# 1. Define (declarative, no storage needed)
ns = Namespace.from_dict(config)

# 2. Mount (activates persistence + logging)
ns.mount(storage)

Detecting Mount Requirements

@mountable        # benefits from mount (DAGs get real provenance)
@mount_required   # needs mount (cache nodes fail without DB)

ns.is_mounted     # True after mount() called
ns.requires_mount # True if cache config, triggers, or @mount_required
ns.has_mountable  # True if DAGs or @mountable present

Detection logic checks:

Declarative Config

YAML namespace entry

namespace:
  - nsref: "market:fetch_prices"
    gref: "myapp:fetch_prices"
    type: cache
    min_ttl: 0.5
    max_ttl: 2.0

from_dict dispatches on type field via _CONFIG_TYPES dict

Storage config

storage:
  cache_db: cache.db
  dag_db: dags.db
  log_file: lyth.log
  log_level: INFO
  loggers:
    lythonic.compose.cached: DEBUG
    lythonic.compose.dag_runner: WARNING

Logging: LogConfig.setup_logging()

class LogConfig(BaseModel):
    log_file: Path | None = None
    log_level: str = "DEBUG"
    loggers: dict[str, str] = {}

    def setup_logging(self) -> None: ...

class StorageConfig(LogConfig):
    cache_db: Path | None = None
    dag_db: Path | None = None
    trigger_db: Path | None = None

Logging lives on LogConfig — usable independently of mount:

Before / After

Before (lyth.py)

# Imperative, 6 calls to register
register_cached_callable(ns, db, ...)
register_cached_callable(ns, db, ...)
_setup_file_logging(storage.log_file)

After (lyth.py)

ns = Namespace.from_dict(
    [e.model_dump() for e in cfg.namespace]
)
ns.mount(cfg.storage)

Two lines. All persistence wiring happens inside mount().

Summary