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 likecomponent_slug
,file_name_template
,required_context
, etc. (SeeComponentRegistryService
for more details onComponentMeta
structure). - Component Handler (
ComponentTypeHandler
): A class that inherits fromComponentTypeHandler
and implements methods likegenerate_content()
to define the code generation logic for this component type. - Path Resolver (Optional): A callable function that takes
component_name: str
andcontext_kwargs: Dict[str, Any]
and returns anOptional[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 corePROJECT_STRUCTURE_DEFINITION
.is_override (bool)
: Set toTrue
if you are intentionally overriding a core component type or a component type registered by another plugin. Defaults toFalse
. IfFalse
and thecomponent_type_key
already exists, registration will be skipped.plugin_name (str)
: The name of your plugin (automatically passed by thePluginManager
).
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:
- Ensure your plugin package is installed in the same Python environment as Architect. During development, you can install it in editable mode:
(Run this from your plugin's root directory, where
pip install -e .
pyproject.toml
is located). - 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.