Changed
-
CLI formatting stacks merged; Rich is the sole human renderer
(#100 <https://github.com/eman/nwp500-python/issues/100>_): a new
src/nwp500/cli/presentation.pyowns all data-shaping for CLI output
(field selection, labels, units, ordering, value formatting and energy
aggregation) as presentation-neutral structures. Because the CLI
hard-requiresrich(there is no plain-text fallback), the redundant
plain-text human renderer and the Rich-vs-plain fallback machinery were
removed:rich_output.pyno longer contains_should_use_rich,
_rich_available, theNWP500_NO_RICHtoggle, or any
_print_*_plainmethods, and now renders the neutral structures
(including the energyTOTAL SUMMARY) exclusively with Rich, consuming
the presentation dataclasses directly instead of dict adapters.
output_formatters.pykeeps only JSON/CSV rendering plus thin
human-output dispatch. Net CLI code drops from ~2088 to ~1838 lines with a
single data-shaping layer and a single human renderer. Non-energy output
is byte-for-byte unchanged (verified by golden capture and the existing
CLI tests); energy output now renders a summary table plus a breakdown
table entirely in Rich rather than printing plain text and a Rich table. -
mqtt/client.py slimmed toward a thin façade (
#99 <https://github.com/eman/nwp500-python/issues/99>_): the ~40 device
control command proxies and the typedsubscribe_*/unsubscribe_*
device-subscription proxies were moved out ofNavienMqttClientinto
two focused mixins,DeviceControlCommandsMixin
(mqtt/_control_commands.py) andDeviceSubscriptionsMixin
(mqtt/_device_subscriptions.py).NavienMqttClientnow inherits
both, so its public API is unchanged, whilemqtt/client.pyshrinks
from ~1572 to ~1196 lines and reads more clearly as connection
orchestration plus a public façade. No behavior change. -
Event system relationship clarified and de-duplicated (
#102 <https://github.com/eman/nwp500-python/issues/102>_):events.pyand
mqtt_events.pyare not two competing event mechanisms.
EventEmitter(events.py) is the sole delivery mechanism, while
mqtt_events.pyprovides the event-name registry
(MqttClientEvents) and the typed dataclass payloads it carries.
Internalemitcall sites inmqtt/state_tracker.py,
mqtt/client.pyandmqtt/subscriptions.pynow reference the
MqttClientEventsconstants instead of duplicating the raw event-name
strings, so each event name is defined in exactly one place. Module
docstrings and the event-system reference docs were updated to document
the relationship, and typed-payload delivery is now covered by tests. -
awscrt types wrapped out of public MQTT signatures (
#101 <https://github.com/eman/nwp500-python/issues/101>_): the library no
longer exposesawscrtSDK types on its own public/semi-public API
surface. A library-ownednwp500.mqtt.QoSIntEnum(also exported
asnwp500.QoS) now replacesawscrt.mqtt.QoSon thepublish
andsubscribemethods ofNavienMqttClient,MqttConnection
andMqttSubscriptionManager, onMqttCommandQueue.enqueueand on
theQueuedCommanddataclass.awscrt.mqtt.Connectionhandles are
typed behind theMqttConnectionHandlealias, and translation to/from
awscrthappens only at the connection-layer boundary
(nwp500/mqtt/types.py).NavienMqttClient.publishnow wraps
otherwise-uncaughtAwsCrtErrorinMqttPublishErrorsoawscrt
exceptions no longer leak out of the public boundary... code-block:: python
OLD
from awscrt import mqtt
await client.publish(topic, payload, qos=mqtt.QoS.AT_LEAST_ONCE)NEW
from nwp500 import QoS
await client.publish(topic, payload, qos=QoS.AT_LEAST_ONCE)
Fixed
- MQTT ack futures now consumed on abandonment (
#97 <https://github.com/eman/nwp500-python/issues/97>_):_await_ack()
inmqtt/connection.pyand the inline subscribe/unsubscribe
acknowledgement waits inmqtt/subscriptions.pyshield the AWS CRT
future so a timeout or cancellation doesn't propagate into the SDK
future. Previously, if the shielded future later completed with an
exception (e.g.AwsCrtErrorfrom clean-session cancellation during
reconnect) after the awaiting task had already given up, nobody
retrieved that exception, and asyncio logged a "Future exception was
never retrieved" warning at garbage-collection time. A done callback
is now attached whenever a wait is abandoned so the eventual
result/exception is always retrieved and logged at debug level
instead of leaking as an unhandled asyncio warning.
Documentation
- Unit-system preference is a deliberate process-wide global (
#103 <https://github.com/eman/nwp500-python/issues/103>_): clarified and
locked in that the preference innwp500.unit_systemis an intentional
process-wide module-level global rather than a
contextvars.ContextVar. A context-local value silently reverted to
auto-detect for real-time data because MQTT message handling runs in tasks
scheduled from AWS CRT callback threads whose context never inherits from
the application task. Added prominent docstring notes to the affected
models (DeviceStatus,DeviceFeature,ReservationEntry,
WeeklyReservationEntry, andTOUPeriod) explaining that unit-aware
computed fields read the preference at access time and share it across all
async tasks and threads. This is a non-breaking documentation and test
change; no API changes.
Added
- Tests for process-wide unit-system semantics (
#103 <https://github.com/eman/nwp500-python/issues/103>_): new
tests/test_unit_system_process_wide.pyverifies that setting the global
preference affects already-constructed model instances at access time and
that the preference is visible across async tasks and threads.