mm-std 0.3.21__py3-none-any.whl → 0.3.23__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 +3 -1
- mm_std/concurrency/async_scheduler.py +6 -6
- mm_std/concurrency/async_task_runner.py +3 -4
- mm_std/http_.py +0 -2
- mm_std/log.py +62 -53
- mm_std/result.py +11 -1
- {mm_std-0.3.21.dist-info → mm_std-0.3.23.dist-info}/METADATA +2 -3
- {mm_std-0.3.21.dist-info → mm_std-0.3.23.dist-info}/RECORD +9 -9
- {mm_std-0.3.21.dist-info → mm_std-0.3.23.dist-info}/WHEEL +0 -0
mm_std/__init__.py
CHANGED
@@ -29,7 +29,7 @@ from .http_ import hrequest as hrequest
|
|
29
29
|
from .http_ import hrequest_async as hrequest_async
|
30
30
|
from .json_ import CustomJSONEncoder as CustomJSONEncoder
|
31
31
|
from .json_ import json_dumps as json_dumps
|
32
|
-
from .log import
|
32
|
+
from .log import configure_logging as configure_logging
|
33
33
|
from .log import init_logger as init_logger
|
34
34
|
from .net import check_port as check_port
|
35
35
|
from .net import get_free_local_port as get_free_local_port
|
@@ -46,6 +46,8 @@ from .random_ import random_str_choice as random_str_choice
|
|
46
46
|
from .result import Err as Err
|
47
47
|
from .result import Ok as Ok
|
48
48
|
from .result import Result as Result
|
49
|
+
from .result import err as err
|
50
|
+
from .result import ok as ok
|
49
51
|
from .result import try_ok as try_ok
|
50
52
|
from .str import number_with_separator as number_with_separator
|
51
53
|
from .str import str_contains_any as str_contains_any
|
@@ -1,18 +1,17 @@
|
|
1
1
|
import asyncio
|
2
|
+
import logging
|
2
3
|
from collections.abc import Awaitable, Callable
|
3
4
|
from dataclasses import dataclass, field
|
4
5
|
from datetime import datetime
|
5
6
|
from typing import Any
|
6
7
|
|
7
|
-
import structlog
|
8
|
-
|
9
8
|
from mm_std.date import utc_now
|
10
9
|
|
11
10
|
type AsyncFunc = Callable[..., Awaitable[object]]
|
12
11
|
type Args = tuple[object, ...]
|
13
12
|
type Kwargs = dict[str, object]
|
14
13
|
|
15
|
-
logger =
|
14
|
+
logger = logging.getLogger(__name__)
|
16
15
|
|
17
16
|
|
18
17
|
class AsyncScheduler:
|
@@ -75,6 +74,7 @@ class AsyncScheduler:
|
|
75
74
|
task = self.tasks[task_id]
|
76
75
|
task.running = True
|
77
76
|
|
77
|
+
elapsed = 0.0
|
78
78
|
try:
|
79
79
|
while self._running:
|
80
80
|
task.last_run = utc_now()
|
@@ -83,11 +83,11 @@ class AsyncScheduler:
|
|
83
83
|
await task.func(*task.args, **task.kwargs)
|
84
84
|
except Exception:
|
85
85
|
task.error_count += 1
|
86
|
-
logger.exception("Error in task",
|
86
|
+
logger.exception("Error in task", extra={"task_id": task_id, "error_count": task.error_count})
|
87
87
|
|
88
88
|
# Calculate elapsed time and sleep if needed
|
89
89
|
elapsed = (utc_now() - task.last_run).total_seconds()
|
90
|
-
sleep_time = max(0, task.interval - elapsed)
|
90
|
+
sleep_time = max(0.0, task.interval - elapsed)
|
91
91
|
if sleep_time > 0:
|
92
92
|
try:
|
93
93
|
await asyncio.sleep(sleep_time)
|
@@ -95,7 +95,7 @@ class AsyncScheduler:
|
|
95
95
|
break
|
96
96
|
finally:
|
97
97
|
task.running = False
|
98
|
-
logger.debug("Finished task",
|
98
|
+
logger.debug("Finished task", extra={"task_id": task_id, "elapsed": elapsed})
|
99
99
|
|
100
100
|
async def _start_all_tasks(self) -> None:
|
101
101
|
"""Starts all tasks concurrently using asyncio tasks."""
|
@@ -1,13 +1,12 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
+
import logging
|
4
5
|
from collections.abc import Awaitable
|
5
6
|
from dataclasses import dataclass
|
6
7
|
from typing import Any
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
logger = structlog.stdlib.get_logger()
|
9
|
+
logger = logging.getLogger(__name__)
|
11
10
|
|
12
11
|
|
13
12
|
class AsyncTaskRunner:
|
@@ -101,7 +100,7 @@ class AsyncTaskRunner:
|
|
101
100
|
results[task.task_id] = res
|
102
101
|
except Exception as e:
|
103
102
|
if not self.no_logging:
|
104
|
-
logger.exception("Task raised an exception", task_id
|
103
|
+
logger.exception("Task raised an exception", extra={"task_id": task.task_id})
|
105
104
|
exceptions[task.task_id] = e
|
106
105
|
|
107
106
|
# Create asyncio tasks for all runner tasks
|
mm_std/http_.py
CHANGED
@@ -7,7 +7,6 @@ from urllib.parse import urlencode
|
|
7
7
|
import aiohttp
|
8
8
|
import pydash
|
9
9
|
import requests
|
10
|
-
import rich
|
11
10
|
from aiohttp_socks import ProxyConnector
|
12
11
|
from requests.auth import AuthBase
|
13
12
|
|
@@ -212,7 +211,6 @@ async def hrequest_async(
|
|
212
211
|
except aiohttp.ClientConnectorError as err:
|
213
212
|
return HResponse(error=f"connection_error: {err}")
|
214
213
|
except aiohttp.ClientError as err:
|
215
|
-
rich.inspect(err)
|
216
214
|
return HResponse(error=f"connection_error: {err}")
|
217
215
|
except Exception as err:
|
218
216
|
if "couldn't connect to proxy" in str(err).lower():
|
mm_std/log.py
CHANGED
@@ -2,8 +2,68 @@ import logging
|
|
2
2
|
from logging.handlers import RotatingFileHandler
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
-
import
|
6
|
-
|
5
|
+
from rich.logging import RichHandler
|
6
|
+
|
7
|
+
|
8
|
+
class ExtraFormatter(logging.Formatter):
|
9
|
+
def format(self, record: logging.LogRecord) -> str:
|
10
|
+
base = super().format(record)
|
11
|
+
extras = {
|
12
|
+
key: value
|
13
|
+
for key, value in record.__dict__.items()
|
14
|
+
if key not in logging.LogRecord.__dict__
|
15
|
+
and key
|
16
|
+
not in (
|
17
|
+
"name",
|
18
|
+
"msg",
|
19
|
+
"args",
|
20
|
+
"levelname",
|
21
|
+
"levelno",
|
22
|
+
"pathname",
|
23
|
+
"filename",
|
24
|
+
"module",
|
25
|
+
"exc_info",
|
26
|
+
"exc_text",
|
27
|
+
"stack_info",
|
28
|
+
"lineno",
|
29
|
+
"funcName",
|
30
|
+
"created",
|
31
|
+
"msecs",
|
32
|
+
"relativeCreated",
|
33
|
+
"thread",
|
34
|
+
"threadName",
|
35
|
+
"processName",
|
36
|
+
"process",
|
37
|
+
"message",
|
38
|
+
"taskName",
|
39
|
+
"asctime",
|
40
|
+
)
|
41
|
+
}
|
42
|
+
if extras:
|
43
|
+
extras_str = " | " + " ".join(f"{k}={v}" for k, v in extras.items())
|
44
|
+
return base + extras_str
|
45
|
+
return base
|
46
|
+
|
47
|
+
|
48
|
+
def configure_logging(developer_console: bool = False, console_level: int = logging.INFO) -> None:
|
49
|
+
"""
|
50
|
+
Configure the root logger with a custom formatter that includes extra fields.
|
51
|
+
"""
|
52
|
+
logger = logging.getLogger()
|
53
|
+
logger.setLevel(console_level)
|
54
|
+
logger.handlers.clear()
|
55
|
+
|
56
|
+
console_handler: logging.Handler
|
57
|
+
|
58
|
+
if developer_console:
|
59
|
+
console_handler = RichHandler(rich_tracebacks=True, show_time=True, show_level=True, show_path=False)
|
60
|
+
formatter = ExtraFormatter("{message}", style="{")
|
61
|
+
else:
|
62
|
+
console_handler = logging.StreamHandler()
|
63
|
+
formatter = ExtraFormatter("{asctime} - {name} - {levelname} - {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{")
|
64
|
+
console_handler.setFormatter(formatter)
|
65
|
+
|
66
|
+
logger.addHandler(console_handler)
|
7
67
|
|
8
68
|
|
9
69
|
def init_logger(name: str, file_path: str | None = None, file_mkdir: bool = True, level: int = logging.DEBUG) -> logging.Logger:
|
@@ -23,54 +83,3 @@ def init_logger(name: str, file_path: str | None = None, file_mkdir: bool = True
|
|
23
83
|
file_handler.setFormatter(fmt)
|
24
84
|
log.addHandler(file_handler)
|
25
85
|
return log
|
26
|
-
|
27
|
-
|
28
|
-
def configure_structlog(json_logs: bool = False, log_level: str = "DEBUG", compact: bool = False) -> None:
|
29
|
-
timestamper: Processor = structlog.processors.TimeStamper(fmt="%H:%M:%S" if compact else "[%Y-%m-%d %H:%M:%S.%f]")
|
30
|
-
|
31
|
-
shared_processors: list[Processor] = [
|
32
|
-
structlog.contextvars.merge_contextvars,
|
33
|
-
structlog.stdlib.add_logger_name,
|
34
|
-
structlog.stdlib.add_log_level,
|
35
|
-
structlog.stdlib.PositionalArgumentsFormatter(),
|
36
|
-
structlog.stdlib.ExtraAdder(),
|
37
|
-
timestamper,
|
38
|
-
structlog.processors.StackInfoRenderer(),
|
39
|
-
]
|
40
|
-
|
41
|
-
if json_logs:
|
42
|
-
# Format the exception only for JSON logs, as we want to pretty-print them when
|
43
|
-
# using the ConsoleRenderer
|
44
|
-
shared_processors.append(structlog.processors.format_exc_info)
|
45
|
-
|
46
|
-
structlog.configure(
|
47
|
-
processors=shared_processors # noqa: RUF005
|
48
|
-
+ [
|
49
|
-
# Prepare event dict for `ProcessorFormatter`.
|
50
|
-
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
51
|
-
],
|
52
|
-
logger_factory=structlog.stdlib.LoggerFactory(),
|
53
|
-
cache_logger_on_first_use=True,
|
54
|
-
)
|
55
|
-
|
56
|
-
log_renderer: Processor
|
57
|
-
log_renderer = structlog.processors.JSONRenderer() if json_logs else structlog.dev.ConsoleRenderer()
|
58
|
-
|
59
|
-
formatter: structlog.stdlib.ProcessorFormatter = structlog.stdlib.ProcessorFormatter(
|
60
|
-
# These run ONLY on `logging` entries that do NOT originate within
|
61
|
-
# structlog.
|
62
|
-
foreign_pre_chain=shared_processors,
|
63
|
-
# These run on ALL entries after the pre_chain is done.
|
64
|
-
processors=[
|
65
|
-
# Remove _record & _from_structlog.
|
66
|
-
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
67
|
-
log_renderer,
|
68
|
-
],
|
69
|
-
)
|
70
|
-
|
71
|
-
handler = logging.StreamHandler()
|
72
|
-
# Use OUR `ProcessorFormatter` to format all `logging` entries.
|
73
|
-
handler.setFormatter(formatter)
|
74
|
-
root_logger: logging.Logger = logging.getLogger()
|
75
|
-
root_logger.addHandler(handler)
|
76
|
-
root_logger.setLevel(log_level.upper())
|
mm_std/result.py
CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import time
|
4
4
|
from collections.abc import Callable
|
5
|
-
from typing import Any, ClassVar, Literal, NoReturn, TypeVar, Union
|
5
|
+
from typing import Any, ClassVar, Literal, NoReturn, TypeGuard, TypeVar, Union
|
6
6
|
|
7
7
|
from pydantic_core import core_schema
|
8
8
|
|
@@ -258,6 +258,16 @@ class UnwrapError(Exception):
|
|
258
258
|
return self._result
|
259
259
|
|
260
260
|
|
261
|
+
def ok(result: Result[T]) -> TypeGuard[Ok[T]]:
|
262
|
+
"""Used for type narrowing from `Result` to `Ok`."""
|
263
|
+
return isinstance(result, Ok)
|
264
|
+
|
265
|
+
|
266
|
+
def err(result: Result[T]) -> TypeGuard[Err]:
|
267
|
+
"""Used for type narrowing from `Result` to `Err`."""
|
268
|
+
return isinstance(result, Err)
|
269
|
+
|
270
|
+
|
261
271
|
def try_ok[T](fn: Callable[..., Result[T]], *, args: tuple[object], attempts: int, delay: float = 0) -> Result[T]:
|
262
272
|
if attempts <= 0:
|
263
273
|
raise ValueError("attempts must be more than zero")
|
@@ -1,15 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mm-std
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.23
|
4
4
|
Requires-Python: >=3.12
|
5
5
|
Requires-Dist: aiohttp-socks~=0.10.1
|
6
6
|
Requires-Dist: aiohttp~=3.11.16
|
7
7
|
Requires-Dist: cryptography~=44.0.2
|
8
8
|
Requires-Dist: pydantic-settings>=2.8.1
|
9
|
-
Requires-Dist: pydantic~=2.11.
|
9
|
+
Requires-Dist: pydantic~=2.11.3
|
10
10
|
Requires-Dist: pydash~=8.0.5
|
11
11
|
Requires-Dist: python-dotenv~=1.1.0
|
12
12
|
Requires-Dist: requests[socks]~=2.32.3
|
13
13
|
Requires-Dist: rich>=13.9.0
|
14
|
-
Requires-Dist: structlog>=25.2.0
|
15
14
|
Requires-Dist: tomlkit~=0.13.2
|
@@ -1,4 +1,4 @@
|
|
1
|
-
mm_std/__init__.py,sha256=
|
1
|
+
mm_std/__init__.py,sha256=bdkBTX7_u4NYtTRdVDHgZUozQlFRBx3TTiMK7Cenup0,3100
|
2
2
|
mm_std/command.py,sha256=ze286wjUjg0QSTgIu-2WZks53_Vclg69UaYYgPpQvCU,1283
|
3
3
|
mm_std/config.py,sha256=4ox4D2CgGR76bvZ2n2vGQOYUDagFnlKEDb87to5zpxE,1871
|
4
4
|
mm_std/crypto.py,sha256=jdk0_TCmeU0pPXMyz9xH6kQHSjjZ9GcGClBwQps5vBo,340
|
@@ -6,25 +6,25 @@ mm_std/date.py,sha256=976eEkSONuNqHQBgSRu8hrtH23tJqztbmHFHLdbP2TY,1879
|
|
6
6
|
mm_std/dict.py,sha256=6GkhJPXD0LiJDxPcYe6jPdEDw-MN7P7mKu6U5XxwYDk,675
|
7
7
|
mm_std/env.py,sha256=5zaR9VeIfObN-4yfgxoFeU5IM1GDeZZj9SuYf7t9sOA,125
|
8
8
|
mm_std/fs.py,sha256=RwarNRJq3tIMG6LVX_g03hasfYpjYFh_O27oVDt5IPQ,291
|
9
|
-
mm_std/http_.py,sha256=
|
9
|
+
mm_std/http_.py,sha256=75GX21e3OSVjJsHzUjFoMIfT8Iq4Fe6YHaEr9EWOF4o,7422
|
10
10
|
mm_std/json_.py,sha256=Naa6mBE4D0yiQGkPNRrFvndnUH3R7ovw3FeaejWV60o,1196
|
11
|
-
mm_std/log.py,sha256=
|
11
|
+
mm_std/log.py,sha256=0TkTsAlUTt00gjgukvsvnZRIAGELq0MI6Lv8mKP-Wz4,2887
|
12
12
|
mm_std/net.py,sha256=qdRCBIDneip6FaPNe5mx31UtYVmzqam_AoUF7ydEyjA,590
|
13
13
|
mm_std/print_.py,sha256=zB7sVbSSF8RffMxvnOdbKCXjCKtKzKV3R68pBri4NkQ,1638
|
14
14
|
mm_std/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
mm_std/random_.py,sha256=OuUX4VJeSd13NZBya4qrGpR2TfN7_87tfebOY6DBUnI,1113
|
16
|
-
mm_std/result.py,sha256=
|
16
|
+
mm_std/result.py,sha256=2cLa0Lb1W7IY1Kxn-r-9GyqDz3colabp00dcvr7AKbU,7917
|
17
17
|
mm_std/str.py,sha256=BEjJ1p5O4-uSYK0h-enasSSDdwzkBbiwdQ4_dsrlEE8,3257
|
18
18
|
mm_std/toml.py,sha256=CNznWKR0bpOxS6e3VB5LGS-Oa9lW-wterkcPUFtPcls,610
|
19
19
|
mm_std/types_.py,sha256=9FGd2q47a8M9QQgsWJR1Kq34jLxBAkYSoJuwih4PPqg,257
|
20
20
|
mm_std/zip.py,sha256=axzF1BwcIygtfNNTefZH7hXKaQqwe-ZH3ChuRWr9dnk,396
|
21
21
|
mm_std/concurrency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
mm_std/concurrency/async_decorators.py,sha256=xEpyipzp3ZhaPHtdeTE-Ikrt67SUTFKBE6LQPeoeh6Q,1735
|
23
|
-
mm_std/concurrency/async_scheduler.py,sha256=
|
24
|
-
mm_std/concurrency/async_task_runner.py,sha256=
|
23
|
+
mm_std/concurrency/async_scheduler.py,sha256=TSu2WDJ8vfKZ4-saM3iZdcpGz8yPpSmud-7jJ4XKdJs,5579
|
24
|
+
mm_std/concurrency/async_task_runner.py,sha256=EN7tN2enkVYVgDbhSiAr-_W4o9m9wBXCveXGT8aVXII,4994
|
25
25
|
mm_std/concurrency/sync_decorators.py,sha256=syCQBOmN7qPO55yzgJB2rbkh10CVww376hmyvs6e5tA,1080
|
26
26
|
mm_std/concurrency/sync_scheduler.py,sha256=j4tBL_cBI1spr0cZplTA7N2CoYsznuORMeRN8rpR6gY,2407
|
27
27
|
mm_std/concurrency/sync_task_runner.py,sha256=s5JPlLYLGQGHIxy4oDS-PN7O9gcy-yPZFoNm8RQwzcw,1780
|
28
|
-
mm_std-0.3.
|
29
|
-
mm_std-0.3.
|
30
|
-
mm_std-0.3.
|
28
|
+
mm_std-0.3.23.dist-info/METADATA,sha256=8i2Wl_kTQ4IApdDBUzpbp8Q6P0RG3V8bffoMp2_9UeE,415
|
29
|
+
mm_std-0.3.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
30
|
+
mm_std-0.3.23.dist-info/RECORD,,
|
File without changes
|