diff --git a/CHANGELOG.md b/CHANGELOG.md index f177a6bc..d90ef139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ## [Unreleased](https://github.com/hynek/structlog/compare/25.5.0...HEAD) +### Added + +- `structlog.remove_file_from_lock()` to release the write lock that is implicitly registered for a file by `PrintLogger`, `WriteLogger`, and `BytesLogger`. + Useful when the underlying file is being closed. + + ### Removed - Python 3.8 and 3.9 support. diff --git a/docs/api.rst b/docs/api.rst index 2c16414f..9da2ca2f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -64,6 +64,8 @@ API Reference .. autoclass:: BytesLoggerFactory +.. autofunction:: remove_file_from_lock + .. autoexception:: DropEvent .. autoclass:: BoundLoggerBase diff --git a/src/structlog/__init__.py b/src/structlog/__init__.py index 0c72e360..d4c617e0 100644 --- a/src/structlog/__init__.py +++ b/src/structlog/__init__.py @@ -37,6 +37,7 @@ PrintLoggerFactory, WriteLogger, WriteLoggerFactory, + remove_file_from_lock, ) from structlog.exceptions import DropEvent from structlog.testing import ReturnLogger, ReturnLoggerFactory @@ -79,6 +80,7 @@ "is_configured", "make_filtering_bound_logger", "processors", + "remove_file_from_lock", "reset_defaults", "stdlib", "testing", diff --git a/src/structlog/_output.py b/src/structlog/_output.py index b1612de6..c8ed13f7 100644 --- a/src/structlog/_output.py +++ b/src/structlog/_output.py @@ -30,6 +30,19 @@ def _get_lock_for_file(file: IO[Any]) -> threading.Lock: return lock +def remove_file_from_lock(file: IO[Any]) -> None: + """ + Remove the write lock associated with *file* from the registry. + + Useful when *file* is being closed and you want to release the lock that + was implicitly created for it by `PrintLogger`, `WriteLogger`, or + `BytesLogger`. A no-op if no lock is registered for *file*. + + .. versionadded:: 26.1.0 + """ + WRITE_LOCKS.pop(file, None) + + class PrintLogger: """ Print events into a file. diff --git a/tests/test_output.py b/tests/test_output.py index 8553aad9..af0b8b78 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -17,6 +17,7 @@ PrintLoggerFactory, WriteLogger, WriteLoggerFactory, + remove_file_from_lock, ) from structlog._output import WRITE_LOCKS, stderr, stdout @@ -68,6 +69,17 @@ def test_lock(self, logger_cls, sio): assert sio in WRITE_LOCKS + def test_remove_file_from_lock(self, logger_cls, sio): + """ + remove_file_from_lock removes the lock entry for the given file. + """ + logger_cls(sio) + assert sio in WRITE_LOCKS + + remove_file_from_lock(sio) + + assert sio not in WRITE_LOCKS + @pytest.mark.parametrize("method", stdlib_log_methods) def test_stdlib_methods_support(self, logger_cls, method, sio): """ diff --git a/tests/typing/api.py b/tests/typing/api.py index 91041329..342d3851 100644 --- a/tests/typing/api.py +++ b/tests/typing/api.py @@ -9,6 +9,7 @@ from __future__ import annotations +import io import logging import logging.config @@ -412,3 +413,7 @@ def f() -> None: cr.repr_native_str cr.repr_native_str = True + +_f = io.StringIO() +structlog.PrintLogger(_f) +structlog.remove_file_from_lock(_f)