@robhowley/py-pit-skills 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,248 @@
1
+ ---
2
+ name: settings-config
3
+ description: Production-ready configuration management for Python backend projects using pydantic-settings. Use this skill when the user needs to add environment variable management, introduce pydantic-settings, replace scattered os.getenv usage, add a settings module, or set up .env support in a FastAPI service or Python backend.
4
+ disable-model-invocation: false
5
+ ---
6
+
7
+ # Skill: settings-config
8
+
9
+ ## Core position
10
+
11
+ This skill creates **clean, production-ready application configuration
12
+ management** using **pydantic-settings**.
13
+
14
+ It enforces disciplined configuration patterns that prevent common
15
+ problems such as:
16
+
17
+ - configuration drift
18
+ - unclear env var naming
19
+ - environment-specific branching
20
+ - accidental secrets in code
21
+
22
+ The skill favors **minimal, explicit configuration surfaces** and
23
+ ensures configuration is:
24
+
25
+ - typed
26
+ - validated
27
+ - environment-driven
28
+ - testable
29
+
30
+ ------------------------------------------------------------------------
31
+
32
+ ## Goals
33
+
34
+ Produce a configuration system that:
35
+
36
+ 1. Centralizes configuration in a **single settings module**
37
+ 2. Uses **typed settings via pydantic**
38
+ 3. Reads configuration from **environment variables**
39
+ 4. Allows `.env` usage in local development
40
+ 5. Avoids configuration logic scattered across the codebase
41
+
42
+ ------------------------------------------------------------------------
43
+
44
+ ## Step 0 — Inspect the existing project first
45
+
46
+ Before generating anything:
47
+
48
+ 1. Check whether a `config.py` or `settings.py` already exists. If it
49
+ does, extend it rather than creating a parallel one.
50
+ 2. Note the existing package layout. If the project was scaffolded with
51
+ `fastapi-init`, config lives at `{pkg_name}/core/config.py` — use
52
+ that path, not `app/config.py`.
53
+ 3. Check whether an `env_prefix` is already in use anywhere
54
+ (`env_prefix=`, `os.getenv("XYZ_`, existing `.env` keys). If one
55
+ exists, adopt it.
56
+ 4. Check whether `.env` loading is already part of the project
57
+ convention (for example a real `.env` file used locally, documented
58
+ setup instructions, or an explicit user request). If so, adopt
59
+ `env_file=".env"` in the config.
60
+
61
+ Do not assume that the presence of `.env.example` alone means `.env`
62
+ should be automatically loaded at runtime.
63
+ 5. Only create new files if no config module is present.
64
+
65
+ ------------------------------------------------------------------------
66
+
67
+ ## Standard structure
68
+
69
+ Create a dedicated settings module.
70
+
71
+ Typical layout (adapt to the actual package structure found in Step 0):
72
+
73
+ project/
74
+ {pkg_name}/
75
+ core/
76
+ config.py ← preferred location for fastapi-init projects
77
+ .env
78
+ .env.example
79
+
80
+ Example implementation:
81
+
82
+ ``` python
83
+ from pydantic_settings import BaseSettings, SettingsConfigDict
84
+
85
+
86
+ class Settings(BaseSettings):
87
+ app_name: str = "service"
88
+ debug: bool = False
89
+ database_url: str
90
+
91
+ model_config = SettingsConfigDict(
92
+ env_prefix="APP_",
93
+ extra="ignore",
94
+ )
95
+
96
+
97
+ settings = Settings()
98
+ ```
99
+
100
+ Usage:
101
+
102
+ ``` python
103
+ from {pkg_name}.core.config import settings
104
+
105
+ print(settings.database_url)
106
+ ```
107
+
108
+ ------------------------------------------------------------------------
109
+
110
+ ## Environment variable pattern
111
+
112
+ Environment variables should follow a **consistent prefix convention**.
113
+
114
+ Example:
115
+
116
+ APP_DATABASE_URL=postgresql://...
117
+ APP_DEBUG=true
118
+
119
+ The prefix prevents collisions with other services or system variables.
120
+
121
+ ------------------------------------------------------------------------
122
+
123
+ ## .env files (optional, local dev)
124
+
125
+ Environment variables are the canonical configuration source. `.env`
126
+ support is an optional local-development convenience layer.
127
+
128
+ To enable it, add `env_file` to the config:
129
+
130
+ ``` python
131
+ model_config = SettingsConfigDict(
132
+ env_prefix="APP_",
133
+ env_file=".env",
134
+ extra="ignore",
135
+ )
136
+ ```
137
+
138
+ Example `.env`:
139
+
140
+ APP_DATABASE_URL=postgresql://localhost/service
141
+ APP_DEBUG=true
142
+
143
+ When `.env` is in use, commit a `.env.example` to the repo showing
144
+ required variables. The `.env` file itself must be in `.gitignore` — it
145
+ may contain secrets and should never be committed.
146
+
147
+ `extra="ignore"` is intentional here (unlike `extra="forbid"` in
148
+ request schemas). Environment variables from the shell, Docker, or CI
149
+ will be present alongside app config — rejecting unknown keys would
150
+ break deployment.
151
+
152
+ ------------------------------------------------------------------------
153
+
154
+ ## Anti-patterns to remove
155
+
156
+ Replace patterns like:
157
+
158
+ ``` python
159
+ import os
160
+ DATABASE_URL = os.getenv("DATABASE_URL")
161
+ ```
162
+
163
+ or scattered config usage across modules.
164
+
165
+ All configuration should flow through the **settings object**.
166
+
167
+ ------------------------------------------------------------------------
168
+
169
+ ## Three subtle rules (important)
170
+
171
+ These rules are what distinguish this skill from generic AI
172
+ configuration scaffolding.
173
+
174
+ ### Rule 1 --- No runtime environment branching
175
+
176
+ Avoid code such as:
177
+
178
+ ``` python
179
+ if ENV == "production":
180
+ ...
181
+ ```
182
+
183
+ Configuration differences should come from **environment variables**,
184
+ not logic in the settings module.
185
+
186
+ The settings layer should remain **purely declarative**.
187
+
188
+ ### Rule 2 --- Canonical environment prefix
189
+
190
+ If the repository already uses a prefix pattern (for example
191
+ `MY_SERVICE_`), the settings model must adopt it.
192
+
193
+ If no prefix exists, create one derived from the package name.
194
+
195
+ Consistency is more important than any specific prefix choice.
196
+
197
+ ### Rule 3 --- Single settings instantiation
198
+
199
+ Instantiate settings **once** and import the instance everywhere.
200
+
201
+ Correct:
202
+
203
+ ``` python
204
+ settings = Settings()
205
+ ```
206
+
207
+ Incorrect:
208
+
209
+ ``` python
210
+ Settings()
211
+ Settings()
212
+ Settings()
213
+ ```
214
+
215
+ Multiple instantiations can lead to:
216
+
217
+ - inconsistent configuration reads
218
+ - test instability
219
+ - hidden environment reloads
220
+
221
+ ------------------------------------------------------------------------
222
+
223
+ ## Output checklist
224
+
225
+ The skill should produce:
226
+
227
+ - `config.py` with `BaseSettings`
228
+ - a `Settings` class
229
+ - a single `settings` instance
230
+ - consistent env var prefix
231
+ - removal of `os.getenv` usage
232
+ - documentation comment describing required variables
233
+ - `.env.example` committed *(if using .env)*
234
+ - `.env` in `.gitignore` *(if using .env)*
235
+
236
+ ------------------------------------------------------------------------
237
+
238
+ ## Summary
239
+
240
+ A good configuration system is:
241
+
242
+ - **typed**
243
+ - **centralized**
244
+ - **environment-driven**
245
+ - **boring and predictable**
246
+
247
+ This skill enforces those properties so configuration never becomes a
248
+ source of production bugs.
@@ -0,0 +1,433 @@
1
+ ---
2
+ name: sqlalchemy-models
3
+ description: Production-ready SQLAlchemy 2.x model patterns for Python backend projects. Use this skill when the user needs to define ORM models, add relationships, introduce a canonical DeclarativeBase, create shared mixins, organize a models package, or fix inconsistent SQLAlchemy model structure in a FastAPI or Python backend.
4
+ disable-model-invocation: false
5
+ ---
6
+
7
+ # Skill: sqlalchemy-models
8
+
9
+ ## Core position
10
+
11
+ This skill creates **clean, production-ready SQLAlchemy 2.x ORM models**
12
+ for Python backend projects.
13
+
14
+ It enforces disciplined model patterns that prevent common problems such as:
15
+
16
+ - inconsistent base/model definitions
17
+ - broken or asymmetric relationships
18
+ - circular imports
19
+ - weak typing
20
+ - migration-hostile schema definitions
21
+ - persistence and API schema concerns getting mixed together
22
+
23
+ The skill favors **explicit, typed, migration-friendly ORM design** and
24
+ ensures model code is:
25
+
26
+ - SQLAlchemy 2.x native
27
+ - typed
28
+ - composable
29
+ - easy to migrate
30
+ - easy to review
31
+
32
+ ------------------------------------------------------------------------
33
+
34
+ ## Goals
35
+
36
+ Produce a model layer that:
37
+
38
+ 1. Uses **SQLAlchemy 2.x canonical style**
39
+ 2. Centralizes model infrastructure around a **single DeclarativeBase**
40
+ 3. Defines columns and relationships with **explicit typing**
41
+ 4. Organizes models in a **predictable package structure**
42
+ 5. Avoids **circular import traps**
43
+ 6. Stays **compatible with Alembic autogenerate**
44
+ 7. Keeps **ORM models separate from Pydantic/API schemas**
45
+
46
+ ------------------------------------------------------------------------
47
+
48
+ ## Step 0 — Inspect the existing project first
49
+
50
+ Before generating anything:
51
+
52
+ 1. Check whether a `models/` package or existing model files already exist. If
53
+ they do, extend the existing structure rather than creating a parallel one.
54
+ 2. Note the existing package layout. If the project was scaffolded with
55
+ `fastapi-init`, the package root is `{pkg_name}/{pkg_name}/` — place models
56
+ at `{pkg_name}/models/`, not `app/models/`.
57
+ 3. Check whether a `DeclarativeBase` subclass already exists anywhere. If one
58
+ does, adopt it rather than introducing a second base.
59
+ 4. Check whether an Alembic `env.py` is present and how it imports
60
+ `Base.metadata` — preserve that import path.
61
+ 5. Only create new files if the relevant structure is absent.
62
+
63
+ ------------------------------------------------------------------------
64
+
65
+ ## When to use this skill
66
+
67
+ Use this skill when the user:
68
+
69
+ - wants to add SQLAlchemy models to a backend project
70
+ - needs to define new ORM entities or relationships
71
+ - wants to migrate older SQLAlchemy code to 2.x style
72
+ - has inconsistent model layout or imports
73
+ - needs timestamp/base mixins
74
+ - wants model patterns that work well with FastAPI
75
+ - needs the model layer cleaned up before adding Alembic migrations or CRUD routes
76
+
77
+ ------------------------------------------------------------------------
78
+
79
+ ## When not to use this skill
80
+
81
+ Do not use this skill when:
82
+
83
+ - the user is asking for Pydantic request/response schemas only
84
+ - the user is not using SQLAlchemy
85
+ - the task is about Alembic migration authoring rather than model design
86
+ - the user explicitly wants a different ORM
87
+
88
+ ------------------------------------------------------------------------
89
+
90
+ ## Non-goals
91
+
92
+ This skill does **not**:
93
+
94
+ - invent unrelated tables or domain entities
95
+ - generate large CRUD/service layers unless the user asks
96
+ - merge ORM models with transport schemas
97
+ - introduce async/session architecture changes unless required by the repo
98
+ - rewrite the database stack beyond what the current project calls for
99
+
100
+ ------------------------------------------------------------------------
101
+
102
+ ## Required stance
103
+
104
+ When applying this skill:
105
+
106
+ - prefer **minimal patches** over broad rewrites
107
+ - preserve the repo's existing architectural direction when sane
108
+ - standardize on **one canonical model pattern**
109
+ - fix root-cause structure issues rather than layering aliases or compatibility shims
110
+ - optimize for maintainability, migration safety, and correctness over cleverness
111
+
112
+ ------------------------------------------------------------------------
113
+
114
+ ## Canonical output requirements
115
+
116
+ A correct solution produced by this skill should usually include:
117
+
118
+ - a single shared `DeclarativeBase`
119
+ - SQLAlchemy 2.x typed fields with `Mapped[...]`
120
+ - `mapped_column(...)` for columns
121
+ - explicit `relationship(...)` declarations
122
+ - symmetric `back_populates` for bidirectional relationships
123
+ - a predictable `models/` package structure
124
+ - import patterns that avoid circular dependencies
125
+ - optional shared mixins only when they reduce duplication cleanly
126
+
127
+ ------------------------------------------------------------------------
128
+
129
+ ## Preferred patterns
130
+
131
+ ### 1) Base class
132
+
133
+ Prefer a single canonical base:
134
+
135
+ ```python
136
+ from sqlalchemy.orm import DeclarativeBase
137
+
138
+
139
+ class Base(DeclarativeBase):
140
+ pass
141
+ ```
142
+
143
+ Do not create multiple unrelated declarative bases unless the repo already
144
+ intentionally uses them.
145
+
146
+ ------------------------------------------------------------------------
147
+
148
+ ### 2) SQLAlchemy 2.x typed columns
149
+
150
+ Prefer:
151
+
152
+ ```python
153
+ from sqlalchemy import String
154
+ from sqlalchemy.orm import Mapped, mapped_column
155
+
156
+
157
+ email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
158
+ ```
159
+
160
+ Avoid legacy untyped declarations like:
161
+
162
+ ```python
163
+ email = Column(String, unique=True)
164
+ ```
165
+
166
+ unless the repo is explicitly locked to an older SQLAlchemy style and the
167
+ user did not ask for modernization.
168
+
169
+ ------------------------------------------------------------------------
170
+
171
+ ### 3) Primary key convention
172
+
173
+ Default to a simple explicit primary key:
174
+
175
+ ```python
176
+ id: Mapped[int] = mapped_column(primary_key=True)
177
+ ```
178
+
179
+ Only introduce UUIDs or custom identifiers when the repo already uses them or
180
+ there is a clear requirement.
181
+
182
+ ------------------------------------------------------------------------
183
+
184
+ ### 4) Relationship symmetry
185
+
186
+ Prefer fully paired relationships:
187
+
188
+ ```python
189
+ class User(Base):
190
+ __tablename__ = "users"
191
+
192
+ id: Mapped[int] = mapped_column(primary_key=True)
193
+ posts: Mapped[list["Post"]] = relationship(back_populates="author")
194
+
195
+
196
+ class Post(Base):
197
+ __tablename__ = "posts"
198
+
199
+ id: Mapped[int] = mapped_column(primary_key=True)
200
+ author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
201
+ author: Mapped["User"] = relationship(back_populates="posts")
202
+ ```
203
+
204
+ Avoid one-sided relationships unless intentionally required.
205
+
206
+ ------------------------------------------------------------------------
207
+
208
+ ### 5) Forward references to reduce import pressure
209
+
210
+ Prefer string references in relationships when models live in separate files:
211
+
212
+ ```python
213
+ posts: Mapped[list["Post"]] = relationship(back_populates="author")
214
+ ```
215
+
216
+ This helps avoid circular imports and keeps modules loosely coupled.
217
+
218
+ ------------------------------------------------------------------------
219
+
220
+ ### 6) Explicit nullability and constraints
221
+
222
+ Be deliberate about nullability, uniqueness, indexes, and defaults.
223
+
224
+ Prefer model fields that make schema intent obvious.
225
+
226
+ Do not rely on vague or accidental defaults.
227
+
228
+ ------------------------------------------------------------------------
229
+
230
+ ### 7) Mixins
231
+
232
+ Use mixins only where they reduce obvious duplication.
233
+
234
+ Typical good candidates:
235
+
236
+ - timestamp fields
237
+ - soft-delete marker fields
238
+ - small shared utility methods
239
+
240
+ Example:
241
+
242
+ ```python
243
+ from datetime import datetime, timezone
244
+ from sqlalchemy import DateTime
245
+ from sqlalchemy.orm import Mapped, mapped_column
246
+
247
+
248
+ def utcnow() -> datetime:
249
+ return datetime.now(timezone.utc)
250
+
251
+
252
+ class TimestampMixin:
253
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
254
+ updated_at: Mapped[datetime] = mapped_column(
255
+ DateTime(timezone=True),
256
+ default=utcnow,
257
+ onupdate=utcnow,
258
+ )
259
+ ```
260
+
261
+ Do not add mixins that obscure the real model shape.
262
+
263
+ ------------------------------------------------------------------------
264
+
265
+ ## Package structure
266
+
267
+ Prefer a predictable model package. Adapt the root to the actual project layout
268
+ found in Step 0 — for `fastapi-init` projects this is `{pkg_name}/models/`, for
269
+ other layouts it may be `app/models/` or similar.
270
+
271
+ ```text
272
+ {pkg_name}/
273
+ models/
274
+ __init__.py
275
+ base.py
276
+ user.py
277
+ post.py
278
+ ```
279
+
280
+ Where appropriate:
281
+
282
+ - `base.py` contains `Base` and small shared mixins
283
+ - each entity gets its own module
284
+ - `models/__init__.py` should import all model classes so that
285
+ `Base.metadata` is fully populated when Alembic (or any other tool)
286
+ imports it — this is what makes autogenerate reliable
287
+
288
+ Avoid dumping all models into one huge file once the project has more than a
289
+ few entities.
290
+
291
+ ------------------------------------------------------------------------
292
+
293
+ ## Import discipline
294
+
295
+ Prefer explicit imports.
296
+
297
+ Good:
298
+
299
+ ```python
300
+ from app.models.user import User
301
+ from app.models.post import Post
302
+ ```
303
+
304
+ Avoid wildcard imports:
305
+
306
+ ```python
307
+ from app.models import *
308
+ ```
309
+
310
+ Also avoid tangled cross-import chains between model modules.
311
+
312
+ ------------------------------------------------------------------------
313
+
314
+ ## Model vs schema boundary
315
+
316
+ Keep ORM models and Pydantic schemas separate.
317
+
318
+ ORM models represent:
319
+
320
+ - persistence
321
+ - relationships
322
+ - table structure
323
+
324
+ Pydantic schemas represent:
325
+
326
+ - request validation
327
+ - response serialization
328
+ - transport contracts
329
+
330
+ Do not collapse both concerns into the same class structure.
331
+
332
+ ------------------------------------------------------------------------
333
+
334
+ ## Alembic compatibility requirements
335
+
336
+ Model code should be written so Alembic autogenerate can reason about it
337
+ cleanly.
338
+
339
+ Prefer:
340
+
341
+ - explicit table names
342
+ - explicit foreign keys
343
+ - explicit constraints where needed
344
+ - stable import paths for model metadata discovery
345
+
346
+ Avoid patterns that obscure metadata registration or hide model definitions.
347
+
348
+ ------------------------------------------------------------------------
349
+
350
+ ## FastAPI integration stance
351
+
352
+ This skill does not redesign session management unless necessary, but it should
353
+ produce model code that fits normal FastAPI backend usage.
354
+
355
+ Assume the expected separation is:
356
+
357
+ - model definitions in `models/`
358
+ - DB session lifecycle elsewhere
359
+ - Pydantic schemas elsewhere
360
+ - route/service layers consume ORM models without redefining them
361
+
362
+ ------------------------------------------------------------------------
363
+
364
+ ## Review checklist
365
+
366
+ Before finishing, verify:
367
+
368
+ - all models inherit from the same `Base`
369
+ - columns use `Mapped[...]` and `mapped_column(...)`
370
+ - relationships are typed and symmetric where applicable
371
+ - foreign keys are explicit
372
+ - `__tablename__` is defined consistently
373
+ - imports do not create obvious circular dependency risks
374
+ - model files are organized predictably
375
+ - ORM models are not mixed with request/response schema logic
376
+ - patterns are migration-friendly
377
+
378
+ ------------------------------------------------------------------------
379
+
380
+ ## Common failure modes this skill should prevent
381
+
382
+ - legacy `Column(...)` style mixed inconsistently with 2.x style
383
+ - missing `back_populates`
384
+ - broken relationship typing
385
+ - circular imports between model files
386
+ - multiple competing base classes
387
+ - hidden metadata registration issues
388
+ - nullable/unique/index behavior implied rather than stated
389
+ - putting API serialization concerns directly into ORM model code
390
+
391
+ ------------------------------------------------------------------------
392
+
393
+ ## Execution pattern
394
+
395
+ When using this skill, the assistant should usually:
396
+
397
+ 1. Inspect the repo's existing DB/model/session conventions
398
+ 2. Identify the canonical path already present or the smallest sound pattern to add
399
+ 3. Normalize model definitions toward SQLAlchemy 2.x style
400
+ 4. Add or clean up base/mixin structure only as much as needed
401
+ 5. Keep patches compact and easy to review
402
+ 6. Call out any follow-on work that belongs in adjacent skills
403
+
404
+ ------------------------------------------------------------------------
405
+
406
+ ## Adjacent skills
407
+
408
+ This skill pairs naturally with others in this plugin and anticipated future
409
+ skills. Not all of these exist yet — treat them as integration points, not
410
+ dependencies.
411
+
412
+ - `settings-config` — database URL and other config values come from here
413
+ - `pydantic-schemas` — API request/response schemas that mirror (but stay
414
+ separate from) the ORM models
415
+ - `alembic-migrations` *(future)* — migration authoring from model metadata
416
+ - `crud-route-builder` *(future)* — service/route layer that consumes models
417
+ - `pytest-backend` *(future)* — test fixtures that use SQLite in-memory DB
418
+
419
+ Typical order when building from scratch:
420
+
421
+ 1. `settings-config`
422
+ 2. `sqlalchemy-models`
423
+ 3. `pydantic-schemas`
424
+ 4. `alembic-migrations`
425
+ 5. `crud-route-builder`
426
+
427
+ ------------------------------------------------------------------------
428
+
429
+ ## Subtle rules
430
+
431
+ - Prefer **canonical path enforcement**: if the repo already has one clearly intended place for model infrastructure, use it rather than creating a parallel pattern.
432
+ - Prefer **minimal patch first**: do not reorganize every model file if a smaller change can establish a clean standard.
433
+ - Prefer **verify before hand-off**: sanity-check imports, typing shape, and relationship symmetry before concluding the model layer is correct.