typemore 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.
typemore-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maoshen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.5
2
+ Name: typemore
3
+ Version: 0.1.0
4
+ Summary: A collection of custom Python data types for specialized behavior.
5
+ Author: maoshen
6
+ Maintainer: maoshen
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: Auto,ChainNode,Debug,DisplayProxy,InfiniteIterator,Interval,LenLimit,LenLimitError,MemLimit,MemLimitError,Point2D,Point3D,PointAnyD,Range,ReadOnlyBox,StrictFloat,TimeLimit,TimeLimitError,more,type,typemore
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pympler>=1.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: build; extra == 'dev'
25
+ Requires-Dist: twine; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ ## License
29
+
30
+ MIT
31
+
32
+ # typemore
33
+
34
+ A collection of custom Python data types for specialized behavior.
35
+
36
+ ## Install
37
+
38
+ pip install typemore
39
+
40
+ ## Usage
41
+
42
+ from typemore import StrictFloat
43
+
44
+ StrictFloat(0.1) + StrictFloat(0.2) # StrictFloat:0.3
@@ -0,0 +1,17 @@
1
+ ## License
2
+
3
+ MIT
4
+
5
+ # typemore
6
+
7
+ A collection of custom Python data types for specialized behavior.
8
+
9
+ ## Install
10
+
11
+ pip install typemore
12
+
13
+ ## Usage
14
+
15
+ from typemore import StrictFloat
16
+
17
+ StrictFloat(0.1) + StrictFloat(0.2) # StrictFloat:0.3
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "typemore"
7
+ version = "0.1.0"
8
+ description = "A collection of custom Python data types for specialized behavior."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = [
14
+ "typemore",
15
+ "type",
16
+ "more",
17
+ "Range",
18
+ "Point2D",
19
+ "Point3D",
20
+ "PointAnyD",
21
+ "Auto",
22
+ "Debug",
23
+ "StrictFloat",
24
+ "Interval",
25
+ "ChainNode",
26
+ "InfiniteIterator",
27
+ "ReadOnlyBox",
28
+ "DisplayProxy",
29
+ "LenLimit",
30
+ "LenLimitError",
31
+ "TimeLimit",
32
+ "TimeLimitError",
33
+ "MemLimit",
34
+ "MemLimitError"]
35
+ classifiers = [
36
+ "Development Status :: 3 - Alpha",
37
+ "Intended Audience :: Developers",
38
+ "License :: OSI Approved :: MIT License",
39
+ "Operating System :: OS Independent",
40
+ "Programming Language :: Python :: 3",
41
+ "Programming Language :: Python :: 3.10",
42
+ "Programming Language :: Python :: 3.11",
43
+ "Programming Language :: Python :: 3.12",
44
+ "Programming Language :: Python :: 3.13",
45
+ "Topic :: Software Development :: Libraries",
46
+ "Typing :: Typed",
47
+ ]
48
+ authors = [
49
+ { name = "maoshen" }
50
+ ]
51
+ maintainers = [
52
+ { name = "maoshen" }
53
+ ]
54
+ dependencies = [
55
+ "pympler>=1.0"
56
+ ]
57
+
58
+ [project.optional-dependencies]
59
+ dev = [
60
+ "build",
61
+ "twine",
62
+ ]
63
+
64
+ [tool.hatch.build.targets.wheel]
65
+ packages = ["typemore"]
66
+
67
+ [tool.hatch.build.targets.sdist]
68
+ include = [
69
+ "/typemore",
70
+ "/README.md",
71
+ ]
@@ -0,0 +1,12 @@
1
+ import sys
2
+
3
+ def private(func):
4
+ def wrapper(*args, **kwargs):
5
+ caller_qualname = sys._getframe(1).f_code.co_qualname
6
+ method_qualname = func.__qualname__
7
+ parent = method_qualname[:method_qualname.rfind('.')]
8
+
9
+ if not caller_qualname.startswith(parent):
10
+ raise RuntimeError(f"{method_qualname} is private")
11
+ return func(*args, **kwargs)
12
+ return wrapper
@@ -0,0 +1,36 @@
1
+ from .types.range import Range
2
+ from .types.point2D import Point2D
3
+ from .types.point3D import Point3D
4
+ from .types.pointAnyD import PointAnyD
5
+ from .types.auto import Auto
6
+ from .types.debug import Debug
7
+ from .types.strictFloat import StrictFloat
8
+ from .types.interval import Interval
9
+ from .types.chainNode import ChainNode
10
+ from .types.infiniteIterator import InfiniteIterator
11
+ from .types.readOnlyBox import ReadOnlyBox
12
+ from .types.displayProxy import DisplayProxy
13
+ from .types.lenLimit import LenLimit, LenLimitError
14
+ from .types.timeLimit import TimeLimit, TimeLimitError
15
+ from .types.memLimit import MemLimit, MemLimitError
16
+
17
+ __version__ = "0.1.0"
18
+ __author__ = "maoshen"
19
+ __all__ = ["Range",
20
+ "Point2D",
21
+ "Point3D",
22
+ "PointAnyD",
23
+ "Auto",
24
+ "Debug",
25
+ "StrictFloat",
26
+ "Interval",
27
+ "ChainNode",
28
+ "InfiniteIterator",
29
+ "ReadOnlyBox",
30
+ "DisplayProxy",
31
+ "LenLimit",
32
+ "LenLimitError",
33
+ "TimeLimit",
34
+ "TimeLimitError",
35
+ "MemLimit",
36
+ "MemLimitError"]
File without changes
File without changes
@@ -0,0 +1,119 @@
1
+ class Auto:
2
+ """
3
+ This class is compatible with most commonly used type-conversion functions.
4
+ It prevents exceptions for conversion operations that would normally raise them.
5
+ """
6
+ _cache = {}
7
+ _hash = None
8
+
9
+ def __new__(cls, auto):
10
+ base = type(auto)
11
+ if base not in cls._cache:
12
+ if base in (bool, type(None)):
13
+ bases = (cls,)
14
+ else:
15
+ bases = (cls, base)
16
+ cls._cache[base] = type(
17
+ f"{cls.__name__}[{base.__name__}]",
18
+ bases,
19
+ {}
20
+ )
21
+ new_cls = cls._cache[base]
22
+
23
+ if auto is None or base is bool:
24
+ instance = super().__new__(new_cls)
25
+ elif issubclass(base, (int, float, complex, str, tuple, frozenset, bytes)):
26
+ instance = base.__new__(new_cls, auto)
27
+ elif issubclass(base, (list, dict, set, bytearray)):
28
+ instance = base.__new__(new_cls)
29
+ base.__init__(instance, auto)
30
+ else:
31
+ instance = base.__new__(new_cls)
32
+ return instance
33
+
34
+ def __init__(self, auto):
35
+ self.auto = self
36
+ self._hash = None
37
+ try:
38
+ hash(auto)
39
+ self._hash = True
40
+ except TypeError:
41
+ self._hash = False
42
+
43
+ def __repr__(self):
44
+ return f"{self.__class__.__name__}:{super().__repr__()}"
45
+
46
+ def __getattr__(self, name):
47
+ raise AttributeError(name)
48
+
49
+ def __dir__(self):
50
+ return list(set(dir(self.__class__) + dir(self.__class__.__mro__[1]) + ['auto']))
51
+
52
+ def __int__(self):
53
+ if not self._hash:
54
+ return len(self)
55
+ if isinstance(self, bool):
56
+ return 1 if bool.__bool__(self) else 0
57
+ if isinstance(self, int):
58
+ return int.__int__(self)
59
+ if isinstance(self, float):
60
+ return int(float.__int__(self))
61
+ if isinstance(self, complex):
62
+ return int(self.real)
63
+ if isinstance(self, str):
64
+ try:
65
+ return int(str.__str__(self))
66
+ except ValueError:
67
+ return 1 if str.__len__(self) else 0
68
+ return 0
69
+
70
+ def __float__(self) -> float:
71
+ return float(self.__int__())
72
+
73
+ def __complex__(self) -> complex:
74
+ if self._hash:
75
+ if isinstance(self.auto, complex):
76
+ return complex(self.real, self.imag)
77
+ elif isinstance(self.auto, int | float):
78
+ return complex(self.auto, 0j)
79
+ elif isinstance(self.auto, bool):
80
+ if self.auto:
81
+ real = 1
82
+ else:
83
+ real = 0
84
+ return complex(real, 0j)
85
+ elif isinstance(self.auto, str):
86
+ try:
87
+ return complex(self.auto)
88
+ except ValueError:
89
+ if self.auto.strip() == '':
90
+ return 0
91
+ else:
92
+ return 1
93
+ return complex(0)
94
+ else:
95
+ return complex(self.__int__(), 0j)
96
+
97
+ def __index__(self) -> int:
98
+ if self._hash:
99
+ if isinstance(self.auto, int | float | bool):
100
+ return int(self.auto)
101
+ elif isinstance(self.auto, str):
102
+ try:
103
+ return int(self.auto)
104
+ except ValueError:
105
+ if self.auto.strip() == '':
106
+ return 0
107
+ else:
108
+ return 1
109
+ elif isinstance(self.auto, complex):
110
+ return int(self.auto.real)
111
+ return 0
112
+ else:
113
+ return self.__int__()
114
+
115
+ def __iter__(self):
116
+ auto = self.auto
117
+ if isinstance(auto, (int, float, complex, bool, bytes)):
118
+ return iter([auto])
119
+ return iter(auto)
@@ -0,0 +1,293 @@
1
+ from __future__ import annotations
2
+
3
+ class PointerNone:
4
+ pass
5
+
6
+ point_none = PointerNone()
7
+
8
+ class ChainNode:
9
+ """
10
+ A singly linked list node whose class also inherits from type(value).
11
+
12
+ Each instance stores a value and a pointer to the next node. The class
13
+ of an instance is generated dynamically based on the type of value, so
14
+ a node can also use the methods and operators of its underlying type.
15
+ For example, ChainNode(5) inherits from int, and ChainNode("a") inherits
16
+ from str.
17
+
18
+ The end of a chain is marked by point_none, a sentinel instance of
19
+ PointerNone. Use "pointer is point_none" to test for the end.
20
+ """
21
+ _cache = {}
22
+
23
+ def __new__(cls, value, pointer = point_none):
24
+ base = type(value)
25
+ if base not in cls._cache:
26
+ if base in (bool, type(None)):
27
+ bases = (cls,)
28
+ else:
29
+ bases = (cls, base)
30
+ cls._cache[base] = type(
31
+ f"{cls.__name__}[{base.__name__}]",
32
+ bases,
33
+ {}
34
+ )
35
+ new_cls = cls._cache[base]
36
+ if value is None or base is bool:
37
+ instance = super().__new__(new_cls)
38
+ return instance
39
+ elif issubclass(base, (int, float, str, tuple, frozenset, bytes)):
40
+ instance= base.__new__(new_cls, value)
41
+ return instance
42
+ else:
43
+ instance = base.__new__(new_cls)
44
+ return instance
45
+
46
+ def __init__(self, value, pointer = point_none):
47
+ self.value = value
48
+ self.pointer = pointer
49
+
50
+ def __repr__(self) -> str:
51
+ return f"Pointer({self.value}, {self.pointer if self.pointer is not point_none else ''})"
52
+
53
+ def __str__(self) -> str:
54
+ return f"({self.value}, {self.pointer if self.pointer is not point_none else ''})"
55
+
56
+ def getvalue_number(self, number: int):
57
+ """
58
+ Return the value of the node at index number, where 0 is self.
59
+
60
+ Raises ValueError if number is not a non-negative int, and IndexError
61
+ if the chain ends before reaching that index.
62
+ """
63
+ if not isinstance(number, int) or number < 0:
64
+ raise ValueError("index out of range")
65
+ node = self
66
+ for _ in range(number):
67
+ if not isinstance(node.pointer, ChainNode):
68
+ raise IndexError("index out of range")
69
+ node = node.pointer
70
+ return node.value
71
+
72
+ def getpointer_number(self, number: int):
73
+ """
74
+ Return the node at index number, where 0 is self.
75
+
76
+ Raises ValueError if number is not a non-negative int, and IndexError
77
+ if the chain ends before reaching that index.
78
+ """
79
+ if not isinstance(number, int) or number < 0:
80
+ raise ValueError("index out of range")
81
+ node = self
82
+ for _ in range(number):
83
+ if not isinstance(node.pointer, ChainNode):
84
+ raise IndexError("index out of range")
85
+ node = node.pointer
86
+ return node
87
+
88
+ def getpointer_value(self, value):
89
+ """
90
+ Return the pointer of the first node whose value equals value.
91
+
92
+ Raises ValueError if no such node exists.
93
+ """
94
+ node = self._find_node(value)
95
+ if node is None:
96
+ raise ValueError("value not found in chain")
97
+ return node.pointer
98
+
99
+ def setpointer_number(self, number: int, pointer) -> None:
100
+ """
101
+ Set the pointer of the node at index number, where 0 is self.
102
+
103
+ Raises ValueError if number is not a non-negative int, and IndexError
104
+ if the chain ends before reaching that index.
105
+ """
106
+ if not isinstance(number, int) or number < 0:
107
+ raise ValueError("index out of range")
108
+ node = self
109
+ for _ in range(number):
110
+ if not isinstance(node, ChainNode):
111
+ raise IndexError("index out of range")
112
+ node = node.pointer
113
+ if not isinstance(node, ChainNode):
114
+ raise IndexError("index out of range")
115
+ node.pointer = pointer
116
+
117
+ def setpointer_value(self, value, pointer) -> None:
118
+ """
119
+ Set the pointer of the first node whose value equals value.
120
+
121
+ Raises ValueError if no such node exists.
122
+ """
123
+ node = self._find_node(value)
124
+ if node is None:
125
+ raise ValueError("value not found in chain")
126
+ node.pointer = pointer
127
+
128
+ def setvalue_number(self, number: int, value) -> None:
129
+ """
130
+ Set the value attribute of the node at index number, where 0 is self.
131
+
132
+ For immutable subclasses such as ChainNode[int], the instance's own
133
+ numeric value does not change; only the value attribute is updated.
134
+ Raises ValueError if number is not a non-negative int, and IndexError
135
+ if the chain ends before reaching that index.
136
+ """
137
+ if not isinstance(number, int) or number < 0:
138
+ raise ValueError("index out of range")
139
+ node = self
140
+ for _ in range(number):
141
+ if not isinstance(node, ChainNode):
142
+ raise IndexError("index out of range")
143
+ node = node.pointer
144
+ if not isinstance(node, ChainNode):
145
+ raise IndexError("index out of range")
146
+ node.value = value
147
+
148
+ def delete_number(self, number: int):
149
+ """
150
+ Delete the node at index number.
151
+
152
+ If number is 0, return the next node as the new head. Otherwise, return
153
+ self. Raises ValueError if number is not a non-negative int, and
154
+ IndexError if the chain ends before reaching that index.
155
+ """
156
+ if not isinstance(number, int) or number < 0:
157
+ raise ValueError("index out of range")
158
+
159
+ if number == 0:
160
+ if not isinstance(self.pointer, ChainNode):
161
+ raise IndexError("index out of range")
162
+ return self.pointer
163
+
164
+ prev = self
165
+ for _ in range(number - 1):
166
+ if not isinstance(prev.pointer, ChainNode):
167
+ raise IndexError("index out of range")
168
+ prev = prev.pointer
169
+
170
+ target = prev.pointer
171
+ if not isinstance(target, ChainNode):
172
+ raise IndexError("index out of range")
173
+ prev.pointer = target.pointer
174
+ return self
175
+
176
+ def delete_value(self, value):
177
+ """
178
+ Delete the first node whose value equals value.
179
+
180
+ Return the new head. When the deleted node is the head, the successor
181
+ becomes the new head. Raises ValueError if no such node exists.
182
+ """
183
+ if self.value == value:
184
+ if not isinstance(self.pointer, ChainNode):
185
+ raise ValueError("value not found in chain")
186
+ return self.pointer
187
+
188
+ prev = self
189
+ cur = self.pointer
190
+ while isinstance(cur, ChainNode):
191
+ if cur.value == value:
192
+ prev.pointer = cur.pointer
193
+ return self
194
+ prev = cur
195
+ cur = cur.pointer
196
+ raise ValueError("value not found in chain")
197
+
198
+ def node_search(self, value) -> int:
199
+ """
200
+ Return the index of the first node whose value equals value.
201
+
202
+ Index 0 is self. Raises ValueError if no such node exists.
203
+ """
204
+ if self.value == value:
205
+ return 0
206
+ pointer_save = self
207
+ pointer_now = self.pointer
208
+ i = 1
209
+ while True:
210
+ if not isinstance(pointer_now, ChainNode):
211
+ if pointer_now == value and pointer_now == pointer_save.value:
212
+ return i
213
+ else:
214
+ raise ValueError(f"{value} not found in chain")
215
+ if pointer_now.value == value:
216
+ return i
217
+ pointer_now = pointer_now.pointer
218
+ pointer_save = pointer_save.pointer
219
+ i += 1
220
+
221
+ def _find_node(self, value):
222
+ """Return the first node whose value equals value, or None."""
223
+ node = self
224
+ while isinstance(node, ChainNode):
225
+ if node.value == value:
226
+ return node
227
+ node = node.pointer
228
+ return None
229
+
230
+ def node_len(self, value=point_none, pointer=point_none) -> int:
231
+ """
232
+ Return the number of nodes starting after a given position.
233
+
234
+ With no argument, count nodes after self. With pointer=xxx, count
235
+ starting at xxx (inclusive). With value=xxx, count starting after the
236
+ node holding xxx. The two keyword arguments are mutually exclusive.
237
+ """
238
+ if pointer is not point_none and value is not point_none:
239
+ raise ValueError("pointer and value are mutually exclusive")
240
+
241
+ if value is not point_none:
242
+ node = self._find_node(value)
243
+ if node is None:
244
+ raise ValueError(f"value {value} not found in chain")
245
+ start = node.pointer
246
+ elif pointer is not point_none:
247
+ start = pointer
248
+ else:
249
+ start = self.pointer
250
+
251
+ i = 0
252
+ while isinstance(start, ChainNode):
253
+ start = start.pointer
254
+ i += 1
255
+ return i
256
+
257
+ def deep_len(self, value=point_none, pointer=point_none) -> int:
258
+ """
259
+ Return the depth of a node relative to self.
260
+
261
+ Positive if the node comes after self, 0 if it is self, negative if it
262
+ comes before self. The two keyword arguments are mutually exclusive.
263
+ Raises ValueError if the node is not on the same chain.
264
+ """
265
+ if pointer is not point_none and value is not point_none:
266
+ raise ValueError("pointer and value are mutually exclusive")
267
+
268
+ if value is not point_none:
269
+ node = self._find_node(value)
270
+ if node is None:
271
+ raise ValueError(f"value {value} not found in chain")
272
+ elif pointer is not point_none:
273
+ node = pointer
274
+ else:
275
+ raise ValueError("either pointer or value is required")
276
+
277
+ cur = self
278
+ depth = 0
279
+ while isinstance(cur, ChainNode):
280
+ if cur is node:
281
+ return depth
282
+ cur = cur.pointer
283
+ depth += 1
284
+
285
+ cur = node
286
+ depth = 0
287
+ while isinstance(cur, ChainNode):
288
+ if cur is self:
289
+ return -depth
290
+ cur = cur.pointer
291
+ depth += 1
292
+
293
+ raise ValueError("the two nodes are not in the same chain")