Skip to content

Configuration API

The configuration module handles loading, validating, and managing project configuration from polyglot.toml files.

Overview

Configuration provides:

  • TOML parsing: Load configuration from files
  • Validation: Ensure configuration is valid
  • Type safety: Pydantic models for configuration
  • Defaults: Sensible default values
  • Error reporting: Clear error messages

Configuration Models

polyglot_ffi.core.config.PolyglotConfig

Bases: BaseModel

Complete polyglot.toml configuration.

Source code in src/polyglot_ffi/core/config.py
class PolyglotConfig(BaseModel):
    """Complete polyglot.toml configuration."""

    project: ProjectConfig
    source: SourceConfig
    targets: List[TargetConfig] = Field(default_factory=list)
    build: BuildConfig = Field(default_factory=BuildConfig)
    type_mappings: Dict[str, TypeMappingConfig] = Field(
        default_factory=dict, description="Custom type mappings"
    )

    @field_validator("targets")
    @classmethod
    def validate_targets(cls, v: List[TargetConfig]) -> List[TargetConfig]:
        """Ensure at least one target is configured."""
        if not v:
            raise ValueError("At least one target language must be configured")
        return v

    model_config = ConfigDict(extra="allow")  # Allow extra fields for future expansion

Functions

validate_targets(v) classmethod

Ensure at least one target is configured.

Source code in src/polyglot_ffi/core/config.py
@field_validator("targets")
@classmethod
def validate_targets(cls, v: List[TargetConfig]) -> List[TargetConfig]:
    """Ensure at least one target is configured."""
    if not v:
        raise ValueError("At least one target language must be configured")
    return v

polyglot_ffi.core.config.SourceConfig

Bases: BaseModel

Source language configuration.

Source code in src/polyglot_ffi/core/config.py
class SourceConfig(BaseModel):
    """Source language configuration."""

    language: str = Field(..., description="Source language (e.g., 'ocaml')")
    files: List[str] = Field(default_factory=list, description="Source files to process")
    dir: Optional[str] = Field(None, description="Source directory")
    exclude: List[str] = Field(default_factory=list, description="Files to exclude")
    libraries: List[str] = Field(
        default_factory=list, description="OCaml libraries to link (e.g., ['str', 'unix'])"
    )

    @field_validator("language")
    @classmethod
    def validate_language(cls, v: str) -> str:
        """Validate source language."""
        supported = ["ocaml"]
        if v.lower() not in supported:
            raise ValueError(f"Unsupported source language: {v}. Supported: {', '.join(supported)}")
        return v.lower()

Functions

validate_language(v) classmethod

Validate source language.

Source code in src/polyglot_ffi/core/config.py
@field_validator("language")
@classmethod
def validate_language(cls, v: str) -> str:
    """Validate source language."""
    supported = ["ocaml"]
    if v.lower() not in supported:
        raise ValueError(f"Unsupported source language: {v}. Supported: {', '.join(supported)}")
    return v.lower()

polyglot_ffi.core.config.TargetConfig

Bases: BaseModel

Target language configuration.

Source code in src/polyglot_ffi/core/config.py
class TargetConfig(BaseModel):
    """Target language configuration."""

    language: str = Field(..., description="Target language (e.g., 'python', 'rust')")
    output_dir: str = Field(default="generated", description="Output directory")
    enabled: bool = Field(default=True, description="Enable this target")

    @field_validator("language")
    @classmethod
    def validate_language(cls, v: str) -> str:
        """Validate target language."""
        supported = ["python", "rust", "c"]
        if v.lower() not in supported:
            raise ValueError(f"Unsupported target language: {v}. Supported: {', '.join(supported)}")
        return v.lower()

Functions

validate_language(v) classmethod

Validate target language.

Source code in src/polyglot_ffi/core/config.py
@field_validator("language")
@classmethod
def validate_language(cls, v: str) -> str:
    """Validate target language."""
    supported = ["python", "rust", "c"]
    if v.lower() not in supported:
        raise ValueError(f"Unsupported target language: {v}. Supported: {', '.join(supported)}")
    return v.lower()

Loading Configuration

From File

from pathlib import Path
from polyglot_ffi.core.config import load_config

# Load from default location (./polyglot.toml)
config = load_config()

# Load from specific path
config = load_config(Path("custom.toml"))

# Access configuration
print(f"Project: {config.name}")
print(f"Targets: {', '.join(t.language for t in config.targets)}")

Creating Configuration

from polyglot_ffi.core.config import (
    ProjectConfig, BindingsConfig, TargetConfig
)

# Create configuration programmatically
config = ProjectConfig(
    name="my-project",
    version="0.1.0",
    description="My FFI bindings",
    bindings=BindingsConfig(
        source_dir="src",
        output_dir="generated",
        source_files=["src/api.mli"]
    ),
    targets=[
        TargetConfig(
            language="python",
            enabled=True,
            output_dir="generated/python"
        )
    ]
)

Saving Configuration

# Convert to dictionary
config_dict = config.dict()

# Save to TOML
import toml
with open("polyglot.toml", "w") as f:
    toml.dump(config_dict, f)

Validation

Automatic Validation

Configuration is validated automatically on load:

from polyglot_ffi.core.config import load_config
from polyglot_ffi.utils.errors import ConfigurationError

try:
    config = load_config(Path("polyglot.toml"))
except ConfigurationError as e:
    print(f"Invalid configuration: {e.message}")
    if e.suggestions:
        print(f"Suggestions: {', '.join(e.suggestions)}")

Manual Validation

from polyglot_ffi.core.config import validate_config

# Validate loaded configuration
errors = validate_config(config)

if errors:
    print("Configuration errors:")
    for error in errors:
        print(f"  - {error}")
else:
    print("Configuration is valid!")

Common Validation Errors

Error Cause Fix
Missing name No project name specified Add name = "project-name"
Invalid language Unsupported target language Use: python, rust, or go
Source file not found .mli file doesn't exist Check source_files paths
Duplicate targets Same language listed twice Remove duplicate
No enabled targets All targets disabled Enable at least one target

Configuration Structure

Complete Example

# polyglot.toml

# Project metadata
[project]
name = "crypto-bindings"
version = "0.1.0"
description = "FFI bindings for crypto library"
authors = ["Jane Developer <jane@example.com>"]

# Bindings configuration
[bindings]
source_dir = "src"
output_dir = "generated"
source_files = ["src/crypto.mli", "src/hash.mli"]
auto_discover = false

# Python target
[targets.python]
enabled = true
output_dir = "generated/python"
module_prefix = "crypto"

# Rust target
[targets.rust]
enabled = true
output_dir = "generated/rust"

# Custom type mappings
[types.binary_data]
ocaml = "bytes"
python = "bytes"
rust = "Vec<u8>"
c = "uint8_t*"

Minimal Example

[project]
name = "simple-project"

[bindings]
source_files = ["src/api.mli"]

[targets.python]
enabled = true

Default Values

The configuration system provides sensible defaults:

# Default bindings config
bindings = BindingsConfig(
    source_dir="src",          # Default source directory
    output_dir="generated",    # Default output directory
    source_files=[],           # No default files
    auto_discover=True         # Auto-discover .mli files
)

# Default target config
target = TargetConfig(
    language="python",
    enabled=True,              # Enabled by default
    output_dir=None,          # Uses bindings.output_dir
    module_prefix=None        # No prefix by default
)

Environment Variables

Override configuration with environment variables:

import os
from polyglot_ffi.core.config import load_config

# Override config file location
os.environ["POLYGLOT_FFI_CONFIG"] = "/path/to/custom.toml"
config = load_config()

# Override output directory
os.environ["POLYGLOT_FFI_OUTPUT"] = "/tmp/bindings"

Accessing Configuration Values

config = load_config()

# Project information
print(f"Name: {config.name}")
print(f"Version: {config.version}")
print(f"Description: {config.description}")

# Bindings settings
print(f"Source dir: {config.bindings.source_dir}")
print(f"Output dir: {config.bindings.output_dir}")
print(f"Source files: {config.bindings.source_files}")

# Targets
for target in config.targets:
    print(f"Target: {target.language}")
    print(f"  Enabled: {target.enabled}")
    print(f"  Output: {target.output_dir}")

# Custom types
if hasattr(config, 'types'):
    for type_name, mappings in config.types.items():
        print(f"Custom type: {type_name}")
        for lang, target_type in mappings.items():
            print(f"  {lang}: {target_type}")

Filtering Targets

# Get only enabled targets
enabled_targets = [t for t in config.targets if t.enabled]

# Get specific language target
python_target = next(
    (t for t in config.targets if t.language == "python"),
    None
)

if python_target:
    print(f"Python output: {python_target.output_dir}")

Creating Default Configuration

from polyglot_ffi.core.config import create_default_config

# Create default config for a new project
config = create_default_config(
    name="my-project",
    target_langs=["python", "rust"]
)

# Save to file
import toml
with open("polyglot.toml", "w") as f:
    toml.dump(config.dict(), f)

Complete Usage Example

from pathlib import Path
from polyglot_ffi.core.config import load_config, validate_config
from polyglot_ffi.utils.errors import ConfigurationError

def setup_project():
    """Load and validate project configuration."""
    try:
        # Load configuration
        config_path = Path("polyglot.toml")
        config = load_config(config_path)

        # Validate
        errors = validate_config(config)
        if errors:
            print("Configuration errors:")
            for error in errors:
                print(f"  ✗ {error}")
            return None

        print(f"✓ Loaded configuration for {config.name}")

        # Check source files exist
        for source_file in config.bindings.source_files:
            if not Path(source_file).exists():
                print(f"✗ Source file not found: {source_file}")
                return None

        # Check enabled targets
        enabled = [t for t in config.targets if t.enabled]
        if not enabled:
            print("✗ No enabled targets")
            return None

        print(f"✓ Found {len(enabled)} enabled target(s):")
        for target in enabled:
            print(f"  - {target.language}")

        return config

    except ConfigurationError as e:
        print(f"✗ Configuration error: {e.message}")
        if e.suggestions:
            print("Suggestions:")
            for suggestion in e.suggestions:
                print(f"  - {suggestion}")
        return None

    except FileNotFoundError:
        print("✗ Configuration file not found: polyglot.toml")
        print("Run 'polyglot-ffi init' to create a new project")
        return None

# Use it
if __name__ == "__main__":
    config = setup_project()
    if config:
        print("\n✓ Configuration valid and ready!")

See Also