winipedia-utils 0.1.62__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of winipedia-utils might be problematic. Click here for more details.

Files changed (91) hide show
  1. winipedia_utils/concurrent/concurrent.py +245 -242
  2. winipedia_utils/concurrent/multiprocessing.py +130 -115
  3. winipedia_utils/concurrent/multithreading.py +93 -93
  4. winipedia_utils/consts.py +23 -23
  5. winipedia_utils/data/__init__.py +1 -1
  6. winipedia_utils/data/dataframe/__init__.py +1 -0
  7. winipedia_utils/data/dataframe/cleaning.py +378 -0
  8. winipedia_utils/data/structures/__init__.py +1 -0
  9. winipedia_utils/data/structures/dicts.py +16 -0
  10. winipedia_utils/django/__init__.py +24 -24
  11. winipedia_utils/django/bulk.py +538 -538
  12. winipedia_utils/django/command.py +334 -334
  13. winipedia_utils/django/database.py +289 -289
  14. winipedia_utils/git/__init__.py +1 -1
  15. winipedia_utils/git/gitignore/__init__.py +1 -1
  16. winipedia_utils/git/gitignore/gitignore.py +136 -136
  17. winipedia_utils/git/pre_commit/__init__.py +1 -1
  18. winipedia_utils/git/pre_commit/config.py +70 -70
  19. winipedia_utils/git/pre_commit/hooks.py +109 -109
  20. winipedia_utils/git/pre_commit/run_hooks.py +49 -49
  21. winipedia_utils/iterating/__init__.py +1 -1
  22. winipedia_utils/iterating/iterate.py +29 -29
  23. winipedia_utils/logging/ansi.py +6 -6
  24. winipedia_utils/logging/config.py +64 -64
  25. winipedia_utils/logging/logger.py +26 -26
  26. winipedia_utils/modules/class_.py +119 -119
  27. winipedia_utils/modules/function.py +101 -103
  28. winipedia_utils/modules/module.py +379 -379
  29. winipedia_utils/modules/package.py +390 -390
  30. winipedia_utils/oop/mixins/meta.py +333 -331
  31. winipedia_utils/oop/mixins/mixin.py +37 -37
  32. winipedia_utils/os/__init__.py +1 -1
  33. winipedia_utils/os/os.py +63 -63
  34. winipedia_utils/projects/__init__.py +1 -1
  35. winipedia_utils/projects/poetry/__init__.py +1 -1
  36. winipedia_utils/projects/poetry/config.py +91 -91
  37. winipedia_utils/projects/poetry/poetry.py +31 -31
  38. winipedia_utils/projects/project.py +48 -48
  39. winipedia_utils/pyside/__init__.py +1 -1
  40. winipedia_utils/pyside/core/__init__.py +1 -1
  41. winipedia_utils/pyside/core/py_qiodevice.py +476 -476
  42. winipedia_utils/pyside/ui/__init__.py +1 -1
  43. winipedia_utils/pyside/ui/base/__init__.py +1 -1
  44. winipedia_utils/pyside/ui/base/base.py +180 -180
  45. winipedia_utils/pyside/ui/pages/__init__.py +1 -1
  46. winipedia_utils/pyside/ui/pages/base/__init__.py +1 -1
  47. winipedia_utils/pyside/ui/pages/base/base.py +92 -92
  48. winipedia_utils/pyside/ui/pages/browser.py +26 -26
  49. winipedia_utils/pyside/ui/pages/player.py +85 -85
  50. winipedia_utils/pyside/ui/widgets/__init__.py +1 -1
  51. winipedia_utils/pyside/ui/widgets/browser.py +243 -243
  52. winipedia_utils/pyside/ui/widgets/clickable_widget.py +57 -57
  53. winipedia_utils/pyside/ui/widgets/media_player.py +430 -423
  54. winipedia_utils/pyside/ui/widgets/notification.py +78 -78
  55. winipedia_utils/pyside/ui/windows/__init__.py +1 -1
  56. winipedia_utils/pyside/ui/windows/base/__init__.py +1 -1
  57. winipedia_utils/pyside/ui/windows/base/base.py +49 -49
  58. winipedia_utils/resources/__init__.py +1 -1
  59. winipedia_utils/resources/svgs/__init__.py +1 -1
  60. winipedia_utils/resources/svgs/download_arrow.svg +2 -2
  61. winipedia_utils/resources/svgs/exit_fullscreen_icon.svg +5 -5
  62. winipedia_utils/resources/svgs/fullscreen_icon.svg +2 -2
  63. winipedia_utils/resources/svgs/menu_icon.svg +3 -3
  64. winipedia_utils/resources/svgs/pause_icon.svg +3 -3
  65. winipedia_utils/resources/svgs/play_icon.svg +16 -16
  66. winipedia_utils/resources/svgs/plus_icon.svg +23 -23
  67. winipedia_utils/resources/svgs/svg.py +15 -15
  68. winipedia_utils/security/__init__.py +1 -1
  69. winipedia_utils/security/cryptography.py +29 -29
  70. winipedia_utils/security/keyring.py +70 -70
  71. winipedia_utils/setup.py +47 -47
  72. winipedia_utils/testing/assertions.py +23 -23
  73. winipedia_utils/testing/convention.py +177 -177
  74. winipedia_utils/testing/create_tests.py +291 -291
  75. winipedia_utils/testing/fixtures.py +28 -28
  76. winipedia_utils/testing/tests/base/fixtures/__init__.py +1 -1
  77. winipedia_utils/testing/tests/base/fixtures/fixture.py +6 -6
  78. winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +33 -33
  79. winipedia_utils/testing/tests/base/fixtures/scopes/function.py +7 -7
  80. winipedia_utils/testing/tests/base/fixtures/scopes/module.py +31 -31
  81. winipedia_utils/testing/tests/base/fixtures/scopes/package.py +7 -7
  82. winipedia_utils/testing/tests/base/fixtures/scopes/session.py +312 -312
  83. winipedia_utils/testing/tests/base/utils/utils.py +82 -82
  84. winipedia_utils/testing/tests/conftest.py +32 -32
  85. winipedia_utils/text/string.py +126 -126
  86. {winipedia_utils-0.1.62.dist-info → winipedia_utils-0.2.0.dist-info}/METADATA +5 -4
  87. winipedia_utils-0.2.0.dist-info/RECORD +103 -0
  88. {winipedia_utils-0.1.62.dist-info → winipedia_utils-0.2.0.dist-info}/WHEEL +1 -1
  89. {winipedia_utils-0.1.62.dist-info → winipedia_utils-0.2.0.dist-info/licenses}/LICENSE +21 -21
  90. winipedia_utils/data/dataframe.py +0 -7
  91. winipedia_utils-0.1.62.dist-info/RECORD +0 -100
@@ -1,331 +1,333 @@
1
- """Metaclass utilities for class behavior modification and enforcement.
2
-
3
- This module provides metaclasses that can be used to
4
- modify class behavior at creation time.
5
- These metaclasses can be used individually or combined to create classes
6
- with enhanced capabilities and stricter implementation requirements.
7
-
8
- """
9
-
10
- import time
11
- from abc import ABCMeta, abstractmethod
12
- from collections.abc import Callable
13
- from functools import wraps
14
- from typing import Any, final
15
-
16
- from winipedia_utils.logging.logger import get_logger
17
- from winipedia_utils.modules.class_ import get_all_methods_from_cls
18
- from winipedia_utils.modules.function import is_func, unwrap_method
19
- from winipedia_utils.text.string import value_to_truncated_string
20
-
21
- logger = get_logger(__name__)
22
-
23
-
24
- class ABCLoggingMeta(ABCMeta):
25
- """Metaclass that automatically adds logging to class methods.
26
-
27
- Wraps non-magic methods with a logging decorator that tracks method calls,
28
- arguments, execution time, and return values. Includes rate limiting to
29
- prevent log flooding.
30
- """
31
-
32
- def __new__(
33
- mcs: type["ABCLoggingMeta"],
34
- name: str,
35
- bases: tuple[type, ...],
36
- dct: dict[str, Any],
37
- ) -> "ABCLoggingMeta":
38
- """Create a new class with logging-wrapped methods.
39
-
40
- Args:
41
- mcs: The metaclass instance
42
- name: The name of the class being created
43
- bases: The base classes of the class being created
44
- dct: The attribute dictionary of the class being created
45
-
46
- Returns:
47
- A new class with logging functionality added to its methods
48
-
49
- """
50
- # Wrap all callables of the class with a logging wrapper
51
-
52
- for attr_name, attr_value in dct.items():
53
- if mcs.is_loggable_method(attr_value):
54
- if isinstance(attr_value, classmethod):
55
- wrapped_method = mcs.wrap_with_logging(
56
- func=attr_value.__func__, class_name=name, call_times={}
57
- )
58
- dct[attr_name] = classmethod(wrapped_method)
59
- elif isinstance(attr_value, staticmethod):
60
- wrapped_method = mcs.wrap_with_logging(
61
- func=attr_value.__func__, class_name=name, call_times={}
62
- )
63
- dct[attr_name] = staticmethod(wrapped_method)
64
- else:
65
- dct[attr_name] = mcs.wrap_with_logging(
66
- func=attr_value, class_name=name, call_times={}
67
- )
68
-
69
- return super().__new__(mcs, name, bases, dct)
70
-
71
- @staticmethod
72
- def is_loggable_method(method: Callable[..., Any]) -> bool:
73
- """Determine if a method should have logging applied.
74
-
75
- Args:
76
- method: The method to check
77
-
78
- Returns:
79
- True if the method should be wrapped with logging, False otherwise
80
-
81
- """
82
- return (
83
- is_func(method) # must be a method-like attribute
84
- and not method.__name__.startswith("__") # must not be a magic method
85
- )
86
-
87
- @staticmethod
88
- def wrap_with_logging(
89
- func: Callable[..., Any],
90
- class_name: str,
91
- call_times: dict[str, float],
92
- ) -> Callable[..., Any]:
93
- """Wrap a function with logging functionality.
94
-
95
- Creates a wrapper that logs method calls, arguments, execution time,
96
- and return values. Includes rate limiting to prevent excessive logging.
97
-
98
- Args:
99
- func: The function to wrap with logging
100
- class_name: The name of the class containing the function
101
- call_times: Dictionary to track when methods were last called
102
-
103
- Returns:
104
- A wrapped function with logging capabilities
105
-
106
- """
107
- time_time = time.time # Cache the time.time function for performance
108
-
109
- @wraps(func)
110
- def wrapper(*args: object, **kwargs: object) -> object:
111
- # call_times as a dictionary to store the call times of the function
112
- # we only log if the time since the last call is greater than the threshold
113
- # this is to avoid spamming the logs
114
-
115
- func_name = func.__name__
116
-
117
- threshold = 1
118
-
119
- last_call_time = call_times.get(func_name, 0)
120
-
121
- current_time = time_time()
122
-
123
- do_logging = (current_time - last_call_time) > threshold
124
-
125
- max_log_length = 20
126
-
127
- if do_logging:
128
- args_str = value_to_truncated_string(
129
- value=args, max_length=max_log_length
130
- )
131
-
132
- kwargs_str = value_to_truncated_string(
133
- value=kwargs, max_length=max_log_length
134
- )
135
-
136
- logger.info(
137
- "%s - Calling %s with %s and %s",
138
- class_name,
139
- func_name,
140
- args_str,
141
- kwargs_str,
142
- )
143
-
144
- # Execute the function and return the result
145
-
146
- result = func(*args, **kwargs)
147
-
148
- if do_logging:
149
- duration = time_time() - current_time
150
-
151
- result_str = value_to_truncated_string(
152
- value=result, max_length=max_log_length
153
- )
154
-
155
- logger.info(
156
- "%s - %s finished with %s seconds -> returning %s",
157
- class_name,
158
- func_name,
159
- duration,
160
- result_str,
161
- )
162
-
163
- # save the call time for the next call
164
-
165
- call_times[func_name] = current_time
166
-
167
- return result
168
-
169
- return wrapper
170
-
171
-
172
- class StrictABCMeta(ABCMeta):
173
- """Metaclass that enforces implementation.
174
-
175
- Ensures that concrete subclasses properly implement all required attributes
176
- and that their types match the expected types from type annotations.
177
- Additionally enforces that methods must be decorated with either @final or
178
- @abstractmethod to make design intentions explicit.
179
- """
180
-
181
- def __init__(
182
- cls: "StrictABCMeta",
183
- name: str,
184
- bases: tuple[type, ...],
185
- namespace: dict[str, Any],
186
- /,
187
- **_kwargs: Any,
188
- ) -> None:
189
- """Initialize a class with implementation checking.
190
-
191
- Verifies that concrete classes (non-abstract) properly implement
192
- all required attributes with the correct types. Also checks that
193
- methods are properly decorated with @final or @abstractmethod.
194
-
195
- Args:
196
- cls: The class being initialized
197
- name: The name of the class
198
- bases: The base classes
199
- namespace: The attribute dictionary
200
-
201
- Raises:
202
- NotImplementedError: If the class doesn't define __abstract__
203
- ValueError: If a required attribute is not implemented
204
- TypeError: If an implemented attribute has the wrong type
205
- TypeError: If a method is neither final nor abstract
206
-
207
- """
208
- super().__init__(name, bases, namespace)
209
-
210
- # Check method decorators regardless of abstract status
211
-
212
- cls.check_method_decorators()
213
-
214
- if cls.is_abstract_cls():
215
- return
216
-
217
- cls.check_attrs_implemented()
218
-
219
- def is_abstract_cls(cls) -> bool:
220
- """Check if the class is abstract.
221
-
222
- Determines abstractness based on if any methods have @abstractmethod.
223
-
224
- Returns:
225
- True if the class is abstract, False otherwise
226
-
227
- """
228
- return any(cls.__abstractmethods__)
229
-
230
- def check_method_decorators(cls) -> None:
231
- """Check that all methods are properly decorated with @final or @abstractmethod.
232
-
233
- Verifies that all methods in the class are explicitly marked
234
- as either final or abstract to enforce design intentions.
235
-
236
- Raises:
237
- TypeError: If a method is neither final nor abstract
238
-
239
- """
240
- # Get all methods defined in this class (not inherited)
241
-
242
- for func in get_all_methods_from_cls(cls, exclude_parent_methods=True):
243
- # Check if the method is marked as final or abstract
244
-
245
- if not cls.is_final_method(func) and not cls.is_abstract_method(func):
246
- msg = (
247
- f"Method {cls.__name__}.{func.__name__} must be decorated with "
248
- f"@{final.__name__} or @{abstractmethod.__name__} "
249
- f"to make design intentions explicit."
250
- )
251
-
252
- raise TypeError(msg)
253
-
254
- @staticmethod
255
- def is_final_method(method: Callable[..., Any]) -> bool:
256
- """Check if a method is marked as final.
257
-
258
- Args:
259
- method: The method to check
260
-
261
- Returns:
262
- True if the method is marked with @final, False otherwise
263
-
264
- """
265
- unwrapped_method = unwrap_method(method)
266
- return getattr(method, "__final__", False) or getattr(
267
- unwrapped_method, "__final__", False
268
- )
269
-
270
- @staticmethod
271
- def is_abstract_method(method: Callable[..., Any]) -> bool:
272
- """Check if a method is an abstract method.
273
-
274
- Args:
275
- method: The method to check
276
-
277
- Returns:
278
- True if the method is marked with @abstractmethod, False otherwise
279
-
280
- """
281
- return getattr(method, "__isabstractmethod__", False)
282
-
283
- def check_attrs_implemented(cls) -> None:
284
- """Check that all required attributes are implemented.
285
-
286
- Verifies that all attributes marked as NotImplemented in parent classes
287
- are properly implemented in this class, and that their types match
288
- the expected types from type annotations.
289
-
290
- Raises:
291
- ValueError: If a required attribute is not implemented
292
-
293
- """
294
- for attr in cls.attrs_to_implement():
295
- value = getattr(cls, attr, NotImplemented)
296
-
297
- if value is NotImplemented:
298
- msg = f"{attr=} must be implemented."
299
-
300
- raise ValueError(msg)
301
-
302
- def attrs_to_implement(cls) -> list[str]:
303
- """Find all attributes marked as NotImplemented in parent classes.
304
-
305
- Searches the class hierarchy for attributes that are set to NotImplemented,
306
- which indicates they must be implemented by concrete subclasses.
307
-
308
- Returns:
309
- List of attribute names that must be implemented
310
-
311
- """
312
- attrs = {
313
- attr
314
- for base_class in cls.__mro__
315
- for attr in dir(base_class)
316
- if getattr(base_class, attr, None) is NotImplemented
317
- }
318
-
319
- return list(attrs)
320
-
321
-
322
- class StrictABCLoggingMeta(StrictABCMeta, ABCLoggingMeta):
323
- """Combined metaclass that merges implementation, logging, and ABC functionality.
324
-
325
- This metaclass combines the features of:
326
- - ImplementationMeta: Enforces implementation of required attributes
327
- - LoggingMeta: Adds automatic logging to methods
328
- - ABCMeta: Provides abstract base class functionality
329
-
330
- Use this metaclass when you need all three behaviors in a single class.
331
- """
1
+ """Metaclass utilities for class behavior modification and enforcement.
2
+
3
+ This module provides metaclasses that can be used to
4
+ modify class behavior at creation time.
5
+ These metaclasses can be used individually or combined to create classes
6
+ with enhanced capabilities and stricter implementation requirements.
7
+
8
+ """
9
+
10
+ import time
11
+ from abc import ABCMeta, abstractmethod
12
+ from collections.abc import Callable
13
+ from functools import wraps
14
+ from typing import Any, final
15
+
16
+ from winipedia_utils.logging.logger import get_logger
17
+ from winipedia_utils.modules.class_ import get_all_methods_from_cls
18
+ from winipedia_utils.modules.function import is_func, unwrap_method
19
+ from winipedia_utils.text.string import value_to_truncated_string
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class ABCLoggingMeta(ABCMeta):
25
+ """Metaclass that automatically adds logging to class methods.
26
+
27
+ Wraps non-magic methods with a logging decorator that tracks method calls,
28
+ arguments, execution time, and return values. Includes rate limiting to
29
+ prevent log flooding.
30
+ """
31
+
32
+ def __new__(
33
+ mcs: type["ABCLoggingMeta"],
34
+ name: str,
35
+ bases: tuple[type, ...],
36
+ dct: dict[str, Any],
37
+ ) -> "ABCLoggingMeta":
38
+ """Create a new class with logging-wrapped methods.
39
+
40
+ Args:
41
+ mcs: The metaclass instance
42
+ name: The name of the class being created
43
+ bases: The base classes of the class being created
44
+ dct: The attribute dictionary of the class being created
45
+
46
+ Returns:
47
+ A new class with logging functionality added to its methods
48
+
49
+ """
50
+ # Wrap all callables of the class with a logging wrapper
51
+
52
+ for attr_name, attr_value in dct.items():
53
+ if mcs.is_loggable_method(attr_value):
54
+ if isinstance(attr_value, classmethod):
55
+ wrapped_method = mcs.wrap_with_logging(
56
+ func=attr_value.__func__, class_name=name, call_times={}
57
+ )
58
+ dct[attr_name] = classmethod(wrapped_method)
59
+ elif isinstance(attr_value, staticmethod):
60
+ wrapped_method = mcs.wrap_with_logging(
61
+ func=attr_value.__func__, class_name=name, call_times={}
62
+ )
63
+ dct[attr_name] = staticmethod(wrapped_method)
64
+ else:
65
+ dct[attr_name] = mcs.wrap_with_logging(
66
+ func=attr_value, class_name=name, call_times={}
67
+ )
68
+
69
+ return super().__new__(mcs, name, bases, dct)
70
+
71
+ @staticmethod
72
+ def is_loggable_method(method: Callable[..., Any]) -> bool:
73
+ """Determine if a method should have logging applied.
74
+
75
+ Args:
76
+ method: The method to check, properties are not logged
77
+ as they are not callable and it turns out to be tricky with them
78
+
79
+ Returns:
80
+ True if the method should be wrapped with logging, False otherwise
81
+
82
+ """
83
+ return (
84
+ is_func(method) # must be a method-like attribute
85
+ and hasattr(method, "__name__") # must have a name
86
+ and not method.__name__.startswith("__") # must not be a magic method
87
+ )
88
+
89
+ @staticmethod
90
+ def wrap_with_logging(
91
+ func: Callable[..., Any],
92
+ class_name: str,
93
+ call_times: dict[str, float],
94
+ ) -> Callable[..., Any]:
95
+ """Wrap a function with logging functionality.
96
+
97
+ Creates a wrapper that logs method calls, arguments, execution time,
98
+ and return values. Includes rate limiting to prevent excessive logging.
99
+
100
+ Args:
101
+ func: The function to wrap with logging
102
+ class_name: The name of the class containing the function
103
+ call_times: Dictionary to track when methods were last called
104
+
105
+ Returns:
106
+ A wrapped function with logging capabilities
107
+
108
+ """
109
+ time_time = time.time # Cache the time.time function for performance
110
+
111
+ @wraps(func)
112
+ def wrapper(*args: object, **kwargs: object) -> object:
113
+ # call_times as a dictionary to store the call times of the function
114
+ # we only log if the time since the last call is greater than the threshold
115
+ # this is to avoid spamming the logs
116
+
117
+ func_name = func.__name__
118
+
119
+ threshold = 1
120
+
121
+ last_call_time = call_times.get(func_name, 0)
122
+
123
+ current_time = time_time()
124
+
125
+ do_logging = (current_time - last_call_time) > threshold
126
+
127
+ max_log_length = 20
128
+
129
+ if do_logging:
130
+ args_str = value_to_truncated_string(
131
+ value=args, max_length=max_log_length
132
+ )
133
+
134
+ kwargs_str = value_to_truncated_string(
135
+ value=kwargs, max_length=max_log_length
136
+ )
137
+
138
+ logger.info(
139
+ "%s - Calling %s with %s and %s",
140
+ class_name,
141
+ func_name,
142
+ args_str,
143
+ kwargs_str,
144
+ )
145
+
146
+ # Execute the function and return the result
147
+
148
+ result = func(*args, **kwargs)
149
+
150
+ if do_logging:
151
+ duration = time_time() - current_time
152
+
153
+ result_str = value_to_truncated_string(
154
+ value=result, max_length=max_log_length
155
+ )
156
+
157
+ logger.info(
158
+ "%s - %s finished with %s seconds -> returning %s",
159
+ class_name,
160
+ func_name,
161
+ duration,
162
+ result_str,
163
+ )
164
+
165
+ # save the call time for the next call
166
+
167
+ call_times[func_name] = current_time
168
+
169
+ return result
170
+
171
+ return wrapper
172
+
173
+
174
+ class StrictABCMeta(ABCMeta):
175
+ """Metaclass that enforces implementation.
176
+
177
+ Ensures that concrete subclasses properly implement all required attributes
178
+ and that their types match the expected types from type annotations.
179
+ Additionally enforces that methods must be decorated with either @final or
180
+ @abstractmethod to make design intentions explicit.
181
+ """
182
+
183
+ def __init__(
184
+ cls: "StrictABCMeta",
185
+ name: str,
186
+ bases: tuple[type, ...],
187
+ namespace: dict[str, Any],
188
+ /,
189
+ **_kwargs: Any,
190
+ ) -> None:
191
+ """Initialize a class with implementation checking.
192
+
193
+ Verifies that concrete classes (non-abstract) properly implement
194
+ all required attributes with the correct types. Also checks that
195
+ methods are properly decorated with @final or @abstractmethod.
196
+
197
+ Args:
198
+ cls: The class being initialized
199
+ name: The name of the class
200
+ bases: The base classes
201
+ namespace: The attribute dictionary
202
+
203
+ Raises:
204
+ NotImplementedError: If the class doesn't define __abstract__
205
+ ValueError: If a required attribute is not implemented
206
+ TypeError: If an implemented attribute has the wrong type
207
+ TypeError: If a method is neither final nor abstract
208
+
209
+ """
210
+ super().__init__(name, bases, namespace)
211
+
212
+ # Check method decorators regardless of abstract status
213
+
214
+ cls.check_method_decorators()
215
+
216
+ if cls.is_abstract_cls():
217
+ return
218
+
219
+ cls.check_attrs_implemented()
220
+
221
+ def is_abstract_cls(cls) -> bool:
222
+ """Check if the class is abstract.
223
+
224
+ Determines abstractness based on if any methods have @abstractmethod.
225
+
226
+ Returns:
227
+ True if the class is abstract, False otherwise
228
+
229
+ """
230
+ return any(cls.__abstractmethods__)
231
+
232
+ def check_method_decorators(cls) -> None:
233
+ """Check that all methods are properly decorated with @final or @abstractmethod.
234
+
235
+ Verifies that all methods in the class are explicitly marked
236
+ as either final or abstract to enforce design intentions.
237
+
238
+ Raises:
239
+ TypeError: If a method is neither final nor abstract
240
+
241
+ """
242
+ # Get all methods defined in this class (not inherited)
243
+
244
+ for func in get_all_methods_from_cls(cls, exclude_parent_methods=True):
245
+ # Check if the method is marked as final or abstract
246
+
247
+ if not cls.is_final_method(func) and not cls.is_abstract_method(func):
248
+ msg = (
249
+ f"Method {cls.__name__}.{func.__name__} must be decorated with "
250
+ f"@{final.__name__} or @{abstractmethod.__name__} "
251
+ f"to make design intentions explicit."
252
+ )
253
+
254
+ raise TypeError(msg)
255
+
256
+ @staticmethod
257
+ def is_final_method(method: Callable[..., Any]) -> bool:
258
+ """Check if a method is marked as final.
259
+
260
+ Args:
261
+ method: The method to check
262
+
263
+ Returns:
264
+ True if the method is marked with @final, False otherwise
265
+
266
+ """
267
+ unwrapped_method = unwrap_method(method)
268
+ return getattr(method, "__final__", False) or getattr(
269
+ unwrapped_method, "__final__", False
270
+ )
271
+
272
+ @staticmethod
273
+ def is_abstract_method(method: Callable[..., Any]) -> bool:
274
+ """Check if a method is an abstract method.
275
+
276
+ Args:
277
+ method: The method to check
278
+
279
+ Returns:
280
+ True if the method is marked with @abstractmethod, False otherwise
281
+
282
+ """
283
+ return getattr(method, "__isabstractmethod__", False)
284
+
285
+ def check_attrs_implemented(cls) -> None:
286
+ """Check that all required attributes are implemented.
287
+
288
+ Verifies that all attributes marked as NotImplemented in parent classes
289
+ are properly implemented in this class, and that their types match
290
+ the expected types from type annotations.
291
+
292
+ Raises:
293
+ ValueError: If a required attribute is not implemented
294
+
295
+ """
296
+ for attr in cls.attrs_to_implement():
297
+ value = getattr(cls, attr, NotImplemented)
298
+
299
+ if value is NotImplemented:
300
+ msg = f"{attr=} must be implemented."
301
+
302
+ raise ValueError(msg)
303
+
304
+ def attrs_to_implement(cls) -> list[str]:
305
+ """Find all attributes marked as NotImplemented in parent classes.
306
+
307
+ Searches the class hierarchy for attributes that are set to NotImplemented,
308
+ which indicates they must be implemented by concrete subclasses.
309
+
310
+ Returns:
311
+ List of attribute names that must be implemented
312
+
313
+ """
314
+ attrs = {
315
+ attr
316
+ for base_class in cls.__mro__
317
+ for attr in dir(base_class)
318
+ if getattr(base_class, attr, None) is NotImplemented
319
+ }
320
+
321
+ return list(attrs)
322
+
323
+
324
+ class StrictABCLoggingMeta(StrictABCMeta, ABCLoggingMeta):
325
+ """Combined metaclass that merges implementation, logging, and ABC functionality.
326
+
327
+ This metaclass combines the features of:
328
+ - ImplementationMeta: Enforces implementation of required attributes
329
+ - LoggingMeta: Adds automatic logging to methods
330
+ - ABCMeta: Provides abstract base class functionality
331
+
332
+ Use this metaclass when you need all three behaviors in a single class.
333
+ """