Skip to content

Usage Guide

This guide provides more detailed information on how to use Nubby in your applications.

Configuration File Definition

Basic File Definition

To define a configuration file, use the new_file_model function:

from nubby.models import new_file_model

# Create a file definition for a file named "app_config"
file_definition = new_file_model("app_config")

This will look for a file named app_config with any of the supported extensions (.json, .yaml, .yml, .toml) in the search paths.

Custom Name Generation

You can customize how section names are generated by providing a name_generator function:

# Use snake_case for section names
def to_snake_case(name):
    import re
    return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()

file_definition = new_file_model("config", name_generator=to_snake_case)

Section Definition

Basic Section Definition

To define a section in your configuration file, use the section decorator:

from dataclasses import dataclass

@file_definition.section("database")
@dataclass
class DatabaseConfig:
    host: str
    port: int
    username: str
    password: str

Automatic Section Naming

If you don't provide a section name, Nubby will use the class name (possibly transformed by the name_generator):

@file_definition.section()  # Will use "database_config" with snake_case generator
@dataclass
class DatabaseConfig:
    host: str
    port: int
    username: str
    password: str

Nested Configuration

You can create nested configuration structures using dataclasses:

@dataclass
class ConnectionDetails:
    host: str
    port: int

@file_definition.section("database")
@dataclass
class DatabaseConfig:
    connection: ConnectionDetails
    username: str
    password: str

Using Configuration

Basic Dependency Injection

Use Bevy's dependency injection to access your configuration:

from bevy import inject, dependency

@inject
def connect_to_database(db_config: DatabaseConfig = dependency()):
    # db_config is automatically loaded from the configuration file
    print(f"Connecting to {db_config.host}:{db_config.port}")
    # ... connection logic ...

Manual Access

You can also access configuration manually:

from nubby import get_active_controller

# Get a configuration instance
db_config = get_active_controller().get_model(DatabaseConfig)

Configuration File Management

Search Paths

By default, Nubby looks for configuration files in the current working directory. You can add additional search paths:

from nubby import get_active_controller

# Add a search path
get_active_controller().add_path("/etc/myapp")
get_active_controller().add_path("~/.config/myapp")

Saving Configuration

You can save changes to a configuration back to the file:

from nubby import get_active_controller

# After modifying the configuration
db_config.port = 5433
get_active_controller().save(db_config)

Custom Loaders

You can create custom loaders for other file formats:

from nubby.loaders import ConfigLoader
from pathlib import Path
from typing import Any

class MyCustomLoader(ConfigLoader):
    extensions = {"mycfg"}

    def __init__(self, path: Path):
        super().__init__(path)
        self._data = None

    def load(self, *args) -> dict[str, Any]:
        # Implement loading logic
        ...

    def write(self, data: dict[str, Any]):
        # Implement writing logic
        ...

# Use the custom loader
from nubby import setup_controller
setup_controller([MyCustomLoader])

Advanced Topics

Multiple Configuration Files

You can define multiple configuration files:

app_config = new_file_model("app_config")
user_config = new_file_model("user_config")

@app_config.section("database")
@dataclass
class DatabaseConfig:
    host: str
    port: int

@user_config.section("preferences")
@dataclass
class UserPreferences:
    theme: str
    language: str

Configuration Validation

You can add validation to your configuration using dataclass post-init:

from dataclasses import dataclass

@file_definition.section("database")
@dataclass
class DatabaseConfig:
    host: str
    port: int

    def __post_init__(self):
        if not self.host:
            raise ValueError("Host cannot be empty")
        if not 1 <= self.port <= 65535:
            raise ValueError(f"Port must be between 1 and 65535, got {self.port}")

Environment Variable Integration

You can integrate environment variables with your configuration:

import os
from dataclasses import dataclass, field

@file_definition.section("database")
@dataclass
class DatabaseConfig:
    host: str = field(default_factory=lambda: os.environ.get("DB_HOST", "localhost"))
    port: int = field(default_factory=lambda: int(os.environ.get("DB_PORT", "5432")))