ml3macro-utils 0.0.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.
- ml3macro/utils/__init__.py +1 -0
- ml3macro/utils/additive_mapping.py +125 -0
- ml3macro/utils/base_enum.py +4 -0
- ml3macro/utils/decorators/__init__.py +0 -0
- ml3macro/utils/decorators/layered_abstract_class.py +66 -0
- ml3macro/utils/identifier.py +11 -0
- ml3macro/utils/iterables.py +12 -0
- ml3macro/utils/optional.py +96 -0
- ml3macro/utils/py.typed +0 -0
- ml3macro/utils/reference.py +77 -0
- ml3macro/utils/strings.py +50 -0
- ml3macro_utils-0.0.0.dist-info/METADATA +87 -0
- ml3macro_utils-0.0.0.dist-info/RECORD +15 -0
- ml3macro_utils-0.0.0.dist-info/WHEEL +5 -0
- ml3macro_utils-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ml3macro Utils - Common utilities and shared code for ml3macro projects."""
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""This is like a frozen (immutable) dict, except that you can add new keys to it.
|
|
2
|
+
Not allowed: updating/deleting existing keys or their values."""
|
|
3
|
+
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Generic,
|
|
7
|
+
Hashable,
|
|
8
|
+
ItemsView,
|
|
9
|
+
Iterator,
|
|
10
|
+
KeysView,
|
|
11
|
+
Mapping,
|
|
12
|
+
Self,
|
|
13
|
+
TypeVar,
|
|
14
|
+
ValuesView,
|
|
15
|
+
overload,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
KeyType = TypeVar("KeyType", bound=Hashable) # Allow any hashable type as key
|
|
19
|
+
ValueType = TypeVar("ValueType")
|
|
20
|
+
_T = TypeVar("_T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AdditiveMapping(Generic[KeyType, ValueType], Mapping[KeyType, ValueType]):
|
|
24
|
+
"""An immutable mapping that allows adding new keys but not modifying existing ones.
|
|
25
|
+
|
|
26
|
+
Once created, existing keys cannot be updated or deleted, but new keys can be
|
|
27
|
+
added using the `add()` method.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
__map: dict[KeyType, ValueType]
|
|
31
|
+
|
|
32
|
+
def __init__(self, **kwargs: ValueType) -> None:
|
|
33
|
+
self.__map = dict(kwargs) # type: ignore[assignment]
|
|
34
|
+
|
|
35
|
+
def __getitem__(self, key: KeyType, /) -> ValueType:
|
|
36
|
+
return self.__map[key]
|
|
37
|
+
|
|
38
|
+
def __len__(self) -> int:
|
|
39
|
+
return len(self.__map)
|
|
40
|
+
|
|
41
|
+
def __iter__(self) -> Iterator[KeyType]:
|
|
42
|
+
return iter(self.__map)
|
|
43
|
+
|
|
44
|
+
@overload
|
|
45
|
+
def get(self, __key: KeyType) -> ValueType | None: ...
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def get(self, __key: KeyType, __default: ValueType) -> ValueType: ...
|
|
49
|
+
|
|
50
|
+
@overload
|
|
51
|
+
def get(self, __key: KeyType, __default: _T) -> ValueType | _T: ...
|
|
52
|
+
|
|
53
|
+
def get(self, __key: KeyType, __default: Any = None) -> ValueType | Any:
|
|
54
|
+
"""Return the value for key if key is in the mapping, else default."""
|
|
55
|
+
return self.__map.get(__key, __default)
|
|
56
|
+
|
|
57
|
+
def add(self, remove_none_values: bool = True, **kwargs: ValueType) -> Self:
|
|
58
|
+
"""Add new key-value pairs to the mapping.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
remove_none_values: If True, skip any keys with None values
|
|
62
|
+
**kwargs: Key-value pairs to add
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Self for method chaining
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
KeyError: If any key already exists
|
|
69
|
+
"""
|
|
70
|
+
self.append(kwargs, remove_none_values=remove_none_values)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def append(
|
|
74
|
+
self,
|
|
75
|
+
additional_mappings: Mapping[KeyType, ValueType] | dict[Any, ValueType],
|
|
76
|
+
/,
|
|
77
|
+
*,
|
|
78
|
+
remove_none_values: bool = True,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Append new key-value pairs from another mapping.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
additional_mappings: Mapping containing key-value pairs to add
|
|
84
|
+
remove_none_values: If True, skip any keys with None values
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
KeyError: If any key already exists
|
|
88
|
+
"""
|
|
89
|
+
kvs_to_add = (
|
|
90
|
+
additional_mappings
|
|
91
|
+
if not remove_none_values
|
|
92
|
+
else {k: v for k, v in additional_mappings.items() if v is not None}
|
|
93
|
+
)
|
|
94
|
+
keys_to_add = kvs_to_add.keys()
|
|
95
|
+
conflicting_keys = [k for k in keys_to_add if k in self.__map]
|
|
96
|
+
if conflicting_keys:
|
|
97
|
+
raise KeyError(
|
|
98
|
+
f"{type(self).__qualname__} cannot update existing keys (but new keys can be added). "
|
|
99
|
+
f"Conflicting keys: {', '.join(str(k) for k in conflicting_keys)}"
|
|
100
|
+
)
|
|
101
|
+
self.__map.update(kvs_to_add)
|
|
102
|
+
|
|
103
|
+
def __setitem__(self, key: KeyType, value: ValueType) -> None:
|
|
104
|
+
"""Prevent direct item assignment."""
|
|
105
|
+
raise TypeError(
|
|
106
|
+
f"'{type(self).__name__}' object does not support item assignment"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def __delitem__(self, key: KeyType) -> None:
|
|
110
|
+
"""Prevent item deletion."""
|
|
111
|
+
raise TypeError(
|
|
112
|
+
f"'{type(self).__name__}' object does not support item deletion"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def keys(self) -> KeysView[KeyType]:
|
|
116
|
+
"""Return a list of keys in the mapping."""
|
|
117
|
+
return KeysView(self.__map)
|
|
118
|
+
|
|
119
|
+
def values(self) -> ValuesView[ValueType]:
|
|
120
|
+
"""Return a list of values in the mapping."""
|
|
121
|
+
return ValuesView(self.__map)
|
|
122
|
+
|
|
123
|
+
def items(self) -> ItemsView[KeyType, ValueType]:
|
|
124
|
+
"""Return a list of (key, value) pairs in the mapping."""
|
|
125
|
+
return ItemsView(self.__map)
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Any, Callable, TypeVar
|
|
2
|
+
|
|
3
|
+
T = TypeVar("T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def layered_abstract_class(base_class: type[T]) -> type[T]:
|
|
7
|
+
"""
|
|
8
|
+
Decorator to enforce that concrete classes cannot inherit directly from the base class.
|
|
9
|
+
They must inherit from an intermediate abstract class.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
base_class: The base class that should only be inherited by abstract classes
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
The same base class with the enforcement logic added
|
|
16
|
+
"""
|
|
17
|
+
original_init_subclass: Callable[..., None] | None = getattr(
|
|
18
|
+
base_class, "__init_subclass__", None
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def decorator_error(s: str, /) -> TypeError:
|
|
22
|
+
return TypeError(f"[@{layered_abstract_class.__name__}]: {s}")
|
|
23
|
+
|
|
24
|
+
def __init_subclass__(cls: type, **kwargs: Any) -> None:
|
|
25
|
+
# Call original __init_subclass__ if it exists
|
|
26
|
+
if original_init_subclass:
|
|
27
|
+
original_init_subclass(**kwargs)
|
|
28
|
+
|
|
29
|
+
# Check if base_class is a direct parent
|
|
30
|
+
if base_class in cls.__bases__:
|
|
31
|
+
# Check if the class is abstract by looking for any unimplemented abstract methods
|
|
32
|
+
# in the entire inheritance chain (excluding object)
|
|
33
|
+
has_unimplemented_abstract_methods = False
|
|
34
|
+
|
|
35
|
+
# Get all abstract methods from the inheritance chain
|
|
36
|
+
all_abstract_methods = set()
|
|
37
|
+
for base in cls.__mro__[1:]: # Skip the class itself
|
|
38
|
+
if base is object:
|
|
39
|
+
continue
|
|
40
|
+
if hasattr(base, "__abstractmethods__"):
|
|
41
|
+
all_abstract_methods.update(base.__abstractmethods__)
|
|
42
|
+
|
|
43
|
+
# Check if any of these abstract methods are still unimplemented in cls
|
|
44
|
+
if all_abstract_methods:
|
|
45
|
+
# Get the methods that cls has implemented
|
|
46
|
+
implemented_methods = set()
|
|
47
|
+
for name in all_abstract_methods:
|
|
48
|
+
if name in cls.__dict__ and not getattr(
|
|
49
|
+
cls.__dict__[name], "__isabstractmethod__", False
|
|
50
|
+
):
|
|
51
|
+
implemented_methods.add(name)
|
|
52
|
+
|
|
53
|
+
# If there are still unimplemented abstract methods, the class is abstract
|
|
54
|
+
if all_abstract_methods - implemented_methods:
|
|
55
|
+
has_unimplemented_abstract_methods = True
|
|
56
|
+
|
|
57
|
+
if not has_unimplemented_abstract_methods:
|
|
58
|
+
raise decorator_error(
|
|
59
|
+
f"{cls.__name__} cannot inherit directly from {base_class.__name__}. "
|
|
60
|
+
"It must inherit from an intermediate abstract class."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Add the __init_subclass__ method to the base class
|
|
64
|
+
# We use setattr to avoid mypy's method assignment complaints
|
|
65
|
+
setattr(base_class, "__init_subclass__", classmethod(__init_subclass__))
|
|
66
|
+
return base_class
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from itertools import tee
|
|
2
|
+
from typing import Callable, Iterable, TypeVar
|
|
3
|
+
|
|
4
|
+
Item = TypeVar("Item")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# TODO: WRITE UNIT TESTS
|
|
8
|
+
def partition(
|
|
9
|
+
iterable: Iterable[Item], /, *, condition: Callable[[Item], bool]
|
|
10
|
+
) -> tuple[Iterable[Item], Iterable[Item]]:
|
|
11
|
+
t1, t2 = tee(iterable)
|
|
12
|
+
return filter(condition, t1), filter(lambda x: not condition(x), t2)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Optional value handling utilities with clear callable behavior documentation.
|
|
2
|
+
|
|
3
|
+
Callable Handling Guidelines:
|
|
4
|
+
1. PREFER returning values from getters when possible
|
|
5
|
+
2. ONLY return callables when you specifically want deferred execution
|
|
6
|
+
3. ALWAYS document when getters return callables
|
|
7
|
+
4. CONSIDER using type hints to clarify callable intent
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Callable, Optional, TypeVar
|
|
11
|
+
|
|
12
|
+
ValueType = TypeVar("ValueType")
|
|
13
|
+
Object = TypeVar("Object")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_default(
|
|
17
|
+
default: ValueType | Callable[[], ValueType],
|
|
18
|
+
) -> ValueType:
|
|
19
|
+
"""Get the default value, calling callable defaults if needed."""
|
|
20
|
+
if callable(default):
|
|
21
|
+
return default()
|
|
22
|
+
return default
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_or_else(
|
|
26
|
+
value: Optional[ValueType],
|
|
27
|
+
default: ValueType | Callable[[], ValueType],
|
|
28
|
+
) -> ValueType:
|
|
29
|
+
"""Return the value if not None, otherwise return the default.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value: The value to check (returns this if not None)
|
|
33
|
+
default: Value or callable to return if value is None
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The value if not None, otherwise the default (calling callable defaults)
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> get_or_else("value", "default")
|
|
40
|
+
"value"
|
|
41
|
+
>>> get_or_else(None, lambda: "computed")
|
|
42
|
+
"computed"
|
|
43
|
+
"""
|
|
44
|
+
if value is None:
|
|
45
|
+
return _get_default(default)
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def obj_get_or_else(
|
|
50
|
+
obj: Optional[Object],
|
|
51
|
+
get_value: Callable[[Object], Optional[ValueType]],
|
|
52
|
+
default: ValueType | Callable[[], ValueType],
|
|
53
|
+
) -> ValueType:
|
|
54
|
+
"""Retrieve a value from an object or return a default.
|
|
55
|
+
|
|
56
|
+
Important Behavior Notes:
|
|
57
|
+
1. Callable Defaults: If default is callable, it's called when needed
|
|
58
|
+
2. Getter Results: The getter's return value is returned AS-IS
|
|
59
|
+
- If getter returns a callable, YOU must decide whether to call it
|
|
60
|
+
- This function does NOT automatically call callables returned by getters
|
|
61
|
+
3. None Handling: Returns default if obj is None OR getter returns None
|
|
62
|
+
|
|
63
|
+
Example Patterns:
|
|
64
|
+
Pattern A - Getter returns values (most common):
|
|
65
|
+
>>> result = obj_get_or_else(person, lambda p: p.name, "Unknown")
|
|
66
|
+
>>> # result is a string, no calling needed
|
|
67
|
+
|
|
68
|
+
Pattern B - Getter returns callables (advanced):
|
|
69
|
+
>>> result = obj_get_or_else(config, lambda c: c.loader, default_loader)
|
|
70
|
+
>>> # result might be callable - check before using!
|
|
71
|
+
>>> final_value = result() if callable(result) else result
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
obj: The object to extract value from (None returns default)
|
|
75
|
+
get_value: Function that extracts value from obj (returns None uses default)
|
|
76
|
+
default: Value or callable to return when obj/value is None
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The extracted value, or default value/callable result
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> class Person:
|
|
83
|
+
... def __init__(self, name):
|
|
84
|
+
... self.name = name
|
|
85
|
+
>>> person = Person("Alice")
|
|
86
|
+
>>> obj_get_or_else(person, lambda p: p.name, "Unknown")
|
|
87
|
+
"Alice"
|
|
88
|
+
>>> obj_get_or_else(None, lambda p: p.name, "Unknown")
|
|
89
|
+
"Unknown"
|
|
90
|
+
"""
|
|
91
|
+
if obj is None:
|
|
92
|
+
return _get_default(default)
|
|
93
|
+
value = get_value(obj)
|
|
94
|
+
if value is None:
|
|
95
|
+
return _get_default(default)
|
|
96
|
+
return value
|
ml3macro/utils/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Utility functions for working with Python object references"""
|
|
2
|
+
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def fully_qualified_name(obj: Any) -> str:
|
|
8
|
+
"""Get the fully qualified name of a Python object (module.class)."""
|
|
9
|
+
|
|
10
|
+
# ------------------------------------------------------------------
|
|
11
|
+
# Partial functions (explicitly not supported)
|
|
12
|
+
# ------------------------------------------------------------------
|
|
13
|
+
if isinstance(obj, partial):
|
|
14
|
+
raise ValueError("Partial functions are not supported")
|
|
15
|
+
|
|
16
|
+
# ------------------------------------------------------------------
|
|
17
|
+
# Properties
|
|
18
|
+
# ------------------------------------------------------------------
|
|
19
|
+
if isinstance(obj, property):
|
|
20
|
+
# The getter (`fget`) carries the real qualified name.
|
|
21
|
+
fget = getattr(obj, "fget", None)
|
|
22
|
+
if (
|
|
23
|
+
fget is not None
|
|
24
|
+
and hasattr(fget, "__module__")
|
|
25
|
+
and hasattr(fget, "__qualname__")
|
|
26
|
+
):
|
|
27
|
+
return f"{fget.__module__}.{fget.__qualname__}"
|
|
28
|
+
raise ValueError(f"Cannot determine FQN for property {obj}")
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
# Modules
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
if hasattr(obj, "__name__") and not hasattr(obj, "__qualname__"):
|
|
34
|
+
# Built‑in modules (e.g. sys, os, math) – just the name
|
|
35
|
+
if getattr(obj, "__name__", "").startswith(("builtins", "sys", "os", "math")):
|
|
36
|
+
return str(obj.__name__)
|
|
37
|
+
return str(obj.__name__)
|
|
38
|
+
|
|
39
|
+
# ------------------------------------------------------------------
|
|
40
|
+
# Primitive values – not supported
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
if obj is None or isinstance(obj, (int, float, str, bool, list, dict, tuple, set)):
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Cannot get FQN for primitive value: {obj}. "
|
|
45
|
+
f"Pass the class attribute descriptor instead, e.g., Parent.attr1"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
# Lambda functions
|
|
50
|
+
# ------------------------------------------------------------------
|
|
51
|
+
if getattr(obj, "__name__", None) == "<lambda>":
|
|
52
|
+
raise ValueError("Cannot get FQN for lambda function")
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Bound methods – reject instance methods, allow class/static methods
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
if hasattr(obj, "__self__"):
|
|
58
|
+
if getattr(obj, "__module__", None) == "builtins":
|
|
59
|
+
# built‑in function – keep going
|
|
60
|
+
pass
|
|
61
|
+
elif obj.__self__ is not None and not isinstance(obj.__self__, type):
|
|
62
|
+
raise ValueError("Cannot get FQN for bound method")
|
|
63
|
+
# staticmethod / classmethod – continue
|
|
64
|
+
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
# Objects without __module__ / __qualname__
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
if not hasattr(obj, "__module__") or not hasattr(obj, "__qualname__"):
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Object {obj} does not have __module__ and __qualname__ attributes"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
module = obj.__module__
|
|
74
|
+
qualname = obj.__qualname__
|
|
75
|
+
if not module or not qualname:
|
|
76
|
+
raise ValueError(f"Object {obj} has empty __module__ or __qualname__")
|
|
77
|
+
return f"{module}.{qualname}"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def strip_margin(s: str, /) -> str:
|
|
6
|
+
if not isinstance(s, str):
|
|
7
|
+
raise TypeError("Input must be a string")
|
|
8
|
+
|
|
9
|
+
lines: Tuple[str, ...] = tuple(s.splitlines())
|
|
10
|
+
|
|
11
|
+
# Remove empty leading lines (immutable approach)
|
|
12
|
+
first_non_empty_idx = next(
|
|
13
|
+
(i for i, line in enumerate(lines) if line.strip()), len(lines)
|
|
14
|
+
)
|
|
15
|
+
trimmed_lines: Tuple[str, ...] = lines[first_non_empty_idx:]
|
|
16
|
+
|
|
17
|
+
# Remove empty trailing lines (immutable approach)
|
|
18
|
+
last_non_empty_idx = next(
|
|
19
|
+
(i for i, line in enumerate(reversed(trimmed_lines)) if line.strip()),
|
|
20
|
+
len(trimmed_lines),
|
|
21
|
+
)
|
|
22
|
+
trimmed_lines = trimmed_lines[: len(trimmed_lines) - last_non_empty_idx]
|
|
23
|
+
|
|
24
|
+
if not trimmed_lines:
|
|
25
|
+
return ""
|
|
26
|
+
|
|
27
|
+
# Calculate margin length from first line
|
|
28
|
+
first_line: str = trimmed_lines[0]
|
|
29
|
+
margin_length: int = len(first_line) - len(first_line.lstrip())
|
|
30
|
+
|
|
31
|
+
# Verify all lines have consistent margin
|
|
32
|
+
for line in trimmed_lines:
|
|
33
|
+
if not line:
|
|
34
|
+
continue # Skip empty lines
|
|
35
|
+
if len(line) < margin_length:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Line '{line}' is shorter than margin length {margin_length}"
|
|
38
|
+
)
|
|
39
|
+
if line[:margin_length].strip():
|
|
40
|
+
raise ValueError(f"Line '{line}' has non-whitespace in margin area")
|
|
41
|
+
|
|
42
|
+
# Process all lines using reduce
|
|
43
|
+
def process_line(acc: Tuple[str, ...], line: str) -> Tuple[str, ...]:
|
|
44
|
+
return (*acc, line[margin_length:]) if line else (*acc, line)
|
|
45
|
+
|
|
46
|
+
result_lines: Tuple[str, ...] = reduce(
|
|
47
|
+
process_line, trimmed_lines[1:], (trimmed_lines[0][margin_length:],)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return "\n".join(result_lines)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ml3macro-utils
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: ml3macro common utils - immutable data structures, enhanced enums, decorators, and utilities
|
|
5
|
+
Author-email: macro <your-email@example.com>
|
|
6
|
+
Maintainer-email: macro <your-email@example.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://codeberg.org/gxyflow/ml3macro
|
|
9
|
+
Project-URL: Repository, https://codeberg.org/gxyflow/ml3macro
|
|
10
|
+
Project-URL: Issues, https://codeberg.org/gxyflow/ml3macro/issues
|
|
11
|
+
Keywords: ml3macro,utils,immutable,enum,decorators
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: <3.15,>=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: frozendict>=2.3.0
|
|
25
|
+
Requires-Dist: strenum>=0.4.0
|
|
26
|
+
Requires-Dist: overrides>=7.0.0
|
|
27
|
+
Requires-Dist: python-ulid>=1.0.0
|
|
28
|
+
|
|
29
|
+
# ml3macro utils
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/ml3macro-utils/)
|
|
32
|
+
[](https://pypi.org/project/ml3macro-utils/)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Common utilities and shared code for ml3macro projects.
|
|
37
|
+
|
|
38
|
+
## Status: In Development
|
|
39
|
+
|
|
40
|
+
This is a new package with general-purpose helper functions/classes/etc for use in my other projects.
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
This package contains reusable utilities and helpers used across ml3macro projects:
|
|
45
|
+
|
|
46
|
+
- **Data Structures**: Immutable/constrained collections like `AdditiveMapping`
|
|
47
|
+
- **Enums**: Enhanced enumeration base classes
|
|
48
|
+
- **Decorators**: Class structure enforcement decorators
|
|
49
|
+
- **Identifiers**: Unique ID generation utilities
|
|
50
|
+
- **Functional Helpers**: Iterables processing, optional value handling
|
|
51
|
+
- **Reflection**: Object introspection utilities
|
|
52
|
+
- **Text Processing**: String manipulation helpers
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install ml3macro-utils
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Import specific utilities as needed:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from ml3macro.utils.additive_mapping import AdditiveMapping
|
|
66
|
+
from ml3macro.utils.identifier import short_id
|
|
67
|
+
from ml3macro.utils.optional import get_or_else
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or import from the main package:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from ml3macro.utils import AdditiveMapping, short_id, get_or_else
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Python 3.11+
|
|
79
|
+
- frozendict
|
|
80
|
+
- strenum
|
|
81
|
+
- overrides
|
|
82
|
+
- python-ulid
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT License
|
|
87
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ml3macro/utils/__init__.py,sha256=EkR0neMLRYRaImWE9EuGVHwAYSYtSccIJTyfH-UWX5U,79
|
|
2
|
+
ml3macro/utils/additive_mapping.py,sha256=KBTErJU073W8FkDXvnReb037fSfpzRZmfuWca4oKI-A,4003
|
|
3
|
+
ml3macro/utils/base_enum.py,sha256=rVp_NvzNuoYDooIXWzUf12LZT7zBVn7QEzvG5AlM_mk,67
|
|
4
|
+
ml3macro/utils/identifier.py,sha256=7Ib2WxnlwwjQ_ydnJI-51tivcOtgnIUwTIBdT-YdBkE,149
|
|
5
|
+
ml3macro/utils/iterables.py,sha256=H0lBNxx-XF7HQRMb3KLY2LJm2fpl_nQy6dwvV5b4jKs,353
|
|
6
|
+
ml3macro/utils/optional.py,sha256=KsLOUUmGPavLZSV67teNUvyYe5H-o5Vvu5z_CcOg5xE,3216
|
|
7
|
+
ml3macro/utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
ml3macro/utils/reference.py,sha256=DXAxctJAN580C8hSg6lTNuXhmM89j3hb8hnGegltrGg,3470
|
|
9
|
+
ml3macro/utils/strings.py,sha256=6CRXThYlzhxU0Pmbui6kf8tkTycI93ntd_cg6qK3mjE,1700
|
|
10
|
+
ml3macro/utils/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
ml3macro/utils/decorators/layered_abstract_class.py,sha256=WpDuv6NhSzS3QrJVumBjAG2rzD2CI2aJAESjZiiuxJo,2776
|
|
12
|
+
ml3macro_utils-0.0.0.dist-info/METADATA,sha256=NDW1jVFhcHvIxhUIkyWJqWmszluIdB3AEl8100V4ZEU,2640
|
|
13
|
+
ml3macro_utils-0.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
14
|
+
ml3macro_utils-0.0.0.dist-info/top_level.txt,sha256=lfZK6lm0zMRNkTTOFqQzbBU0gf7ed_O_Wat-yy13Gwc,9
|
|
15
|
+
ml3macro_utils-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ml3macro
|