Basic Usage

This page provides basic examples of using objectstate.

Simple Configuration

The most basic usage of objectstate:

from dataclasses import dataclass
from objectstate import (
    set_base_config_type,
    LazyDataclassFactory,
    config_context,
)

@dataclass
class AppConfig:
    api_key: str = "default-key"
    timeout: int = 30
    retries: int = 3

# Setup
set_base_config_type(AppConfig)
LazyAppConfig = LazyDataclassFactory.make_lazy_simple(AppConfig)

# Create configuration
config = AppConfig(
    api_key="prod-key-123",
    timeout=60
)

# Use with context
with config_context(config):
    lazy = LazyAppConfig()
    print(lazy.api_key)   # "prod-key-123"
    print(lazy.timeout)   # 60
    print(lazy.retries)   # 3 (default)

Multiple Configurations

Managing multiple configuration types:

from dataclasses import dataclass
from objectstate import LazyDataclassFactory, config_context

@dataclass
class DatabaseConfig:
    host: str = "localhost"
    port: int = 5432
    database: str = "myapp"

@dataclass
class CacheConfig:
    backend: str = "redis"
    ttl: int = 300
    max_size: int = 1000

# Create lazy versions
LazyDB = LazyDataclassFactory.make_lazy_simple(DatabaseConfig)
LazyCache = LazyDataclassFactory.make_lazy_simple(CacheConfig)

# Setup configurations
db_config = DatabaseConfig(
    host="prod.db.internal",
    port=5433,
    database="production"
)

cache_config = CacheConfig(
    backend="memcached",
    ttl=600
)

# Use both in context
with config_context(db_config):
    with config_context(cache_config):
        db = LazyDB()
        cache = LazyCache()

        print(f"Connecting to {db.host}:{db.port}/{db.database}")
        print(f"Cache: {cache.backend}, TTL: {cache.ttl}s")

Function Integration

Using lazy configs with functions:

from dataclasses import dataclass
from objectstate import LazyDataclassFactory, config_context

@dataclass
class ProcessConfig:
    batch_size: int = 100
    parallel: bool = False
    log_level: str = "INFO"

LazyProcess = LazyDataclassFactory.make_lazy_simple(ProcessConfig)

def process_items(items: list, config: LazyProcess):
    """Process items using configuration."""
    print(f"Processing {len(items)} items")
    print(f"Batch size: {config.batch_size}")
    print(f"Parallel: {config.parallel}")
    print(f"Log level: {config.log_level}")

    # Process in batches
    for i in range(0, len(items), config.batch_size):
        batch = items[i:i + config.batch_size]
        print(f"Processing batch of {len(batch)} items")

# Use it
config = ProcessConfig(batch_size=50, parallel=True)

with config_context(config):
    items = list(range(250))
    lazy_cfg = LazyProcess()
    process_items(items, lazy_cfg)

Overriding Values

Explicitly override context values:

from dataclasses import dataclass
from objectstate import LazyDataclassFactory, config_context

@dataclass
class ServerConfig:
    host: str = "0.0.0.0"
    port: int = 8000
    workers: int = 4

LazyServer = LazyDataclassFactory.make_lazy_simple(ServerConfig)

# Context provides defaults
context_config = ServerConfig(
    host="prod.server.com",
    port=443,
    workers=8
)

with config_context(context_config):
    # Use all from context
    server1 = LazyServer()
    print(f"Server 1: {server1.host}:{server1.port}, workers={server1.workers}")
    # Output: Server 1: prod.server.com:443, workers=8

    # Override specific values
    server2 = LazyServer(port=8443)
    print(f"Server 2: {server2.host}:{server2.port}, workers={server2.workers}")
    # Output: Server 2: prod.server.com:8443, workers=8

    # Override multiple values
    server3 = LazyServer(host="localhost", port=8080, workers=1)
    print(f"Server 3: {server3.host}:{server3.port}, workers={server3.workers}")
    # Output: Server 3: localhost:8080, workers=1

Automatic Field Injection with Decorators

Using the decorator pattern for automatic field injection and lazy class generation:

from dataclasses import dataclass
from objectstate import auto_create_decorator, ensure_global_config_context

# Create global config with auto_create_decorator
@auto_create_decorator
@dataclass
class GlobalPipelineConfig:
    num_workers: int = 4
    verbose: bool = False

# This automatically creates:
# 1. A decorator named `global_pipeline_config`
# 2. A lazy class named `PipelineConfig`

# Use the decorator on other config classes
@global_pipeline_config
@dataclass
class DatabaseConfig:
    host: str = "localhost"
    port: int = 5432
    pool_size: int = 10

@global_pipeline_config
@dataclass
class CacheConfig:
    backend: str = "redis"
    ttl: int = 300

# After module loading, GlobalPipelineConfig automatically has:
# - database_config: DatabaseConfig = DatabaseConfig()
# - cache_config: CacheConfig = CacheConfig()
# And lazy classes are created: LazyDatabaseConfig, LazyCacheConfig

# Create and set up global config instance
global_config = GlobalPipelineConfig(
    num_workers=8,
    verbose=True
)

# REQUIRED: Establish global context for lazy resolution
ensure_global_config_context(GlobalPipelineConfig, global_config)

# Now you can access injected configs
print(f"Database: {global_config.database_config.host}:{global_config.database_config.port}")
print(f"Cache: {global_config.cache_config.backend}, TTL={global_config.cache_config.ttl}")

Key Benefits:

  • Automatic injection: Decorated configs become fields in the global config

  • Lazy class generation: Lazy versions are automatically created

  • Modular structure: Each component has its own config class

  • Type-safe: Full IDE support and type checking

Nested Dataclass Auto-Lazification

The framework automatically converts nested dataclass fields to lazy versions:

from dataclasses import dataclass
from objectstate import LazyDataclassFactory, config_context

@dataclass
class LoggingConfig:
    level: str = "INFO"
    format: str = "json"

@dataclass
class AppConfig:
    app_name: str = "MyApp"
    logging: LoggingConfig = LoggingConfig()
    port: int = 8000

# Create lazy version - nested LoggingConfig is automatically lazified
LazyAppConfig = LazyDataclassFactory.make_lazy_simple(AppConfig)

# Use it
config = AppConfig(
    app_name="ProductionApp",
    logging=LoggingConfig(level="WARNING", format="text"),
    port=443
)

with config_context(config):
    lazy_app = LazyAppConfig()

    # Access nested config - it's automatically lazy
    print(f"App: {lazy_app.app_name}")
    print(f"Log level: {lazy_app.logging.level}")
    print(f"Log format: {lazy_app.logging.format}")
    print(f"Port: {lazy_app.port}")

Benefits:

  • No need to manually create LazyLoggingConfig

  • Works recursively for deeply nested configs

  • Preserves all field properties and metadata