dublib 0.28.0__tar.gz → 0.28.2__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.
- {dublib-0.28.0/src/dublib.egg-info → dublib-0.28.2}/PKG-INFO +3 -1
- {dublib-0.28.0 → dublib-0.28.2}/pyproject.toml +10 -4
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Templates/Bus.py +1 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Templates/__init__.py +2 -2
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Terminalyzer/Command/Parser.py +66 -17
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Terminalyzer/Helper.py +3 -2
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Terminalyzer/__init__.py +7 -5
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/TextStyler/__init__.py +1 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Validators.py +9 -12
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/readline.py +4 -2
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Engine/Configurator.py +6 -5
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Engine/GetText.py +1 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Engine/Patcher.py +3 -4
- dublib-0.28.2/src/dublib/Exceptions/CLI/__init__.py +2 -0
- dublib-0.28.2/src/dublib/Exceptions/Engine/__init__.py +1 -0
- dublib-0.28.2/src/dublib/Exceptions/__init__.py +4 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Methods/Data.py +6 -4
- dublib-0.28.2/src/dublib/Methods/Decorators.py +53 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Methods/Filesystem.py +50 -35
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Methods/System.py +2 -6
- dublib-0.28.2/src/dublib/Polyglot/__init__.py +2 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/TelebotUtils/Cache.py +31 -23
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/TelebotUtils/Master/Decorators.py +1 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/TelebotUtils/Master/__init__.py +1 -2
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/TelebotUtils/Users.py +9 -10
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/WebRequestor.py +4 -4
- dublib-0.28.2/src/dublib/__init__.py +1 -0
- {dublib-0.28.0 → dublib-0.28.2/src/dublib.egg-info}/PKG-INFO +3 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib.egg-info/SOURCES.txt +0 -1
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib.egg-info/requires.txt +2 -0
- dublib-0.28.0/src/dublib/Exceptions/CLI/__init__.py +0 -2
- dublib-0.28.0/src/dublib/Exceptions/Engine/__init__.py +0 -1
- dublib-0.28.0/src/dublib/Exceptions/__init__.py +0 -1
- dublib-0.28.0/src/dublib/Methods/Decorators.py +0 -23
- dublib-0.28.0/src/dublib/Polyglot/__init__.py +0 -2
- dublib-0.28.0/src/dublib/TelebotUtils/__init__.py +0 -3
- dublib-0.28.0/src/dublib/__init__.py +0 -1
- {dublib-0.28.0 → dublib-0.28.2}/LICENSE +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/README.md +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/setup.cfg +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/Terminalyzer/Command/Definition.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/TextStyler/Codes.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/TextStyler/Escapes.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/CLI/TextStyler/FastStyler.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Core.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Engine/Bus.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Exceptions/CLI/Terminalyzer.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Exceptions/CLI/Validators.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Exceptions/Engine/Bus.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Exceptions/TelebotUtils.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Exceptions/WebRequestor.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Polyglot/HTML.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/Polyglot/Markdown.py +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib/py.typed +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib.egg-info/dependency_links.txt +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/src/dublib.egg-info/top_level.txt +0 -0
- {dublib-0.28.0 → dublib-0.28.2}/tests/test_Polyglot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dublib
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.2
|
|
4
4
|
Summary: Коллекция полезных модулей от DUB1401.
|
|
5
5
|
Author-email: DUB1401 <vlad.milosta@outlook.com>
|
|
6
6
|
License: This is free and unencumbered software released into the public domain.
|
|
@@ -52,6 +52,7 @@ Requires-Dist: httpx[http2]
|
|
|
52
52
|
Requires-Dist: orjson
|
|
53
53
|
Requires-Dist: prettytable
|
|
54
54
|
Requires-Dist: requests
|
|
55
|
+
Requires-Dist: pathvalidate
|
|
55
56
|
Requires-Dist: pyyaml
|
|
56
57
|
Requires-Dist: pydantic
|
|
57
58
|
Requires-Dist: validators
|
|
@@ -65,6 +66,7 @@ Requires-Dist: pyTelegramBotAPI; extra == "telebot"
|
|
|
65
66
|
Provides-Extra: dev
|
|
66
67
|
Requires-Dist: mypy; extra == "dev"
|
|
67
68
|
Requires-Dist: pytest; extra == "dev"
|
|
69
|
+
Requires-Dist: ruff; extra == "dev"
|
|
68
70
|
Requires-Dist: Sphinx; extra == "dev"
|
|
69
71
|
Requires-Dist: sphinx-rtd-theme; extra == "dev"
|
|
70
72
|
Dynamic: license-file
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "dublib"
|
|
6
|
-
version = "0.28.
|
|
6
|
+
version = "0.28.2"
|
|
7
7
|
description = "Коллекция полезных модулей от DUB1401."
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "DUB1401", email = "vlad.milosta@outlook.com"}
|
|
@@ -20,6 +20,7 @@ dependencies = [
|
|
|
20
20
|
"orjson",
|
|
21
21
|
"prettytable",
|
|
22
22
|
"requests",
|
|
23
|
+
"pathvalidate",
|
|
23
24
|
"pyyaml",
|
|
24
25
|
"pydantic",
|
|
25
26
|
"validators",
|
|
@@ -48,6 +49,7 @@ telebot = [
|
|
|
48
49
|
dev = [
|
|
49
50
|
"mypy",
|
|
50
51
|
"pytest",
|
|
52
|
+
"ruff",
|
|
51
53
|
"Sphinx",
|
|
52
54
|
"sphinx-rtd-theme"
|
|
53
55
|
]
|
|
@@ -57,9 +59,13 @@ ignore_missing_imports = true
|
|
|
57
59
|
check_untyped_defs = true
|
|
58
60
|
non_interactive = true
|
|
59
61
|
follow_imports = "silent"
|
|
60
|
-
files = [
|
|
61
|
-
|
|
62
|
-
]
|
|
62
|
+
files = ["src"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
include = ["src/*"]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
ignore = ["E701"]
|
|
63
69
|
|
|
64
70
|
[project.urls]
|
|
65
71
|
Documentation = "https://github.com/DUB1401/dublib/tree/main/docs"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .. import readline
|
|
1
|
+
from .. import readline as readline
|
|
2
2
|
|
|
3
3
|
#==========================================================================================#
|
|
4
4
|
# >>>>> ШАБЛОНЫ ВВОДА-ВЫВОДА <<<<< #
|
|
@@ -29,6 +29,6 @@ def Confirmation(text: str, question: str | None = None, yes: str | None = None,
|
|
|
29
29
|
InputLine = input(f"{text}\n{question} [{yes}/{no}]: ").strip().lower()
|
|
30
30
|
if InputLine == yes.lower(): Response = True
|
|
31
31
|
if InputLine == no.lower(): Response = False
|
|
32
|
-
if Response
|
|
32
|
+
if Response is not None: break
|
|
33
33
|
|
|
34
34
|
return Response
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
from .... import Exceptions
|
|
2
2
|
|
|
3
|
-
from typing import cast, Sequence, TYPE_CHECKING
|
|
3
|
+
from typing import cast, overload, Sequence, TypeVar, TYPE_CHECKING
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from .Definition import _Flag, _Key, Command
|
|
9
9
|
|
|
10
|
+
#==========================================================================================#
|
|
11
|
+
# >>>>> ПЕРЕМЕННЫЕ ТИПОВ <<<<< #
|
|
12
|
+
#==========================================================================================#
|
|
13
|
+
|
|
14
|
+
_EXPECTED_TYPE = TypeVar("_EXPECTED_TYPE", bool, float, int, Path, str, datetime)
|
|
15
|
+
|
|
10
16
|
#==========================================================================================#
|
|
11
17
|
# >>>>> ПРЕДСТАВЛЕНИЯ ОБРАБОТАННЫХ ПАРАМЕТРОВ <<<<< #
|
|
12
18
|
#==========================================================================================#
|
|
@@ -135,9 +141,9 @@ class _ParsedCommandParameters:
|
|
|
135
141
|
|
|
136
142
|
Result = list()
|
|
137
143
|
|
|
138
|
-
for
|
|
139
|
-
for Parameter in
|
|
140
|
-
if type(Parameter)
|
|
144
|
+
for CurrentSequence in (self.__Positions.values(), self.__BasePosition):
|
|
145
|
+
for Parameter in CurrentSequence:
|
|
146
|
+
if type(Parameter) is required_type: Result.append(Parameter)
|
|
141
147
|
|
|
142
148
|
return tuple(Result)
|
|
143
149
|
|
|
@@ -259,25 +265,49 @@ class ParsedCommandData:
|
|
|
259
265
|
|
|
260
266
|
return False
|
|
261
267
|
|
|
262
|
-
|
|
268
|
+
@overload
|
|
269
|
+
def get_key_value(self, key: str, expected_type: type[_EXPECTED_TYPE], exception: bool = False) -> _EXPECTED_TYPE | None: ...
|
|
270
|
+
|
|
271
|
+
@overload
|
|
272
|
+
def get_key_value(self, key: str, expected_type: None = None, exception: bool = False) -> bool | float | int | Path | str | datetime | None: ...
|
|
273
|
+
|
|
274
|
+
def get_key_value(self, key: str, expected_type: type | None = None, exception: bool = False) -> bool | float | int | Path | str | datetime | None:
|
|
263
275
|
"""
|
|
264
276
|
Возвращает значение активированного ключа.
|
|
265
277
|
|
|
266
278
|
:param key: Название ключа.
|
|
267
279
|
:type key: str
|
|
268
|
-
:param
|
|
280
|
+
:param expected_type: Ожидаемый тип значения. Если тип не соответствует, будет выброшено исключение `TypeError`. Проверяются только значения, отличные от `None`.
|
|
281
|
+
:type expected_type: type[bool | float | int | Path | str | datetime]
|
|
282
|
+
:param exception: Указывает, нужно ли выбрасывать исключение при отсутствии ключа.
|
|
269
283
|
:type exception: bool
|
|
270
|
-
:raises KeyError: Выбрасывается в случае активации соответствующего параметра и запросе значения отсутствующего ключа.
|
|
271
284
|
:return: Значение ключа или `None` при отсутствующем ключе.
|
|
272
285
|
:rtype: bool | float | int | Path | str | datetime | None
|
|
286
|
+
:raises KeyError: Ключ не найден. Отключается параметром `exception`.
|
|
287
|
+
:raises TypeError: Ожидается другой тип данных.
|
|
273
288
|
"""
|
|
274
289
|
|
|
275
|
-
|
|
276
|
-
|
|
290
|
+
ValueToReturn: bool | float | int | Path | str | datetime | None = None
|
|
291
|
+
IsKeyFound: bool = False
|
|
277
292
|
|
|
278
|
-
|
|
293
|
+
for CurrentKey in self.__ParsedData.keys:
|
|
294
|
+
if key == CurrentKey.name or key in CurrentKey.aliases:
|
|
295
|
+
ValueToReturn = CurrentKey.value
|
|
296
|
+
IsKeyFound = True
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
if not IsKeyFound:
|
|
300
|
+
if exception:
|
|
301
|
+
raise KeyError(key)
|
|
302
|
+
else:
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
if expected_type and ValueToReturn is not None:
|
|
306
|
+
ReturnedType = type(ValueToReturn)
|
|
307
|
+
if ReturnedType is not expected_type:
|
|
308
|
+
raise TypeError(f"Expected \"{expected_type}\", but on key {ReturnedType}.")
|
|
279
309
|
|
|
280
|
-
return
|
|
310
|
+
return ValueToReturn
|
|
281
311
|
|
|
282
312
|
def get_position_parameter(self, position_name: str) -> _ParsedArgument | _ParsedFlag | _ParsedKey | None:
|
|
283
313
|
"""
|
|
@@ -291,25 +321,44 @@ class ParsedCommandData:
|
|
|
291
321
|
"""
|
|
292
322
|
|
|
293
323
|
return self.__ParsedData.positions[position_name]
|
|
294
|
-
|
|
295
|
-
|
|
324
|
+
|
|
325
|
+
@overload
|
|
326
|
+
def get_position_value(self, position_name: str, expected_type: type[_EXPECTED_TYPE]) -> _EXPECTED_TYPE: ...
|
|
327
|
+
|
|
328
|
+
@overload
|
|
329
|
+
def get_position_value(self, position_name: str, expected_type: None = None) -> bool | float | int | Path | str | datetime | None: ...
|
|
330
|
+
|
|
331
|
+
def get_position_value(self, position_name: str, expected_type: type | None = None) -> bool | float | int | Path | str | datetime | None:
|
|
296
332
|
"""
|
|
297
333
|
Для аргументов и ключей на позиции возвращает значение, для флагов – `True` при активации.
|
|
298
334
|
|
|
299
335
|
:param position_name: Имя позиции.
|
|
300
336
|
:type position_name: str
|
|
301
|
-
:
|
|
337
|
+
:param expected_type: Ожидаемый тип значения. Если тип не соответствует, будет выбрашено исключение `TypeError`.
|
|
338
|
+
:type expected_type: type[bool | float | int | Path | str | datetime]
|
|
339
|
+
:return: Параметр позиции или `None` при пустой позиции. Для флага возвращает основное имя флага.
|
|
302
340
|
:rtype: bool | float | int | Path | str | datetime | None
|
|
303
341
|
:raises KeyError: Позиция не обнаружена.
|
|
342
|
+
:raises TypeError: Ожидается другой тип данных.
|
|
304
343
|
"""
|
|
305
344
|
|
|
306
345
|
ParsedParameter = self.get_position_parameter(position_name)
|
|
307
|
-
|
|
346
|
+
ValueToReturn: bool | float | int | Path | str | datetime | None = None
|
|
308
347
|
|
|
309
|
-
if
|
|
348
|
+
if not ParsedParameter:
|
|
349
|
+
ValueToReturn = None
|
|
350
|
+
elif type(ParsedParameter) is _ParsedFlag:
|
|
351
|
+
ValueToReturn = ParsedParameter.name
|
|
310
352
|
else:
|
|
311
353
|
ParsedParameter = cast(_ParsedArgument | _ParsedKey, ParsedParameter)
|
|
312
|
-
|
|
354
|
+
ValueToReturn = ParsedParameter.value
|
|
355
|
+
|
|
356
|
+
if expected_type:
|
|
357
|
+
ReturnedType = type(ValueToReturn)
|
|
358
|
+
if ReturnedType is not expected_type:
|
|
359
|
+
raise TypeError(f"Expected \"{expected_type}\", but on position {ReturnedType}.")
|
|
360
|
+
|
|
361
|
+
return ValueToReturn
|
|
313
362
|
|
|
314
363
|
def to_dict(self) -> dict:
|
|
315
364
|
"""
|
|
@@ -296,8 +296,9 @@ class Helper:
|
|
|
296
296
|
#---> Генерация таблицы.
|
|
297
297
|
#==========================================================================================#
|
|
298
298
|
Tables = list()
|
|
299
|
+
Categories: list[str | None] = list(CommandsCategories.keys())
|
|
299
300
|
|
|
300
|
-
for Category in
|
|
301
|
+
for Category in Categories:
|
|
301
302
|
HelpTable: dict[str, list] = {
|
|
302
303
|
"Commands": [],
|
|
303
304
|
"Descriptions": []
|
|
@@ -320,7 +321,7 @@ class Helper:
|
|
|
320
321
|
|
|
321
322
|
TableObject.align = "l"
|
|
322
323
|
TableString = TableObject.get_string()
|
|
323
|
-
if
|
|
324
|
+
if len(Categories) > 1: TableString = " " * 8 + TableString.lstrip()
|
|
324
325
|
Tables.append(TableString)
|
|
325
326
|
|
|
326
327
|
self.__Callback("\n\n".join(Tables))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .Command.Parser import _CommandParser, ParsedCommandData
|
|
2
|
+
from ..Validators import ValidableTypes as ValidableTypes
|
|
2
3
|
from .Command.Definition import Command
|
|
3
|
-
from ..Validators import ValidableTypes
|
|
4
4
|
from .Helper import Helper
|
|
5
5
|
|
|
6
6
|
from ...Core import LOGS_HANDLER
|
|
@@ -46,7 +46,7 @@ class Terminalyzer:
|
|
|
46
46
|
|
|
47
47
|
for CurrentPosition in command.positions:
|
|
48
48
|
if CurrentPosition.description and len(CurrentPosition.parameters) == 1 and CurrentPosition.parameters[0].description:
|
|
49
|
-
LOGGER.warning(f"Command: \"{command.name}\".
|
|
49
|
+
LOGGER.warning(f"Command: \"{command.name}\". Parameter description suppressed by position description on \"{CurrentPosition.name}\".")
|
|
50
50
|
|
|
51
51
|
def __CheckCommandForEmptyPositions(self, command: Command):
|
|
52
52
|
"""
|
|
@@ -119,6 +119,7 @@ class Terminalyzer:
|
|
|
119
119
|
:raises Exceptions.CLI.Terminalyzer.MultipleCommandDefinition: Множественное определение команды.
|
|
120
120
|
"""
|
|
121
121
|
|
|
122
|
+
commands = commands.copy()
|
|
122
123
|
if not self.__CommandName: return None
|
|
123
124
|
|
|
124
125
|
self.__ValidateCommandsDefinitions(commands)
|
|
@@ -133,8 +134,9 @@ class Terminalyzer:
|
|
|
133
134
|
break
|
|
134
135
|
|
|
135
136
|
if self.__Helper.is_enabled and CommandData and CommandData.name == "help":
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
if CommandData.arguments:
|
|
138
|
+
CommandName = cast(str, CommandData.arguments[0])
|
|
139
|
+
self.__Helper.generate_help_command(commands, CommandName, CommandData.check_flag("-t"))
|
|
138
140
|
else: self.__Helper.generate_help_list(commands)
|
|
139
141
|
|
|
140
142
|
return CommandData
|
|
@@ -147,7 +149,7 @@ class Terminalyzer:
|
|
|
147
149
|
:type parameters: Sequence[str] | None
|
|
148
150
|
"""
|
|
149
151
|
|
|
150
|
-
self.__Input = input
|
|
152
|
+
self.__Input = list(input) if input else sys.argv[1:]
|
|
151
153
|
|
|
152
154
|
self.__CommandName = None
|
|
153
155
|
self.__Parameters: tuple = tuple()
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from enum import Enum
|
|
8
8
|
|
|
9
|
+
from pathvalidate import is_valid_filepath
|
|
9
10
|
import dateparser
|
|
10
11
|
import validators
|
|
11
12
|
|
|
@@ -149,7 +150,7 @@ class Validator_Base64(BaseValidator[str]):
|
|
|
149
150
|
|
|
150
151
|
try:
|
|
151
152
|
Result = validators.base64(value)
|
|
152
|
-
if type(Result)
|
|
153
|
+
if type(Result) is bool: return Result
|
|
153
154
|
except validators.ValidationError: pass
|
|
154
155
|
|
|
155
156
|
return False
|
|
@@ -216,7 +217,7 @@ class Validator_Datetime(BaseValidator[datetime]):
|
|
|
216
217
|
try:
|
|
217
218
|
dateparser.parse(value)
|
|
218
219
|
return True
|
|
219
|
-
except: pass
|
|
220
|
+
except (ValueError, TypeError): pass
|
|
220
221
|
|
|
221
222
|
return False
|
|
222
223
|
|
|
@@ -248,7 +249,7 @@ class Validator_Email(BaseValidator[str]):
|
|
|
248
249
|
|
|
249
250
|
try:
|
|
250
251
|
Result = validators.email(value)
|
|
251
|
-
if type(Result)
|
|
252
|
+
if type(Result) is bool: return Result
|
|
252
253
|
except validators.ValidationError: pass
|
|
253
254
|
|
|
254
255
|
return False
|
|
@@ -337,7 +338,7 @@ class Validator_IPv4(BaseValidator[str]):
|
|
|
337
338
|
|
|
338
339
|
try:
|
|
339
340
|
Result = validators.ipv4(value)
|
|
340
|
-
if type(Result)
|
|
341
|
+
if type(Result) is bool: return Result
|
|
341
342
|
except validators.ValidationError: pass
|
|
342
343
|
|
|
343
344
|
return False
|
|
@@ -370,7 +371,7 @@ class Validator_IPv6(BaseValidator[str]):
|
|
|
370
371
|
|
|
371
372
|
try:
|
|
372
373
|
Result = validators.ipv6(value)
|
|
373
|
-
if type(Result)
|
|
374
|
+
if type(Result) is bool: return Result
|
|
374
375
|
except validators.ValidationError: pass
|
|
375
376
|
|
|
376
377
|
return False
|
|
@@ -439,10 +440,7 @@ class Validator_Path(BaseValidator[Path]):
|
|
|
439
440
|
:rtype: bool
|
|
440
441
|
"""
|
|
441
442
|
|
|
442
|
-
|
|
443
|
-
Path(value)
|
|
444
|
-
return True
|
|
445
|
-
except: return False
|
|
443
|
+
return is_valid_filepath(value)
|
|
446
444
|
|
|
447
445
|
class Validator_UnsignedInteger(BaseValidator[int]):
|
|
448
446
|
|
|
@@ -500,7 +498,7 @@ class Validator_URL(BaseValidator[str]):
|
|
|
500
498
|
|
|
501
499
|
try:
|
|
502
500
|
Result = validators.url(value)
|
|
503
|
-
if type(Result)
|
|
501
|
+
if type(Result) is bool: return Result
|
|
504
502
|
except validators.ValidationError: pass
|
|
505
503
|
|
|
506
504
|
return False
|
|
@@ -531,8 +529,7 @@ class Validator_ValidPath(BaseValidator[Path]):
|
|
|
531
529
|
:rtype: bool
|
|
532
530
|
"""
|
|
533
531
|
|
|
534
|
-
|
|
535
|
-
except: return False
|
|
532
|
+
return Path(value).exists()
|
|
536
533
|
|
|
537
534
|
#==========================================================================================#
|
|
538
535
|
# >>>>> ПЕРЕЧИСЛЕНИЕ ВАЛИДАТОРОВ <<<<< #
|
|
@@ -17,7 +17,7 @@ class Config:
|
|
|
17
17
|
#==========================================================================================#
|
|
18
18
|
|
|
19
19
|
__EXCEPTION_DISABLED = object()
|
|
20
|
-
__INSTANCES: "dict[
|
|
20
|
+
__INSTANCES: "dict[str, Config]" = dict()
|
|
21
21
|
|
|
22
22
|
#==========================================================================================#
|
|
23
23
|
# >>>>> СВОЙСТВА <<<<< #
|
|
@@ -84,18 +84,19 @@ class Config:
|
|
|
84
84
|
if args[0] not in cls.__INSTANCES:
|
|
85
85
|
Instance = super().__new__(cls)
|
|
86
86
|
Instance._IS_INITIALIZED = False
|
|
87
|
-
|
|
87
|
+
ConfigPath = Path(args[0])
|
|
88
|
+
cls.__INSTANCES[ConfigPath.as_posix()] = Instance
|
|
88
89
|
|
|
89
90
|
return cls.__INSTANCES[args[0]]
|
|
90
91
|
|
|
91
|
-
def __init__(self, path: PathLike):
|
|
92
|
+
def __init__(self, path: str | PathLike[str]):
|
|
92
93
|
"""
|
|
93
94
|
Контейнер конфигурации.
|
|
94
95
|
|
|
95
96
|
Может работать с файлами JSON и YAML. Определение происходит по расширению файла, в противном случае предпочтение отдаётся JSON.
|
|
96
97
|
|
|
97
98
|
:param path: Путь к файлу параметров. На данный момент поддерживается только JSON.
|
|
98
|
-
:type path: PathLike
|
|
99
|
+
:type path: str | PathLike[str]
|
|
99
100
|
"""
|
|
100
101
|
|
|
101
102
|
if self._IS_INITIALIZED: return
|
|
@@ -248,7 +249,7 @@ class Config:
|
|
|
248
249
|
def unload(self):
|
|
249
250
|
"""Выгружает контейнер из памяти."""
|
|
250
251
|
|
|
251
|
-
del self.__INSTANCES[self.path]
|
|
252
|
+
del self.__INSTANCES[self.path.as_posix()]
|
|
252
253
|
|
|
253
254
|
def validate(self, model: type[BaseModel] | None = None):
|
|
254
255
|
"""
|
|
@@ -58,7 +58,7 @@ class GetText:
|
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
60
|
if languages:
|
|
61
|
-
if type(languages)
|
|
61
|
+
if type(languages) is str: languages = [languages]
|
|
62
62
|
|
|
63
63
|
try: return gettext.translation(GetText._DOMAIN, GetText._PATH, languages = languages).gettext(message)
|
|
64
64
|
except FileNotFoundError: return GetText.gettext(message)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from ..Methods.Filesystem import ReadTextFile, WriteTextFile
|
|
2
|
-
from ..Methods.Data import ToSequence
|
|
3
2
|
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from os import PathLike
|
|
@@ -34,12 +33,12 @@ class Patch:
|
|
|
34
33
|
# >>>>> МЕТОДЫ <<<<< #
|
|
35
34
|
#==========================================================================================#
|
|
36
35
|
|
|
37
|
-
def __init__(self, path: PathLike):
|
|
36
|
+
def __init__(self, path: str | PathLike[str]):
|
|
38
37
|
"""
|
|
39
38
|
Патч.
|
|
40
39
|
|
|
41
40
|
:param path: Путь к файлу.
|
|
42
|
-
:type path: PathLike
|
|
41
|
+
:type path: str | PathLike[str]
|
|
43
42
|
"""
|
|
44
43
|
|
|
45
44
|
self.__Path = Path(path)
|
|
@@ -93,7 +92,7 @@ class Patch:
|
|
|
93
92
|
:type count: int | None
|
|
94
93
|
"""
|
|
95
94
|
|
|
96
|
-
if count
|
|
95
|
+
if count is None: count = -1
|
|
97
96
|
self.__TextLines = self.text.replace(old, new, count).split("\n")
|
|
98
97
|
|
|
99
98
|
def replace_by_regex(self, regex: str, text: str, count: int = 0):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import Bus as Bus
|
|
@@ -18,9 +18,9 @@ def Copy(data: Any) -> Any:
|
|
|
18
18
|
:return: Копия объекта.
|
|
19
19
|
:rtype: Any
|
|
20
20
|
"""
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
try: return orjson.loads(orjson.dumps(data))
|
|
23
|
-
except: return copy.deepcopy(data)
|
|
23
|
+
except (orjson.JSONDecodeError, orjson.JSONEncodeError): return copy.deepcopy(data)
|
|
24
24
|
|
|
25
25
|
def StringifyFloat(number: float, round_factor: int = 2) -> str:
|
|
26
26
|
"""
|
|
@@ -47,7 +47,7 @@ def StringToBool(value: str, literals: Sequence[str] = ("false", "0")) -> bool:
|
|
|
47
47
|
|
|
48
48
|
:param value: Преобразуемая строка.
|
|
49
49
|
:type value: str
|
|
50
|
-
:param literals: Набор строк, интерпретируемых как `False`.
|
|
50
|
+
:param literals: Набор строк, интерпретируемых как `False`. Нечувствителен к регистру.
|
|
51
51
|
:type literals: Sequence[str]
|
|
52
52
|
:return: Результирующее значение.
|
|
53
53
|
:rtype: bool
|
|
@@ -59,8 +59,10 @@ def StringToBool(value: str, literals: Sequence[str] = ("false", "0")) -> bool:
|
|
|
59
59
|
|
|
60
60
|
@overload
|
|
61
61
|
def ToSequence(value: Any, target_type: type[list]) -> list: ...
|
|
62
|
+
|
|
62
63
|
@overload
|
|
63
64
|
def ToSequence(value: Any, target_type: type[set]) -> set: ...
|
|
65
|
+
|
|
64
66
|
@overload
|
|
65
67
|
def ToSequence(value: Any, target_type: type[tuple] = ...) -> tuple: ...
|
|
66
68
|
|
|
@@ -76,7 +78,7 @@ def ToSequence(value: Any, target_type: type[list | set | tuple] = tuple) -> lis
|
|
|
76
78
|
:rtype: list | set | tuple
|
|
77
79
|
"""
|
|
78
80
|
|
|
79
|
-
if type(value)
|
|
81
|
+
if type(value) is target_type: return value
|
|
80
82
|
if type(value) in (list, set, tuple): return target_type(value)
|
|
81
83
|
|
|
82
84
|
return target_type((value,))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from ..CLI.TextStyler import FastStyler
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
def deprecated(message: str | None = None):
|
|
7
|
+
"""
|
|
8
|
+
Декоратор: помечает функцию как устаревшую.
|
|
9
|
+
|
|
10
|
+
:param message: Сопровождающее сообщение.
|
|
11
|
+
:type message: str | None
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def decorator(function):
|
|
15
|
+
|
|
16
|
+
@functools.wraps(function)
|
|
17
|
+
def Wrapper(*args, **kwargs):
|
|
18
|
+
Message = f" {message}" or ""
|
|
19
|
+
Function = FastStyler(function.__name__).decorate.bold
|
|
20
|
+
warnings.warn(f"{Function} is deprecated.{Message}", DeprecationWarning, stacklevel = 2)
|
|
21
|
+
|
|
22
|
+
return function(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
return Wrapper
|
|
25
|
+
|
|
26
|
+
return decorator
|
|
27
|
+
|
|
28
|
+
def run_before_method(method_name: str):
|
|
29
|
+
"""
|
|
30
|
+
Декоратор: запускает публичный или защищённый метод, не принимающий аргументов, перед выполнением метода экземпляра класса. Запущенный метод имеет доступ к экземпляру.
|
|
31
|
+
|
|
32
|
+
:param method_name: Имя метода.
|
|
33
|
+
:type method_name: str
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if method_name.startswith("__"):
|
|
37
|
+
raise ValueError("Only public and protected methods supported in @run_befor_method decorator.")
|
|
38
|
+
|
|
39
|
+
def decorator(function):
|
|
40
|
+
|
|
41
|
+
@functools.wraps(function)
|
|
42
|
+
def Wrapper(*args, **kwargs):
|
|
43
|
+
if args:
|
|
44
|
+
Object = args[0]
|
|
45
|
+
Method = getattr(Object, method_name, None)
|
|
46
|
+
if Method and callable(Method):
|
|
47
|
+
Method()
|
|
48
|
+
|
|
49
|
+
return function(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
return Wrapper
|
|
52
|
+
|
|
53
|
+
return decorator
|