pyquoks 1.2.1__py3-none-any.whl → 1.3.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.
pyquoks/data.py CHANGED
@@ -1,194 +1,522 @@
1
1
  from __future__ import annotations
2
- import configparser, datetime, logging, winreg, json, sys, io, os
2
+ import configparser, datetime, logging, sqlite3, json, sys, io, os
3
3
  import requests, PIL.Image, PIL.ImageDraw
4
4
  from . import utils
5
5
 
6
6
 
7
- # Providers
7
+ # region Providers
8
+
8
9
  class IDataProvider:
9
- _PATH: str
10
+ """
11
+ Class for providing data from JSON-like files
12
+ """
13
+
14
+ _PATH: str = utils.get_path("data/")
15
+ """
16
+ Path to the directory with JSON-like files
17
+ """
18
+
19
+ _FILENAME: str = "{0}.json"
20
+ """
21
+ Filename of JSON-like files
22
+ """
23
+
10
24
  _DATA_VALUES: dict[str, type]
25
+ """
26
+ Dictionary with filenames and containers
27
+
28
+ Example:
29
+ _DATA_VALUES = {"users": UsersContainer}
30
+ """
11
31
 
12
32
  def __init__(self) -> None:
13
- for k, v in self._DATA_VALUES.items():
33
+ for filename, container in self._DATA_VALUES.items():
14
34
  try:
15
- with open(self._PATH.format(k), "rb") as file:
16
- setattr(self, k, v(json_data=json.loads(file.read())))
35
+ with open(self._PATH + self._FILENAME.format(filename), "rb") as file:
36
+ setattr(self, filename, container(json.loads(file.read())))
17
37
  except:
18
- setattr(self, k, None)
38
+ setattr(self, filename, None)
19
39
 
20
40
 
21
41
  class IConfigProvider:
42
+ """
43
+ Class for providing data from configuration file
44
+ """
45
+
22
46
  class IConfig:
47
+ """
48
+ Class that represents a section in configuration file
49
+ """
50
+
23
51
  _SECTION: str = None
52
+ """
53
+ Name of the section in configuration file
54
+
55
+ Example:
56
+ _SECTION = "Settings"
57
+ """
58
+
24
59
  _CONFIG_VALUES: dict[str, type]
60
+ """
61
+ Dictionary with settings and their types
62
+ """
25
63
 
26
64
  def __init__(self, parent: IConfigProvider = None) -> None:
27
65
  if isinstance(parent, IConfigProvider):
28
66
  self._CONFIG_VALUES = parent._CONFIG_VALUES.get(self._SECTION)
29
- self._incorrect_content_exception = configparser.ParsingError("config.ini is filled incorrectly!")
67
+
68
+ self._incorrect_content_exception = configparser.ParsingError(
69
+ "configuration file is filled incorrectly!"
70
+ )
30
71
  self._config = configparser.ConfigParser()
31
- self._config.read(utils.get_path("config.ini"))
72
+ self._config.read(parent._PATH)
73
+
32
74
  if not self._config.has_section(self._SECTION):
33
75
  self._config.add_section(self._SECTION)
34
- for k, v in self._CONFIG_VALUES.items():
76
+
77
+ for setting, data_type in self._CONFIG_VALUES.items():
35
78
  try:
36
- setattr(self, k, self._config.get(self._SECTION, k))
79
+ setattr(self, setting, self._config.get(self._SECTION, setting))
37
80
  except:
38
- self._config.set(self._SECTION, k, v.__name__)
39
- with open(utils.get_path("config.ini"), "w", encoding="utf-8") as file:
40
- self._config.write(fp=file)
41
- for k, v in self._CONFIG_VALUES.items():
81
+ self._config.set(self._SECTION, setting, data_type.__name__)
82
+ with open(parent._PATH, "w", encoding="utf-8") as file:
83
+ self._config.write(file)
84
+
85
+ for setting, data_type in self._CONFIG_VALUES.items():
42
86
  try:
43
- if v == int:
44
- setattr(self, k, int(getattr(self, k)))
45
- elif v == bool:
46
- if getattr(self, k) not in (str(True), str(False)):
47
- setattr(self, k, None)
48
- raise self._incorrect_content_exception
49
- else:
50
- setattr(self, k, getattr(self, k) == str(True))
51
- elif v in (dict, list):
52
- setattr(self, k, json.loads(getattr(self, k)))
87
+ match data_type.__name__:
88
+ case "int":
89
+ setattr(self, setting, int(getattr(self, setting)))
90
+ case "bool":
91
+ if getattr(self, setting) not in (str(True), str(False)):
92
+ setattr(self, setting, None)
93
+ raise self._incorrect_content_exception
94
+ else:
95
+ setattr(self, setting, getattr(self, setting) == str(True))
96
+ case "dict" | "list":
97
+ setattr(self, setting, json.loads(getattr(self, setting)))
53
98
  except:
54
- setattr(self, k, None)
99
+ setattr(self, setting, None)
55
100
  raise self._incorrect_content_exception
101
+
56
102
  if not self.values:
57
103
  raise self._incorrect_content_exception
58
104
 
59
105
  @property
60
106
  def values(self) -> dict | None:
107
+ """
108
+ :return: Values stored in section
109
+ """
110
+
61
111
  try:
62
- return {i: getattr(self, i) for i in self._CONFIG_VALUES}
112
+ return {setting: getattr(self, setting) for setting in self._CONFIG_VALUES.keys()}
63
113
  except:
64
114
  return None
65
115
 
116
+ _PATH: str = utils.get_path("config.ini")
117
+ """
118
+ Path to the configuration file
119
+ """
120
+
66
121
  _CONFIG_VALUES: dict[str, dict[str, type]]
122
+ """
123
+ Dictionary with sections, their settings and their types
124
+
125
+ Example:
126
+ _CONFIG_VALUES = {"Settings": {"version": str}}
127
+ """
128
+
67
129
  _CONFIG_OBJECTS: dict[str, type]
130
+ """
131
+ Dictionary with names of attributes and child objects
132
+
133
+ Example:
134
+ _CONFIG_OBJECTS = {"settings": SettingsConfig}
135
+ """
68
136
 
69
137
  def __init__(self) -> None:
70
- for k, v in self._CONFIG_OBJECTS.items():
71
- setattr(self, k, v(self))
138
+ for name, data_class in self._CONFIG_OBJECTS.items():
139
+ setattr(self, name, data_class(self))
72
140
 
73
141
 
74
142
  class IAssetsProvider:
143
+ """
144
+ Class for providing various assets data
145
+ """
146
+
75
147
  class IDirectory:
148
+ """
149
+ Class that represents a directory with various assets
150
+ """
151
+
76
152
  _PATH: str = None
153
+ """
154
+ Path to the directory with assets files
155
+
156
+ Example:
157
+ _PATH = "images/"
158
+ """
159
+
160
+ _FILENAME: str = None
161
+ """
162
+ Filename of assets files
163
+
164
+ Example:
165
+ _FILENAME = "{0}.png"
166
+ """
167
+
77
168
  _NAMES: set[str]
169
+ """
170
+ Names of files in the directory
171
+
172
+ Example:
173
+ _NAMES = {"picture1", "picture2"}
174
+ """
78
175
 
79
176
  def __init__(self, parent: IAssetsProvider) -> None:
80
- for i in self._NAMES:
81
- setattr(self, i, parent.file_image(parent._PATH.format(self._PATH.format(i))))
177
+ self._PATH = parent._PATH + self._PATH
178
+
179
+ if isinstance(parent, IAssetsProvider):
180
+ for filename in self._NAMES:
181
+ setattr(self, filename, parent.file_image(self._PATH + self._FILENAME.format(filename)))
82
182
 
83
183
  class INetwork:
184
+ """
185
+ Class that represents a set of images obtained from a network
186
+ """
187
+
84
188
  _URLS: dict[str, str]
189
+ """
190
+ Dictionary with names of attributes and URLs
191
+
192
+ Example:
193
+ _URLS = {"example": "https://example.com/image.png"}
194
+ """
85
195
 
86
196
  def __init__(self, parent: IAssetsProvider) -> None:
87
- for k, v in self._URLS:
88
- setattr(self, k, parent.network_image(v))
197
+ if isinstance(parent, IAssetsProvider):
198
+ for name, url in self._URLS.items():
199
+ setattr(self, name, parent.network_image(url))
200
+
201
+ _PATH: str = utils.get_path("assets/")
202
+ """
203
+ Path to the directory with assets folders
204
+ """
89
205
 
90
- _PATH: str
91
206
  _ASSETS_OBJECTS: dict[str, type]
207
+ """
208
+ Dictionary with names of attributes and child objects
209
+
210
+ Example:
211
+ _ASSETS_OBJECTS = {"images": ImagesAssets, "example": ExampleNetwork}
212
+ """
92
213
 
93
214
  def __init__(self) -> None:
94
- for k, v in self._ASSETS_OBJECTS.items():
95
- setattr(self, k, v(self))
215
+ for name, data_class in self._ASSETS_OBJECTS.items():
216
+ setattr(self, name, data_class(self))
96
217
 
97
218
  @staticmethod
98
219
  def file_image(path: str) -> PIL.Image.Image:
220
+ """
221
+ :return: Image object from a file
222
+ """
223
+
99
224
  with open(path, "rb") as file:
100
225
  return PIL.Image.open(io.BytesIO(file.read()))
101
226
 
102
227
  @staticmethod
103
228
  def network_image(url: str) -> PIL.Image.Image:
229
+ """
230
+ :return: Image object from a URL
231
+ """
232
+
104
233
  return PIL.Image.open(io.BytesIO(requests.get(url).content))
105
234
 
106
235
  @staticmethod
107
236
  def round_corners(image: PIL.Image.Image, radius: int) -> PIL.Image.Image:
237
+ """
238
+ :return: Image with rounded edges of the specified radius
239
+ """
240
+
108
241
  if image.mode != "RGB":
109
242
  image = image.convert("RGB")
110
243
  width, height = image.size
244
+
111
245
  shape = PIL.Image.new("L", (radius * 2, radius * 2), 0)
112
- alpha = PIL.Image.new("L", image.size, "white")
113
246
  PIL.ImageDraw.Draw(shape).ellipse((0, 0, radius * 2, radius * 2), fill=255)
247
+
248
+ alpha = PIL.Image.new("L", image.size, "white")
114
249
  alpha.paste(shape.crop((0, 0, radius, radius)), (0, 0))
115
250
  alpha.paste(shape.crop((0, radius, radius, radius * 2)), (0, height - radius))
116
251
  alpha.paste(shape.crop((radius, 0, radius * 2, radius)), (width - radius, 0))
117
252
  alpha.paste(shape.crop((radius, radius, radius * 2, radius * 2)), (width - radius, height - radius))
118
253
  image.putalpha(alpha)
254
+
119
255
  return image
120
256
 
121
257
 
122
258
  class IStringsProvider:
259
+ """
260
+ Class for providing various strings data
261
+ """
262
+
123
263
  class IStrings:
264
+ """
265
+ Class that represents a container for strings
266
+ """
267
+
124
268
  pass
125
269
 
126
270
  _STRINGS_OBJECTS: dict[str, type]
271
+ """
272
+ Dictionary with names of attributes and child objects
273
+
274
+ Example:
275
+ _STRINGS_OBJECTS = {"localizable": LocalizableStrings}
276
+ """
127
277
 
128
278
  def __init__(self) -> None:
129
- for k, v in self._STRINGS_OBJECTS.items():
130
- setattr(self, k, v())
279
+ for name, data_class in self._STRINGS_OBJECTS.items():
280
+ setattr(self, name, data_class())
281
+
131
282
 
283
+ # endregion
284
+
285
+ # region Managers
286
+
287
+ class IDatabaseManager:
288
+ """
289
+ Class for managing database connections
290
+ """
291
+
292
+ class IDatabase(sqlite3.Connection):
293
+ """
294
+ Class that represents a database connection
295
+ """
132
296
 
133
- # Managers
134
- class IRegistryManager:
135
- class IRegistry:
136
297
  _NAME: str = None
137
- _REGISTRY_VALUES: dict[str, int]
298
+ """
299
+ Name of the database
138
300
 
139
- def __init__(self, parent: IRegistryManager = None) -> None:
140
- if isinstance(parent, IRegistryManager):
141
- self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
142
- self._path = winreg.CreateKey(parent._path, self._NAME)
143
- for i in self._REGISTRY_VALUES.keys():
144
- try:
145
- setattr(self, i, winreg.QueryValueEx(self._path, i)[int()])
146
- except:
147
- setattr(self, i, None)
301
+ Example:
302
+ _NAME = "users"
303
+ """
304
+
305
+ _SQL: str = None
306
+ """
307
+ SQL expression for creating a database
308
+
309
+ Example:
310
+ _SQL = f\"\"\"CREATE TABLE IF NOT EXISTS {_NAME} (user_id INTEGER PRIMARY KEY NOT NULL)\"\"\"
311
+ """
312
+
313
+ _FILENAME: str = "{0}.db"
314
+ """
315
+ File extension of database
316
+ """
317
+
318
+ def __init__(self, parent: IDatabaseManager) -> None:
319
+ if isinstance(parent, IDatabaseManager):
320
+ self._FILENAME = self._FILENAME.format(self._NAME)
321
+
322
+ super().__init__(
323
+ database=parent._PATH + self._FILENAME,
324
+ check_same_thread=False,
325
+ )
326
+
327
+ self._cursor = self.cursor()
328
+ self._db_cursor.execute(self._SQL)
329
+ self.commit()
148
330
 
149
331
  @property
150
- def values(self) -> dict | None:
151
- try:
152
- return {i: getattr(self, i) for i in self._REGISTRY_VALUES}
153
- except:
154
- return None
332
+ def _db_cursor(self) -> sqlite3.Cursor:
333
+ return self._cursor
155
334
 
156
- def refresh(self) -> IRegistryManager.IRegistry:
157
- self.__init__()
158
- return self
335
+ _PATH: str = utils.get_path("db/")
336
+ """
337
+ Path to the directory with databases
338
+ """
159
339
 
160
- def update(self, **kwargs) -> None:
161
- for k, v in kwargs.items():
162
- winreg.SetValueEx(self._path, k, None, self._REGISTRY_VALUES.get(k), v)
163
- setattr(self, k, v)
340
+ _DATABASE_OBJECTS: dict[str, type]
341
+ """
342
+ Dictionary with names of attributes and child objects
164
343
 
165
- _KEY: str
166
- _REGISTRY_VALUES: dict[str, dict[str, int]]
167
- _REGISTRY_OBJECTS: dict[str, type]
168
- _path: winreg.HKEYType
344
+ Example:
345
+ _DATABASE_OBJECTS = {"users": UsersDatabase}
346
+ """
169
347
 
170
348
  def __init__(self) -> None:
171
- self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
172
- for k, v in self._REGISTRY_OBJECTS.items():
173
- setattr(self, k, v(self))
349
+ os.makedirs(self._PATH, exist_ok=True)
350
+
351
+ for name, data_class in self._DATABASE_OBJECTS.items():
352
+ setattr(self, name, data_class(self))
353
+
354
+ def close_all(self) -> None:
355
+ """
356
+ Closes all database connections
357
+ """
358
+ for database in self._DATABASE_OBJECTS.keys():
359
+ getattr(self, database).close()
360
+
361
+
362
+ if sys.platform == "win32":
363
+ import winreg
364
+
365
+
366
+ class IRegistryManager:
367
+ """
368
+ Class for managing data in the Windows Registry
369
+ """
370
+
371
+ class IRegistry:
372
+ """
373
+ Class that represents a key with parameters in the Windows Registry
374
+ """
375
+
376
+ _NAME: str = None
377
+ """
378
+ Name of key in the Windows Registry
379
+
380
+ Example:
381
+ _NAME = "OAuth"
382
+ """
383
+
384
+ _REGISTRY_VALUES: dict[str, int]
385
+ """
386
+ Dictionary with settings and their types
387
+ """
388
+
389
+ _path: winreg.HKEYType
390
+
391
+ def __init__(self, parent: IRegistryManager = None) -> None:
392
+ if isinstance(parent, IRegistryManager):
393
+ self._REGISTRY_VALUES = parent._REGISTRY_VALUES.get(self._NAME)
394
+ self._path = winreg.CreateKey(parent._path, self._NAME)
395
+
396
+ for setting in self._REGISTRY_VALUES.keys():
397
+ try:
398
+ setattr(self, setting, winreg.QueryValueEx(self._path, setting)[int()])
399
+ except:
400
+ setattr(self, setting, None)
174
401
 
175
- def refresh(self) -> IRegistryManager:
176
- self.__init__()
177
- return self
402
+ @property
403
+ def values(self) -> dict | None:
404
+ """
405
+ :return: Values stored in key in the Windows Registry
406
+ """
178
407
 
408
+ try:
409
+ return {setting: getattr(self, setting) for setting in self._REGISTRY_VALUES.keys()}
410
+ except:
411
+ return None
412
+
413
+ def refresh(self) -> IRegistryManager.IRegistry:
414
+ """
415
+ :return: Instance with refreshed values
416
+ """
417
+
418
+ self.__init__()
419
+ return self
420
+
421
+ def update(self, **kwargs) -> None:
422
+ """
423
+ Updates provided settings in the Windows Registry
424
+ """
425
+
426
+ for setting, value in kwargs.items():
427
+ winreg.SetValueEx(self._path, setting, None, self._REGISTRY_VALUES.get(setting), value)
428
+ setattr(self, setting, value)
429
+
430
+ _KEY: str
431
+ """
432
+ Path to key in the Windows Registry
433
+
434
+ Example:
435
+ _KEY = "Software\\\\\\\\diquoks Software\\\\\\\\pyquoks"
436
+ """
437
+
438
+ _REGISTRY_VALUES: dict[str, dict[str, int]]
439
+ """
440
+ Dictionary with keys, their settings and their types
441
+
442
+ Example:
443
+ _REGISTRY_VALUES = {"OAuth": {"access_token": winreg.REG_SZ}}
444
+ """
445
+
446
+ _REGISTRY_OBJECTS: dict[str, type]
447
+ """
448
+ Dictionary with names of attributes and child objects
449
+
450
+ Example:
451
+ _REGISTRY_OBJECTS = {"oauth": OAuthRegistry}
452
+ """
453
+
454
+ _path: winreg.HKEYType
455
+
456
+ def __init__(self) -> None:
457
+ self._path = winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._KEY)
458
+
459
+ for name, data_class in self._REGISTRY_OBJECTS.items():
460
+ setattr(self, name, data_class(self))
461
+
462
+ def refresh(self) -> IRegistryManager:
463
+ """
464
+ :return: Instance with refreshed values
465
+ """
466
+
467
+ self.__init__()
468
+ return self
469
+
470
+
471
+ # endregion
472
+
473
+ # region Services
179
474
 
180
- # Services
181
475
  class LoggerService(logging.Logger):
182
- def __init__(self, name: str, file_handling: bool = True, filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"), level: int = logging.NOTSET, folder_name: str = "logs") -> None:
476
+ """
477
+ Class that provides methods for parallel logging
478
+ """
479
+
480
+ def __init__(
481
+ self,
482
+ name: str, file_handling: bool = True,
483
+ filename: str = datetime.datetime.now().strftime("%d-%m-%y-%H-%M-%S"),
484
+ level: int = logging.NOTSET,
485
+ path: str = utils.get_path("logs/", only_abspath=True),
486
+ ) -> None:
183
487
  super().__init__(name, level)
488
+
184
489
  stream_handler = logging.StreamHandler(sys.stdout)
185
- stream_handler.setFormatter(logging.Formatter(fmt="$levelname $asctime $name - $message", datefmt="%d-%m-%y %H:%M:%S", style="$"))
490
+ stream_handler.setFormatter(
491
+ logging.Formatter(
492
+ fmt="$levelname $asctime $name - $message",
493
+ datefmt="%d-%m-%y %H:%M:%S",
494
+ style="$",
495
+ )
496
+ )
186
497
  self.addHandler(stream_handler)
498
+
187
499
  if file_handling:
188
- os.makedirs(utils.get_path(folder_name, only_abspath=True), exist_ok=True)
189
- file_handler = logging.FileHandler(utils.get_path(f"{folder_name}/{filename}-{name}.log", only_abspath=True), encoding="utf-8")
190
- file_handler.setFormatter(logging.Formatter(fmt="$levelname $asctime - $message", datefmt="%d-%m-%y %H:%M:%S", style="$"))
500
+ os.makedirs(path, exist_ok=True)
501
+
502
+ file_handler = logging.FileHandler(
503
+ path + f"{filename}-{name}.log",
504
+ encoding="utf-8",
505
+ )
506
+ file_handler.setFormatter(
507
+ logging.Formatter(
508
+ fmt="$levelname $asctime - $message",
509
+ datefmt="%d-%m-%y %H:%M:%S",
510
+ style="$",
511
+ ),
512
+ )
191
513
  self.addHandler(file_handler)
192
514
 
193
515
  def log_exception(self, e: Exception) -> None:
516
+ """
517
+ Logs an exception with detailed traceback
518
+ """
519
+
194
520
  self.error(msg=e, exc_info=True)
521
+
522
+ # endregion
pyquoks/localhost.py CHANGED
@@ -1,17 +1,35 @@
1
1
  from __future__ import annotations
2
+ import typing
2
3
  import waitress, flask
3
4
 
4
5
 
5
6
  class ILocalhostFlask(flask.Flask):
6
- _RULES: dict[str, function]
7
+ """
8
+ Class for creating a simple localhost server
9
+ """
10
+
11
+ _RULES: dict[str, typing.Callable]
12
+ """
13
+ Dictionary with rules and functions
14
+
15
+ Example:
16
+ _RULES = {"/": base_redirect}
17
+ """
7
18
 
8
19
  def __init__(self, import_name: str) -> None:
9
20
  super().__init__(import_name)
10
21
 
11
- for k, v in self._RULES.items():
12
- self.add_url_rule(rule=k, view_func=v)
22
+ for rule, view_func in self._RULES.items():
23
+ self.add_url_rule(
24
+ rule=rule,
25
+ view_func=view_func,
26
+ )
13
27
 
14
28
  def serve(self, port: int) -> None:
29
+ """
30
+ Starts this Flask application
31
+ """
32
+
15
33
  waitress.serve(
16
34
  app=self,
17
35
  host="127.0.0.1",
pyquoks/models.py CHANGED
@@ -2,62 +2,137 @@ from __future__ import annotations
2
2
 
3
3
 
4
4
  class IContainer:
5
- _ATTRIBUTES: set[str] = None
5
+ """
6
+ Class for storing lists of models and another parameters
7
+ """
8
+
9
+ _ATTRIBUTES: set[str] | dict[str, set[str]] = None
10
+ """
11
+ Set of parameters that also can be stored one key deep
12
+
13
+ Example:
14
+ _ATTRIBUTES = {"beatmap_id", "score_id"}
15
+
16
+ _ATTRIBUTES = {"attributes": {"max_combo", "star_rating"}}
17
+ """
18
+
6
19
  _DATA: dict[str, type] = None
20
+ """
21
+ Dictionary with name and type of models stored in list inside ``json_data``
22
+
23
+ Example:
24
+ _DATA = {"scores": ScoreModel}
25
+ """
26
+
7
27
  _OBJECTS: dict[str, type] = None
8
- data: dict
28
+ """
29
+ Dictionary with keys of ``json_data`` and types of models stored in a list inside
30
+
31
+ Example:
32
+ _OBJECTS = {"beatmaps": BeatmapModel, "scores": ScoreModel}
33
+ """
34
+
35
+ _data: dict
36
+ """
37
+ Initial data that was passed into object
38
+ """
9
39
 
10
40
  def __init__(self, json_data: dict) -> None:
11
- setattr(self, "data", json_data)
41
+ self._data = json_data
42
+
12
43
  if isinstance(self._ATTRIBUTES, set):
13
- for i in self._ATTRIBUTES:
14
- setattr(self, i, self.data.get(i, None))
44
+ for name in self._ATTRIBUTES:
45
+ setattr(self, name, self._data.get(name, None))
46
+ elif isinstance(self._ATTRIBUTES, dict):
47
+ for key, attributes in self._ATTRIBUTES.items():
48
+ if isinstance(attributes, set):
49
+ for name in attributes:
50
+ try:
51
+ setattr(self, name, self._data.get(key).get(name, None))
52
+ except:
53
+ setattr(self, name, None)
54
+
15
55
  if isinstance(self._DATA, dict):
16
- for k, v in self._DATA.items():
56
+ for name, data_class in self._DATA.items():
17
57
  try:
18
- setattr(self, k, [v(i) for i in self.data])
58
+ setattr(self, name, [data_class(data) for data in self._data])
19
59
  except:
20
- setattr(self, k, None)
60
+ setattr(self, name, None)
21
61
  elif isinstance(self._OBJECTS, dict):
22
- for k, v in self._OBJECTS.items():
62
+ for name, data_class in self._OBJECTS.items():
23
63
  try:
24
- setattr(self, k, [v(i) for i in self.data.get(k)])
64
+ setattr(self, name, [data_class(data) for data in self._data.get(name)])
25
65
  except:
26
- setattr(self, k, None)
66
+ setattr(self, name, None)
27
67
 
28
68
 
29
69
  class IModel:
70
+ """
71
+ Class for storing parameters and models
72
+ """
73
+
30
74
  _ATTRIBUTES: set[str] | dict[str, set[str]] = None
75
+ """
76
+ Set of parameters that also can be stored one key deep
77
+
78
+ Example:
79
+ _ATTRIBUTES = {"beatmap_id", "score_id"}
80
+
81
+ _ATTRIBUTES = {"attributes": {"max_combo", "star_rating"}}
82
+ """
83
+
31
84
  _OBJECTS: dict[str, type] = None
32
- data: dict | list[dict]
85
+ """
86
+ Dictionary with attributes and their models
87
+
88
+ Example:
89
+ _OBJECTS = {"score": ScoreModel}
90
+ """
91
+
92
+ _data: dict | list[dict]
93
+ """
94
+ Initial data that was passed into object
95
+ """
33
96
 
34
97
  def __init__(self, json_data: dict | list[dict]) -> None:
35
- setattr(self, "data", json_data)
98
+ self._data = json_data
99
+
36
100
  if isinstance(self._ATTRIBUTES, set):
37
- for i in self._ATTRIBUTES:
38
- setattr(self, i, self.data.get(i, None))
101
+ for name in self._ATTRIBUTES:
102
+ setattr(self, name, self._data.get(name, None))
39
103
  elif isinstance(self._ATTRIBUTES, dict):
40
- for k, v in self._ATTRIBUTES.items():
41
- if isinstance(v, set):
42
- for i in v:
104
+ for key, attributes in self._ATTRIBUTES.items():
105
+ if isinstance(attributes, set):
106
+ for name in attributes:
43
107
  try:
44
- setattr(self, i, self.data.get(k).get(i, None))
108
+ setattr(self, name, self._data.get(key).get(name, None))
45
109
  except:
46
- setattr(self, i, None)
110
+ setattr(self, name, None)
111
+
47
112
  if isinstance(self._OBJECTS, dict):
48
- for k, v in self._OBJECTS.items():
113
+ for name, data_class in self._OBJECTS.items():
49
114
  try:
50
- setattr(self, k, v(self.data.get(k)))
115
+ setattr(self, name, data_class(self._data.get(name)))
51
116
  except:
52
- setattr(self, k, None)
117
+ setattr(self, name, None)
53
118
 
54
119
 
55
120
  class IValues:
121
+ """
122
+ Class for storing various parameters and values
123
+ """
124
+
56
125
  _ATTRIBUTES: set[str] = None
126
+ """
127
+ Attributes that can be stored in this class
128
+
129
+ Example:
130
+ _ATTRIBUTES = {"settings", "path"}
131
+ """
57
132
 
58
133
  def __init__(self, **kwargs) -> None:
59
- for i in self._ATTRIBUTES:
60
- setattr(self, i, kwargs.get(i, None))
134
+ for name in self._ATTRIBUTES:
135
+ setattr(self, name, kwargs.get(name, None))
61
136
 
62
137
  def update(self, **kwargs) -> None:
63
138
  self.__init__(**kwargs)
pyquoks/utils.py CHANGED
@@ -3,7 +3,12 @@ import sys, os
3
3
 
4
4
 
5
5
  def get_path(relative_path: str, only_abspath: bool = False) -> str:
6
+ """
7
+ :return: Absolute path for provided relative path
8
+ """
9
+
6
10
  try:
11
+ # noinspection PyUnresolvedReferences
7
12
  base_path = sys._MEIPASS
8
13
  except:
9
14
  base_path = os.path.abspath(".")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyquoks
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: Пакет PyPI для часто используемых модулей в проектах diquoks
5
5
  Home-page: https://diquoks.ru
6
6
  Author: Denis Titovets
@@ -12,7 +12,7 @@ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: blinker==1.9.0
15
- Requires-Dist: certifi==2025.8.3
15
+ Requires-Dist: certifi==2025.10.5
16
16
  Requires-Dist: charset-normalizer==3.4.3
17
17
  Requires-Dist: click==8.3.0
18
18
  Requires-Dist: colorama==0.4.6
@@ -20,7 +20,7 @@ Requires-Dist: Flask==3.1.2
20
20
  Requires-Dist: idna==3.10
21
21
  Requires-Dist: itsdangerous==2.2.0
22
22
  Requires-Dist: Jinja2==3.1.6
23
- Requires-Dist: MarkupSafe==3.0.2
23
+ Requires-Dist: MarkupSafe==3.0.3
24
24
  Requires-Dist: pillow==11.3.0
25
25
  Requires-Dist: requests==2.32.5
26
26
  Requires-Dist: urllib3==2.5.0
@@ -46,6 +46,7 @@ Dynamic: license-file
46
46
 
47
47
  #### Связь с разработчиком
48
48
 
49
+ - [План разработки pyquoks](https://www.icloud.com/notes/0e0C-Bm4IkqXuBYqJrq00yhog)
49
50
  - [Telegram для связи](https://t.me/diquoks)
50
51
  - [Почта для связи](mailto:diquoks@yandex.ru)
51
52
 
@@ -0,0 +1,10 @@
1
+ pyquoks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pyquoks/data.py,sha256=5PkERLBiNmuEYy8mYTe2AWUUEPt3lAtH5tG9TlyBl4w,15399
3
+ pyquoks/localhost.py,sha256=2sl1CovYrnIzBYW-eKDIYOfi5bUsSVTAdQobzAHpIys,828
4
+ pyquoks/models.py,sha256=kdduuHgh8RLe8_S5Lmv3jS6hqsZG-oYQhPTgk2C11ug,4171
5
+ pyquoks/utils.py,sha256=lcHZyIFIflGMqAhDkqm8GWOUFwb2f-IHgIPZxEiHBz0,484
6
+ pyquoks-1.3.0.dist-info/licenses/LICENSE,sha256=eEd8UIYxvKUY7vqrV1XTFo70_FQdiW6o1fznseCXRJs,1095
7
+ pyquoks-1.3.0.dist-info/METADATA,sha256=sCQPZSFYT7zfM4hFygVOeCWHXz1QEt7i9oOzhmKGNgw,1802
8
+ pyquoks-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ pyquoks-1.3.0.dist-info/top_level.txt,sha256=4Eqn44TCCp-D8V6To92e8-KKr49ZGf0dCcsdRQmPkhc,8
10
+ pyquoks-1.3.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- pyquoks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pyquoks/data.py,sha256=9vL7UciizlTp5-wHnvdTey2FuqGxC5wf-NOKX9wpN-I,7752
3
- pyquoks/localhost.py,sha256=X3JPGybKjiQLl7jqkyGFt_1D2jTNyFr_2qGu9FmKceI,479
4
- pyquoks/models.py,sha256=_2mxn7nBZzm8jALJSKcFHal06RGh2whHYp7De-bBxqk,2187
5
- pyquoks/utils.py,sha256=uQd1GoMOBHzab6xfAVovgG89MoXKKDqhD3ep8bs1lc4,362
6
- pyquoks-1.2.1.dist-info/licenses/LICENSE,sha256=eEd8UIYxvKUY7vqrV1XTFo70_FQdiW6o1fznseCXRJs,1095
7
- pyquoks-1.2.1.dist-info/METADATA,sha256=ZreScvmW3tV8Dj7r6l2Be0sH7trgjr25GQKorGVnskA,1702
8
- pyquoks-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- pyquoks-1.2.1.dist-info/top_level.txt,sha256=4Eqn44TCCp-D8V6To92e8-KKr49ZGf0dCcsdRQmPkhc,8
10
- pyquoks-1.2.1.dist-info/RECORD,,