@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.
Files changed (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,281 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Flask Configuration Patterns
7
+
8
+ ## Class-Based Configuration
9
+
10
+ ```python
11
+ # config.py
12
+ import os
13
+ from datetime import timedelta
14
+
15
+ class Config:
16
+ """Base configuration."""
17
+ SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key")
18
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
19
+
20
+ # JWT
21
+ JWT_SECRET_KEY = os.environ.get("JWT_SECRET_KEY", SECRET_KEY)
22
+ JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
23
+ JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30)
24
+
25
+ # Mail
26
+ MAIL_SERVER = os.environ.get("MAIL_SERVER", "localhost")
27
+ MAIL_PORT = int(os.environ.get("MAIL_PORT", 587))
28
+ MAIL_USE_TLS = True
29
+
30
+ class DevelopmentConfig(Config):
31
+ """Development configuration."""
32
+ DEBUG = True
33
+ SQLALCHEMY_DATABASE_URI = os.environ.get(
34
+ "DATABASE_URL",
35
+ "postgresql://localhost/app_dev"
36
+ )
37
+ SQLALCHEMY_ECHO = True # Log SQL queries
38
+
39
+ class TestingConfig(Config):
40
+ """Testing configuration."""
41
+ TESTING = True
42
+ SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
43
+ WTF_CSRF_ENABLED = False
44
+
45
+ class ProductionConfig(Config):
46
+ """Production configuration."""
47
+ DEBUG = False
48
+ SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
49
+
50
+ # Security
51
+ SESSION_COOKIE_SECURE = True
52
+ SESSION_COOKIE_HTTPONLY = True
53
+ SESSION_COOKIE_SAMESITE = "Lax"
54
+
55
+ config = {
56
+ "development": DevelopmentConfig,
57
+ "testing": TestingConfig,
58
+ "production": ProductionConfig,
59
+ "default": DevelopmentConfig,
60
+ }
61
+ ```
62
+
63
+ ## Loading Configuration
64
+
65
+ ```python
66
+ # app/__init__.py
67
+ from flask import Flask
68
+ from config import config
69
+
70
+ def create_app(config_name: str = None) -> Flask:
71
+ if config_name is None:
72
+ config_name = os.environ.get("FLASK_CONFIG", "development")
73
+
74
+ app = Flask(__name__)
75
+ app.config.from_object(config[config_name])
76
+
77
+ # Load additional config from file
78
+ app.config.from_pyfile("config.py", silent=True)
79
+
80
+ # Load from environment variable pointing to file
81
+ app.config.from_envvar("APP_CONFIG_FILE", silent=True)
82
+
83
+ return app
84
+ ```
85
+
86
+ ## Environment Variables
87
+
88
+ ```python
89
+ # .env (development)
90
+ FLASK_APP=app
91
+ FLASK_CONFIG=development
92
+ SECRET_KEY=your-secret-key
93
+ DATABASE_URL=postgresql://user:pass@localhost/app_dev
94
+ REDIS_URL=redis://localhost:6379/0
95
+
96
+ # Load with python-dotenv
97
+ from dotenv import load_dotenv
98
+ load_dotenv()
99
+
100
+ # Or in create_app
101
+ def create_app(config_name: str = None) -> Flask:
102
+ load_dotenv()
103
+ ...
104
+ ```
105
+
106
+ ## Pydantic Settings (Recommended)
107
+
108
+ ```python
109
+ from pydantic_settings import BaseSettings
110
+ from functools import lru_cache
111
+
112
+ class Settings(BaseSettings):
113
+ # App
114
+ app_name: str = "My App"
115
+ debug: bool = False
116
+ secret_key: str
117
+
118
+ # Database
119
+ database_url: str
120
+
121
+ # Redis
122
+ redis_url: str = "redis://localhost:6379/0"
123
+
124
+ # JWT
125
+ jwt_secret_key: str | None = None
126
+ jwt_access_token_expires: int = 3600 # seconds
127
+
128
+ # Mail
129
+ mail_server: str = "localhost"
130
+ mail_port: int = 587
131
+ mail_username: str | None = None
132
+ mail_password: str | None = None
133
+
134
+ class Config:
135
+ env_file = ".env"
136
+ env_file_encoding = "utf-8"
137
+
138
+ @lru_cache
139
+ def get_settings() -> Settings:
140
+ return Settings()
141
+
142
+ settings = get_settings()
143
+
144
+ # Usage in app
145
+ def create_app() -> Flask:
146
+ app = Flask(__name__)
147
+
148
+ app.config["SECRET_KEY"] = settings.secret_key
149
+ app.config["SQLALCHEMY_DATABASE_URI"] = settings.database_url
150
+ app.config["DEBUG"] = settings.debug
151
+
152
+ return app
153
+ ```
154
+
155
+ ## Configuration Validation
156
+
157
+ ```python
158
+ def validate_config(app: Flask):
159
+ """Validate required configuration."""
160
+ required = [
161
+ "SECRET_KEY",
162
+ "SQLALCHEMY_DATABASE_URI",
163
+ ]
164
+
165
+ missing = [key for key in required if not app.config.get(key)]
166
+
167
+ if missing:
168
+ raise RuntimeError(f"Missing required config: {', '.join(missing)}")
169
+
170
+ # Validate SECRET_KEY strength in production
171
+ if not app.debug:
172
+ if len(app.config["SECRET_KEY"]) < 32:
173
+ raise RuntimeError("SECRET_KEY must be at least 32 characters in production")
174
+
175
+ def create_app(config_name: str = None) -> Flask:
176
+ app = Flask(__name__)
177
+ app.config.from_object(config[config_name])
178
+ validate_config(app)
179
+ return app
180
+ ```
181
+
182
+ ## Instance Configuration
183
+
184
+ ```python
185
+ # instance/config.py (not in version control)
186
+ SECRET_KEY = "your-production-secret"
187
+ SQLALCHEMY_DATABASE_URI = "postgresql://prod-db/app"
188
+
189
+ # Load instance config
190
+ app = Flask(__name__, instance_relative_config=True)
191
+ app.config.from_pyfile("config.py", silent=True)
192
+ ```
193
+
194
+ ## Configuration by Feature
195
+
196
+ ```python
197
+ class Config:
198
+ # Core
199
+ SECRET_KEY = os.environ["SECRET_KEY"]
200
+
201
+ # Database
202
+ SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
203
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
204
+ SQLALCHEMY_ENGINE_OPTIONS = {
205
+ "pool_size": 10,
206
+ "pool_recycle": 3600,
207
+ "pool_pre_ping": True,
208
+ }
209
+
210
+ # Cache
211
+ CACHE_TYPE = "redis"
212
+ CACHE_REDIS_URL = os.environ.get("REDIS_URL")
213
+ CACHE_DEFAULT_TIMEOUT = 300
214
+
215
+ # Session
216
+ SESSION_TYPE = "redis"
217
+ SESSION_REDIS = redis.from_url(os.environ.get("REDIS_URL"))
218
+ PERMANENT_SESSION_LIFETIME = timedelta(days=7)
219
+
220
+ # Security
221
+ SESSION_COOKIE_SECURE = True
222
+ SESSION_COOKIE_HTTPONLY = True
223
+ SESSION_COOKIE_SAMESITE = "Lax"
224
+
225
+ # CORS
226
+ CORS_ORIGINS = os.environ.get("CORS_ORIGINS", "").split(",")
227
+
228
+ # Uploads
229
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
230
+ UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER", "/tmp/uploads")
231
+
232
+ # Logging
233
+ LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
234
+ LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
235
+ ```
236
+
237
+ ## Runtime Configuration Access
238
+
239
+ ```python
240
+ from flask import current_app
241
+
242
+ @users_bp.route("/config-example")
243
+ def config_example():
244
+ # Access config in routes
245
+ debug = current_app.config["DEBUG"]
246
+ app_name = current_app.config.get("APP_NAME", "Default")
247
+
248
+ return jsonify({
249
+ "debug": debug,
250
+ "app_name": app_name,
251
+ })
252
+
253
+ # In services
254
+ class EmailService:
255
+ def __init__(self):
256
+ self.server = current_app.config["MAIL_SERVER"]
257
+ self.port = current_app.config["MAIL_PORT"]
258
+ ```
259
+
260
+ ## Configuration for Extensions
261
+
262
+ ```python
263
+ def configure_extensions(app: Flask):
264
+ """Configure Flask extensions."""
265
+ # SQLAlchemy
266
+ app.config.setdefault("SQLALCHEMY_TRACK_MODIFICATIONS", False)
267
+
268
+ # JWT
269
+ app.config.setdefault("JWT_TOKEN_LOCATION", ["headers"])
270
+ app.config.setdefault("JWT_HEADER_NAME", "Authorization")
271
+ app.config.setdefault("JWT_HEADER_TYPE", "Bearer")
272
+
273
+ # CORS
274
+ app.config.setdefault("CORS_SUPPORTS_CREDENTIALS", True)
275
+
276
+ def create_app(config_name: str = None) -> Flask:
277
+ app = Flask(__name__)
278
+ app.config.from_object(config[config_name])
279
+ configure_extensions(app)
280
+ return app
281
+ ```
@@ -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
+ ```