updates2mqtt 1.5.0__py3-none-any.whl → 1.5.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/config.py CHANGED
@@ -25,7 +25,7 @@ class MqttConfig:
25
25
  password: str = f"${{oc.env:MQTT_PASS,{MISSING}}}"
26
26
  port: int = "${oc.decode:${oc.env:MQTT_PORT,1883}}" # type: ignore[assignment]
27
27
  topic_root: str = "updates2mqtt"
28
- protocol: str = "3.11"
28
+ protocol: str = "${oc.env:MQTT_VERSION,3.11}"
29
29
 
30
30
 
31
31
  @dataclass
@@ -80,12 +80,12 @@ class NodeConfig:
80
80
 
81
81
  @dataclass
82
82
  class LogConfig:
83
- level: LogLevel = LogLevel.INFO
83
+ level: LogLevel = "${oc.decode:${oc.env:U2M_LOG_LEVEL,INFO}}" # type: ignore[assignment] # pyright: ignore[reportAssignmentType]
84
84
 
85
85
 
86
86
  @dataclass
87
87
  class Config:
88
- log: LogConfig = field(default_factory=LogConfig)
88
+ log: LogConfig = field(default_factory=LogConfig) # pyright: ignore[reportArgumentType, reportCallIssue]
89
89
  node: NodeConfig = field(default_factory=NodeConfig)
90
90
  mqtt: MqttConfig = field(default_factory=MqttConfig) # pyright: ignore[reportArgumentType, reportCallIssue]
91
91
  homeassistant: HomeAssistantConfig = field(default_factory=HomeAssistantConfig)
@@ -132,22 +132,29 @@ def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
132
132
  raise
133
133
 
134
134
 
135
+ def is_autogen_config() -> bool:
136
+ env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
137
+ return not (env_var and env_var.lower() in ("no", "0", "false"))
138
+
139
+
135
140
  def load_app_config(conf_file_path: Path, return_invalid: bool = False) -> Config | None:
136
141
  base_cfg: DictConfig = OmegaConf.structured(Config)
137
142
  if conf_file_path.exists():
138
143
  cfg: DictConfig = typing.cast("DictConfig", OmegaConf.merge(base_cfg, OmegaConf.load(conf_file_path)))
139
- else:
144
+ elif is_autogen_config():
140
145
  if not conf_file_path.parent.exists():
141
146
  try:
142
147
  log.debug(f"Creating config directory {conf_file_path.parent} if not already present")
143
148
  conf_file_path.parent.mkdir(parents=True, exist_ok=True)
144
149
  except Exception:
145
- log.exception("Unable to create config directory", path=conf_file_path.parent)
150
+ log.warning("Unable to create config directory", path=conf_file_path.parent)
146
151
  try:
147
152
  conf_file_path.write_text(OmegaConf.to_yaml(base_cfg))
148
153
  log.info(f"Auto-generated a new config file at {conf_file_path}")
149
154
  except Exception:
150
- log.exception("Unable to write config file", path=conf_file_path)
155
+ log.warning("Unable to write config file", path=conf_file_path)
156
+ cfg = base_cfg
157
+ else:
151
158
  cfg = base_cfg
152
159
 
153
160
  try:
@@ -21,7 +21,6 @@ HASS_UPDATE_SCHEMA = [
21
21
  def hass_format_config(
22
22
  discovery: Discovery,
23
23
  object_id: str,
24
- node_name: str,
25
24
  state_topic: str,
26
25
  command_topic: str | None,
27
26
  force_command_topic: bool | None,
@@ -29,13 +28,8 @@ def hass_format_config(
29
28
  area: str | None = None,
30
29
  session: str | None = None,
31
30
  ) -> dict[str, Any]:
32
- if device_creation:
33
- # avoid duplication, since Home Assistant will concatenate device and entity name on update
34
- name: str = f"{discovery.name} {discovery.source_type}"
35
- else:
36
- name = f"{discovery.name} {discovery.source_type} on {node_name}"
37
31
  config: dict[str, Any] = {
38
- "name": name,
32
+ "name": discovery.title,
39
33
  "device_class": None, # not firmware, so defaults to null
40
34
  "unique_id": object_id,
41
35
  "state_topic": state_topic,
@@ -46,7 +40,7 @@ def hass_format_config(
46
40
  "can_restart": discovery.can_restart,
47
41
  "update_policy": discovery.update_policy,
48
42
  "origin": {
49
- "name": f"{node_name} updates2mqtt",
43
+ "name": f"{discovery.node} updates2mqtt",
50
44
  "sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
51
45
  "support_url": "https://github.com/rhizomatics/updates2mqtt/issues",
52
46
  },
@@ -57,10 +51,10 @@ def hass_format_config(
57
51
  config["icon"] = discovery.device_icon
58
52
  if device_creation:
59
53
  config["device"] = {
60
- "name": f"{node_name} updates2mqtt",
54
+ "name": f"{discovery.node} updates2mqtt",
61
55
  "sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
62
56
  "manufacturer": "rhizomatics",
63
- "identifiers": [f"{node_name}.updates2mqtt"],
57
+ "identifiers": [f"{discovery.node}.updates2mqtt"],
64
58
  }
65
59
  if area:
66
60
  config["device"]["suggested_area"] = area
@@ -74,14 +68,11 @@ def hass_format_config(
74
68
  return config
75
69
 
76
70
 
77
- def hass_format_state(discovery: Discovery, node_name: str, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
78
- title: str = (
79
- discovery.title_template.format(name=discovery.name, node=node_name) if discovery.title_template else discovery.name
80
- )
71
+ def hass_format_state(discovery: Discovery, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
81
72
  state = {
82
73
  "installed_version": discovery.current_version,
83
74
  "latest_version": discovery.latest_version,
84
- "title": title,
75
+ "title": discovery.title,
85
76
  "in_progress": in_progress,
86
77
  }
87
78
  if discovery.release_summary:
@@ -101,18 +101,22 @@ class DockerProvider(ReleaseProvider):
101
101
  if not cwd or not Path(cwd).is_dir():
102
102
  logger.warn("Invalid compose path, skipped %s", command)
103
103
  return False
104
- logger.info(f"Executing compose {command} {args} {service}")
104
+
105
105
  cmd: str = "docker-compose" if self.cfg.compose_version == "v1" else "docker compose"
106
+ logger.info(f"Executing {cmd} {command} {args} {service}")
106
107
  cmd = cmd + " " + command.value
107
108
  if args:
108
109
  cmd = cmd + " " + args
109
110
  if service:
110
111
  cmd = cmd + " " + service
111
112
 
112
- proc = subprocess.run(cmd, check=False, shell=True, cwd=cwd)
113
+ proc: subprocess.CompletedProcess[str] = subprocess.run(cmd, check=False, shell=True, cwd=cwd, text=True)
113
114
  if proc.returncode == 0:
114
115
  logger.info(f"{command} via compose successful")
115
116
  return True
117
+ if proc.stderr and "unknown command: docker compose" in proc.stderr:
118
+ logger.warning("docker compose set to wrong version, seems like v1 installed")
119
+ self.cfg.compose_version = "v1"
116
120
  logger.warn(
117
121
  f"{command} failed: %s",
118
122
  proc.returncode,
@@ -275,6 +279,12 @@ class DockerProvider(ReleaseProvider):
275
279
  logger.info(f"Update not available, can_pull:{can_pull}, can_build:{can_build},can_restart{can_restart}")
276
280
  if relnotes_url:
277
281
  features.append("RELEASE_NOTES")
282
+ if can_pull:
283
+ update_type: str = "Docker Image"
284
+ elif can_build:
285
+ update_type = "Docker Build"
286
+ else:
287
+ update_type = "Unavailable"
278
288
  custom["can_pull"] = can_pull
279
289
 
280
290
  logger.debug("Analyze generated discovery", discovery_name=c.name, current_version=local_version)
@@ -282,15 +292,16 @@ class DockerProvider(ReleaseProvider):
282
292
  self,
283
293
  c.name,
284
294
  session,
295
+ node=self.node_cfg.name,
285
296
  entity_picture_url=picture_url,
286
297
  release_url=relnotes_url,
287
298
  current_version=local_version,
288
299
  update_policy=update_policy,
289
300
  update_last_attempt=(original_discovery and original_discovery.update_last_attempt) or None,
290
301
  latest_version=latest_version if latest_version != NO_KNOWN_IMAGE else local_version,
291
- title_template="Docker image update for {name} on {node}",
292
302
  device_icon=self.cfg.device_icon,
293
303
  can_update=can_update,
304
+ update_type=update_type,
294
305
  can_build=can_build,
295
306
  can_restart=can_restart,
296
307
  status=(c.status == "running" and "on") or "off",
updates2mqtt/model.py CHANGED
@@ -14,6 +14,7 @@ class Discovery:
14
14
  provider: "ReleaseProvider",
15
15
  name: str,
16
16
  session: str,
17
+ node: str,
17
18
  entity_picture_url: str | None = None,
18
19
  current_version: str | None = None,
19
20
  latest_version: str | None = None,
@@ -21,11 +22,12 @@ class Discovery:
21
22
  can_build: bool = False,
22
23
  can_restart: bool = False,
23
24
  status: str = "on",
25
+ update_type: str | None = "Update",
24
26
  update_policy: str | None = None,
25
27
  update_last_attempt: float | None = None,
26
28
  release_url: str | None = None,
27
29
  release_summary: str | None = None,
28
- title_template: str = "Update for {name} on {node}",
30
+ title_template: str = "{discovery.update_type} for {discovery.name} on {discovery.node}",
29
31
  device_icon: str | None = None,
30
32
  custom: dict[str, Any] | None = None,
31
33
  features: list[str] | None = None,
@@ -34,6 +36,7 @@ class Discovery:
34
36
  self.source_type: str = provider.source_type
35
37
  self.session: str = session
36
38
  self.name: str = name
39
+ self.node: str = node
37
40
  self.entity_picture_url: str | None = entity_picture_url
38
41
  self.current_version: str | None = current_version
39
42
  self.latest_version: str | None = latest_version
@@ -44,6 +47,7 @@ class Discovery:
44
47
  self.release_summary: str | None = release_summary
45
48
  self.title_template: str | None = title_template
46
49
  self.device_icon: str | None = device_icon
50
+ self.update_type: str | None = update_type
47
51
  self.status: str = status
48
52
  self.update_policy: str | None = update_policy
49
53
  self.update_last_attempt: float | None = update_last_attempt
@@ -54,6 +58,12 @@ class Discovery:
54
58
  """Build a custom string representation"""
55
59
  return f"Discovery('{self.name}','{self.source_type}',current={self.current_version},latest={self.latest_version})"
56
60
 
61
+ @property
62
+ def title(self) -> str:
63
+ if self.title_template:
64
+ return self.title_template.format(discovery=self)
65
+ return self.name
66
+
57
67
 
58
68
  class ReleaseProvider:
59
69
  """Abstract base class for release providers, such as container scanners or package managers API calls"""
updates2mqtt/mqtt.py CHANGED
@@ -309,7 +309,6 @@ class MqttPublisher:
309
309
  self.state_topic(discovery),
310
310
  hass_format_state(
311
311
  discovery,
312
- self.node_cfg.name,
313
312
  discovery.session,
314
313
  in_progress=in_progress,
315
314
  ),
@@ -322,7 +321,6 @@ class MqttPublisher:
322
321
  hass_format_config(
323
322
  discovery=discovery,
324
323
  object_id=object_id,
325
- node_name=self.node_cfg.name,
326
324
  area=self.hass_cfg.area,
327
325
  state_topic=self.state_topic(discovery),
328
326
  command_topic=self.command_topic(discovery.provider),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: updates2mqtt
3
- Version: 1.5.0
3
+ Version: 1.5.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
@@ -0,0 +1,16 @@
1
+ updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
2
+ updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
3
+ updates2mqtt/app.py,sha256=pYcNprv7_htqD6wiXX5j24FhPglEeFNdqMfPclq5SfU,8887
4
+ updates2mqtt/config.py,sha256=nlAszykZa7t9nu99RB8g_tIzQxmdfWyilTZJY3Fl4e4,6099
5
+ updates2mqtt/hass_formatter.py,sha256=bj6qpElMqt1DqKlxp4ZjwaCJAD-ed3xsq5ZOg4FbeC8,3216
6
+ updates2mqtt/model.py,sha256=KNsLflgWaRvGrNdq1Vy2QnDLfVSLPWLSt4gBXj9COa4,4170
7
+ updates2mqtt/mqtt.py,sha256=j7nlSka-LqRethZk_rc4Z8iOV7ey28TQFjSfrPeTjv4,14995
8
+ updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
10
+ updates2mqtt/integrations/docker.py,sha256=Mi3tFjVDbCIsHBgH6UQ3yC5Y7HQk9JgEyH6PQuNJtFU,21263
11
+ updates2mqtt/integrations/git_utils.py,sha256=bPCmQiZpKpMcrGI7xAVmePXHFn8WwjcPNkf7xqDsGQA,2319
12
+ updates2mqtt-1.5.1.dist-info/METADATA,sha256=iPMKAgEFTFZ98pouv3SnTTStP74SEPBuV8jN3QbBpX8,9392
13
+ updates2mqtt-1.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
+ updates2mqtt-1.5.1.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
15
+ updates2mqtt-1.5.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ updates2mqtt-1.5.1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- updates2mqtt/__init__.py,sha256=gnmHrLOSYc-N1-c5VG46OpNpoXEybKzYhEvFMm955P8,237
2
- updates2mqtt/__main__.py,sha256=HBF00oH5fhS33sI_CdbxNlaUvbIzuuGxwnRYdhHqx0M,194
3
- updates2mqtt/app.py,sha256=pYcNprv7_htqD6wiXX5j24FhPglEeFNdqMfPclq5SfU,8887
4
- updates2mqtt/config.py,sha256=TJakojUkPJ0xJ2aivJ9BIlDLw8glqJzTOsm1E__vJ9M,5706
5
- updates2mqtt/hass_formatter.py,sha256=adopRRagQte7ok1ZBPcYBIkDAo9YLXcI2y_jtdNWZP8,3638
6
- updates2mqtt/model.py,sha256=O6GQFhfQvwQDxlZScFHrpQgFNkUrUAeaGGP6AqNua78,3827
7
- updates2mqtt/mqtt.py,sha256=yNpYlYdDr6S4ltLZqAbjUgxsJQZKzOfcFJoUr-fcNlI,15077
8
- updates2mqtt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- updates2mqtt/integrations/__init__.py,sha256=KmNTUxvVWvqI7rl4I0xZg7XaCmcMS2O4OSv-ClsWM4Q,109
10
- updates2mqtt/integrations/docker.py,sha256=dTbiIB2bjiGY46ovlkiB998ekhXX4M2KhvZqfzqV-Oo,20790
11
- updates2mqtt/integrations/git_utils.py,sha256=bPCmQiZpKpMcrGI7xAVmePXHFn8WwjcPNkf7xqDsGQA,2319
12
- updates2mqtt-1.5.0.dist-info/METADATA,sha256=oowFbgm4wTYWv3vlBTgiYe3prCI2UVTJztMT4GWLEN8,9392
13
- updates2mqtt-1.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- updates2mqtt-1.5.0.dist-info/entry_points.txt,sha256=Hc6NZ2dBevYSUKTJU6NOs8Mw7Vt0S-9lq5FuKb76NCc,54
15
- updates2mqtt-1.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- updates2mqtt-1.5.0.dist-info/RECORD,,