Skip to main content

Creating Component Plugins

Castlecraft Architect's plugin system allows you to extend its core functionality by defining new component types, overriding existing ones, and customizing how component file paths are resolved. This guide will walk you through the process of creating your own component plugin.

Overview

Component plugins are Python packages that Architect can discover and load at startup. They interact with Architect's core services through a dedicated PluginRegistrationAPI.

With plugins, you can:

  • Define New Component Types: Introduce entirely new kinds of architectural components with their own metadata, generation logic (handlers), and file placement rules.
  • Override Core Components: Change the behavior of existing components, such as modifying their default file templates, the code they generate, or where their files are located.
  • Customize Path Resolution: Provide custom logic to determine the file path for any component type.

Plugin Structure

A component plugin is typically a Python package with a specific structure to make it discoverable by Architect.

1. Python Package

Your plugin should be a standard Python package (i.e., a directory containing an __init__.py file and your plugin modules).

2. Registration Function

The core of your plugin is a registration function. This function will be called by Architect when your plugin is loaded. It receives an instance of the PluginRegistrationAPI and the plugin's name. For detailed information on the PluginRegistrationAPI, see its reference documentation. Example plugin.py:

import logging
from typing import TYPE_CHECKING, Any, Dict, Optional

# Import necessary types from Architect (adjust paths if your plugin is external)
if TYPE_CHECKING:
from castlecraft_architect.infrastructure.code_generation.plugin_api import PluginRegistrationAPI
from castlecraft_architect.infrastructure.code_generation.component_registry_service import ComponentMeta
from castlecraft_architect.infrastructure.code_generation.component_handlers.base_handler import ComponentTypeHandler

logger = logging.getLogger(__name__)

# --- 1. Define your Custom Component Handler (if any) ---
# class MyCustomComponentHandler(ComponentTypeHandler):
# # ... implementation ...

# --- 2. Define Metadata for your Component ---
# MY_COMPONENT_META: "ComponentMeta" = {
# "component_slug": "my_customs",
# "file_name_template": "my_custom_${name}.ext",
# "required_context": ["bounded_ctx"],
# "description": "A custom component type.",
# }

# --- 3. Define a Custom Path Resolver (if any) ---
# def my_custom_path_resolver(component_name: str, context_kwargs: Dict[str, Any]) -> Optional[str]:
# # ... logic to return a path string ...
# return f"custom_components/{context_kwargs.get('bounded_ctx', 'shared')}/{component_name}.ext"

def register_my_plugin_components(api: "PluginRegistrationAPI", plugin_name: str):
logger.info(f"Plugin '{plugin_name}' is registering its components.")

# Example: Registering a new component
# api.register_component(
# component_type_key="my_custom_component",
# meta=MY_COMPONENT_META,
# handler_class=MyCustomComponentHandler,
# path_resolver=my_custom_path_resolver,
# is_override=False, # Set to True if overriding a core component
# plugin_name=plugin_name
# )

logger.info(f"Plugin '{plugin_name}' finished registration.")

3. Entry Point Definition (pyproject.toml)

To make your plugin discoverable, you need to define an entry point in your plugin's pyproject.toml file. Architect looks for plugins under the architect.components entry point group.

Example pyproject.toml for your plugin:

[project.entry-points."architect.components"]
my_plugin_name = "my_plugin_package.plugin:register_my_plugin_components"
# ^ Unique name for your plugin entry point
# ^ Path to your plugin package and module
# ^ Name of your registration function
  • my_plugin_name: This is the name Architect will use to identify your plugin (e.g., in logs). It can be different from your package name.
  • my_plugin_package.plugin:register_my_plugin_components: This points to the registration function within your plugin package.

Defining Components

When registering a component, you'll provide:

  • Component Metadata (ComponentMeta): A dictionary defining properties like component_slug, file_name_template, required_context, etc. (See ComponentRegistryService for more details on ComponentMeta structure).
  • Component Handler (ComponentTypeHandler): A class that inherits from ComponentTypeHandler and implements methods like generate_content() to define the code generation logic for this component type.
  • Path Resolver (Optional): A callable function that takes component_name: str and context_kwargs: Dict[str, Any] and returns an Optional[str] representing the file path for the component.

Using PluginRegistrationAPI

The PluginRegistrationAPI object passed to your registration function has one primary method:

  • register_component(...):
    • component_type_key (str): A unique string key for your component type (e.g., "my_custom_widget").
    • meta (ComponentMeta): The metadata dictionary for your component.
    • handler_class (Type[ComponentTypeHandler]): The handler class for your component.
    • path_resolver (Optional[Callable]): Your custom path resolver function (optional). If not provided for a new component, Architect won't be able to determine its path unless it matches a pattern in the core PROJECT_STRUCTURE_DEFINITION.
    • is_override (bool): Set to True if you are intentionally overriding a core component type or a component type registered by another plugin. Defaults to False. If False and the component_type_key already exists, registration will be skipped.
    • plugin_name (str): The name of your plugin (automatically passed by the PluginManager).

Example Plugin

For a practical example, refer to the architect_sample_plugin located in the examples/ directory of the Architect project. It demonstrates:

  • Defining a new component (sample_note).
  • Providing a custom handler and path resolver for it.
  • Overriding a core component (dto) with new metadata, handler, and path resolver.

Installation and Discovery

For Architect to discover and load your plugin:

  1. Ensure your plugin package is installed in the same Python environment as Architect. During development, you can install it in editable mode:
    pip install -e .
    (Run this from your plugin's root directory, where pyproject.toml is located).
  2. Architect will automatically scan for entry points in the architect.components group during startup.

By following these steps, you can effectively extend Castlecraft Architect to support new component types and customize existing behaviors to fit your project's specific needs.