Configuration

This module provides unified configuration management for BUVIS tools with automatic precedence handling across multiple sources.

Overview

The configuration system loads settings from multiple sources with clear precedence rules:

  1. CLI arguments (highest priority)

  2. Environment variables (BUVIS_* prefix)

  3. YAML config files (auto-discovered)

  4. Model defaults (lowest priority)

This means a --debug flag always wins over BUVIS_DEBUG=false in the environment, which wins over debug: false in a YAML file.

Source Mapping

Each Python field maps to CLI, ENV, and YAML sources with consistent naming:

Mapping Example for PhotoSettings

Python Field

CLI Argument

Environment Variable

YAML Path

debug

--debug

BUVIS_PHOTO_DEBUG

photo.debug

log_level

--log-level

BUVIS_PHOTO_LOG_LEVEL

photo.log_level

library_path

(custom option)

BUVIS_PHOTO_LIBRARY_PATH

photo.library_path

db.host (nested)

(custom option)

BUVIS_PHOTO__DB__HOST

photo.db.host

Naming rules:

  • CLI: Built-in options (--debug, --log-level) are added automatically. Custom fields require explicit Click options - see Adding Custom CLI Options below.

  • ENV: Prefix from env_prefix + field name in SCREAMING_SNAKE_CASE. Nested fields use __ delimiter.

  • YAML: Tool name (derived from prefix) as root key, then field names with . for nesting.

Adding Custom CLI Options

The @buvis_options decorator only adds --debug, --log-level, --config-dir, and --config. For custom fields, add your own Click options and pass them to cli_overrides:

import click
from buvis.pybase.configuration import ConfigResolver, GlobalSettings
from pydantic_settings import SettingsConfigDict
from pathlib import Path

class PhotoSettings(GlobalSettings):
    model_config = SettingsConfigDict(env_prefix="BUVIS_PHOTO_")
    library_path: Path = Path.home() / "Pictures"
    quality: int = 85

@click.command()
@click.option("--library", type=click.Path(exists=True), help="Photo library path")
@click.option("--quality", type=int, help="JPEG quality (1-100)")
@click.option("--debug/--no-debug", default=None)
def main(library: str | None, quality: int | None, debug: bool | None) -> None:
    cli_overrides = {
        k: v for k, v in {
            "library_path": Path(library) if library else None,
            "quality": quality,
            "debug": debug,
        }.items() if v is not None
    }

    resolver = ConfigResolver()
    settings = resolver.resolve(PhotoSettings, cli_overrides=cli_overrides)
    click.echo(f"Library: {settings.library_path}")

For nested fields (e.g., db.host), flatten them in cli_overrides:

# Nested settings don't have direct CLI support in cli_overrides.
# Use ENV vars or YAML for nested fields, or restructure as top-level.

Tool-Specific Configuration Files

You can separate tool configuration from global settings using dedicated files. The loader searches for both buvis.yaml AND buvis-{tool}.yaml:

Option 1: Section in global file

# ~/.config/buvis/buvis.yaml
debug: false
log_level: INFO

photo:                    # Tool section (from BUVIS_PHOTO_ prefix)
  library_path: /media/photos
  quality: 90

Option 2: Separate tool file

# ~/.config/buvis/buvis-photo.yaml
library_path: /media/photos
quality: 90

Both are merged. Tool-specific file values override global file values for that tool.

The tool name is derived from env_prefix:

  • BUVIS_PHOTO_ → searches for buvis-photo.yaml and photo: section

  • BUVIS_ (default) → only buvis.yaml, no tool section

Complete example:

from buvis.pybase.configuration import GlobalSettings, ToolSettings
from pydantic_settings import SettingsConfigDict
from pathlib import Path

class DatabaseSettings(ToolSettings):
    host: str = "localhost"
    port: int = 5432

class PhotoSettings(GlobalSettings):
    model_config = SettingsConfigDict(
        env_prefix="BUVIS_PHOTO_",
        env_nested_delimiter="__",
    )
    library_path: Path = Path.home() / "Pictures"
    db: DatabaseSettings = DatabaseSettings()

All equivalent ways to set db.host to "prod.db.local":

# Environment variable (note double underscore for nesting)
export BUVIS_PHOTO__DB__HOST=prod.db.local
# YAML (~/.config/buvis/buvis.yaml)
photo:
  db:
    host: prod.db.local
# Python default
class DatabaseSettings(ToolSettings):
    host: str = "prod.db.local"

Quick Start

Add configuration to any Click command:

import click
from buvis.pybase.configuration import buvis_options, get_settings

@click.command()
@buvis_options
@click.pass_context
def main(ctx: click.Context) -> None:
    settings = get_settings(ctx)
    if settings.debug:
        click.echo("Debug mode enabled")
    click.echo(f"Log level: {settings.log_level}")

if __name__ == "__main__":
    main()

This adds --debug, --log-level, --config-dir, and --config options. Values resolve from CLI > ENV > YAML > defaults.

For tool-specific settings, see Downstream Project Integration or Custom Settings Classes.

Migration Guide

This section covers migrating from deprecated patterns to the new configuration system.

From BuvisCommand Pattern

BuvisCommand is deprecated. Replace YAML spec files with typed settings.

Before (deprecated):

from buvis.pybase.command import BuvisCommand

class MyCommand(BuvisCommand):
    def __init__(self):
        super().__init__()
        self._setattr_from_config(cfg, __file__)

With command_input_spec.yaml:

source_dir:
  default: /tmp/source
output_format:
  default: json

After:

from pathlib import Path
import click
from buvis.pybase.configuration import GlobalSettings, buvis_options, get_settings
from pydantic_settings import SettingsConfigDict

class MyCommandSettings(GlobalSettings):
    model_config = SettingsConfigDict(env_prefix="BUVIS_MYCMD_")
    source_dir: Path = Path("/tmp/source")
    output_format: str = "json"

@click.command()
@buvis_options(settings_class=MyCommandSettings)
@click.pass_context
def main(ctx: click.Context) -> None:
    settings = get_settings(ctx, MyCommandSettings)
    # Use settings.source_dir, settings.output_format

Benefits: type safety, validation at startup, environment variable support built-in.

Downstream Project Integration

Step-by-step guide for projects depending on buvis-pybase.

Step 1: Define Settings

# myproject/settings.py
from buvis.pybase.configuration import GlobalSettings
from pydantic_settings import SettingsConfigDict
from pathlib import Path

class MyProjectSettings(GlobalSettings):
    model_config = SettingsConfigDict(
        env_prefix="MYPROJECT_",
        env_nested_delimiter="__",
    )
    data_dir: Path = Path.home() / ".myproject"
    verbose: bool = False

Step 2: Wire CLI

# myproject/cli.py
import click
from buvis.pybase.configuration import buvis_options, get_settings
from myproject.settings import MyProjectSettings

@click.command()
@buvis_options(settings_class=MyProjectSettings)
@click.pass_context
def main(ctx: click.Context) -> None:
    settings = get_settings(ctx, MyProjectSettings)
    if settings.verbose:
        click.echo(f"Data dir: {settings.data_dir}")

Step 3: Configure (any combination)

CLI: myproject --debug --log-level DEBUG

Environment:

export MYPROJECT_DATA_DIR=/var/data
export MYPROJECT_VERBOSE=true

YAML (~/.config/buvis/buvis.yaml):

myproject:
  data_dir: /var/data
  verbose: true

Example: Abbreviations from Config

Load abbreviation definitions from YAML and pass to StringOperator:

from buvis.pybase.configuration import GlobalSettings, ToolSettings
from buvis.pybase.formatting import StringOperator
from pydantic_settings import SettingsConfigDict

class FormattingSettings(ToolSettings):
    abbreviations: list[dict] = []

class DocSettings(GlobalSettings):
    model_config = SettingsConfigDict(env_prefix="DOC_")
    formatting: FormattingSettings = FormattingSettings()

# In CLI:
settings = get_settings(ctx, DocSettings)
expanded = StringOperator.replace_abbreviations(
    text="Use the API",
    abbreviations=settings.formatting.abbreviations,
    level=1,
)  # -> "Use the Application Programming Interface"

YAML:

doc:
  formatting:
    abbreviations:
      - API: Application Programming Interface
      - CLI: Command Line Interface

YAML Configuration

File Locations

Config files are discovered in order (first found wins):

  1. $BUVIS_CONFIG_DIR/buvis.yaml (if env var set)

  2. $XDG_CONFIG_HOME/buvis/buvis.yaml (or ~/.config/buvis/buvis.yaml)

  3. ~/.buvis/buvis.yaml (legacy)

  4. ./buvis.yaml (current directory)

For tool-specific config, files named buvis-{tool}.yaml are also checked.

File Format

# ~/.config/buvis/buvis.yaml
debug: false
log_level: INFO
output_format: text

# Tool-specific sections
photo:
  watermark: true
  default_album: shared
music:
  normalize: true
  bitrate: 320

Environment Variable Substitution

YAML files support environment variable interpolation:

database:
  host: ${DB_HOST}                    # Required - fails if not set
  port: ${DB_PORT:-5432}              # Optional with default
  password: ${DB_PASSWORD}
  connection_string: $${NOT_EXPANDED} # Escaped - becomes literal ${NOT_EXPANDED}

Substitution is applied automatically by ConfigResolver when it loads YAML:

from buvis.pybase.configuration import ConfigResolver
from myapp.settings import PhotoSettings

resolver = ConfigResolver()
settings = resolver.resolve(PhotoSettings)

Environment Variables

The GlobalSettings base class uses the BUVIS_ prefix in SCREAMING_SNAKE_CASE. Override env_prefix on your settings class (as shown in PhotoSettings above) to scope variables per tool:

export BUVIS_PHOTO_DEBUG=true
export BUVIS_PHOTO_LOG_LEVEL=DEBUG
export BUVIS_PHOTO_OUTPUT_FORMAT=json

For nested fields, use double underscores:

export BUVIS_PHOTO__MUSIC__NORMALIZE=true
export BUVIS_PHOTO__MUSIC__BITRATE=256

Custom Settings Classes

GlobalSettings vs ToolSettings

Use the right base class:

  • GlobalSettings (pydantic_settings.BaseSettings) - For your root settings class. Loads from environment variables automatically using the env_prefix.

  • ToolSettings (pydantic.BaseModel) - For nested settings within a root class. Does NOT load from env directly; the parent GlobalSettings handles env resolution for nested fields.

from buvis.pybase.configuration import GlobalSettings, ToolSettings

# ToolSettings for nested - no env loading, just structure
class DatabaseSettings(ToolSettings):
    host: str = "localhost"
    port: int = 5432

# GlobalSettings for root - loads BUVIS_MYAPP_* from env
class MyAppSettings(GlobalSettings):
    model_config = SettingsConfigDict(env_prefix="BUVIS_MYAPP_")
    db: DatabaseSettings = DatabaseSettings()  # Nested

# Environment variable for nested field:
# BUVIS_MYAPP__DB__HOST=prod.db.local  (double underscore!)

Why this matters: If you used GlobalSettings for nested classes, each would try to load from env independently with potentially conflicting prefixes.

Both classes are frozen (immutable) - you cannot modify settings after creation:

settings = get_settings(ctx, MyAppSettings)
settings.db.host = "other"  # Raises: ValidationError (frozen)

Composing Settings

Model tool namespaces with ToolSettings and compose them into your root GlobalSettings subclass:

from typing import Literal
from buvis.pybase.configuration import GlobalSettings, ToolSettings
from pydantic_settings import SettingsConfigDict

class MusicSettings(ToolSettings):
    normalize: bool = True
    bitrate: int = 320

class PhotoSettings(GlobalSettings):
    model_config = SettingsConfigDict(
        env_prefix="BUVIS_PHOTO_",
        env_nested_delimiter="__",
    )
    resolution: Literal["low", "medium", "high"] = "high"
    watermark: bool = False
    music: MusicSettings = MusicSettings()

Nested environment variables map to these namespaces (for example, BUVIS_PHOTO__RESOLUTION=medium or BUVIS_PHOTO__MUSIC__BITRATE=256).

Using ConfigResolver Directly

For non-Click applications or custom resolution:

from buvis.pybase.configuration import ConfigResolver
from myapp.settings import PhotoSettings

resolver = ConfigResolver()
settings = resolver.resolve(
    PhotoSettings,
    cli_overrides={"debug": True},  # Simulate CLI args
)

# Check where each value came from
print(resolver.sources)  # {"debug": ConfigSource.CLI, "log_level": ConfigSource.DEFAULT}

Security Considerations

Sensitive Fields

Fields matching patterns like password, token, api_key, secret are automatically:

  • Masked in __repr__ output (shows ***)

  • Logged at INFO level (vs DEBUG for normal fields)

  • Hidden in validation error messages

from buvis.pybase.configuration import SafeLoggingMixin
from pydantic_settings import BaseSettings

class SecureSettings(SafeLoggingMixin, BaseSettings):
    api_key: str
    password: str

s = SecureSettings(api_key="secret123", password="hunter2")
print(s)  # SecureSettings(api_key='***', password='***')

JSON Size Limits

Environment variables containing JSON are limited to 64KB to prevent DoS:

from buvis.pybase.configuration import SecureSettingsMixin
from pydantic_settings import BaseSettings

class MySettings(SecureSettingsMixin, BaseSettings):
    model_config = {"env_prefix": "MYAPP_"}
    complex_config: dict = {}

# Raises ValueError if MYAPP_COMPLEX_CONFIG exceeds 64KB

Error Handling

from buvis.pybase.configuration import (
    ConfigResolver,
    ConfigurationError,
    MissingEnvVarError,
)
from myapp.settings import PhotoSettings

try:
    resolver = ConfigResolver()
    settings = resolver.resolve(PhotoSettings)
except MissingEnvVarError as e:
    print(f"Missing required env vars: {e.var_names}")
except ConfigurationError as e:
    print(f"Config error: {e}")

Testing

Testing Code That Uses Settings

For unit tests, create settings directly without the resolver:

import pytest
from myapp.settings import MyAppSettings

def test_with_custom_settings():
    # Create settings with test values directly
    settings = MyAppSettings(
        debug=True,
        db={"host": "test-db", "port": 5433},
    )
    assert settings.debug is True
    assert settings.db.host == "test-db"

Mocking get_settings in Click Commands

Use pytest-mock to mock get_settings in CLI tests:

import pytest
from click.testing import CliRunner
from myapp.cli import main
from myapp.settings import MyAppSettings

@pytest.fixture
def mock_settings(mocker):
    settings = MyAppSettings(debug=True)
    mocker.patch("myapp.cli.get_settings", return_value=settings)
    return settings

def test_cli_with_mocked_settings(mock_settings):
    runner = CliRunner()
    result = runner.invoke(main)
    assert result.exit_code == 0

Testing with Environment Variables

Use monkeypatch to set env vars for integration tests:

def test_settings_from_env(monkeypatch):
    monkeypatch.setenv("BUVIS_MYAPP_DEBUG", "true")
    monkeypatch.setenv("BUVIS_MYAPP__DB__HOST", "env-db")

    from buvis.pybase.configuration import ConfigResolver
    from myapp.settings import MyAppSettings

    resolver = ConfigResolver()
    settings = resolver.resolve(MyAppSettings)

    assert settings.debug is True
    assert settings.db.host == "env-db"

Troubleshooting

Missing Environment Variables

If YAML uses ${VAR} without a default, you’ll get MissingEnvVarError:

# buvis.yaml
database:
  password: ${DB_PASSWORD}  # Fails if not set

# Fix: provide default
database:
  password: ${DB_PASSWORD:-}  # Empty string default

YAML Not Loading

Check file locations (in order):

  1. $BUVIS_CONFIG_DIR/buvis.yaml

  2. ~/.config/buvis/buvis.yaml

  3. ~/.buvis/buvis.yaml

  4. ./buvis.yaml

Debug with:

from buvis.pybase.configuration import ConfigResolver

resolver = ConfigResolver()
settings = resolver.resolve(MySettings)
print(resolver.sources)  # Shows where each value came from

Precedence Confusion

CLI always wins, then ENV, then YAML, then defaults.

# This won't work as expected:
export BUVIS_DEBUG=true
myapp --no-debug  # CLI wins -> debug=False

API Reference

Core Classes

GlobalSettings

class buvis.pybase.configuration.GlobalSettings(_case_sensitive: bool | None = None, _nested_model_default_partial_update: bool | None = None, _env_prefix: str | None = None, _env_file: DotenvType | None = PosixPath('.'), _env_file_encoding: str | None = None, _env_ignore_empty: bool | None = None, _env_nested_delimiter: str | None = None, _env_nested_max_split: int | None = None, _env_parse_none_str: str | None = None, _env_parse_enums: bool | None = None, _cli_prog_name: str | None = None, _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, _cli_settings_source: CliSettingsSource[Any] | None = None, _cli_parse_none_str: str | None = None, _cli_hide_none_type: bool | None = None, _cli_avoid_json: bool | None = None, _cli_enforce_required: bool | None = None, _cli_use_class_docs_for_groups: bool | None = None, _cli_exit_on_error: bool | None = None, _cli_prefix: str | None = None, _cli_flag_prefix_char: str | None = None, _cli_implicit_flags: bool | None = None, _cli_ignore_unknown_args: bool | None = None, _cli_kebab_case: bool | Literal['all', 'no_enums'] | None = None, _cli_shortcuts: Mapping[str, str | list[str]] | None = None, _secrets_dir: PathType | None = None, *, debug: bool = False, log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO', output_format: Literal['text', 'json', 'yaml'] = 'text')

Bases: BaseSettings

Global runtime settings for BUVIS tools.

Loads from environment variables with BUVIS_ prefix. Nested delimiter is __ (e.g., BUVIS_PHOTO__LIBRARY_PATH).

debug: bool
log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
model_config = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': None, 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': '__', 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': 'BUVIS_', 'extra': 'forbid', 'frozen': True, 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

output_format: Literal['text', 'json', 'yaml']

ToolSettings

class buvis.pybase.configuration.ToolSettings(*, enabled: bool = True)

Bases: BaseModel

Base for tool-specific settings.

All tool namespaces inherit from this. Each tool gets enabled: bool = True. Subclasses add tool-specific fields. Uses BaseModel (not BaseSettings) since parent GlobalSettings handles ENV resolution.

enabled: bool
model_config = {'extra': 'forbid', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ConfigResolver

class buvis.pybase.configuration.ConfigResolver

Bases: object

Unified configuration loader with precedence handling.

Orchestrates config loading from multiple sources:

  • YAML files (via ConfigurationLoader discovery)

  • Environment variables (via Pydantic, BUVIS_* prefix)

  • CLI overrides (passed to resolve())

Precedence (highest to lowest):

  1. CLI overrides

  2. Environment variables (BUVIS_* prefix)

  3. YAML config files

  4. Model defaults

Example

Basic usage:

resolver = ConfigResolver()
settings = resolver.resolve(GlobalSettings)

With CLI overrides from Click:

@click.command()
@click.option('--debug', is_flag=True)
def main(debug):
    resolver = ConfigResolver()
    settings = resolver.resolve(
        GlobalSettings,
        cli_overrides={"debug": debug} if debug else None,
    )

Custom config directory:

settings = resolver.resolve(GlobalSettings, config_dir="/etc/buvis")

Note

Settings are immutable after resolve(). Instances are frozen. The tool name is inferred from settings_class.model_config['env_prefix'] following the pattern BUVIS_{TOOL}_ -> "tool"; you no longer pass tool_name manually.

resolve(settings_class: type[T], config_dir: str | None = None, config_path: Path | None = None, cli_overrides: dict[str, Any] | None = None) T

Instantiate a settings class with precedence: CLI > ENV > YAML > Defaults.

Parameters:
  • settings_class – The Pydantic settings class to instantiate.

  • config_dir – Optional configuration directory that overrides BUVIS_CONFIG_DIR environment variable for this resolution.

  • config_path – Optional path to YAML config file.

  • cli_overrides – Explicit overrides typically parsed from CLI options.

Returns:

An immutable instance of settings_class populated with resolved values. The instance is frozen and cannot be modified after creation.

Raises:

ConfigurationError – If validation fails for any configuration value.

Note

Precedence order (highest to lowest):

  1. CLI overrides (explicit values passed in cli_overrides)

  2. Environment variables (Pydantic handles automatically)

  3. YAML config file values

  4. Model field defaults

Config file discovery uses the tool name derived from settings_class.model_config['env_prefix'] (BUVIS_{TOOL}_ -> "tool") to locate YAML files.

property sources: dict[str, ConfigSource]

Get copy of source tracking dict.

ConfigSource

class buvis.pybase.configuration.ConfigSource(*values)

Bases: Enum

Source from which a configuration value was obtained.

CLI = 'cli'
DEFAULT = 'default'
ENV = 'env'
YAML = 'yaml'

Mixins

SafeLoggingMixin

class buvis.pybase.configuration.SafeLoggingMixin

Bases: object

Mixin that sanitizes sensitive values in __repr__.

Per PRD: “Sanitize before logging: Don’t log raw JSON (may contain secrets)”

Masks values for fields whose names match sensitive patterns like ‘api_key’, ‘password’, ‘token’, ‘authorization’, etc.

SecureSettingsMixin

class buvis.pybase.configuration.SecureSettingsMixin

Bases: object

Mixin adding security validations for settings.

Validates:

  • JSON env values don’t exceed 64KB

  • Complex types come from validated JSON, not eval()

Example:

class MySettings(SecureSettingsMixin, BaseSettings):
    ...
validate_json_sizes(data: dict[str, Any]) dict[str, Any]

Check env var sizes before parsing.

Parameters:

data – Input data dict from pydantic.

Returns:

Unmodified data dict if all validations pass.

Raises:

ValueError – If any prefixed env var exceeds MAX_JSON_ENV_SIZE.

Click Integration

buvis.pybase.configuration.buvis_options(func: F) F
buvis.pybase.configuration.buvis_options(settings_class_or_func: type[T] | None = None, *, settings_class: type[T] | None = None) Callable[[F], F]

Add standard BUVIS options to a Click command.

Adds --debug/--no-debug, --log-level, --config-dir, and --config options. Resolves settings using ConfigResolver and injects into Click context. Can be applied as @buvis_options, @buvis_options(), or @buvis_options(settings_class=CustomSettings).

Example:

@click.command()
@buvis_options(settings_class=GlobalSettings)
@click.pass_context
def cli(ctx):
    settings = ctx.obj["settings"]
    if settings.debug:
        click.echo("Debug mode enabled")
buvis.pybase.configuration.get_settings(ctx: Context) GlobalSettings
buvis.pybase.configuration.get_settings(ctx: Context, settings_class: type[T]) T

Get settings from Click context.

Parameters:
  • ctx – Click context with settings stored by buvis_options decorator.

  • settings_class – Specific settings class to retrieve from context. Defaults to GlobalSettings for backward compatibility.

Raises:

RuntimeError – If called before buvis_options decorator ran.

Returns:

The requested settings instance from context.

Exceptions

exception buvis.pybase.configuration.ConfigurationError

Bases: Exception

Configuration loading or validation failed.

exception buvis.pybase.configuration.ConfigurationKeyNotFoundError(message: str = 'Key not found in configuration.')

Bases: Exception

Key not found in configuration exception.

Parameters:

message – Error message describing the missing key.

exception buvis.pybase.configuration.MissingEnvVarError(var_names: list[str])

Bases: Exception

Required env var not set.

Parameters:

var_names – List of missing environment variable names.

var_names

The missing variable names.

Validators

buvis.pybase.configuration.validate_nesting_depth(model_class: type[BaseModel]) None

Validate that a model does not exceed the allowed nesting depth.

Parameters:

model_class – The Pydantic model class to validate.

Raises:

ValueError – If the model’s nesting depth exceeds MAX_NESTING_DEPTH.

buvis.pybase.configuration.validate_json_env_size(env_var_name: str) None

Validate that an environment variable’s JSON payload fits within limits.

Parameters:

env_var_name – The name of the environment variable containing JSON data.

Raises:

ValueError – If the variable contains more than MAX_JSON_ENV_SIZE bytes.

buvis.pybase.configuration.get_model_depth(model_class: type[BaseModel], current_depth: int = 0) int

Calculate the maximum nesting depth of a Pydantic model.

The function traverses nested BaseModel fields to find the deepest path and short-circuits once the depth exceeds MAX_NESTING_DEPTH + 1.

Parameters:
  • model_class – The root Pydantic model class to examine.

  • current_depth – The depth of the current model in the recursion tree.

Returns:

The maximum nesting depth encountered relative to the root model.

Return type:

int

buvis.pybase.configuration.is_sensitive_field(field_path: str) bool

Check if field path contains sensitive data indicators.

Parameters:

field_path – Dotted field path (e.g., “database.password”).

Returns:

True if any part matches sensitive patterns.

Constants

buvis.pybase.configuration.MAX_NESTING_DEPTH = 5

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

buvis.pybase.configuration.MAX_JSON_ENV_SIZE = 65536

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4