pythonLogs 4.0.5__tar.gz → 5.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythonLogs
3
- Version: 4.0.5
3
+ Version: 5.0.0
4
4
  Summary: High-performance Python logging library with file rotation and optimized caching for better performance
5
5
  License: MIT
6
6
  Keywords: python3,python-3,python,log,logging,logger,logutils,log-utils,pythonLogs
7
7
  Author: Daniel Costa
8
8
  Author-email: danieldcsta@gmail.com
9
9
  Maintainer: Daniel Costa
10
- Requires-Python: >=3.10,<4.0
10
+ Requires-Python: >=3.12,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Environment :: Other Environment
13
13
  Classifier: Intended Audience :: Developers
@@ -15,8 +15,6 @@ Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Natural Language :: English
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Programming Language :: Python :: 3.12
21
19
  Classifier: Programming Language :: Python :: 3.13
22
20
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -34,8 +32,10 @@ Description-Content-Type: text/markdown
34
32
  [![PyPi](https://img.shields.io/pypi/v/pythonLogs.svg)](https://pypi.python.org/pypi/pythonLogs)
35
33
  [![PyPI Downloads](https://static.pepy.tech/badge/pythonLogs)](https://pepy.tech/projects/pythonLogs)
36
34
  [![codecov](https://codecov.io/gh/ddc/pythonLogs/graph/badge.svg?token=QsjwsmYzgD)](https://codecov.io/gh/ddc/pythonLogs)
37
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
35
+ [![CI/CD Pipeline](https://github.com/ddc/pythonLogs/actions/workflows/workflow.yml/badge.svg)](https://github.com/ddc/pythonLogs/actions/workflows/workflow.yml)
36
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ddc_pythonLogs&metric=alert_status)](https://sonarcloud.io/dashboard?id=ddc_pythonLogs)
38
37
  [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A//actions-badge.atrox.dev/ddc/pythonLogs/badge?ref=main&label=build&logo=none)](https://actions-badge.atrox.dev/ddc/pythonLogs/goto?ref=main)
38
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
39
39
  [![Python](https://img.shields.io/pypi/pyversions/pythonLogs.svg)](https://www.python.org/downloads)
40
40
 
41
41
  [![Support me on GitHub](https://img.shields.io/badge/Support_me_on_GitHub-154c79?style=for-the-badge&logo=github)](https://github.com/sponsors/ddc)
@@ -57,10 +57,12 @@ High-performance Python logging library with file rotation and optimized caching
57
57
  - [Memory Management](#memory-management)
58
58
  - [Flexible Configuration Options](#flexible-configuration-options)
59
59
  - [Migration Guide](#migration-guide)
60
- - [Development](#source-code)
61
- - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
60
+ - [Development](#development)
61
+ - [Development](#development)
62
+ - [Building from Source](#building-from-source)
63
+ - [Running Tests](#running-tests)
62
64
  - [License](#license)
63
- - [Buy me a cup of coffee](#buy-me-a-cup-of-coffee)
65
+ - [Support](#support)
64
66
 
65
67
 
66
68
 
@@ -547,26 +549,28 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
547
549
  - 📚 **Centralized configuration** through factory pattern
548
550
 
549
551
 
550
- # Source Code
551
- ### Build
552
+ # Development
553
+
554
+ ### Building from Source
552
555
  ```shell
553
556
  poetry build -f wheel
554
557
  ```
555
558
 
556
-
557
- # Run Tests and Get Coverage Report using Poe
559
+ ### Running Tests
558
560
  ```shell
559
561
  poetry update --with test
560
- poe test
562
+ poe tests
561
563
  ```
562
564
 
563
-
564
565
  # License
566
+
565
567
  Released under the [MIT License](LICENSE)
566
568
 
569
+ # Support
570
+
571
+ If you find this project helpful, consider supporting development:
567
572
 
568
- # Buy me a cup of coffee
569
- + [GitHub Sponsor](https://github.com/sponsors/ddc)
570
- + [ko-fi](https://ko-fi.com/ddcsta)
571
- + [Paypal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
573
+ - [GitHub Sponsor](https://github.com/sponsors/ddc)
574
+ - [ko-fi](https://ko-fi.com/ddcsta)
575
+ - [PayPal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
572
576
 
@@ -5,8 +5,10 @@
5
5
  [![PyPi](https://img.shields.io/pypi/v/pythonLogs.svg)](https://pypi.python.org/pypi/pythonLogs)
6
6
  [![PyPI Downloads](https://static.pepy.tech/badge/pythonLogs)](https://pepy.tech/projects/pythonLogs)
7
7
  [![codecov](https://codecov.io/gh/ddc/pythonLogs/graph/badge.svg?token=QsjwsmYzgD)](https://codecov.io/gh/ddc/pythonLogs)
8
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
8
+ [![CI/CD Pipeline](https://github.com/ddc/pythonLogs/actions/workflows/workflow.yml/badge.svg)](https://github.com/ddc/pythonLogs/actions/workflows/workflow.yml)
9
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ddc_pythonLogs&metric=alert_status)](https://sonarcloud.io/dashboard?id=ddc_pythonLogs)
9
10
  [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A//actions-badge.atrox.dev/ddc/pythonLogs/badge?ref=main&label=build&logo=none)](https://actions-badge.atrox.dev/ddc/pythonLogs/goto?ref=main)
11
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
10
12
  [![Python](https://img.shields.io/pypi/pyversions/pythonLogs.svg)](https://www.python.org/downloads)
11
13
 
12
14
  [![Support me on GitHub](https://img.shields.io/badge/Support_me_on_GitHub-154c79?style=for-the-badge&logo=github)](https://github.com/sponsors/ddc)
@@ -28,10 +30,12 @@ High-performance Python logging library with file rotation and optimized caching
28
30
  - [Memory Management](#memory-management)
29
31
  - [Flexible Configuration Options](#flexible-configuration-options)
30
32
  - [Migration Guide](#migration-guide)
31
- - [Development](#source-code)
32
- - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
33
+ - [Development](#development)
34
+ - [Development](#development)
35
+ - [Building from Source](#building-from-source)
36
+ - [Running Tests](#running-tests)
33
37
  - [License](#license)
34
- - [Buy me a cup of coffee](#buy-me-a-cup-of-coffee)
38
+ - [Support](#support)
35
39
 
36
40
 
37
41
 
@@ -518,25 +522,27 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
518
522
  - 📚 **Centralized configuration** through factory pattern
519
523
 
520
524
 
521
- # Source Code
522
- ### Build
525
+ # Development
526
+
527
+ ### Building from Source
523
528
  ```shell
524
529
  poetry build -f wheel
525
530
  ```
526
531
 
527
-
528
- # Run Tests and Get Coverage Report using Poe
532
+ ### Running Tests
529
533
  ```shell
530
534
  poetry update --with test
531
- poe test
535
+ poe tests
532
536
  ```
533
537
 
534
-
535
538
  # License
539
+
536
540
  Released under the [MIT License](LICENSE)
537
541
 
542
+ # Support
543
+
544
+ If you find this project helpful, consider supporting development:
538
545
 
539
- # Buy me a cup of coffee
540
- + [GitHub Sponsor](https://github.com/sponsors/ddc)
541
- + [ko-fi](https://ko-fi.com/ddcsta)
542
- + [Paypal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
546
+ - [GitHub Sponsor](https://github.com/sponsors/ddc)
547
+ - [ko-fi](https://ko-fi.com/ddcsta)
548
+ - [PayPal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python
2
+ import compileall
3
+ import sys
4
+ import tomllib
5
+ from pathlib import Path
6
+
7
+
8
+ def parse_python_version_requirement():
9
+ """Parse Python version requirement from pyproject.toml"""
10
+ pyproject_path = Path(__file__).parent / "pyproject.toml"
11
+
12
+ with open(pyproject_path, "rb") as f:
13
+ data = tomllib.load(f)
14
+
15
+ python_req = data["tool"]["poetry"]["dependencies"]["python"]
16
+
17
+ # Parse version constraint like "^3.12"
18
+ if python_req.startswith("^"):
19
+ min_version = python_req[1:]
20
+ major, minor = map(int, min_version.split("."))
21
+ return major, minor
22
+ elif python_req.startswith(">="):
23
+ min_version = python_req[2:]
24
+ major, minor = map(int, min_version.split("."))
25
+ return major, minor
26
+ else:
27
+ # Default fallback
28
+ return 3, 12
29
+
30
+
31
+ def get_compatible_python_versions(min_major, min_minor):
32
+ """Generate list of compatible Python versions"""
33
+ versions = []
34
+ current_major, current_minor = sys.version_info[:2]
35
+
36
+ # Start from the minimum required version
37
+ for major in range(min_major, min_major + 1): # Only Python 3.x for now
38
+ start_minor = min_minor if major == min_major else 0
39
+ # Go up to current Python version + 2 minor versions for future compatibility
40
+ end_minor = max(current_minor + 2, 12) if major == current_major else 12
41
+
42
+ for minor in range(start_minor, end_minor + 1):
43
+ if major == 3 and minor >= min_minor:
44
+ versions.append(f"{major}.{minor}")
45
+
46
+ return versions
47
+
48
+
49
+ def build():
50
+ """Build bytecode for all compatible Python versions"""
51
+ try:
52
+ min_major, min_minor = parse_python_version_requirement()
53
+ compatible_versions = get_compatible_python_versions(min_major, min_minor)
54
+
55
+ print(f"Building for Python versions: {', '.join(compatible_versions)}")
56
+
57
+ # Compile for current Python version
58
+ print(f"Compiling for Python {sys.version_info.major}.{sys.version_info.minor}")
59
+ compileall.compile_dir('pythonDatabases', force=True)
60
+
61
+ # Note: For actual cross-version bytecode, you'd need multiple Python interpreters
62
+ # This compiles with current interpreter but documents compatibility
63
+ print("Build completed successfully")
64
+
65
+ except Exception as e:
66
+ print(f"Build failed: {e}")
67
+ sys.exit(1)
68
+
69
+
70
+ if __name__ == "__main__":
71
+ build()
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "pythonLogs"
7
- version = "4.0.5"
7
+ version = "5.0.0"
8
8
  description = "High-performance Python logging library with file rotation and optimized caching for better performance"
9
9
  license = "MIT"
10
10
  readme = "README.md"
@@ -30,8 +30,15 @@ classifiers = [
30
30
  "Natural Language :: English",
31
31
  ]
32
32
 
33
+ [tool.poetry.build]
34
+ script = "build.py"
35
+ generate-setup-file = false
36
+
37
+ [tool.poetry.group.test]
38
+ optional = true
39
+
33
40
  [tool.poetry.dependencies]
34
- python = "^3.10"
41
+ python = "^3.12"
35
42
  pydantic-settings = "^2.10.1"
36
43
  python-dotenv = "^1.1.1"
37
44
 
@@ -48,9 +55,6 @@ _coverage_xml = "coverage xml"
48
55
  tests = ["_test", "_coverage_report", "_coverage_xml"]
49
56
  test = ["tests"]
50
57
 
51
- [tool.poetry.group.test]
52
- optional = true
53
-
54
58
  [tool.black]
55
59
  line-length = 120
56
60
  skip-string-normalization = true
@@ -58,7 +58,7 @@ __author__ = "Daniel Costa"
58
58
  __email__ = "danieldcsta@gmail.com>"
59
59
  __license__ = "MIT"
60
60
  __copyright__ = "Copyright 2024-present ddc"
61
- _req_python_version = (3, 10, 0)
61
+ _req_python_version = (3, 12, 0)
62
62
 
63
63
 
64
64
  try:
@@ -34,7 +34,14 @@ class BasicLog:
34
34
  logger.setLevel(self.level)
35
35
  logging.Formatter.converter = get_timezone_function(self.timezone)
36
36
  _format = get_format(self.showlocation, self.appname, self.timezone)
37
- logging.basicConfig(datefmt=self.datefmt, encoding=self.encoding, format=_format)
37
+
38
+ # Only add handler if logger doesn't have any handlers
39
+ if not logger.handlers:
40
+ handler = logging.StreamHandler()
41
+ formatter = logging.Formatter(_format, datefmt=self.datefmt)
42
+ handler.setFormatter(formatter)
43
+ logger.addHandler(handler)
44
+
38
45
  self.logger = logger
39
46
  # Register weak reference for memory tracking
40
47
  register_logger_weakref(logger)
@@ -3,6 +3,7 @@ import atexit
3
3
  import logging
4
4
  import threading
5
5
  import time
6
+ from dataclasses import dataclass
6
7
  from enum import Enum
7
8
  from typing import Dict, Optional, Tuple, Union
8
9
  from pythonLogs.basic_log import BasicLog
@@ -12,6 +13,25 @@ from pythonLogs.size_rotating import SizeRotatingLog
12
13
  from pythonLogs.timed_rotating import TimedRotatingLog
13
14
 
14
15
 
16
+ @dataclass
17
+ class LoggerConfig:
18
+ """Configuration class to group logger parameters"""
19
+ level: Optional[Union[LogLevel, str]] = None
20
+ name: Optional[str] = None
21
+ directory: Optional[str] = None
22
+ filenames: Optional[list | tuple] = None
23
+ encoding: Optional[str] = None
24
+ datefmt: Optional[str] = None
25
+ timezone: Optional[str] = None
26
+ streamhandler: Optional[bool] = None
27
+ showlocation: Optional[bool] = None
28
+ maxmbytes: Optional[int] = None
29
+ when: Optional[Union[RotateWhen, str]] = None
30
+ sufix: Optional[str] = None
31
+ rotateatutc: Optional[bool] = None
32
+ daystokeep: Optional[int] = None
33
+
34
+
15
35
  class LoggerType(str, Enum):
16
36
  """Available logger types"""
17
37
  BASIC = "basic"
@@ -80,7 +100,7 @@ class LoggerFactory:
80
100
 
81
101
  # Check if logger already exists in the registry
82
102
  if name in cls._logger_registry:
83
- logger, timestamp = cls._logger_registry[name]
103
+ logger, _ = cls._logger_registry[name]
84
104
  # Update timestamp for LRU tracking
85
105
  cls._logger_registry[name] = (logger, time.time())
86
106
  return logger
@@ -189,21 +209,8 @@ class LoggerFactory:
189
209
  @staticmethod
190
210
  def create_logger(
191
211
  logger_type: Union[LoggerType, str],
192
- level: Optional[Union[LogLevel, str]] = None,
193
- name: Optional[str] = None,
194
- directory: Optional[str] = None,
195
- filenames: Optional[list | tuple] = None,
196
- encoding: Optional[str] = None,
197
- datefmt: Optional[str] = None,
198
- timezone: Optional[str] = None,
199
- streamhandler: Optional[bool] = None,
200
- showlocation: Optional[bool] = None, # Size rotating specific
201
- maxmbytes: Optional[int] = None, # Timed rotating specific
202
- when: Optional[Union[RotateWhen, str]] = None,
203
- sufix: Optional[str] = None,
204
- rotateatutc: Optional[bool] = None,
205
- # Common
206
- daystokeep: Optional[int] = None,
212
+ config: Optional[LoggerConfig] = None,
213
+ **kwargs
207
214
  ) -> logging.Logger:
208
215
 
209
216
  """
@@ -211,20 +218,8 @@ class LoggerFactory:
211
218
 
212
219
  Args:
213
220
  logger_type: Type of logger to create (LoggerType enum or string)
214
- level: Log level (LogLevel enum or string: DEBUG, INFO, WARNING, ERROR, CRITICAL)
215
- name: Logger name
216
- directory: Log directory path
217
- filenames: List/tuple of log filenames
218
- encoding: File encoding
219
- datefmt: Date format string
220
- timezone: Timezone for timestamps
221
- streamhandler: Enable console output
222
- showlocation: Show file location in logs
223
- maxmbytes: Max file size in MB (size rotating only)
224
- when: When to rotate (RotateWhen enum or string: MIDNIGHT, HOURLY, DAILY, etc.)
225
- sufix: Date suffix for rotated files (timed rotating only)
226
- rotateatutc: Rotate at UTC time (timed rotating only)
227
- daystokeep: Days to keep old logs
221
+ config: LoggerConfig object with logger parameters
222
+ **kwargs: Individual logger parameters (for backward compatibility)
228
223
 
229
224
  Returns:
230
225
  Configured logger instance
@@ -239,50 +234,72 @@ class LoggerFactory:
239
234
  except ValueError:
240
235
  raise ValueError(f"Invalid logger type: {logger_type}. Valid types: {[t.value for t in LoggerType]}")
241
236
 
237
+ # Merge config and kwargs (kwargs take precedence for backward compatibility)
238
+ if config is None:
239
+ config = LoggerConfig()
240
+
241
+ # Create a new config with kwargs overriding config values
242
+ final_config = LoggerConfig(
243
+ level=kwargs.get('level', config.level),
244
+ name=kwargs.get('name', config.name),
245
+ directory=kwargs.get('directory', config.directory),
246
+ filenames=kwargs.get('filenames', config.filenames),
247
+ encoding=kwargs.get('encoding', config.encoding),
248
+ datefmt=kwargs.get('datefmt', config.datefmt),
249
+ timezone=kwargs.get('timezone', config.timezone),
250
+ streamhandler=kwargs.get('streamhandler', config.streamhandler),
251
+ showlocation=kwargs.get('showlocation', config.showlocation),
252
+ maxmbytes=kwargs.get('maxmbytes', config.maxmbytes),
253
+ when=kwargs.get('when', config.when),
254
+ sufix=kwargs.get('sufix', config.sufix),
255
+ rotateatutc=kwargs.get('rotateatutc', config.rotateatutc),
256
+ daystokeep=kwargs.get('daystokeep', config.daystokeep)
257
+ )
258
+
242
259
  # Convert enum values to strings for logger classes
243
- level_str = level.value if isinstance(level, LogLevel) else level
244
- when_str = when.value if isinstance(when, RotateWhen) else when
260
+ level_str = final_config.level.value if isinstance(final_config.level, LogLevel) else final_config.level
261
+ when_str = final_config.when.value if isinstance(final_config.when, RotateWhen) else final_config.when
245
262
 
246
263
  # Create logger based on type
247
264
  match logger_type:
248
265
  case LoggerType.BASIC:
249
266
  logger_instance = BasicLog(
250
267
  level=level_str,
251
- name=name,
252
- encoding=encoding,
253
- datefmt=datefmt,
254
- timezone=timezone,
255
- showlocation=showlocation, )
268
+ name=final_config.name,
269
+ encoding=final_config.encoding,
270
+ datefmt=final_config.datefmt,
271
+ timezone=final_config.timezone,
272
+ showlocation=final_config.showlocation, )
256
273
 
257
274
  case LoggerType.SIZE_ROTATING:
258
275
  logger_instance = SizeRotatingLog(
259
276
  level=level_str,
260
- name=name,
261
- directory=directory,
262
- filenames=filenames,
263
- maxmbytes=maxmbytes,
264
- daystokeep=daystokeep,
265
- encoding=encoding,
266
- datefmt=datefmt,
267
- timezone=timezone,
268
- streamhandler=streamhandler,
269
- showlocation=showlocation, )
277
+ name=final_config.name,
278
+ directory=final_config.directory,
279
+ filenames=final_config.filenames,
280
+ maxmbytes=final_config.maxmbytes,
281
+ daystokeep=final_config.daystokeep,
282
+ encoding=final_config.encoding,
283
+ datefmt=final_config.datefmt,
284
+ timezone=final_config.timezone,
285
+ streamhandler=final_config.streamhandler,
286
+ showlocation=final_config.showlocation, )
270
287
 
271
288
  case LoggerType.TIMED_ROTATING:
272
289
  logger_instance = TimedRotatingLog(
273
290
  level=level_str,
274
- name=name,
275
- directory=directory,
276
- filenames=filenames,
291
+ name=final_config.name,
292
+ directory=final_config.directory,
293
+ filenames=final_config.filenames,
277
294
  when=when_str,
278
- sufix=sufix,
279
- daystokeep=daystokeep,
280
- encoding=encoding,
281
- datefmt=datefmt,
282
- timezone=timezone,
283
- streamhandler=streamhandler,
284
- showlocation=showlocation,
285
- rotateatutc=rotateatutc, )
295
+ sufix=final_config.sufix,
296
+ daystokeep=final_config.daystokeep,
297
+ encoding=final_config.encoding,
298
+ datefmt=final_config.datefmt,
299
+ timezone=final_config.timezone,
300
+ streamhandler=final_config.streamhandler,
301
+ showlocation=final_config.showlocation,
302
+ rotateatutc=final_config.rotateatutc, )
286
303
 
287
304
  case _:
288
305
  raise ValueError(f"Unsupported logger type: {logger_type}")
@@ -11,7 +11,7 @@ from pythonLogs.constants import (
11
11
  DEFAULT_ROTATE_SUFFIX,
12
12
  DEFAULT_TIMEZONE,
13
13
  LogLevel,
14
- RotateWhen
14
+ RotateWhen,
15
15
  )
16
16
 
17
17
 
@@ -1,7 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  import functools
3
3
  import threading
4
- from typing import Any, Callable, Dict, TypeVar, Type
4
+ from typing import Any, Callable, Dict, Type, TypeVar
5
+
5
6
 
6
7
  F = TypeVar('F', bound=Callable[..., Any])
7
8
 
@@ -58,34 +59,49 @@ def thread_safe(func: F) -> F:
58
59
  return wrapper
59
60
 
60
61
 
62
+ def _get_wrappable_methods(cls: Type) -> list:
63
+ """Helper function to get methods that should be made thread-safe."""
64
+ return [
65
+ method_name for method_name in dir(cls)
66
+ if (callable(getattr(cls, method_name, None)) and
67
+ not method_name.startswith('_') and
68
+ method_name not in ['__enter__', '__exit__', '__init__'])
69
+ ]
70
+
71
+
72
+ def _ensure_class_has_lock(cls: Type) -> None:
73
+ """Ensure the class has a lock attribute."""
74
+ if not hasattr(cls, '_lock'):
75
+ cls._lock = threading.RLock()
76
+
77
+
78
+ def _should_wrap_method(cls: Type, method_name: str, original_method: Any) -> bool:
79
+ """Check if a method should be wrapped with thread safety."""
80
+ return (hasattr(cls, method_name) and
81
+ callable(original_method) and
82
+ not hasattr(original_method, '_thread_safe_wrapped'))
83
+
84
+
61
85
  def auto_thread_safe(thread_safe_methods: list = None):
62
86
  """Class decorator that adds automatic thread safety to specified methods."""
63
87
 
64
88
  def decorator(cls: Type) -> Type:
65
- # Add lock to class if not present
66
- if not hasattr(cls, '_lock'):
67
- cls._lock = threading.RLock()
89
+ _ensure_class_has_lock(cls)
68
90
 
69
91
  # Store thread-safe methods list
70
92
  if thread_safe_methods:
71
93
  cls._thread_safe_methods = thread_safe_methods
72
94
 
73
95
  # Get methods to make thread-safe
74
- methods_to_wrap = thread_safe_methods or [
75
- method_name for method_name in dir(cls)
76
- if (callable(getattr(cls, method_name, None)) and
77
- not method_name.startswith('_') and
78
- method_name not in ['__enter__', '__exit__', '__init__'])
79
- ]
96
+ methods_to_wrap = thread_safe_methods or _get_wrappable_methods(cls)
80
97
 
81
98
  # Wrap each method
82
99
  for method_name in methods_to_wrap:
83
- if hasattr(cls, method_name):
84
- original_method = getattr(cls, method_name)
85
- if callable(original_method) and not hasattr(original_method, '_thread_safe_wrapped'):
86
- wrapped_method = thread_safe(original_method)
87
- wrapped_method._thread_safe_wrapped = True
88
- setattr(cls, method_name, wrapped_method)
100
+ original_method = getattr(cls, method_name, None)
101
+ if _should_wrap_method(cls, method_name, original_method):
102
+ wrapped_method = thread_safe(original_method)
103
+ wrapped_method._thread_safe_wrapped = True
104
+ setattr(cls, method_name, wrapped_method)
89
105
 
90
106
  return cls
91
107
 
@@ -132,4 +148,4 @@ class ThreadSafeContext:
132
148
  return self
133
149
 
134
150
  def __exit__(self, exc_type, exc_val, exc_tb):
135
- self.lock.release()
151
+ self.lock.release()
@@ -10,7 +10,7 @@ from pythonLogs.log_utils import (
10
10
  get_logger_and_formatter,
11
11
  get_stream_handler,
12
12
  gzip_file_with_sufix,
13
- remove_old_logs,
13
+ remove_old_logs,
14
14
  )
15
15
  from pythonLogs.memory_utils import cleanup_logger_handlers, register_logger_weakref
16
16
  from pythonLogs.settings import get_log_settings
File without changes