Skip to content

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-dir for template overrides, and --refresh-modules to force re-fetch GitHub modules.
  • moltpy init-config — Generate a starter configuration file with inline documentation
  • moltpy 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:

  1. Project name, description, Python version
  2. Preset selection
  3. Module selection (if custom preset)
  4. Module variable configuration
  5. Post-generation options (uv sync, git init, pre-commit)
  6. 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, variables
  • ModuleManifest — Module metadata from manifest.json
  • Preset — Named configuration with module list
  • ModuleVariable — 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). Discovers manifest.json and optional module.py files containing BaseModule subclasses.
  • Dependency Resolution — Topological sort for implies
  • Conflict Detection — Validates conflicts relationships and reports unknown modules
  • Variable Access — Exposes module configuration variables
  • Hook Loading — Instantiates BaseModule subclasses for pre_render/post_render hook 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:

  1. Core Templates — Renders src/moltpy/templates/ (always included)
  2. Module Templates — Renders selected module templates/ directories
  3. Snippets — Injects snippet fragments into shared files at <!-- moltpy:SNIPPETS --> or # moltpy:SNIPPETS markers, or appends at the end if no marker is found
  4. Static Files — Copies static assets verbatim
  5. Dry Run Mode — Collects file metadata without writing to disk
  6. Template Overrides — User-provided --template-dir is prepended to the Jinja2 loader, allowing core and module template overrides
  7. Security — Uses jinja2.sandbox.SandboxedEnvironment to prevent untrusted external templates from accessing dangerous Python internals

Custom Jinja2 filters: - to_snake_caseMyProjectmy_project - to_pascal_casemy_projectMyProject - to_kebab_casemy_projectmy-project

Resource Generator (generator.py)

ResourceGenerator scaffolds CRUD resources into existing MoltPy projects:

  1. Project Validation — Ensures app/main.py and app/database.py exist
  2. Field Parsing — Splits name:type arguments and maps to SQLAlchemy/Pydantic types
  3. Template Rendering — Renders 5 Jinja2 templates from src/moltpy/generator_templates/
  4. Code Injection — Injects router imports into app/main.py (or app/routers/v1/api.py) and registers models in app/models/__init__.py
  5. Alembic Migration — Runs alembic revision --autogenerate with graceful degradation if the database is unreachable
  6. Auto-Formatting — Runs ruff format and ruff check --fix on generated files

Builder (builder.py)

ProjectBuilder orchestrates the full generation flow:

  1. Validates module selection via Registry
  2. Runs BaseModule.pre_render() hooks for selected modules
  3. Renders all templates via Renderer (supports --dry-run)
  4. Validates generated Python syntax using py_compile on every .py file
  5. Runs BaseModule.post_render() hooks for selected modules
  6. Generates .env file with secure random secrets (via secrets.token_hex())
  7. Runs optional post-generation hooks (skipped with --skip-post-generation):
  8. uv sync — Install dependencies
  9. git init + initial commit
  10. pre-commit install
  11. Checks for existing directories and refuses to overwrite unless --force is 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_KEY and database passwords generated with secrets.token_hex() (cryptographically secure)
  • .env file is never overwritten if it already exists
  • Generated projects include .env.example but .gitignore excludes .env
  • Auth module uses bcrypt for password hashing and JWT for tokens