Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

Flask: UnboundLocalError: cannot access local variable Migrate

Verified FixPython 3.10+Flask 3.0+Silo: flask

Quick Fix / Solution Rapide

This error occurs when Python tries to reference an imported extension class (such as Migrate) inside a function where the same name is assigned locally later in the block. Use distinct variable names and instantiate extensions globally.

Root Cause Analysis

This error occurs when Python compiles a function containing a local variable assignment that shares the exact same name as an imported class or global object (such as Migrate or db), causing Python to treat the variable as local throughout the entire function scope.

Root Cause 1: Python Scope and Local Variable Binding Rules

In Python, if a variable is assigned anywhere within a function body (e.g. Migrate = Migrate(app, db)), Python's compiler marks that variable name as strictly local to that entire function scope. Any attempt to read or reference that variable before the assignment line executes raises UnboundLocalError: cannot access local variable 'Migrate' where it is not associated with a value.

Root Cause 2: Naming the Instance Variable Identically to the Class

Developers frequently write Migrate = Migrate(app, db) with uppercase 'M', intending to create an instance of the Migrate class. Because the left-hand side assignment reuses the exact identifier of the imported class, the right-hand side evaluation fails because the local variable Migrate has not yet been bound.

Root Cause 3: Misconfigured Application Factory Pattern

In the Flask application factory pattern (create_app()), extensions should be instantiated globally without arguments (migrate = Migrate()) and bound to the application inside the factory using migrate.init_app(app, db). Instantiating extensions directly inside create_app() creates variable scoping issues.

Root Cause 4: Circular Import Workarounds

Attempting to resolve circular imports between models.py and app.py by placing from flask_migrate import Migrate inside function blocks while assigning to variables of similar names triggers scope collisions.

Reproduction Code (MCVE)

Example: Bug Reproduction
Migrate = "GlobalMigrateExtension"

def create_app_broken():
    # Referencing Migrate before local assignment creates an unbound local variable in Python
    app_instance = {"extension": Migrate}
    Migrate = None  # Local assignment causes Python compiler to treat Migrate as local throughout create_app_broken
    return app_instance

create_app_broken()

Solution 1: Adopt the Standard Flask Extension Initialization Pattern

Instantiate extension objects globally with lowercase instance names (migrate = Migrate()) and bind them in the factory with .init_app().

Example: Recommended Solution
from flask import Flask

# Simulating extension classes (like Flask-SQLAlchemy or Flask-Migrate)
class SQLAlchemyExtension:
    def init_app(self, app):
        self.app = app

class MigrateExtension:
    def init_app(self, app, db):
        self.app = app
        self.db = db

# Solution 1: Instantiate extensions globally and bind them with init_app() in the factory
db = SQLAlchemyExtension()
migrate = MigrateExtension()

def create_app() -> Flask:
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    
    db.init_app(app)
    migrate.init_app(app, db)
    
    return app

test_app = create_app()
print("Flask app created successfully with initialized extensions:", test_app.name)
assert bool(test_app.name)

Solution 2: Use Distinct Names for Extension Classes and Instances

Always maintain strict naming conventions: PascalCase for classes (Migrate, SQLAlchemy) and snake_case for instances (migrate_instance, db_session).

Example: Alternative Solution
from flask import Flask

# Solution 2: Avoid variable name collisions between classes and instance variables
class DatabaseMigrator:
    def __init__(self, app=None):
        if app:
            self.init_app(app)
    def init_app(self, app):
        self.app = app

# Distinct naming: migrator_instance vs DatabaseMigrator
migrator_instance = DatabaseMigrator()

def init_services(app: Flask):
    migrator_instance.init_app(app)
    return migrator_instance

app = Flask("test_app")
service = init_services(app)
print("Service bound cleanly:", service.app.name)
assert service.app.name == "test_app" 

A common trap is attempting to fix UnboundLocalError by adding global Migrate inside create_app(). Using global mutates module-level state, breaks test isolation across test suites, and prevents running multiple application instances in parallel. The clean solution is the init_app factory pattern.

Another edge case occurs with flask_cors.CORS or flask_jwt_extended.JWTManager: naming instances CORS = CORS(app) triggers this exact UnboundLocalError. Always name your instance cors = CORS(app).

Contrast UnboundLocalError with NameError: NameError occurs when a variable name has not been defined anywhere in local, global, or built-in scopes; UnboundLocalError is a specific subclass of NameError that occurs when a variable is known to exist in the local scope but is accessed before assignment.