Source code for melder.aether.aether_configuration_builder

import logging
import threading
from collections.abc import Callable
from typing import Any

from melder.aether.aether_configuration import AetherConfiguration
from melder.utilities.general_base.cleanable import Cleanable
from melder.utilities.helpers.id_builder import IDBuilder


[docs] class AetherConfigurationBuilder(Cleanable): """ One-shot builder for Aether root configuration assembly. Purpose: Mirror the repo's mutable-then-finalize configuration style while making ownership explicit for the wrapped `AetherConfiguration`. Registration: MELDER KERNEL - guarded. Obtained through `Aether.create_configuration_builder()`. Subsystem Context: The fluent front for `AetherConfiguration`, matching the builder pairing used by every other configuration lane in the system. System Context: ONE-SHOT MEANS OWNERSHIP TRANSFERS. The builder owns one mutable configuration during assembly and hands it off exactly once; after `build()` / `finalize()` / `activate()` it has surrendered it and is not a factory that stamps out more. That is what makes ownership unambiguous - at every moment exactly one object is responsible for the configuration, and there is never a window where both the builder and the caller could mutate it. AGENT_ACCESS: public AGENT_PURPOSE: access: public. Fluent one-shot builder for AetherConfiguration. Assemble, then build()/finalize()/activate() - ownership transfers on that call and the builder is spent. Obtain via Aether.create_configuration_builder(). """ __slots__ = Cleanable.__slots__ + [ "_id", "_lock", "_configuration", "_finalized", ] def __init__(self) -> None: """ Initialize one builder with a fresh Aether configuration. Contract: - Constructs and OWNS a fresh `AetherConfiguration`. The builder is a lifetime wrapper around exactly one configuration, not a factory that can produce several. - Starts un-finalized; ownership transfers only at `build()`. Owned State: Owns its lock, id, and the wrapped configuration until `build()` hands it over. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: None. """ super().__init__() self._id: str = IDBuilder.create_id() self._lock: threading.RLock = threading.RLock() self._configuration: AetherConfiguration = AetherConfiguration() self._finalized: bool = False
[docs] def cleanup(self) -> None: """ Idempotently cleanup the builder and any still-owned configuration. Contract: - IDEMPOTENT and OWNERSHIP-AWARE: it cleans the wrapped configuration ONLY IF `build()` has not already handed it to a caller. Cleaning a builder you never built therefore DESTROYS the configuration; cleaning one you did build leaves the caller's configuration intact. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: None. """ if self._cleaned: return with self._lock: if self._cleaned: return self._cleaned = True if not self._finalized: self._configuration.cleanup() del self._configuration del self._id
[docs] def with_defaults(self) -> AetherConfigurationBuilder: """ Apply the default Aether logger policy. Contract: - Delegates to the wrapped configuration and returns the BUILDER, not the configuration - the chain stays at the builder layer. - Overwrites anything set earlier, so call it first. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: AetherConfigurationBuilder: This builder. """ self.check_cleaned() self._configuration.with_defaults() return self
[docs] def with_channel_logger_activation_enabled( self, enabled: bool, ) -> AetherConfigurationBuilder: """ Set the automatic channel logger activation flag. Contract: - Delegates to the wrapped configuration and returns the BUILDER. - Refused once the wrapped configuration is frozen. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: AetherConfigurationBuilder: This builder. Args: enabled: True to let the utility system activate provider-backed channel loggers. Disabled by default so importing melder never activates logging. """ self.check_cleaned() self._configuration.with_channel_logger_activation_enabled(enabled) return self
[docs] def with_channel_logger_resolver( self, resolver: Callable[..., Any] | None, ) -> AetherConfigurationBuilder: """ Set the channel logger resolver. Contract: - Delegates to the wrapped configuration and returns the BUILDER. - `None` clears the resolver. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: AetherConfigurationBuilder: This builder. Args: resolver: Callable the utility system will use to obtain channel loggers. Recorded as a presence flag only - the record never carries code. """ self.check_cleaned() self._configuration.with_channel_logger_resolver(resolver) return self
[docs] def with_default_logger( self, logger: logging.Logger | None, ) -> AetherConfigurationBuilder: """ Set the stdlib fallback logger. Contract: - Delegates to the wrapped configuration and returns the BUILDER. - `None` clears the default logger. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: AetherConfigurationBuilder: This builder. Args: logger: Fallback logger object used when no channel resolver answers. """ self.check_cleaned() self._configuration.with_default_logger(logger) return self
[docs] def build(self) -> AetherConfiguration: """ Finalize and transfer ownership of the wrapped configuration. Contract: - ONE-SHOT AND CONSUMING. It finalizes the configuration, transfers ownership to the caller, and then CLEANS THIS BUILDER. The builder is dead afterwards and a second `build()` raises. - Ownership transfer is the point: after a successful build the caller owns the configuration and the builder will never clean it. - Returns a FROZEN configuration, not an activated one - call `activate()` on the result if it is going live. Threading: State transitions are applied under the configuration lock. Lifecycle / Cleanup: Guarded by `check_cleaned()`. Raises: RuntimeError: If the configuration has been cleaned. Returns: AetherConfiguration: Finalized configuration instance. """ self.check_cleaned() self._finalize_configuration() return self._handoff_configuration()
def _handoff_configuration(self) -> AetherConfiguration: """ Transfer builder-owned configuration ownership to the caller. Returns: AetherConfiguration: The configuration previously owned by this builder. """ with self._lock: if self._finalized: raise RuntimeError( "AetherConfigurationBuilder no longer owns a configuration." ) configuration = self._configuration self._finalized = True self.cleanup() return configuration def _finalize_configuration(self) -> None: """ Run the configuration finalization lifecycle before ownership transfer. """ self._configuration.finalize()