mm-std 0.3.21__py3-none-any.whl → 0.3.22__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 +1 -1
- mm_std/concurrency/async_scheduler.py +6 -6
- mm_std/concurrency/async_task_runner.py +3 -4
- mm_std/log.py +62 -53
- {mm_std-0.3.21.dist-info → mm_std-0.3.22.dist-info}/METADATA +1 -2
- {mm_std-0.3.21.dist-info → mm_std-0.3.22.dist-info}/RECORD +7 -7
- {mm_std-0.3.21.dist-info → mm_std-0.3.22.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
|
@@ -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/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())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mm-std
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.22
|
4
4
|
Requires-Python: >=3.12
|
5
5
|
Requires-Dist: aiohttp-socks~=0.10.1
|
6
6
|
Requires-Dist: aiohttp~=3.11.16
|
@@ -11,5 +11,4 @@ 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=ETGLXQW17ZnV5i6kdhoNA-1MulHZixpzGIOLlYpiYx0,3040
|
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
|
@@ -8,7 +8,7 @@ mm_std/env.py,sha256=5zaR9VeIfObN-4yfgxoFeU5IM1GDeZZj9SuYf7t9sOA,125
|
|
8
8
|
mm_std/fs.py,sha256=RwarNRJq3tIMG6LVX_g03hasfYpjYFh_O27oVDt5IPQ,291
|
9
9
|
mm_std/http_.py,sha256=cozBUGZcbKp9sZuEnu7bklwa6lTE0RxEUVo_aNt1_kE,7468
|
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
|
@@ -20,11 +20,11 @@ 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.22.dist-info/METADATA,sha256=BN3U-377sfY6l7yYDzLyW3RRkYqAU581moSJAfIDU8c,415
|
29
|
+
mm_std-0.3.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
30
|
+
mm_std-0.3.22.dist-info/RECORD,,
|
File without changes
|