bateau84/skills

python-database

Python database access with stdlib sqlite3 — parameterized queries, connection context managers, sqlite3.Row factory, transactions, the repository pattern, schema migrations via PRAGMA userversion, and SQLite pragmas (WAL, foreignkeys).

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/bateau84/skills --skill python-database

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

Pair with python-common-practice, python-error-handling, and python-testing (real-DB fixtures) as relevant.

Persona: You are a Python engineer who keeps SQL at the edge. The rest of the app talks to a repository, never to a cursor.

SQLite with sqlite3

The stdlib sqlite3 module is enough for an embedded DB — no ORM required. Keep all SQL behind a repository so the rest of the code depends on methods, not on the schema. This is sortarr's db/{connection,migrations,repository} layout.

Parameterize every query — never format SQL

String-formatting user input into SQL is the classic injection vulnerability. Pass parameters with ? placeholders; the driver escapes them safely.

python
# ✗ Bad — SQL injection; an attacker controls the query
cur.execute(f"SELECT * FROM tracks WHERE id = '{track_id}'")

# ✓ Good — parameterized; value can never alter the query
cur.execute("SELECT * FROM tracks WHERE id = ?", (track_id,))
cur.execute("INSERT INTO tracks (id, title) VALUES (?, ?)", (tid, title))

Use ? (qmark) placeholders, or :name with a dict. Identifiers (table/column names) can't be parameterized — if they're dynamic, validate them against an allow-list, never interpolate raw input.

Connections via a context manager

Open and close connections deterministically with a context manager — leaked connections lock the file. sortarr's get_connection is the model: sqlite3.Row factory, error logged-and-re-raised, close() in finally.

python
from contextlib import contextmanager
import sqlite3

@contextmanager
def get_connection(db_path: str):
    con = sqlite3.connect(db_path)
    con.row_factory = sqlite3.Row     # rows behave like dicts: row["title"]
    try:
        yield con
    finally:
        con.close()

row_factory = sqlite3.Row lets you access columns by name (row["title"]) instead of brittle positional indexing (row[1]) — adding a column won't silently shift your fields.

Transactions — commit or roll back as a unit

sqlite3 opens an implicit transaction; nothing persists until `commit()`. Wrap a multi-statement change so a failure rolls back cleanly. The connection used as a context manager commits on success and rolls back on exception:

python
with get_connection(db_path) as con:
    with con:                                  # commit on success, rollback on exception
        con.execute("INSERT INTO runs (started) VALUES (?)", (now,))
        con.execute("UPDATE state SET last_run = ?", (now,))

Note the two withs do different jobs: the outer closes the connection; the inner (with con:) is the transaction boundary.

Repository pattern

Expose intent-named methods, not SQL, so callers (and the FastAPI layer) never touch a cursor. Type the repository against a Protocol so tests can substitute a fake (→ See python-type-checking).

python
class Repository:
    def __init__(self, con: sqlite3.Connection):
        self._con = con

    def save_decision(self, d: MatchDecision) -> None:
        self._con.execute(
            "INSERT OR REPLACE INTO decisions (video_id, verdict) VALUES (?, ?)",
            (d.video_id, d.verdict),
        )

    def decision_for(self, video_id: str) -> MatchDecision | None:
        row = self._con.execute(
            "SELECT video_id, verdict FROM decisions WHERE video_id = ?", (video_id,)
        ).fetchone()
        return MatchDecision(**row) if row else None

Return None for "not found" — that's expected absence, not an error (→ See python-error-handling).

Migrations

Apply schema changes idempotently at startup with a versioned migration runner (PRAGMA user_version), so the app self-migrates and there's no separate command. Each migration runs once, in order, inside a transaction. Use CREATE TABLE IF NOT EXISTS and additive ALTER TABLE so re-running is safe.

Pragmas worth setting

python
con.execute("PRAGMA foreign_keys = ON")     # off by default in sqlite3 — enforce FKs
con.execute("PRAGMA journal_mode = WAL")    # concurrent readers + a writer; better throughput

foreign_keys is off by default — turn it on per connection or referential integrity isn't enforced.

Sync sqlite3 on an async event loop

The stdlib sqlite3 API is blocking. Calling it directly inside an async def FastAPI handler stalls the event loop for the whole query — every other in-flight request waits. SQLite is also single-writer: concurrent writers serialize on a file lock, so one slow write blocks the rest. Keep DB work off the loop one of two ways:

python
# ✓ A plain `def` handler — FastAPI runs it in a threadpool, off the loop
@router.get("/stats")
def get_stats(repo: Repository = Depends(get_repo)) -> StatsOut:
    return repo.stats()                       # blocking sqlite is fine here

# ✓ Inside an async handler, offload the blocking call
from starlette.concurrency import run_in_threadpool

@router.get("/decisions/{vid}")
async def get_decision(vid: str, repo=Depends(get_repo)) -> DecisionOut:
    row = await run_in_threadpool(repo.decision_for, vid)   # or asyncio.to_thread
    ...

Don't share one connection across tasks or threads — sqlite3 connections aren't safe for concurrent use. Open per request (or per job), or use a thread-aware pool. In an APScheduler job, open the connection inside the job, not once at import time (→ See python-async).

Common mistakes

MistakeFix
f-string / %-format SQL with inputUse ? placeholders; never interpolate values.
Forgetting commit()Use with con: for the transaction, or commit explicitly.
Positional row[1] accesscon.row_factory = sqlite3.Row; access by name.
Connection never closedOpen via a context manager; close in finally.
Assuming FK constraints enforcedPRAGMA foreign_keys = ON per connection.
SQL scattered across the appConfine it to a repository; callers use methods.
Blocking sqlite3 call inside an async def handlerUse a def handler (threadpool) or await run_in_threadpool(...) / asyncio.to_thread.
Sharing one connection across tasks/threadsOpen per request or per job; sqlite3 connections aren't concurrency-safe.
Mocking the DB in testsUse a real tmp_path SQLite DB — it's fast and catches SQL errors.

Diagnose: 1- ruff/bandit — flag string-formatted SQL (S608) 2- EXPLAIN QUERY PLAN <sql> — confirm an index is used before "optimizing" 3- run the test suite against a real tmp_path DB — SQL typos surface immediately.

References

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills
bateau84
커뮤니티

behavioral-spec

Derive bounded, implementation-independent requirements from agreed product intent through goal refinement, coverage lenses, obstacle analysis, provenance tracking, and counterexample checks. Use to select, author, and reconcile Behavioral Requirements (BR), Quality Scenarios (QS), and Obligation Contracts (OC) without expanding scope. Not for concrete API/schema/protocol realization (use architectural-spec) or human-centered stories and scenarios (use user-stories-and-scenarios).

1시간 설치 수
1
GitHub Stars
0
업데이트
10월 18일
bateau84
커뮤니티

desktop-ui-design

Desktop GUI design for native and hybrid applications: platform conventions (macOS HIG, Windows Fluent, GNOME HIG), menus, keyboard shortcuts, window management, session persistence, native vs Electron/Tauri trade-offs, and accessibility APIs. Use for Swift/AppKit, C/WinUI, GTK+, or Electron/Tauri/Flutter desktop design. Add web-ui-design for a web app shipped as a desktop wrapper. Not for browser-only interfaces, terminal applications (use tui-design), or command-line tools (use cli-design).

1시간 설치 수
1
GitHub Stars
0
업데이트
10월 18일
bateau84
커뮤니티

git-commit-discipline

Small coherent commits, explanatory messages, shared-working-tree discipline, and safe undo/recovery. Use when asked to commit, when a diff mixes concerns, when agents share a working tree, or when undoing or recovering Git changes. Not for content conflicts (use git-conflicts).

1시간 설치 수
1
GitHub Stars
0
업데이트
10월 18일
bateau84
커뮤니티

golang-dependency-injection

google/wire, uber-go/dig, uber-go/fx, samber/do, manual constructor injection, and service containers in Go. Use when designing service architecture, setting up dependency injection, refactoring tightly coupled code, or managing singletons and service factories.

1시간 설치 수
1
GitHub Stars
0
업데이트
10월 18일