rexys-pyutils 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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: rexys-pyutils
3
+ Version: 0.1.0
4
+ Summary: PyUtils!
5
+ Requires-Python: >=3.10
@@ -0,0 +1 @@
1
+ hello!
@@ -0,0 +1,9 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rexys-pyutils"
7
+ version = "0.1.0"
8
+ description = "PyUtils!"
9
+ requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ from .utils import Tuple, connection, Items
@@ -0,0 +1,356 @@
1
+ import math, socket,base64,sys, hashlib, pickle, random, pygame as pyg
2
+ from typing_extensions import TypeAlias
3
+ from pygame.surface import SurfaceType
4
+ from socketio.exceptions import SocketIOError
5
+ from cryptography.fernet import Fernet
6
+
7
+ class Tuple:
8
+ """
9
+ Usage: \n
10
+ var = Tuple(x, y, z=None, ...) \n
11
+ var.x = var.n1 = var[0] = x\n
12
+ var.y = var.n2 = var[1] = y\n
13
+ var.z = var.n3 = var[2] = z
14
+ """
15
+
16
+ def __init__(self, *vArgs):
17
+ if not all(isinstance(i, (int, float)) for i in vArgs): raise TypeError("Tuples may only contain numbers for now")
18
+ self.argv = []
19
+ for i,obj in enumerate(vArgs):
20
+ self.argv.append(obj)
21
+
22
+ @property
23
+ def __class__(self):
24
+ return tuple
25
+
26
+ def __getattr__(self, attr) -> int:
27
+ name = attr.lower()
28
+ if name.startswith('n') and name[1:].isdigit():
29
+ index = int(name[1:]) - 1
30
+ if 0 <= index < len(self.argv):
31
+ return self.argv[index]
32
+ raise IndexError(f"Tuple index out of range")
33
+ raise AttributeError(f"'Tuple' object has no attribute '{name}'")
34
+
35
+ @property
36
+ def x(self) -> int: return self.argv[0]
37
+ @property
38
+ def y(self) -> int: return self.argv[1]
39
+ @property
40
+ def z(self) -> int: return self.argv[2]
41
+ @property
42
+ def tuple(self) -> tuple: return tuple(self.argv)
43
+
44
+ def str(self):
45
+ """Returns string representation of the Tuple"""
46
+ fullString = ""
47
+ for arg in self.argv:
48
+ fullString += str(arg)
49
+ fullString += ", "
50
+ return fullString
51
+
52
+ def __str__(self):
53
+ """Returns string representation of the Tuple"""
54
+ fullString = ""
55
+ for arg in self.argv:
56
+ fullString += str(arg)
57
+ fullString += ", "
58
+ return fullString
59
+
60
+ def __repr__(self): # optional, for debugging
61
+ return self.__str__()
62
+
63
+ def __iter__(self):
64
+ for arg in self.argv:
65
+ yield arg
66
+
67
+ def __len__(self):
68
+ return len(self.argv)
69
+
70
+ def __getitem__(self, i):
71
+ return self.argv[i]
72
+
73
+
74
+ class connection(socket.socket):
75
+ def __init__(self, family: socket.AddressFamily | int = -1, type: socket.SocketKind | int = -1, proto: int = -1, fileno: int | None = None):
76
+ self.publicKey = None
77
+ self.privateKey = _generateKey()
78
+ self.is_host = None
79
+ self.mixedKey = None
80
+ self.key = None
81
+ super().__init__(family, type, proto, fileno)
82
+ def _fail(self, conn=None):
83
+ if conn is not None:
84
+ conn.close()
85
+ else:
86
+ super().close()
87
+ return None
88
+ def _recvDict(self, conn, bufsize=4096):
89
+ """Receive and unpickle a dict from a socket. Returns None on failure."""
90
+ try:
91
+ data = conn.recv(bufsize)
92
+ except:
93
+ return None
94
+ if data is None: return None
95
+ try:
96
+ result = pickle.loads(data)
97
+ except:
98
+ return None
99
+ if not isinstance(result, dict): return None
100
+ return result
101
+ def _sendDict(self, conn, data: dict):
102
+ conn.send(pickle.dumps(data))
103
+ def bind(self, address, /):
104
+ self.publicKey = _generateKey()
105
+ self.is_host = True
106
+ return super().bind(address)
107
+ def accept(self):
108
+ conn, addr = super().accept()
109
+ client = connection(fileno=conn.fileno())
110
+ client.is_host = False
111
+ client.privateKey = self.privateKey
112
+ client.publicKey = self.publicKey
113
+ client.mixedKey = _combine(self.publicKey, self.privateKey)
114
+ # send public key and mixed key to client
115
+ conn.send(pickle.dumps({"publicKey": client.publicKey}))
116
+ conn.send(pickle.dumps({"mixedKeyServer": client.mixedKey}))
117
+ # receive client's mixed key
118
+ realData = self._recvDict(conn)
119
+ if realData is None: return self._fail(conn)
120
+ if realData.get("mixedKeyClient") is None: return self._fail(conn)
121
+ clientMixedKey = realData["mixedKeyClient"]
122
+ client.key = Fernet(_makeFernetKey(clientMixedKey, self.privateKey))
123
+ return client, addr
124
+ def connect(self, address, /):
125
+ self.is_host = False
126
+ data = super().connect(address)
127
+ # receive public key
128
+ realData = self._recvDict(self)
129
+ if realData is None: return self._fail()
130
+ if realData.get("publicKey") is None: return self._fail()
131
+ self.publicKey = realData["publicKey"]
132
+ # receive server's mixed key
133
+ realData = self._recvDict(self)
134
+ if realData is None: return self._fail()
135
+ if realData.get("mixedKeyServer") is None: return self._fail()
136
+ serverMixedKey = realData["mixedKeyServer"]
137
+ # send client's mixed key
138
+ self.mixedKey = _combine(self.publicKey, self.privateKey)
139
+ self._sendDict(self, {"mixedKeyClient": self.mixedKey})
140
+ self.key = Fernet(_makeFernetKey(serverMixedKey, self.mixedKey))
141
+ return None
142
+ def send(self, data: TypeAlias, flags: int = 0, /):
143
+ if isinstance(data, dict):
144
+ data = self.key.encrypt(pickle.dumps(data))
145
+ elif isinstance(data, str):
146
+ data = self.key.encrypt(data.encode())
147
+ elif isinstance(data, bytes):
148
+ data = self.key.encrypt(data)
149
+ else:
150
+ raise TypeError("data must be dict, str or bytes")
151
+ return super().send(data, flags)
152
+ def recv(self, bufsize: int, flags: int = 0, /):
153
+ data = super().recv(bufsize, flags)
154
+ return self.key.decrypt(data)
155
+ def _randomStr(length=15):
156
+ temp = ""
157
+ for i in range(length):
158
+ temp1 = round(random.random() * 62) + 48
159
+ temp2 = temp1
160
+ if 57 < temp1 < 65:
161
+ temp2 = temp1 + 7
162
+ elif 90 < temp1 < 97:
163
+ temp2 = temp1 + 6
164
+ temp += chr(temp2)
165
+ return temp
166
+ def _combine(a: str, b: str) -> str:
167
+ """Combine two strings into an irreversible hash string."""
168
+ return hashlib.sha256((a + b).encode()).hexdigest()
169
+ def _makeFernetKey(a: str, b: str) -> bytes:
170
+ """Combine two strings into a valid 32-byte Fernet key."""
171
+ raw = hashlib.sha256((a + b).encode()).digest() # 32 raw bytes
172
+ return base64.urlsafe_b64encode(raw) # Fernet needs base64
173
+ def _generateKey() -> str:
174
+ return _combine(_randomStr(), _randomStr())
175
+
176
+
177
+ class _Outline:
178
+ def __init__(self, outline):
179
+ if isinstance(outline, dict):
180
+ enabled = outline.get("enabled", False)
181
+ thickness = outline.get("thickness", 5)
182
+ color = outline.get("color", "black")
183
+ rounding = outline.get("rounding", 0)
184
+ elif isinstance(outline, tuple):
185
+ enabled = outline[0] if len(outline) > 0 else False
186
+ thickness = outline[1] if len(outline) > 1 else 5
187
+ color = outline[2] if len(outline) > 2 else "black"
188
+ rounding = outline[3] if len(outline) > 3 else 0
189
+ else:
190
+ raise TypeError("outline must be a dict or tuple")
191
+ if not isinstance(enabled, bool):
192
+ raise TypeError('outline "enabled" must exist and be a bool')
193
+ if not isinstance(thickness, int):
194
+ raise TypeError('outline "thickness" must exist and be an int')
195
+ if not isinstance(color, str):
196
+ raise TypeError('outline "color" must exist and be a str')
197
+ if not isinstance(rounding, int):
198
+ raise TypeError('outline "rounding" must exist and be an int')
199
+ self.enabled = enabled
200
+ self.thickness = thickness
201
+ self.color = color
202
+ self.rounding = rounding
203
+ class _TextObject:
204
+ def __init__(self, name:str, text:str, x:int, y:int, size:int, color:str, autoPos:bool, priority:int, hit:bool=True, antiAlias:bool=True):
205
+ self.name = name
206
+ self.text = text
207
+ self.category = "text"
208
+ self.color = color
209
+ self.size = size
210
+ self.hit = hit
211
+ self.priority = priority
212
+ self.autoPos = autoPos
213
+ self.antiAlias = antiAlias
214
+ screen = pyg.display.get_surface()
215
+ if autoPos and screen:
216
+ self.x_ratio = x / screen.get_width()
217
+ self.y_ratio = y / screen.get_height()
218
+ else:
219
+ self.x_ratio = None
220
+ self.y_ratio = None
221
+ self.x = x
222
+ self.y = y
223
+ class _Object:
224
+ def __init__(self, name:str, x:int, y:int, size, category:str, outline:tuple, color:str, hit:bool=True, autoPos:bool=True, priority:int=10):
225
+ if not isinstance(outline, (dict, tuple)):
226
+ raise TypeError("outline must be a dict or tuple.")
227
+ if not 1 <= len(outline) <= 4:
228
+ raise TypeError("outline must follow {'enabled':bool,'thickness':int,'color':str,'rounding': int} or (bool,int,str,int)")
229
+ self.name = name
230
+ self.category = category
231
+ self.color = color
232
+ self.hit = hit
233
+ self.autoPos = autoPos
234
+ self.priority = priority
235
+ screen = pyg.display.get_surface()
236
+ if autoPos and screen:
237
+ self.x_ratio = x / screen.get_width()
238
+ self.y_ratio = y / screen.get_height()
239
+ else:
240
+ self.x_ratio = None
241
+ self.y_ratio = None
242
+ self.x = x
243
+ self.y = y
244
+ if category == "circle":
245
+ if isinstance(size, int):
246
+ self.size = size
247
+ elif isinstance(size, tuple):
248
+ self.size = size[0]
249
+ else:
250
+ raise TypeError("size must be an int or tuple")
251
+ self.radius = self.size
252
+ else:
253
+ if not isinstance(size, tuple): raise TypeError("size must be a tuple")
254
+ if len(size) < 2: raise TypeError("size must have 2 values")
255
+ self.width = size[0]
256
+ self.height = size[1]
257
+ try:
258
+ self.outline: _Outline = _Outline(outline)
259
+ except TypeError:
260
+ self.outline: _Outline = _Outline((False,))
261
+
262
+ class Items:
263
+ @staticmethod
264
+ def init(size: Tuple | tuple, *args, **kwargs) -> 'Items':
265
+ pyg.init()
266
+ surface = pyg.display.set_mode(size, vsync=1,*args, **kwargs)
267
+ return Items(surface)
268
+ def __init__(self, display: SurfaceType):
269
+ self.display = display
270
+ self.objects: list[_Object | _TextObject] = []
271
+ self.pixels: dict[tuple,list[_Object | _TextObject]] = {(0,0): []}
272
+ self.render = self.render(self)
273
+ def exist(self, name, x: int = None, y: int = None, literal: bool = False):
274
+ if x is None or y is None:
275
+ if isinstance(name, str):
276
+ for thing in self.objects:
277
+ if thing.name == name: return True
278
+ elif isinstance(name, _Object):
279
+ if literal:
280
+ if name in self.objects: return True
281
+ else:
282
+ for thing in self.objects:
283
+ if thing.name == name.name: return True
284
+ else:
285
+ raise TypeError("name must be a str or Object")
286
+ else:
287
+ if name is None:
288
+ return bool(self.pixels[(x, y)])
289
+ elif isinstance(name, str):
290
+ for pixel in self.pixels[(x, y)]:
291
+ if pixel.name == name: return True
292
+ elif isinstance(name, _Object):
293
+ if literal:
294
+ if name in self.pixels[(x, y)]: return True
295
+ else:
296
+ for pixel in self.pixels[(x, y)]:
297
+ if pixel.name == name.name and pixel.category == name.category: return True
298
+ else:
299
+ raise TypeError("name must be a str or Object")
300
+ return False
301
+ class render:
302
+ def __init__(self,selff):
303
+ self.parent = selff
304
+ def text(self,name,text,x,y,size,color="black",autoPos:bool=True,priority:int=10):
305
+ a = _TextObject(name, text, x, y, size, color, autoPos, priority)
306
+ self.parent.objects.append(a)
307
+ self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
308
+ return a
309
+ def box(self,name:str,x:int,y:int,width:int,height:int,color="white",outline=None):
310
+ a=_Object(name,x,y,(width,height),"rectangle",outline,color)
311
+ self.parent.objects.append(a)
312
+ self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
313
+ return a
314
+ def circle(self,name:str,x:int,y:int,radius:int,color="white",outline=(True,)):
315
+ a=_Object(name,x,y,radius,"circle",outline, color)
316
+ self.parent.objects.append(a)
317
+ self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
318
+ return a
319
+
320
+ def draw(self):
321
+ sw = self.display.get_width()
322
+ sh = self.display.get_height()
323
+ def getPos(item):
324
+ if item.x_ratio is not None:
325
+ return item.x_ratio * sw, item.y_ratio * sh
326
+ return item.x, item.y
327
+ for item in self.objects:
328
+ x, y = getPos(item)
329
+ if item.category == "text":
330
+ itemFont = pyg.font.Font(item.name, item.size)
331
+ itemSurface = itemFont.render(item.text, item.antiAlias, item.color)
332
+ self.display.blit(itemSurface, (x, y))
333
+ elif item.category == "rectangle":
334
+ pyg.draw.rect(self.display, item.color, (x, y, item.width, item.height))
335
+ if item.outline.enabled:
336
+ pyg.draw.rect(self.display, item.outline.color, (x, y, item.width, item.height),item.outline.thickness, item.outline.rounding)
337
+ elif item.category == "circle":
338
+ pyg.draw.circle(self.display, item.color, (x, y), item.radius)
339
+ if item.outline.enabled:
340
+ pyg.draw.circle(self.display, item.outline.color, (x, y), item.radius, item.outline.thickness)
341
+ def flip(self):
342
+ self.draw()
343
+ pyg.display.flip()
344
+ def update(self,items):
345
+ if isinstance(items, SurfaceType):
346
+ self.display = items
347
+ elif isinstance(items, tuple):
348
+ if all(isinstance(item,(_Object,_TextObject)) for item in items):
349
+ for item in items:
350
+ if item in self.objects: continue
351
+ self.objects.append(item)
352
+ self.objects = sorted(self.objects, key=lambda x: x.priority)
353
+ else:
354
+ raise TypeError("argument must be a tuple full of Objects or TextObjects")
355
+ else:
356
+ raise TypeError("argument must be a tuple or a surfaceType")
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: rexys-pyutils
3
+ Version: 0.1.0
4
+ Summary: PyUtils!
5
+ Requires-Python: >=3.10
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ pyutils/__init__.py
4
+ pyutils/utils.py
5
+ rexys_pyutils.egg-info/PKG-INFO
6
+ rexys_pyutils.egg-info/SOURCES.txt
7
+ rexys_pyutils.egg-info/dependency_links.txt
8
+ rexys_pyutils.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+