from werkzeug.routing import BaseConverter
from werkzeug.exceptions import NotFound
from flask import abort
class ModelConverter(BaseConverter):
model = None
pk_type = int
regex = r"\d+"
def __init__(self, url_map, *args, **kwargs):
super().__init__(url_map, *args, **kwargs)
if self.model is None:
raise RuntimeError("ModelConverter subclass must set `model`")
def to_python(self, value):
from extensions import db
try:
pk = self.pk_type(value)
except (TypeError, ValueError):
raise NotFound()
instance = db.session.get(self.model, pk)
if instance is None:
abort(404)
return instance
def to_url(self, value):
pk = getattr(value, "id", value)
return str(pk)
def make_model_converter(model, pk_type=int):
return type(
f"{model.__name__}Converter",
(ModelConverter,),
{"model": model, "pk_type": pk_type},
)
def register_model_converters(app, mapping):
for name, model in mapping.items():
app.url_map.converters[name] = make_model_converter(model)
from extensions import db
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
bio = db.Column(db.Text, default="")
articles = db.relationship("Article", back_populates="author")
class Article(db.Model):
__tablename__ = "articles"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
body = db.Column(db.Text, nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
author = db.relationship("User", back_populates="articles")
from flask import Flask, render_template, url_for
from extensions import db
from converters import register_model_converters
from models import User, Article
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
db.init_app(app)
register_model_converters(app, {"user": User, "article": Article})
@app.route("/users/<user:user>")
def profile(user):
return render_template("profile.html", user=user)
@app.route("/articles/<article:article>")
def article_detail(article):
author_url = url_for("profile", user=article.author)
return render_template(
"article.html", article=article, author_url=author_url
)
return app
if __name__ == "__main__":
create_app().run(debug=True)
Flask route parameters normally arrive as raw strings or ints, leaving each view responsible for parsing the ID, querying the database, and returning a 404 when nothing matches. That boilerplate multiplies across every endpoint and is easy to get subtly wrong. This snippet pushes that work into a reusable Werkzeug BaseConverter so a route can declare <user:user> and receive a fully-loaded User instance directly.
In ModelConverter, the converter subclasses Werkzeug's BaseConverter and overrides to_python and to_url. to_python is called during URL matching: it parses the captured segment into a primary-key type, runs db.session.get(model, pk), and calls abort(404) when the row is missing. Raising an HTTPException from inside to_python is a supported pattern — Werkzeug lets the exception propagate and Flask turns it into a proper 404 response before the view ever runs. to_url handles the reverse direction so url_for('profile', user=some_user) serializes the object back to its ID, keeping URL generation symmetric with matching.
A subtle point is that each model needs its own converter subclass bound to a specific model class. make_model_converter is a small factory that builds those subclasses on the fly, and register_model_converters wires each one into app.url_map.converters under a friendly name like user or article. The regex attribute constrains matches to digits, so non-numeric segments fall through to other routes instead of hitting the database.
In app.py, the converters are registered right after the app and extension are created. The profile and article_detail views then take user and article parameters that are already ORM instances, so the view bodies collapse to rendering logic. Because the lookup and 404 happen during routing, the views are guaranteed a non-null object.
The trade-off is that a database query runs during URL matching, so it is inappropriate for parameters that are not always loaded, and it can hide N+1 patterns if several converters fire per request. It shines for the common REST-style case of a single resource keyed by ID, where it removes repetitive get_or_404 calls and centralizes the not-found behavior in one tested place.
Related snips
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
module Paginatable
extend ActiveSupport::Concern
MAX_PER_PAGE = 100
DEFAULT_PER_PAGE = 25
API Pagination Headers (Link + Total)
import UIKit
import WebKit
class WebViewController: UIViewController {
private var webView: WKWebView!
private var progressView: UIProgressView!
WKWebView for web content display
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
Laravel Eloquent relationships with eager loading
export const CACHE_VERSION = 'v7';
export const APP_SHELL = '/index.html';
export const PRECACHE_URLS = [
'/',
Service worker: cache static assets safely
Share this code
Here's the card — post it anywhere.