Skip to content

chore: Python 3.14 modernization and lint rule adoption#96

Merged
eman merged 5 commits into
mainfrom
chore/py314-modernity
Jul 5, 2026
Merged

chore: Python 3.14 modernization and lint rule adoption#96
eman merged 5 commits into
mainfrom
chore/py314-modernity

Conversation

@eman

@eman eman commented Jul 5, 2026

Copy link
Copy Markdown
Owner

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

  • Drop from __future__ import annotations project-wide (57 files across src/tests/examples): redundant on a python_requires >= 3.14 package where PEP 649 lazy annotation evaluation is the default. Full test suite confirms pydantic models are unaffected.
  • Adopt ruff rule sets RUF, DTZ, PTH, ASYNC, PERF with documented ignores:
    • ASYNC109: public timeout: parameters are a deliberate API; switching to asyncio.timeout() at call sites would be a breaking change with no behavioral benefit
    • RUF001-003: degree signs / en-dashes in docstrings are intentional
    • RUF022: __all__ lists are deliberately grouped by category
  • Fixes for all resulting findings:
    • timezone-naive datetime.now() written to CSV exports → timezone-aware local time (DTZ005)
    • open() on Path values → Path.open() (PTH123)
    • manual zip(items, items[1:])itertools.pairwise() (RUF007)
    • _CAPABILITY_MAP mutable class attribute → ClassVar annotation (RUF012)
    • dangling asyncio.create_task results in tests now stored/awaited (RUF006)
    • f-string conversion flags, unused noqa, unused unpacked variables (auto-fixed)
  • PeriodicRequestTypeStrEnum (was plain Enum with string values; the repo already uses StrEnum elsewhere)
  • slots=True on the frozen event dataclasses (mqtt_events.py payloads, events.EventListener) — created per state change, so slots cut per-event overhead
  • match for the periodic request-type dispatch
  • Token cache 0600 + datetime.utcnow() example fix: byte-identical to fix: address high-priority evaluation findings (hotfix batch) #90's changes so the independent branches merge cleanly

Deliberately deferred

Tests

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 passed
  • ruff check / ruff format --check (with the new rule sets): clean
  • pyright src/nwp500: 0 errors
  • mypy src/nwp500: no issues

emmanuel 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)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 annotations throughout 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, and models/_converters; updated a string-valued enum to StrEnum and added slots=True to 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.

Comment thread examples/advanced/mqtt_diagnostics.py Outdated
@eman
eman changed the base branch from refactor/architecture-cleanup to main July 5, 2026 16:54
emmanuel 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.
@eman
eman merged commit 0d38ed7 into main Jul 5, 2026
7 checks passed
@eman
eman deleted the chore/py314-modernity branch July 5, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants