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 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 configure_structlog as configure_structlog
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 = structlog.stdlib.get_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", task_id=task_id, error=task.error_count)
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", task_id=task_id, elapsed=elapsed)
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
- import structlog
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=task.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 structlog
6
- from structlog.typing import Processor
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.21
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.2
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=ATaZN6AzmRUJIc8IE4PichCbh45pNncbBidUG7V1m0Q,3044
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=cozBUGZcbKp9sZuEnu7bklwa6lTE0RxEUVo_aNt1_kE,7468
9
+ mm_std/http_.py,sha256=75GX21e3OSVjJsHzUjFoMIfT8Iq4Fe6YHaEr9EWOF4o,7422
10
10
  mm_std/json_.py,sha256=Naa6mBE4D0yiQGkPNRrFvndnUH3R7ovw3FeaejWV60o,1196
11
- mm_std/log.py,sha256=vQzNJ0oenvuHF9sXDI6CUZDR12BcDURUZVwkpdNle4Q,2983
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=KLnPWjICYFkP6CAhq7Ifs22XSD-PQ9RkG6n1-cZcXkM,7625
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=cTsEY0FJKqlA94Ap-5Yx4KFOc-0QP5lRocn4P2n3cdE,5526
24
- mm_std/concurrency/async_task_runner.py,sha256=JpofaXCSqQDgQTh1FvaCWxsUsFWNPJGfXpuTlkF7hS0,4988
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.21.dist-info/METADATA,sha256=_VWt4WbyHA-fN5KkHKXpSyu1evey3AtLnrJR4Sbdprs,448
29
- mm_std-0.3.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
- mm_std-0.3.21.dist-info/RECORD,,
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,,