updates2mqtt 1.4.0__py3-none-any.whl → 1.4.1__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.
- updates2mqtt/app.py +15 -10
- updates2mqtt/integrations/docker.py +10 -4
- updates2mqtt/model.py +4 -0
- updates2mqtt/mqtt.py +6 -5
- {updates2mqtt-1.4.0.dist-info → updates2mqtt-1.4.1.dist-info}/METADATA +5 -1
- {updates2mqtt-1.4.0.dist-info → updates2mqtt-1.4.1.dist-info}/RECORD +9 -9
- {updates2mqtt-1.4.0.dist-info → updates2mqtt-1.4.1.dist-info}/WHEEL +0 -0
- {updates2mqtt-1.4.0.dist-info → updates2mqtt-1.4.1.dist-info}/entry_points.txt +0 -0
- {updates2mqtt-1.4.0.dist-info → updates2mqtt-1.4.1.dist-info}/licenses/LICENSE +0 -0
updates2mqtt/app.py
CHANGED
|
@@ -16,7 +16,7 @@ from updates2mqtt.model import Discovery, ReleaseProvider
|
|
|
16
16
|
|
|
17
17
|
from .config import Config, load_app_config, load_package_info
|
|
18
18
|
from .integrations.docker import DockerProvider
|
|
19
|
-
from .mqtt import
|
|
19
|
+
from .mqtt import MqttPublisher
|
|
20
20
|
|
|
21
21
|
log = structlog.get_logger()
|
|
22
22
|
|
|
@@ -48,7 +48,7 @@ class App:
|
|
|
48
48
|
log.debug("Logging initialized", level=self.cfg.log.level)
|
|
49
49
|
self.common_pkg = load_package_info(PKG_INFO_FILE)
|
|
50
50
|
|
|
51
|
-
self.publisher =
|
|
51
|
+
self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
|
|
52
52
|
|
|
53
53
|
self.scanners: list[ReleaseProvider] = []
|
|
54
54
|
self.scan_count: int = 0
|
|
@@ -74,7 +74,7 @@ class App:
|
|
|
74
74
|
break
|
|
75
75
|
log.info("Scanning", source=scanner.source_type, session=session)
|
|
76
76
|
async with asyncio.TaskGroup() as tg:
|
|
77
|
-
async for discovery in scanner.scan(session): #
|
|
77
|
+
async for discovery in scanner.scan(session): # xtype: ignore[attr-defined]
|
|
78
78
|
tg.create_task(self.on_discovery(discovery), name=f"discovery-{discovery.name}")
|
|
79
79
|
if self.stopped.is_set():
|
|
80
80
|
break
|
|
@@ -83,7 +83,7 @@ class App:
|
|
|
83
83
|
log.info("Scan complete", source_type=scanner.source_type)
|
|
84
84
|
self.last_scan_timestamp = datetime.now(UTC).isoformat()
|
|
85
85
|
|
|
86
|
-
async def
|
|
86
|
+
async def main_loop(self) -> None:
|
|
87
87
|
log.debug("Starting run loop")
|
|
88
88
|
self.publisher.start()
|
|
89
89
|
|
|
@@ -93,7 +93,7 @@ class App:
|
|
|
93
93
|
f"Setting up healthcheck every {self.cfg.node.healthcheck.interval} seconds to topic {self.healthcheck_topic}"
|
|
94
94
|
)
|
|
95
95
|
self.healthcheck_loop_task = asyncio.create_task(
|
|
96
|
-
repeated_call(self.healthcheck, interval=self.cfg.node.healthcheck.interval)
|
|
96
|
+
repeated_call(self.healthcheck, interval=self.cfg.node.healthcheck.interval), name="healthcheck"
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
for scanner in self.scanners:
|
|
@@ -145,7 +145,8 @@ class App:
|
|
|
145
145
|
log.info(f"Cancelling {len(running_tasks)} tasks")
|
|
146
146
|
for t in running_tasks:
|
|
147
147
|
log.debug("Cancelling task", task=t.get_name())
|
|
148
|
-
t.
|
|
148
|
+
if t.get_name() == "healthcheck" or t.get_name().startswith("discovery-"):
|
|
149
|
+
t.cancel()
|
|
149
150
|
await asyncio.gather(*running_tasks, return_exceptions=True)
|
|
150
151
|
log.debug("Cancellation task completed")
|
|
151
152
|
|
|
@@ -154,7 +155,11 @@ class App:
|
|
|
154
155
|
self.stopped.set()
|
|
155
156
|
for scanner in self.scanners:
|
|
156
157
|
scanner.stop()
|
|
157
|
-
interrupt_task = asyncio.get_event_loop().create_task(
|
|
158
|
+
interrupt_task = asyncio.get_event_loop().create_task(
|
|
159
|
+
self.interrupt_tasks(),
|
|
160
|
+
eager_start=True, # type: ignore[call-arg] # pyright: ignore[reportCallIssue]
|
|
161
|
+
name="interrupt",
|
|
162
|
+
)
|
|
158
163
|
for t in asyncio.all_tasks():
|
|
159
164
|
log.debug("Tasks waiting = %s", t)
|
|
160
165
|
self.publisher.stop()
|
|
@@ -168,7 +173,7 @@ class App:
|
|
|
168
173
|
self.publisher.publish(
|
|
169
174
|
topic=self.healthcheck_topic,
|
|
170
175
|
payload={
|
|
171
|
-
"version": updates2mqtt.version,
|
|
176
|
+
"version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
|
|
172
177
|
"node": self.cfg.node.name,
|
|
173
178
|
"heartbeat_raw": time.time(),
|
|
174
179
|
"heartbeat_stamp": datetime.now(UTC).isoformat(),
|
|
@@ -197,12 +202,12 @@ def run() -> None:
|
|
|
197
202
|
|
|
198
203
|
from .app import App
|
|
199
204
|
|
|
200
|
-
log.debug(f"Starting updates2mqtt v{updates2mqtt.version}")
|
|
205
|
+
log.debug(f"Starting updates2mqtt v{updates2mqtt.version}") # pyright: ignore[reportAttributeAccessIssue]
|
|
201
206
|
app = App()
|
|
202
207
|
|
|
203
208
|
signal.signal(signal.SIGTERM, app.shutdown)
|
|
204
209
|
try:
|
|
205
|
-
asyncio.run(app.
|
|
210
|
+
asyncio.run(app.main_loop(), debug=False)
|
|
206
211
|
log.debug("App exited gracefully")
|
|
207
212
|
except asyncio.CancelledError:
|
|
208
213
|
log.debug("App exited on cancelled task")
|
|
@@ -135,8 +135,8 @@ class DockerProvider(ReleaseProvider):
|
|
|
135
135
|
image_ref = None
|
|
136
136
|
image_name = None
|
|
137
137
|
local_versions = None
|
|
138
|
-
if c.attrs is None:
|
|
139
|
-
logger.warn("No container attributes found, discovery rejected")
|
|
138
|
+
if c.attrs is None or not c.attrs:
|
|
139
|
+
logger.warn("No container attributes found, discovery rejected")
|
|
140
140
|
return None
|
|
141
141
|
if c.name is None:
|
|
142
142
|
logger.warn("No container name found, discovery rejected")
|
|
@@ -145,7 +145,7 @@ class DockerProvider(ReleaseProvider):
|
|
|
145
145
|
def env_override(env_var: str, default: Any) -> Any | None:
|
|
146
146
|
return default if c_env.get(env_var) is None else c_env.get(env_var)
|
|
147
147
|
|
|
148
|
-
env_str = c.attrs
|
|
148
|
+
env_str = c.attrs.get("Config", {}).get("Env")
|
|
149
149
|
c_env = dict(env.split("=", maxsplit=1) for env in env_str if "==" not in env)
|
|
150
150
|
ignore_container: str | None = env_override("UPD2MQTT_IGNORE", "FALSE")
|
|
151
151
|
if ignore_container and ignore_container.upper() in ("1", "TRUE"):
|
|
@@ -247,11 +247,17 @@ class DockerProvider(ReleaseProvider):
|
|
|
247
247
|
can_build: bool = self.cfg.allow_build and custom.get("git_repo_path") is not None
|
|
248
248
|
can_restart: bool = self.cfg.allow_restart and custom.get("compose_path") is not None
|
|
249
249
|
can_update: bool = False
|
|
250
|
+
if self.cfg.allow_pull and not can_pull and not can_build:
|
|
251
|
+
logger.info(
|
|
252
|
+
f"Pull not available, image_ref:{image_ref},local_version:{local_version},latest_version:{latest_version}"
|
|
253
|
+
)
|
|
250
254
|
if can_pull or can_build or can_restart:
|
|
251
255
|
# public install-neutral capabilities and Home Assistant features
|
|
252
256
|
can_update = True
|
|
253
257
|
features.append("INSTALL")
|
|
254
258
|
features.append("PROGRESS")
|
|
259
|
+
elif any((self.cfg.allow_build, self.cfg.allow_restart, self.cfg.allow_pull)):
|
|
260
|
+
logger.info(f"Update not available, can_pull:{can_pull}, can_build:{can_build},can_restart{can_restart}")
|
|
255
261
|
if relnotes_url:
|
|
256
262
|
features.append("RELEASE_NOTES")
|
|
257
263
|
custom["can_pull"] = can_pull
|
|
@@ -279,7 +285,7 @@ class DockerProvider(ReleaseProvider):
|
|
|
279
285
|
logger.exception("Docker Discovery Failure", container_attrs=c.attrs)
|
|
280
286
|
return None
|
|
281
287
|
|
|
282
|
-
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
288
|
+
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
283
289
|
logger = self.log.bind(session=session, action="scan")
|
|
284
290
|
containers = results = 0
|
|
285
291
|
for c in self.client.containers.list():
|
updates2mqtt/model.py
CHANGED
|
@@ -80,6 +80,10 @@ class ReleaseProvider:
|
|
|
80
80
|
@abstractmethod
|
|
81
81
|
async def scan(self, session: str) -> AsyncGenerator[Discovery]:
|
|
82
82
|
"""Scan for components to monitor"""
|
|
83
|
+
raise NotImplementedError
|
|
84
|
+
# force recognition as an async generator
|
|
85
|
+
if False: # type: ignore[unreachable]
|
|
86
|
+
yield 0
|
|
83
87
|
|
|
84
88
|
def hass_config_format(self, discovery: Discovery) -> dict:
|
|
85
89
|
_ = discovery
|
updates2mqtt/mqtt.py
CHANGED
|
@@ -28,7 +28,7 @@ class LocalMessage:
|
|
|
28
28
|
payload: str | None = field(default=None)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
31
|
+
class MqttPublisher:
|
|
32
32
|
def __init__(self, cfg: MqttConfig, node_cfg: NodeConfig, hass_cfg: HomeAssistantConfig) -> None:
|
|
33
33
|
self.cfg: MqttConfig = cfg
|
|
34
34
|
self.node_cfg: NodeConfig = node_cfg
|
|
@@ -90,7 +90,7 @@ class MqttClient:
|
|
|
90
90
|
self.client = None
|
|
91
91
|
|
|
92
92
|
def is_available(self) -> bool:
|
|
93
|
-
return not self.fatal_failure.is_set()
|
|
93
|
+
return self.client is not None and not self.fatal_failure.is_set()
|
|
94
94
|
|
|
95
95
|
def on_connect(
|
|
96
96
|
self, _client: mqtt.Client, _userdata: Any, _flags: mqtt.ConnectFlags, rc: ReasonCode, _props: Properties | None
|
|
@@ -103,8 +103,8 @@ class MqttClient:
|
|
|
103
103
|
log.error("Invalid MQTT credentials", result_code=rc)
|
|
104
104
|
|
|
105
105
|
self.log.info("Connected to broker", result_code=rc)
|
|
106
|
-
for topic in self.providers_by_topic:
|
|
107
|
-
self.log.info("(Re)subscribing", topic=topic)
|
|
106
|
+
for topic, provider in self.providers_by_topic.items():
|
|
107
|
+
self.log.info("(Re)subscribing", topic=topic, provider=provider.source_type)
|
|
108
108
|
self.client.subscribe(topic)
|
|
109
109
|
|
|
110
110
|
def on_disconnect(
|
|
@@ -272,7 +272,8 @@ class MqttClient:
|
|
|
272
272
|
if msg.topic in self.providers_by_topic:
|
|
273
273
|
self.handle_message(msg)
|
|
274
274
|
else:
|
|
275
|
-
|
|
275
|
+
# apparently the root non-wildcard sub sometimes brings in child topics
|
|
276
|
+
self.log.debug("Unhandled message #%s on %s:%s", msg.mid, msg.topic, msg.payload)
|
|
276
277
|
|
|
277
278
|
def handle_message(self, msg: mqtt.MQTTMessage | LocalMessage) -> None:
|
|
278
279
|
def update_start(discovery: Discovery) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: updates2mqtt
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: System update and docker image notification and execution over MQTT
|
|
5
5
|
Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
|
|
6
6
|
Project-URL: Repository, https://github.com/rhizomatics/updates2mqtt
|
|
@@ -39,8 +39,12 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
# updates2mqtt
|
|
41
41
|
|
|
42
|
+
[](https://github.com/rhizomatics)
|
|
43
|
+
|
|
42
44
|
[](https://pypi.org/project/updates2mqtt/)
|
|
43
45
|
[](https://github.com/rhizomatics/supernotify)
|
|
46
|
+

|
|
47
|
+

|
|
44
48
|
[](https://results.pre-commit.ci/latest/github/rhizomatics/updates2mqtt/main)
|
|
45
49
|
[](https://github.com/rhizomatics/updates2mqtt/actions/workflows/pypi-publish.yml)
|
|
46
50
|
[](https://github.com/rhizomatics/updates2mqtt/actions/workflows/python-package.yml)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
|
|
2
2
|
updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
|
|
3
|
-
updates2mqtt/app.py,sha256
|
|
3
|
+
updates2mqtt/app.py,sha256=-jAe9HcSSRmq5SZCvlsb8bFq2yVBNZQ0alQ5iRjl3tY,8518
|
|
4
4
|
updates2mqtt/config.py,sha256=SK6uhDyUb9C2JYVd0j6KBHzSAfaCFcOUbmmgsq6VSs0,5027
|
|
5
5
|
updates2mqtt/hass_formatter.py,sha256=Ulfj8F0e_1QMmRuJzHsNM2WxHbz9sIkWOWjRq3kQZzs,2772
|
|
6
|
-
updates2mqtt/model.py,sha256=
|
|
7
|
-
updates2mqtt/mqtt.py,sha256=
|
|
6
|
+
updates2mqtt/model.py,sha256=O6GQFhfQvwQDxlZScFHrpQgFNkUrUAeaGGP6AqNua78,3827
|
|
7
|
+
updates2mqtt/mqtt.py,sha256=WiGB2yFj2xUb-5LZh84zTPLilSvHn0CgUCn41FhC5Sk,14664
|
|
8
8
|
updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
|
|
10
|
-
updates2mqtt/integrations/docker.py,sha256=
|
|
10
|
+
updates2mqtt/integrations/docker.py,sha256=Yb0XsrRyNZYAkw_ayd2iYLPFwgeA51Lz9HBVAGq3rs4,19273
|
|
11
11
|
updates2mqtt/integrations/git_utils.py,sha256=bPCmQiZpKpMcrGI7xAVmePXHFn8WwjcPNkf7xqDsGQA,2319
|
|
12
|
-
updates2mqtt-1.4.
|
|
13
|
-
updates2mqtt-1.4.
|
|
14
|
-
updates2mqtt-1.4.
|
|
15
|
-
updates2mqtt-1.4.
|
|
16
|
-
updates2mqtt-1.4.
|
|
12
|
+
updates2mqtt-1.4.1.dist-info/METADATA,sha256=Q_yunyG90zU0B3uw2U5_FIImF28DHLNcK0otr6tTzVU,9310
|
|
13
|
+
updates2mqtt-1.4.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
updates2mqtt-1.4.1.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
|
|
15
|
+
updates2mqtt-1.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
updates2mqtt-1.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|