Skip to content

Commit 975e8ba

Browse files
committed
V6.0.2
1 parent 54ca7af commit 975e8ba

28 files changed

+243
-240
lines changed

.github/workflows/workflow.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ jobs:
1717
- uses: astral-sh/ruff-action@v3
1818
with:
1919
args: "check"
20-
- uses: astral-sh/ruff-action@v3
21-
with:
22-
args: "format --check"
2320

2421
test:
2522
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
2623
runs-on: ${{ matrix.os }}
24+
needs: lint
2725
strategy:
2826
fail-fast: false
2927
matrix:
@@ -66,7 +64,7 @@ jobs:
6664
build:
6765
name: Build Package
6866
runs-on: ubuntu-latest
69-
needs: [lint, test]
67+
needs: test
7068
if: startsWith(github.ref, 'refs/tags/v')
7169
steps:
7270
- uses: actions/checkout@v6

pythonLogs/basic_log.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pythonLogs.core.thread_safety import auto_thread_safe
66

77

8-
@auto_thread_safe(['init'])
8+
@auto_thread_safe(["init"])
99
class BasicLog:
1010
"""Basic logger with context manager support for automatic resource cleanup."""
1111

@@ -48,13 +48,13 @@ def init(self):
4848

4949
def __enter__(self):
5050
"""Context manager entry."""
51-
if not hasattr(self, 'logger') or self.logger is None:
51+
if not hasattr(self, "logger") or self.logger is None:
5252
self.init()
5353
return self.logger
5454

5555
def __exit__(self, exc_type, exc_val, exc_tb):
5656
"""Context manager exit with automatic cleanup."""
57-
if hasattr(self, 'logger'):
57+
if hasattr(self, "logger"):
5858
cleanup_logger_handlers(self.logger)
5959

6060
@staticmethod

pythonLogs/core/factory.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def get_memory_limits(cls) -> dict[str, int]:
215215
Dictionary with current max_loggers and ttl_seconds settings
216216
"""
217217
with cls._registry_lock:
218-
return {'max_loggers': cls._max_loggers, 'ttl_seconds': cls._logger_ttl}
218+
return {"max_loggers": cls._max_loggers, "ttl_seconds": cls._logger_ttl}
219219

220220
@staticmethod
221221
def create_logger(logger_type: LoggerType | str, config: LoggerConfig | None = None, **kwargs) -> logging.Logger:
@@ -248,20 +248,20 @@ def create_logger(logger_type: LoggerType | str, config: LoggerConfig | None = N
248248

249249
# Create a new config with kwargs overriding config values
250250
final_config = LoggerConfig(
251-
level=kwargs.get('level', config.level),
252-
name=kwargs.get('name', config.name),
253-
directory=kwargs.get('directory', config.directory),
254-
filenames=kwargs.get('filenames', config.filenames),
255-
encoding=kwargs.get('encoding', config.encoding),
256-
datefmt=kwargs.get('datefmt', config.datefmt),
257-
timezone=kwargs.get('timezone', config.timezone),
258-
streamhandler=kwargs.get('streamhandler', config.streamhandler),
259-
showlocation=kwargs.get('showlocation', config.showlocation),
260-
maxmbytes=kwargs.get('maxmbytes', config.maxmbytes),
261-
when=kwargs.get('when', config.when),
262-
sufix=kwargs.get('sufix', config.sufix),
263-
rotateatutc=kwargs.get('rotateatutc', config.rotateatutc),
264-
daystokeep=kwargs.get('daystokeep', config.daystokeep),
251+
level=kwargs.get("level", config.level),
252+
name=kwargs.get("name", config.name),
253+
directory=kwargs.get("directory", config.directory),
254+
filenames=kwargs.get("filenames", config.filenames),
255+
encoding=kwargs.get("encoding", config.encoding),
256+
datefmt=kwargs.get("datefmt", config.datefmt),
257+
timezone=kwargs.get("timezone", config.timezone),
258+
streamhandler=kwargs.get("streamhandler", config.streamhandler),
259+
showlocation=kwargs.get("showlocation", config.showlocation),
260+
maxmbytes=kwargs.get("maxmbytes", config.maxmbytes),
261+
when=kwargs.get("when", config.when),
262+
sufix=kwargs.get("sufix", config.sufix),
263+
rotateatutc=kwargs.get("rotateatutc", config.rotateatutc),
264+
daystokeep=kwargs.get("daystokeep", config.daystokeep),
265265
)
266266

267267
# Convert enum values to strings for logger classes

pythonLogs/core/log_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ def init(self) -> None: ...
2424

2525
def __enter__(self):
2626
"""Context manager entry."""
27-
if not hasattr(self, 'logger') or self.logger is None:
27+
if not hasattr(self, "logger") or self.logger is None:
2828
self.init()
2929
return self.logger
3030

3131
def __exit__(self, exc_type, exc_val, exc_tb):
3232
"""Context manager exit with automatic cleanup."""
33-
if hasattr(self, 'logger'):
33+
if hasattr(self, "logger"):
3434
cleanup_logger_handlers(self.logger)
3535

3636
@staticmethod

pythonLogs/core/memory_utils.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,13 @@ def get_memory_stats() -> dict[str, Any]:
126126
directory_stats = log_utils.get_directory_cache_stats()
127127

128128
return {
129-
'registry_size': registry_size,
130-
'formatter_cache_size': formatter_cache_size,
131-
'directory_cache_size': directory_stats['cached_directories'],
132-
'active_logger_count': get_active_logger_count(),
133-
'max_registry_size': factory_limits['max_loggers'],
134-
'max_formatter_cache': _max_formatters,
135-
'max_directory_cache': directory_stats['max_directories'],
129+
"registry_size": registry_size,
130+
"formatter_cache_size": formatter_cache_size,
131+
"directory_cache_size": directory_stats["cached_directories"],
132+
"active_logger_count": get_active_logger_count(),
133+
"max_registry_size": factory_limits["max_loggers"],
134+
"max_formatter_cache": _max_formatters,
135+
"max_directory_cache": directory_stats["max_directories"],
136136
}
137137

138138

@@ -173,7 +173,7 @@ def force_garbage_collection() -> dict[str, int]:
173173
collected = gc.collect()
174174

175175
return {
176-
'objects_collected': collected,
177-
'garbage_count': len(gc.garbage),
178-
'reference_cycles': gc.get_count(),
176+
"objects_collected": collected,
177+
"garbage_count": len(gc.garbage),
178+
"reference_cycles": gc.get_count(),
179179
}

pythonLogs/core/thread_safety.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import Callable
44
from typing import Any, TypeVar
55

6-
F = TypeVar('F', bound=Callable[..., Any])
6+
F = TypeVar("F", bound=Callable[..., Any])
77

88

99
class ThreadSafeMeta(type):
@@ -14,20 +14,20 @@ def __new__(mcs, name: str, bases: tuple, namespace: dict[str, Any], **kwargs):
1414
cls = super().__new__(mcs, name, bases, namespace)
1515

1616
# Add a class-level lock if not already present
17-
if not hasattr(cls, '_lock'):
17+
if not hasattr(cls, "_lock"):
1818
cls._lock = threading.RLock()
1919

2020
# Get methods that should be thread-safe (exclude private/dunder methods)
21-
thread_safe_methods = getattr(cls, '_thread_safe_methods', None)
21+
thread_safe_methods = getattr(cls, "_thread_safe_methods", None)
2222
if thread_safe_methods is None:
2323
# Auto-detect public methods that modify state
2424
thread_safe_methods = [
2525
method_name
2626
for method_name in namespace
2727
if (
2828
callable(getattr(cls, method_name, None))
29-
and not method_name.startswith('_')
30-
and method_name not in ['__enter__', '__exit__', '__init__']
29+
and not method_name.startswith("_")
30+
and method_name not in ["__enter__", "__exit__", "__init__"]
3131
)
3232
]
3333

@@ -48,10 +48,10 @@ def thread_safe(func: F) -> F:
4848
@functools.wraps(func)
4949
def wrapper(self, *args, **kwargs):
5050
# Use instance lock if available, otherwise class lock
51-
lock = getattr(self, '_lock', None)
51+
lock = getattr(self, "_lock", None)
5252
if lock is None:
5353
# Check if class has lock, if not create one
54-
if not hasattr(self.__class__, '_lock'):
54+
if not hasattr(self.__class__, "_lock"):
5555
self.__class__._lock = threading.RLock()
5656
lock = self.__class__._lock
5757

@@ -68,22 +68,22 @@ def _get_wrappable_methods(cls: type) -> list:
6868
for method_name in dir(cls)
6969
if (
7070
callable(getattr(cls, method_name, None))
71-
and not method_name.startswith('_')
72-
and method_name not in ['__enter__', '__exit__', '__init__']
71+
and not method_name.startswith("_")
72+
and method_name not in ["__enter__", "__exit__", "__init__"]
7373
)
7474
]
7575

7676

7777
def _ensure_class_has_lock(cls: type) -> None:
7878
"""Ensure the class has a lock attribute."""
79-
if not hasattr(cls, '_lock'):
79+
if not hasattr(cls, "_lock"):
8080
cls._lock = threading.RLock()
8181

8282

8383
def _should_wrap_method(cls: type, method_name: str, original_method: Any) -> bool:
8484
"""Check if a method should be wrapped with thread safety."""
8585
return (
86-
hasattr(cls, method_name) and callable(original_method) and not hasattr(original_method, '_thread_safe_wrapped')
86+
hasattr(cls, method_name) and callable(original_method) and not hasattr(original_method, "_thread_safe_wrapped")
8787
)
8888

8989

@@ -117,21 +117,21 @@ class AutoThreadSafe:
117117
"""Base class that provides automatic thread safety for all public methods."""
118118

119119
def __init__(self):
120-
if not hasattr(self, '_lock'):
120+
if not hasattr(self, "_lock"):
121121
self._lock = threading.RLock()
122122

123123
def __init_subclass__(cls, **kwargs):
124124
super().__init_subclass__(**kwargs)
125125

126126
# Add class-level lock
127-
if not hasattr(cls, '_lock'):
127+
if not hasattr(cls, "_lock"):
128128
cls._lock = threading.RLock()
129129

130130
# Auto-wrap public methods
131131
for attr_name in dir(cls):
132-
if not attr_name.startswith('_'):
132+
if not attr_name.startswith("_"):
133133
attr = getattr(cls, attr_name)
134-
if callable(attr) and not hasattr(attr, '_thread_safe_wrapped'):
134+
if callable(attr) and not hasattr(attr, "_thread_safe_wrapped"):
135135
wrapped_attr = thread_safe(attr)
136136
wrapped_attr._thread_safe_wrapped = True
137137
setattr(cls, attr_name, wrapped_attr)

pythonLogs/size_rotating.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from pythonLogs.core.thread_safety import auto_thread_safe
2121

2222

23-
@auto_thread_safe(['init'])
23+
@auto_thread_safe(["init"])
2424
class SizeRotatingLog(RotatingLogMixin):
2525
"""Size-based rotating logger with context manager support for automatic resource cleanup."""
2626

pythonLogs/timed_rotating.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from pythonLogs.core.thread_safety import auto_thread_safe
1717

1818

19-
@auto_thread_safe(['init'])
19+
@auto_thread_safe(["init"])
2020
class TimedRotatingLog(RotatingLogMixin):
2121
"""
2222
Time-based rotating logger with context manager support for automatic resource cleanup.

tests/context_management/test_context_managers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def test_size_rotating_context_manager(self):
7171
assert logger.level == logging.DEBUG
7272

7373
# Should have file handlers
74-
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
74+
file_handlers = [h for h in logger.handlers if hasattr(h, "baseFilename")]
7575
assert len(file_handlers) > 0
7676

7777
# Test logging
@@ -98,7 +98,7 @@ def test_timed_rotating_context_manager(self):
9898
assert logger.level == logging.WARNING
9999

100100
# Should have file handlers
101-
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
101+
file_handlers = [h for h in logger.handlers if hasattr(h, "baseFilename")]
102102
assert len(file_handlers) > 0
103103

104104
# Test logging
@@ -134,7 +134,7 @@ def test_context_manager_without_init(self):
134134

135135
with logger_instance as logger:
136136
# Context manager should have called init()
137-
assert hasattr(logger_instance, 'logger')
137+
assert hasattr(logger_instance, "logger")
138138
assert logger_instance.logger is not None
139139
assert isinstance(logger, logging.Logger)
140140
logger.info("Test message")
@@ -148,7 +148,7 @@ def test_context_manager_with_existing_init(self):
148148

149149
# Call init() manually first
150150
manual_logger = logger_instance.init()
151-
assert hasattr(logger_instance, 'logger')
151+
assert hasattr(logger_instance, "logger")
152152

153153
with logger_instance as context_logger:
154154
# Should return the same logger
@@ -167,7 +167,7 @@ def test_multiple_file_handlers_cleanup(self):
167167
name=logger_name, directory=self.temp_dir, filenames=multiple_files, maxmbytes=1
168168
) as logger:
169169
# Should have multiple file handlers
170-
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
170+
file_handlers = [h for h in logger.handlers if hasattr(h, "baseFilename")]
171171
assert len(file_handlers) == len(multiple_files)
172172

173173
logger.info("Test message to multiple files")
@@ -188,7 +188,7 @@ def test_stream_handler_cleanup(self):
188188
) as logger:
189189
# Should have both file and stream handlers
190190
stream_handlers = [h for h in logger.handlers if isinstance(h, logging.StreamHandler)]
191-
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
191+
file_handlers = [h for h in logger.handlers if hasattr(h, "baseFilename")]
192192

193193
assert len(stream_handlers) > 0
194194
assert len(file_handlers) > 0

tests/context_management/test_resource_management.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def test_registry_clear_with_file_handlers(self):
165165
logger.info("Test message before cleanup")
166166

167167
# Verify we have multiple handlers
168-
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
168+
file_handlers = [h for h in logger.handlers if hasattr(h, "baseFilename")]
169169
stream_handlers = [h for h in logger.handlers if isinstance(h, logging.StreamHandler)]
170170

171171
assert len(file_handlers) == 2 # Two file handlers

0 commit comments

Comments
 (0)