chore: Python 3.14 modernization and lint rule adoption#96
Merged
Conversation
added 3 commits
July 5, 2026 08:23
- Send flat raw protocol payloads for weekly reservation and recirculation schedules; the model dump double-nested the request and leaked computed display fields (incl. unit-converted temperature) into device commands. Add NavienBaseModel.to_protocol_dict() - Preserve command queue order on failed flush (deque with re-insert at front instead of tail) - Discard queued commands older than max_queued_command_age (default 300s) instead of replaying stale control commands after long outages - Use truncated remainder (firmware semantics) instead of Python's floored modulo in the ASYMMETRIC Fahrenheit formula; fixes off-by-one conversions for sub-zero temperatures - Correct freeze protection default limits to raw half-Celsius 12/20 (43F/50F documented fixed range) instead of 43/65 - Emit error_detected when the error code changes between two non-zero codes - Round TOU prices half-up via Decimal (round() banker's rounding under-encoded exact halves); encode bool prices instead of passing them through as pre-encoded ints - Fix inverted enable-flag doc, wrong week-bitfield example (158->84), and int/float doctest values
BREAKING CHANGES (major version bump required): - Remove deprecated NavienMqttClient.control property; use the delegated methods on the client directly - Remove never-raised exception classes: TokenExpiredError, MqttSubscriptionError, DeviceNotFoundError, DeviceOfflineError, DeviceOperationError (catch the parent classes instead) - Stop re-exporting internal plumbing from the top-level namespace: requires_capability, MqttDeviceInfoCache, MqttDeviceCapabilityChecker, log_performance, and the eight bit-encoding helpers (import from nwp500.encoding et al.) Cleanup: - Delete dead nwp500.cli.commands module (never imported, drifted from the real click commands) - Delete unused converters.str_enum_validator and module-level temperature conversion wrappers; simplify div_10/mul_10 - Delete no-op NavienMqttClient._on_message_received placeholder and unused CLI width calculations - Deduplicate the four Temperature classes via a _scale ClassVar on the base (behavior unchanged, ~200 lines removed); doctests pass - Deduplicate field_factory metadata-merge boilerplate - Use converters.device_bool_from_python in mqtt/control.py instead of inline 2/1 literals - Resolve auth.py version from distribution metadata, removing the order-dependent 'from . import __version__' near-circular import - Update docs and examples to import encoding helpers from nwp500.encoding; add public-API surface tests
- Remove 'from __future__ import annotations' project-wide (57 files); redundant on a >=3.14-only package (PEP 649 default) - Adopt RUF, DTZ, PTH, ASYNC, PERF ruff rule sets with documented ignores (ASYNC109 public timeout params, RUF001-003 intentional unicode, RUF022 grouped __all__) - Fix all resulting findings: timezone-aware CSV timestamps, Path.open(), itertools.pairwise in the CLI range collapser, ClassVar on the capability map, stored task references in tests - Convert PeriodicRequestType to StrEnum - Add slots=True to frozen event dataclasses (mqtt_events, EventListener) - Use match for periodic request-type dispatch - Write token cache with 0600 permissions (mirrors #90 so the stacked branches merge cleanly); fix deprecated datetime.utcnow() in the diagnostics example likewise - Add unit tests for previously untested modules: topic_builder, field_factory, models/_converters (19 tests)
Contributor
There was a problem hiding this comment.
Pull request overview
Modernizes the nwp500 Python codebase for Python 3.14 by removing redundant from __future__ import annotations, adopting additional Ruff rule sets (DTZ/PTH/ASYNC/PERF/RUF), and applying the resulting fixes across library code, CLI, tests, and examples—plus adding targeted tests for previously untested utility modules.
Changes:
- Removed
from __future__ import annotationsthroughout the project to align with Python 3.14 defaults. - Adopted additional Ruff rule sets and applied fixes (timezone-aware timestamps,
Path.open(),itertools.pairwise(), stored/awaited tasks in tests,ClassVar, etc.). - Added new tests for
topic_builder,field_factory, andmodels/_converters; updated a string-valued enum toStrEnumand addedslots=Trueto frequently-created event dataclasses.
Reviewed changes
Copilot reviewed 63 out of 63 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_utility_modules.py | Adds new unit tests for utility modules (topics, field metadata, converters). |
| tests/test_reservations.py | Removes redundant future-annotations import. |
| tests/test_public_api.py | Removes redundant future-annotations import. |
| tests/test_protocol_correctness.py | Removes redundant future-annotations import. |
| tests/test_multi_device.py | Avoids unused kwargs binding to satisfy new linting. |
| tests/test_mqtt_reconnection.py | Removes redundant future-annotations import. |
| tests/test_mqtt_reconnection_storm.py | Removes redundant future-annotations import; formatting/noqa cleanup. |
| tests/test_mqtt_clean_session_resume.py | Removes redundant future-annotations import. |
| tests/test_events.py | Stores/awaits created task to avoid dangling task warnings. |
| tests/test_bug_fixes.py | Stores/awaits created task to avoid dangling task warnings. |
| src/nwp500/utils.py | Removes redundant future-annotations import. |
| src/nwp500/unit_system.py | Removes redundant future-annotations import. |
| src/nwp500/topic_builder.py | Removes redundant future-annotations import. |
| src/nwp500/temperature.py | Removes redundant future-annotations import. |
| src/nwp500/reservations.py | Removes redundant future-annotations import. |
| src/nwp500/openei.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/utils.py | Switches PeriodicRequestType to StrEnum; removes future-annotations import. |
| src/nwp500/mqtt/subscriptions.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/state_tracker.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/reconnection.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/periodic.py | Uses match dispatch for periodic request type; removes future-annotations import. |
| src/nwp500/mqtt/diagnostics.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/control.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/connection.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/command_queue.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/client.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt/init.py | Removes redundant future-annotations import. |
| src/nwp500/mqtt_events.py | Adds slots=True to event payload dataclasses; removes future-annotations import. |
| src/nwp500/models/tou.py | Removes redundant future-annotations import. |
| src/nwp500/models/status.py | Removes redundant future-annotations import. |
| src/nwp500/models/schedule.py | Removes redundant future-annotations import. |
| src/nwp500/models/mqtt_models.py | Removes redundant future-annotations import. |
| src/nwp500/models/feature.py | Removes redundant future-annotations import. |
| src/nwp500/models/energy.py | Removes redundant future-annotations import. |
| src/nwp500/models/device.py | Removes redundant future-annotations import. |
| src/nwp500/models/_converters.py | Removes redundant future-annotations import. |
| src/nwp500/models/init.py | Removes redundant future-annotations import. |
| src/nwp500/field_factory.py | Removes redundant future-annotations import. |
| src/nwp500/factory.py | Removes redundant future-annotations import. |
| src/nwp500/exceptions.py | Removes redundant future-annotations import. |
| src/nwp500/events.py | Adds slots=True to EventListener; removes future-annotations import. |
| src/nwp500/enums.py | Removes redundant future-annotations import. |
| src/nwp500/encoding.py | Removes redundant future-annotations import. |
| src/nwp500/device_info_cache.py | Removes redundant future-annotations import. |
| src/nwp500/device_capabilities.py | Annotates _CAPABILITY_MAP as ClassVar[...]; updates typing import. |
| src/nwp500/converters.py | Removes redundant future-annotations import. |
| src/nwp500/config.py | Removes redundant future-annotations import. |
| src/nwp500/command_decorators.py | Tweaks error formatting to satisfy new lint rules; removes future-annotations import. |
| src/nwp500/cli/token_storage.py | Writes token cache with mode 0600 using os.open; uses Path.open() for reads. |
| src/nwp500/cli/rich_output.py | Uses itertools.pairwise() for range collapsing; removes future-annotations import. |
| src/nwp500/cli/output_formatters.py | Uses timezone-aware local timestamps and Path.open() for CSV writes. |
| src/nwp500/cli/monitoring.py | Removes redundant future-annotations import. |
| src/nwp500/cli/handlers.py | Removes redundant future-annotations import. |
| src/nwp500/cli/main.py | Removes redundant future-annotations import. |
| src/nwp500/cli/init.py | Removes redundant future-annotations import. |
| src/nwp500/auth.py | Minor string-formatting cleanups for raised errors; removes future-annotations import. |
| src/nwp500/api_client.py | Minor string-formatting cleanup in raised error; removes future-annotations import. |
| src/nwp500/_base.py | Removes redundant future-annotations import. |
| src/nwp500/init.py | Removes redundant future-annotations import. |
| pyproject.toml | Enables additional Ruff rule sets and documents intentional ignores. |
| examples/mask.py | Removes redundant future-annotations import. |
| examples/advanced/mqtt_diagnostics.py | Fixes datetime.utcnow() usage; (still has open(Path, ...) needing Path.open()). |
| CHANGELOG.rst | Documents Python 3.14 modernization, lint adoption, and added tests. |
added 2 commits
July 5, 2026 09:55
# Conflicts: # CHANGELOG.rst # src/nwp500/auth.py # src/nwp500/encoding.py # src/nwp500/mqtt/client.py # src/nwp500/mqtt/command_queue.py # src/nwp500/temperature.py # src/nwp500/unit_system.py # tests/test_protocol_correctness.py # tests/test_public_api.py
Addresses review feedback: two open() calls on Path objects remained in examples/advanced/mqtt_diagnostics.py.
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.
Seventh and final batch from the comprehensive repo evaluation. Stacked on #95 (which stacks on #94) — merge order: #94 → #95 → this.
The codebase was already largely modern (builtin generics,
X | None, StrEnum/IntEnum, pydantic v2 idioms,match,datetime.now(UTC)); this PR closes the remaining gaps.Changes
from __future__ import annotationsproject-wide (57 files across src/tests/examples): redundant on apython_requires >= 3.14package where PEP 649 lazy annotation evaluation is the default. Full test suite confirms pydantic models are unaffected.RUF,DTZ,PTH,ASYNC,PERFwith documented ignores:ASYNC109: publictimeout:parameters are a deliberate API; switching toasyncio.timeout()at call sites would be a breaking change with no behavioral benefitRUF001-003: degree signs / en-dashes in docstrings are intentionalRUF022:__all__lists are deliberately grouped by categorydatetime.now()written to CSV exports → timezone-aware local time (DTZ005)open()onPathvalues →Path.open()(PTH123)zip(items, items[1:])→itertools.pairwise()(RUF007)_CAPABILITY_MAPmutable class attribute →ClassVarannotation (RUF012)asyncio.create_taskresults in tests now stored/awaited (RUF006)PeriodicRequestType→StrEnum(was plainEnumwith string values; the repo already usesStrEnumelsewhere)slots=Trueon the frozen event dataclasses (mqtt_events.pypayloads,events.EventListener) — created per state change, so slots cut per-event overheadmatchfor the periodic request-type dispatchdatetime.utcnow()example fix: byte-identical to fix: address high-priority evaluation findings (hotfix batch) #90's changes so the independent branches merge cleanlyDeliberately deferred
nwp500.cli.*mypyignore_errorsoverride — worthwhile but belongs with the CLI formatting-layer refactor deferred from refactor!: trim public API, remove dead code, deduplicate conversions #95Tests
New
tests/test_utility_modules.py(19 tests) for previously zero-coverage modules:topic_builder(full topic schema),field_factory(defaults, custom units, caller-metadata merge),models/_converters(unit-preference conversions + round-trips).Validation
pytest: 541 passedruff check/ruff format --check(with the new rule sets): cleanpyright src/nwp500: 0 errorsmypy src/nwp500: no issues