@malamute/ai-rules 1.0.0 → 1.2.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 +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/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/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- 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/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# FastAPI Project Guidelines
|
|
2
|
+
|
|
3
|
+
@../_shared/CLAUDE.md
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- Python 3.12+
|
|
8
|
+
- FastAPI 0.115+ (Pydantic v2 by default)
|
|
9
|
+
- SQLAlchemy 2.0+ (async support)
|
|
10
|
+
- Pydantic v2 for validation
|
|
11
|
+
- pytest + httpx for testing
|
|
12
|
+
- uv or poetry for dependencies
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/app/
|
|
18
|
+
├── main.py # Entry point
|
|
19
|
+
├── config.py # Settings (pydantic-settings)
|
|
20
|
+
├── database.py # DB session, engine
|
|
21
|
+
├── [domain]/ # Feature modules
|
|
22
|
+
│ ├── router.py # API endpoints
|
|
23
|
+
│ ├── schemas.py # Pydantic models (request/response)
|
|
24
|
+
│ ├── models.py # SQLAlchemy models
|
|
25
|
+
│ ├── service.py # Business logic
|
|
26
|
+
│ ├── repository.py # Data access
|
|
27
|
+
│ └── dependencies.py # Route dependencies
|
|
28
|
+
├── core/ # Shared utilities
|
|
29
|
+
│ ├── exceptions.py
|
|
30
|
+
│ └── security.py
|
|
31
|
+
└── common/
|
|
32
|
+
├── models.py # Base models
|
|
33
|
+
└── schemas.py # Shared schemas
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## FastAPI Patterns
|
|
37
|
+
|
|
38
|
+
### Dependency Injection
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from typing import Annotated
|
|
42
|
+
from fastapi import Depends
|
|
43
|
+
|
|
44
|
+
CurrentUser = Annotated[User, Depends(get_current_user)]
|
|
45
|
+
DbSession = Annotated[AsyncSession, Depends(get_db)]
|
|
46
|
+
|
|
47
|
+
@router.get("/users/me")
|
|
48
|
+
async def get_me(user: CurrentUser, db: DbSession) -> UserResponse:
|
|
49
|
+
return await user_service.get_profile(db, user.id)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Lifespan (not `on_event`)
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from contextlib import asynccontextmanager
|
|
56
|
+
|
|
57
|
+
@asynccontextmanager
|
|
58
|
+
async def lifespan(app: FastAPI):
|
|
59
|
+
# Startup
|
|
60
|
+
app.state.db = await create_engine()
|
|
61
|
+
app.state.redis = await aioredis.from_url(settings.redis_url)
|
|
62
|
+
yield
|
|
63
|
+
# Shutdown
|
|
64
|
+
await app.state.db.dispose()
|
|
65
|
+
await app.state.redis.close()
|
|
66
|
+
|
|
67
|
+
app = FastAPI(lifespan=lifespan)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Response Models
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
@router.get(
|
|
74
|
+
"/users/{user_id}",
|
|
75
|
+
response_model=UserResponse,
|
|
76
|
+
status_code=status.HTTP_200_OK,
|
|
77
|
+
)
|
|
78
|
+
async def get_user(user_id: int, db: DbSession) -> User:
|
|
79
|
+
user = await user_repo.get(db, user_id)
|
|
80
|
+
if not user:
|
|
81
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
82
|
+
return user
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Pydantic v2
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pydantic import BaseModel, ConfigDict, Field, EmailStr
|
|
89
|
+
|
|
90
|
+
class UserBase(BaseModel):
|
|
91
|
+
email: EmailStr
|
|
92
|
+
name: str = Field(min_length=1, max_length=100)
|
|
93
|
+
|
|
94
|
+
class UserCreate(UserBase):
|
|
95
|
+
password: str = Field(min_length=8)
|
|
96
|
+
|
|
97
|
+
class UserResponse(UserBase):
|
|
98
|
+
id: int
|
|
99
|
+
model_config = ConfigDict(from_attributes=True)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## SQLAlchemy 2.0
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
|
|
106
|
+
|
|
107
|
+
class Base(DeclarativeBase):
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
class User(Base):
|
|
111
|
+
__tablename__ = "users"
|
|
112
|
+
|
|
113
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
114
|
+
email: Mapped[str] = mapped_column(unique=True, index=True)
|
|
115
|
+
hashed_password: Mapped[str]
|
|
116
|
+
is_active: Mapped[bool] = mapped_column(default=True)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Error Handling
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
class AppException(Exception):
|
|
123
|
+
def __init__(self, status_code: int, detail: str):
|
|
124
|
+
self.status_code = status_code
|
|
125
|
+
self.detail = detail
|
|
126
|
+
|
|
127
|
+
@app.exception_handler(AppException)
|
|
128
|
+
async def app_exception_handler(request: Request, exc: AppException):
|
|
129
|
+
return JSONResponse(
|
|
130
|
+
status_code=exc.status_code,
|
|
131
|
+
content={"error": exc.detail},
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Commands
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uvicorn app.main:app --reload # Dev server
|
|
139
|
+
pytest # Run tests
|
|
140
|
+
pytest --cov=app # Coverage
|
|
141
|
+
ruff check . && ruff format . # Lint + format
|
|
142
|
+
alembic upgrade head # Run migrations
|
|
143
|
+
alembic revision --autogenerate # Generate migration
|
|
144
|
+
```
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Flask Blueprint Patterns
|
|
7
|
+
|
|
8
|
+
## Basic Blueprint
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
# GOOD - organized blueprint
|
|
12
|
+
from flask import Blueprint, request, jsonify
|
|
13
|
+
|
|
14
|
+
users_bp = Blueprint("users", __name__)
|
|
15
|
+
|
|
16
|
+
@users_bp.route("/", methods=["GET"])
|
|
17
|
+
def list_users():
|
|
18
|
+
"""List all users."""
|
|
19
|
+
users = UserService.get_all()
|
|
20
|
+
return jsonify(UserSchema(many=True).dump(users))
|
|
21
|
+
|
|
22
|
+
@users_bp.route("/", methods=["POST"])
|
|
23
|
+
def create_user():
|
|
24
|
+
"""Create a new user."""
|
|
25
|
+
schema = UserCreateSchema()
|
|
26
|
+
data = schema.load(request.get_json())
|
|
27
|
+
user = UserService.create(data)
|
|
28
|
+
return jsonify(UserSchema().dump(user)), 201
|
|
29
|
+
|
|
30
|
+
@users_bp.route("/<int:user_id>")
|
|
31
|
+
def get_user(user_id: int):
|
|
32
|
+
"""Get user by ID."""
|
|
33
|
+
user = UserService.get_by_id(user_id)
|
|
34
|
+
if not user:
|
|
35
|
+
return jsonify({"error": "User not found"}), 404
|
|
36
|
+
return jsonify(UserSchema().dump(user))
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Blueprint Registration
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# app/__init__.py
|
|
43
|
+
from flask import Flask
|
|
44
|
+
|
|
45
|
+
def create_app(config_name: str = "development") -> Flask:
|
|
46
|
+
app = Flask(__name__)
|
|
47
|
+
app.config.from_object(config[config_name])
|
|
48
|
+
|
|
49
|
+
# Register blueprints
|
|
50
|
+
from app.users import users_bp
|
|
51
|
+
from app.auth import auth_bp
|
|
52
|
+
from app.products import products_bp
|
|
53
|
+
|
|
54
|
+
app.register_blueprint(users_bp, url_prefix="/api/v1/users")
|
|
55
|
+
app.register_blueprint(auth_bp, url_prefix="/api/v1/auth")
|
|
56
|
+
app.register_blueprint(products_bp, url_prefix="/api/v1/products")
|
|
57
|
+
|
|
58
|
+
return app
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Blueprint with Resources
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# app/users/__init__.py
|
|
65
|
+
from flask import Blueprint
|
|
66
|
+
|
|
67
|
+
users_bp = Blueprint("users", __name__)
|
|
68
|
+
|
|
69
|
+
# Import routes after blueprint creation to avoid circular imports
|
|
70
|
+
from app.users import routes # noqa: E402, F401
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# app/users/routes.py
|
|
75
|
+
from app.users import users_bp
|
|
76
|
+
from app.users.schemas import UserSchema, UserCreateSchema
|
|
77
|
+
from app.users.services import UserService
|
|
78
|
+
|
|
79
|
+
@users_bp.route("/", methods=["GET"])
|
|
80
|
+
def list_users():
|
|
81
|
+
...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Nested Blueprints
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# API versioning with nested blueprints
|
|
88
|
+
api_v1 = Blueprint("api_v1", __name__, url_prefix="/api/v1")
|
|
89
|
+
api_v2 = Blueprint("api_v2", __name__, url_prefix="/api/v2")
|
|
90
|
+
|
|
91
|
+
# Register child blueprints
|
|
92
|
+
api_v1.register_blueprint(users_bp, url_prefix="/users")
|
|
93
|
+
api_v1.register_blueprint(products_bp, url_prefix="/products")
|
|
94
|
+
|
|
95
|
+
api_v2.register_blueprint(users_v2_bp, url_prefix="/users")
|
|
96
|
+
|
|
97
|
+
# Register with app
|
|
98
|
+
app.register_blueprint(api_v1)
|
|
99
|
+
app.register_blueprint(api_v2)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Blueprint-Specific Error Handlers
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
users_bp = Blueprint("users", __name__)
|
|
106
|
+
|
|
107
|
+
@users_bp.errorhandler(404)
|
|
108
|
+
def user_not_found(error):
|
|
109
|
+
return jsonify({"error": "User not found"}), 404
|
|
110
|
+
|
|
111
|
+
@users_bp.errorhandler(ValidationError)
|
|
112
|
+
def validation_error(error):
|
|
113
|
+
return jsonify({"error": "Validation failed", "details": error.messages}), 400
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Blueprint Hooks
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
users_bp = Blueprint("users", __name__)
|
|
120
|
+
|
|
121
|
+
@users_bp.before_request
|
|
122
|
+
def before_user_request():
|
|
123
|
+
"""Run before every request to this blueprint."""
|
|
124
|
+
# Verify API key, log request, etc.
|
|
125
|
+
if not verify_api_key(request.headers.get("X-API-Key")):
|
|
126
|
+
return jsonify({"error": "Invalid API key"}), 401
|
|
127
|
+
|
|
128
|
+
@users_bp.after_request
|
|
129
|
+
def after_user_request(response):
|
|
130
|
+
"""Run after every request to this blueprint."""
|
|
131
|
+
response.headers["X-Blueprint"] = "users"
|
|
132
|
+
return response
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Blueprint URL Builders
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from flask import url_for
|
|
139
|
+
|
|
140
|
+
# Build URL for blueprint route
|
|
141
|
+
url = url_for("users.get_user", user_id=1) # /api/v1/users/1
|
|
142
|
+
|
|
143
|
+
# With external URL
|
|
144
|
+
url = url_for("users.get_user", user_id=1, _external=True)
|
|
145
|
+
# https://example.com/api/v1/users/1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Blueprint Templates
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Blueprint with templates
|
|
152
|
+
users_bp = Blueprint(
|
|
153
|
+
"users",
|
|
154
|
+
__name__,
|
|
155
|
+
template_folder="templates",
|
|
156
|
+
static_folder="static",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@users_bp.route("/profile")
|
|
160
|
+
def profile():
|
|
161
|
+
return render_template("users/profile.html") # From blueprint's templates/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Blueprint Context Processor
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
@users_bp.context_processor
|
|
168
|
+
def user_context():
|
|
169
|
+
"""Add variables to all templates rendered by this blueprint."""
|
|
170
|
+
return {
|
|
171
|
+
"current_user": get_current_user(),
|
|
172
|
+
"user_count": User.query.count(),
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Class-Based Views with Blueprints
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from flask.views import MethodView
|
|
180
|
+
|
|
181
|
+
class UserAPI(MethodView):
|
|
182
|
+
def get(self, user_id: int | None = None):
|
|
183
|
+
if user_id is None:
|
|
184
|
+
return jsonify(UserSchema(many=True).dump(User.query.all()))
|
|
185
|
+
user = User.query.get_or_404(user_id)
|
|
186
|
+
return jsonify(UserSchema().dump(user))
|
|
187
|
+
|
|
188
|
+
def post(self):
|
|
189
|
+
data = UserCreateSchema().load(request.get_json())
|
|
190
|
+
user = UserService.create(data)
|
|
191
|
+
return jsonify(UserSchema().dump(user)), 201
|
|
192
|
+
|
|
193
|
+
def put(self, user_id: int):
|
|
194
|
+
user = User.query.get_or_404(user_id)
|
|
195
|
+
data = UserUpdateSchema().load(request.get_json())
|
|
196
|
+
user = UserService.update(user, data)
|
|
197
|
+
return jsonify(UserSchema().dump(user))
|
|
198
|
+
|
|
199
|
+
def delete(self, user_id: int):
|
|
200
|
+
user = User.query.get_or_404(user_id)
|
|
201
|
+
UserService.delete(user)
|
|
202
|
+
return "", 204
|
|
203
|
+
|
|
204
|
+
# Register view
|
|
205
|
+
user_view = UserAPI.as_view("user_api")
|
|
206
|
+
users_bp.add_url_rule("/", view_func=user_view, methods=["GET", "POST"])
|
|
207
|
+
users_bp.add_url_rule("/<int:user_id>", view_func=user_view, methods=["GET", "PUT", "DELETE"])
|
|
208
|
+
```
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Flask CLI Commands
|
|
7
|
+
|
|
8
|
+
## Basic Commands
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import click
|
|
12
|
+
from flask import Flask
|
|
13
|
+
from flask.cli import with_appcontext
|
|
14
|
+
|
|
15
|
+
app = Flask(__name__)
|
|
16
|
+
|
|
17
|
+
@app.cli.command("init-db")
|
|
18
|
+
@with_appcontext
|
|
19
|
+
def init_db_command():
|
|
20
|
+
"""Initialize the database."""
|
|
21
|
+
db.create_all()
|
|
22
|
+
click.echo("Database initialized.")
|
|
23
|
+
|
|
24
|
+
@app.cli.command("seed")
|
|
25
|
+
@with_appcontext
|
|
26
|
+
def seed_command():
|
|
27
|
+
"""Seed the database with sample data."""
|
|
28
|
+
from app.seeds import seed_all
|
|
29
|
+
seed_all()
|
|
30
|
+
click.echo("Database seeded.")
|
|
31
|
+
|
|
32
|
+
# Usage:
|
|
33
|
+
# flask init-db
|
|
34
|
+
# flask seed
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Commands with Arguments
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
@app.cli.command("create-user")
|
|
41
|
+
@click.argument("email")
|
|
42
|
+
@click.argument("name")
|
|
43
|
+
@click.option("--admin", is_flag=True, help="Make user an admin")
|
|
44
|
+
@with_appcontext
|
|
45
|
+
def create_user_command(email: str, name: str, admin: bool):
|
|
46
|
+
"""Create a new user."""
|
|
47
|
+
user = User(email=email, name=name, is_admin=admin)
|
|
48
|
+
db.session.add(user)
|
|
49
|
+
db.session.commit()
|
|
50
|
+
click.echo(f"Created user: {user.email} (admin={admin})")
|
|
51
|
+
|
|
52
|
+
# Usage:
|
|
53
|
+
# flask create-user john@example.com "John Doe"
|
|
54
|
+
# flask create-user admin@example.com "Admin" --admin
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Commands with Options
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
@app.cli.command("export-users")
|
|
61
|
+
@click.option("--format", type=click.Choice(["json", "csv"]), default="json")
|
|
62
|
+
@click.option("--output", "-o", type=click.Path(), default="users.json")
|
|
63
|
+
@click.option("--active-only", is_flag=True, help="Export only active users")
|
|
64
|
+
@with_appcontext
|
|
65
|
+
def export_users_command(format: str, output: str, active_only: bool):
|
|
66
|
+
"""Export users to a file."""
|
|
67
|
+
query = User.query
|
|
68
|
+
if active_only:
|
|
69
|
+
query = query.filter(User.is_active == True)
|
|
70
|
+
|
|
71
|
+
users = query.all()
|
|
72
|
+
|
|
73
|
+
if format == "json":
|
|
74
|
+
data = UserSchema(many=True).dump(users)
|
|
75
|
+
with open(output, "w") as f:
|
|
76
|
+
json.dump(data, f, indent=2)
|
|
77
|
+
elif format == "csv":
|
|
78
|
+
# CSV export logic
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
click.echo(f"Exported {len(users)} users to {output}")
|
|
82
|
+
|
|
83
|
+
# Usage:
|
|
84
|
+
# flask export-users --format csv -o users.csv --active-only
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Command Groups
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
@app.cli.group()
|
|
91
|
+
def user():
|
|
92
|
+
"""User management commands."""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@user.command("create")
|
|
96
|
+
@click.argument("email")
|
|
97
|
+
@click.argument("name")
|
|
98
|
+
@with_appcontext
|
|
99
|
+
def user_create(email: str, name: str):
|
|
100
|
+
"""Create a new user."""
|
|
101
|
+
user = User(email=email, name=name)
|
|
102
|
+
db.session.add(user)
|
|
103
|
+
db.session.commit()
|
|
104
|
+
click.echo(f"Created: {user.email}")
|
|
105
|
+
|
|
106
|
+
@user.command("delete")
|
|
107
|
+
@click.argument("email")
|
|
108
|
+
@click.confirmation_option(prompt="Are you sure?")
|
|
109
|
+
@with_appcontext
|
|
110
|
+
def user_delete(email: str):
|
|
111
|
+
"""Delete a user."""
|
|
112
|
+
user = User.query.filter_by(email=email).first()
|
|
113
|
+
if not user:
|
|
114
|
+
click.echo(f"User not found: {email}", err=True)
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
db.session.delete(user)
|
|
118
|
+
db.session.commit()
|
|
119
|
+
click.echo(f"Deleted: {email}")
|
|
120
|
+
|
|
121
|
+
@user.command("list")
|
|
122
|
+
@click.option("--limit", default=10)
|
|
123
|
+
@with_appcontext
|
|
124
|
+
def user_list(limit: int):
|
|
125
|
+
"""List users."""
|
|
126
|
+
users = User.query.limit(limit).all()
|
|
127
|
+
for user in users:
|
|
128
|
+
click.echo(f"{user.id}: {user.email} ({user.name})")
|
|
129
|
+
|
|
130
|
+
# Usage:
|
|
131
|
+
# flask user create john@example.com "John"
|
|
132
|
+
# flask user delete john@example.com
|
|
133
|
+
# flask user list --limit 20
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Blueprint Commands
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# In blueprint
|
|
140
|
+
from flask import Blueprint
|
|
141
|
+
|
|
142
|
+
users_bp = Blueprint("users", __name__, cli_group="users")
|
|
143
|
+
|
|
144
|
+
@users_bp.cli.command("sync")
|
|
145
|
+
@with_appcontext
|
|
146
|
+
def sync_users():
|
|
147
|
+
"""Sync users from external source."""
|
|
148
|
+
# Sync logic
|
|
149
|
+
click.echo("Users synced")
|
|
150
|
+
|
|
151
|
+
# Usage:
|
|
152
|
+
# flask users sync
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Progress Bars
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
@app.cli.command("migrate-data")
|
|
159
|
+
@with_appcontext
|
|
160
|
+
def migrate_data_command():
|
|
161
|
+
"""Migrate data with progress bar."""
|
|
162
|
+
records = OldModel.query.all()
|
|
163
|
+
|
|
164
|
+
with click.progressbar(records, label="Migrating") as bar:
|
|
165
|
+
for record in bar:
|
|
166
|
+
new_record = migrate_record(record)
|
|
167
|
+
db.session.add(new_record)
|
|
168
|
+
|
|
169
|
+
db.session.commit()
|
|
170
|
+
click.echo("Migration complete!")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Colored Output
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
@app.cli.command("check")
|
|
177
|
+
@with_appcontext
|
|
178
|
+
def check_command():
|
|
179
|
+
"""Check application health."""
|
|
180
|
+
# Database
|
|
181
|
+
try:
|
|
182
|
+
db.session.execute("SELECT 1")
|
|
183
|
+
click.secho("✓ Database: OK", fg="green")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
click.secho(f"✗ Database: {e}", fg="red")
|
|
186
|
+
|
|
187
|
+
# Redis
|
|
188
|
+
try:
|
|
189
|
+
redis_client.ping()
|
|
190
|
+
click.secho("✓ Redis: OK", fg="green")
|
|
191
|
+
except Exception as e:
|
|
192
|
+
click.secho(f"✗ Redis: {e}", fg="red")
|
|
193
|
+
|
|
194
|
+
# External API
|
|
195
|
+
try:
|
|
196
|
+
response = requests.get(f"{API_URL}/health", timeout=5)
|
|
197
|
+
response.raise_for_status()
|
|
198
|
+
click.secho("✓ External API: OK", fg="green")
|
|
199
|
+
except Exception as e:
|
|
200
|
+
click.secho(f"✗ External API: {e}", fg="yellow")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Interactive Prompts
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
@app.cli.command("setup")
|
|
207
|
+
def setup_command():
|
|
208
|
+
"""Interactive setup wizard."""
|
|
209
|
+
click.echo("Welcome to the setup wizard!")
|
|
210
|
+
|
|
211
|
+
# Text input
|
|
212
|
+
app_name = click.prompt("Application name", default="My App")
|
|
213
|
+
|
|
214
|
+
# Password input
|
|
215
|
+
secret = click.prompt("Secret key", hide_input=True)
|
|
216
|
+
|
|
217
|
+
# Choice
|
|
218
|
+
env = click.prompt(
|
|
219
|
+
"Environment",
|
|
220
|
+
type=click.Choice(["development", "staging", "production"]),
|
|
221
|
+
default="development",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Confirmation
|
|
225
|
+
if click.confirm("Save configuration?"):
|
|
226
|
+
save_config(app_name=app_name, secret=secret, env=env)
|
|
227
|
+
click.echo("Configuration saved!")
|
|
228
|
+
else:
|
|
229
|
+
click.echo("Aborted.")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Async Commands (Flask 2.0+)
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
import asyncio
|
|
236
|
+
|
|
237
|
+
@app.cli.command("async-task")
|
|
238
|
+
@with_appcontext
|
|
239
|
+
def async_task_command():
|
|
240
|
+
"""Run async task."""
|
|
241
|
+
asyncio.run(run_async_task())
|
|
242
|
+
|
|
243
|
+
async def run_async_task():
|
|
244
|
+
async with aiohttp.ClientSession() as session:
|
|
245
|
+
async with session.get("https://api.example.com/data") as response:
|
|
246
|
+
data = await response.json()
|
|
247
|
+
click.echo(f"Fetched {len(data)} records")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Custom CLI Script
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# manage.py
|
|
254
|
+
import click
|
|
255
|
+
from app import create_app, db
|
|
256
|
+
|
|
257
|
+
app = create_app()
|
|
258
|
+
|
|
259
|
+
@click.group()
|
|
260
|
+
def cli():
|
|
261
|
+
"""Management script."""
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
@cli.command()
|
|
265
|
+
def runserver():
|
|
266
|
+
"""Run development server."""
|
|
267
|
+
app.run(debug=True)
|
|
268
|
+
|
|
269
|
+
@cli.command()
|
|
270
|
+
@click.option("--drop", is_flag=True, help="Drop tables first")
|
|
271
|
+
def initdb(drop: bool):
|
|
272
|
+
"""Initialize database."""
|
|
273
|
+
with app.app_context():
|
|
274
|
+
if drop:
|
|
275
|
+
db.drop_all()
|
|
276
|
+
db.create_all()
|
|
277
|
+
click.echo("Database initialized.")
|
|
278
|
+
|
|
279
|
+
if __name__ == "__main__":
|
|
280
|
+
cli()
|
|
281
|
+
|
|
282
|
+
# Usage:
|
|
283
|
+
# python manage.py runserver
|
|
284
|
+
# python manage.py initdb --drop
|
|
285
|
+
```
|