updates2mqtt 1.6.0__py3-none-any.whl → 1.7.0__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 CHANGED
@@ -14,7 +14,7 @@ import structlog
14
14
  import updates2mqtt
15
15
  from updates2mqtt.model import Discovery, ReleaseProvider
16
16
 
17
- from .config import Config, PackageUpdateInfo, load_app_config, load_package_info
17
+ from .config import Config, PublishPolicy, UpdatePolicy, load_app_config
18
18
  from .integrations.docker import DockerProvider
19
19
  from .mqtt import MqttPublisher
20
20
 
@@ -49,7 +49,6 @@ class App:
49
49
 
50
50
  structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, str(self.cfg.log.level))))
51
51
  log.debug("Logging initialized", level=self.cfg.log.level)
52
- self.common_pkg: dict[str, PackageUpdateInfo] = load_package_info(PKG_INFO_FILE)
53
52
 
54
53
  self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
55
54
 
@@ -57,7 +56,7 @@ class App:
57
56
  self.scan_count: int = 0
58
57
  self.last_scan: str | None = None
59
58
  if self.cfg.docker.enabled:
60
- self.scanners.append(DockerProvider(self.cfg.docker, self.common_pkg, self.cfg.node, self.self_bounce))
59
+ self.scanners.append(DockerProvider(self.cfg.docker, self.cfg.node, self.self_bounce))
61
60
  self.stopped = Event()
62
61
  self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
63
62
 
@@ -104,6 +103,7 @@ class App:
104
103
  )
105
104
 
106
105
  for scanner in self.scanners:
106
+ scanner.initialize()
107
107
  self.publisher.subscribe_hass_command(scanner)
108
108
 
109
109
  while not self.stopped.is_set() and self.publisher.is_available():
@@ -122,12 +122,15 @@ class App:
122
122
  async def on_discovery(self, discovery: Discovery) -> None:
123
123
  dlog = log.bind(name=discovery.name)
124
124
  try:
125
- if self.cfg.homeassistant.discovery.enabled:
125
+ if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.cfg.homeassistant.discovery.enabled:
126
+ # Switch off MQTT discovery if not Home Assistant enabled
126
127
  self.publisher.publish_hass_config(discovery)
127
-
128
- self.publisher.publish_hass_state(discovery)
128
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT):
129
+ self.publisher.publish_hass_state(discovery)
130
+ if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
131
+ self.publisher.publish_discovery(discovery)
129
132
  if (
130
- discovery.update_policy == "Auto"
133
+ discovery.update_policy == UpdatePolicy.AUTO
131
134
  and discovery.can_update
132
135
  and discovery.latest_version != discovery.current_version
133
136
  ):
updates2mqtt/config.py CHANGED
@@ -9,6 +9,20 @@ from omegaconf import MISSING, DictConfig, MissingMandatoryValue, OmegaConf, Val
9
9
 
10
10
  log = structlog.get_logger()
11
11
 
12
+ PKG_INFO_FILE = Path("./common_packages.yaml")
13
+ NO_KNOWN_IMAGE = "UNKNOWN"
14
+
15
+
16
+ class UpdatePolicy(StrEnum):
17
+ AUTO = "Auto"
18
+ PASSIVE = "Passive"
19
+
20
+
21
+ class PublishPolicy(StrEnum):
22
+ HOMEASSISTANT = "HomeAssistant"
23
+ MQTT = "MQTT"
24
+ SILENT = "Silent"
25
+
12
26
 
13
27
  class LogLevel(StrEnum):
14
28
  DEBUG = "DEBUG"
@@ -18,6 +32,13 @@ class LogLevel(StrEnum):
18
32
  CRITICAL = "CRITICAL"
19
33
 
20
34
 
35
+ class VersionType:
36
+ SHORT_SHA = "short_sha"
37
+ FULL_SHA = "full_sha"
38
+ VERSION_REVISION = "version_revision"
39
+ VERSION = "version"
40
+
41
+
21
42
  @dataclass
22
43
  class MqttConfig:
23
44
  host: str = "${oc.env:MQTT_HOST,localhost}"
@@ -34,6 +55,12 @@ class MetadataSourceConfig:
34
55
  cache_ttl: int = 60 * 60 * 24 * 7 # 1 week
35
56
 
36
57
 
58
+ @dataclass
59
+ class Selector:
60
+ include: list[str] | None = None
61
+ exclude: list[str] | None = None
62
+
63
+
37
64
  @dataclass
38
65
  class DockerConfig:
39
66
  enabled: bool = True
@@ -47,7 +74,8 @@ class DockerConfig:
47
74
  discover_metadata: dict[str, MetadataSourceConfig] = field(
48
75
  default_factory=lambda: {"linuxserver.io": MetadataSourceConfig(enabled=True)}
49
76
  )
50
- api_throttle_wait: int = 60 * 15
77
+ default_api_backoff: int = 60 * 15
78
+ image_ref_select: Selector = field(default_factory=lambda: Selector())
51
79
 
52
80
 
53
81
  @dataclass
@@ -62,6 +90,7 @@ class HomeAssistantConfig:
62
90
  state_topic_suffix: str = "state"
63
91
  device_creation: bool = True
64
92
  force_command_topic: bool = False
93
+ extra_attributes: bool = True
65
94
  area: str | None = None
66
95
 
67
96
 
@@ -115,24 +144,6 @@ class IncompleteConfigException(BaseException):
115
144
  pass
116
145
 
117
146
 
118
- def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
119
- if pkginfo_file_path.exists():
120
- log.debug("Loading common package update info", path=pkginfo_file_path)
121
- cfg = OmegaConf.load(pkginfo_file_path)
122
- else:
123
- log.warn("No common package update info found", path=pkginfo_file_path)
124
- cfg = OmegaConf.structured(UpdateInfoConfig)
125
- try:
126
- # omegaconf broken-ness on optional fields and converting to backclasses
127
- pkg_conf: dict[str, PackageUpdateInfo] = {
128
- pkg: PackageUpdateInfo(**pkg_cfg) for pkg, pkg_cfg in cfg.common_packages.items()
129
- }
130
- return pkg_conf
131
- except (MissingMandatoryValue, ValidationError) as e:
132
- log.error("Configuration error %s", e, path=pkginfo_file_path.as_posix())
133
- raise
134
-
135
-
136
147
  def is_autogen_config() -> bool:
137
148
  env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
138
149
  return not (env_var and env_var.lower() in ("no", "0", "false"))
@@ -23,28 +23,26 @@ def hass_format_config(
23
23
  object_id: str,
24
24
  state_topic: str,
25
25
  command_topic: str | None,
26
+ attrs_topic: str | None,
26
27
  force_command_topic: bool | None,
27
28
  device_creation: bool = True,
28
29
  area: str | None = None,
29
- session: str | None = None,
30
30
  ) -> dict[str, Any]:
31
31
  config: dict[str, Any] = {
32
32
  "name": discovery.title,
33
33
  "device_class": None, # not firmware, so defaults to null
34
34
  "unique_id": object_id,
35
35
  "state_topic": state_topic,
36
- "source_session": session,
37
36
  "supported_features": discovery.features,
38
- "can_update": discovery.can_update,
39
- "can_build": discovery.can_build,
40
- "can_restart": discovery.can_restart,
41
- "update_policy": discovery.update_policy,
37
+ "default_entity_id": f"update.{discovery.node}_{discovery.provider.source_type}_{discovery.name}",
42
38
  "origin": {
43
39
  "name": f"{discovery.node} updates2mqtt",
44
40
  "sw_version": updates2mqtt.version, # pyright: ignore[reportAttributeAccessIssue]
45
41
  "support_url": "https://github.com/rhizomatics/updates2mqtt/issues",
46
42
  },
47
43
  }
44
+ if attrs_topic:
45
+ config["json_attributes_topic"] = attrs_topic
48
46
  if discovery.entity_picture_url:
49
47
  config["entity_picture"] = discovery.entity_picture_url
50
48
  if discovery.device_icon:
@@ -62,14 +60,12 @@ def hass_format_config(
62
60
  config["command_topic"] = command_topic
63
61
  if discovery.can_update:
64
62
  config["payload_install"] = f"{discovery.source_type}|{discovery.name}|install"
65
- config["custom"] = {}
66
- config["custom"][discovery.source_type] = discovery.custom
67
- config.update(discovery.provider.hass_config_format(discovery))
63
+
68
64
  return config
69
65
 
70
66
 
71
67
  def hass_format_state(discovery: Discovery, session: str, in_progress: bool = False) -> dict[str, Any]: # noqa: ARG001
72
- state = {
68
+ state: dict[str, str | dict | list | bool | None] = {
73
69
  "installed_version": discovery.current_version,
74
70
  "latest_version": discovery.latest_version,
75
71
  "title": discovery.title,
@@ -79,11 +75,5 @@ def hass_format_state(discovery: Discovery, session: str, in_progress: bool = Fa
79
75
  state["release_summary"] = discovery.release_summary
80
76
  if discovery.release_url:
81
77
  state["release_url"] = discovery.release_url
82
- custom_state = discovery.provider.hass_state_format(discovery)
83
- if custom_state:
84
- state.update(custom_state)
85
- invalid_keys = [k for k in state if k not in HASS_UPDATE_SCHEMA]
86
- if invalid_keys:
87
- log.warning(f"Invalid keys in state: {invalid_keys}")
88
- state = {k: v for k, v in state.items() if k in HASS_UPDATE_SCHEMA}
78
+
89
79
  return state