Skip to main content

Database Migrations

Framework M uses Alembic to manage database migrations. It supports both automatic discovery of DocTypes and selective migration based on "installed apps".

Migration Anatomy

Migrations are stored in the host application's directory or within modular library paths.

  • alembic/env.py: Orchestrates how DocTypes are discovered and how MetaData is built.
  • alembic/versions/ or migrations/: Contains the generated migration scripts.
  • alembic.ini: Configuration file for Alembic.

Discovery Strategies

Framework M supports two modes of DocType discovery for migrations:

This mode mimics Frappe's behavior. Migrations ONLY manage DocTypes from apps specified in the INSTALLED_APPS list.

How to trigger:

  • Use the --apps flag in the CLI:
    m migrate create "added finance" --apps "finance,m_imprest"
  • Or set the INSTALLED_APPS environment variable:
    export INSTALLED_APPS="finance,wms"
    m migrate status

2. Automated Entry-Point Discovery

In modular deployments, Framework M scans for apps registered via the framework_m.apps entry point.

Example pyproject.toml configuration:

[project.entry-points."framework_m.apps"]
wms = "wms.plugin:app"

The system will automatically find these apps and their associated DocTypes during the migration scan.

MIGRATIONS_DIR Discovery Sequence

When scanning an app, the engine looks for migration files in the following order:

  1. Custom Directory: A migrations/ directory at the package root.
  2. Alembic Default: The standard alembic/versions/ path.
  3. Fallback: Skips if no migration directory is found.

Sync vs. Migrate (The Dual-Track Approach)

To provide a Frappe-like "it just works" experience while maintaining production-grade control, Framework M uses a dual-track strategy:

1. The "Sync" Track (Declarative / Declarative DDL)

Use m migrate sync when you want the database to exactly match your current DocType definitions.

  • Behavior: Compares current DocTypes with the DB schema and applies CREATE TABLE / ALTER TABLE immediately.
  • Safety First: The sync engine is additive-only. It will NOT drop tables or columns found in the database but missing from code, as it lacks the physical code to execute destructive DDL.
  • Verification: Use m migrate verify to identify "ghost" tables or columns that are safe to prune manually via Alembic.
  • Self-Contained: No migration files are created. It is entirely driven by code.
  • Use Case: Rapid development and "Syncing" schema stability.

Smart Sync (Deterministic Optimization)

The sync command uses a deterministic hashing strategy to avoid expensive database reflection. On every run, it compares a checksum of your local DocType definition against a signature stored in the __schema_versions__ system table.

If the definitions haven't changed, the command skips the database scan entirely, resulting in near-instant execution. Use the --force flag to bypass this cache and force a full reflection. For more details on how this works, see the Deterministic Schema Migrations explanation.

Migration Hooks

Each app can implement before_sync and after_sync hooks in its migrations/__init__.py file to handle custom setup or cleanup logic. See the Migration Hooks Reference for more details.

2. The "Migrate" Track (Imperative / Versioned Patches)

Use the standard commands (create, run) when you need to ship ordered patches or logic-heavy transitions (data migrations, complex index changes).

  • Behavior: Uses version files inside each app's migration folder.
  • Traceability: Provides a clear audit log of every change applied to the database.
  • Use Case: Production rollouts and complex data transformations.

Comparisons with Frappe

ConceptFrappeFramework M
Auto-Syncbench migrate (sync phase)m migrate sync
Manual Patchespatches.txt + Python scriptsm migrate run + Alembic scripts
ModularityDistributed patches.txt in each appDistributed alembic/versions or migrations/ in each app
DependenciesHandled via after_install or global orderHandled via Alembic's depends_on

Virtual DocTypes (Schema-Less Support)

If you need a DocType for API exposure or SDUI modeling but do NOT want a corresponding database table, use the create_schema flag:

class MyVirtualDocType(DocType):
class Meta:
create_schema = False # The Migration Engine will skip this DocType

Virtual DocTypes are typically used with custom repositories that implement the RepositoryProtocol for alternative storage (e.g., Redis, YAML, or external APIs).

Safe-by-Default Deployment

The Migration Engine implements a Positive Table Identification model to prevent accidental data loss.

  • Managed Tables: Only tables corresponding to registered DocTypes are considered "managed".
  • External Tables: Tables like alembic_version or third-party artifacts are automatically ignored and will never be suggested for dropping.
  • Explicit Drops: Destructive operations are gated behind the --allow-drop flag:
    m migrate sync --allow-drop

Commands

CommandDescription
m migrate all(Recommended) Runs both versioned patches (run) and declarative sync.
m migrate sync [--force]Direct DocType -> DB synchronization (no files).
m migrate verifyPerforms a dry-run comparison between code and DB to assess risk.
m migrate initInitialize Alembic for this project.
m migrate create "msg" [--app ...]Generate a versioned patch for a specific app.
m migrate statusShow current database revision and pending status.

Dry-Run Verification

Before executing a sync or a migration, use m migrate verify. This command categorizes schema changes based on risk:

  • 🟢 SAFE: Non-destructive changes (e.g., adding a nullable column, creating a new table).
  • 🟡 WARNING: Changes with potential for data loss (e.g., shrinking a VARCHAR length).
  • 🔴 BREAKING: Destructive changes requiring a phased approach (e.g., dropping a column).

Production Migration Strategy

To ensure database stability, we classify all schema changes based on their risk level:

Change Classification

CategoryExamplesAuto-Apply (Dev)?Production Strategy
SafeAdd nullable column, add table, add index✅ YesApply directly (m migrate)
ReviewAdd non-nullable column, change type (compatible)⚠️ Generate onlyReview → Staging → Prod
DangerousDrop column, rename column, change type (incompatible)❌ NoManual migration with data handling

Zero-Downtime Deployment Strategy (DevOps)

By decoupling sync and run, Framework M enables advanced zero-downtime workflows:

  1. Stage 1: Safe Sync - Run m migrate sync (declarative) before updating application code. This adds new nullable columns or tables that don't break existing code.
  2. Stage 2: Code Rollout - Update application instances to the new version.
  3. Stage 3: Data Patches - Run m migrate run (imperative) to perform data transformations or breaking schema changes once the new code is fully operational.

Tips for Monorepos

In a monorepo, it is best to run migrations from the main app (apps/business-m) but specify which libs should be included in the schema using the --apps flag.