mm-std 0.1.8__py3-none-any.whl → 0.1.10__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.
- mm_std/__init__.py +7 -7
- mm_std/command.py +1 -1
- mm_std/concurrency.py +5 -10
- mm_std/config.py +13 -32
- mm_std/date.py +2 -6
- mm_std/http_.py +143 -0
- mm_std/json_.py +1 -2
- mm_std/net.py +1 -139
- mm_std/print_.py +3 -4
- mm_std/result.py +25 -25
- mm_std/str.py +2 -8
- mm_std/types_.py +4 -0
- mm_std-0.1.10.dist-info/METADATA +11 -0
- mm_std-0.1.10.dist-info/RECORD +23 -0
- {mm_std-0.1.8.dist-info → mm_std-0.1.10.dist-info}/WHEEL +1 -1
- mm_std/telegram.py +0 -35
- mm_std/types.py +0 -4
- mm_std-0.1.8.dist-info/METADATA +0 -11
- mm_std-0.1.8.dist-info/RECORD +0 -23
mm_std/__init__.py
CHANGED
@@ -15,16 +15,17 @@ from .date import utc_now as utc_now
|
|
15
15
|
from .date import utc_random as utc_random
|
16
16
|
from .dict import replace_empty_values as replace_empty_values
|
17
17
|
from .env import get_dotenv as get_dotenv
|
18
|
+
from .http_ import CHROME_USER_AGENT as CHROME_USER_AGENT
|
19
|
+
from .http_ import FIREFOX_USER_AGENT as FIREFOX_USER_AGENT
|
20
|
+
from .http_ import HResponse as HResponse
|
21
|
+
from .http_ import add_query_params_to_url as add_query_params_to_url
|
22
|
+
from .http_ import hr as hr
|
23
|
+
from .http_ import hrequest as hrequest
|
18
24
|
from .json_ import CustomJSONEncoder as CustomJSONEncoder
|
19
25
|
from .json_ import json_dumps as json_dumps
|
20
26
|
from .log import init_logger as init_logger
|
21
|
-
from .net import CHROME_USER_AGENT as CHROME_USER_AGENT
|
22
|
-
from .net import FIREFOX_USER_AGENT as FIREFOX_USER_AGENT
|
23
|
-
from .net import HResponse as HResponse
|
24
|
-
from .net import add_query_params_to_url as add_query_params_to_url
|
25
27
|
from .net import check_port as check_port
|
26
|
-
from .net import
|
27
|
-
from .net import hrequest as hrequest
|
28
|
+
from .net import get_free_local_port as get_free_local_port
|
28
29
|
from .print_ import PrintFormat as PrintFormat
|
29
30
|
from .print_ import fatal as fatal
|
30
31
|
from .print_ import print_console as print_console
|
@@ -42,5 +43,4 @@ from .str import number_with_separator as number_with_separator
|
|
42
43
|
from .str import str_ends_with_any as str_ends_with_any
|
43
44
|
from .str import str_starts_with_any as str_starts_with_any
|
44
45
|
from .str import str_to_list as str_to_list
|
45
|
-
from .telegram import send_telegram_message as send_telegram_message
|
46
46
|
from .zip import read_text_from_zip_archive as read_text_from_zip_archive
|
mm_std/command.py
CHANGED
@@ -19,7 +19,7 @@ def run_command(cmd: str, timeout: int | None = 60, capture_output: bool = True,
|
|
19
19
|
if echo_cmd_console:
|
20
20
|
print(cmd) # noqa: T201
|
21
21
|
try:
|
22
|
-
process = subprocess.run(cmd, timeout=timeout, capture_output=capture_output, shell=True, check=False) # nosec
|
22
|
+
process = subprocess.run(cmd, timeout=timeout, capture_output=capture_output, shell=True, check=False) # noqa: S602 # nosec
|
23
23
|
stdout = process.stdout.decode("utf-8") if capture_output else ""
|
24
24
|
stderr = process.stderr.decode("utf-8") if capture_output else ""
|
25
25
|
return CommandResult(stdout=stdout, stderr=stderr, code=process.returncode)
|
mm_std/concurrency.py
CHANGED
@@ -8,7 +8,6 @@ from dataclasses import dataclass, field
|
|
8
8
|
from datetime import datetime
|
9
9
|
from logging import Logger
|
10
10
|
from threading import Lock, Thread
|
11
|
-
from typing import ParamSpec, TypeVar
|
12
11
|
|
13
12
|
from .date import is_too_old, utc_now
|
14
13
|
|
@@ -57,11 +56,7 @@ class ConcurrentTasks:
|
|
57
56
|
self.timeout_error = True
|
58
57
|
|
59
58
|
|
60
|
-
T =
|
61
|
-
P = ParamSpec("P")
|
62
|
-
|
63
|
-
|
64
|
-
def synchronized_parameter(arg_index: int = 0, skip_if_locked: bool = False) -> Callable[..., Callable[P, T | None]]:
|
59
|
+
def synchronized_parameter[T, **P](arg_index: int = 0, skip_if_locked: bool = False) -> Callable[..., Callable[P, T | None]]:
|
65
60
|
locks: dict[object, Lock] = defaultdict(Lock)
|
66
61
|
|
67
62
|
def outer(func: Callable[P, T]) -> Callable[P, T | None]:
|
@@ -81,7 +76,7 @@ def synchronized_parameter(arg_index: int = 0, skip_if_locked: bool = False) ->
|
|
81
76
|
return outer
|
82
77
|
|
83
78
|
|
84
|
-
def synchronized(fn: Callable[P, T]) -> Callable[P, T]:
|
79
|
+
def synchronized[T, **P](fn: Callable[P, T]) -> Callable[P, T]:
|
85
80
|
lock = Lock()
|
86
81
|
|
87
82
|
@functools.wraps(fn)
|
@@ -93,7 +88,7 @@ def synchronized(fn: Callable[P, T]) -> Callable[P, T]:
|
|
93
88
|
|
94
89
|
|
95
90
|
class Scheduler:
|
96
|
-
def __init__(self, log: Logger, loop_delay: float = 0.5, debug: bool = False):
|
91
|
+
def __init__(self, log: Logger, loop_delay: float = 0.5, debug: bool = False) -> None:
|
97
92
|
self.log = log
|
98
93
|
self.debug = debug
|
99
94
|
self.loop_delay = loop_delay
|
@@ -126,8 +121,8 @@ class Scheduler:
|
|
126
121
|
try:
|
127
122
|
job.func(*job.args)
|
128
123
|
self._debug(f"_run_job: {job} done")
|
129
|
-
except Exception
|
130
|
-
self.log.exception("scheduler error
|
124
|
+
except Exception:
|
125
|
+
self.log.exception("scheduler error")
|
131
126
|
self._debug(f"_run_job: {job} error")
|
132
127
|
finally:
|
133
128
|
job.is_running = False
|
mm_std/config.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import io
|
2
2
|
from pathlib import Path
|
3
|
+
from typing import Self
|
3
4
|
|
4
5
|
import yaml
|
5
6
|
from pydantic import BaseModel, ConfigDict, ValidationError
|
6
7
|
|
7
|
-
from .
|
8
|
+
from .result import Err, Ok, Result
|
8
9
|
from .str import str_to_list
|
9
10
|
from .zip import read_text_from_zip_archive
|
10
11
|
|
@@ -29,47 +30,27 @@ class BaseConfig(BaseModel):
|
|
29
30
|
return v
|
30
31
|
|
31
32
|
@classmethod
|
32
|
-
def read_config[
|
33
|
-
cls: type[T],
|
34
|
-
config_path: io.TextIOWrapper | str | Path,
|
35
|
-
error_print_type: PrintFormat = PrintFormat.PLAIN,
|
36
|
-
zip_password: str = "",
|
37
|
-
) -> T:
|
33
|
+
def read_config(cls, config_path: io.TextIOWrapper | str | Path, zip_password: str = "") -> Result[Self]: # nosec
|
38
34
|
try:
|
39
35
|
# is it zip archive?
|
40
36
|
if isinstance(config_path, str) and config_path.endswith(".zip"):
|
41
37
|
config_path = str(Path(config_path).expanduser())
|
42
|
-
return cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password)))
|
38
|
+
return Ok(cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password))))
|
43
39
|
if isinstance(config_path, io.TextIOWrapper) and config_path.name.endswith(".zip"):
|
44
40
|
config_path = str(Path(config_path.name).expanduser())
|
45
|
-
return cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password)))
|
41
|
+
return Ok(cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password))))
|
46
42
|
if isinstance(config_path, Path) and config_path.name.endswith(".zip"):
|
47
43
|
config_path = str(config_path.expanduser())
|
48
|
-
return cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password)))
|
44
|
+
return Ok(cls(**yaml.full_load(read_text_from_zip_archive(config_path, password=zip_password))))
|
49
45
|
|
50
46
|
# plain yml file
|
51
47
|
if isinstance(config_path, str):
|
52
|
-
return cls(**yaml.full_load(Path(config_path).expanduser().read_text()))
|
53
|
-
|
54
|
-
return cls(**yaml.full_load(config_path.expanduser().read_text()))
|
55
|
-
|
56
|
-
|
48
|
+
return Ok(cls(**yaml.full_load(Path(config_path).expanduser().read_text())))
|
49
|
+
if isinstance(config_path, Path):
|
50
|
+
return Ok(cls(**yaml.full_load(config_path.expanduser().read_text())))
|
51
|
+
|
52
|
+
return Ok(cls(**yaml.full_load(config_path)))
|
57
53
|
except ValidationError as err:
|
58
|
-
|
59
|
-
json_errors = []
|
60
|
-
rows = []
|
61
|
-
for e in err.errors():
|
62
|
-
loc = e["loc"]
|
63
|
-
field = ".".join(str(lo) for lo in loc) if len(loc) > 0 else ""
|
64
|
-
print_plain(f"{field} {e['msg']}", error_print_type)
|
65
|
-
json_errors.append({field: e["msg"]})
|
66
|
-
rows.append([field, e["msg"]])
|
67
|
-
print_table("config validation errors", ["field", "message"], rows)
|
68
|
-
print_json({"errors": json_errors}, error_print_type)
|
69
|
-
exit(1)
|
54
|
+
return Err("validator error", data={"validaton_errors": err})
|
70
55
|
except Exception as err:
|
71
|
-
|
72
|
-
print_json({"exception": str(err)})
|
73
|
-
else:
|
74
|
-
print_console(f"config error: {err!s}")
|
75
|
-
exit(1)
|
56
|
+
return Err(err)
|
mm_std/date.py
CHANGED
@@ -43,19 +43,15 @@ def parse_date(value: str, ignore_tz: bool = False) -> datetime:
|
|
43
43
|
|
44
44
|
for fmt in date_formats:
|
45
45
|
try:
|
46
|
-
dt = datetime.strptime(value, fmt)
|
46
|
+
dt = datetime.strptime(value, fmt) # noqa: DTZ007
|
47
47
|
if ignore_tz and dt.tzinfo is not None:
|
48
48
|
dt = dt.replace(tzinfo=None)
|
49
|
-
return dt
|
49
|
+
return dt # noqa: TRY300
|
50
50
|
except ValueError:
|
51
51
|
continue
|
52
52
|
raise ValueError(f"Time data '{value}' does not match any known format.")
|
53
53
|
|
54
54
|
|
55
|
-
# def parse_date(value: str, ignore_tz: bool = False) -> datetime:
|
56
|
-
# return parser.parse(value, ignoretz=ignore_tz)
|
57
|
-
|
58
|
-
|
59
55
|
def utc_random(
|
60
56
|
*,
|
61
57
|
from_time: datetime | None = None,
|
mm_std/http_.py
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
import json
|
2
|
+
from dataclasses import asdict, dataclass, field
|
3
|
+
from typing import Any
|
4
|
+
from urllib.parse import urlencode
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
import pydash
|
8
|
+
from httpx._types import AuthTypes
|
9
|
+
|
10
|
+
from mm_std.result import Err, Ok, Result
|
11
|
+
|
12
|
+
FIREFOX_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0"
|
13
|
+
SAFARI_USER_AGENT = (
|
14
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15"
|
15
|
+
)
|
16
|
+
CHROME_USER_AGENT = (
|
17
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class HResponse:
|
23
|
+
code: int = 0
|
24
|
+
error: str | None = None
|
25
|
+
body: str = ""
|
26
|
+
headers: dict[str, str] = field(default_factory=dict)
|
27
|
+
|
28
|
+
_json_data: Any = None
|
29
|
+
_json_parsed = False
|
30
|
+
_json_parsed_error = False
|
31
|
+
|
32
|
+
def _parse_json(self) -> None:
|
33
|
+
try:
|
34
|
+
self._json_data = None
|
35
|
+
self._json_data = json.loads(self.body)
|
36
|
+
self._json_parsed_error = False
|
37
|
+
except json.JSONDecodeError:
|
38
|
+
self._json_parsed_error = True
|
39
|
+
self._json_parsed = True
|
40
|
+
|
41
|
+
@property
|
42
|
+
def json(self) -> Any: # noqa: ANN401
|
43
|
+
if not self._json_parsed:
|
44
|
+
self._parse_json()
|
45
|
+
return self._json_data
|
46
|
+
|
47
|
+
@property
|
48
|
+
def json_parse_error(self) -> bool:
|
49
|
+
if not self._json_parsed:
|
50
|
+
self._parse_json()
|
51
|
+
return self._json_parsed_error
|
52
|
+
|
53
|
+
@property
|
54
|
+
def content_type(self) -> str | None:
|
55
|
+
for key in self.headers:
|
56
|
+
if key.lower() == "content-type":
|
57
|
+
return self.headers[key]
|
58
|
+
return None
|
59
|
+
|
60
|
+
def to_err_result[T](self, error: str | None = None) -> Err:
|
61
|
+
return Err(error or self.error or "error", data=asdict(self))
|
62
|
+
|
63
|
+
def to_ok_result[T](self, result: T) -> Result[T]:
|
64
|
+
return Ok(result, data=asdict(self))
|
65
|
+
|
66
|
+
def is_error(self) -> bool:
|
67
|
+
return self.error is not None
|
68
|
+
|
69
|
+
def is_timeout_error(self) -> bool:
|
70
|
+
return self.error == "timeout"
|
71
|
+
|
72
|
+
def is_proxy_error(self) -> bool:
|
73
|
+
return self.error == "proxy_error"
|
74
|
+
|
75
|
+
def is_connection_error(self) -> bool:
|
76
|
+
return self.error is not None and self.error.startswith("connection_error:")
|
77
|
+
|
78
|
+
def to_dict(self) -> dict[str, Any]:
|
79
|
+
return pydash.omit(asdict(self), "_json_data")
|
80
|
+
|
81
|
+
|
82
|
+
def hrequest(
|
83
|
+
url: str,
|
84
|
+
*,
|
85
|
+
method: str = "GET",
|
86
|
+
proxy: str | None = None,
|
87
|
+
params: dict[str, Any] | None = None,
|
88
|
+
headers: dict[str, Any] | None = None,
|
89
|
+
cookies: dict[str, Any] | None = None,
|
90
|
+
timeout: float = 10,
|
91
|
+
user_agent: str | None = None,
|
92
|
+
json_params: bool = True,
|
93
|
+
auth: AuthTypes | None = None,
|
94
|
+
verify: bool = True,
|
95
|
+
) -> HResponse:
|
96
|
+
query_params: dict[str, Any] | None = None
|
97
|
+
data: dict[str, Any] | None = None
|
98
|
+
json_: dict[str, Any] | None = None
|
99
|
+
method = method.upper()
|
100
|
+
if not headers:
|
101
|
+
headers = {}
|
102
|
+
if user_agent:
|
103
|
+
headers["user-agent"] = user_agent
|
104
|
+
if method == "GET":
|
105
|
+
query_params = params
|
106
|
+
elif json_params:
|
107
|
+
json_ = params
|
108
|
+
else:
|
109
|
+
data = params
|
110
|
+
|
111
|
+
try:
|
112
|
+
r = httpx.request(
|
113
|
+
method,
|
114
|
+
url,
|
115
|
+
proxy=proxy,
|
116
|
+
timeout=timeout,
|
117
|
+
cookies=cookies,
|
118
|
+
auth=auth,
|
119
|
+
verify=verify,
|
120
|
+
headers=headers,
|
121
|
+
params=query_params,
|
122
|
+
json=json_,
|
123
|
+
data=data,
|
124
|
+
)
|
125
|
+
return HResponse(code=r.status_code, body=r.text, headers=dict(r.headers))
|
126
|
+
except httpx.TimeoutException:
|
127
|
+
return HResponse(error="timeout")
|
128
|
+
except httpx.ProxyError:
|
129
|
+
return HResponse(error="proxy_error")
|
130
|
+
except httpx.HTTPError as err:
|
131
|
+
return HResponse(error=f"connection_error: {err}")
|
132
|
+
except Exception as err:
|
133
|
+
return HResponse(error=f"exception: {err}")
|
134
|
+
|
135
|
+
|
136
|
+
def add_query_params_to_url(url: str, params: dict[str, object]) -> str:
|
137
|
+
query_params = urlencode({k: v for k, v in params.items() if v is not None})
|
138
|
+
if query_params:
|
139
|
+
url += f"?{query_params}"
|
140
|
+
return url
|
141
|
+
|
142
|
+
|
143
|
+
hr = hrequest
|
mm_std/json_.py
CHANGED
@@ -4,7 +4,6 @@ from datetime import date, datetime
|
|
4
4
|
from decimal import Decimal
|
5
5
|
from enum import Enum
|
6
6
|
from json import JSONEncoder
|
7
|
-
from typing import Any
|
8
7
|
|
9
8
|
from pydantic import BaseModel
|
10
9
|
|
@@ -12,7 +11,7 @@ from mm_std.result import Err, Ok
|
|
12
11
|
|
13
12
|
|
14
13
|
class CustomJSONEncoder(JSONEncoder):
|
15
|
-
def default(self, o:
|
14
|
+
def default(self, o: object) -> object:
|
16
15
|
if isinstance(o, Ok):
|
17
16
|
return {"ok": o.ok}
|
18
17
|
if isinstance(o, Err):
|
mm_std/net.py
CHANGED
@@ -1,134 +1,6 @@
|
|
1
|
-
import json
|
2
1
|
import socket
|
3
2
|
import time
|
4
|
-
from
|
5
|
-
from json import JSONDecodeError
|
6
|
-
from typing import Any, cast
|
7
|
-
from urllib.parse import urlencode
|
8
|
-
|
9
|
-
import httpx
|
10
|
-
import pydash
|
11
|
-
|
12
|
-
from mm_std.result import Err, Ok, Result
|
13
|
-
|
14
|
-
FIREFOX_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0"
|
15
|
-
SAFARI_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15" # fmt: skip # noqa
|
16
|
-
CHROME_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" # fmt: skip # noqa
|
17
|
-
|
18
|
-
|
19
|
-
@dataclass
|
20
|
-
class HResponse:
|
21
|
-
code: int = 0
|
22
|
-
error: str | None = None
|
23
|
-
body: str = ""
|
24
|
-
headers: dict[str, str] = field(default_factory=dict)
|
25
|
-
|
26
|
-
_json_data: Any = None
|
27
|
-
_json_parsed = False
|
28
|
-
_json_parsed_error = False
|
29
|
-
|
30
|
-
def _parse_json(self) -> None:
|
31
|
-
try:
|
32
|
-
self._json_data = None
|
33
|
-
self._json_data = json.loads(self.body)
|
34
|
-
self._json_parsed_error = False
|
35
|
-
except JSONDecodeError:
|
36
|
-
self._json_parsed_error = True
|
37
|
-
self._json_parsed = True
|
38
|
-
|
39
|
-
@property
|
40
|
-
def json(self) -> Any:
|
41
|
-
if not self._json_parsed:
|
42
|
-
self._parse_json()
|
43
|
-
return self._json_data
|
44
|
-
|
45
|
-
@property
|
46
|
-
def json_parse_error(self) -> bool:
|
47
|
-
if not self._json_parsed:
|
48
|
-
self._parse_json()
|
49
|
-
return self._json_parsed_error
|
50
|
-
|
51
|
-
@property
|
52
|
-
def content_type(self) -> str | None:
|
53
|
-
for key in self.headers.keys():
|
54
|
-
if key.lower() == "content-type":
|
55
|
-
return self.headers[key]
|
56
|
-
return None
|
57
|
-
|
58
|
-
def to_err_result[T](self, error: str | None = None) -> Err:
|
59
|
-
return Err(error or self.error or "error", data=asdict(self))
|
60
|
-
|
61
|
-
def to_ok_result[T](self, result: T) -> Result[T]:
|
62
|
-
return Ok(result, data=asdict(self))
|
63
|
-
|
64
|
-
def is_error(self) -> bool:
|
65
|
-
return self.error is not None
|
66
|
-
|
67
|
-
def is_timeout_error(self) -> bool:
|
68
|
-
return self.error == "timeout"
|
69
|
-
|
70
|
-
def is_proxy_error(self) -> bool:
|
71
|
-
return self.error == "proxy_error"
|
72
|
-
|
73
|
-
def is_connection_error(self) -> bool:
|
74
|
-
return self.error is not None and self.error.startswith("connection_error:")
|
75
|
-
|
76
|
-
def to_dict(self) -> dict[str, Any]:
|
77
|
-
return pydash.omit(asdict(self), "_json_data")
|
78
|
-
|
79
|
-
|
80
|
-
def hrequest(
|
81
|
-
url: str,
|
82
|
-
*,
|
83
|
-
method: str = "GET",
|
84
|
-
proxy: str | None = None,
|
85
|
-
params: dict[str, Any] | None = None,
|
86
|
-
headers: dict[str, Any] | None = None,
|
87
|
-
cookies: dict[str, Any] | None = None,
|
88
|
-
timeout: float = 10,
|
89
|
-
user_agent: str | None = None,
|
90
|
-
json_params: bool = True,
|
91
|
-
auth: Any = None,
|
92
|
-
verify: bool = True,
|
93
|
-
) -> HResponse:
|
94
|
-
query_params: dict[str, Any] | None = None
|
95
|
-
data: dict[str, Any] | None = None
|
96
|
-
json_: dict[str, Any] | None = None
|
97
|
-
method = method.upper()
|
98
|
-
if not headers:
|
99
|
-
headers = {}
|
100
|
-
if user_agent:
|
101
|
-
headers["user-agent"] = user_agent
|
102
|
-
if method == "GET":
|
103
|
-
query_params = params
|
104
|
-
elif json_params:
|
105
|
-
json_ = params
|
106
|
-
else:
|
107
|
-
data = params
|
108
|
-
|
109
|
-
try:
|
110
|
-
r = httpx.request(
|
111
|
-
method,
|
112
|
-
url,
|
113
|
-
proxy=proxy,
|
114
|
-
timeout=timeout,
|
115
|
-
cookies=cookies,
|
116
|
-
auth=auth,
|
117
|
-
verify=verify,
|
118
|
-
headers=headers,
|
119
|
-
params=query_params,
|
120
|
-
json=json_,
|
121
|
-
data=data,
|
122
|
-
)
|
123
|
-
return HResponse(code=r.status_code, body=r.text, headers=dict(r.headers))
|
124
|
-
except httpx.TimeoutException:
|
125
|
-
return HResponse(error="timeout")
|
126
|
-
except httpx.ProxyError:
|
127
|
-
return HResponse(error="proxy_error")
|
128
|
-
except httpx.HTTPError as err:
|
129
|
-
return HResponse(error=f"connection_error: {err}")
|
130
|
-
except Exception as err:
|
131
|
-
return HResponse(error=f"exception: {err}")
|
3
|
+
from typing import cast
|
132
4
|
|
133
5
|
|
134
6
|
def check_port(ip: str, port: int, attempts: int = 3, sleep_seconds: float = 1, timeout: float = 1) -> bool:
|
@@ -148,13 +20,3 @@ def get_free_local_port() -> int:
|
|
148
20
|
port = sock.getsockname()[1]
|
149
21
|
sock.close()
|
150
22
|
return cast(int, port)
|
151
|
-
|
152
|
-
|
153
|
-
def add_query_params_to_url(url: str, params: dict[str, object]) -> str:
|
154
|
-
query_params = urlencode({k: v for k, v in params.items() if v is not None})
|
155
|
-
if query_params:
|
156
|
-
url += f"?{query_params}"
|
157
|
-
return url
|
158
|
-
|
159
|
-
|
160
|
-
hr = hrequest
|
mm_std/print_.py
CHANGED
@@ -26,11 +26,10 @@ def print_console(*messages: object, print_json: bool = False) -> None:
|
|
26
26
|
message = messages[0]
|
27
27
|
if isinstance(message, str):
|
28
28
|
print(message) # noqa: T201
|
29
|
+
elif print_json:
|
30
|
+
rich.print_json(json_dumps(message))
|
29
31
|
else:
|
30
|
-
|
31
|
-
rich.print_json(json_dumps(message))
|
32
|
-
else:
|
33
|
-
rich.print(message)
|
32
|
+
rich.print(message)
|
34
33
|
else:
|
35
34
|
rich.print(messages)
|
36
35
|
|
mm_std/result.py
CHANGED
@@ -1,29 +1,30 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import time
|
4
|
-
from
|
5
|
-
from typing import Any, Literal, NoReturn
|
4
|
+
from typing import TYPE_CHECKING, Any, Literal, NoReturn
|
6
5
|
|
7
6
|
from pydantic_core import core_schema
|
8
7
|
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from collections.abc import Callable
|
10
|
+
|
9
11
|
|
10
12
|
class Ok[T]:
|
11
13
|
__match_args__ = ("ok",)
|
12
14
|
|
13
|
-
def __init__(self, ok: T, data:
|
15
|
+
def __init__(self, ok: T, data: object = None) -> None:
|
14
16
|
self.ok = ok
|
15
17
|
self.data = data
|
16
18
|
|
17
19
|
def __repr__(self) -> str:
|
18
20
|
if self.data is None:
|
19
21
|
return f"Ok({self.ok!r})"
|
20
|
-
|
21
|
-
return f"Ok({self.ok!r}, data={self.data!r})"
|
22
|
+
return f"Ok({self.ok!r}, data={self.data!r})"
|
22
23
|
|
23
|
-
def __eq__(self, other:
|
24
|
+
def __eq__(self, other: object) -> bool:
|
24
25
|
return isinstance(other, Ok) and self.ok == other.ok and self.data == other.data
|
25
26
|
|
26
|
-
def __ne__(self, other:
|
27
|
+
def __ne__(self, other: object) -> bool:
|
27
28
|
return not (self == other)
|
28
29
|
|
29
30
|
def __hash__(self) -> int:
|
@@ -54,26 +55,26 @@ class Ok[T]:
|
|
54
55
|
def unwrap_or[U](self, _default: U) -> T:
|
55
56
|
return self.ok
|
56
57
|
|
57
|
-
def unwrap_or_else(self,
|
58
|
+
def unwrap_or_else(self, _op: object) -> T:
|
58
59
|
return self.ok
|
59
60
|
|
60
|
-
def unwrap_or_raise(self,
|
61
|
+
def unwrap_or_raise(self, _e: object) -> T:
|
61
62
|
return self.ok
|
62
63
|
|
63
64
|
def map[U](self, op: Callable[[T], U]) -> Ok[U]:
|
64
65
|
return Ok(op(self.ok), data=self.data)
|
65
66
|
|
66
|
-
def map_or[U](self,
|
67
|
+
def map_or[U](self, _default: object, op: Callable[[T], U]) -> U:
|
67
68
|
return op(self.ok)
|
68
69
|
|
69
|
-
def map_or_else[U](self,
|
70
|
+
def map_or_else[U](self, _err_op: object, ok_op: Callable[[T], U]) -> U:
|
70
71
|
"""
|
71
72
|
The contained result is `Ok`, so return original value mapped to
|
72
73
|
a new value using the passed in `op` function.
|
73
74
|
"""
|
74
75
|
return ok_op(self.ok)
|
75
76
|
|
76
|
-
def map_err(self,
|
77
|
+
def map_err(self, _op: object) -> Ok[T]:
|
77
78
|
"""
|
78
79
|
The contained result is `Ok`, so return `Ok` with the original value
|
79
80
|
"""
|
@@ -93,7 +94,7 @@ class Ok[T]:
|
|
93
94
|
res.data = self.data
|
94
95
|
return res
|
95
96
|
|
96
|
-
def or_else(self,
|
97
|
+
def or_else(self, _op: object) -> Ok[T]:
|
97
98
|
return self
|
98
99
|
|
99
100
|
def ok_or_err(self) -> T | str:
|
@@ -103,7 +104,7 @@ class Ok[T]:
|
|
103
104
|
return self.ok
|
104
105
|
|
105
106
|
@classmethod
|
106
|
-
def __get_pydantic_core_schema__(cls, _source_type:
|
107
|
+
def __get_pydantic_core_schema__(cls, _source_type: object, _handler: object) -> core_schema.CoreSchema:
|
107
108
|
return core_schema.model_schema(
|
108
109
|
cls,
|
109
110
|
core_schema.model_fields_schema(
|
@@ -118,20 +119,19 @@ class Ok[T]:
|
|
118
119
|
class Err:
|
119
120
|
__match_args__ = ("err",)
|
120
121
|
|
121
|
-
def __init__(self, err: str | Exception, data:
|
122
|
+
def __init__(self, err: str | Exception, data: object = None) -> None:
|
122
123
|
self.err = f"exception: {err}" if isinstance(err, Exception) else err
|
123
124
|
self.data = data
|
124
125
|
|
125
126
|
def __repr__(self) -> str:
|
126
127
|
if self.data is None:
|
127
128
|
return f"Err({self.err!r})"
|
128
|
-
|
129
|
-
return f"Err({self.err!r}, data={self.data!r})"
|
129
|
+
return f"Err({self.err!r}, data={self.data!r})"
|
130
130
|
|
131
|
-
def __eq__(self, other:
|
131
|
+
def __eq__(self, other: object) -> bool:
|
132
132
|
return isinstance(other, Err) and self.err == other.err and self.data == other.data
|
133
133
|
|
134
|
-
def __ne__(self, other:
|
134
|
+
def __ne__(self, other: object) -> bool:
|
135
135
|
return not (self == other)
|
136
136
|
|
137
137
|
def __hash__(self) -> int:
|
@@ -199,25 +199,25 @@ class Err:
|
|
199
199
|
"""
|
200
200
|
raise e(self.err)
|
201
201
|
|
202
|
-
def map(self,
|
202
|
+
def map(self, _op: object) -> Err:
|
203
203
|
"""
|
204
204
|
Return `Err` with the same value
|
205
205
|
"""
|
206
206
|
return self
|
207
207
|
|
208
|
-
def map_or[U](self, default: U,
|
208
|
+
def map_or[U](self, default: U, _op: object) -> U:
|
209
209
|
"""
|
210
210
|
Return the default value
|
211
211
|
"""
|
212
212
|
return default
|
213
213
|
|
214
|
-
def map_or_else[U](self, err_op: Callable[[str], U],
|
214
|
+
def map_or_else[U](self, err_op: Callable[[str], U], _ok_op: object) -> U:
|
215
215
|
"""
|
216
216
|
Return the result of the default operation
|
217
217
|
"""
|
218
218
|
return err_op(self.err)
|
219
219
|
|
220
|
-
def and_then(self,
|
220
|
+
def and_then(self, _op: object) -> Err:
|
221
221
|
"""
|
222
222
|
The contained result is `Err`, so return `Err` with the original value
|
223
223
|
"""
|
@@ -230,7 +230,7 @@ class Err:
|
|
230
230
|
return None
|
231
231
|
|
232
232
|
@classmethod
|
233
|
-
def __get_pydantic_core_schema__(cls, _source_type:
|
233
|
+
def __get_pydantic_core_schema__(cls, _source_type: object, _handler: object) -> core_schema.CoreSchema:
|
234
234
|
return core_schema.model_schema(
|
235
235
|
cls,
|
236
236
|
core_schema.model_fields_schema(
|
@@ -257,7 +257,7 @@ class UnwrapError(Exception):
|
|
257
257
|
return self._result
|
258
258
|
|
259
259
|
|
260
|
-
def try_ok[T](fn: Callable[..., Result[T]], *, args: tuple[object], attempts: int, delay:
|
260
|
+
def try_ok[T](fn: Callable[..., Result[T]], *, args: tuple[object], attempts: int, delay: float = 0) -> Result[T]:
|
261
261
|
if attempts <= 0:
|
262
262
|
raise ValueError("attempts must be more than zero")
|
263
263
|
res: Result[T] = Err("not started")
|
mm_std/str.py
CHANGED
@@ -62,18 +62,12 @@ def number_with_separator(
|
|
62
62
|
|
63
63
|
def str_starts_with_any(value: str, prefixes: list[str]) -> bool:
|
64
64
|
"""check if str starts with any of prefixes"""
|
65
|
-
for prefix in prefixes
|
66
|
-
if value.startswith(prefix):
|
67
|
-
return True
|
68
|
-
return False
|
65
|
+
return any(value.startswith(prefix) for prefix in prefixes)
|
69
66
|
|
70
67
|
|
71
68
|
def str_ends_with_any(value: str, prefixes: list[str]) -> bool:
|
72
69
|
"""check if str ends with any of prefixes"""
|
73
|
-
for prefix in prefixes
|
74
|
-
if value.endswith(prefix):
|
75
|
-
return True
|
76
|
-
return False
|
70
|
+
return any(value.endswith(prefix) for prefix in prefixes)
|
77
71
|
|
78
72
|
|
79
73
|
def split_on_plus_minus_tokens(value: str) -> list[str]:
|
mm_std/types_.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: mm-std
|
3
|
+
Version: 0.1.10
|
4
|
+
Requires-Python: >=3.12
|
5
|
+
Requires-Dist: cryptography~=44.0.0
|
6
|
+
Requires-Dist: httpx[http2,socks]~=0.28.1
|
7
|
+
Requires-Dist: pydantic~=2.10.5
|
8
|
+
Requires-Dist: pydash~=8.0.5
|
9
|
+
Requires-Dist: python-dotenv~=1.0.1
|
10
|
+
Requires-Dist: pyyaml~=6.0.2
|
11
|
+
Requires-Dist: rich~=13.9.4
|
@@ -0,0 +1,23 @@
|
|
1
|
+
mm_std/__init__.py,sha256=dtYnmQP_HkWxIJvuCJGpex3RHvG2V0Ekh7oYstRQoco,2291
|
2
|
+
mm_std/command.py,sha256=ze286wjUjg0QSTgIu-2WZks53_Vclg69UaYYgPpQvCU,1283
|
3
|
+
mm_std/concurrency.py,sha256=4kKLhde6YQYsjJJjH6K5eMQj6FtegEz55Mo5TmhQMM0,5242
|
4
|
+
mm_std/config.py,sha256=je47fOnFPooO65vc15LaR_Pi0HPfmhQmHJKU7ft_I08,2300
|
5
|
+
mm_std/crypto.py,sha256=jdk0_TCmeU0pPXMyz9xH6kQHSjjZ9GcGClBwQps5vBo,340
|
6
|
+
mm_std/date.py,sha256=976eEkSONuNqHQBgSRu8hrtH23tJqztbmHFHLdbP2TY,1879
|
7
|
+
mm_std/dict.py,sha256=kJBPVG9vEqHiSgKKoji8gVGL1yEBbxAmFNn0zz17AUg,180
|
8
|
+
mm_std/env.py,sha256=5zaR9VeIfObN-4yfgxoFeU5IM1GDeZZj9SuYf7t9sOA,125
|
9
|
+
mm_std/fs.py,sha256=RwarNRJq3tIMG6LVX_g03hasfYpjYFh_O27oVDt5IPQ,291
|
10
|
+
mm_std/http_.py,sha256=QaPPXVb-rOS0BpoKdYQ0ABm_-mcR5dNa7Uqn-SeW_kE,4119
|
11
|
+
mm_std/json_.py,sha256=VWjJsfLt-dQJiwc7vgpTElMr69NxpzX_mpmBoCkeoT8,999
|
12
|
+
mm_std/log.py,sha256=6ux6njNKc_ZCQlvWn1FZR6vcSY2Cem-mQzmNXvsg5IE,913
|
13
|
+
mm_std/net.py,sha256=qdRCBIDneip6FaPNe5mx31UtYVmzqam_AoUF7ydEyjA,590
|
14
|
+
mm_std/print_.py,sha256=HAhaiEMZZLV3CpG6UfwoUb6-Ftcun16YFC-CRcAcXOY,1525
|
15
|
+
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
mm_std/random_.py,sha256=OuUX4VJeSd13NZBya4qrGpR2TfN7_87tfebOY6DBUnI,1113
|
17
|
+
mm_std/result.py,sha256=w2Y1dji0LozcJpPMnFkqN0UbxJUm1C8Kc5ZB1vLXx20,7443
|
18
|
+
mm_std/str.py,sha256=jS7VAI7i_a3iqnfaW4Iw2LZRTv0Tml4kmMbP2S2IUF4,3067
|
19
|
+
mm_std/types_.py,sha256=hvZlnvBWyB8CL_MeEWWD0Y0nN677plibYn3yD-5g7xs,99
|
20
|
+
mm_std/zip.py,sha256=2EXcae4HO5U4kObj2Lj8jl5F2OUpT-WRlJybTyFzt6I,370
|
21
|
+
mm_std-0.1.10.dist-info/METADATA,sha256=I1b0CBj5aAvOH54hNApMxiKu1GJUZpqw_D5mrEuZa8k,307
|
22
|
+
mm_std-0.1.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
mm_std-0.1.10.dist-info/RECORD,,
|
mm_std/telegram.py
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
import pydash
|
4
|
-
|
5
|
-
from mm_std import Err, Ok, Result
|
6
|
-
from mm_std.net import hrequest
|
7
|
-
|
8
|
-
|
9
|
-
def send_telegram_message(bot_token: str, chat_id: int, message: str, long_message_delay: int = 3) -> Result[list[int]]:
|
10
|
-
messages = _split_string(message, 4096)
|
11
|
-
responses = []
|
12
|
-
result = []
|
13
|
-
while True:
|
14
|
-
text = messages.pop(0)
|
15
|
-
params = {"chat_id": chat_id, "text": text}
|
16
|
-
res = hrequest(f"https://api.telegram.org/bot{bot_token}/sendMessage", method="post", params=params)
|
17
|
-
responses.append(res.json)
|
18
|
-
if res.error is not None:
|
19
|
-
return Err(res.error, data={"last_res": res.to_dict(), "responses": responses})
|
20
|
-
|
21
|
-
message_id = pydash.get(res.json, "result.message_id")
|
22
|
-
if message_id:
|
23
|
-
result.append(message_id)
|
24
|
-
else:
|
25
|
-
return Err("unknown_response", data={"last_res": res.to_dict(), "responses": responses})
|
26
|
-
|
27
|
-
if len(messages):
|
28
|
-
time.sleep(long_message_delay)
|
29
|
-
else:
|
30
|
-
break
|
31
|
-
return Ok(result, data={"responses": responses})
|
32
|
-
|
33
|
-
|
34
|
-
def _split_string(text: str, chars_per_string: int) -> list[str]:
|
35
|
-
return [text[i : i + chars_per_string] for i in range(0, len(text), chars_per_string)]
|
mm_std/types.py
DELETED
mm_std-0.1.8.dist-info/METADATA
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: mm-std
|
3
|
-
Version: 0.1.8
|
4
|
-
Requires-Python: >=3.12
|
5
|
-
Requires-Dist: cryptography~=44.0.0
|
6
|
-
Requires-Dist: httpx[http2,socks]~=0.28.0
|
7
|
-
Requires-Dist: pydantic~=2.10.2
|
8
|
-
Requires-Dist: pydash~=8.0.4
|
9
|
-
Requires-Dist: python-dotenv~=1.0.1
|
10
|
-
Requires-Dist: pyyaml~=6.0.1
|
11
|
-
Requires-Dist: rich~=13.0
|
mm_std-0.1.8.dist-info/RECORD
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
mm_std/__init__.py,sha256=4pa6AjO-0mfrfBi6FS5sqOk1HFAKt2OMovA12ys7UvQ,2288
|
2
|
-
mm_std/command.py,sha256=r1n9ZHyMFhNkNOH9grRCm5J0hhX4_v0c2wdaal8iCZY,1270
|
3
|
-
mm_std/concurrency.py,sha256=SZGSTlswpFRZwsI7ztFWYca1XOPToLe0oBNJBZlSlgo,5311
|
4
|
-
mm_std/config.py,sha256=Wo_VS6MO1vTZosOHv97mCo41YYGKZaRAeergaSVzUcs,3111
|
5
|
-
mm_std/crypto.py,sha256=jdk0_TCmeU0pPXMyz9xH6kQHSjjZ9GcGClBwQps5vBo,340
|
6
|
-
mm_std/date.py,sha256=dhlBE8dTzPe0Qhu5f6YMeR5ka5k2B0IoMe8VA7_-pII,1969
|
7
|
-
mm_std/dict.py,sha256=kJBPVG9vEqHiSgKKoji8gVGL1yEBbxAmFNn0zz17AUg,180
|
8
|
-
mm_std/env.py,sha256=5zaR9VeIfObN-4yfgxoFeU5IM1GDeZZj9SuYf7t9sOA,125
|
9
|
-
mm_std/fs.py,sha256=RwarNRJq3tIMG6LVX_g03hasfYpjYFh_O27oVDt5IPQ,291
|
10
|
-
mm_std/json_.py,sha256=12uGLwmnrRA63QI0nUx-UU33zXcyShbsKCoOQiIJ8io,1016
|
11
|
-
mm_std/log.py,sha256=6ux6njNKc_ZCQlvWn1FZR6vcSY2Cem-mQzmNXvsg5IE,913
|
12
|
-
mm_std/net.py,sha256=k3kl9OY23VoF2md6UyRBDQ9lhRzWlFCuhalDtv6Of90,4686
|
13
|
-
mm_std/print_.py,sha256=mMixwfdrLEYW15ez7_QxXdrV-d38q9XJP8tB8F7P2pI,1553
|
14
|
-
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
mm_std/random_.py,sha256=OuUX4VJeSd13NZBya4qrGpR2TfN7_87tfebOY6DBUnI,1113
|
16
|
-
mm_std/result.py,sha256=1uCb2YySZGHLVNrGRQYhs6BngB8aEW2MKY9Bx6j2wNQ,7407
|
17
|
-
mm_std/str.py,sha256=nG5XF5870xM2PAvU0LZrJDk-d54LYwRLGnSahIekOVw,3151
|
18
|
-
mm_std/telegram.py,sha256=QrHPnsy0LTeqpd-g3RaHhk7gWIfHZEgnMs-S5DLW-vU,1220
|
19
|
-
mm_std/types.py,sha256=KpFtJ-BTmDfmmFeOSlgq6cMbCfGGOQjh1oWvdcrW-kw,116
|
20
|
-
mm_std/zip.py,sha256=2EXcae4HO5U4kObj2Lj8jl5F2OUpT-WRlJybTyFzt6I,370
|
21
|
-
mm_std-0.1.8.dist-info/METADATA,sha256=qS2lfsHdWITmGzMWqikfNAAA-bZrWRfZHayi1_tF5bA,304
|
22
|
-
mm_std-0.1.8.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
23
|
-
mm_std-0.1.8.dist-info/RECORD,,
|