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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ listclasses