@sylix/coworker 2.0.11 → 2.0.14

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 (169) hide show
  1. package/dist/commands/slash/config.d.ts.map +1 -1
  2. package/dist/commands/slash/config.js +22 -4
  3. package/dist/commands/slash/config.js.map +1 -1
  4. package/dist/core/CoWorkerAgent.d.ts.map +1 -1
  5. package/dist/core/CoWorkerAgent.js +6 -3
  6. package/dist/core/CoWorkerAgent.js.map +1 -1
  7. package/dist/skills/defaults/accessibility/screen-reader-testing.md +545 -0
  8. package/dist/skills/defaults/accessibility/wcag-audit-patterns.md +555 -0
  9. package/dist/skills/defaults/ai-ml/rag.md +276 -0
  10. package/dist/skills/defaults/backend-development/api-design-principles.md +528 -0
  11. package/dist/skills/defaults/backend-development/api-design.md +285 -0
  12. package/dist/skills/defaults/backend-development/architecture-patterns.md +494 -0
  13. package/dist/skills/defaults/backend-development/async-python.md +237 -0
  14. package/dist/skills/defaults/backend-development/auth-implementation-patterns.md +638 -0
  15. package/dist/skills/defaults/backend-development/bazel-build-optimization.md +387 -0
  16. package/dist/skills/defaults/backend-development/billing-automation/SKILL.md +566 -0
  17. package/dist/skills/defaults/backend-development/code-review-excellence.md +538 -0
  18. package/dist/skills/defaults/backend-development/cqrs-implementation.md +554 -0
  19. package/dist/skills/defaults/backend-development/database-design.md +305 -0
  20. package/dist/skills/defaults/backend-development/debugging-strategies.md +536 -0
  21. package/dist/skills/defaults/backend-development/e2e-testing-patterns.md +544 -0
  22. package/dist/skills/defaults/backend-development/error-handling-patterns.md +641 -0
  23. package/dist/skills/defaults/backend-development/fastapi-templates.md +559 -0
  24. package/dist/skills/defaults/backend-development/fastapi.md +309 -0
  25. package/dist/skills/defaults/backend-development/git-advanced-workflows.md +405 -0
  26. package/dist/skills/defaults/backend-development/microservices-patterns.md +595 -0
  27. package/dist/skills/defaults/backend-development/microservices.md +284 -0
  28. package/dist/skills/defaults/backend-development/monorepo-management.md +623 -0
  29. package/dist/skills/defaults/backend-development/nodejs-backend-patterns.md +1048 -0
  30. package/dist/skills/defaults/backend-development/nx-workspace-patterns.md +457 -0
  31. package/dist/skills/defaults/backend-development/paypal-integration/SKILL.md +478 -0
  32. package/dist/skills/defaults/backend-development/pci-compliance/SKILL.md +480 -0
  33. package/dist/skills/defaults/backend-development/python-anti-patterns.md +349 -0
  34. package/dist/skills/defaults/backend-development/python-background-jobs.md +364 -0
  35. package/dist/skills/defaults/backend-development/python-code-style.md +360 -0
  36. package/dist/skills/defaults/backend-development/python-configuration.md +368 -0
  37. package/dist/skills/defaults/backend-development/python-design-patterns.md +296 -0
  38. package/dist/skills/defaults/backend-development/python-error-handling.md +323 -0
  39. package/dist/skills/defaults/backend-development/python-packaging.md +887 -0
  40. package/dist/skills/defaults/backend-development/python-performance-optimization.md +874 -0
  41. package/dist/skills/defaults/backend-development/python-project-structure.md +252 -0
  42. package/dist/skills/defaults/backend-development/python-resilience.md +376 -0
  43. package/dist/skills/defaults/backend-development/python-resource-management.md +421 -0
  44. package/dist/skills/defaults/backend-development/python-type-safety.md +428 -0
  45. package/dist/skills/defaults/backend-development/sql-optimization-patterns.md +509 -0
  46. package/dist/skills/defaults/backend-development/stripe-integration/SKILL.md +522 -0
  47. package/dist/skills/defaults/backend-development/turborepo-caching.md +376 -0
  48. package/dist/skills/defaults/blockchain/defi-protocol-templates.md +430 -0
  49. package/dist/skills/defaults/blockchain/nft-standards.md +364 -0
  50. package/dist/skills/defaults/blockchain/solidity-security.md +514 -0
  51. package/dist/skills/defaults/blockchain/web3-testing.md +360 -0
  52. package/dist/skills/defaults/business/competitive-landscape/SKILL.md +527 -0
  53. package/dist/skills/defaults/business/market-sizing-analysis/SKILL.md +451 -0
  54. package/dist/skills/defaults/business/startup-financial-modeling/SKILL.md +494 -0
  55. package/dist/skills/defaults/business/startup-metrics-framework/SKILL.md +564 -0
  56. package/dist/skills/defaults/business/team-composition-analysis.md +437 -0
  57. package/dist/skills/defaults/compliance/employment-contract-templates/SKILL.md +527 -0
  58. package/dist/skills/defaults/compliance/gdpr-data-handling/SKILL.md +630 -0
  59. package/dist/skills/defaults/data-engineering/airflow-dag-patterns.md +436 -0
  60. package/dist/skills/defaults/data-engineering/airflow.md +519 -0
  61. package/dist/skills/defaults/data-engineering/data-quality.md +583 -0
  62. package/dist/skills/defaults/data-engineering/dbt-transformation-patterns.md +482 -0
  63. package/dist/skills/defaults/data-engineering/dbt.md +556 -0
  64. package/dist/skills/defaults/data-engineering/ml-pipeline-workflow/SKILL.md +247 -0
  65. package/dist/skills/defaults/data-engineering/spark-optimization.md +348 -0
  66. package/dist/skills/defaults/data-engineering/spark.md +411 -0
  67. package/dist/skills/defaults/database/postgresql.md +202 -0
  68. package/dist/skills/defaults/debugging/systematic-debugging.md +249 -0
  69. package/dist/skills/defaults/devops/architecture-decision-records.md +448 -0
  70. package/dist/skills/defaults/devops/changelog-automation.md +580 -0
  71. package/dist/skills/defaults/devops/cicd.md +314 -0
  72. package/dist/skills/defaults/devops/cloud.md +263 -0
  73. package/dist/skills/defaults/devops/code-review-excellence.md +299 -0
  74. package/dist/skills/defaults/devops/cost-optimization.md +295 -0
  75. package/dist/skills/defaults/devops/deployment-pipeline-design.md +356 -0
  76. package/dist/skills/defaults/devops/docker.md +281 -0
  77. package/dist/skills/defaults/devops/git-workflows.md +205 -0
  78. package/dist/skills/defaults/devops/github-actions.md +311 -0
  79. package/dist/skills/defaults/devops/gitlab-ci-patterns.md +266 -0
  80. package/dist/skills/defaults/devops/hybrid-cloud-networking.md +241 -0
  81. package/dist/skills/defaults/devops/istio-traffic-management.md +327 -0
  82. package/dist/skills/defaults/devops/kubernetes.md +339 -0
  83. package/dist/skills/defaults/devops/linkerd-patterns.md +311 -0
  84. package/dist/skills/defaults/devops/multi-cloud-architecture.md +181 -0
  85. package/dist/skills/defaults/devops/observability.md +243 -0
  86. package/dist/skills/defaults/devops/openapi-spec-generation.md +1024 -0
  87. package/dist/skills/defaults/devops/postmortem-writing.md +396 -0
  88. package/dist/skills/defaults/devops/prometheus-configuration.md +265 -0
  89. package/dist/skills/defaults/devops/secrets-management.md +341 -0
  90. package/dist/skills/defaults/devops/service-mesh-observability.md +385 -0
  91. package/dist/skills/defaults/devops/terraform-module-library.md +244 -0
  92. package/dist/skills/defaults/finance/backtesting-frameworks/SKILL.md +663 -0
  93. package/dist/skills/defaults/finance/risk-metrics-calculation/SKILL.md +557 -0
  94. package/dist/skills/defaults/frontend/accessibility-compliance.md +420 -0
  95. package/dist/skills/defaults/frontend/design-system-patterns.md +337 -0
  96. package/dist/skills/defaults/frontend/interaction-design.md +327 -0
  97. package/dist/skills/defaults/frontend/javascript.md +311 -0
  98. package/dist/skills/defaults/frontend/modern-javascript-patterns.md +927 -0
  99. package/dist/skills/defaults/frontend/react-native-design.md +440 -0
  100. package/dist/skills/defaults/frontend/react.md +345 -0
  101. package/dist/skills/defaults/frontend/responsive-design.md +472 -0
  102. package/dist/skills/defaults/frontend/tailwind-design-system.md +337 -0
  103. package/dist/skills/defaults/frontend/typescript-advanced-types.md +724 -0
  104. package/dist/skills/defaults/frontend/typescript.md +334 -0
  105. package/dist/skills/defaults/frontend/visual-design-foundations.md +326 -0
  106. package/dist/skills/defaults/frontend/web-component-design.md +279 -0
  107. package/dist/skills/defaults/game-development/godot-gdscript-patterns.md +188 -0
  108. package/dist/skills/defaults/game-development/unity-ecs-patterns.md +594 -0
  109. package/dist/skills/defaults/kubernetes/gitops-workflow.md +285 -0
  110. package/dist/skills/defaults/kubernetes/gitops.md +280 -0
  111. package/dist/skills/defaults/kubernetes/helm-chart-scaffolding.md +553 -0
  112. package/dist/skills/defaults/kubernetes/helm.md +343 -0
  113. package/dist/skills/defaults/kubernetes/k8s-manifest-generator.md +501 -0
  114. package/dist/skills/defaults/kubernetes/k8s-security-policies.md +342 -0
  115. package/dist/skills/defaults/kubernetes/manifests.md +330 -0
  116. package/dist/skills/defaults/kubernetes/security.md +337 -0
  117. package/dist/skills/defaults/llm-application/embedding-strategies.md +608 -0
  118. package/dist/skills/defaults/llm-application/hybrid-search-implementation.md +570 -0
  119. package/dist/skills/defaults/llm-application/hybrid-search.md +570 -0
  120. package/dist/skills/defaults/llm-application/langchain-architecture.md +666 -0
  121. package/dist/skills/defaults/llm-application/langchain.md +259 -0
  122. package/dist/skills/defaults/llm-application/llm-evaluation.md +695 -0
  123. package/dist/skills/defaults/llm-application/prompt-engineering-patterns.md +449 -0
  124. package/dist/skills/defaults/llm-application/prompt-engineering.md +219 -0
  125. package/dist/skills/defaults/llm-application/rag-implementation.md +434 -0
  126. package/dist/skills/defaults/llm-application/similarity-search-patterns.md +560 -0
  127. package/dist/skills/defaults/llm-application/similarity-search.md +560 -0
  128. package/dist/skills/defaults/llm-application/vector-index-tuning.md +523 -0
  129. package/dist/skills/defaults/mobile/mobile-android-design.md +440 -0
  130. package/dist/skills/defaults/mobile/mobile-ios-design.md +266 -0
  131. package/dist/skills/defaults/monitoring/distributed-tracing.md +436 -0
  132. package/dist/skills/defaults/monitoring/grafana-dashboards.md +370 -0
  133. package/dist/skills/defaults/monitoring/prometheus-configuration.md +379 -0
  134. package/dist/skills/defaults/monitoring/slo-implementation.md +323 -0
  135. package/dist/skills/defaults/refactoring/code-refactoring.md +349 -0
  136. package/dist/skills/defaults/security/anti-reversing-techniques/SKILL.md +559 -0
  137. package/dist/skills/defaults/security/auditor.md +168 -0
  138. package/dist/skills/defaults/security/binary-analysis-patterns/SKILL.md +438 -0
  139. package/dist/skills/defaults/security/memory-forensics/SKILL.md +483 -0
  140. package/dist/skills/defaults/security/mtls-configuration.md +349 -0
  141. package/dist/skills/defaults/security/protocol-reverse-engineering/SKILL.md +520 -0
  142. package/dist/skills/defaults/security/sast-configuration.md +182 -0
  143. package/dist/skills/defaults/security/security.md +313 -0
  144. package/dist/skills/defaults/security/stride-analysis.md +273 -0
  145. package/dist/skills/defaults/security/threat-mitigation-mapping.md +290 -0
  146. package/dist/skills/defaults/systems/bash-defensive-patterns/SKILL.md +539 -0
  147. package/dist/skills/defaults/systems/bats-testing-patterns/SKILL.md +631 -0
  148. package/dist/skills/defaults/systems/go-concurrency-patterns.md +657 -0
  149. package/dist/skills/defaults/systems/memory-safety-patterns.md +605 -0
  150. package/dist/skills/defaults/systems/rust-async-patterns.md +519 -0
  151. package/dist/skills/defaults/systems/shellcheck-configuration/SKILL.md +456 -0
  152. package/dist/skills/defaults/team-collaboration/multi-reviewer-patterns.md +126 -0
  153. package/dist/skills/defaults/team-collaboration/parallel-feature-development.md +151 -0
  154. package/dist/skills/defaults/testing/javascript-testing-patterns.md +1021 -0
  155. package/dist/skills/defaults/testing/python-testing-patterns.md +351 -0
  156. package/dist/skills/defaults/testing/testing.md +332 -0
  157. package/dist/skills/defaults/workflows/context-driven-development.md +384 -0
  158. package/dist/skills/defaults/workflows/track-management.md +592 -0
  159. package/dist/skills/defaults/workflows/workflow-patterns.md +622 -0
  160. package/dist/skills/index.d.ts +11 -0
  161. package/dist/skills/index.d.ts.map +1 -0
  162. package/dist/skills/index.js +129 -0
  163. package/dist/skills/index.js.map +1 -0
  164. package/dist/utils/character.js +4 -4
  165. package/dist/utils/character.js.map +1 -1
  166. package/dist/utils/inputbar.d.ts.map +1 -1
  167. package/dist/utils/inputbar.js +7 -0
  168. package/dist/utils/inputbar.js.map +1 -1
  169. package/package.json +1 -1
@@ -0,0 +1,421 @@
1
+ ---
2
+ name: python-resource-management
3
+ description: Python resource management with context managers, cleanup patterns, and streaming. Use when managing connections, file handles, implementing cleanup logic, or building streaming responses with accumulated state.
4
+ ---
5
+
6
+ # Python Resource Management
7
+
8
+ Manage resources deterministically using context managers. Resources like database connections, file handles, and network sockets should be released reliably, even when exceptions occur.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Managing database connections and connection pools
13
+ - Working with file handles and I/O
14
+ - Implementing custom context managers
15
+ - Building streaming responses with state
16
+ - Handling nested resource cleanup
17
+ - Creating async context managers
18
+
19
+ ## Core Concepts
20
+
21
+ ### 1. Context Managers
22
+
23
+ The `with` statement ensures resources are released automatically, even on exceptions.
24
+
25
+ ### 2. Protocol Methods
26
+
27
+ `__enter__`/`__exit__` for sync, `__aenter__`/`__aexit__` for async resource management.
28
+
29
+ ### 3. Unconditional Cleanup
30
+
31
+ `__exit__` always runs, regardless of whether an exception occurred.
32
+
33
+ ### 4. Exception Handling
34
+
35
+ Return `True` from `__exit__` to suppress exceptions, `False` to propagate them.
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from contextlib import contextmanager
41
+
42
+ @contextmanager
43
+ def managed_resource():
44
+ resource = acquire_resource()
45
+ try:
46
+ yield resource
47
+ finally:
48
+ resource.cleanup()
49
+
50
+ with managed_resource() as r:
51
+ r.do_work()
52
+ ```
53
+
54
+ ## Fundamental Patterns
55
+
56
+ ### Pattern 1: Class-Based Context Manager
57
+
58
+ Implement the context manager protocol for complex resources.
59
+
60
+ ```python
61
+ class DatabaseConnection:
62
+ """Database connection with automatic cleanup."""
63
+
64
+ def __init__(self, dsn: str) -> None:
65
+ self._dsn = dsn
66
+ self._conn: Connection | None = None
67
+
68
+ def connect(self) -> None:
69
+ """Establish database connection."""
70
+ self._conn = psycopg.connect(self._dsn)
71
+
72
+ def close(self) -> None:
73
+ """Close connection if open."""
74
+ if self._conn is not None:
75
+ self._conn.close()
76
+ self._conn = None
77
+
78
+ def __enter__(self) -> "DatabaseConnection":
79
+ """Enter context: connect and return self."""
80
+ self.connect()
81
+ return self
82
+
83
+ def __exit__(
84
+ self,
85
+ exc_type: type[BaseException] | None,
86
+ exc_val: BaseException | None,
87
+ exc_tb: TracebackType | None,
88
+ ) -> None:
89
+ """Exit context: always close connection."""
90
+ self.close()
91
+
92
+ # Usage with context manager (preferred)
93
+ with DatabaseConnection(dsn) as db:
94
+ result = db.execute(query)
95
+
96
+ # Manual management when needed
97
+ db = DatabaseConnection(dsn)
98
+ db.connect()
99
+ try:
100
+ result = db.execute(query)
101
+ finally:
102
+ db.close()
103
+ ```
104
+
105
+ ### Pattern 2: Async Context Manager
106
+
107
+ For async resources, implement the async protocol.
108
+
109
+ ```python
110
+ class AsyncDatabasePool:
111
+ """Async database connection pool."""
112
+
113
+ def __init__(self, dsn: str, min_size: int = 1, max_size: int = 10) -> None:
114
+ self._dsn = dsn
115
+ self._min_size = min_size
116
+ self._max_size = max_size
117
+ self._pool: asyncpg.Pool | None = None
118
+
119
+ async def __aenter__(self) -> "AsyncDatabasePool":
120
+ """Create connection pool."""
121
+ self._pool = await asyncpg.create_pool(
122
+ self._dsn,
123
+ min_size=self._min_size,
124
+ max_size=self._max_size,
125
+ )
126
+ return self
127
+
128
+ async def __aexit__(
129
+ self,
130
+ exc_type: type[BaseException] | None,
131
+ exc_val: BaseException | None,
132
+ exc_tb: TracebackType | None,
133
+ ) -> None:
134
+ """Close all connections in pool."""
135
+ if self._pool is not None:
136
+ await self._pool.close()
137
+
138
+ async def execute(self, query: str, *args) -> list[dict]:
139
+ """Execute query using pooled connection."""
140
+ async with self._pool.acquire() as conn:
141
+ return await conn.fetch(query, *args)
142
+
143
+ # Usage
144
+ async with AsyncDatabasePool(dsn) as pool:
145
+ users = await pool.execute("SELECT * FROM users WHERE active = $1", True)
146
+ ```
147
+
148
+ ### Pattern 3: Using @contextmanager Decorator
149
+
150
+ Simplify context managers with the decorator for straightforward cases.
151
+
152
+ ```python
153
+ from contextlib import contextmanager, asynccontextmanager
154
+ import time
155
+ import structlog
156
+
157
+ logger = structlog.get_logger()
158
+
159
+ @contextmanager
160
+ def timed_block(name: str):
161
+ """Time a block of code."""
162
+ start = time.perf_counter()
163
+ try:
164
+ yield
165
+ finally:
166
+ elapsed = time.perf_counter() - start
167
+ logger.info(f"{name} completed", duration_seconds=round(elapsed, 3))
168
+
169
+ # Usage
170
+ with timed_block("data_processing"):
171
+ process_large_dataset()
172
+
173
+ @asynccontextmanager
174
+ async def database_transaction(conn: AsyncConnection):
175
+ """Manage database transaction."""
176
+ await conn.execute("BEGIN")
177
+ try:
178
+ yield conn
179
+ await conn.execute("COMMIT")
180
+ except Exception:
181
+ await conn.execute("ROLLBACK")
182
+ raise
183
+
184
+ # Usage
185
+ async with database_transaction(conn) as tx:
186
+ await tx.execute("INSERT INTO users ...")
187
+ await tx.execute("INSERT INTO audit_log ...")
188
+ ```
189
+
190
+ ### Pattern 4: Unconditional Resource Release
191
+
192
+ Always clean up resources in `__exit__`, regardless of exceptions.
193
+
194
+ ```python
195
+ class FileProcessor:
196
+ """Process file with guaranteed cleanup."""
197
+
198
+ def __init__(self, path: str) -> None:
199
+ self._path = path
200
+ self._file: IO | None = None
201
+ self._temp_files: list[Path] = []
202
+
203
+ def __enter__(self) -> "FileProcessor":
204
+ self._file = open(self._path, "r")
205
+ return self
206
+
207
+ def __exit__(
208
+ self,
209
+ exc_type: type[BaseException] | None,
210
+ exc_val: BaseException | None,
211
+ exc_tb: TracebackType | None,
212
+ ) -> None:
213
+ """Clean up all resources unconditionally."""
214
+ # Close main file
215
+ if self._file is not None:
216
+ self._file.close()
217
+
218
+ # Clean up any temporary files
219
+ for temp_file in self._temp_files:
220
+ try:
221
+ temp_file.unlink()
222
+ except OSError:
223
+ pass # Best effort cleanup
224
+
225
+ # Return None/False to propagate any exception
226
+ ```
227
+
228
+ ## Advanced Patterns
229
+
230
+ ### Pattern 5: Selective Exception Suppression
231
+
232
+ Only suppress specific, documented exceptions.
233
+
234
+ ```python
235
+ class StreamWriter:
236
+ """Writer that handles broken pipe gracefully."""
237
+
238
+ def __init__(self, stream) -> None:
239
+ self._stream = stream
240
+
241
+ def __enter__(self) -> "StreamWriter":
242
+ return self
243
+
244
+ def __exit__(
245
+ self,
246
+ exc_type: type[BaseException] | None,
247
+ exc_val: BaseException | None,
248
+ exc_tb: TracebackType | None,
249
+ ) -> bool:
250
+ """Clean up, suppressing BrokenPipeError on shutdown."""
251
+ self._stream.close()
252
+
253
+ # Suppress BrokenPipeError (client disconnected)
254
+ # This is expected behavior, not an error
255
+ if exc_type is BrokenPipeError:
256
+ return True # Exception suppressed
257
+
258
+ return False # Propagate all other exceptions
259
+ ```
260
+
261
+ ### Pattern 6: Streaming with Accumulated State
262
+
263
+ Maintain both incremental chunks and accumulated state during streaming.
264
+
265
+ ```python
266
+ from collections.abc import Generator
267
+ from dataclasses import dataclass, field
268
+
269
+ @dataclass
270
+ class StreamingResult:
271
+ """Accumulated streaming result."""
272
+
273
+ chunks: list[str] = field(default_factory=list)
274
+ _finalized: bool = False
275
+
276
+ @property
277
+ def content(self) -> str:
278
+ """Get accumulated content."""
279
+ return "".join(self.chunks)
280
+
281
+ def add_chunk(self, chunk: str) -> None:
282
+ """Add chunk to accumulator."""
283
+ if self._finalized:
284
+ raise RuntimeError("Cannot add to finalized result")
285
+ self.chunks.append(chunk)
286
+
287
+ def finalize(self) -> str:
288
+ """Mark stream complete and return content."""
289
+ self._finalized = True
290
+ return self.content
291
+
292
+ def stream_with_accumulation(
293
+ response: StreamingResponse,
294
+ ) -> Generator[tuple[str, str], None, str]:
295
+ """Stream response while accumulating content.
296
+
297
+ Yields:
298
+ Tuple of (accumulated_content, new_chunk) for each chunk.
299
+
300
+ Returns:
301
+ Final accumulated content.
302
+ """
303
+ result = StreamingResult()
304
+
305
+ for chunk in response.iter_content():
306
+ result.add_chunk(chunk)
307
+ yield result.content, chunk
308
+
309
+ return result.finalize()
310
+ ```
311
+
312
+ ### Pattern 7: Efficient String Accumulation
313
+
314
+ Avoid O(n²) string concatenation when accumulating.
315
+
316
+ ```python
317
+ def accumulate_stream(stream) -> str:
318
+ """Efficiently accumulate stream content."""
319
+ # BAD: O(n²) due to string immutability
320
+ # content = ""
321
+ # for chunk in stream:
322
+ # content += chunk # Creates new string each time
323
+
324
+ # GOOD: O(n) with list and join
325
+ chunks: list[str] = []
326
+ for chunk in stream:
327
+ chunks.append(chunk)
328
+ return "".join(chunks) # Single allocation
329
+ ```
330
+
331
+ ### Pattern 8: Tracking Stream Metrics
332
+
333
+ Measure time-to-first-byte and total streaming time.
334
+
335
+ ```python
336
+ import time
337
+ from collections.abc import Generator
338
+
339
+ def stream_with_metrics(
340
+ response: StreamingResponse,
341
+ ) -> Generator[str, None, dict]:
342
+ """Stream response while collecting metrics.
343
+
344
+ Yields:
345
+ Content chunks.
346
+
347
+ Returns:
348
+ Metrics dictionary.
349
+ """
350
+ start = time.perf_counter()
351
+ first_chunk_time: float | None = None
352
+ chunk_count = 0
353
+ total_bytes = 0
354
+
355
+ for chunk in response.iter_content():
356
+ if first_chunk_time is None:
357
+ first_chunk_time = time.perf_counter() - start
358
+
359
+ chunk_count += 1
360
+ total_bytes += len(chunk.encode())
361
+ yield chunk
362
+
363
+ total_time = time.perf_counter() - start
364
+
365
+ return {
366
+ "time_to_first_byte_ms": round((first_chunk_time or 0) * 1000, 2),
367
+ "total_time_ms": round(total_time * 1000, 2),
368
+ "chunk_count": chunk_count,
369
+ "total_bytes": total_bytes,
370
+ }
371
+ ```
372
+
373
+ ### Pattern 9: Managing Multiple Resources with ExitStack
374
+
375
+ Handle a dynamic number of resources cleanly.
376
+
377
+ ```python
378
+ from contextlib import ExitStack, AsyncExitStack
379
+ from pathlib import Path
380
+
381
+ def process_files(paths: list[Path]) -> list[str]:
382
+ """Process multiple files with automatic cleanup."""
383
+ results = []
384
+
385
+ with ExitStack() as stack:
386
+ # Open all files - they'll all be closed when block exits
387
+ files = [stack.enter_context(open(p)) for p in paths]
388
+
389
+ for f in files:
390
+ results.append(f.read())
391
+
392
+ return results
393
+
394
+ async def process_connections(hosts: list[str]) -> list[dict]:
395
+ """Process multiple async connections."""
396
+ results = []
397
+
398
+ async with AsyncExitStack() as stack:
399
+ connections = [
400
+ await stack.enter_async_context(connect_to_host(host))
401
+ for host in hosts
402
+ ]
403
+
404
+ for conn in connections:
405
+ results.append(await conn.fetch_data())
406
+
407
+ return results
408
+ ```
409
+
410
+ ## Best Practices Summary
411
+
412
+ 1. **Always use context managers** - For any resource that needs cleanup
413
+ 2. **Clean up unconditionally** - `__exit__` runs even on exception
414
+ 3. **Don't suppress unexpectedly** - Return `False` unless suppression is intentional
415
+ 4. **Use @contextmanager** - For simple resource patterns
416
+ 5. **Implement both protocols** - Support `with` and manual management
417
+ 6. **Use ExitStack** - For dynamic numbers of resources
418
+ 7. **Accumulate efficiently** - List + join, not string concatenation
419
+ 8. **Track metrics** - Time-to-first-byte matters for streaming
420
+ 9. **Document behavior** - Especially exception suppression
421
+ 10. **Test cleanup paths** - Verify resources are released on errors