more-abc 2.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.
- more_abc-2.0.0/.gitignore +4 -0
- more_abc-2.0.0/License +21 -0
- more_abc-2.0.0/PKG-INFO +312 -0
- more_abc-2.0.0/README.md +288 -0
- more_abc-2.0.0/more_abc/__init__.py +138 -0
- more_abc-2.0.0/more_abc/__init__.pyi +52 -0
- more_abc-2.0.0/more_abc/__main__.py +45 -0
- more_abc-2.0.0/more_abc/__main__.pyi +1 -0
- more_abc-2.0.0/more_abc/abc_dataclasses.py +35 -0
- more_abc-2.0.0/more_abc/abc_dataclasses.pyi +26 -0
- more_abc-2.0.0/more_abc/abc_enum.py +25 -0
- more_abc-2.0.0/more_abc/abc_enum.pyi +8 -0
- more_abc-2.0.0/more_abc/abc_loogging.py +89 -0
- more_abc-2.0.0/more_abc/abc_loogging.pyi +29 -0
- more_abc-2.0.0/more_abc/collections_abc.py +157 -0
- more_abc-2.0.0/more_abc/collections_abc.pyi +63 -0
- more_abc-2.0.0/more_abc/more.py +98 -0
- more_abc-2.0.0/more_abc/more.pyi +39 -0
- more_abc-2.0.0/more_abc/py.typed +0 -0
- more_abc-2.0.0/pyproject.toml +38 -0
more_abc-2.0.0/License
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 Evan Yang <quantbit@126.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
more_abc-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: more-abc
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: An extension of the abc module with many similar features added.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aiwonderland/more_abc
|
|
6
|
+
Project-URL: Repository, https://github.com/aiwonderland/more_abc
|
|
7
|
+
Author-email: Evan Yang <quantbit@126.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: License
|
|
10
|
+
Keywords: abc,abstract,collections,dataclass,enum,logging,mixin
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# more_abc
|
|
26
|
+
|
|
27
|
+
This module is an extension of the `abc` module,
|
|
28
|
+
with many similar features added.
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+

|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install more_abc
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### ABCMixin
|
|
43
|
+
|
|
44
|
+
`ABCMixin` enforces implementation of `initialize`, `validate`, and `to_dict` on subclasses, and provides `is_valid()` and `get_info()` for free.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from more_abc import ABCMixin
|
|
48
|
+
|
|
49
|
+
class User(ABCMixin):
|
|
50
|
+
def __init__(self, name: str, age: int):
|
|
51
|
+
self.name = name
|
|
52
|
+
self.age = age
|
|
53
|
+
self.initialize()
|
|
54
|
+
|
|
55
|
+
def initialize(self):
|
|
56
|
+
self.active = True
|
|
57
|
+
|
|
58
|
+
def validate(self) -> bool:
|
|
59
|
+
return isinstance(self.name, str) and self.age >= 0
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict:
|
|
62
|
+
return {"name": self.name, "age": self.age, "active": self.active}
|
|
63
|
+
|
|
64
|
+
user = User("Alice", 30)
|
|
65
|
+
print(user.is_valid()) # True
|
|
66
|
+
print(user.get_info()) # {'data': {'name': 'Alice', 'age': 30, 'active': True}, 'is_valid': True, 'class_name': 'User'}
|
|
67
|
+
print(repr(user)) # User({'name': 'Alice', 'age': 30, 'active': True})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### ABCException
|
|
71
|
+
|
|
72
|
+
`ABCException` is an abstract base for custom exceptions. Subclasses must implement `_get_message()`.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from more_abc import ABCException
|
|
76
|
+
|
|
77
|
+
class NotFoundError(ABCException):
|
|
78
|
+
def _get_message(self) -> str:
|
|
79
|
+
return f"Class {self.cls!r} was not found."
|
|
80
|
+
|
|
81
|
+
raise NotFoundError(cls="MyClass")
|
|
82
|
+
# NotFoundError: Class 'MyClass' was not found.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### ABCWarning
|
|
86
|
+
|
|
87
|
+
`ABCWarning` works the same way as `ABCException` but for warnings.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import warnings
|
|
91
|
+
from more_abc import ABCWarning
|
|
92
|
+
|
|
93
|
+
class DeprecatedWarning(ABCWarning):
|
|
94
|
+
def _get_message(self) -> str:
|
|
95
|
+
return f"{self.cls!r} is deprecated and will be removed in a future version."
|
|
96
|
+
|
|
97
|
+
warnings.warn(DeprecatedWarning(cls="OldClass"))
|
|
98
|
+
# DeprecatedWarning: 'OldClass' is deprecated and will be removed in a future version.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### AbcEnum
|
|
102
|
+
|
|
103
|
+
`AbcEnum` is an `Enum` base class that supports abstract methods. Use it when you want to define an enum interface that concrete subclasses must implement.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from abc import abstractmethod
|
|
107
|
+
from more_abc import AbcEnum
|
|
108
|
+
|
|
109
|
+
class Direction(AbcEnum):
|
|
110
|
+
NORTH = "N"
|
|
111
|
+
SOUTH = "S"
|
|
112
|
+
EAST = "E"
|
|
113
|
+
WEST = "W"
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def opposite(self) -> "Direction": ...
|
|
117
|
+
|
|
118
|
+
# Direction.NORTH → TypeError: Can't instantiate abstract class Direction …
|
|
119
|
+
|
|
120
|
+
class CardinalDirection(Direction):
|
|
121
|
+
NORTH = "N"
|
|
122
|
+
SOUTH = "S"
|
|
123
|
+
EAST = "E"
|
|
124
|
+
WEST = "W"
|
|
125
|
+
|
|
126
|
+
def opposite(self) -> "CardinalDirection":
|
|
127
|
+
_opp = {"N": "S", "S": "N", "E": "W", "W": "E"}
|
|
128
|
+
return CardinalDirection(_opp[self.value])
|
|
129
|
+
|
|
130
|
+
print(CardinalDirection.NORTH.opposite()) # CardinalDirection.SOUTH
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`ABCEnumMeta` is the underlying combined metaclass (`ABCMeta + EnumMeta`) and is available for advanced use cases.
|
|
134
|
+
|
|
135
|
+
### abc_dataclass
|
|
136
|
+
|
|
137
|
+
`abc_dataclass` is a drop-in replacement for `@dataclass` that automatically gives the class `ABCMeta` as its metaclass, so you can use `@abstractmethod` without manually inheriting from `ABC`.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from abc import abstractmethod
|
|
141
|
+
from more_abc import abc_dataclass
|
|
142
|
+
|
|
143
|
+
@abc_dataclass
|
|
144
|
+
class Shape:
|
|
145
|
+
color: str
|
|
146
|
+
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def area(self) -> float: ...
|
|
149
|
+
|
|
150
|
+
@abc_dataclass(frozen=True)
|
|
151
|
+
class Circle(Shape):
|
|
152
|
+
radius: float
|
|
153
|
+
|
|
154
|
+
def area(self) -> float:
|
|
155
|
+
return 3.14159 * self.radius ** 2
|
|
156
|
+
|
|
157
|
+
c = Circle(color="red", radius=5.0)
|
|
158
|
+
print(c.area()) # 78.53975
|
|
159
|
+
print(c) # Circle(color='red', radius=5.0)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Attempting to instantiate an abstract class raises `TypeError` as expected:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
Shape(color="blue") # TypeError: Can't instantiate abstract class Shape ...
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Type aliases
|
|
169
|
+
|
|
170
|
+
`ABCclassType` and `ABCMetaclassType` mirror the pattern from the `types` module.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from more_abc import ABCclassType, ABCMetaclassType
|
|
174
|
+
from abc import ABC, ABCMeta
|
|
175
|
+
|
|
176
|
+
assert ABCclassType is type(ABC) # True
|
|
177
|
+
assert ABCMetaclassType is type(ABCMeta) # True
|
|
178
|
+
|
|
179
|
+
def accepts_abc_class(cls: ABCclassType):
|
|
180
|
+
print(f"Got an ABC class: {cls}")
|
|
181
|
+
|
|
182
|
+
accepts_abc_class(ABC)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### AbstractLogHandler
|
|
186
|
+
|
|
187
|
+
`AbstractLogHandler` is an abstract base for `logging.Handler`. Subclasses must implement `configure()` and `emit()`. The concrete `close()` method handles thread-safe resource cleanup automatically.
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
import logging
|
|
191
|
+
from more_abc import AbstractLogHandler
|
|
192
|
+
|
|
193
|
+
class PrintHandler(AbstractLogHandler):
|
|
194
|
+
def configure(self, config: dict) -> None:
|
|
195
|
+
self._handler_config.update(config)
|
|
196
|
+
|
|
197
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
198
|
+
print(self.format(record))
|
|
199
|
+
|
|
200
|
+
handler = PrintHandler(level=logging.DEBUG)
|
|
201
|
+
handler.configure({"prefix": "[LOG]"})
|
|
202
|
+
|
|
203
|
+
logger = logging.getLogger("demo")
|
|
204
|
+
logger.addHandler(handler)
|
|
205
|
+
logger.warning("something happened")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### AbstractLogFormatter
|
|
209
|
+
|
|
210
|
+
`AbstractLogFormatter` is an abstract base for `logging.Formatter`. Subclasses must implement `format()`.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
import logging
|
|
214
|
+
from more_abc import AbstractLogFormatter
|
|
215
|
+
|
|
216
|
+
class UpperFormatter(AbstractLogFormatter):
|
|
217
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
218
|
+
return f"[{record.levelname}] {record.getMessage().upper()}"
|
|
219
|
+
|
|
220
|
+
handler = logging.StreamHandler()
|
|
221
|
+
handler.setFormatter(UpperFormatter())
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### AbstractLogFilter
|
|
225
|
+
|
|
226
|
+
`AbstractLogFilter` is an abstract base for `logging.Filter`. Subclasses must implement `filter()`, returning `True` to allow a record through or `False` to discard it.
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
import logging
|
|
230
|
+
from more_abc import AbstractLogFilter
|
|
231
|
+
|
|
232
|
+
class ErrorOnlyFilter(AbstractLogFilter):
|
|
233
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
234
|
+
return record.levelno >= logging.ERROR
|
|
235
|
+
|
|
236
|
+
handler = logging.StreamHandler()
|
|
237
|
+
handler.addFilter(ErrorOnlyFilter())
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### abc re-exports
|
|
241
|
+
|
|
242
|
+
`more_abc` re-exports all public symbols from the standard `abc` module, so you can use it as a single import for everything ABC-related.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
# Instead of:
|
|
246
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
247
|
+
from more_abc import ABCMixin, abc_dataclass
|
|
248
|
+
|
|
249
|
+
# You can do:
|
|
250
|
+
from more_abc import ABC, ABCMeta, abstractmethod, ABCMixin, abc_dataclass
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Available re-exports: `ABC`, `ABCMeta`, `abstractmethod`, `abstractproperty`, `get_cache_token`.
|
|
254
|
+
|
|
255
|
+
### Sortable / Filterable / Transformable
|
|
256
|
+
|
|
257
|
+
`more_abc.collections_abc` provides three ABC families for custom collection types, each following the same `Base* / *Mixin / *` pattern.
|
|
258
|
+
|
|
259
|
+
**Sortable** — in-place sorting via `__sort__`:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from more_abc import Sortable
|
|
263
|
+
|
|
264
|
+
class NumberList(Sortable):
|
|
265
|
+
def __init__(self, data: list):
|
|
266
|
+
self._data = list(data)
|
|
267
|
+
|
|
268
|
+
def __sort__(self, reverse=False):
|
|
269
|
+
self._data.sort(reverse=reverse)
|
|
270
|
+
|
|
271
|
+
def __copy__(self):
|
|
272
|
+
return NumberList(self._data)
|
|
273
|
+
|
|
274
|
+
nl = NumberList([3, 1, 2])
|
|
275
|
+
nl.sort() # in-place: [1, 2, 3]
|
|
276
|
+
asc = nl.sorted(reverse=True) # new copy: [3, 2, 1]
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Filterable** — predicate filtering via `__filter__`:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from more_abc import Filterable
|
|
283
|
+
|
|
284
|
+
class NumberList(Filterable):
|
|
285
|
+
def __init__(self, data: list):
|
|
286
|
+
self._data = list(data)
|
|
287
|
+
|
|
288
|
+
def __filter__(self, predicate):
|
|
289
|
+
return NumberList([x for x in self._data if predicate(x)])
|
|
290
|
+
|
|
291
|
+
nl = NumberList([1, 2, 3, 4, 5])
|
|
292
|
+
evens = nl.filter(lambda x: x % 2 == 0) # [2, 4]
|
|
293
|
+
odds = nl.reject(lambda x: x % 2 == 0) # [1, 3, 5]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Transformable** — element-wise mapping via `__transform__`:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from more_abc import Transformable
|
|
300
|
+
|
|
301
|
+
class NumberList(Transformable):
|
|
302
|
+
def __init__(self, data: list):
|
|
303
|
+
self._data = list(data)
|
|
304
|
+
|
|
305
|
+
def __transform__(self, func):
|
|
306
|
+
return NumberList([func(x) for x in self._data])
|
|
307
|
+
|
|
308
|
+
nl = NumberList([1, 2, 3])
|
|
309
|
+
doubled = nl.map(lambda x: x * 2) # [2, 4, 6]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`BaseSortable`, `SortableMixin`, `BaseFilterable`, `FilterableMixin`, `BaseTransformable`, and `TransformableMixin` are also exported for advanced composition.
|
more_abc-2.0.0/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# more_abc
|
|
2
|
+
|
|
3
|
+
This module is an extension of the `abc` module,
|
|
4
|
+
with many similar features added.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install more_abc
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### ABCMixin
|
|
19
|
+
|
|
20
|
+
`ABCMixin` enforces implementation of `initialize`, `validate`, and `to_dict` on subclasses, and provides `is_valid()` and `get_info()` for free.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from more_abc import ABCMixin
|
|
24
|
+
|
|
25
|
+
class User(ABCMixin):
|
|
26
|
+
def __init__(self, name: str, age: int):
|
|
27
|
+
self.name = name
|
|
28
|
+
self.age = age
|
|
29
|
+
self.initialize()
|
|
30
|
+
|
|
31
|
+
def initialize(self):
|
|
32
|
+
self.active = True
|
|
33
|
+
|
|
34
|
+
def validate(self) -> bool:
|
|
35
|
+
return isinstance(self.name, str) and self.age >= 0
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict:
|
|
38
|
+
return {"name": self.name, "age": self.age, "active": self.active}
|
|
39
|
+
|
|
40
|
+
user = User("Alice", 30)
|
|
41
|
+
print(user.is_valid()) # True
|
|
42
|
+
print(user.get_info()) # {'data': {'name': 'Alice', 'age': 30, 'active': True}, 'is_valid': True, 'class_name': 'User'}
|
|
43
|
+
print(repr(user)) # User({'name': 'Alice', 'age': 30, 'active': True})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### ABCException
|
|
47
|
+
|
|
48
|
+
`ABCException` is an abstract base for custom exceptions. Subclasses must implement `_get_message()`.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from more_abc import ABCException
|
|
52
|
+
|
|
53
|
+
class NotFoundError(ABCException):
|
|
54
|
+
def _get_message(self) -> str:
|
|
55
|
+
return f"Class {self.cls!r} was not found."
|
|
56
|
+
|
|
57
|
+
raise NotFoundError(cls="MyClass")
|
|
58
|
+
# NotFoundError: Class 'MyClass' was not found.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### ABCWarning
|
|
62
|
+
|
|
63
|
+
`ABCWarning` works the same way as `ABCException` but for warnings.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import warnings
|
|
67
|
+
from more_abc import ABCWarning
|
|
68
|
+
|
|
69
|
+
class DeprecatedWarning(ABCWarning):
|
|
70
|
+
def _get_message(self) -> str:
|
|
71
|
+
return f"{self.cls!r} is deprecated and will be removed in a future version."
|
|
72
|
+
|
|
73
|
+
warnings.warn(DeprecatedWarning(cls="OldClass"))
|
|
74
|
+
# DeprecatedWarning: 'OldClass' is deprecated and will be removed in a future version.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### AbcEnum
|
|
78
|
+
|
|
79
|
+
`AbcEnum` is an `Enum` base class that supports abstract methods. Use it when you want to define an enum interface that concrete subclasses must implement.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from abc import abstractmethod
|
|
83
|
+
from more_abc import AbcEnum
|
|
84
|
+
|
|
85
|
+
class Direction(AbcEnum):
|
|
86
|
+
NORTH = "N"
|
|
87
|
+
SOUTH = "S"
|
|
88
|
+
EAST = "E"
|
|
89
|
+
WEST = "W"
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def opposite(self) -> "Direction": ...
|
|
93
|
+
|
|
94
|
+
# Direction.NORTH → TypeError: Can't instantiate abstract class Direction …
|
|
95
|
+
|
|
96
|
+
class CardinalDirection(Direction):
|
|
97
|
+
NORTH = "N"
|
|
98
|
+
SOUTH = "S"
|
|
99
|
+
EAST = "E"
|
|
100
|
+
WEST = "W"
|
|
101
|
+
|
|
102
|
+
def opposite(self) -> "CardinalDirection":
|
|
103
|
+
_opp = {"N": "S", "S": "N", "E": "W", "W": "E"}
|
|
104
|
+
return CardinalDirection(_opp[self.value])
|
|
105
|
+
|
|
106
|
+
print(CardinalDirection.NORTH.opposite()) # CardinalDirection.SOUTH
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`ABCEnumMeta` is the underlying combined metaclass (`ABCMeta + EnumMeta`) and is available for advanced use cases.
|
|
110
|
+
|
|
111
|
+
### abc_dataclass
|
|
112
|
+
|
|
113
|
+
`abc_dataclass` is a drop-in replacement for `@dataclass` that automatically gives the class `ABCMeta` as its metaclass, so you can use `@abstractmethod` without manually inheriting from `ABC`.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from abc import abstractmethod
|
|
117
|
+
from more_abc import abc_dataclass
|
|
118
|
+
|
|
119
|
+
@abc_dataclass
|
|
120
|
+
class Shape:
|
|
121
|
+
color: str
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def area(self) -> float: ...
|
|
125
|
+
|
|
126
|
+
@abc_dataclass(frozen=True)
|
|
127
|
+
class Circle(Shape):
|
|
128
|
+
radius: float
|
|
129
|
+
|
|
130
|
+
def area(self) -> float:
|
|
131
|
+
return 3.14159 * self.radius ** 2
|
|
132
|
+
|
|
133
|
+
c = Circle(color="red", radius=5.0)
|
|
134
|
+
print(c.area()) # 78.53975
|
|
135
|
+
print(c) # Circle(color='red', radius=5.0)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Attempting to instantiate an abstract class raises `TypeError` as expected:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
Shape(color="blue") # TypeError: Can't instantiate abstract class Shape ...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Type aliases
|
|
145
|
+
|
|
146
|
+
`ABCclassType` and `ABCMetaclassType` mirror the pattern from the `types` module.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from more_abc import ABCclassType, ABCMetaclassType
|
|
150
|
+
from abc import ABC, ABCMeta
|
|
151
|
+
|
|
152
|
+
assert ABCclassType is type(ABC) # True
|
|
153
|
+
assert ABCMetaclassType is type(ABCMeta) # True
|
|
154
|
+
|
|
155
|
+
def accepts_abc_class(cls: ABCclassType):
|
|
156
|
+
print(f"Got an ABC class: {cls}")
|
|
157
|
+
|
|
158
|
+
accepts_abc_class(ABC)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### AbstractLogHandler
|
|
162
|
+
|
|
163
|
+
`AbstractLogHandler` is an abstract base for `logging.Handler`. Subclasses must implement `configure()` and `emit()`. The concrete `close()` method handles thread-safe resource cleanup automatically.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
import logging
|
|
167
|
+
from more_abc import AbstractLogHandler
|
|
168
|
+
|
|
169
|
+
class PrintHandler(AbstractLogHandler):
|
|
170
|
+
def configure(self, config: dict) -> None:
|
|
171
|
+
self._handler_config.update(config)
|
|
172
|
+
|
|
173
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
174
|
+
print(self.format(record))
|
|
175
|
+
|
|
176
|
+
handler = PrintHandler(level=logging.DEBUG)
|
|
177
|
+
handler.configure({"prefix": "[LOG]"})
|
|
178
|
+
|
|
179
|
+
logger = logging.getLogger("demo")
|
|
180
|
+
logger.addHandler(handler)
|
|
181
|
+
logger.warning("something happened")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### AbstractLogFormatter
|
|
185
|
+
|
|
186
|
+
`AbstractLogFormatter` is an abstract base for `logging.Formatter`. Subclasses must implement `format()`.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
import logging
|
|
190
|
+
from more_abc import AbstractLogFormatter
|
|
191
|
+
|
|
192
|
+
class UpperFormatter(AbstractLogFormatter):
|
|
193
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
194
|
+
return f"[{record.levelname}] {record.getMessage().upper()}"
|
|
195
|
+
|
|
196
|
+
handler = logging.StreamHandler()
|
|
197
|
+
handler.setFormatter(UpperFormatter())
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### AbstractLogFilter
|
|
201
|
+
|
|
202
|
+
`AbstractLogFilter` is an abstract base for `logging.Filter`. Subclasses must implement `filter()`, returning `True` to allow a record through or `False` to discard it.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import logging
|
|
206
|
+
from more_abc import AbstractLogFilter
|
|
207
|
+
|
|
208
|
+
class ErrorOnlyFilter(AbstractLogFilter):
|
|
209
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
210
|
+
return record.levelno >= logging.ERROR
|
|
211
|
+
|
|
212
|
+
handler = logging.StreamHandler()
|
|
213
|
+
handler.addFilter(ErrorOnlyFilter())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### abc re-exports
|
|
217
|
+
|
|
218
|
+
`more_abc` re-exports all public symbols from the standard `abc` module, so you can use it as a single import for everything ABC-related.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Instead of:
|
|
222
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
223
|
+
from more_abc import ABCMixin, abc_dataclass
|
|
224
|
+
|
|
225
|
+
# You can do:
|
|
226
|
+
from more_abc import ABC, ABCMeta, abstractmethod, ABCMixin, abc_dataclass
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Available re-exports: `ABC`, `ABCMeta`, `abstractmethod`, `abstractproperty`, `get_cache_token`.
|
|
230
|
+
|
|
231
|
+
### Sortable / Filterable / Transformable
|
|
232
|
+
|
|
233
|
+
`more_abc.collections_abc` provides three ABC families for custom collection types, each following the same `Base* / *Mixin / *` pattern.
|
|
234
|
+
|
|
235
|
+
**Sortable** — in-place sorting via `__sort__`:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from more_abc import Sortable
|
|
239
|
+
|
|
240
|
+
class NumberList(Sortable):
|
|
241
|
+
def __init__(self, data: list):
|
|
242
|
+
self._data = list(data)
|
|
243
|
+
|
|
244
|
+
def __sort__(self, reverse=False):
|
|
245
|
+
self._data.sort(reverse=reverse)
|
|
246
|
+
|
|
247
|
+
def __copy__(self):
|
|
248
|
+
return NumberList(self._data)
|
|
249
|
+
|
|
250
|
+
nl = NumberList([3, 1, 2])
|
|
251
|
+
nl.sort() # in-place: [1, 2, 3]
|
|
252
|
+
asc = nl.sorted(reverse=True) # new copy: [3, 2, 1]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Filterable** — predicate filtering via `__filter__`:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from more_abc import Filterable
|
|
259
|
+
|
|
260
|
+
class NumberList(Filterable):
|
|
261
|
+
def __init__(self, data: list):
|
|
262
|
+
self._data = list(data)
|
|
263
|
+
|
|
264
|
+
def __filter__(self, predicate):
|
|
265
|
+
return NumberList([x for x in self._data if predicate(x)])
|
|
266
|
+
|
|
267
|
+
nl = NumberList([1, 2, 3, 4, 5])
|
|
268
|
+
evens = nl.filter(lambda x: x % 2 == 0) # [2, 4]
|
|
269
|
+
odds = nl.reject(lambda x: x % 2 == 0) # [1, 3, 5]
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Transformable** — element-wise mapping via `__transform__`:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from more_abc import Transformable
|
|
276
|
+
|
|
277
|
+
class NumberList(Transformable):
|
|
278
|
+
def __init__(self, data: list):
|
|
279
|
+
self._data = list(data)
|
|
280
|
+
|
|
281
|
+
def __transform__(self, func):
|
|
282
|
+
return NumberList([func(x) for x in self._data])
|
|
283
|
+
|
|
284
|
+
nl = NumberList([1, 2, 3])
|
|
285
|
+
doubled = nl.map(lambda x: x * 2) # [2, 4, 6]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`BaseSortable`, `SortableMixin`, `BaseFilterable`, `FilterableMixin`, `BaseTransformable`, and `TransformableMixin` are also exported for advanced composition.
|