@malamute/ai-rules 1.0.0 → 1.3.0

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.
Files changed (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -1,332 +0,0 @@
1
- ---
2
- paths:
3
- - "**/*.py"
4
- ---
5
-
6
- # Flask Rules
7
-
8
- ## Application Factory
9
-
10
- ```python
11
- from flask import Flask
12
- from flask_sqlalchemy import SQLAlchemy
13
- from flask_migrate import Migrate
14
-
15
- db = SQLAlchemy()
16
- migrate = Migrate()
17
-
18
- def create_app(config_name: str = "development") -> Flask:
19
- app = Flask(__name__)
20
- app.config.from_object(config[config_name])
21
-
22
- # Initialize extensions
23
- db.init_app(app)
24
- migrate.init_app(app, db)
25
-
26
- # Register blueprints
27
- from app.users import bp as users_bp
28
- from app.auth import bp as auth_bp
29
-
30
- app.register_blueprint(users_bp, url_prefix="/api/v1/users")
31
- app.register_blueprint(auth_bp, url_prefix="/api/v1/auth")
32
-
33
- # Register error handlers
34
- register_error_handlers(app)
35
-
36
- return app
37
- ```
38
-
39
- ## Blueprints
40
-
41
- ```python
42
- from flask import Blueprint, request, jsonify
43
- from http import HTTPStatus
44
-
45
- bp = Blueprint("users", __name__)
46
-
47
- @bp.get("/")
48
- def list_users():
49
- """List all users with pagination."""
50
- page = request.args.get("page", 1, type=int)
51
- per_page = request.args.get("per_page", 20, type=int)
52
-
53
- pagination = User.query.paginate(page=page, per_page=per_page)
54
-
55
- return jsonify({
56
- "items": [user.to_dict() for user in pagination.items],
57
- "total": pagination.total,
58
- "page": pagination.page,
59
- "pages": pagination.pages,
60
- })
61
-
62
-
63
- @bp.get("/<int:user_id>")
64
- def get_user(user_id: int):
65
- """Get a user by ID."""
66
- user = db.get_or_404(User, user_id)
67
- return jsonify(user.to_dict())
68
-
69
-
70
- @bp.post("/")
71
- def create_user():
72
- """Create a new user."""
73
- data = request.get_json()
74
-
75
- # Validation
76
- errors = validate_user_data(data)
77
- if errors:
78
- return jsonify({"errors": errors}), HTTPStatus.BAD_REQUEST
79
-
80
- user = User(
81
- email=data["email"],
82
- name=data["name"],
83
- )
84
- user.set_password(data["password"])
85
-
86
- db.session.add(user)
87
- db.session.commit()
88
-
89
- return jsonify(user.to_dict()), HTTPStatus.CREATED
90
-
91
-
92
- @bp.put("/<int:user_id>")
93
- def update_user(user_id: int):
94
- """Update a user."""
95
- user = db.get_or_404(User, user_id)
96
- data = request.get_json()
97
-
98
- if "email" in data:
99
- user.email = data["email"]
100
- if "name" in data:
101
- user.name = data["name"]
102
-
103
- db.session.commit()
104
- return jsonify(user.to_dict())
105
-
106
-
107
- @bp.delete("/<int:user_id>")
108
- def delete_user(user_id: int):
109
- """Delete a user."""
110
- user = db.get_or_404(User, user_id)
111
- db.session.delete(user)
112
- db.session.commit()
113
- return "", HTTPStatus.NO_CONTENT
114
- ```
115
-
116
- ## Models with Flask-SQLAlchemy
117
-
118
- ```python
119
- from datetime import datetime
120
- from werkzeug.security import generate_password_hash, check_password_hash
121
- from app import db
122
-
123
- class TimestampMixin:
124
- created_at = db.Column(db.DateTime, default=datetime.utcnow)
125
- updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
126
-
127
- class User(TimestampMixin, db.Model):
128
- __tablename__ = "users"
129
-
130
- id = db.Column(db.Integer, primary_key=True)
131
- email = db.Column(db.String(256), unique=True, nullable=False, index=True)
132
- password_hash = db.Column(db.String(256), nullable=False)
133
- name = db.Column(db.String(100))
134
- role = db.Column(db.String(50), default="user")
135
-
136
- # Relationships
137
- posts = db.relationship("Post", back_populates="author", lazy="dynamic")
138
-
139
- def set_password(self, password: str) -> None:
140
- self.password_hash = generate_password_hash(password)
141
-
142
- def check_password(self, password: str) -> bool:
143
- return check_password_hash(self.password_hash, password)
144
-
145
- def to_dict(self) -> dict:
146
- return {
147
- "id": self.id,
148
- "email": self.email,
149
- "name": self.name,
150
- "role": self.role,
151
- "created_at": self.created_at.isoformat(),
152
- }
153
- ```
154
-
155
- ## Error Handling
156
-
157
- ```python
158
- from flask import jsonify
159
- from werkzeug.exceptions import HTTPException
160
-
161
- def register_error_handlers(app: Flask) -> None:
162
-
163
- @app.errorhandler(HTTPException)
164
- def handle_http_exception(error: HTTPException):
165
- return jsonify({
166
- "error": error.name,
167
- "message": error.description,
168
- }), error.code
169
-
170
- @app.errorhandler(ValidationError)
171
- def handle_validation_error(error: ValidationError):
172
- return jsonify({
173
- "error": "Validation Error",
174
- "message": str(error),
175
- "details": error.errors,
176
- }), HTTPStatus.BAD_REQUEST
177
-
178
- @app.errorhandler(Exception)
179
- def handle_generic_exception(error: Exception):
180
- app.logger.exception("Unhandled exception")
181
- return jsonify({
182
- "error": "Internal Server Error",
183
- "message": "An unexpected error occurred",
184
- }), HTTPStatus.INTERNAL_SERVER_ERROR
185
- ```
186
-
187
- ## Authentication with Flask-JWT-Extended
188
-
189
- ```python
190
- from flask_jwt_extended import (
191
- JWTManager,
192
- create_access_token,
193
- create_refresh_token,
194
- jwt_required,
195
- get_jwt_identity,
196
- current_user,
197
- )
198
-
199
- jwt = JWTManager()
200
-
201
- @jwt.user_identity_loader
202
- def user_identity_lookup(user: User) -> int:
203
- return user.id
204
-
205
- @jwt.user_lookup_loader
206
- def user_lookup_callback(_jwt_header, jwt_data) -> User | None:
207
- identity = jwt_data["sub"]
208
- return User.query.get(identity)
209
-
210
- # Auth blueprint
211
- auth_bp = Blueprint("auth", __name__)
212
-
213
- @auth_bp.post("/login")
214
- def login():
215
- data = request.get_json()
216
- user = User.query.filter_by(email=data["email"]).first()
217
-
218
- if not user or not user.check_password(data["password"]):
219
- return jsonify({"error": "Invalid credentials"}), HTTPStatus.UNAUTHORIZED
220
-
221
- return jsonify({
222
- "access_token": create_access_token(identity=user),
223
- "refresh_token": create_refresh_token(identity=user),
224
- })
225
-
226
- @auth_bp.post("/refresh")
227
- @jwt_required(refresh=True)
228
- def refresh():
229
- user = current_user
230
- return jsonify({
231
- "access_token": create_access_token(identity=user),
232
- })
233
-
234
- # Protected route
235
- @bp.get("/me")
236
- @jwt_required()
237
- def get_current_user():
238
- return jsonify(current_user.to_dict())
239
- ```
240
-
241
- ## Request Validation with Marshmallow
242
-
243
- ```python
244
- from marshmallow import Schema, fields, validate, ValidationError, post_load
245
-
246
- class UserCreateSchema(Schema):
247
- email = fields.Email(required=True)
248
- password = fields.Str(required=True, validate=validate.Length(min=8))
249
- name = fields.Str(required=True, validate=validate.Length(min=1, max=100))
250
-
251
- class UserUpdateSchema(Schema):
252
- email = fields.Email()
253
- name = fields.Str(validate=validate.Length(max=100))
254
-
255
- class UserResponseSchema(Schema):
256
- id = fields.Int(dump_only=True)
257
- email = fields.Email()
258
- name = fields.Str()
259
- created_at = fields.DateTime(dump_only=True)
260
-
261
- # Usage in route
262
- @bp.post("/")
263
- def create_user():
264
- schema = UserCreateSchema()
265
- try:
266
- data = schema.load(request.get_json())
267
- except ValidationError as err:
268
- return jsonify({"errors": err.messages}), HTTPStatus.BAD_REQUEST
269
-
270
- # Create user...
271
- ```
272
-
273
- ## Configuration
274
-
275
- ```python
276
- import os
277
-
278
- class Config:
279
- SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key")
280
- SQLALCHEMY_TRACK_MODIFICATIONS = False
281
- JWT_SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "jwt-secret")
282
- JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
283
-
284
- class DevelopmentConfig(Config):
285
- DEBUG = True
286
- SQLALCHEMY_DATABASE_URI = os.environ.get(
287
- "DATABASE_URL", "sqlite:///dev.db"
288
- )
289
-
290
- class ProductionConfig(Config):
291
- DEBUG = False
292
- SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
293
-
294
- class TestingConfig(Config):
295
- TESTING = True
296
- SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
297
-
298
- config = {
299
- "development": DevelopmentConfig,
300
- "production": ProductionConfig,
301
- "testing": TestingConfig,
302
- }
303
- ```
304
-
305
- ## CLI Commands
306
-
307
- ```python
308
- import click
309
- from flask.cli import with_appcontext
310
-
311
- @app.cli.command("seed-db")
312
- @with_appcontext
313
- def seed_db():
314
- """Seed the database with initial data."""
315
- admin = User(email="admin@example.com", name="Admin", role="admin")
316
- admin.set_password("admin123")
317
- db.session.add(admin)
318
- db.session.commit()
319
- click.echo("Database seeded!")
320
-
321
- @app.cli.command("create-admin")
322
- @click.argument("email")
323
- @click.password_option()
324
- @with_appcontext
325
- def create_admin(email: str, password: str):
326
- """Create an admin user."""
327
- user = User(email=email, role="admin")
328
- user.set_password(password)
329
- db.session.add(user)
330
- db.session.commit()
331
- click.echo(f"Admin {email} created!")
332
- ```
@@ -1,18 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(python *)",
5
- "Bash(uvicorn *)",
6
- "Bash(fastapi *)",
7
- "Bash(flask *)",
8
- "Bash(pytest *)",
9
- "Bash(ruff *)",
10
- "Bash(mypy *)",
11
- "Bash(alembic *)",
12
- "Bash(uv *)",
13
- "Bash(poetry *)",
14
- "Bash(pip *)"
15
- ],
16
- "deny": []
17
- }
18
- }
@@ -1,273 +0,0 @@
1
- # Python Project Guidelines
2
-
3
- @../_shared/CLAUDE.md
4
-
5
- ## Stack
6
-
7
- - Python 3.11+
8
- - FastAPI or Flask
9
- - SQLAlchemy 2.0+ (async support)
10
- - Pydantic v2 for validation
11
- - pytest for testing
12
- - uv or poetry for dependency management
13
-
14
- ## Architecture
15
-
16
- ### Domain-Based Structure (Recommended for Monoliths)
17
-
18
- ```
19
- src/
20
- ├── app/
21
- │ ├── __init__.py
22
- │ ├── main.py # Application entry point
23
- │ ├── config.py # Settings with pydantic-settings
24
- │ ├── database.py # DB session, engine
25
- │ │
26
- │ ├── users/ # Domain module
27
- │ │ ├── __init__.py
28
- │ │ ├── router.py # API endpoints
29
- │ │ ├── schemas.py # Pydantic models (request/response)
30
- │ │ ├── models.py # SQLAlchemy models
31
- │ │ ├── service.py # Business logic
32
- │ │ ├── repository.py # Data access
33
- │ │ ├── dependencies.py # Route dependencies
34
- │ │ └── exceptions.py # Domain exceptions
35
- │ │
36
- │ ├── auth/
37
- │ │ ├── router.py
38
- │ │ ├── schemas.py
39
- │ │ ├── service.py
40
- │ │ ├── jwt.py
41
- │ │ └── dependencies.py
42
- │ │
43
- │ ├── core/ # Shared utilities
44
- │ │ ├── __init__.py
45
- │ │ ├── exceptions.py # Base exceptions
46
- │ │ ├── security.py # Password hashing, etc.
47
- │ │ └── pagination.py
48
- │ │
49
- │ └── common/
50
- │ ├── models.py # Base model classes
51
- │ └── schemas.py # Shared schemas
52
-
53
- ├── tests/
54
- │ ├── conftest.py
55
- │ ├── users/
56
- │ │ ├── test_router.py
57
- │ │ └── test_service.py
58
- │ └── auth/
59
-
60
- ├── alembic/ # Migrations
61
- │ ├── versions/
62
- │ └── env.py
63
-
64
- ├── pyproject.toml
65
- └── alembic.ini
66
- ```
67
-
68
- ### File-Type Structure (For Microservices)
69
-
70
- ```
71
- src/
72
- ├── app/
73
- │ ├── main.py
74
- │ ├── config.py
75
- │ ├── routers/
76
- │ │ ├── users.py
77
- │ │ └── auth.py
78
- │ ├── schemas/
79
- │ │ ├── user.py
80
- │ │ └── auth.py
81
- │ ├── models/
82
- │ │ └── user.py
83
- │ ├── services/
84
- │ │ └── user_service.py
85
- │ └── repositories/
86
- │ └── user_repository.py
87
- ```
88
-
89
- ## Code Style
90
-
91
- ### Type Hints (Required)
92
-
93
- ```python
94
- # Always use type hints
95
- def get_user_by_email(email: str) -> User | None:
96
- ...
97
-
98
- async def create_user(user_data: UserCreate) -> User:
99
- ...
100
-
101
- # Use modern syntax (Python 3.10+)
102
- def process_items(items: list[str]) -> dict[str, int]: # Not List[str]
103
- ...
104
- ```
105
-
106
- ### Naming Conventions
107
-
108
- | Element | Convention | Example |
109
- |---------|------------|---------|
110
- | Modules | snake_case | `user_service.py` |
111
- | Classes | PascalCase | `UserService` |
112
- | Functions | snake_case | `get_user_by_id` |
113
- | Variables | snake_case | `user_count` |
114
- | Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` |
115
- | Private | _prefix | `_internal_method` |
116
-
117
- ### Async/Await
118
-
119
- ```python
120
- # FastAPI is async-first - use async for I/O operations
121
- async def get_users(db: AsyncSession) -> list[User]:
122
- result = await db.execute(select(User))
123
- return result.scalars().all()
124
-
125
- # Don't mix sync I/O in async functions
126
- # Bad - blocks event loop
127
- async def bad_example():
128
- time.sleep(1) # Blocks!
129
- requests.get(url) # Blocks!
130
-
131
- # Good - use async versions
132
- async def good_example():
133
- await asyncio.sleep(1)
134
- async with httpx.AsyncClient() as client:
135
- await client.get(url)
136
- ```
137
-
138
- ## Commands
139
-
140
- ```bash
141
- # Development
142
- uvicorn app.main:app --reload
143
- # or
144
- fastapi dev app/main.py
145
-
146
- # Tests
147
- pytest
148
- pytest -v --cov=app
149
- pytest -k "test_users"
150
-
151
- # Linting
152
- ruff check .
153
- ruff format .
154
-
155
- # Type checking
156
- mypy app/
157
-
158
- # Migrations (Alembic)
159
- alembic revision --autogenerate -m "Add users table"
160
- alembic upgrade head
161
- alembic downgrade -1
162
- ```
163
-
164
- ## Common Patterns
165
-
166
- ### Pydantic Settings
167
-
168
- ```python
169
- from pydantic_settings import BaseSettings, SettingsConfigDict
170
-
171
- class Settings(BaseSettings):
172
- model_config = SettingsConfigDict(
173
- env_file=".env",
174
- env_file_encoding="utf-8",
175
- extra="ignore",
176
- )
177
-
178
- # Database
179
- database_url: str
180
-
181
- # Auth
182
- secret_key: str
183
- algorithm: str = "HS256"
184
- access_token_expire_minutes: int = 30
185
-
186
- # App
187
- debug: bool = False
188
- api_prefix: str = "/api/v1"
189
-
190
- settings = Settings()
191
- ```
192
-
193
- ### Dependency Injection
194
-
195
- ```python
196
- from typing import Annotated
197
- from fastapi import Depends
198
-
199
- # Database session dependency
200
- async def get_db() -> AsyncGenerator[AsyncSession, None]:
201
- async with async_session_maker() as session:
202
- try:
203
- yield session
204
- await session.commit()
205
- except Exception:
206
- await session.rollback()
207
- raise
208
-
209
- DbSession = Annotated[AsyncSession, Depends(get_db)]
210
-
211
- # Service dependency
212
- def get_user_service(db: DbSession) -> UserService:
213
- return UserService(UserRepository(db))
214
-
215
- UserServiceDep = Annotated[UserService, Depends(get_user_service)]
216
-
217
- # Usage in router
218
- @router.get("/{user_id}")
219
- async def get_user(user_id: int, service: UserServiceDep) -> UserResponse:
220
- return await service.get_by_id(user_id)
221
- ```
222
-
223
- ### Exception Handling
224
-
225
- ```python
226
- from fastapi import HTTPException, status
227
-
228
- # Domain exceptions
229
- class UserNotFoundError(Exception):
230
- def __init__(self, user_id: int):
231
- self.user_id = user_id
232
- super().__init__(f"User {user_id} not found")
233
-
234
- class EmailAlreadyExistsError(Exception):
235
- def __init__(self, email: str):
236
- self.email = email
237
-
238
- # Global exception handler
239
- @app.exception_handler(UserNotFoundError)
240
- async def user_not_found_handler(request: Request, exc: UserNotFoundError):
241
- return JSONResponse(
242
- status_code=status.HTTP_404_NOT_FOUND,
243
- content={"detail": str(exc)},
244
- )
245
-
246
- # Or use HTTPException directly in service
247
- class UserService:
248
- async def get_by_id(self, user_id: int) -> User:
249
- user = await self.repository.get(user_id)
250
- if not user:
251
- raise HTTPException(
252
- status_code=status.HTTP_404_NOT_FOUND,
253
- detail=f"User {user_id} not found",
254
- )
255
- return user
256
- ```
257
-
258
- ### API Versioning
259
-
260
- ```python
261
- from fastapi import APIRouter
262
-
263
- # Versioned routers
264
- v1_router = APIRouter(prefix="/api/v1")
265
- v1_router.include_router(users.router, prefix="/users", tags=["users"])
266
- v1_router.include_router(auth.router, prefix="/auth", tags=["auth"])
267
-
268
- v2_router = APIRouter(prefix="/api/v2")
269
- # ... v2 routes
270
-
271
- app.include_router(v1_router)
272
- app.include_router(v2_router)
273
- ```