clsforge 0.1.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.
@@ -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
+ [![GitHub](https://img.shields.io/badge/github-code-blue?label=GitHub)](https://github.com/oyghen/clsforge)
15
+ [![PyPI](https://img.shields.io/pypi/v/clsforge?label=PyPI)](https://pypi.org/project/clsforge)
16
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/oyghen/clsforge/blob/main/LICENSE)
17
+ [![CI](https://github.com/oyghen/clsforge/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/oyghen/clsforge/actions/workflows/ci.yml)
18
+
19
+ </div>
20
+
21
+ ```shell
22
+ pip install clsforge
23
+ ```
@@ -0,0 +1,14 @@
1
+ <div align="center">
2
+
3
+ # clsforge
4
+
5
+ [![GitHub](https://img.shields.io/badge/github-code-blue?label=GitHub)](https://github.com/oyghen/clsforge)
6
+ [![PyPI](https://img.shields.io/pypi/v/clsforge?label=PyPI)](https://pypi.org/project/clsforge)
7
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/oyghen/clsforge/blob/main/LICENSE)
8
+ [![CI](https://github.com/oyghen/clsforge/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/oyghen/clsforge/actions/workflows/ci.yml)
9
+
10
+ </div>
11
+
12
+ ```shell
13
+ pip install clsforge
14
+ ```
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "clsforge"
3
+ version = "0.1.0"
4
+ description = ""
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "oyghen", email = "oyghen@duck.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = []
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.11.9,<0.12.0"]
14
+ build-backend = "uv_build"
15
+
16
+ [dependency-groups]
17
+ dev = [
18
+ "pre-commit>=4.6.0",
19
+ "pytest>=9.0.3",
20
+ "ruff>=0.15.12",
21
+ ]
@@ -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
@@ -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}")
@@ -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))
@@ -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
File without changes