listclasses 1.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.
listclasses/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import listclass
|
listclasses/main.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from copy import copy, deepcopy
|
|
2
|
+
import types
|
|
3
|
+
from typing import Union, get_args, get_origin
|
|
4
|
+
|
|
5
|
+
def _type(something):
|
|
6
|
+
return type(something)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def listclass(cls = None, *, len: int = 0, frozen: bool = False, type: type = any):
|
|
10
|
+
"""
|
|
11
|
+
A listclass decorator
|
|
12
|
+
|
|
13
|
+
use case:
|
|
14
|
+
|
|
15
|
+
@listclass
|
|
16
|
+
class MyList:
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
:param len: length of a listclass, 0 means that the length is dynamic
|
|
20
|
+
:param frozen: bool, is the class mutable
|
|
21
|
+
:param type: type of values, defaults to any
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def wraps(cls):
|
|
26
|
+
original_init = getattr(cls, '__init__', None)
|
|
27
|
+
def new_init(self, *args, **kwargs):
|
|
28
|
+
if args.__len__() == len or len == 0:
|
|
29
|
+
if args.__len__() == len or len == 0:
|
|
30
|
+
for i in range(args.__len__()):
|
|
31
|
+
# Check if the allowed type is a modern Union (int | str) or legacy typing.Union
|
|
32
|
+
is_union = isinstance(type, types.UnionType) or get_origin(type) is Union
|
|
33
|
+
|
|
34
|
+
if is_union:
|
|
35
|
+
allowed_types = get_args(type)
|
|
36
|
+
if not isinstance(args[i], allowed_types):
|
|
37
|
+
type_names = " | ".join(t.__name__ for t in allowed_types)
|
|
38
|
+
got_name = _type(args[i]).__name__
|
|
39
|
+
raise TypeError(f'Incorrect type: expected: {type_names} got: {got_name}')
|
|
40
|
+
else:
|
|
41
|
+
if (not isinstance(args[i], type)) and (type != any):
|
|
42
|
+
expected_name = type.__name__ if hasattr(type, '__name__') else str(type)
|
|
43
|
+
got_name = _type(args[i]).__name__
|
|
44
|
+
raise TypeError(f'Incorrect type: expected: {expected_name} got: {got_name}')
|
|
45
|
+
self._items = [*args]
|
|
46
|
+
elif args.__len__() > len:
|
|
47
|
+
raise ValueError(f'Too many items: expected: {len} got: {args.__len__()}')
|
|
48
|
+
elif args.__len__() < len:
|
|
49
|
+
raise ValueError(f'Too few items: expected: {len} got: {args.__len__()}')
|
|
50
|
+
original_init(self, **kwargs)
|
|
51
|
+
|
|
52
|
+
def new_repr(self):
|
|
53
|
+
items = [str(item) for item in self._items]
|
|
54
|
+
return f"{cls.__name__}({', '.join(items)})"
|
|
55
|
+
|
|
56
|
+
def new_setitem(self, key, value):
|
|
57
|
+
if frozen:
|
|
58
|
+
raise TypeError(f"'{cls.__name__}' object does not support item assignment")
|
|
59
|
+
# Check if the allowed type is a modern Union (int | str) or legacy typing.Union
|
|
60
|
+
is_union = isinstance(type, types.UnionType) or get_origin(type) is Union
|
|
61
|
+
|
|
62
|
+
if is_union:
|
|
63
|
+
allowed_types = get_args(type)
|
|
64
|
+
if not isinstance(args[i], allowed_types):
|
|
65
|
+
type_names = " | ".join(t.__name__ for t in allowed_types)
|
|
66
|
+
got_name = _type(args[i]).__name__
|
|
67
|
+
raise TypeError(f'Incorrect type: expected: {type_names} got: {got_name}')
|
|
68
|
+
else:
|
|
69
|
+
if (not isinstance(args[i], type)) and (type != any):
|
|
70
|
+
expected_name = type.__name__ if hasattr(type, '__name__') else str(type)
|
|
71
|
+
got_name = _type(args[i]).__name__
|
|
72
|
+
raise TypeError(f'Incorrect type: expected: {expected_name} got: {got_name}')
|
|
73
|
+
self._items[key] = value
|
|
74
|
+
|
|
75
|
+
def new_copy(self):
|
|
76
|
+
new_instance = cls.__new__(cls)
|
|
77
|
+
new_instance.__dict__.update(self.__dict__)
|
|
78
|
+
new_instance._items = copy(self._items)
|
|
79
|
+
return new_instance
|
|
80
|
+
|
|
81
|
+
def new_deepcopy(self, memo):
|
|
82
|
+
new_instance = cls.__new__(cls)
|
|
83
|
+
memo[id(self)] = new_instance
|
|
84
|
+
for k, v in self.__dict__.items():
|
|
85
|
+
new_instance.__dict__[k] = deepcopy(v, memo)
|
|
86
|
+
return new_instance
|
|
87
|
+
|
|
88
|
+
def print_dotted(self) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Prints a list in a format:
|
|
91
|
+
· list[0]\n
|
|
92
|
+
· list[1]\n
|
|
93
|
+
· list[2]
|
|
94
|
+
"""
|
|
95
|
+
for item in self._items:
|
|
96
|
+
print('·', item)
|
|
97
|
+
|
|
98
|
+
def print_numbered(self) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Prints a list in a format:
|
|
101
|
+
1. list[0]
|
|
102
|
+
2. list[1]
|
|
103
|
+
3. list[2]
|
|
104
|
+
"""
|
|
105
|
+
for i in range(self.__len__()):
|
|
106
|
+
print(str(i + 1) + ".", self[i])
|
|
107
|
+
|
|
108
|
+
def new_eq(self, other):
|
|
109
|
+
if not isinstance(other, cls):
|
|
110
|
+
return NotImplemented
|
|
111
|
+
return self._items == other._items
|
|
112
|
+
|
|
113
|
+
def new_ne(self, other):
|
|
114
|
+
if not isinstance(other, cls):
|
|
115
|
+
return NotImplemented
|
|
116
|
+
return self._items != other._items
|
|
117
|
+
|
|
118
|
+
def new_add(self, other):
|
|
119
|
+
if isinstance(other, cls):
|
|
120
|
+
items_to_add = other._items
|
|
121
|
+
elif hasattr(other, '__iter__'):
|
|
122
|
+
items_to_add = other
|
|
123
|
+
else:
|
|
124
|
+
return NotImplemented
|
|
125
|
+
|
|
126
|
+
# Tworzenie nowej instancji bez ponownego wywoływania __init__
|
|
127
|
+
new_instance = cls.__new__(cls)
|
|
128
|
+
new_instance.__dict__.update(copy.deepcopy(self.__dict__))
|
|
129
|
+
new_instance._items = self._items + list(items_to_add)
|
|
130
|
+
return new_instance
|
|
131
|
+
|
|
132
|
+
def new_iadd(self, other):
|
|
133
|
+
if isinstance(other, cls):
|
|
134
|
+
self._items.extend(other._items)
|
|
135
|
+
elif hasattr(other, '__iter__'):
|
|
136
|
+
self._items.extend(other)
|
|
137
|
+
else:
|
|
138
|
+
return NotImplemented
|
|
139
|
+
# Check if the allowed type is a modern Union (int | str) or legacy typing.Union
|
|
140
|
+
is_union = isinstance(type, types.UnionType) or get_origin(type) is Union
|
|
141
|
+
if is_union:
|
|
142
|
+
allowed_types = get_args(type)
|
|
143
|
+
if not isinstance(args[i], allowed_types):
|
|
144
|
+
type_names = " | ".join(t.__name__ for t in allowed_types)
|
|
145
|
+
got_name = _type(args[i]).__name__
|
|
146
|
+
raise TypeError(f'Incorrect type: expected: {type_names} got: {got_name}')
|
|
147
|
+
else:
|
|
148
|
+
if (not isinstance(args[i], type)) and (type != any):
|
|
149
|
+
expected_name = type.__name__ if hasattr(type, '__name__') else str(type)
|
|
150
|
+
got_name = _type(args[i]).__name__
|
|
151
|
+
raise TypeError(f'Incorrect type: expected: {expected_name} got: {got_name}')
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
def new_class_getitem(cls, params):
|
|
155
|
+
return types.GenericAlias(cls, params)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
cls.__init__ = new_init
|
|
159
|
+
cls.__repr__ = new_repr
|
|
160
|
+
cls.__setitem__ = new_setitem
|
|
161
|
+
cls.__eq__ = new_eq
|
|
162
|
+
cls.__ne__ = new_ne
|
|
163
|
+
cls.__class_getitem__ = new_class_getitem
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
cls.print_dotted = print_dotted
|
|
167
|
+
cls.print_numbered = print_numbered
|
|
168
|
+
|
|
169
|
+
if not frozen:
|
|
170
|
+
if len == 0:
|
|
171
|
+
cls.append = lambda self, item: self._items.append(item)
|
|
172
|
+
cls.pop = lambda self, index=-1: self._items.pop(index)
|
|
173
|
+
cls.clear = lambda self: self._items.clear()
|
|
174
|
+
cls.insert = lambda self, index, item: self._items.insert(index, item)
|
|
175
|
+
cls.__iadd__ = new_iadd
|
|
176
|
+
cls.__delitem__ = lambda self, index: self._items.__delitem__(index)
|
|
177
|
+
cls.sort = lambda self, *args, **kwargs: self._items.sort(*args, **kwargs)
|
|
178
|
+
cls.reverse = lambda self: self._items.reverse()
|
|
179
|
+
cls.__add__ = new_add
|
|
180
|
+
cls.__copy__ = new_copy
|
|
181
|
+
cls.__deepcopy__ = new_deepcopy
|
|
182
|
+
cls.count = lambda self, item: self._items.count(item)
|
|
183
|
+
cls.index = lambda self, item, *args: self._items.index(item, *args)
|
|
184
|
+
cls.__contains__ = lambda self, item: item in self._items
|
|
185
|
+
cls.contains = cls.__contains__
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
cls.__len__ = lambda self: self._items.__len__()
|
|
189
|
+
cls.__getitem__ = lambda self, index: self._items[index]
|
|
190
|
+
cls.__iter__ = lambda self: iter(self._items)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
return cls
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# See if we're being called as @listclass or @listclass().
|
|
197
|
+
if cls is None:
|
|
198
|
+
# We're called with parens.
|
|
199
|
+
return wraps
|
|
200
|
+
# We're called as @listclass without parens.
|
|
201
|
+
return wraps(cls)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: listclasses
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple decorator to make custom sequence types
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
Dynamic: description
|
|
7
|
+
Dynamic: description-content-type
|
|
8
|
+
Dynamic: summary
|
|
9
|
+
|
|
10
|
+
# listclasses
|
|
11
|
+
A Python library providing a @listclass decorator that transforms standard classes into powerful,
|
|
12
|
+
type-safe, and highly customizable custom sequence types
|
|
13
|
+
|
|
14
|
+
# Features:
|
|
15
|
+
|
|
16
|
+
· Type Safety: Enforce strict data types for sequence items
|
|
17
|
+
· Length Constraints: Define fixed-size or dynamic sequences
|
|
18
|
+
· Immutability: Easily create read-only (frozen) collections
|
|
19
|
+
· Rich API: Seamlessly integrates with built-in functions (len(), repr(), iteration, slicing)
|
|
20
|
+
· Copy Support: Native support for shallow and deep copying
|
|
21
|
+
· Pretty Printing: Built-in methods for clean text formatting out of the box
|
|
22
|
+
· Installation: Add the listclass decorator code directly to your project
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from listclasses import listclass
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
# Basic Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from listclasses import listclass
|
|
32
|
+
|
|
33
|
+
@listclass
|
|
34
|
+
class Inventory:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# Create an instance with initial items
|
|
38
|
+
items = Inventory("Sword", "Shield", "Potion")
|
|
39
|
+
|
|
40
|
+
print(items) # Output: Inventory(Sword, Shield, Potion)
|
|
41
|
+
print(items[0]) # Output: Sword
|
|
42
|
+
print(len(items)) # Output: 3
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
# Advanced Configuration
|
|
46
|
+
|
|
47
|
+
The @listclass decorator accepts configuration parameters to restrict container behavior
|
|
48
|
+
|
|
49
|
+
## Enforcing Strict Types
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
@listclass(type=int)
|
|
53
|
+
class IntegerList:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# Works perfectly
|
|
57
|
+
numbers = IntegerList(1, 2, 3)
|
|
58
|
+
|
|
59
|
+
# Raises TypeError: Incorrect type: expected: int got: str
|
|
60
|
+
numbers = IntegerList(1, "two", 3)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Enforcing more than one type
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
@listclass(type=int | None)
|
|
67
|
+
class List:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
# Works perfectly
|
|
71
|
+
numbers_and_None = List(1, 2, 3, None)
|
|
72
|
+
|
|
73
|
+
# Raises TypeError: Incorrect type: expected: int | None got: str
|
|
74
|
+
numbers_and_None = List(1, "two", 3)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Setting Fixed Lengths
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
@listclass(len=2)
|
|
81
|
+
class Coordinates:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Works perfectly
|
|
85
|
+
point = Coordinates(10, 20)
|
|
86
|
+
|
|
87
|
+
# Raises ValueError: Too many items: expected: 2 got: 3
|
|
88
|
+
point = Coordinates(10, 20, 30)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Creating Frozen (Immutable) Lists
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
@listclass(frozen=True)
|
|
95
|
+
class ReadOnlyData:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
data = ReadOnlyData("A", "B")
|
|
99
|
+
|
|
100
|
+
# Raises TypeError: 'ReadOnlyData' object does not support item assignment
|
|
101
|
+
data[0] = "Z"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Built-in Formatting
|
|
105
|
+
listclasses comes with utility methods to print your items cleanly.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
@listclass
|
|
109
|
+
class TodoList:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
todos = TodoList("Buy milk", "Clean room", "Code Python")
|
|
113
|
+
|
|
114
|
+
# Dotted list format
|
|
115
|
+
todos.print_dotted()
|
|
116
|
+
# Output:
|
|
117
|
+
# · Buy milk
|
|
118
|
+
# · Clean room
|
|
119
|
+
# · Code Python
|
|
120
|
+
|
|
121
|
+
# Numbered list format
|
|
122
|
+
todos.print_numbered()
|
|
123
|
+
# Output:
|
|
124
|
+
# 1. Buy milk
|
|
125
|
+
# 2. Clean room
|
|
126
|
+
# 3. Code Python
|
|
127
|
+
```
|
|
128
|
+
# API Reference
|
|
129
|
+
|
|
130
|
+
## Decorator Arguments
|
|
131
|
+
|
|
132
|
+
| Argument | Type | Default | Description |
|
|
133
|
+
|:--------:|:----:|:-------:|:-----------------------------------------------------------------:|
|
|
134
|
+
| len | int | 0 | Expected length of the collection. 0 means dynamic size. |
|
|
135
|
+
| type | type | any | Restricts elements to a single or multiple explicit python types. |
|
|
136
|
+
| frozen | bool | False | If True, prevents item assignment and mutating operations. |
|
|
137
|
+
|
|
138
|
+
## Available Methods
|
|
139
|
+
|
|
140
|
+
Depending on configuration (frozen and len), instances support standard list operations:
|
|
141
|
+
|
|
142
|
+
· Access: __getitem__, __iter__, __contains__, count(), index()
|
|
143
|
+
· Mutation (Dynamic only): append(), pop(), clear(), insert(), __delitem__
|
|
144
|
+
· Ordering (Mutable only): sort(), reverse()
|
|
145
|
+
· Math: __add__ (+), __iadd__ (+=)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
listclasses/__init__.py,sha256=3b-srK06956nkAbYn-lTUM8_wai0qOk4XnY2qfSl5lw,27
|
|
2
|
+
listclasses/main.py,sha256=AH4hMIjOhDLeWxGk1RS_4oq7UBsp5D1bAT1NYH4osgg,8403
|
|
3
|
+
listclasses-1.0.0.dist-info/METADATA,sha256=PlbeSThXPJduGomO3lc2Qn-DPxTKhLMYKG5_kKCIfAc,3791
|
|
4
|
+
listclasses-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
listclasses-1.0.0.dist-info/top_level.txt,sha256=CR2eZwcQk7brVXF6mDJ37221xXIE8VHMozrk0d1UTRY,12
|
|
6
|
+
listclasses-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
listclasses
|