clsforge 0.1.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.
- clsforge/__init__.py +17 -0
- clsforge/exceptions.py +19 -0
- clsforge/meta.py +45 -0
- clsforge/mixin.py +83 -0
- clsforge/py.typed +0 -0
- clsforge-0.1.0.dist-info/METADATA +23 -0
- clsforge-0.1.0.dist-info/RECORD +8 -0
- clsforge-0.1.0.dist-info/WHEEL +4 -0
clsforge/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"__version__",
|
|
3
|
+
"ClsForgeError",
|
|
4
|
+
"FrozenClassError",
|
|
5
|
+
"InvalidChoiceError",
|
|
6
|
+
"FrozenClassMeta",
|
|
7
|
+
"EnumMixin",
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from importlib import metadata
|
|
11
|
+
|
|
12
|
+
__version__ = metadata.version(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from clsforge.exceptions import ClsForgeError, FrozenClassError, InvalidChoiceError
|
|
16
|
+
from clsforge.meta import FrozenClassMeta
|
|
17
|
+
from clsforge.mixin import EnumMixin
|
clsforge/exceptions.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__all__ = ["ClsForgeError", "FrozenClassError", "InvalidChoiceError"]
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClsForgeError(Exception): ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FrozenClassError(ClsForgeError, TypeError): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InvalidChoiceError(ClsForgeError, ValueError):
|
|
16
|
+
"""Raised when a value is not one of the allowed choices."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, value: T, choices: Sequence[T]) -> None:
|
|
19
|
+
super().__init__(f"invalid value {value!r}: expected one of {choices!r}")
|
clsforge/meta.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
__all__ = ["FrozenClassMeta"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from clsforge import FrozenClassError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FrozenClassMeta(type):
|
|
8
|
+
"""Metaclass for immutable class definitions.
|
|
9
|
+
|
|
10
|
+
Examples
|
|
11
|
+
--------
|
|
12
|
+
>>> class UserTable(metaclass=FrozenClassMeta):
|
|
13
|
+
... NAME = "users"
|
|
14
|
+
... PRIMARY_KEY = "id"
|
|
15
|
+
...
|
|
16
|
+
>>> UserTable.NAME
|
|
17
|
+
'users'
|
|
18
|
+
>>> UserTable.PRIMARY_KEY
|
|
19
|
+
'id'
|
|
20
|
+
>>> UserTable.PRIMARY_KEY = "user_id" # doctest: +SKIP
|
|
21
|
+
Traceback (most recent call last):
|
|
22
|
+
...
|
|
23
|
+
FrozenClassError
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __call__(cls, *args, **kwargs):
|
|
27
|
+
raise FrozenClassError(
|
|
28
|
+
f"Cannot instantiate frozen class '{cls.__name__}'. "
|
|
29
|
+
f"Frozen classes are class-only definitions and do not support instances."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def __setattr__(cls, key, value):
|
|
33
|
+
raise FrozenClassError(
|
|
34
|
+
f"Cannot set field '{key}' to {value!r} on frozen class '{cls.__name__}'. "
|
|
35
|
+
f"Frozen classes are immutable after class creation."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def __delattr__(cls, key):
|
|
39
|
+
raise FrozenClassError(
|
|
40
|
+
f"Cannot delete field '{key}' from frozen class '{cls.__name__}'. "
|
|
41
|
+
f"Frozen classes are immutable after class creation."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def __new__(mcls, name, bases, namespace):
|
|
45
|
+
return super().__new__(mcls, name, bases, dict(namespace))
|
clsforge/mixin.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
__all__ = ["EnumMixin"]
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnumMixin:
|
|
8
|
+
"""Utility mixin for enum classes.
|
|
9
|
+
|
|
10
|
+
Examples
|
|
11
|
+
--------
|
|
12
|
+
>>> from enum import Enum
|
|
13
|
+
>>>
|
|
14
|
+
>>> class Color(EnumMixin, Enum):
|
|
15
|
+
... RED = "red"
|
|
16
|
+
... BLUE = "blue"
|
|
17
|
+
...
|
|
18
|
+
>>> Color.get_count()
|
|
19
|
+
2
|
|
20
|
+
>>> Color.get_names()
|
|
21
|
+
('RED', 'BLUE')
|
|
22
|
+
>>> Color.get_values()
|
|
23
|
+
('red', 'blue')
|
|
24
|
+
>>> Color.get_member_by_name("RED")
|
|
25
|
+
<Color.RED: 'red'>
|
|
26
|
+
>>> Color.get_member_by_value("red")
|
|
27
|
+
<Color.RED: 'red'>
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_members(cls) -> tuple[Enum, ...]:
|
|
32
|
+
"""Return a tuple of all members."""
|
|
33
|
+
enum_cls = cast(type[Enum], cls)
|
|
34
|
+
return tuple(enum_cls)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_count(cls) -> int:
|
|
38
|
+
"""Return the number of enum members."""
|
|
39
|
+
return len(cls.get_members())
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def get_names(cls) -> tuple[str, ...]:
|
|
43
|
+
"""Return a tuple of all member names."""
|
|
44
|
+
return tuple(member.name for member in cls.get_members())
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def get_values(cls) -> tuple[Any, ...]:
|
|
48
|
+
"""Return a tuple of all member values."""
|
|
49
|
+
return tuple(member.value for member in cls.get_members())
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def get_member_by_name(cls, name: str, preview: int = 6) -> Enum:
|
|
53
|
+
"""Return an enum member by its name."""
|
|
54
|
+
enum_cls = cast(type[Enum], cls)
|
|
55
|
+
try:
|
|
56
|
+
return enum_cls[name]
|
|
57
|
+
except KeyError as exc:
|
|
58
|
+
available = cls._format_preview(cls.get_names(), preview)
|
|
59
|
+
raise KeyError(
|
|
60
|
+
f"{cls.__qualname__} has no member named {name!r}. "
|
|
61
|
+
f"Available names ({cls.get_count()}): {available}"
|
|
62
|
+
) from exc
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_member_by_value(cls, value: Any, preview: int = 6) -> Enum:
|
|
66
|
+
"""Return an enum member by its value."""
|
|
67
|
+
enum_cls = cast(type[Enum], cls)
|
|
68
|
+
try:
|
|
69
|
+
return enum_cls(value)
|
|
70
|
+
except ValueError as exc:
|
|
71
|
+
available = cls._format_preview(cls.get_values(), preview)
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"{cls.__qualname__} has no member with value {value!r}. "
|
|
74
|
+
f"Available values ({cls.get_count()}): {available}"
|
|
75
|
+
) from exc
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _format_preview(items: tuple[Any, ...], n: int) -> str:
|
|
79
|
+
"""Return a formatted preview string of items, truncated after n items."""
|
|
80
|
+
preview = ", ".join(map(repr, items[:n]))
|
|
81
|
+
if len(items) > n:
|
|
82
|
+
preview += ", ..."
|
|
83
|
+
return preview
|
clsforge/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: clsforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: oyghen
|
|
6
|
+
Author-email: oyghen <oyghen@duck.com>
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
|
|
12
|
+
# clsforge
|
|
13
|
+
|
|
14
|
+
[](https://github.com/oyghen/clsforge)
|
|
15
|
+
[](https://pypi.org/project/clsforge)
|
|
16
|
+
[](https://github.com/oyghen/clsforge/blob/main/LICENSE)
|
|
17
|
+
[](https://github.com/oyghen/clsforge/actions/workflows/ci.yml)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
pip install clsforge
|
|
23
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
clsforge/__init__.py,sha256=Yyq7vDy94mF6q9SaCj5ikRLNOu1hq3BXIXRt7vJkwC0,383
|
|
2
|
+
clsforge/exceptions.py,sha256=Dm1lrvDipIy9xfMpVX2HRjAqCQL02OoPxKEEtjpKQ0A,514
|
|
3
|
+
clsforge/meta.py,sha256=Zz8NTND7J1CaQqbckjHa7RMOT6IDG_O65tOVyL5un5s,1316
|
|
4
|
+
clsforge/mixin.py,sha256=IIhJriQTdBS46o8Uz3NccNXgPyf8EzgEAsbGpwfRJtI,2572
|
|
5
|
+
clsforge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
clsforge-0.1.0.dist-info/WHEEL,sha256=iCTolw4aw2dP3yfM-EQCGTDsFCXL_ymmbYnBRVH7plA,81
|
|
7
|
+
clsforge-0.1.0.dist-info/METADATA,sha256=S3_CU9jCLaWrt39I863VWTYFn9qViFb7GQZ1i9wR1Do,744
|
|
8
|
+
clsforge-0.1.0.dist-info/RECORD,,
|