Claude Code for Peewee: Lightweight Python ORM — Claude Skills 360 Blog
Blog / AI / Claude Code for Peewee: Lightweight Python ORM
AI

Claude Code for Peewee: Lightweight Python ORM

Published: March 16, 2028
Read time: 5 min read
By: Claude Skills 360

Peewee is a small, expressive ORM for SQLite, PostgreSQL, and MySQL. pip install peewee. Model: from peewee import *; db = SqliteDatabase("app.db"). class User(Model): name = CharField(); class Meta: database = db. Create: db.create_tables([User]). Insert: User.create(name="Alice"). User(name="Bob").save(). Select: User.select(). .where(User.name == "Alice"). .order_by(User.name). .limit(10). Get: User.get(User.id == 1). User.get_or_none(User.name == "Alice"). Update: User.update(name="Bob").where(User.id == 1).execute(). u = User.get(id=1); u.name = "Bob"; u.save(). Delete: User.delete().where(...).execute(). u.delete_instance(). Foreign key: author = ForeignKeyField(User, backref="posts"). Post.select().join(User). Bulk: User.bulk_create([User(name="A"), User(name="B")]). Transaction: with db.atomic():. @db.atomic(). Prefetch: User.select().prefetch(Post). Fields: CharField, IntegerField, FloatField, BooleanField, DateTimeField, TextField, BlobField, AutoField, JSONField(pg). null=True default= unique=True index=True. Pool: from playhouse.pool import PooledSqliteDatabase. Migrate: from playhouse.migrate import *; migrator = SqliteMigrator(db). Claude Code generates Peewee models, query builders, and database migration pipelines.

CLAUDE.md for Peewee

## Peewee Stack
- Version: peewee >= 3.17 | pip install peewee
- DB: SqliteDatabase("app.db") | PostgresqlDatabase(db, user, pw) | MySQLDatabase
- Model: class User(Model): name = CharField() | class Meta: database = db
- CRUD: User.create() | .get() | .get_or_none() | .save() | .delete_instance()
- Query: User.select().where(User.active==True).order_by(-User.created).limit(10)
- Join: Post.select().join(User).where(User.name=="Alice")
- Atomic: with db.atomic(): ... | @db.atomic() decorator

Peewee ORM Pipeline

# app/models.py — Peewee models, CRUD helpers, and query patterns
from __future__ import annotations

import datetime
from typing import Any

from peewee import (
    AutoField,
    BooleanField,
    CharField,
    DateTimeField,
    FloatField,
    ForeignKeyField,
    IntegerField,
    Model,
    SqliteDatabase,
    TextField,
)

# ─────────────────────────────────────────────────────────────────────────────
# Database setup
# ─────────────────────────────────────────────────────────────────────────────

db = SqliteDatabase(
    "app.db",
    pragmas={
        "journal_mode": "wal",
        "foreign_keys": 1,
        "cache_size":   -64 * 1024,  # 64 MB
    },
)


class BaseModel(Model):
    """Base model: all subclasses share the same database."""
    class Meta:
        database = db


# ─────────────────────────────────────────────────────────────────────────────
# Models
# ─────────────────────────────────────────────────────────────────────────────

class User(BaseModel):
    id         = AutoField()
    name       = CharField(max_length=200)
    email      = CharField(max_length=200, unique=True)
    active     = BooleanField(default=True)
    score      = FloatField(default=0.0)
    created_at = DateTimeField(default=datetime.datetime.utcnow)

    class Meta:
        table_name = "users"
        indexes    = ((("email",), True),)  # unique index


class Tag(BaseModel):
    id   = AutoField()
    name = CharField(max_length=100, unique=True)


class Post(BaseModel):
    id         = AutoField()
    author     = ForeignKeyField(User, backref="posts", on_delete="CASCADE")
    title      = CharField(max_length=500)
    body       = TextField()
    published  = BooleanField(default=False)
    view_count = IntegerField(default=0)
    created_at = DateTimeField(default=datetime.datetime.utcnow)

    class Meta:
        table_name = "posts"
        indexes    = ((("author", "title"), False),)


class PostTag(BaseModel):
    """Many-to-many join table."""
    post = ForeignKeyField(Post, backref="post_tags", on_delete="CASCADE")
    tag  = ForeignKeyField(Tag,  backref="post_tags", on_delete="CASCADE")

    class Meta:
        table_name = "post_tags"
        primary_key = False
        indexes = ((("post", "tag"), True),)


ALL_MODELS = [User, Tag, Post, PostTag]


# ─────────────────────────────────────────────────────────────────────────────
# 1. Database initialisation
# ─────────────────────────────────────────────────────────────────────────────

def init_db(create_tables: bool = True) -> None:
    """Connect and optionally create all tables."""
    db.connect(reuse_if_open=True)
    if create_tables:
        db.create_tables(ALL_MODELS, safe=True)


def close_db() -> None:
    if not db.is_closed():
        db.close()


# ─────────────────────────────────────────────────────────────────────────────
# 2. CRUD helpers
# ─────────────────────────────────────────────────────────────────────────────

def create_user(name: str, email: str, score: float = 0.0) -> User:
    """Create and return a new User."""
    return User.create(name=name, email=email, score=score)


def get_user(user_id: int) -> User | None:
    """Fetch a user by ID; None if not found."""
    return User.get_or_none(User.id == user_id)


def get_user_by_email(email: str) -> User | None:
    return User.get_or_none(User.email == email)


def update_user(user: User, **fields: Any) -> User:
    """Update user fields and save."""
    for key, value in fields.items():
        setattr(user, key, value)
    user.save()
    return user


def deactivate_user(user: User) -> None:
    User.update(active=False).where(User.id == user.id).execute()


def create_post(author: User, title: str, body: str, published: bool = False) -> Post:
    return Post.create(author=author, title=title, body=body, published=published)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Query patterns
# ─────────────────────────────────────────────────────────────────────────────

def active_users(limit: int = 50) -> list[User]:
    return list(
        User.select()
        .where(User.active == True)
        .order_by(User.name)
        .limit(limit)
    )


def top_users_by_score(n: int = 10) -> list[User]:
    return list(
        User.select()
        .where(User.active == True)
        .order_by(-User.score)
        .limit(n)
    )


def published_posts(page: int = 1, per_page: int = 20) -> list[Post]:
    return list(
        Post.select(Post, User)
        .join(User)
        .where(Post.published == True)
        .order_by(-Post.created_at)
        .paginate(page, per_page)
    )


def posts_by_user(user: User) -> list[Post]:
    """Return all posts by a user, newest first."""
    return list(user.posts.order_by(-Post.created_at))


def search_posts(query: str, limit: int = 20) -> list[Post]:
    """Full text search on title and body."""
    return list(
        Post.select()
        .where((Post.title.contains(query)) | (Post.body.contains(query)))
        .where(Post.published == True)
        .limit(limit)
    )


def user_post_count() -> list[dict[str, Any]]:
    """Return each user's post count as a list of dicts."""
    from peewee import fn
    query = (
        User.select(User, fn.COUNT(Post.id).alias("post_count"))
        .join(Post, join_type="LEFT OUTER")
        .group_by(User.id)
        .order_by(-fn.COUNT(Post.id))
    )
    return [{"user": u.name, "count": u.post_count} for u in query]


# ─────────────────────────────────────────────────────────────────────────────
# 4. Bulk operations
# ─────────────────────────────────────────────────────────────────────────────

def bulk_create_users(users_data: list[dict[str, Any]], chunk_size: int = 100) -> int:
    """Bulk insert users. Returns number created."""
    users = [User(**d) for d in users_data]
    with db.atomic():
        User.bulk_create(users, batch_size=chunk_size)
    return len(users)


def bulk_update_scores(updates: list[dict[str, Any]]) -> int:
    """
    Bulk-update user scores.
    updates: [{"id": 1, "score": 100.0}, ...]
    """
    with db.atomic():
        User.bulk_update(
            [User(id=u["id"], score=u["score"]) for u in updates],
            fields=[User.score],
            batch_size=100,
        )
    return len(updates)


# ─────────────────────────────────────────────────────────────────────────────
# 5. Many-to-many tag helpers
# ─────────────────────────────────────────────────────────────────────────────

def get_or_create_tag(name: str) -> Tag:
    tag, _ = Tag.get_or_create(name=name.lower().strip())
    return tag


def tag_post(post: Post, tag_names: list[str]) -> None:
    """Attach tags to a post (idempotent)."""
    with db.atomic():
        for name in tag_names:
            tag = get_or_create_tag(name)
            PostTag.get_or_create(post=post, tag=tag)


def posts_by_tag(tag_name: str, limit: int = 20) -> list[Post]:
    tag = Tag.get_or_none(Tag.name == tag_name.lower())
    if tag is None:
        return []
    return list(
        Post.select()
        .join(PostTag)
        .join(Tag)
        .where(Tag.name == tag_name.lower(), Post.published == True)
        .limit(limit)
    )


# ─────────────────────────────────────────────────────────────────────────────
# 6. Flask / FastAPI integration
# ─────────────────────────────────────────────────────────────────────────────

def flask_db_hooks(app) -> None:
    """
    Register Peewee connect/close hooks for a Flask app.
    Opens a connection before each request and closes after.
    """
    @app.before_request
    def _open():
        db.connect(reuse_if_open=True)

    @app.teardown_request
    def _close(exc):
        if not db.is_closed():
            db.close()


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import os

    DB_FILE = "/tmp/peewee_demo.db"
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    db.init(DB_FILE)
    init_db()

    print("=== Create users ===")
    with db.atomic():
        alice = create_user("Alice", "[email protected]", score=98.5)
        bob   = create_user("Bob",   "[email protected]",   score=87.0)
        carol = create_user("Carol", "[email protected]", score=92.0)
    print(f"  Created: {alice.name}, {bob.name}, {carol.name}")

    print("\n=== Queries ===")
    top = top_users_by_score(n=3)
    for u in top:
        print(f"  {u.name:10} score={u.score}")

    print("\n=== Posts ===")
    with db.atomic():
        p1 = create_post(alice, "Hello Python", "Python is great!", published=True)
        p2 = create_post(alice, "Async Tips",   "Use asyncio!",     published=True)
        p3 = create_post(bob,   "Draft post",   "WIP",              published=False)

    tag_post(p1, ["python", "tutorial"])
    tag_post(p2, ["python", "async"])

    published = published_posts()
    print(f"  Published: {[p.title for p in published]}")

    print("\n=== Join query (post count per user) ===")
    for row in user_post_count():
        print(f"  {row['user']:10}: {row['count']} posts")

    print("\n=== Tag search ===")
    python_posts = posts_by_tag("python")
    print(f"  Posts tagged 'python': {[p.title for p in python_posts]}")

    print("\n=== Search ===")
    results = search_posts("Python")
    print(f"  Search 'Python': {[p.title for p in results]}")

    print("\n=== Bulk create ===")
    bulk_data = [{"name": f"User{i}", "email": f"u{i}@x.com"} for i in range(10)]
    n = bulk_create_users(bulk_data)
    print(f"  Bulk created {n} users | Total: {User.select().count()}")

    close_db()
    os.remove(DB_FILE)

For the SQLAlchemy alternative — SQLAlchemy is more powerful (supports async, complex relationships, full migration with Alembic, and all major databases including async engines), but has a steeper learning curve; Peewee’s concise model declaration (name = CharField()) and chainable query API (.where().order_by().limit()) make it faster to get a working data layer for small-to-medium projects, especially with SQLite; use Peewee when you want an ORM without the complexity and boilerplate of SQLAlchemy core + ORM. For the Django ORM alternative — Django’s ORM is excellent but only works within a Django project; Peewee is framework-agnostic and works with Flask, FastAPI, CLI tools, and standalone scripts — one class User(Model) is all you need to start querying. The Claude Skills 360 bundle includes Peewee skill sets covering SqliteDatabase/PostgresqlDatabase/MySQLDatabase setup, Model field types (CharField, ForeignKeyField, DateTimeField, etc.), CRUD (create/get_or_none/save/delete_instance), query chaining (where/order_by/limit/paginate), JOINs and prefetch, db.atomic() transactions, bulk_create() and bulk_update(), fn.COUNT/fn.SUM aggregate queries, many-to-many patterns with junction models, flask_db_hooks() connection management, and PostgreSQL JSONField. Start with the free tier to try Peewee ORM code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free