From 977d75fb6e1e6a74b62eb052b5404985971d9487 Mon Sep 17 00:00:00 2001 From: wjddn279 Date: Mon, 13 Apr 2026 17:37:01 +0900 Subject: [PATCH 1/2] Expose remove_file_from_lock helper for WRITE_LOCKS cleanup --- src/structlog/__init__.py | 2 ++ src/structlog/_output.py | 4 ++++ tests/test_output.py | 12 ++++++++++++ 3 files changed, 18 insertions(+) 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..9602f094 100644 --- a/src/structlog/_output.py +++ b/src/structlog/_output.py @@ -30,6 +30,10 @@ def _get_lock_for_file(file: IO[Any]) -> threading.Lock: return lock +def remove_file_from_lock(file: IO[Any]): + del WRITE_LOCKS[file] + + 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): """ From 6f7563d464c33ee4fe51eea3eb4e2cdfef808649 Mon Sep 17 00:00:00 2001 From: wjddn279 Date: Mon, 13 Apr 2026 17:54:17 +0900 Subject: [PATCH 2/2] fix for guideline --- CHANGELOG.md | 6 ++++++ docs/api.rst | 2 ++ src/structlog/_output.py | 13 +++++++++++-- tests/typing/api.py | 5 +++++ 4 files changed, 24 insertions(+), 2 deletions(-) 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/_output.py b/src/structlog/_output.py index 9602f094..c8ed13f7 100644 --- a/src/structlog/_output.py +++ b/src/structlog/_output.py @@ -30,8 +30,17 @@ def _get_lock_for_file(file: IO[Any]) -> threading.Lock: return lock -def remove_file_from_lock(file: IO[Any]): - del WRITE_LOCKS[file] +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: 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)