@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.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/package.json +20 -0
- package/skills/alembic-migrations/SKILL.md +391 -0
- package/skills/click-cli/SKILL.md +204 -0
- package/skills/click-cli-linter/SKILL.md +192 -0
- package/skills/code-quality/SKILL.md +398 -0
- package/skills/dockerize-service/SKILL.md +280 -0
- package/skills/fastapi-errors/SKILL.md +319 -0
- package/skills/fastapi-init/SKILL.md +356 -0
- package/skills/pydantic-schemas/SKILL.md +500 -0
- package/skills/pytest-service/SKILL.md +216 -0
- package/skills/settings-config/SKILL.md +248 -0
- package/skills/sqlalchemy-models/SKILL.md +433 -0
- package/skills/uv/SKILL.md +310 -0
|
@@ -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.
|