troubleshooting2024-09-15·7·269/348

Flask Blueprint 라우팅 충돌

Diagnosing and resolving Flask Blueprint routing conflicts — when URL rules collide, duplicate endpoints break your application, and blueprints override each other.

Flask Blueprint 라우팅 충돌

Introduction

Flask Blueprints are the standard way to organize Flask applications into modular components. However, as your application grows and you add more blueprints, routing conflicts become inevitable. Two blueprints may define the same URL rule, or endpoints may collide, causing unpredictable behavior.

I encountered this while refactoring a monolithic Flask application in Lisbon into separate blueprint modules. The application had grown organically over two years, and the routing table had become a tangled mess of conflicting URL rules.

Environment

Flask 3.0.2
Python 3.12.3
Werkzeug 3.0.1

Problem

Error 1: Duplicate endpoint names

from flask import Flask, Blueprint

api = Blueprint("api", __name__)
admin = Blueprint("admin", __name__)

@api.route("/users")
def get_users():
    return "API users"

@admin.route("/users")
def get_users():  # Same function name!
    return "Admin users"

app = Flask(__name__)
app.register_blueprint(api)
app.register_blueprint(admin)
AssertionError: View function 'api.get_users' is already mapped 
to endpoint 'get_users'

Error 2: URL rule conflicts

from flask import Flask, Blueprint

web = Blueprint("web", __name__)
api = Blueprint("api", __name__)

@web.route("/api/data")
def web_api_data():
    return "Web data"

@api.route("/api/data")
def api_data():
    return "API data"

app = Flask(__name__)
app.register_blueprint(web)
app.register_blueprint(api)
# Both respond to /api/data — which one wins?

Error 3: Blueprint prefix collision

from flask import Flask, Blueprint

users_bp = Blueprint("users", __name__)
posts_bp = Blueprint("posts", __name__)

@users_bp.route("/list")
def user_list():
    return "User list"

@posts_bp.route("/list")
def post_list():
    return "Post list"

app = Flask(__name__)
app.register_blueprint(users_bp, url_prefix="/api")
app.register_blueprint(posts_bp, url_prefix="/api")
# Both have /api/list — collision!

Error 4: Static file conflicts

from flask import Flask, Blueprint

static_bp = Blueprint("static", __name__, static_folder="static")
another_bp = Blueprint("another", __name__, static_folder="static")

app = Flask(__name__)
app.register_blueprint(static_bp)
app.register_blueprint(another_bp)
# Both try to serve from /static/ — conflict

Analysis

Flask Blueprint routing conflicts have several causes.

Cause 1: Same endpoint names. Flask uses function names as endpoint names by default. Two blueprints defining functions with the same name create a collision, even if they are in different modules.

Cause 2: Overlapping URL rules. When two blueprints register routes for the same URL, Flask uses the last registered blueprint. This can cause silent bugs where the wrong handler is invoked.

Cause 3: URL prefix collisions. When blueprints are registered with the same prefix, their routes may overlap.

Cause 4: Missing name parameter. If you do not provide a unique name when creating a Blueprint, Flask may generate conflicting names.

Check your routing table:

from flask import Flask

app = Flask(__name__)

# Register all blueprints here...

# Print all registered rules
with app.app_context():
    for rule in app.url_map.iter_rules():
        endpoint = rule.endpoint
        methods = rule.methods
        print(f"{rule.rule} -> {endpoint} [{', '.join(methods)}]")

Solution

Fix 1: Use unique endpoint names with name parameter

from flask import Flask, Blueprint

api_users = Blueprint("api_users", __name__)
admin_users = Blueprint("admin_users", __name__)

@api_users.route("/users")
def get_api_users():
    return "API users"

@admin_users.route("/users")
def get_admin_users():
    return "Admin users"

app = Flask(__name__)
app.register_blueprint(api_users)
app.register_blueprint(admin_users)

Fix 2: Use URL prefixes to separate routes

from flask import Flask, Blueprint

api = Blueprint("api", __name__, url_prefix="/api")
admin = Blueprint("admin", __name__, url_prefix="/admin")

@api.route("/users")
def api_users():
    return "API users"

@admin.route("/users")
def admin_users():
    return "Admin users"

app = Flask(__name__)
app.register_blueprint(api)
app.register_blueprint(admin)

# /api/users and /admin/users — no conflict

Fix 3: Use unique function names within blueprints

from flask import Flask, Blueprint

api = Blueprint("api", __name__)
admin = Blueprint("admin", __name__)

@api.route("/users")
def api_get_users():
    return "API users"

@admin.route("/users")
def admin_get_users():
    return "Admin users"

app = Flask(__name__)
app.register_blueprint(api)
app.register_blueprint(admin)

Fix 4: Validate routes at startup

from flask import Flask

def validate_routes(app):
    """Check for duplicate URL rules."""
    seen = {}
    for rule in app.url_map.iter_rules():
        key = (rule.rule, tuple(sorted(rule.methods - {'OPTIONS', 'HEAD'})))
        if key in seen:
            print(f"CONFLICT: {rule.rule} -> {rule.endpoint} and {seen[key]}")
        seen[key] = rule.endpoint

app = Flask(__name__)
# Register all blueprints...
validate_routes(app)

Fix 5: Use endpoint parameter to avoid collisions

from flask import Flask, Blueprint

api = Blueprint("api", __name__)
admin = Blueprint("admin", __name__)

@api.route("/users", endpoint="api_users")
def api_users_handler():
    return "API users"

@admin.route("/users", endpoint="admin_users")
def admin_users_handler():
    return "Admin users"

app = Flask(__name__)
app.register_blueprint(api)
app.register_blueprint(admin)

Lessons Learned

  • Always give blueprints unique names when creating them — do not rely on the module name.
  • Use URL prefixes to separate blueprint routes logically.
  • Use unique function names within each blueprint to avoid endpoint collisions.
  • Validate your routing table at application startup to catch conflicts early.
  • Use the endpoint parameter in @route when you need explicit control over endpoint names.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.