@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
@@ -0,0 +1,238 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Flask Context Management
7
+
8
+ ## Application Context
9
+
10
+ ```python
11
+ from flask import current_app, g
12
+
13
+ # GOOD - use application context
14
+ with app.app_context():
15
+ db.create_all()
16
+ current_app.logger.info("Database initialized")
17
+
18
+ # In CLI commands
19
+ @app.cli.command("init-db")
20
+ def init_db():
21
+ # Already in app context
22
+ db.create_all()
23
+ click.echo("Database initialized")
24
+
25
+ # BAD - access outside context
26
+ db.create_all() # RuntimeError: Working outside of application context
27
+ ```
28
+
29
+ ## Request Context
30
+
31
+ ```python
32
+ from flask import request, session, g
33
+
34
+ # Automatically pushed during requests
35
+ @app.route("/")
36
+ def index():
37
+ user_agent = request.headers.get("User-Agent")
38
+ user_id = session.get("user_id")
39
+ return f"UA: {user_agent}"
40
+
41
+ # Test request context
42
+ with app.test_request_context("/users?page=2"):
43
+ assert request.path == "/users"
44
+ assert request.args["page"] == "2"
45
+ url = url_for("users.list_users")
46
+ ```
47
+
48
+ ## The `g` Object
49
+
50
+ ```python
51
+ from flask import g
52
+
53
+ # GOOD - store per-request data in g
54
+ @app.before_request
55
+ def load_user():
56
+ user_id = session.get("user_id")
57
+ if user_id:
58
+ g.user = User.query.get(user_id)
59
+ else:
60
+ g.user = None
61
+
62
+ @app.route("/profile")
63
+ def profile():
64
+ if g.user is None:
65
+ return redirect(url_for("auth.login"))
66
+ return render_template("profile.html", user=g.user)
67
+
68
+ # GOOD - lazy loading with g
69
+ def get_db():
70
+ if "db" not in g:
71
+ g.db = connect_to_database()
72
+ return g.db
73
+
74
+ @app.teardown_appcontext
75
+ def close_db(exception):
76
+ db = g.pop("db", None)
77
+ if db is not None:
78
+ db.close()
79
+ ```
80
+
81
+ ## Request Hooks
82
+
83
+ ```python
84
+ # Before first request (deprecated in Flask 2.3+)
85
+ # Use app.before_request or lifespan pattern instead
86
+
87
+ @app.before_request
88
+ def before_every_request():
89
+ """Run before every request."""
90
+ g.start_time = time.time()
91
+ g.request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
92
+
93
+ @app.after_request
94
+ def after_every_request(response):
95
+ """Run after every request (even on error)."""
96
+ duration = time.time() - g.start_time
97
+ response.headers["X-Request-ID"] = g.request_id
98
+ response.headers["X-Response-Time"] = f"{duration:.4f}s"
99
+ return response
100
+
101
+ @app.teardown_request
102
+ def teardown_request(exception):
103
+ """Run at the end of request, for cleanup."""
104
+ if exception:
105
+ app.logger.error(f"Request failed: {exception}")
106
+ ```
107
+
108
+ ## Blueprint-Specific Hooks
109
+
110
+ ```python
111
+ users_bp = Blueprint("users", __name__)
112
+
113
+ @users_bp.before_request
114
+ def before_user_request():
115
+ """Only runs for requests to this blueprint."""
116
+ g.service = UserService(db.session)
117
+
118
+ @users_bp.after_request
119
+ def after_user_request(response):
120
+ """Only runs for requests to this blueprint."""
121
+ return response
122
+
123
+ # App-wide hooks still run for blueprint requests
124
+ ```
125
+
126
+ ## Context Locals with werkzeug
127
+
128
+ ```python
129
+ from werkzeug.local import LocalStack, LocalProxy
130
+
131
+ # Custom context local
132
+ _request_ctx_stack = LocalStack()
133
+
134
+ def get_current_request_id():
135
+ ctx = _request_ctx_stack.top
136
+ if ctx is not None:
137
+ return ctx.request_id
138
+ return None
139
+
140
+ request_id = LocalProxy(get_current_request_id)
141
+ ```
142
+
143
+ ## Async Context (Flask 2.0+)
144
+
145
+ ```python
146
+ from flask import Flask
147
+ import asyncio
148
+
149
+ app = Flask(__name__)
150
+
151
+ @app.route("/async")
152
+ async def async_route():
153
+ # Can use async/await in routes
154
+ result = await some_async_operation()
155
+ return jsonify(result)
156
+
157
+ # Context is preserved in async functions
158
+ @app.route("/async-context")
159
+ async def async_with_context():
160
+ # current_app, g, request all work
161
+ app.logger.info("Async route called")
162
+ g.async_data = await fetch_data()
163
+ return jsonify(g.async_data)
164
+ ```
165
+
166
+ ## Testing Contexts
167
+
168
+ ```python
169
+ import pytest
170
+
171
+ @pytest.fixture
172
+ def app():
173
+ app = create_app("testing")
174
+ return app
175
+
176
+ @pytest.fixture
177
+ def client(app):
178
+ return app.test_client()
179
+
180
+ @pytest.fixture
181
+ def app_context(app):
182
+ with app.app_context():
183
+ yield
184
+
185
+ @pytest.fixture
186
+ def request_context(app):
187
+ with app.test_request_context():
188
+ yield
189
+
190
+ def test_with_app_context(app_context):
191
+ # current_app is available
192
+ assert current_app.config["TESTING"]
193
+
194
+ def test_with_request_context(request_context):
195
+ # request, g are available
196
+ g.user = User(name="Test")
197
+ assert g.user.name == "Test"
198
+ ```
199
+
200
+ ## Pushing Contexts Manually
201
+
202
+ ```python
203
+ # For background tasks, CLI commands, etc.
204
+ def background_task(app, user_id):
205
+ with app.app_context():
206
+ user = User.query.get(user_id)
207
+ send_email(user)
208
+
209
+ # Thread-safe context pushing
210
+ from threading import Thread
211
+
212
+ def run_in_thread(app):
213
+ def wrapper():
214
+ with app.app_context():
215
+ do_work()
216
+
217
+ thread = Thread(target=wrapper)
218
+ thread.start()
219
+ return thread
220
+ ```
221
+
222
+ ## Context Processor
223
+
224
+ ```python
225
+ @app.context_processor
226
+ def inject_globals():
227
+ """Inject variables into all templates."""
228
+ return {
229
+ "current_year": datetime.now().year,
230
+ "app_name": current_app.config["APP_NAME"],
231
+ "user": g.get("user"),
232
+ }
233
+
234
+ # In templates
235
+ # {{ current_year }}
236
+ # {{ app_name }}
237
+ # {% if user %}Hello {{ user.name }}{% endif %}
238
+ ```
@@ -0,0 +1,278 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Flask Error Handling
7
+
8
+ ## HTTP Error Handlers
9
+
10
+ ```python
11
+ from flask import jsonify, render_template
12
+ from werkzeug.exceptions import HTTPException
13
+
14
+ # JSON API error handler
15
+ @app.errorhandler(HTTPException)
16
+ def handle_http_exception(error):
17
+ return jsonify({
18
+ "error": error.name,
19
+ "message": error.description,
20
+ "status_code": error.code,
21
+ }), error.code
22
+
23
+ # Specific error handlers
24
+ @app.errorhandler(404)
25
+ def not_found(error):
26
+ if request.accept_mimetypes.accept_json:
27
+ return jsonify({"error": "Not found"}), 404
28
+ return render_template("errors/404.html"), 404
29
+
30
+ @app.errorhandler(500)
31
+ def internal_error(error):
32
+ db.session.rollback() # Rollback failed transaction
33
+ return jsonify({"error": "Internal server error"}), 500
34
+
35
+ @app.errorhandler(429)
36
+ def rate_limit_exceeded(error):
37
+ return jsonify({
38
+ "error": "Rate limit exceeded",
39
+ "retry_after": error.description,
40
+ }), 429
41
+ ```
42
+
43
+ ## Custom Exception Classes
44
+
45
+ ```python
46
+ class AppException(Exception):
47
+ """Base exception for application errors."""
48
+ status_code = 500
49
+ error_code = "INTERNAL_ERROR"
50
+ message = "An unexpected error occurred"
51
+
52
+ def __init__(self, message: str = None, payload: dict = None):
53
+ super().__init__()
54
+ self.message = message or self.message
55
+ self.payload = payload
56
+
57
+ def to_dict(self) -> dict:
58
+ rv = {
59
+ "error": self.error_code,
60
+ "message": self.message,
61
+ }
62
+ if self.payload:
63
+ rv["details"] = self.payload
64
+ return rv
65
+
66
+ class NotFoundError(AppException):
67
+ status_code = 404
68
+ error_code = "NOT_FOUND"
69
+ message = "Resource not found"
70
+
71
+ class ValidationError(AppException):
72
+ status_code = 400
73
+ error_code = "VALIDATION_ERROR"
74
+ message = "Validation failed"
75
+
76
+ class UnauthorizedError(AppException):
77
+ status_code = 401
78
+ error_code = "UNAUTHORIZED"
79
+ message = "Authentication required"
80
+
81
+ class ForbiddenError(AppException):
82
+ status_code = 403
83
+ error_code = "FORBIDDEN"
84
+ message = "Access denied"
85
+
86
+ class ConflictError(AppException):
87
+ status_code = 409
88
+ error_code = "CONFLICT"
89
+ message = "Resource already exists"
90
+
91
+ # Register handler
92
+ @app.errorhandler(AppException)
93
+ def handle_app_exception(error):
94
+ response = jsonify(error.to_dict())
95
+ response.status_code = error.status_code
96
+ return response
97
+
98
+ # Usage
99
+ @users_bp.route("/<int:user_id>")
100
+ def get_user(user_id: int):
101
+ user = User.query.get(user_id)
102
+ if not user:
103
+ raise NotFoundError(f"User {user_id} not found")
104
+ return jsonify(UserSchema().dump(user))
105
+ ```
106
+
107
+ ## Marshmallow Validation Errors
108
+
109
+ ```python
110
+ from marshmallow import ValidationError as MarshmallowValidationError
111
+
112
+ @app.errorhandler(MarshmallowValidationError)
113
+ def handle_validation_error(error):
114
+ return jsonify({
115
+ "error": "VALIDATION_ERROR",
116
+ "message": "Input validation failed",
117
+ "details": error.messages,
118
+ }), 400
119
+
120
+ # Usage
121
+ @users_bp.route("/", methods=["POST"])
122
+ def create_user():
123
+ schema = UserCreateSchema()
124
+ data = schema.load(request.get_json()) # Raises ValidationError if invalid
125
+ user = UserService.create(data)
126
+ return jsonify(UserSchema().dump(user)), 201
127
+ ```
128
+
129
+ ## SQLAlchemy Errors
130
+
131
+ ```python
132
+ from sqlalchemy.exc import IntegrityError, SQLAlchemyError
133
+
134
+ @app.errorhandler(IntegrityError)
135
+ def handle_integrity_error(error):
136
+ db.session.rollback()
137
+
138
+ # Parse constraint violation
139
+ if "unique constraint" in str(error.orig).lower():
140
+ return jsonify({
141
+ "error": "DUPLICATE_ENTRY",
142
+ "message": "A record with this value already exists",
143
+ }), 409
144
+
145
+ return jsonify({
146
+ "error": "DATABASE_ERROR",
147
+ "message": "Database constraint violation",
148
+ }), 400
149
+
150
+ @app.errorhandler(SQLAlchemyError)
151
+ def handle_db_error(error):
152
+ db.session.rollback()
153
+ app.logger.error(f"Database error: {error}")
154
+ return jsonify({
155
+ "error": "DATABASE_ERROR",
156
+ "message": "A database error occurred",
157
+ }), 500
158
+ ```
159
+
160
+ ## Logging Errors
161
+
162
+ ```python
163
+ import logging
164
+ import traceback
165
+
166
+ @app.errorhandler(Exception)
167
+ def handle_unexpected_error(error):
168
+ # Log full traceback
169
+ app.logger.error(
170
+ "Unhandled exception",
171
+ extra={
172
+ "error": str(error),
173
+ "traceback": traceback.format_exc(),
174
+ "path": request.path,
175
+ "method": request.method,
176
+ "user_id": g.get("user_id"),
177
+ },
178
+ )
179
+
180
+ # Return generic error to client
181
+ if app.debug:
182
+ return jsonify({
183
+ "error": "INTERNAL_ERROR",
184
+ "message": str(error),
185
+ "traceback": traceback.format_exc(),
186
+ }), 500
187
+
188
+ return jsonify({
189
+ "error": "INTERNAL_ERROR",
190
+ "message": "An unexpected error occurred",
191
+ }), 500
192
+ ```
193
+
194
+ ## Blueprint Error Handlers
195
+
196
+ ```python
197
+ users_bp = Blueprint("users", __name__)
198
+
199
+ # Only handles errors from this blueprint
200
+ @users_bp.errorhandler(404)
201
+ def user_not_found(error):
202
+ return jsonify({
203
+ "error": "USER_NOT_FOUND",
204
+ "message": "The requested user was not found",
205
+ }), 404
206
+
207
+ # App-level handler is fallback
208
+ @app.errorhandler(404)
209
+ def generic_not_found(error):
210
+ return jsonify({
211
+ "error": "NOT_FOUND",
212
+ "message": "Resource not found",
213
+ }), 404
214
+ ```
215
+
216
+ ## Error Response Format
217
+
218
+ ```python
219
+ from dataclasses import dataclass
220
+ from typing import Any
221
+
222
+ @dataclass
223
+ class ErrorResponse:
224
+ error: str
225
+ message: str
226
+ status_code: int
227
+ details: dict[str, Any] | None = None
228
+ request_id: str | None = None
229
+
230
+ def to_dict(self) -> dict:
231
+ data = {
232
+ "error": self.error,
233
+ "message": self.message,
234
+ }
235
+ if self.details:
236
+ data["details"] = self.details
237
+ if self.request_id:
238
+ data["request_id"] = self.request_id
239
+ return data
240
+
241
+ def error_response(
242
+ error: str,
243
+ message: str,
244
+ status_code: int,
245
+ details: dict = None,
246
+ ) -> tuple:
247
+ response = ErrorResponse(
248
+ error=error,
249
+ message=message,
250
+ status_code=status_code,
251
+ details=details,
252
+ request_id=g.get("request_id"),
253
+ )
254
+ return jsonify(response.to_dict()), status_code
255
+ ```
256
+
257
+ ## Abort with Custom Response
258
+
259
+ ```python
260
+ from flask import abort
261
+
262
+ @users_bp.route("/<int:user_id>")
263
+ def get_user(user_id: int):
264
+ user = User.query.get(user_id)
265
+ if not user:
266
+ abort(404, description="User not found")
267
+ return jsonify(UserSchema().dump(user))
268
+
269
+ # Or with custom response
270
+ from werkzeug.exceptions import NotFound
271
+
272
+ @users_bp.route("/<int:user_id>")
273
+ def get_user(user_id: int):
274
+ user = User.query.get(user_id)
275
+ if not user:
276
+ raise NotFound(f"User with ID {user_id} not found")
277
+ return jsonify(UserSchema().dump(user))
278
+ ```