somestuff-oepnvstuff 0.2.3__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.
- somestuff/oepnvstuff/__init__.py +118 -0
- somestuff/oepnvstuff/check_realtime.py +585 -0
- somestuff/oepnvstuff/gtfs_realtime.py +260 -0
- somestuff/oepnvstuff/gtfs_static.py +817 -0
- somestuff/oepnvstuff/monitor.py +627 -0
- somestuff/oepnvstuff/mqtt_bridge.py +389 -0
- somestuff/oepnvstuff/py.typed +0 -0
- somestuff_oepnvstuff-0.2.3.dist-info/METADATA +301 -0
- somestuff_oepnvstuff-0.2.3.dist-info/RECORD +12 -0
- somestuff_oepnvstuff-0.2.3.dist-info/WHEEL +4 -0
- somestuff_oepnvstuff-0.2.3.dist-info/entry_points.txt +2 -0
- somestuff_oepnvstuff-0.2.3.dist-info/licenses/LICENSE.md +163 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""oepnvstuff — GTFS / GTFS-Realtime public-transport (ÖPNV) tooling.
|
|
2
|
+
|
|
3
|
+
Validates whether an open GTFS-Realtime feed actually carries real-time data
|
|
4
|
+
(delays / actual times) for configurable transit lines at a configurable
|
|
5
|
+
station, either as a one-shot check or as a polling watch loop with pluggable
|
|
6
|
+
``on_*`` handlers (console reporting, MQTT publishing, …).
|
|
7
|
+
|
|
8
|
+
Central logging setup lives here (loguru), mirroring ucmstuff's setup. The
|
|
9
|
+
modules in this package (and third-party libraries such as ``requests``/
|
|
10
|
+
``urllib3`` and ``paho-mqtt``) log via the stdlib ``logging`` module, so
|
|
11
|
+
:func:`configure_logging` installs an intercept handler that funnels those
|
|
12
|
+
records into loguru instead of rewriting every call site.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from types import FrameType
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from loguru import logger as glogger
|
|
24
|
+
from tabulate import tabulate
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from loguru import Record
|
|
28
|
+
|
|
29
|
+
__version__ = "0.2.3"
|
|
30
|
+
|
|
31
|
+
__all__ = ["__version__", "configure_logging", "print_banner"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _loguru_skiplog_filter(record: "Record") -> bool:
|
|
35
|
+
"""Decide whether a loguru record should be emitted.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
record: The loguru record about to be handled by a sink.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
``False`` for records whose ``extra['skiplog']`` flag is truthy (they are
|
|
42
|
+
hidden), ``True`` otherwise.
|
|
43
|
+
"""
|
|
44
|
+
return not record.get("extra", {}).get("skiplog", False)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _InterceptHandler(logging.Handler):
|
|
48
|
+
"""Route stdlib ``logging`` records into loguru with correct call-site info.
|
|
49
|
+
|
|
50
|
+
Every module in this package uses ``logging.getLogger(...)``; installing this
|
|
51
|
+
on the root logger lets :func:`configure_logging` govern all of them (format,
|
|
52
|
+
level, colour) without touching the individual call sites.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
56
|
+
"""Forward a single stdlib record to loguru.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
record: The stdlib log record to re-emit through loguru, preserving
|
|
60
|
+
its level, message, exception info and originating call site.
|
|
61
|
+
"""
|
|
62
|
+
# Map the stdlib level name to a loguru level, falling back to the number.
|
|
63
|
+
level: str | int
|
|
64
|
+
try:
|
|
65
|
+
level = glogger.level(record.levelname).name
|
|
66
|
+
except ValueError:
|
|
67
|
+
level = record.levelno
|
|
68
|
+
# Walk out of the logging module so {module}/{function}/{line} point at the
|
|
69
|
+
# real caller rather than at this handler (canonical loguru intercept recipe).
|
|
70
|
+
frame: FrameType | None = inspect.currentframe()
|
|
71
|
+
depth = 0
|
|
72
|
+
while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
|
|
73
|
+
frame = frame.f_back
|
|
74
|
+
depth += 1
|
|
75
|
+
glogger.bind(classname=record.name).opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def configure_logging(
|
|
79
|
+
verbose: bool = False, loguru_filter: Callable[["Record"], bool] = _loguru_skiplog_filter
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Configure a default ``loguru`` sink and funnel stdlib logging into it.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
verbose: ``True`` logs at DEBUG, otherwise honours ``LOGURU_LEVEL`` (INFO
|
|
85
|
+
by default).
|
|
86
|
+
loguru_filter: Predicate deciding, per loguru record, whether it is emitted
|
|
87
|
+
by the sink. Defaults to :func:`_loguru_skiplog_filter`.
|
|
88
|
+
"""
|
|
89
|
+
level_name: str = "DEBUG" if verbose else os.getenv("LOGURU_LEVEL", "INFO")
|
|
90
|
+
os.environ["LOGURU_LEVEL"] = level_name
|
|
91
|
+
glogger.remove()
|
|
92
|
+
logger_fmt: str = (
|
|
93
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{module}</cyan>::<cyan>{extra[classname]}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
|
|
94
|
+
)
|
|
95
|
+
glogger.add(sys.stderr, level=level_name, format=logger_fmt, filter=loguru_filter) # type: ignore[arg-type]
|
|
96
|
+
glogger.configure(extra={"classname": "None", "skiplog": False})
|
|
97
|
+
# Replace any stdlib handlers with the intercept so all ``logging`` output
|
|
98
|
+
# (this package's and third-party libraries') flows through loguru.
|
|
99
|
+
logging.basicConfig(handlers=[_InterceptHandler()], level=logging.getLevelNamesMapping()[level_name], force=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def print_banner() -> None:
|
|
103
|
+
"""Log the startup banner with version, build time and project links."""
|
|
104
|
+
startup_rows = [
|
|
105
|
+
["version", __version__],
|
|
106
|
+
["buildtime", os.getenv("BUILDTIME", "unknown")],
|
|
107
|
+
["github", "https://github.com/vroomfondel/somestuff/tree/main/somestuff/oepnvstuff"],
|
|
108
|
+
["Docker Hub", "https://hub.docker.com/r/xomoxcc/somestuff"],
|
|
109
|
+
]
|
|
110
|
+
table_str = tabulate(startup_rows, tablefmt="mixed_grid")
|
|
111
|
+
lines = table_str.split("\n")
|
|
112
|
+
table_width = len(lines[0])
|
|
113
|
+
title = "oepnv GTFS-realtime checker starting up"
|
|
114
|
+
title_border = "┍" + "━" * (table_width - 2) + "┑"
|
|
115
|
+
title_row = "│ " + title.center(table_width - 4) + " │"
|
|
116
|
+
separator = lines[0].replace("┍", "┝").replace("┑", "┥").replace("┯", "┿")
|
|
117
|
+
|
|
118
|
+
glogger.opt(raw=True).info(f"\n{title_border}\n{title_row}\n{separator}\n{'\n'.join(lines[1:])}\n")
|