Skip to content

Plating API Reference

This page contains the complete API reference for the Plating package, automatically generated from source code docstrings.

For practical guides and usage examples, see:

API Documentation

Auto-generated API documentation for plating:

Modern async documentation generation with full foundation integration.

A clean, type-safe API for generating high-quality Terraform/OpenTofu provider documentation with foundation patterns.

Key Features: - Type-safe async-first API - Full foundation integration (retry, metrics, circuit breakers) - Registry-based component discovery - Integrated markdown validation with configurable rules - Context-aware template rendering with Jinja2 - Support for resources, data sources, functions, and provider docs

Example Usage
import asyncio
from pathlib import Path
from plating import Plating, ComponentType, PlatingContext
from provide.foundation import pout

async def main():
    # Initialize with foundation context
    context = PlatingContext(
        provider_name="my_provider",
        log_level="INFO",
        no_color=False
    )

    api = Plating(context)

    # Create missing templates
    adorn_result = await api.adorn(component_types=[ComponentType.RESOURCE])
    pout(f"✅ Created {adorn_result.templates_generated} templates")

    # Generate docs with validation
    plate_result = await api.plate(
        Path("docs"),
        component_types=[ComponentType.RESOURCE],
        validate_markdown=True,
        force=True
    )

    if plate_result.success:
        pout(f"✅ Generated {len(plate_result.output_files)} files")

    # Validate existing documentation
    validation_result = await api.validate()
    pout(f"📊 Validation: {validation_result.passed}/{validation_result.total} passed")

# Run the async main
asyncio.run(main())
CLI Usage
# Create missing templates
plating adorn --component-type resource --provider-name my_provider

# Generate documentation
plating plate --output-dir docs --validate

# Validate existing docs
plating validate --output-dir docs

# Show registry info
plating info --provider-name my_provider

Attributes

__version__ module-attribute

__version__ = get_version('plating', caller_file=__file__)

plating_metrics module-attribute

plating_metrics = PlatingMetrics()

template_engine module-attribute

template_engine = AsyncTemplateEngine()

PlatingContext module-attribute

PlatingContext = PlatingCLIContext

__all__ module-attribute

__all__ = [
    "AdornResult",
    "ArgumentInfo",
    "AsyncTemplateEngine",
    "ComponentType",
    "DocumentationAdorner",
    "DocumentationPlater",
    "FunctionPlatingBundle",
    "ModularPlatingBundle",
    "ModularPlatingDiscovery",
    "PlateResult",
    "Plating",
    "PlatingContext",
    "PlatingRegistry",
    "SchemaInfo",
    "TemplateMetadataExtractor",
    "ValidationResult",
    "__version__",
    "get_plating_registry",
    "plating",
    "plating_metrics",
    "reset_plating_registry",
    "template_engine",
    "with_circuit_breaker",
    "with_metrics",
    "with_retry",
    "with_timing",
]

Classes

FunctionPlatingBundle

Bases: PlatingBundle

Specialized PlatingBundle for individual function templates.

Attributes

template_file class-attribute instance-attribute
template_file: Path = field()

Functions

load_main_template
load_main_template() -> str | None

Load the specific template file for this function.

Source code in plating/bundles/function.py
def load_main_template(self) -> str | None:
    """Load the specific template file for this function."""
    try:
        return self.template_file.read_text(encoding="utf-8")
    except Exception:
        return None

ModularPlatingBundle

Represents a single .plating bundle with its assets.

Attributes

name instance-attribute
name: str
plating_dir instance-attribute
plating_dir: Path
component_type instance-attribute
component_type: str
docs_dir property
docs_dir: Path

Directory containing documentation templates.

examples_dir property
examples_dir: Path

Directory containing example files.

fixtures_dir property
fixtures_dir: Path

Directory containing fixture files.

Functions

has_main_template
has_main_template() -> bool

Check if bundle has a main template file.

Source code in plating/bundles/base.py
def has_main_template(self) -> bool:
    """Check if bundle has a main template file."""
    template_file = self.docs_dir / f"{self.name}.tmpl.md"
    pyvider_template = self.docs_dir / f"pyvider_{self.name}.tmpl.md"
    main_template = self.docs_dir / "main.md.j2"

    return any(template.exists() for template in [template_file, pyvider_template, main_template])
has_examples
has_examples() -> bool

Check if bundle has example files (flat .tf or grouped).

Source code in plating/bundles/base.py
def has_examples(self) -> bool:
    """Check if bundle has example files (flat .tf or grouped)."""
    if not self.examples_dir.exists():
        return False

    # Check for flat .tf files
    if any(self.examples_dir.glob("*.tf")):
        return True

    # Check for grouped examples (subdirectories with main.tf)
    return any(subdir.is_dir() and (subdir / "main.tf").exists() for subdir in self.examples_dir.iterdir())
load_main_template
load_main_template() -> str | None

Load the main template file for this component.

Source code in plating/bundles/base.py
def load_main_template(self) -> str | None:
    """Load the main template file for this component."""
    template_file = self.docs_dir / f"{self.name}.tmpl.md"
    pyvider_template = self.docs_dir / f"pyvider_{self.name}.tmpl.md"
    main_template = self.docs_dir / "main.md.j2"

    # First, try component-specific templates
    for template_path in [template_file, pyvider_template]:
        if template_path.exists():
            try:
                return template_path.read_text(encoding="utf-8")
            except Exception:
                return None

    # Only use main.md.j2 if it's the only component in this bundle directory
    # Check if this bundle contains multiple components by looking for other .tmpl.md files
    if main_template.exists():
        component_templates = list(self.docs_dir.glob("*.tmpl.md"))
        if len(component_templates) <= 1:  # Only this component or no specific templates
            try:
                return main_template.read_text(encoding="utf-8")
            except Exception:
                return None

    return None
load_examples
load_examples() -> dict[str, str]

Load all example files - both flat .tf and grouped subdirs.

Returns:

Type Description
dict[str, str]

Dictionary mapping example name to content:

dict[str, str]
  • Flat .tf files: key is filename stem (e.g., "basic.tf" -> "basic")
dict[str, str]
  • Grouped examples: key is subdirectory name (e.g., "full_stack/main.tf" -> "full_stack")
Source code in plating/bundles/base.py
def load_examples(self) -> dict[str, str]:
    """Load all example files - both flat .tf and grouped subdirs.

    Returns:
        Dictionary mapping example name to content:
        - Flat .tf files: key is filename stem (e.g., "basic.tf" -> "basic")
        - Grouped examples: key is subdirectory name (e.g., "full_stack/main.tf" -> "full_stack")
    """
    examples: dict[str, str] = {}
    if not self.examples_dir.exists():
        return examples

    # Load flat .tf files (backward compatible)
    for example_file in self.examples_dir.glob("*.tf"):
        try:
            examples[example_file.stem] = example_file.read_text(encoding="utf-8")
        except Exception:
            continue

    # Load grouped examples (subdirectories with main.tf)
    for subdir in self.examples_dir.iterdir():
        if subdir.is_dir():
            main_tf = subdir / "main.tf"
            if main_tf.exists():
                try:
                    examples[subdir.name] = main_tf.read_text(encoding="utf-8")
                except Exception:
                    continue

    return examples
load_partials
load_partials() -> dict[str, str]

Load all partial files from docs directory.

Source code in plating/bundles/base.py
def load_partials(self) -> dict[str, str]:
    """Load all partial files from docs directory."""
    partials: dict[str, str] = {}
    if not self.docs_dir.exists():
        return partials

    for partial_file in self.docs_dir.glob("_*"):
        if partial_file.is_file():
            try:
                partials[partial_file.name] = partial_file.read_text(encoding="utf-8")
            except Exception:
                continue
    return partials
load_fixtures
load_fixtures() -> dict[str, str]

Load all fixture files from fixtures directory.

Source code in plating/bundles/base.py
def load_fixtures(self) -> dict[str, str]:
    """Load all fixture files from fixtures directory."""
    fixtures: dict[str, str] = {}
    if not self.fixtures_dir.exists():
        return fixtures

    for fixture_file in self.fixtures_dir.rglob("*"):
        if fixture_file.is_file():
            try:
                rel_path = fixture_file.relative_to(self.fixtures_dir)
                fixtures[str(rel_path)] = fixture_file.read_text(encoding="utf-8")
            except Exception:
                continue
    return fixtures
get_example_groups
get_example_groups() -> list[str]

Get names of example groups (subdirectories with main.tf).

Returns:

Type Description
list[str]

List of group names (subdirectory names)

Source code in plating/bundles/base.py
def get_example_groups(self) -> list[str]:
    """Get names of example groups (subdirectories with main.tf).

    Returns:
        List of group names (subdirectory names)
    """
    if not self.examples_dir.exists():
        return []

    group_names = []
    for subdir in self.examples_dir.iterdir():
        if subdir.is_dir() and (subdir / "main.tf").exists():
            group_names.append(subdir.name)

    return group_names
load_group_fixtures
load_group_fixtures(group_name: str) -> dict[str, Path]

Load fixture files from a specific example group.

Parameters:

Name Type Description Default
group_name str

Name of the example group

required

Returns:

Type Description
dict[str, Path]

Dictionary mapping relative path to source Path object

Source code in plating/bundles/base.py
def load_group_fixtures(self, group_name: str) -> dict[str, Path]:
    """Load fixture files from a specific example group.

    Args:
        group_name: Name of the example group

    Returns:
        Dictionary mapping relative path to source Path object
    """
    group_fixtures_dir = self.examples_dir / group_name / "fixtures"
    if not group_fixtures_dir.exists():
        return {}

    fixtures = {}
    for file_path in group_fixtures_dir.rglob("*"):
        if file_path.is_file():
            rel_path = file_path.relative_to(group_fixtures_dir)
            fixtures[str(rel_path)] = file_path

    return fixtures

ModularPlatingDiscovery

ModularPlatingDiscovery(package_name: str | None = None)

Discovers .plating bundles from installed packages.

Initialize discovery.

Parameters:

Name Type Description Default
package_name str | None

Specific package to search, or None to search all packages

None
Source code in plating/discovery/finder.py
def __init__(self, package_name: str | None = None) -> None:
    """Initialize discovery.

    Args:
        package_name: Specific package to search, or None to search all packages
    """
    self.package_name = package_name

Attributes

package_name instance-attribute
package_name = package_name

Functions

discover_bundles
discover_bundles(
    component_type: str | None = None,
) -> list[PlatingBundle]

Discover all .plating bundles from installed package(s).

Parameters:

Name Type Description Default
component_type str | None

Optional filter for specific component type

None

Returns:

Type Description
list[PlatingBundle]

List of discovered PlatingBundle objects

Source code in plating/discovery/finder.py
def discover_bundles(self, component_type: str | None = None) -> list[PlatingBundle]:
    """Discover all .plating bundles from installed package(s).

    Args:
        component_type: Optional filter for specific component type

    Returns:
        List of discovered PlatingBundle objects
    """
    if self.package_name:
        # Single package discovery
        return self._discover_from_package(self.package_name, component_type)
    else:
        # Global discovery from all installed packages
        return self._discover_from_all_packages(component_type)

DocumentationAdorner

DocumentationAdorner()

Enhances documentation content with metadata and template variables.

Source code in plating/generation/adorner.py
def __init__(self) -> None:
    self.config = get_config()

Attributes

config instance-attribute
config = get_config()

Functions

adorn_function_template
adorn_function_template(
    template_content: str,
    function_name: str,
    metadata: dict[str, Any],
) -> dict[str, Any]

Create template context for function documentation.

Parameters:

Name Type Description Default
template_content str

Raw template content

required
function_name str

Name of the function

required
metadata dict[str, Any]

Function metadata from extractor

required

Returns:

Type Description
dict[str, Any]

Dictionary containing all template variables

Source code in plating/generation/adorner.py
def adorn_function_template(
    self, template_content: str, function_name: str, metadata: dict[str, Any]
) -> dict[str, Any]:
    """Create template context for function documentation.

    Args:
        template_content: Raw template content
        function_name: Name of the function
        metadata: Function metadata from extractor

    Returns:
        Dictionary containing all template variables
    """

    # Create example function that templates can call
    def example(example_name: str) -> str:
        examples = metadata.get("examples", {})
        result = examples.get(example_name, self.config.example_placeholder)
        return str(result)

    return {
        "function_name": function_name,
        "template_content": template_content,
        "example": example,
        **metadata,
    }
adorn_resource_template
adorn_resource_template(
    template_content: str,
    resource_name: str,
    metadata: dict[str, Any],
) -> dict[str, Any]

Create template context for resource documentation.

Parameters:

Name Type Description Default
template_content str

Raw template content

required
resource_name str

Name of the resource

required
metadata dict[str, Any]

Resource metadata

required

Returns:

Type Description
dict[str, Any]

Dictionary containing all template variables

Source code in plating/generation/adorner.py
def adorn_resource_template(
    self, template_content: str, resource_name: str, metadata: dict[str, Any]
) -> dict[str, Any]:
    """Create template context for resource documentation.

    Args:
        template_content: Raw template content
        resource_name: Name of the resource
        metadata: Resource metadata

    Returns:
        Dictionary containing all template variables
    """

    # Create example function that templates can call
    def example(example_name: str) -> str:
        examples = metadata.get("examples", {})
        result = examples.get(example_name, self.config.example_placeholder)
        return str(result)

    # Create schema function that templates can call
    def schema() -> str:
        schema_info = metadata.get("schema", {})
        if not schema_info:
            return "No schema information available."
        # Convert schema to markdown format
        return str(schema_info)

    return {
        "resource_name": resource_name,
        "template_content": template_content,
        "example": example,
        "schema": schema,
        **metadata,
    }
enhance_template_context
enhance_template_context(
    context: dict[str, Any], additional_data: dict[str, Any]
) -> dict[str, Any]

Enhance template context with additional metadata.

Parameters:

Name Type Description Default
context dict[str, Any]

Base template context

required
additional_data dict[str, Any]

Additional data to merge

required

Returns:

Type Description
dict[str, Any]

Enhanced template context

Source code in plating/generation/adorner.py
def enhance_template_context(
    self, context: dict[str, Any], additional_data: dict[str, Any]
) -> dict[str, Any]:
    """Enhance template context with additional metadata.

    Args:
        context: Base template context
        additional_data: Additional data to merge

    Returns:
        Enhanced template context
    """
    enhanced = context.copy()
    enhanced.update(additional_data)
    return enhanced

DocumentationPlater

DocumentationPlater(package_name: str)

Orchestrates the complete documentation generation process using async rendering.

Source code in plating/generation/plater.py
def __init__(self, package_name: str) -> None:
    self.package_name = package_name
    self.discovery = PlatingDiscovery(package_name)
    self.extractor = TemplateMetadataExtractor()
    self.adorner = DocumentationAdorner()
    self.renderer = AsyncTemplateEngine()

Attributes

package_name instance-attribute
package_name = package_name
discovery instance-attribute
discovery = PlatingDiscovery(package_name)
extractor instance-attribute
extractor = TemplateMetadataExtractor()
adorner instance-attribute
adorner = DocumentationAdorner()
renderer instance-attribute
renderer = AsyncTemplateEngine()

Functions

generate_documentation async
generate_documentation(
    output_dir: Path, component_type: str | None = None
) -> list[tuple[Path, str]]

Generate documentation for all discovered components.

Parameters:

Name Type Description Default
output_dir Path

Directory to write generated documentation

required
component_type str | None

Optional filter for component type

None

Returns:

Type Description
list[tuple[Path, str]]

List of (file_path, content) tuples for generated files

Source code in plating/generation/plater.py
async def generate_documentation(
    self, output_dir: Path, component_type: str | None = None
) -> list[tuple[Path, str]]:
    """Generate documentation for all discovered components.

    Args:
        output_dir: Directory to write generated documentation
        component_type: Optional filter for component type

    Returns:
        List of (file_path, content) tuples for generated files
    """
    bundles = self.discovery.discover_bundles(component_type)
    generated_files: list[tuple[Path, str]] = []

    for bundle in bundles:
        if isinstance(bundle, FunctionPlatingBundle):
            files = await self._generate_function_documentation(bundle, output_dir)
            generated_files.extend(files)
        else:
            files = await self._generate_component_documentation(bundle, output_dir)
            generated_files.extend(files)

    return generated_files

Plating

Plating(
    context: PlatingContext, package_name: str | None = None
)

Modern async API for all plating operations with foundation integration.

Initialize plating API with foundation context.

Parameters:

Name Type Description Default
context PlatingContext

PlatingContext with configuration (required)

required
package_name str | None

Package to search for plating bundles, or None to search all packages

None
Source code in plating/plating.py
def __init__(self, context: PlatingContext, package_name: str | None = None) -> None:
    """Initialize plating API with foundation context.

    Args:
        context: PlatingContext with configuration (required)
        package_name: Package to search for plating bundles, or None to search all packages
    """
    self.context = context
    self.package_name = package_name

    # Foundation patterns
    self.registry = get_plating_registry(package_name)

    # Schema processing
    self._provider_schema: dict[str, Any] | None = None

    # Resilience patterns for file I/O and network operations
    self.retry_policy = RetryPolicy(
        max_attempts=3,
        backoff=BackoffStrategy.EXPONENTIAL,
        base_delay=0.5,
        max_delay=10.0,
        retryable_errors=(IOError, OSError, TimeoutError, ConnectionError),
    )

Attributes

context instance-attribute
context = context
package_name instance-attribute
package_name = package_name
registry instance-attribute
registry = get_plating_registry(package_name)
retry_policy instance-attribute
retry_policy = RetryPolicy(
    max_attempts=3,
    backoff=EXPONENTIAL,
    base_delay=0.5,
    max_delay=10.0,
    retryable_errors=(
        IOError,
        OSError,
        TimeoutError,
        ConnectionError,
    ),
)

Functions

adorn async
adorn(
    component_types: list[ComponentType] | None = None,
) -> AdornResult

Generate template structure for discovered components.

Parameters:

Name Type Description Default
component_types list[ComponentType] | None

Specific component types to adorn

None

Returns:

Type Description
AdornResult

AdornResult with generation statistics

Source code in plating/plating.py
@with_timing
@with_retry()
@with_metrics("adorn")
async def adorn(
    self,
    component_types: list[ComponentType] | None = None,
) -> AdornResult:
    """Generate template structure for discovered components.

    Args:
        component_types: Specific component types to adorn

    Returns:
        AdornResult with generation statistics
    """
    if not self.package_name:
        pout(
            "⚠️  Adorn requires a package name. Run with --package-name or in a project with pyproject.toml."
        )
        return AdornResult(errors=["--package-name is required for adorn."])

    pout(f"🎨 Adorning components in package: {self.package_name}")

    try:
        adorner = PlatingAdorner(self.package_name)
        target_types = [ct.value for ct in component_types] if component_types else None
        adorned_counts = await adorner.adorn_missing(target_types)

        total_adorned = sum(adorned_counts.values())

        return AdornResult(
            templates_generated=total_adorned,
            examples_created=total_adorned,  # Each adorn creates one template and one example
            errors=[],
        )

    except Exception as e:
        logger.error("Adorn operation failed", error=str(e))
        return AdornResult(errors=[f"An unexpected error occurred: {e}"])
plate async
plate(
    output_dir: Path | None = None,
    component_types: list[ComponentType] | None = None,
    force: bool = False,
    validate_markdown: bool = True,
    project_root: Path | None = None,
    flat_nav: bool = False,
) -> PlateResult

Generate documentation from plating bundles.

Parameters:

Name Type Description Default
output_dir Path | None

Output directory for documentation

None
component_types list[ComponentType] | None

Component types to generate

None
force bool

Overwrite existing files

False
validate_markdown bool

Enable markdown validation

True
project_root Path | None

Project root directory (auto-detected if not provided)

None
flat_nav bool

Generate flat registry-style navigation (default: grouped by capability)

False

Returns:

Type Description
PlateResult

PlateResult with generation statistics

Source code in plating/plating.py
@with_timing
@with_retry()
@with_metrics("plate")
async def plate(
    self,
    output_dir: Path | None = None,
    component_types: list[ComponentType] | None = None,
    force: bool = False,
    validate_markdown: bool = True,
    project_root: Path | None = None,
    flat_nav: bool = False,
) -> PlateResult:
    """Generate documentation from plating bundles.

    Args:
        output_dir: Output directory for documentation
        component_types: Component types to generate
        force: Overwrite existing files
        validate_markdown: Enable markdown validation
        project_root: Project root directory (auto-detected if not provided)
        flat_nav: Generate flat registry-style navigation (default: grouped by capability)

    Returns:
        PlateResult with generation statistics
    """
    # Detect project root if not provided
    if project_root is None:
        project_root = find_project_root()

    # Determine final output directory with improved logic
    final_output_dir = get_output_directory(output_dir, project_root)

    logger.info(f"Using output directory: {final_output_dir}")
    if project_root:
        logger.info(f"Project root detected: {project_root}")
    else:
        logger.warning("No project root detected, using current directory as base")

    # Validate and create output directory
    try:
        final_output_dir.mkdir(parents=True, exist_ok=True)
    except (PermissionError, OSError) as e:
        logger.error("Failed to create output directory", path=final_output_dir, error=str(e))
        raise FileSystemError(
            path=final_output_dir, operation="create directory", reason=str(e), caused_by=e
        ) from e

    # Ensure we can write to the directory
    if not os.access(final_output_dir, os.W_OK):
        logger.error("Output directory is not writable", path=final_output_dir)
        raise FileSystemError(
            path=final_output_dir,
            operation="write",
            reason="Directory is not writable (check permissions)",
        )

    # Content merging from component packages (before component generation)
    config = get_config(project_root=project_root)
    if config.merge_content_from:
        logger.info("Starting content merge from component packages")
        try:
            merger = ContentMerger(config)

            # Discover content sources
            sources = await merger.discover_content_sources()
            if sources:
                logger.info(f"Discovered {len(sources)} content source(s)")

                # Collect all content files
                await merger.collect_content_files()

                # Merge to output directory (raises on collision)
                await merger.merge_content(final_output_dir)

                logger.info("Content merge completed successfully")
            else:
                logger.debug("No content sources discovered for merging")

        except PlatingContentCollisionError as e:
            logger.error("Content collision detected during merge", error=str(e))
            raise
        except Exception as e:
            logger.error("Unexpected error during content merge", error=str(e))
            raise

    if not component_types:
        component_types = [ComponentType.RESOURCE, ComponentType.DATA_SOURCE, ComponentType.FUNCTION]

    start_time = time.monotonic()
    result = PlateResult(duration_seconds=0.0, files_generated=0, errors=[], output_files=[])

    # Await schema extraction before rendering
    self._provider_schema = await self._extract_provider_schema()

    # Track unique bundles processed
    processed_bundles: set[str] = set()

    for component_type in component_types:
        components = self.registry.get_components_with_templates(component_type)
        logger.info(f"Generating docs for {len(components)} {component_type.value} components")

        # Track unique bundle directories
        for component in components:
            processed_bundles.add(str(component.plating_dir))

        files_before = result.files_generated
        await render_component_docs(
            components,
            component_type,
            final_output_dir,
            force,
            result,
            self.context,
            self._provider_schema or {},
        )
        files_for_type = result.files_generated - files_before
        if files_for_type > 0:
            logger.info(f"Generated {files_for_type} {component_type.value} documentation files")

    # Generate provider index page
    pout("📝 Generating provider index...")
    generate_provider_index(
        final_output_dir, force, result, self.context, self._provider_schema or {}, self.registry
    )

    # Generate mkdocs navigation if mkdocs.yml exists or should be created
    try:
        from plating.mkdocs import MkdocsNavGenerator

        # Collect all components for mkdocs nav generation
        all_components_for_nav = []
        for component_type in component_types:
            components = self.registry.get_components_with_templates(component_type)
            for component in components:
                all_components_for_nav.append((component, component_type))

        # Generate mkdocs navigation
        if all_components_for_nav:
            nav_generator = MkdocsNavGenerator(final_output_dir.parent)
            nav_dict = nav_generator.generate_nav(
                all_components_for_nav, include_guides=True, flat_layout=flat_nav
            )
            nav_generator.update_mkdocs_config(nav_dict)
            if flat_nav:
                logger.info("Generated flat registry-style mkdocs navigation structure")
            else:
                logger.info("Generated grouped mkdocs navigation structure")
    except Exception as e:
        logger.warning(f"Could not generate mkdocs navigation: {e}")

    # Update result with tracking info
    result.bundles_processed = len(processed_bundles)
    result.duration_seconds = time.monotonic() - start_time

    # Validate if requested (disabled due to markdown validator dependency)
    # if validate_markdown and result.output_files:
    #     validation_result = await self.validate(output_dir, component_types)
    #     result.errors.extend(validation_result.errors)

    return result
validate async
validate(
    output_dir: Path | None = None,
    component_types: list[ComponentType] | None = None,
    project_root: Path | None = None,
) -> ValidationResult

Validate generated documentation.

Parameters:

Name Type Description Default
output_dir Path | None

Directory containing documentation

None
component_types list[ComponentType] | None

Component types to validate

None
project_root Path | None

Project root directory (auto-detected if not provided)

None

Returns:

Type Description
ValidationResult

ValidationResult with any errors found

Source code in plating/plating.py
@with_timing
@with_metrics("validate")
async def validate(
    self,
    output_dir: Path | None = None,
    component_types: list[ComponentType] | None = None,
    project_root: Path | None = None,
) -> ValidationResult:
    """Validate generated documentation.

    Args:
        output_dir: Directory containing documentation
        component_types: Component types to validate
        project_root: Project root directory (auto-detected if not provided)

    Returns:
        ValidationResult with any errors found
    """
    # Use same logic as plate method for consistency
    if project_root is None:
        project_root = find_project_root()

    final_output_dir = get_output_directory(output_dir, project_root)
    component_types = component_types or [
        ComponentType.RESOURCE,
        ComponentType.DATA_SOURCE,
        ComponentType.FUNCTION,
    ]

    errors = []
    files_checked = 0
    passed = 0

    for component_type in component_types:
        type_dir = final_output_dir / component_type.output_subdir
        if not type_dir.exists():
            continue

        for md_file in type_dir.glob("*.md"):
            try:
                # For now, just simulate validation (since markdown validator is disabled)
                files_checked += 1
                passed += 1  # Assume validation passes
            except Exception as e:
                errors.append(f"Failed to validate {md_file}: {e}")

    return ValidationResult(
        total=files_checked,
        passed=passed,
        failed=len(errors),
        skipped=0,
        duration_seconds=0.0,
        errors=errors,
    )
get_registry_stats
get_registry_stats() -> dict[str, Any]

Get registry statistics.

Source code in plating/plating.py
def get_registry_stats(self) -> dict[str, Any]:
    """Get registry statistics."""
    stats = {"total_components": 0, "component_types": []}

    for component_type in [ComponentType.RESOURCE, ComponentType.DATA_SOURCE, ComponentType.FUNCTION]:
        components = self.registry.get_components(component_type)
        with_templates = self.registry.get_components_with_templates(component_type)

        stats[component_type.value] = {
            "total": len(components),
            "with_templates": len(with_templates),
        }
        stats["total_components"] += len(components)
        if components:
            stats["component_types"].append(component_type.value)

    return stats

PlatingRegistry

PlatingRegistry(package_name: str | None = None)

Bases: Registry

Component registry using foundation Registry pattern with ComponentSet support.

Initialize registry with package discovery.

Parameters:

Name Type Description Default
package_name str | None

Package to search for plating bundles, or None to search all packages

None
Source code in plating/registry.py
def __init__(self, package_name: str | None = None) -> None:
    """Initialize registry with package discovery.

    Args:
        package_name: Package to search for plating bundles, or None to search all packages
    """
    super().__init__()
    self.package_name = package_name

    # Foundation resilience for discovery
    self._retry_policy = RetryPolicy(
        max_attempts=3,
        backoff=BackoffStrategy.EXPONENTIAL,
        base_delay=0.5,
        max_delay=5.0,
        retryable_errors=(OSError, ImportError, AttributeError),
    )
    self._retry_executor = RetryExecutor(self._retry_policy)

    # Initialize discovery with error handling
    try:
        self._discovery = PlatingDiscovery(package_name)
        # Auto-discover on initialization
        self._discover_and_register()
    except Exception as e:
        scope = package_name if package_name else "all packages"
        logger.error(f"Failed to initialize discovery for {scope}: {e}")
        # Set discovery to None so we can still create the registry
        self._discovery = None

Attributes

package_name instance-attribute
package_name = package_name

Functions

get_components
get_components(
    component_type: ComponentType,
) -> list[PlatingBundle]

Get all components of a specific type.

Parameters:

Name Type Description Default
component_type ComponentType

The component type to filter by

required

Returns:

Type Description
list[PlatingBundle]

List of PlatingBundle objects

Source code in plating/registry.py
def get_components(self, component_type: ComponentType) -> list[PlatingBundle]:
    """Get all components of a specific type.

    Args:
        component_type: The component type to filter by

    Returns:
        List of PlatingBundle objects
    """
    names = self.list_dimension(component_type.value)
    entries = []
    for name in names:
        entry = self.get_entry(name=name, dimension=component_type.value)
        if entry:
            entries.append(entry)
    return [entry.value.bundle for entry in entries]
get_component
get_component(
    component_type: ComponentType, name: str
) -> PlatingBundle | None

Get a specific component by type and name.

Parameters:

Name Type Description Default
component_type ComponentType

The component type

required
name str

The component name

required

Returns:

Type Description
PlatingBundle | None

PlatingBundle if found, None otherwise

Source code in plating/registry.py
def get_component(self, component_type: ComponentType, name: str) -> PlatingBundle | None:
    """Get a specific component by type and name.

    Args:
        component_type: The component type
        name: The component name

    Returns:
        PlatingBundle if found, None otherwise
    """
    entry = self.get_entry(name=name, dimension=component_type.value)
    return entry.value.bundle if entry else None
get_components_with_templates
get_components_with_templates(
    component_type: ComponentType,
) -> list[PlatingBundle]

Get components of a type that have templates.

Parameters:

Name Type Description Default
component_type ComponentType

The component type to filter by

required

Returns:

Type Description
list[PlatingBundle]

List of PlatingBundle objects with templates

Source code in plating/registry.py
def get_components_with_templates(self, component_type: ComponentType) -> list[PlatingBundle]:
    """Get components of a type that have templates.

    Args:
        component_type: The component type to filter by

    Returns:
        List of PlatingBundle objects with templates
    """
    components = self.get_components(component_type)
    return [bundle for bundle in components if bundle.has_main_template()]
get_components_with_examples
get_components_with_examples(
    component_type: ComponentType,
) -> list[PlatingBundle]

Get components of a type that have examples.

Parameters:

Name Type Description Default
component_type ComponentType

The component type to filter by

required

Returns:

Type Description
list[PlatingBundle]

List of PlatingBundle objects with examples

Source code in plating/registry.py
def get_components_with_examples(self, component_type: ComponentType) -> list[PlatingBundle]:
    """Get components of a type that have examples.

    Args:
        component_type: The component type to filter by

    Returns:
        List of PlatingBundle objects with examples
    """
    components = self.get_components(component_type)
    return [bundle for bundle in components if bundle.has_examples()]
get_all_component_types
get_all_component_types() -> list[ComponentType]

Get all registered component types.

Returns:

Type Description
list[ComponentType]

List of ComponentType enums found in registry

Source code in plating/registry.py
def get_all_component_types(self) -> list[ComponentType]:
    """Get all registered component types.

    Returns:
        List of ComponentType enums found in registry
    """
    dimensions = self.list_all().keys()
    component_types = []

    for dimension in dimensions:
        try:
            comp_type = ComponentType(dimension)
            component_types.append(comp_type)
        except ValueError:
            # Skip unknown component types
            pass

    return component_types
refresh
refresh() -> None

Refresh the registry by re-discovering components.

Source code in plating/registry.py
def refresh(self) -> None:
    """Refresh the registry by re-discovering components."""
    logger.info("Refreshing plating registry")
    self.clear()
    self._discover_and_register()
get_registry_stats
get_registry_stats() -> dict[str, Any]

Get statistics about the registry contents.

Returns:

Type Description
dict[str, Any]

Dictionary with registry statistics

Source code in plating/registry.py
def get_registry_stats(self) -> dict[str, Any]:
    """Get statistics about the registry contents.

    Returns:
        Dictionary with registry statistics
    """
    stats = {}
    all_names = self.list_all()

    stats["total_components"] = sum(len(names) for names in all_names.values())
    stats["component_types"] = list(all_names.keys())

    for comp_type, names in all_names.items():
        stats[f"{comp_type}_count"] = len(names)

        # Get actual entries to access metadata
        entries = []
        for name in names:
            entry = self.get_entry(name=name, dimension=comp_type)
            if entry:
                entries.append(entry)

        # Count bundles with templates/examples
        bundles_with_templates = sum(
            1 for entry in entries if entry.value.metadata.get("has_template", False)
        )
        bundles_with_examples = sum(
            1 for entry in entries if entry.value.metadata.get("has_examples", False)
        )

        stats[f"{comp_type}_with_templates"] = bundles_with_templates
        stats[f"{comp_type}_with_examples"] = bundles_with_examples

    return stats

AsyncTemplateEngine

AsyncTemplateEngine()

Async-first template engine with foundation integration.

Source code in plating/templating/engine.py
def __init__(self) -> None:
    self._jinja_env = None
    self._template_cache: dict[str, str] = {}

Functions

render async
render(
    bundle: PlatingBundle, context: PlatingContext
) -> str

Render template with context and partials.

Parameters:

Name Type Description Default
bundle PlatingBundle

PlatingBundle containing template and assets

required
context PlatingContext

Type-safe context for rendering

required

Returns:

Type Description
str

Rendered template string

Raises:

Type Description
TemplateError

If template rendering fails

FileSystemError

If template loading fails

Source code in plating/templating/engine.py
@with_timing
@with_metrics("template_render")
async def render(self, bundle: PlatingBundle, context: PlatingContext) -> str:
    """Render template with context and partials.

    Args:
        bundle: PlatingBundle containing template and assets
        context: Type-safe context for rendering

    Returns:
        Rendered template string

    Raises:
        TemplateError: If template rendering fails
        FileSystemError: If template loading fails
    """
    try:
        # Load template and partials concurrently
        template_task = asyncio.create_task(self._load_template(bundle))
        partials_task = asyncio.create_task(self._load_partials(bundle))

        template_content, partials = await asyncio.gather(template_task, partials_task)

        if not template_content:
            logger.debug(f"No template found for {bundle.name}, skipping")
            return ""

        # Prepare templates dict
        templates = {"main.tmpl": template_content}
        templates.update(partials)

        # Create Jinja environment
        env = self._get_jinja_env(templates)

        # Convert context to dict
        context_dict = context.to_dict()

        # Override template functions with context-aware implementations
        env.globals["example"] = lambda key: self._format_example_with_context(key, context.examples)

        # Override schema function to return actual schema
        if context.schema:
            env.globals["schema"] = lambda: context.schema.to_markdown()
        else:
            env.globals["schema"] = lambda: ""

        # Render template asynchronously
        template = env.get_template("main.tmpl")

        async with plating_metrics.track_operation("template_render", bundle=bundle.name):
            rendered = await template.render_async(**context_dict)
            # Apply global header/footer injection
            return self._apply_global_wrappers(rendered, context)

    except Jinja2TemplateError as e:
        # Extract line number if available
        line_number = getattr(e, "lineno", None)
        error_msg = str(e)

        logger.error(
            "Template rendering failed",
            bundle=bundle.name,
            error=error_msg,
            line_number=line_number,
            **context.to_dict(),
        )

        raise TemplateError(
            template_path=bundle.plating_dir / "docs" / f"{bundle.name}.tmpl.md",
            reason=error_msg,
            line_number=line_number,
            context=getattr(e, "source", None),
        ) from e

    except OSError as e:
        logger.error("File system error during template rendering", bundle=bundle.name, error=str(e))
        raise FileSystemError(
            path=bundle.plating_dir,
            operation="read template",
            reason=str(e),
            caused_by=e,
        ) from e

    except Exception as e:
        logger.exception("Unexpected error during template rendering", bundle=bundle.name)
        raise TemplateError(
            template_path=bundle.plating_dir / "docs" / f"{bundle.name}.tmpl.md",
            reason=f"Unexpected error: {type(e).__name__}: {e}",
        ) from e
render_batch async
render_batch(
    items: list[tuple[PlatingBundle, PlatingContext]],
) -> list[str]

Render multiple templates in parallel.

Parameters:

Name Type Description Default
items list[tuple[PlatingBundle, PlatingContext]]

List of (bundle, context) tuples to render

required

Returns:

Type Description
list[str]

List of rendered template strings

Raises:

Type Description
TemplateError

If any template rendering fails

FileSystemError

If any template loading fails

Source code in plating/templating/engine.py
@with_timing
@with_metrics("template_render_batch")
async def render_batch(self, items: list[tuple[PlatingBundle, PlatingContext]]) -> list[str]:
    """Render multiple templates in parallel.

    Args:
        items: List of (bundle, context) tuples to render

    Returns:
        List of rendered template strings

    Raises:
        TemplateError: If any template rendering fails
        FileSystemError: If any template loading fails
    """
    tasks = [asyncio.create_task(self.render(bundle, context)) for bundle, context in items]

    async with plating_metrics.track_operation("batch_render", count=len(items)):
        return await asyncio.gather(*tasks)
clear_cache
clear_cache() -> None

Clear template cache.

Source code in plating/templating/engine.py
def clear_cache(self) -> None:
    """Clear template cache."""
    self._template_cache.clear()

TemplateMetadataExtractor

TemplateMetadataExtractor()

Extracts metadata from function implementations for template rendering.

Source code in plating/templating/metadata.py
def __init__(self) -> None:
    self.config = get_config()

Attributes

config instance-attribute
config = get_config()

Functions

extract_function_metadata
extract_function_metadata(
    function_name: str, component_type: str
) -> dict[str, Any]

Extract metadata for a function to populate templates.

Parameters:

Name Type Description Default
function_name str

Name of the function

required
component_type str

Type of component (function, resource, etc.)

required

Returns:

Type Description
dict[str, Any]

Dictionary containing metadata for template rendering

Source code in plating/templating/metadata.py
def extract_function_metadata(self, function_name: str, component_type: str) -> dict[str, Any]:
    """Extract metadata for a function to populate templates.

    Args:
        function_name: Name of the function
        component_type: Type of component (function, resource, etc.)

    Returns:
        Dictionary containing metadata for template rendering
    """
    return self._generate_function_metadata(function_name)
discover_template_files
discover_template_files(docs_dir: Path) -> list[Path]

Discover all template files in a docs directory.

Parameters:

Name Type Description Default
docs_dir Path

Directory containing template files

required

Returns:

Type Description
list[Path]

List of template file paths

Source code in plating/templating/metadata.py
def discover_template_files(self, docs_dir: Path) -> list[Path]:
    """Discover all template files in a docs directory.

    Args:
        docs_dir: Directory containing template files

    Returns:
        List of template file paths
    """
    if not docs_dir.exists():
        return []

    template_files = []
    for template_file in docs_dir.glob("*.tmpl.md"):
        template_files.append(template_file)

    return template_files

AdornResult

Result from adorn operations.

Attributes

components_processed class-attribute instance-attribute
components_processed: int = 0
templates_generated class-attribute instance-attribute
templates_generated: int = 0
examples_created class-attribute instance-attribute
examples_created: int = 0
errors class-attribute instance-attribute
errors: list[str] = field(factory=list)
success property
success: bool

Whether the operation succeeded.

ArgumentInfo

Information about a function argument.

Attributes

name instance-attribute
name: str
type instance-attribute
type: str
description class-attribute instance-attribute
description: str = ''
required class-attribute instance-attribute
required: bool = True

Functions

to_dict
to_dict() -> dict[str, Any]

Convert to dictionary for serialization.

Source code in plating/types.py
def to_dict(self) -> dict[str, Any]:
    """Convert to dictionary for serialization."""
    return {
        "name": self.name,
        "type": self.type,
        "description": self.description,
        "required": self.required,
    }
from_dict classmethod
from_dict(data: dict[str, Any]) -> ArgumentInfo

Create from dictionary.

Source code in plating/types.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "ArgumentInfo":
    """Create from dictionary."""
    return cls(
        name=data.get("name", ""),
        type=data.get("type", ""),
        description=data.get("description", ""),
        required=data.get("required", True),
    )

ComponentType

Bases: Enum

Type-safe component types for Terraform/OpenTofu providers.

Supported types: - RESOURCE: Terraform resources - DATA_SOURCE: Terraform data sources - FUNCTION: Provider-defined functions - PROVIDER: Provider configuration

Attributes

RESOURCE class-attribute instance-attribute
RESOURCE = 'resource'
DATA_SOURCE class-attribute instance-attribute
DATA_SOURCE = 'data_source'
FUNCTION class-attribute instance-attribute
FUNCTION = 'function'
PROVIDER class-attribute instance-attribute
PROVIDER = 'provider'
display_name property
display_name: str

Get the formatted display name.

output_subdir property
output_subdir: str

Get the output subdirectory name for Terraform Registry structure.

PlateResult

Result from plate operations.

Attributes

bundles_processed class-attribute instance-attribute
bundles_processed: int = 0
files_generated class-attribute instance-attribute
files_generated: int = 0
duration_seconds class-attribute instance-attribute
duration_seconds: float = 0.0
errors class-attribute instance-attribute
errors: list[str] = field(factory=list)
output_files class-attribute instance-attribute
output_files: list[Path] = field(factory=list)
success property
success: bool

Whether the operation succeeded.

SchemaInfo

Structured schema information.

Attributes

description class-attribute instance-attribute
description: str = ''
attributes class-attribute instance-attribute
attributes: dict[str, dict[str, Any]] = field(factory=dict)
blocks class-attribute instance-attribute
blocks: dict[str, dict[str, Any]] = field(factory=dict)
test_only class-attribute instance-attribute
test_only: bool = False
component_of class-attribute instance-attribute
component_of: str | None = None

Functions

from_dict classmethod
from_dict(schema_dict: dict[str, Any]) -> SchemaInfo

Create SchemaInfo from a raw schema dictionary.

Source code in plating/types.py
@classmethod
def from_dict(cls, schema_dict: dict[str, Any]) -> "SchemaInfo":
    """Create SchemaInfo from a raw schema dictionary."""
    if not schema_dict:
        return cls()

    block = schema_dict.get("block", {})
    return cls(
        description=schema_dict.get("description", ""),
        attributes=block.get("attributes", {}),
        blocks=block.get("block_types", {}),
        test_only=schema_dict.get("test_only", False),
        component_of=schema_dict.get("component_of"),
    )
to_markdown
to_markdown() -> str

Convert schema to markdown format.

Source code in plating/types.py
def to_markdown(self) -> str:
    """Convert schema to markdown format."""
    if not self.attributes and not self.blocks:
        return ""

    lines = ["## Schema", ""]

    # Group attributes by type
    required_attrs = []
    optional_attrs = []
    computed_attrs = []

    for attr_name, attr_def in self.attributes.items():
        attr_type = self._format_type(attr_def.get("type"))
        description = attr_def.get("description", "")

        if attr_def.get("required"):
            required_attrs.append((attr_name, attr_type, description))
        elif attr_def.get("computed") and not attr_def.get("optional"):
            computed_attrs.append((attr_name, attr_type, description))
        else:
            optional_attrs.append((attr_name, attr_type, description))

    # Format sections
    if required_attrs:
        lines.extend(["### Required", ""])
        for name, type_str, desc in required_attrs:
            lines.append(f"- `{name}` ({type_str}) - {desc}")
        lines.append("")

    if optional_attrs:
        lines.extend(["### Optional", ""])
        for name, type_str, desc in optional_attrs:
            lines.append(f"- `{name}` ({type_str}) - {desc}")
        lines.append("")

    if computed_attrs:
        lines.extend(["### Read-Only", ""])
        for name, type_str, desc in computed_attrs:
            lines.append(f"- `{name}` ({type_str}) - {desc}")
        lines.append("")

    # Handle nested blocks
    if self.blocks:
        lines.extend(["### Blocks", ""])
        for block_name, block_def in self.blocks.items():
            max_items = block_def.get("max_items", 0)
            if max_items == 1:
                lines.append(f"- `{block_name}` (Optional)")
            else:
                lines.append(f"- `{block_name}` (Optional, List)")
        lines.append("")

    return "\n".join(lines)

ValidationResult

Result from validation operations with markdown linting support.

Attributes

total class-attribute instance-attribute
total: int = 0
passed class-attribute instance-attribute
passed: int = 0
failed class-attribute instance-attribute
failed: int = 0
skipped class-attribute instance-attribute
skipped: int = 0
duration_seconds class-attribute instance-attribute
duration_seconds: float = 0.0
failures class-attribute instance-attribute
failures: dict[str, str] = field(factory=dict)
errors class-attribute instance-attribute
errors: list[str] = field(factory=list)
lint_errors class-attribute instance-attribute
lint_errors: list[str] = field(factory=list)
terraform_version class-attribute instance-attribute
terraform_version: str = ''
success property
success: bool

Whether all validations passed.

Functions

with_circuit_breaker

with_circuit_breaker(
    failure_threshold: int = 3,
    recovery_timeout: float = 30.0,
    expected_exception: type[Exception] = Exception,
) -> Callable[[F], F]

Decorator for circuit breaker protection.

Parameters:

Name Type Description Default
failure_threshold int

Number of failures before opening circuit

3
recovery_timeout float

Time to wait before attempting recovery

30.0
expected_exception type[Exception]

Exception type that triggers circuit breaker

Exception
Source code in plating/decorators.py
def with_circuit_breaker(
    failure_threshold: int = 3, recovery_timeout: float = 30.0, expected_exception: type[Exception] = Exception
) -> Callable[[F], F]:
    """Decorator for circuit breaker protection.

    Args:
        failure_threshold: Number of failures before opening circuit
        recovery_timeout: Time to wait before attempting recovery
        expected_exception: Exception type that triggers circuit breaker
    """

    def decorator(func: F) -> F:
        circuit = SyncCircuitBreaker(
            failure_threshold=failure_threshold,
            recovery_timeout=recovery_timeout,
            expected_exception=expected_exception,
        )

        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args, **kwargs):
                return await circuit.call_async(func, *args, **kwargs)

            return async_wrapper
        else:

            @functools.wraps(func)
            def sync_wrapper(*args, **kwargs):
                return circuit.call(func, *args, **kwargs)

            return sync_wrapper

    return decorator

with_metrics

with_metrics(operation_name: str) -> Callable[[F], F]

Decorator for automatic metrics collection via structured logging.

Parameters:

Name Type Description Default
operation_name str

Name for the operation metrics

required
Source code in plating/decorators.py
def with_metrics(operation_name: str) -> Callable[[F], F]:
    """Decorator for automatic metrics collection via structured logging.

    Args:
        operation_name: Name for the operation metrics
    """

    def decorator(func: F) -> F:
        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args, **kwargs):
                start = time.perf_counter()
                try:
                    result = await func(*args, **kwargs)
                    duration = time.perf_counter() - start
                    logger.info(
                        f"Operation {operation_name} completed",
                        operation=operation_name,
                        status="success",
                        duration_seconds=duration,
                    )
                    return result
                except Exception as e:
                    duration = time.perf_counter() - start
                    logger.error(
                        f"Operation {operation_name} failed",
                        operation=operation_name,
                        status="error",
                        error=type(e).__name__,
                        duration_seconds=duration,
                    )
                    raise

            return async_wrapper
        else:

            @functools.wraps(func)
            def sync_wrapper(*args, **kwargs):
                start = time.perf_counter()
                try:
                    result = func(*args, **kwargs)
                    duration = time.perf_counter() - start
                    logger.info(
                        f"Operation {operation_name} completed",
                        operation=operation_name,
                        status="success",
                        duration_seconds=duration,
                    )
                    return result
                except Exception as e:
                    duration = time.perf_counter() - start
                    logger.error(
                        f"Operation {operation_name} failed",
                        operation=operation_name,
                        status="error",
                        error=type(e).__name__,
                        duration_seconds=duration,
                    )
                    raise

            return sync_wrapper

    return decorator

with_retry

with_retry(
    max_attempts: int = 3,
    backoff: str = "exponential",
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    retryable_errors: tuple[type[Exception], ...] = (
        Exception,
    ),
) -> Callable[[F], F]

Decorator for automatic retry with exponential backoff.

Parameters:

Name Type Description Default
max_attempts int

Maximum number of retry attempts

3
backoff str

Backoff strategy ("exponential", "linear", "constant")

'exponential'
base_delay float

Base delay between retries

1.0
max_delay float

Maximum delay between retries

60.0
retryable_errors tuple[type[Exception], ...]

Tuple of exception types that should trigger retry

(Exception,)
Source code in plating/decorators.py
def with_retry(
    max_attempts: int = 3,
    backoff: str = "exponential",
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    retryable_errors: tuple[type[Exception], ...] = (Exception,),
) -> Callable[[F], F]:
    """Decorator for automatic retry with exponential backoff.

    Args:
        max_attempts: Maximum number of retry attempts
        backoff: Backoff strategy ("exponential", "linear", "constant")
        base_delay: Base delay between retries
        max_delay: Maximum delay between retries
        retryable_errors: Tuple of exception types that should trigger retry
    """

    def decorator(func: F) -> F:
        # Convert string to BackoffStrategy enum
        backoff_strategy = {
            "exponential": BackoffStrategy.EXPONENTIAL,
            "linear": BackoffStrategy.LINEAR,
            "fixed": BackoffStrategy.FIXED,
            "constant": BackoffStrategy.FIXED,  # Map constant to fixed
        }.get(backoff, BackoffStrategy.EXPONENTIAL)

        retry_policy = RetryPolicy(
            max_attempts=max_attempts,
            backoff=backoff_strategy,
            base_delay=base_delay,
            max_delay=max_delay,
            retryable_errors=retryable_errors,
        )
        retry_executor = RetryExecutor(retry_policy)

        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args, **kwargs):
                return await retry_executor.execute_async(func, *args, **kwargs)

            return async_wrapper
        else:

            @functools.wraps(func)
            def sync_wrapper(*args, **kwargs):
                return retry_executor.execute_sync(func, *args, **kwargs)

            return sync_wrapper

    return decorator

with_timing

with_timing(func: F) -> F

Decorator for automatic timing with structured logging.

Uses foundation's timed_block for consistent timing and logging.

Source code in plating/decorators.py
def with_timing(func: F) -> F:
    """Decorator for automatic timing with structured logging.

    Uses foundation's timed_block for consistent timing and logging.
    """
    if asyncio.iscoroutinefunction(func):

        @functools.wraps(func)
        async def async_wrapper(*args, **kwargs):
            from provide.foundation.utils import timed_block

            operation_name = f"{func.__module__}.{func.__name__}"
            with timed_block(logger, operation_name):
                return await func(*args, **kwargs)

        return async_wrapper
    else:

        @functools.wraps(func)
        def sync_wrapper(*args, **kwargs):
            from provide.foundation.utils import timed_block

            operation_name = f"{func.__module__}.{func.__name__}"
            with timed_block(logger, operation_name):
                return func(*args, **kwargs)

        return sync_wrapper

get_plating_registry

get_plating_registry(
    package_name: str | None = None,
) -> PlatingRegistry

Get or create the global plating registry.

Parameters:

Name Type Description Default
package_name str | None

Package to search for components, or None to search all packages

None

Returns:

Type Description
PlatingRegistry

PlatingRegistry instance

Source code in plating/registry.py
def get_plating_registry(package_name: str | None = None) -> PlatingRegistry:
    """Get or create the global plating registry.

    Args:
        package_name: Package to search for components, or None to search all packages

    Returns:
        PlatingRegistry instance
    """
    global _global_registry
    if _global_registry is None:
        _global_registry = PlatingRegistry(package_name)
    return _global_registry

reset_plating_registry

reset_plating_registry() -> None

Reset the global registry (primarily for testing).

Source code in plating/registry.py
def reset_plating_registry() -> None:
    """Reset the global registry (primarily for testing)."""
    global _global_registry
    _global_registry = None