Repository navigation
fix: log a missing MQTT client at debug when the tree is transport-free by design - #20
Merged
dcj merged 1 commit intoAug 2, 2026
Conversation
…ee by design A tree built with mqtt_cfg=None has no client because that is what was asked for, so every traversal reports one: a 31-device tree emitted 1,593 WARNING lines saying only that the caller got what they requested. _transport_free() is true when the root holds no client and was given no config to build one from. The twelve NoMqttClient sites log at debug when it holds and keep their previous severity otherwise, so "you forgot to start the root" stays as loud as it was. Bring-your-own-transport is not transport-free: the client is present, so its absence would still be an anomaly. Property and Node resolve the predicate through node -> device -> root, mirroring the walk already in Property.start_mqtt_client. An incomplete chain falls through as not transport-free, so a half-built tree stays loud rather than going quiet. Closes #11.
cayossarian
force-pushed
the
fix/transport-free-log-severity
branch
from
August 2, 2026 22:50
8fd2286 to
b649eb5
Compare
dcj
approved these changes
Aug 2, 2026
dcj
left a comment
Contributor
There was a problem hiding this comment.
Clean, well-measured logging-severity fix (Closes #11). Verified locally: merges conflict-free with #19 and the combined suite is green (533 passed, ruff clean); checked that #19's connectivity gate and this PR's _log_missing_client helper compose correctly in publish_value. Merging on Bill's behalf; bundling into the 0.16.0 release.
dcj
added a commit
that referenced
this pull request
Aug 7, 2026
* fix: publish a device's $state after its children in refresh_tree refresh_tree() walked the tree pre-order, publishing a device completely -- description, nodes, state -- before recursing. The root therefore announced $state=ready while the children its own $description names had published nothing at all. On a 37-device enclosure tree the root went ready at publish 1 and the last child at publish 73. This is the normal path, not an edge case: on_connect calls refresh_tree() for SDK-owned clients on both the initial connect and every reconnect. Homie 5 invites a controller to gate on the root's $state, and one that does proceeded against a tree whose children had not described themselves. After a broker restart a previously-healthy consumer re-read a root claiming ready with nothing under it. Description and nodes now publish first, then descendants, then this device's own state. The set of messages is unchanged and only the order differs: on the same tree, 74 publishes and 74 unique topics before and after, identical topic sets, with the root's $state moving from index 1 to index 73. The regression test fails on main and passes here. The existing suite passes either way -- no test asserted the old order, which is why it survived. * review fixups: ruff format, drop orphaned _publish_self, depth-2 order assertion - ruff format collapse on child_states (the only CI blocker; our pin is 0.15.21) - delete Device._publish_self(), which this change left with zero callers and a docstring still claiming it serves the reconnect cascade - assert the state-after-children rule at depth 2 in the existing three-level test: a fix that reordered only the root passed all 546 tests as written - reframe the comment and CHANGELOG entry as a producer-side narrowing rather than a guarantee consumers may build on, and add the LWT/atomic-commit reason (a lost root makes every child lost, so the final publish is one atomic flip) - credit @cayossarian in the CHANGELOG, matching #10/#12/#20 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --------- Co-authored-by: Donald Clark Jackson <[email protected]> Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #11. The
debug/warningsplit you confirmed, with the predicate exactly as youspecified it.
The predicate
Device._transport_free()— the root holds no client and was given no config to build onefrom:
_mqtt_cfgis the term that separates the two cases. A root told nothing is passive byrequest; a root told how to build a client and holding none is broken. That is the "you
forgot to start the root" warning the issue is careful to preserve, and it stays exactly as
loud as it was.
PropertyandNoderesolve it throughnode -> device -> root, mirroring the walk alreadyin
Property.start_mqtt_client. An incomplete chain falls through as not transport-free,so a half-built tree stays loud rather than going quiet — silencing a detached property would
hide a real bug.
Before / after
Same 31-device / 120-property transport-free tree, same workload (build,
set_state(READY),publish every property, subscribe every settable):
main@7518c7aIdentical event count — nothing is dropped, only reclassified, so
DEBUGstill shows everyone. The blunt workaround (
logging.getLogger("homie").setLevel(logging.ERROR)) is no longerneeded, which matters because it also hid the genuine warnings.
The twelve sites
All now route through one module-level helper, so the severity rule is stated once:
propertyGetMqttClient,propertyStartMqttClient,propertyPublishValue,propertyClearValue,propertySetSubscribe,nodeGetMqttClient,deviceGetMqttClient,deviceStartMqttClient,deviceStop,deviceDeleteAllFromMqtt,deviceClearTopic,devicePublish.One judgement call worth your eye
devicePublishNoMqttClientwas alreadyinfoonmain, notwarning. Transport-free dropsit to
debugwith the rest, but the expected-a-client case keepsinforather than beingpromoted — reading "WARNING otherwise" literally would have made one site louder than it is
today, which is a behaviour change the issue did not ask for. That is what the
level=parameter expresses. Say the word if you'd rather have it uniform.
Bring-your-own-transport is not transport-free
#14 landed after the issue was filed, so worth stating explicitly: an injected client sets
mqttc, so the predicate isFalseand a missing client on that path still warns. That isthe right answer — the caller handed a client over, so its absence is an anomaly, not a
request. There is a test pinning it.
Rebased onto #19. The predicate reads
mqttcand_mqtt_cfg, which #19 retyped withoutchanging their meaning, so it is unaffected;
_owned_clientis deliberately not part of it,since an SDK-built client that is missing is exactly the case that should stay loud.
Tests
Six in
TestTransportFreeLogSeverity: no WARNING anywhere in a transport-free tree whileDEBUG records are present; a config-bearing root still warns; an injected client is not
transport-free; the predicate resolves identically from
Device,NodeandProperty; adetached property stays loud; and
devicePublishkeepsinfowhen a client was expected.pytest: 533 passed, on 3.14 and on 3.10.ruff check .andruff format --check .clean.
Also confirmed while here
Thanks for the 3.10-3.13 matrix in #16 — that closes the gap where the declared floor wasn't
exercised by CI. This branch is green across it.