К основному содержанию
правило

sqlalchemy — правило для Cursor: SQLAlchemy 2.x в Python

Правило для Cursor задаёт стиль SQLAlchemy 2.x: типизированные модели, сессии через контекстный менеджер, select() вместо устаревшего Query.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/sqlalchemy.mdc
Открыть на GitHub

Что делает sqlalchemy

Файл .cursor/rules/sqlalchemy.mdc подключается к Cursor и применяется ко всем файлам **/*.py. Внутри — набор соглашений по SQLAlchemy 2.x с примерами «плохо» и «хорошо»: модели на DeclarativeBase и Mapped, миксины для полей вроде created_at, работа с сессией через with Session(engine) as session: и явные транзакции через session.begin().

Отдельный блок про запросы. Правило требует select() вместе с session.execute() или session.scalar(), потому что старый session.query() в 2.x считается устаревшим. Для связей советует selectinload или joinedload, чтобы не ловить N+1 в цикле по пользователям и их адресам.

Правило рассчитано на проекты, которые уже перешли на 2-style API. Если кодовая база сидит на declarative_base() и Column, примеры будут конфликтовать с текущими моделями, и это повод сначала решить, переносите вы схему или нет. Текст правила обрезан на разделе про обновление объектов, так что про удаление и bulk-операции там ничего нет.

Какие задачи решает

  • перевести модели с declarative_base() на DeclarativeBase и Mapped
  • убрать утечки сессий через контекстный менеджер
  • заменить session.query() на select()
  • избавиться от N+1 при обходе связей
  • вынести общие поля вроде таймстемпов в миксин

Примеры запросов агенту

  • Перепиши модели в models.py на SQLAlchemy 2.x с Mapped и DeclarativeBase
  • Проверь этот запрос: если он использует session.query(), переделай на select()
  • Добавь selectinload для связи addresses, чтобы не было N+1

Как установить sqlalchemy

Вручную: положить файлы

.cursor/rules/sqlalchemy.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Из того же набора69

tornado Правило для Cursor задаёт стиль кода в Tornado: только async/await для ввода-вывода, полные аннотации типов и один обработчик ошибок на всё приложение. ★ 3 579звёзд репо three-js Правило для Cursor задаёт стандарты разработки на three.js: обработка ошибок WebGL, ленивая загрузка ассетов, оптимизация цикла рендера и освобождение ресурсов. ★ 3 579звёзд репо svelte Правило для Cursor задаёт, как писать компоненты Svelte 5: руны вместо старой реактивности, типизация и ключи в списках. Пригодится, если проект уже на Svelte 5. ★ 3 579звёзд репо streamlit Правило для Cursor задаёт, как писать приложения на Streamlit: кэшировать тяжёлые операции, держать UI отдельно от логики и разбивать проект на модули. ★ 3 579звёзд репо solidity Правило задаёт стиль и требования к Solidity-коду: фиксированный pragma, кастомные ошибки, защита от реентрантности. Пригодится, когда пишете контракты в Cursor. ★ 3 579звёзд репо shadcn Правило для Cursor задаёт команде единый стиль работы с shadcn/ui: структура папок, типы, варианты через CVA, формы на React Hook Form и Zod. Пригодится, когда компонентов становится много. ★ 3 579звёзд репо sentry Правило задаёт, как инициализировать Sentry в проекте: где ставить SDK, какие переменные окружения использовать, как не сломать чужой код. ★ 3 579звёзд репо scikit-image Правило задаёт команде единый стиль работы с scikit-image: неизменяемость входных массивов, float-диапазон [0, 1], векторизация вместо циклов по пикселям. ★ 3 579звёзд репо

Похожие

SQLAlchemy ORM Expert Customer Support Tech Enablement Team Скилл по SQLAlchemy 2.0+ для поддержки клиентов: модели тикетов, сессии, async-запросы и PostgreSQL. Пригодится, когда пишете бэкенд на FastAPI. CX Codex +1 470установок django-perf-review getsentry Скилл проверяет Django-код на N+1 запросы, неограниченные queryset и отсутствующие индексы. Пригодится перед релизом или когда страница тормозит на больших таблицах. CX Codex +1 2 900установок prisma-development Mindrally Задаёт агенту правила работы с Prisma ORM и TypeScript: схема, миграции, транзакции, обработка ошибок. Пригодится, когда в проекте есть Prisma. CX Codex +1 698установок alembic Customer Support Tech Enablement Team Скилл ведёт миграции Alembic в проектах на SQLAlchemy: от alembic init до выката в прод и отката. Пригодится, когда схема поддержки меняется часто. CX Codex +1 644установок prisma-client-api prisma Справочник по Prisma Client API для написания запросов к БД, CRUD-операций и фильтрации. Срабатывает на упоминания findMany, create, update, delete и транзакций. CX Codex +1 391 400установок prisma-cli prisma Справочник по командам Prisma ORM 7 для ИИ-агентов. Помогает запускать миграции, генерировать клиент и безопасно работать с базой данных без случайного сброса данных. CX Codex +1 388 700установок

Рядом в теме «Данные и базы»

Всё по теме «Данные и базы» →

Вопросы о sqlalchemy

Как установить правило sqlalchemy в Cursor?

Создайте файл .cursor/rules/sqlalchemy.mdc и вставьте в него содержимое правила. Cursor подхватит его для файлов, подходящих под glob **/*.py.

Что делает это правило?

Задаёт соглашения SQLAlchemy 2.x для Python-кода: типизированные модели, сессии как контекстный менеджер, запросы через select() и жадную загрузку связей.

Подойдёт ли оно проекту на старом SQLAlchemy?

Правило описывает только 2-style API, поэтому в кодовой базе на declarative_base() и Column примеры будут расходиться с текущим кодом. Сначала стоит определиться с миграцией.

Исходный текст

sqlalchemy.mdc · CC0-1.0
---
description: This guide enforces modern SQLAlchemy 2.x best practices for Python applications, ensuring type-safe, performant, and maintainable database interactions.
globs: **/*.py
---
# SQLAlchemy Best Practices (2.x Style)

This document outlines the definitive best practices for using SQLAlchemy 2.x in our projects. We exclusively adopt the "2-style" API, leveraging its fully typed, declarative features for robust and maintainable code.

## 1. Code Organization and Data Modeling

Always define your ORM models using the modern `DeclarativeBase` and `Mapped` annotations. Keep model definitions in a dedicated `models.py` or `orm_models/` directory.

### 1.1 Declarative Models with Type Annotations

Use `DeclarativeBase` as your base class and `Mapped` for all ORM-mapped attributes. This enables static type checking and clear schema definition.

❌ BAD: Legacy `declarative_base()` function and untyped `Column`.
```python
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String) # No type hint
```

✅ GOOD: Modern `DeclarativeBase` and `Mapped` for explicit typing.
```python
from typing import List, Optional
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import String, ForeignKey, func, DateTime

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    fullname: Mapped[Optional[str]]
    created_at: Mapped[DateTime] = mapped_column(DateTime, insert_default=func.now())

    addresses: Mapped[List["Address"]] = relationship(back_populates="user", cascade="all, delete-orphan")

class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(primary_key=True)
    email_address: Mapped[str]
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))

    user: Mapped["User"] = relationship(back_populates="addresses")
```

### 1.2 Mixins for Common Fields

Implement common fields like timestamps, soft-delete flags, or version counters using mixins.

```python
from datetime import datetime
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import DateTime, func

class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(DateTime, insert_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime, insert_default=func.now(), onupdate=func.now())

class SoftDeleteMixin:
    deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime, default=None)

class User(Base, TimestampMixin, SoftDeleteMixin):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    # ... other fields
```

## 2. Session Management

The `Session` is your unit of work. Always use it as a context manager to ensure proper transaction handling and resource cleanup.

### 2.1 Context Manager for Sessions

Wrap all database operations within a `with Session(engine) as session:` block. This guarantees `commit()` on success and `rollback()` on error, followed by `close()`.

❌ BAD: Manual session handling, prone to leaks and uncommitted transactions.
```python
session = Session(engine)
try:
    user = User(name="Alice")
    session.add(user)
    session.commit()
except Exception:
    session.rollback()
finally:
    session.close()
```

✅ GOOD: `Session` as a context manager.
```python
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine

engine = create_engine("sqlite:///./test.db")
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def create_user(name: str):
    with Session() as session:
        user = User(name=name)
        session.add(user)
        session.commit() # Flushes changes and commits transaction
        session.refresh(user) # Refresh to get auto-generated IDs
        return user
```

### 2.2 Explicit Transaction Blocks

For complex operations requiring multiple steps within a single transaction, use `session.begin()`.

```python
def transfer_funds(from_account_id: int, to_account_id: int, amount: float):
    with Session() as session:
        with session.begin(): # Explicit transaction block
            from_account = session.get(Account, from_account_id)
            to_account = session.get(Account, to_account_id)

            if not from_account or not to_account:
                raise ValueError("Account not found")
            if from_account.balance < amount:
                raise ValueError("Insufficient funds")

            from_account.balance -= amount
            to_account.balance += amount
        # Transaction commits here if no exceptions, or rolls back if an error occurs
    print(f"Transferred {amount} from {from_account_id} to {to_account_id}")
```

## 3. Querying and Optimization

Always use the `select()` construct for queries and prioritize eager loading for relationships to avoid N+1 problems.

### 3.1 Use `select()` for All Queries

The `Query` object is deprecated in 2.x. Use `select()` for all ORM and Core queries.

❌ BAD: Using the legacy `session.query()` API.
```python
users = session.query(User).filter(User.name == "Alice").all()
```

✅ GOOD: Using `select()` with `session.execute()`.
```python
from sqlalchemy import select

def get_user_by_name(session: Session, name: str) -> Optional[User]:
    stmt = select(User).where(User.name == name)
    return session.scalar(stmt) # Use scalar for single result, one_or_none for one, all for list
```

### 3.2 Eager Loading Relationships

Prevent N+1 query issues by eagerly loading related objects using `selectinload` or `joinedload`. `selectinload` is generally preferred for collections.

❌ BAD: Lazy loading in a loop, leading to N+1 queries.
```python
users = session.scalars(select(User)).all()
for user in users:
    print(f"{user.name} has {len(user.addresses)} addresses") # Each access triggers a new query
```

✅ GOOD: Eager loading with `selectinload`.
```python
from sqlalchemy.orm import selectinload

def get_users_with_addresses(session: Session) -> List[User]:
    stmt = select(User).options(selectinload(User.addresses))
    return session.scalars(stmt).all()

# Now, accessing user.addresses won't trigger additional queries
users = get_users_with_addresses(session)
for user in users:
    print(f"{user.name} has {len(user.addresses)} addresses")
```

## 4. Data Manipulation

The ORM Unit of Work pattern handles inserts, updates, and deletes efficiently.

### 4.1 Adding and Updating Objects

Add new objects with `session.add()`. Modifications to existing objects are tracked automatically.

```python
def update_user_name(session: Session, user_id: int, new_name: str):
    user = session.get(User, user_id) # Efficiently get by primary key
    if user:
        user.name = new_name # Change is tracked
        session.commit()
    return user

def add_new_user_with_address(session: Session, name: str, email: str):
    user = User(name=name, fullname=name)
    address = Address(email_address=email, user=user) # Relationship automatically links
    session.add(user) # Adding user also adds address due to cascade
    session.commit()
    return user
```

### 4.2 Deleting Objects

Delete objects using `session.delete()`.

```python
def delete_user(session: Session, user_id: int):
    user = session.get(User, user_id)
    if user:
        session.delete(user)
        session.commit()
        print(f"User {user_id} deleted.")
    else:
        print(f"User {user_id} not found.")
```

### 4.3 Bulk Operations (Use Sparingly)

Only use bulk operations (`session.bulk_insert_mappings`, `session.execute(insert/update/delete)`) when performance is paramount and ORM event handling (e.g., `onupdate` hooks) is not required.

```python
from sqlalchemy import insert

def bulk_insert_users(session: Session, user_data: List[dict]):
    # This bypasses ORM object creation and event hooks.
    # Use when you have raw data and need speed.
    session.execute(insert(User), user_data)
    session.commit()

# Example user_data: [{"name": "Charlie", "fullname": "Charlie Brown"}, ...]
```

## 5. Migrations and Type Checking

Integrate Alembic for schema migrations and ensure static type checking with Mypy or Ruff.

### 5.1 Alembic for Schema Evolution

Always use Alembic to manage database schema changes. Never manually alter the database schema.

```bash
# Initialize Alembic (once per project)
alembic init -t async migrations

# Generate a new migration script
alembic revision --autogenerate -m "Add new_column to user_account"

# Apply migrations
alembic upgrade head
```

### 5.2 Static Type Checking

Leverage Python's `typing` and SQLAlchemy's `Mapped` for comprehensive type checking with tools like Mypy or Ruff. This catches schema mismatches and API misuses early.

```python
# mypy.ini or pyproject.toml configuration for mypy
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой