@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.
- package/README.md +272 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/_shared/rules/conventions/documentation.md +324 -0
- package/configs/_shared/rules/conventions/git.md +265 -0
- package/configs/_shared/rules/conventions/npm.md +80 -0
- package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
- package/configs/_shared/rules/conventions/principles.md +334 -0
- package/configs/_shared/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/rules/devops/docker.md +275 -0
- package/configs/_shared/rules/devops/nx.md +194 -0
- package/configs/_shared/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/rules/lang/python/async.md +337 -0
- package/configs/_shared/rules/lang/python/celery.md +476 -0
- package/configs/_shared/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/rules/lang/python/python.md +172 -0
- package/configs/_shared/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/rules/quality/error-handling.md +48 -0
- package/configs/_shared/rules/quality/logging.md +45 -0
- package/configs/_shared/rules/quality/observability.md +240 -0
- package/configs/_shared/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/rules/security/secrets-management.md +222 -0
- package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
- package/configs/angular/rules/core/resource.md +285 -0
- package/configs/angular/rules/core/signals.md +323 -0
- package/configs/angular/rules/http.md +338 -0
- package/configs/angular/rules/routing.md +291 -0
- package/configs/angular/rules/ssr.md +312 -0
- package/configs/angular/rules/state/signal-store.md +408 -0
- package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
- package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
- package/configs/angular/rules/ui/aria.md +422 -0
- package/configs/angular/rules/ui/forms.md +424 -0
- package/configs/angular/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/{.claude/settings.json → settings.json} +3 -0
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/dotnet/rules/background-services.md +552 -0
- package/configs/dotnet/rules/configuration.md +426 -0
- package/configs/dotnet/rules/ddd.md +447 -0
- package/configs/dotnet/rules/dependency-injection.md +343 -0
- package/configs/dotnet/rules/mediatr.md +320 -0
- package/configs/dotnet/rules/middleware.md +489 -0
- package/configs/dotnet/rules/result-pattern.md +363 -0
- package/configs/dotnet/rules/validation.md +388 -0
- package/configs/dotnet/settings.json +29 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/fastapi/rules/background-tasks.md +254 -0
- package/configs/fastapi/rules/dependencies.md +170 -0
- package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
- package/configs/fastapi/rules/lifespan.md +274 -0
- package/configs/fastapi/rules/middleware.md +229 -0
- package/configs/fastapi/rules/pydantic.md +433 -0
- package/configs/fastapi/rules/responses.md +251 -0
- package/configs/fastapi/rules/routers.md +202 -0
- package/configs/fastapi/rules/security.md +222 -0
- package/configs/fastapi/rules/testing.md +251 -0
- package/configs/fastapi/rules/websockets.md +298 -0
- package/configs/fastapi/settings.json +35 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/flask/rules/blueprints.md +208 -0
- package/configs/flask/rules/cli.md +285 -0
- package/configs/flask/rules/configuration.md +281 -0
- package/configs/flask/rules/context.md +238 -0
- package/configs/flask/rules/error-handlers.md +278 -0
- package/configs/flask/rules/extensions.md +278 -0
- package/configs/flask/rules/flask.md +171 -0
- package/configs/flask/rules/marshmallow.md +206 -0
- package/configs/flask/rules/security.md +267 -0
- package/configs/flask/rules/testing.md +284 -0
- package/configs/flask/settings.json +35 -0
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nestjs/rules/common-patterns.md +300 -0
- package/configs/nestjs/rules/filters.md +376 -0
- package/configs/nestjs/rules/interceptors.md +317 -0
- package/configs/nestjs/rules/middleware.md +321 -0
- package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
- package/configs/nestjs/rules/pipes.md +351 -0
- package/configs/nestjs/rules/websockets.md +451 -0
- package/configs/nestjs/settings.json +31 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/configs/nextjs/rules/api-routes.md +358 -0
- package/configs/nextjs/rules/authentication.md +355 -0
- package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
- package/configs/nextjs/rules/data-fetching.md +249 -0
- package/configs/nextjs/rules/database.md +400 -0
- package/configs/nextjs/rules/middleware.md +303 -0
- package/configs/nextjs/rules/routing.md +324 -0
- package/configs/nextjs/rules/seo.md +350 -0
- package/configs/nextjs/rules/server-actions.md +353 -0
- package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
- package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
- package/package.json +24 -9
- package/src/cli.js +218 -0
- package/src/config.js +63 -0
- package/src/index.js +4 -0
- package/src/installer.js +414 -0
- package/src/merge.js +109 -0
- package/src/tech-config.json +45 -0
- package/src/utils.js +88 -0
- package/configs/dotnet/.claude/settings.json +0 -9
- package/configs/nestjs/.claude/settings.json +0 -15
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
- /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
- /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
|
-
}
|
package/configs/python/CLAUDE.md
DELETED
|
@@ -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
|
-
```
|