Coverage for amqtt/utils.py: 80%
49 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-08-12 14:35 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-08-12 14:35 +0000
1from __future__ import annotations
3from importlib import import_module
4import logging
5from pathlib import Path
6import secrets
7import string
8import sys
9import typing
10from typing import Any
12import yaml
14if typing.TYPE_CHECKING: 14 ↛ 15line 14 didn't jump to line 15 because the condition on line 14 was never true
15 from amqtt.session import Session
17logger = logging.getLogger(__name__)
20def format_client_message(
21 session: Session | None = None,
22 address: str | None = None,
23 port: int | None = None,
24) -> str:
25 """Format a client message for logging."""
26 if session:
27 return f"(client id={session.client_id})"
28 if address is not None and port is not None:
29 return f"(client @={address}:{port})"
30 return "(unknown client)"
33def gen_client_id() -> str:
34 """Generate a random client ID."""
35 gen_id = "amqtt/"
37 # Use secrets to generate a secure random client ID
38 # Defining a valid set of characters for client ID generation
39 valid_chars = string.ascii_letters + string.digits
40 gen_id += "".join(secrets.choice(valid_chars) for _ in range(16))
41 return gen_id
44def read_yaml_config(config_file: str | Path) -> dict[str, Any] | None:
45 """Read a YAML configuration file."""
46 try:
47 with Path(str(config_file)).open(encoding="utf-8") as stream:
48 yaml_result: dict[str, Any] = yaml.full_load(stream)
49 return yaml_result
50 except yaml.YAMLError:
51 logger.exception(f"Invalid config_file {config_file}")
52 return None
55def cached_import(module_path: str, class_name: str | None = None) -> Any:
56 """Return cached import of a class from a module path (or retrieve, cache and then return)."""
57 # Check whether module is loaded and fully initialized.
58 if not ((module := sys.modules.get(module_path))
59 and (spec := getattr(module, "__spec__", None))
60 and getattr(spec, "_initializing", False) is False):
61 module = import_module(module_path)
62 if class_name: 62 ↛ 64line 62 didn't jump to line 64 because the condition on line 62 was always true
63 return getattr(module, class_name)
64 return module
67def import_string(dotted_path: str) -> Any:
68 """Import a dotted module path.
70 Returns:
71 attribute/class designated by the last name in the path
73 Raises:
74 ImportError (if the import failed)
76 """
77 try:
78 module_path, class_name = dotted_path.rsplit(".", 1)
79 except ValueError as err:
80 msg = f"{dotted_path} doesn't look like a module path"
81 raise ImportError(msg) from err
83 try:
84 return cached_import(module_path, class_name)
85 except AttributeError as err:
86 msg = f'Module "{module_path}" does not define a "{class_name}" attribute/class'
88 raise ImportError(msg) from err