Custom Modules¶
MoltPy's module system is fully extensible. You can create custom modules without modifying any core code.
Module Structure¶
Each module lives in src/moltpy/modules/<module_name>/ and contains:
my_module/
├── manifest.json # Required: module metadata
├── module.py # Optional: BaseModule subclass with hooks
├── templates/ # Jinja2 templates for generated files
│ └── app/
│ └── my_module.py.j2
├── snippets/ # Optional: fragments injected into shared files
│ └── pyproject.toml.j2
└── static/ # Optional: static files copied verbatim
Manifest Format¶
{
"name": "my_module",
"description": "What this module does",
"category": "infrastructure",
"implies": ["database"],
"conflicts": ["other_module"],
"default": false,
"variables": {
"my_setting": {
"type": "str",
"default": "default_value",
"prompt": "Human-readable prompt"
},
"my_advanced_setting": {
"type": "bool",
"default": false,
"prompt": "Enable advanced feature?",
"advanced": true
}
}
}
Manifest Fields¶
| Field | Required | Description |
|---|---|---|
name |
Yes | Module identifier (snake_case) |
description |
Yes | Short human-readable description |
category |
Yes | Grouping key for UI |
implies |
No | Auto-selected dependencies |
conflicts |
No | Modules that cannot coexist |
default |
No | Pre-selected in custom mode |
variables |
No | Per-module configuration variables |
Variable Fields¶
| Field | Required | Description |
|---|---|---|
type |
Yes | Variable type: str, int, bool, or choice |
default |
Yes | Default value used when the variable is skipped |
prompt |
Yes | Human-readable prompt text shown in the wizard |
advanced |
No | If true, the variable is only shown when the user enables "advanced configuration options". Defaults to false |
choices |
No | Required when type is choice. List of valid string options |
Templates¶
Files ending in .j2 are processed as Jinja2 templates. The .j2 suffix is stripped in the output.
Template Context¶
Your templates have access to:
{{ project.name }} # Project name
{{ project.description }} # Project description
{{ project.python_version }} # Python version
{{ modules }} # List of resolved module names
{{ modules_info }} # Dict of module metadata and variables
{{ has_module('auth') }} # Check if a module is included
{{ config }} # Full ProjectConfig instance
Example Template¶
# templates/app/core/my_module.py.j2
"""My custom module."""
from __future__ import annotations
from app.config import settings
{% if has_module('database') %}
from app.database import async_session
{% endif %}
MY_SETTING = settings.MY_MODULE_SETTING
Snippets¶
Snippets are small fragments appended to shared files. Use them for:
- Adding dependencies to
pyproject.toml - Adding environment variables to
.env.example - Adding imports or routes to
app/main.py
Snippet File Naming¶
The relative path within snippets/ determines the target file:
snippets/pyproject.toml.j2 → appends to pyproject.toml
snippets/.env.example.j2 → appends to .env.example
snippets/app/main.py.j2 → appends to app/main.py
Note: Snippets replace <!-- moltpy:SNIPPETS --> or # moltpy:SNIPPETS markers if present in the target file; otherwise they are appended at the end. For files like app/main.py where order matters, add a marker to the core template or use conditional blocks directly.
Example Snippet¶
Static Files¶
Files in static/ are copied verbatim into the generated project. Use them for:
- Configuration files
- Assets
- Scripts that do not need templating
Module Hooks¶
Modules can implement pre_render() and post_render() hooks by creating a module.py file with a BaseModule subclass:
from moltpy.modules.base import BaseModule
class MyModule(BaseModule):
def pre_render(self, context: dict) -> dict:
# Modify template context before rendering
context["my_module_enabled"] = True
return context
def post_render(self, target_dir: Path, context: dict) -> None:
# Perform additional file operations after rendering
pass
External Modules¶
MoltPy can load modules from external sources without modifying core code.
Local External Modules¶
Place module directories in ~/.config/moltpy/modules/:
~/.config/moltpy/modules/
├── my_custom_module/
│ ├── manifest.json
│ ├── templates/
│ └── snippets/
These modules are auto-discovered alongside built-in modules.
GitHub Modules¶
Reference modules directly from GitHub repositories:
Format: github:<user>/<repo>#<module-dir-name>
With version pinning:
GitHub modules are cached in ~/.cache/moltpy/github_modules/. Use --refresh-modules to force a re-download.
Template Overrides¶
You can override built-in templates (core or module) without forking MoltPy:
Place override templates in the same relative path as the built-ins:
my-templates/
├── app/
│ └── main.py.j2 # Overrides core template
└── auth/
└── templates/
└── app/
└── auth.py.j2 # Overrides auth module template
Override templates are validated for Jinja2 syntax before any files are written.
Testing Your Module¶
- Place your module in
src/moltpy/modules/<name>/(for built-in) or~/.config/moltpy/modules/<name>/(for external) - Run
moltpy modules listto verify it is discovered - Run
moltpy modules info <name>to verify the manifest - Create a test project:
moltpy new --modules <name> --no-interactive - Use
--dry-runto preview output without writing files
Module Best Practices¶
- Keep modules focused — One clear purpose per module
- Use
implies— Do not force users to manually select dependencies - Use
has_module()— Make your templates conditional when depending on other modules - Document variables — Clear prompts help users configure correctly
- Use
advancedfor power-user options — Keep the wizard simple for beginners by marking complex or niche variables as"advanced": true - Handle missing config gracefully — Use defaults when variables are not set