Architecture¶
MoltPy is designed around a modular pipeline that transforms user configuration into a fully rendered project directory.
High-Level Flow¶
CLI (cli.py)
↓
Config File (optional) — load from JSON/YAML via --config-file
↓
Wizard (wizard.py) — interactive prompts (if no config file and interactive)
↓
Config (config.py) — Pydantic validation
↓
External Fetcher (external.py) — clone GitHub modules, scan local external dirs
↓
Registry (registry.py) — module discovery, dependency resolution, conflict detection
↓
Renderer (renderer.py) — Jinja2 template rendering (SandboxedEnvironment)
↓
Builder (builder.py) — syntax validation, .env generation, post-generation hooks
Resource Scaffolding (generator.py)
↓
moltpy generate resource <Model> [fields...]
Components¶
CLI (cli.py)¶
Entry point using Click. Handles:
moltpy new— Project creation (interactive and non-interactive). Supports--dry-run,--force,--config-file,--skip-post-generation,--template-dirfor template overrides, and--refresh-modulesto force re-fetch GitHub modules.moltpy init-config— Generate a starter configuration file with inline documentationmoltpy preset— Preset management (list, save, delete)moltpy modules— Module exploration (list, info)moltpy generate resource— CRUD scaffolding in existing projects
Wizard (wizard.py)¶
Interactive prompt flow using Inquirer and Rich:
- Project name, description, Python version
- Preset selection
- Module selection (if custom preset)
- Module variable configuration
- Post-generation options (uv sync, git init, pre-commit)
- Module summary panel showing auto-implied dependencies
Skipped when --config-file or --no-interactive is used.
Config (config.py)¶
Pydantic v2 models for type-safe validation:
ProjectConfig— Project name, description, Python version, modules, variablesModuleManifest— Module metadata frommanifest.jsonPreset— Named configuration with module listModuleVariable— Per-module configuration variable schema
Also supports file I/O:
- ProjectConfig.load_from_file(path) — Load from .json, .yaml, or .yml
- ProjectConfig.to_yaml(registry) — Serialize to commented YAML
Registry (registry.py)¶
ModuleRegistry handles module lifecycle:
- Discovery — Scans
src/moltpy/modules/at runtime, plus external paths (~/.config/moltpy/modules/and fetched GitHub repos). Discoversmanifest.jsonand optionalmodule.pyfiles containingBaseModulesubclasses. - Dependency Resolution — Topological sort for
implies - Conflict Detection — Validates
conflictsrelationships and reports unknown modules - Variable Access — Exposes module configuration variables
- Hook Loading — Instantiates
BaseModulesubclasses forpre_render/post_renderhook execution
registry = ModuleRegistry()
registry.list_modules() # All discovered modules
registry.get("auth") # Single module metadata
registry.resolve_dependencies(["auth"]) # ["auth", "users", "database"]
registry.validate_selection(["auth", "celery"]) # (False, ["arq conflicts with celery"])
Renderer (renderer.py)¶
TemplateRenderer orchestrates file generation:
- Core Templates — Renders
src/moltpy/templates/(always included) - Module Templates — Renders selected module
templates/directories - Snippets — Injects snippet fragments into shared files at
<!-- moltpy:SNIPPETS -->or# moltpy:SNIPPETSmarkers, or appends at the end if no marker is found - Static Files — Copies static assets verbatim
- Dry Run Mode — Collects file metadata without writing to disk
- Template Overrides — User-provided
--template-diris prepended to the Jinja2 loader, allowing core and module template overrides - Security — Uses
jinja2.sandbox.SandboxedEnvironmentto prevent untrusted external templates from accessing dangerous Python internals
Custom Jinja2 filters:
- to_snake_case — MyProject → my_project
- to_pascal_case — my_project → MyProject
- to_kebab_case — my_project → my-project
Resource Generator (generator.py)¶
ResourceGenerator scaffolds CRUD resources into existing MoltPy projects:
- Project Validation — Ensures
app/main.pyandapp/database.pyexist - Field Parsing — Splits
name:typearguments and maps to SQLAlchemy/Pydantic types - Template Rendering — Renders 5 Jinja2 templates from
src/moltpy/generator_templates/ - Code Injection — Injects router imports into
app/main.py(orapp/routers/v1/api.py) and registers models inapp/models/__init__.py - Alembic Migration — Runs
alembic revision --autogeneratewith graceful degradation if the database is unreachable - Auto-Formatting — Runs
ruff formatandruff check --fixon generated files
Builder (builder.py)¶
ProjectBuilder orchestrates the full generation flow:
- Validates module selection via Registry
- Runs
BaseModule.pre_render()hooks for selected modules - Renders all templates via Renderer (supports
--dry-run) - Validates generated Python syntax using
py_compileon every.pyfile - Runs
BaseModule.post_render()hooks for selected modules - Generates
.envfile with secure random secrets (viasecrets.token_hex()) - Runs optional post-generation hooks (skipped with
--skip-post-generation): uv sync— Install dependenciesgit init+ initial commitpre-commit install- Checks for existing directories and refuses to overwrite unless
--forceis passed
Module System¶
Discovery¶
Modules are auto-discovered by scanning src/moltpy/modules/ and any external paths:
for dir in modules_dir.iterdir():
manifest = dir / "manifest.json"
if manifest.exists():
register_module(manifest)
for ext_dir in external_paths:
for dir in ext_dir.iterdir():
manifest = dir / "manifest.json"
if manifest.exists():
register_module(manifest) # Overrides built-in if same name
No code changes are required to add a new module.
Dependency Resolution¶
Uses topological sort to resolve implies chains:
Input: ["auth"]
auth implies: ["users"]
users implies: ["database"]
Output: ["database", "users", "auth"]
Template Context¶
Each template receives:
{
"project": {
"name": "my_api",
"description": "My API",
"python_version": "3.12",
},
"modules": ["database", "users", "auth"],
"modules_info": {
"auth": {
"manifest": ModuleManifest(...),
"variables": {"token_expire_minutes": 30},
},
},
"has_module": lambda name: name in modules,
"config": ProjectConfig(...),
}
Security¶
SECRET_KEYand database passwords generated withsecrets.token_hex()(cryptographically secure).envfile is never overwritten if it already exists- Generated projects include
.env.examplebut.gitignoreexcludes.env - Auth module uses bcrypt for password hashing and JWT for tokens