Flask: UnboundLocalError: cannot access local variable Migrate
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)
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().
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).
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.