Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/dicklesworthstone-python-resource-management/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 182% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 109% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 111% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 142% | 0% |
Manage resources deterministically using context managers. Resources like database connections, file handles, and network sockets should be released reliably, even when exceptions occur.
The with statement ensures resources are released automatically, even on exceptions.
__enter__/__exit__ for sync, __aenter__/__aexit__ for async resource management.
__exit__ always runs, regardless of whether an exception occurred.
Return True from __exit__ to suppress exceptions, False to propagate them.
pythonfrom contextlib import contextmanager @contextmanager def managed_resource(): resource = acquire_resource() try: yield resource finally: resource.cleanup() with managed_resource() as r: r.do_work()
Implement the context manager protocol for complex resources.
pythonclass DatabaseConnection: """Database connection with automatic cleanup.""" def __init__(self, dsn: str) -> None: self._dsn = dsn self._conn: Connection | None = None def connect(self) -> None: """Establish database connection.""" self._conn = psycopg.connect(self._dsn) def close(self) -> None: """Close connection if open.""" if self._conn is not None: self._conn.close() self._conn = None def __enter__(self) -> "DatabaseConnection": """Enter context: connect and return self.""" self.connect() return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Exit context: always close connection.""" self.close() # Usage with context manager (preferred) with DatabaseConnection(dsn) as db: result = db.execute(query) # Manual management when needed db = DatabaseConnection(dsn) db.connect() try: result = db.execute(query) finally: db.close()
For async resources, implement the async protocol.
pythonclass AsyncDatabasePool: """Async database connection pool.""" def __init__(self, dsn: str, min_size: int = 1, max_size: int = 10) -> None: self._dsn = dsn self._min_size = min_size self._max_size = max_size self._pool: asyncpg.Pool | None = None async def __aenter__(self) -> "AsyncDatabasePool": """Create connection pool.""" self._pool = await asyncpg.create_pool( self._dsn, min_size=self._min_size, max_size=self._max_size, ) return self async def __aexit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Close all connections in pool.""" if self._pool is not None: await self._pool.close() async def execute(self, query: str, *args) -> list[dict]: """Execute query using pooled connection.""" async with self._pool.acquire() as conn: return await conn.fetch(query, *args) # Usage async with AsyncDatabasePool(dsn) as pool: users = await pool.execute("SELECT * FROM users WHERE active = $1", True)
Simplify context managers with the decorator for straightforward cases.
pythonfrom contextlib import contextmanager, asynccontextmanager import time import structlog logger = structlog.get_logger() @contextmanager def timed_block(name: str): """Time a block of code.""" start = time.perf_counter() try: yield finally: elapsed = time.perf_counter() - start logger.info(f"{name} completed", duration_seconds=round(elapsed, 3)) # Usage with timed_block("data_processing"): process_large_dataset() @asynccontextmanager async def database_transaction(conn: AsyncConnection): """Manage database transaction.""" await conn.execute("BEGIN") try: yield conn await conn.execute("COMMIT") except Exception: await conn.execute("ROLLBACK") raise # Usage async with database_transaction(conn) as tx: await tx.execute("INSERT INTO users ...") await tx.execute("INSERT INTO audit_log ...")
Always clean up resources in __exit__, regardless of exceptions.
pythonclass FileProcessor: """Process file with guaranteed cleanup.""" def __init__(self, path: str) -> None: self._path = path self._file: IO | None = None self._temp_files: list[Path] = [] def __enter__(self) -> "FileProcessor": self._file = open(self._path, "r") return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: """Clean up all resources unconditionally.""" # Close main file if self._file is not None: self._file.close() # Clean up any temporary files for temp_file in self._temp_files: try: temp_file.unlink() except OSError: pass # Best effort cleanup # Return None/False to propagate any exception
Only suppress specific, documented exceptions.
pythonclass StreamWriter: """Writer that handles broken pipe gracefully.""" def __init__(self, stream) -> None: self._stream = stream def __enter__(self) -> "StreamWriter": return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> bool: """Clean up, suppressing BrokenPipeError on shutdown.""" self._stream.close() # Suppress BrokenPipeError (client disconnected) # This is expected behavior, not an error if exc_type is BrokenPipeError: return True # Exception suppressed return False # Propagate all other exceptions
Maintain both incremental chunks and accumulated state during streaming.
pythonfrom collections.abc import Generator from dataclasses import dataclass, field @dataclass class StreamingResult: """Accumulated streaming result.""" chunks: list[str] = field(default_factory=list) _finalized: bool = False @property def content(self) -> str: """Get accumulated content.""" return "".join(self.chunks) def add_chunk(self, chunk: str) -> None: """Add chunk to accumulator.""" if self._finalized: raise RuntimeError("Cannot add to finalized result") self.chunks.append(chunk) def finalize(self) -> str: """Mark stream complete and return content.""" self._finalized = True return self.content def stream_with_accumulation( response: StreamingResponse, ) -> Generator[tuple[str, str], None, str]: """Stream response while accumulating content. Yields: Tuple of (accumulated_content, new_chunk) for each chunk. Returns: Final accumulated content. """ result = StreamingResult() for chunk in response.iter_content(): result.add_chunk(chunk) yield result.content, chunk return result.finalize()
Avoid O(n²) string concatenation when accumulating.
pythondef accumulate_stream(stream) -> str: """Efficiently accumulate stream content.""" # BAD: O(n²) due to string immutability # content = "" # for chunk in stream: # content += chunk # Creates new string each time # GOOD: O(n) with list and join chunks: list[str] = [] for chunk in stream: chunks.append(chunk) return "".join(chunks) # Single allocation
Measure time-to-first-byte and total streaming time.
pythonimport time from collections.abc import Generator def stream_with_metrics( response: StreamingResponse, ) -> Generator[str, None, dict]: """Stream response while collecting metrics. Yields: Content chunks. Returns: Metrics dictionary. """ start = time.perf_counter() first_chunk_time: float | None = None chunk_count = 0 total_bytes = 0 for chunk in response.iter_content(): if first_chunk_time is None: first_chunk_time = time.perf_counter() - start chunk_count += 1 total_bytes += len(chunk.encode()) yield chunk total_time = time.perf_counter() - start return { "time_to_first_byte_ms": round((first_chunk_time or 0) * 1000, 2), "total_time_ms": round(total_time * 1000, 2), "chunk_count": chunk_count, "total_bytes": total_bytes, }
Handle a dynamic number of resources cleanly.
pythonfrom contextlib import ExitStack, AsyncExitStack from pathlib import Path def process_files(paths: list[Path]) -> list[str]: """Process multiple files with automatic cleanup.""" results = [] with ExitStack() as stack: # Open all files - they'll all be closed when block exits files = [stack.enter_context(open(p)) for p in paths] for f in files: results.append(f.read()) return results async def process_connections(hosts: list[str]) -> list[dict]: """Process multiple async connections.""" results = [] async with AsyncExitStack() as stack: connections = [ await stack.enter_async_context(connect_to_host(host)) for host in hosts ] for conn in connections: results.append(await conn.fetch_data()) return results
__exit__ runs even on exceptionFalse unless suppression is intentionalwith and manual management| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 12,545 | 27,213 | +117% | 1 | 1 | 0% | 2,676 | 5,601 | +109% | 0 | 0 | — |
case-02 | pass→pass | 11,511 | 9,785 | -15% | 1 | 1 | 0% | 2,264 | 4,772 | +111% | 0 | 0 | — |
case-03 | pass→pass | 11,922 | 12,280 | +3% | 1 | 1 | 0% | 2,053 | 4,969 | +142% | 0 | 0 | — |
case-04 | pass→pass | 11,827 | 11,362 | -4% | 1 | 1 | 0% | 2,229 | 4,988 | +124% | 0 | 0 | — |
case-05 | pass→pass | 18,516 | 13,660 | -26% | 1 | 1 | 0% | 2,508 | 4,902 | +95% | 0 | 0 | — |
case-06 | fail→pass | 9,668 | 11,458 | +19% | 1 | 1 | 0% | 1,683 | 4,751 | +182% | 0 | 0 | — |
case-07 | fail→fail | 9,389 | 36,690 | +291% | 1 | 1 | 0% | 1,715 | 5,631 | +228% | 0 | 0 | — |
case-08 | pass→pass | 14,865 | 11,781 | -21% | 1 | 1 | 0% | 2,566 | 5,118 | +99% | 0 | 0 | — |
case-09 | pass→pass | 14,008 | 16,857 | +20% | 1 | 1 | 0% | 2,369 | 5,114 | +116% | 0 | 0 | — |
case-10 | pass→pass | 8,742 | 10,102 | +16% | 1 | 1 | 0% | 1,312 | 4,670 | +256% | 0 | 0 | — |
case-11 | pass→pass | 19,303 | 18,922 | -2% | 1 | 1 | 0% | 3,537 | 6,514 | +84% | 0 | 0 | — |
case-12 | pass→pass | 12,623 | 10,808 | -14% | 1 | 1 | 0% | 2,216 | 4,935 | +123% | 0 | 0 | — |
case-13 | pass→pass | 13,295 | 8,091 | -39% | 1 | 1 | 0% | 2,493 | 4,197 | +68% | 0 | 0 | — |
case-14 | pass→pass | 6,095 | 5,232 | -14% | 1 | 1 | 0% | 1,108 | 3,810 | +244% | 0 | 0 | — |
case-15 | pass→pass | 25,368 | 14,241 | -44% | 1 | 1 | 0% | 2,397 | 5,565 | +132% | 0 | 0 | — |
case-16 | pass→pass | 11,162 | 8,894 | -20% | 1 | 1 | 0% | 2,042 | 4,408 | +116% | 0 | 0 | — |
case-17 | pass→pass | 13,309 | 8,321 | -37% | 1 | 1 | 0% | 2,067 | 4,529 | +119% | 0 | 0 | — |
case-18 | pass→pass | 11,524 | 8,935 | -22% | 1 | 1 | 0% | 2,169 | 4,502 | +108% | 0 | 0 | — |
case-19 | pass→pass | 10,209 | 6,762 | -34% | 1 | 1 | 0% | 1,834 | 4,095 | +123% | 0 | 0 | — |
case-20 | pass→pass | 10,595 | 8,389 | -21% | 1 | 1 | 0% | 2,065 | 4,560 | +121% | 0 | 0 | — |
case-21 | fail→fail | 16,062 | 15,702 | -2% | 1 | 1 | 0% | 2,384 | 5,498 | +131% | 0 | 0 | — |
case-22 | fail→pass | 15,181 | 14,860 | -2% | 1 | 1 | 0% | 2,588 | 5,642 | +118% | 0 | 0 | — |
case-23 | pass→pass | 15,244 | 12,538 | -18% | 1 | 1 | 0% | 2,468 | 4,875 | +98% | 0 | 0 | — |
case-24 | pass→pass | 11,977 | 13,521 | +13% | 1 | 1 | 0% | 2,013 | 4,887 | +143% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +8 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.