fix(audit): raise on write-after-close + weakref.finalize + parametrized event coverage
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
import dataclasses
|
||||
import io
|
||||
import json
|
||||
import weakref
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
@@ -128,22 +129,36 @@ class ErrorEvent(_BaseEvent):
|
||||
|
||||
|
||||
class AuditWriter:
|
||||
"""Streaming JSONL writer. No-op when path is None."""
|
||||
def __init__(self, path: Path | str | None, mode: str = "w") -> None:
|
||||
"""Streaming JSONL writer. No-op when path is None.
|
||||
|
||||
Once `close()` is called, subsequent `write()` calls raise ValueError.
|
||||
Use as a context manager (`with AuditWriter(p) as w:`) for guaranteed close.
|
||||
"""
|
||||
def __init__(self, path: Path | str | None, mode: Literal["w", "a"] = "w") -> None:
|
||||
self._path = Path(path) if path is not None else None
|
||||
self._fp: io.TextIOBase | None = None
|
||||
self._enabled = self._path is not None
|
||||
self._closed = False
|
||||
self._fp: io.TextIOWrapper | None = None
|
||||
if self._path is not None:
|
||||
self._path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._fp = self._path.open(mode, encoding="utf-8")
|
||||
if self._fp is not None:
|
||||
weakref.finalize(self, self._fp.close)
|
||||
|
||||
def write(self, event: _BaseEvent) -> None:
|
||||
if self._fp is None:
|
||||
if not self._enabled:
|
||||
return
|
||||
if self._closed:
|
||||
raise ValueError("AuditWriter is closed")
|
||||
assert self._fp is not None
|
||||
self._fp.write(json.dumps(event.to_dict(), ensure_ascii=False))
|
||||
self._fp.write("\n")
|
||||
self._fp.flush()
|
||||
self._fp.flush() # flush per event so a crash mid-run preserves the audit trail
|
||||
|
||||
def close(self) -> None:
|
||||
if self._closed:
|
||||
return
|
||||
self._closed = True
|
||||
if self._fp is not None:
|
||||
self._fp.close()
|
||||
self._fp = None
|
||||
@@ -151,5 +166,5 @@ class AuditWriter:
|
||||
def __enter__(self) -> "AuditWriter":
|
||||
return self
|
||||
|
||||
def __exit__(self, *args) -> None:
|
||||
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
self.close()
|
||||
|
||||
Reference in New Issue
Block a user