nwp500-python 7.4.5__tar.gz → 7.4.7__tar.gz
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.
- nwp500_python-7.4.7/.github/RESOLVING_PR_COMMENTS.md +376 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.github/copilot-instructions.md +65 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.github/workflows/ci.yml +19 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.gitignore +7 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/CHANGELOG.rst +53 -0
- {nwp500_python-7.4.5/src/nwp500_python.egg-info → nwp500_python-7.4.7}/PKG-INFO +4 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/README.rst +1 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/api/nwp500.rst +8 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/advanced_features_explained.rst +1 -1
- nwp500_python-7.4.7/docs/guides/scheduling.rst +681 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/time_of_use.rst +163 -155
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/index.rst +1 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/openapi.yaml +231 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/cli.rst +85 -31
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/device_control.rst +1 -2
- nwp500_python-7.4.7/examples/advanced/tou_openei.py +121 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/pyproject.toml +5 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/setup.cfg +1 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/__init__.py +11 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/api_client.py +106 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/auth.py +5 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/__init__.py +6 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/__main__.py +294 -5
- nwp500_python-7.4.7/src/nwp500/cli/handlers.py +1131 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/rich_output.py +364 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/converters.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/encoding.py +92 -37
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/factory.py +6 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/models.py +126 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/client.py +13 -4
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/command_queue.py +1 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/control.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/subscriptions.py +40 -9
- nwp500_python-7.4.7/src/nwp500/openei.py +213 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/temperature.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/unit_system.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7/src/nwp500_python.egg-info}/PKG-INFO +4 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/SOURCES.txt +6 -3
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/requires.txt +2 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_api_helpers.py +44 -7
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_model_converters.py +6 -8
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_models.py +133 -1
- nwp500_python-7.4.7/tests/test_mqtt_hypothesis.py +180 -0
- nwp500_python-7.4.7/tests/test_openei.py +219 -0
- nwp500_python-7.4.7/tests/test_tou_api.py +262 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tox.ini +1 -0
- nwp500_python-7.4.5/.agent/workflows/pre-completion-testing.md +0 -73
- nwp500_python-7.4.5/docs/guides/reservations.rst +0 -721
- nwp500_python-7.4.5/docs/guides/scheduling_features.rst +0 -406
- nwp500_python-7.4.5/examples/advanced/tou_openei.py +0 -328
- nwp500_python-7.4.5/src/nwp500/cli/handlers.py +0 -564
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.coveragerc +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.github/workflows/release.yml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.pre-commit-config.yaml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/.readthedocs.yml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/AUTHORS.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/CONTRIBUTING.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/LICENSE.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/Makefile +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/RELEASE.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/Makefile +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/_static/.gitignore +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/authors.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/changelog.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/conf.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/configuration.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/development/contributing.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/development/history.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/enumerations.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/authentication.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/auto_recovery.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/command_queue.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/energy_monitoring.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/event_system.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/home_assistant_integration.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/mqtt_diagnostics.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/guides/unit_conversion.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/installation.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/license.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/data_conversions.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/device_features.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/device_status.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/error_codes.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/quick_reference.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/protocol/rest_api.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/api_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/auth_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/events.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/exceptions.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/models.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/python_api/mqtt_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/quickstart.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/docs/requirements.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/.ruff.toml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/README.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/power_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/mask.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/README.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/bump_version.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/extract_changelog.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/format.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/lint.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/setup-dev.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/scripts/validate_version.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/setup.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/commands.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/config.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/enums.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/periodic.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt/utils.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/py.typed +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500/utils.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/conftest.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_auth.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_cli_basic.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_cli_commands.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_command_decorators.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_command_queue.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_exceptions.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_unit_switching.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.7}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# How to Resolve GitHub Review Comments
|
|
2
|
+
|
|
3
|
+
This guide documents how to mark review comment conversations as resolved after addressing reviewer feedback in pull requests.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Get all review threads for PR #74
|
|
9
|
+
gh api graphql -f query='
|
|
10
|
+
query {
|
|
11
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
12
|
+
pullRequest(number: 74) {
|
|
13
|
+
reviewThreads(first: 10) {
|
|
14
|
+
nodes {
|
|
15
|
+
id
|
|
16
|
+
path
|
|
17
|
+
line
|
|
18
|
+
isResolved
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
25
|
+
|
|
26
|
+
# 2. Resolve a specific thread
|
|
27
|
+
gh api graphql -f query='
|
|
28
|
+
mutation {
|
|
29
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
30
|
+
thread { isResolved }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why Resolve Comments?
|
|
37
|
+
|
|
38
|
+
- **Clarity**: Shows reviewers their feedback has been acted upon
|
|
39
|
+
- **Workflow**: Prevents reviewers from re-reading already-addressed comments
|
|
40
|
+
- **Signal Readiness**: Indicates PR is ready for another review pass
|
|
41
|
+
- **Clean PR Interface**: Makes GitHub's PR conversation cleaner and easier to follow
|
|
42
|
+
|
|
43
|
+
## Workflow: Address and Resolve
|
|
44
|
+
|
|
45
|
+
### 1. Address the Comment
|
|
46
|
+
|
|
47
|
+
Make the code changes requested by the reviewer:
|
|
48
|
+
- Fix bugs
|
|
49
|
+
- Update documentation
|
|
50
|
+
- Refactor code
|
|
51
|
+
- Add tests
|
|
52
|
+
|
|
53
|
+
**Example:**
|
|
54
|
+
```bash
|
|
55
|
+
# Edit file based on comment
|
|
56
|
+
nano src/nwp500/converters.py
|
|
57
|
+
|
|
58
|
+
# Commit the change
|
|
59
|
+
git add src/nwp500/converters.py
|
|
60
|
+
git commit -m "Fix div_10 converter to handle string inputs"
|
|
61
|
+
|
|
62
|
+
# Push to remote
|
|
63
|
+
git push
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Verify Tests Pass
|
|
67
|
+
|
|
68
|
+
Run all validation before marking comments as resolved:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run tests
|
|
72
|
+
pytest --ignore=tests/test_mqtt_hypothesis.py
|
|
73
|
+
|
|
74
|
+
# Run linting
|
|
75
|
+
make ci-lint
|
|
76
|
+
|
|
77
|
+
# Run type checking
|
|
78
|
+
python3 -m mypy src/nwp500 --config-file pyproject.toml
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
All must pass before proceeding.
|
|
82
|
+
|
|
83
|
+
### 3. Get Review Thread IDs
|
|
84
|
+
|
|
85
|
+
Query GraphQL to find threads that need resolving:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
gh api graphql -f query='
|
|
89
|
+
query {
|
|
90
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
91
|
+
pullRequest(number: 74) {
|
|
92
|
+
reviewThreads(first: 10) {
|
|
93
|
+
nodes {
|
|
94
|
+
id
|
|
95
|
+
path
|
|
96
|
+
line
|
|
97
|
+
isResolved
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Output example:**
|
|
107
|
+
```json
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
"id": "PRRT_kwDOP_hNvM5ukIVT",
|
|
111
|
+
"path": "src/nwp500/converters.py",
|
|
112
|
+
"line": 125,
|
|
113
|
+
"isResolved": false
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "PRRT_kwDOP_hNvM5ukIVo",
|
|
117
|
+
"path": "tests/test_model_converters.py",
|
|
118
|
+
"line": 212,
|
|
119
|
+
"isResolved": false
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. Identify Which Threads to Resolve
|
|
125
|
+
|
|
126
|
+
Cross-reference the output with:
|
|
127
|
+
1. Which file you modified
|
|
128
|
+
2. Which line the comment was on (approximately)
|
|
129
|
+
3. Whether `isResolved` is `false`
|
|
130
|
+
|
|
131
|
+
### 5. Resolve the Threads
|
|
132
|
+
|
|
133
|
+
For each thread you addressed:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gh api graphql -f query='
|
|
137
|
+
mutation {
|
|
138
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
139
|
+
thread {
|
|
140
|
+
isResolved
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Success response:
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"data": {
|
|
151
|
+
"resolveReviewThread": {
|
|
152
|
+
"thread": {
|
|
153
|
+
"isResolved": true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 6. Verify All Are Resolved
|
|
161
|
+
|
|
162
|
+
Re-run the query from Step 3 to confirm all addressed threads now show `"isResolved": true`:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
gh api graphql -f query='
|
|
166
|
+
query {
|
|
167
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
168
|
+
pullRequest(number: 74) {
|
|
169
|
+
reviewThreads(first: 10) {
|
|
170
|
+
nodes {
|
|
171
|
+
path
|
|
172
|
+
isResolved
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Batch Resolving Multiple Threads
|
|
182
|
+
|
|
183
|
+
If you have many threads to resolve, use a loop:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
#!/bin/bash
|
|
187
|
+
|
|
188
|
+
# Define thread IDs
|
|
189
|
+
THREAD_IDS=(
|
|
190
|
+
"PRRT_kwDOP_hNvM5ukIVT"
|
|
191
|
+
"PRRT_kwDOP_hNvM5ukIVo"
|
|
192
|
+
"PRRT_kwDOP_hNvM5ukIVx"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Resolve each one
|
|
196
|
+
for thread_id in "${THREAD_IDS[@]}"; do
|
|
197
|
+
gh api graphql -f query="
|
|
198
|
+
mutation {
|
|
199
|
+
resolveReviewThread(input: {threadId: \"$thread_id\"}) {
|
|
200
|
+
thread { isResolved }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
" && echo "✓ $thread_id resolved"
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
echo "All threads resolved!"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or as a one-liner:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
for id in PRRT_kwDOP_hNvM5ukIVT PRRT_kwDOP_hNvM5ukIVo PRRT_kwDOP_hNvM5ukIVx; do
|
|
213
|
+
gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: \"$id\"}) { thread { isResolved } } }" && echo "✓ $id resolved"
|
|
214
|
+
done
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Shell Function (Optional)
|
|
218
|
+
|
|
219
|
+
Add this to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.):
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Resolve a single GitHub PR review thread
|
|
223
|
+
resolve-pr-thread() {
|
|
224
|
+
local thread_id="$1"
|
|
225
|
+
if [[ -z "$thread_id" ]]; then
|
|
226
|
+
echo "Usage: resolve-pr-thread THREAD_ID"
|
|
227
|
+
echo "Example: resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT"
|
|
228
|
+
return 1
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
gh api graphql -f query="
|
|
232
|
+
mutation {
|
|
233
|
+
resolveReviewThread(input: {threadId: \"$thread_id\"}) {
|
|
234
|
+
thread { isResolved }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
" | jq '.data.resolveReviewThread.thread.isResolved'
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Get all unresolved threads for a PR
|
|
241
|
+
pr-threads() {
|
|
242
|
+
local pr_num="${1:?PR number required}"
|
|
243
|
+
gh api graphql -f query="
|
|
244
|
+
query {
|
|
245
|
+
repository(owner: \"eman\", name: \"nwp500-python\") {
|
|
246
|
+
pullRequest(number: $pr_num) {
|
|
247
|
+
reviewThreads(first: 10) {
|
|
248
|
+
nodes {
|
|
249
|
+
id
|
|
250
|
+
path
|
|
251
|
+
line
|
|
252
|
+
isResolved
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
" | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Usage:
|
|
263
|
+
```bash
|
|
264
|
+
pr-threads 74 # List all threads for PR #74
|
|
265
|
+
resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT # Resolve one thread
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Special Cases
|
|
269
|
+
|
|
270
|
+
### Unresolving a Thread
|
|
271
|
+
|
|
272
|
+
If a reviewer asks for changes after you marked it resolved, unresolve it:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
gh api graphql -f query='
|
|
276
|
+
mutation {
|
|
277
|
+
unresolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
278
|
+
thread {
|
|
279
|
+
isResolved
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
'
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Force-Pushed Commits
|
|
287
|
+
|
|
288
|
+
When you amend and force-push commits:
|
|
289
|
+
1. Old review threads remain resolvable
|
|
290
|
+
2. Thread IDs don't change
|
|
291
|
+
3. Line numbers may be different in new commits
|
|
292
|
+
4. Comments still point to old code but threads can be resolved
|
|
293
|
+
5. This is normal GitHub behavior - resolve all threads once your changes are complete
|
|
294
|
+
|
|
295
|
+
### Multiple Changes to Same File
|
|
296
|
+
|
|
297
|
+
If a reviewer left multiple comments on the same file and you addressed them all:
|
|
298
|
+
1. Make all changes to the file
|
|
299
|
+
2. Commit and push
|
|
300
|
+
3. Get all thread IDs for that file
|
|
301
|
+
4. Resolve each one individually
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
### "Not Found" Error
|
|
306
|
+
|
|
307
|
+
**Problem:**
|
|
308
|
+
```
|
|
309
|
+
{
|
|
310
|
+
"message": "Not Found",
|
|
311
|
+
"documentation_url": "https://docs.github.com/rest",
|
|
312
|
+
"status": 404
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Solutions:**
|
|
317
|
+
- Verify PR number is correct
|
|
318
|
+
- Check you have access to the repository
|
|
319
|
+
- Verify `gh auth status` shows you're authenticated
|
|
320
|
+
- Try running `gh auth login` again
|
|
321
|
+
|
|
322
|
+
### No Threads Returned
|
|
323
|
+
|
|
324
|
+
**Problem:**
|
|
325
|
+
```
|
|
326
|
+
{
|
|
327
|
+
"data": {
|
|
328
|
+
"repository": {
|
|
329
|
+
"pullRequest": {
|
|
330
|
+
"reviewThreads": {
|
|
331
|
+
"nodes": []
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Solutions:**
|
|
340
|
+
- Verify PR number is correct
|
|
341
|
+
- The PR may have only general comments (not review comments)
|
|
342
|
+
- Try increasing `first: 10` to `first: 100` if there are many threads
|
|
343
|
+
- Verify reviewers left inline code comments, not just PR comments
|
|
344
|
+
|
|
345
|
+
### Thread Won't Resolve
|
|
346
|
+
|
|
347
|
+
**Problem:**
|
|
348
|
+
Mutation succeeds but `isResolved` still returns `false` on next check
|
|
349
|
+
|
|
350
|
+
**Solutions:**
|
|
351
|
+
- Wait a moment and query again (GitHub API may have delay)
|
|
352
|
+
- Verify you're using the exact same thread ID
|
|
353
|
+
- Check that you have write permissions on the repository
|
|
354
|
+
- Try running `gh auth refresh` to refresh your token
|
|
355
|
+
|
|
356
|
+
### Can't Find Thread ID for Comment I Fixed
|
|
357
|
+
|
|
358
|
+
**Problem:**
|
|
359
|
+
You fixed the code but can't find the matching thread ID
|
|
360
|
+
|
|
361
|
+
**Possible Causes:**
|
|
362
|
+
1. The comment was a general PR comment, not an inline code comment (can't be resolved)
|
|
363
|
+
2. The thread was already resolved by someone else
|
|
364
|
+
3. You're looking at the wrong PR number
|
|
365
|
+
4. The comment was deleted by the reviewer
|
|
366
|
+
|
|
367
|
+
**Solution:**
|
|
368
|
+
Go to the PR on GitHub.com and manually verify the comment still exists and is an inline review comment (not a general comment).
|
|
369
|
+
|
|
370
|
+
## References
|
|
371
|
+
|
|
372
|
+
- [GitHub GraphQL API: resolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#resolvereviewthread)
|
|
373
|
+
- [GitHub GraphQL API: unresolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#unresolvereviewthread)
|
|
374
|
+
- [GitHub GraphQL API: Review Threads](https://docs.github.com/en/graphql/reference/objects#reviewthread)
|
|
375
|
+
- [GitHub CLI: gh api](https://cli.github.com/manual/gh_api)
|
|
376
|
+
- [GitHub: Review conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#about-pull-request-reviews)
|
|
@@ -54,6 +54,71 @@ When working on pull requests, use the GitHub CLI to access review comments:
|
|
|
54
54
|
|
|
55
55
|
This ensures you can address all feedback from code reviewers systematically.
|
|
56
56
|
|
|
57
|
+
#### Marking Conversations as Resolved
|
|
58
|
+
|
|
59
|
+
After addressing a review comment, mark the conversation as resolved to signal reviewers that the feedback has been acted upon.
|
|
60
|
+
|
|
61
|
+
**Step 1: Get review thread IDs**
|
|
62
|
+
```bash
|
|
63
|
+
gh api graphql -f query='
|
|
64
|
+
query {
|
|
65
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
66
|
+
pullRequest(number: 74) {
|
|
67
|
+
reviewThreads(first: 10) {
|
|
68
|
+
nodes {
|
|
69
|
+
id
|
|
70
|
+
path
|
|
71
|
+
line
|
|
72
|
+
isResolved
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Replace `74` with the actual PR number. This returns thread IDs like `PRRT_kwDOP_hNvM5ukIVT`.
|
|
82
|
+
|
|
83
|
+
**Step 2: Resolve a thread**
|
|
84
|
+
```bash
|
|
85
|
+
gh api graphql -f query='
|
|
86
|
+
mutation {
|
|
87
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
88
|
+
thread {
|
|
89
|
+
isResolved
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Replace the thread ID with the one from Step 1. Success response shows `"isResolved": true`.
|
|
97
|
+
|
|
98
|
+
**Step 3: Resolve multiple threads efficiently**
|
|
99
|
+
```bash
|
|
100
|
+
for id in PRRT_kwDOP_hNvM5ukIVT PRRT_kwDOP_hNvM5ukIVo PRRT_kwDOP_hNvM5ukIVx; do
|
|
101
|
+
gh api graphql -f query="
|
|
102
|
+
mutation {
|
|
103
|
+
resolveReviewThread(input: {threadId: \"$id\"}) {
|
|
104
|
+
thread { isResolved }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
" && echo "✓ $id resolved"
|
|
108
|
+
done
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Step 4: Verify all are resolved**
|
|
112
|
+
Re-run Step 1 query and confirm all addressed threads show `"isResolved": true`.
|
|
113
|
+
|
|
114
|
+
**Important Notes:**
|
|
115
|
+
- Only inline code review comments can be resolved (not general PR comments)
|
|
116
|
+
- Conversations remain resolvable even after force-pushing commits
|
|
117
|
+
- When commits are amended and force-pushed, old thread IDs remain valid
|
|
118
|
+
- If you need to reopen a resolved thread, use `unresolveReviewThread` mutation instead
|
|
119
|
+
|
|
120
|
+
See detailed instructions at `.github/copilot-instructions.md` for more examples and troubleshooting.
|
|
121
|
+
|
|
57
122
|
### Before Committing Changes
|
|
58
123
|
Always run these checks before finalizing changes to ensure your code will pass CI:
|
|
59
124
|
1. **Linting**: `make ci-lint` - Ensures code style matches CI requirements
|
|
@@ -28,6 +28,25 @@ jobs:
|
|
|
28
28
|
- name: Run tox lint
|
|
29
29
|
run: tox -e lint
|
|
30
30
|
|
|
31
|
+
security:
|
|
32
|
+
name: Security Check
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Set up Python
|
|
38
|
+
uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: '3.13'
|
|
41
|
+
|
|
42
|
+
- name: Install ruff
|
|
43
|
+
run: |
|
|
44
|
+
python -m pip install --upgrade pip
|
|
45
|
+
python -m pip install ruff
|
|
46
|
+
|
|
47
|
+
- name: Run security checks (ruff bandit)
|
|
48
|
+
run: ruff check --select S src/
|
|
49
|
+
|
|
31
50
|
test:
|
|
32
51
|
name: Test on Python ${{ matrix.python-version }}
|
|
33
52
|
runs-on: ubuntu-latest
|
|
@@ -38,6 +38,7 @@ htmlcov/*
|
|
|
38
38
|
junit*.xml
|
|
39
39
|
coverage.xml
|
|
40
40
|
.pytest_cache/
|
|
41
|
+
.hypothesis/
|
|
41
42
|
|
|
42
43
|
# Diagnostics output
|
|
43
44
|
diagnostics_output/
|
|
@@ -61,3 +62,9 @@ venv*/
|
|
|
61
62
|
.secrets
|
|
62
63
|
reference/*
|
|
63
64
|
resources/*
|
|
65
|
+
|
|
66
|
+
# Tool caches
|
|
67
|
+
.mypy_cache/
|
|
68
|
+
.ruff_cache/
|
|
69
|
+
.bandit
|
|
70
|
+
.agent/
|
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 7.4.7 (2026-02-17)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Added
|
|
9
|
+
-----
|
|
10
|
+
- **OpenEI Client Module**: New ``OpenEIClient`` async client (``nwp500.openei``) for browsing utility rate plans from the OpenEI API by zip code. Supports listing utilities, filtering rate plans, and fetching plan details. API key read from ``OPENEI_API_KEY`` environment variable.
|
|
11
|
+
- **Convert TOU API**: ``NavienAPIClient.convert_tou()`` sends raw OpenEI rate data to the Navien backend for server-side conversion into device-ready TOU schedules with season/week bitfields and scaled pricing.
|
|
12
|
+
- **Update TOU API**: ``NavienAPIClient.update_tou()`` applies a converted TOU rate plan to a device, matching the mobile app's ``PUT /device/tou`` endpoint.
|
|
13
|
+
- **ConvertedTOUPlan Model**: New Pydantic model for parsed ``convert_tou()`` results (utility, name, schedule).
|
|
14
|
+
- **CLI ``tou rates``**: Browse utilities and rate plans for a zip code (``nwp500 tou rates 94903``).
|
|
15
|
+
- **CLI ``tou plan``**: View converted rate plan details with decoded pricing (``nwp500 tou plan 94903 "EV Rate A"``).
|
|
16
|
+
- **CLI ``tou apply``**: Apply a rate plan to the water heater with optional ``--enable`` flag to activate TOU via MQTT.
|
|
17
|
+
- **CLI Reservations Table Output**: ``nwp-cli reservations get`` now displays reservations as a formatted table by default with global status indicator (ENABLED/DISABLED). Use ``--json`` flag for JSON output.
|
|
18
|
+
- **CLI ``anti-legionella set-period``**: New subcommand to change the Anti-Legionella cycle period (1-30 days) without toggling the feature. Use ``nwp-cli anti-legionella set-period 7`` to update cycle period.
|
|
19
|
+
|
|
20
|
+
Changed
|
|
21
|
+
-------
|
|
22
|
+
- **``examples/advanced/tou_openei.py``**: Rewritten to use the new ``OpenEIClient`` and ``convert_tou()``/``update_tou()`` library methods instead of inline OpenEI API calls and client-side conversion.
|
|
23
|
+
|
|
24
|
+
Fixed
|
|
25
|
+
-----
|
|
26
|
+
- **Week Bitfield Encoding (CRITICAL)**: Fixed MGPP week bitfield encoding to match NaviLink APK protocol. Sunday is now correctly bit 7 (128), Monday bit 6 (64), ..., Saturday bit 1 (2); bit 0 is unused. Affects all reservation and TOU schedule operations. Verified against reference captures.
|
|
27
|
+
- **Enable/Disable Convention**: Fixed reservation and TOU enable/disable flags to use standard device boolean convention (1=OFF, 2=ON) instead of inverted logic. This aligns with other device binary sensors and matches app behavior. Global reservation status now correctly shows DISABLED when ``reservationUse=1``.
|
|
28
|
+
- **Reservation Set Command Timeout**: Fixed ``reservations set`` subscription pattern that had extra wildcards preventing response matching. Command now receives confirmations correctly.
|
|
29
|
+
- **Intermittent Fetch Bug**: Tightened MQTT topic filter for reservation fetch from ``/res/`` to ``/res/rsv/`` with content validation to prevent false matches on unrelated response messages.
|
|
30
|
+
|
|
31
|
+
Version 7.4.6 (2026-02-13)
|
|
32
|
+
==========================
|
|
33
|
+
|
|
34
|
+
Fixed
|
|
35
|
+
-----
|
|
36
|
+
- **Converter Consistency**: ``div_10()`` and ``mul_10()`` now correctly apply division/multiplication to all input types after ``float()`` conversion, not just ``int``/``float`` types
|
|
37
|
+
- **Reservation Decoding**: Fixed ``decode_reservation_hex()`` to validate chunk length before checking for empty entries, preventing potential out-of-bounds access
|
|
38
|
+
- **Factory Cleanup**: ``create_navien_clients()`` now properly cleans up auth session if authentication fails during context manager entry
|
|
39
|
+
- **MQTT Reconnection**: MQTT client now resubscribes to all topics after successful reconnection
|
|
40
|
+
- **Subscription Leak**: Fixed resource leak where ``wait_for_device_feature()`` did not unsubscribe its callback after completion
|
|
41
|
+
- **Duplicate Handlers**: Subscription manager now prevents duplicate callback registration for the same topic
|
|
42
|
+
- **Command Queue**: ``MqttCommandQueue`` now raises on ``QueueFull`` instead of silently swallowing the error
|
|
43
|
+
- **Flow Rate Metadata**: Removed hardcoded ``"GPM"`` unit from ``recirc_dhw_flow_rate`` field; unit is now dynamic based on unit system
|
|
44
|
+
- **Temperature Rounding**: ``RawCelsius`` Fahrenheit conversion now uses a catch-all default for standard rounding instead of matching only ``STANDARD`` enum value
|
|
45
|
+
- **Unit System Default**: ``is_metric_preferred()`` now returns ``False`` (Fahrenheit) instead of ``None`` when no unit system override or context is set
|
|
46
|
+
|
|
47
|
+
Security
|
|
48
|
+
--------
|
|
49
|
+
- **Sensitive Data Logging**: Redacted MQTT topics in subscription manager logging to prevent leaking device IDs (resolves CodeQL alerts)
|
|
50
|
+
|
|
51
|
+
Added
|
|
52
|
+
-----
|
|
53
|
+
- **Auth Session Property**: Added ``NavienAuthClient.session`` property to access the active ``aiohttp`` session without using ``getattr``
|
|
54
|
+
- **Unsubscribe Feature**: Added ``unsubscribe_device_feature()`` method to MQTT client and subscription manager for targeted callback removal
|
|
55
|
+
- **Hypothesis Fuzzing**: Added property-based fuzzing tests for MQTT payload handling
|
|
56
|
+
- **Bandit Security Scanning**: Added bandit configuration for security analysis in CI
|
|
57
|
+
|
|
5
58
|
Version 7.4.5 (2026-02-04)
|
|
6
59
|
==========================
|
|
7
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nwp500-python
|
|
3
|
-
Version: 7.4.
|
|
3
|
+
Version: 7.4.7
|
|
4
4
|
Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
|
|
5
5
|
Home-page: https://github.com/eman/nwp500-python
|
|
6
6
|
Author: Emmanuel Levijarvi
|
|
@@ -29,6 +29,7 @@ Requires-Dist: setuptools; extra == "testing"
|
|
|
29
29
|
Requires-Dist: pytest; extra == "testing"
|
|
30
30
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
31
31
|
Requires-Dist: pytest-asyncio; extra == "testing"
|
|
32
|
+
Requires-Dist: hypothesis; extra == "testing"
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pyright>=1.1.0; extra == "dev"
|
|
@@ -36,6 +37,7 @@ Requires-Dist: setuptools; extra == "dev"
|
|
|
36
37
|
Requires-Dist: pytest; extra == "dev"
|
|
37
38
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
39
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
40
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
39
41
|
Dynamic: license-file
|
|
40
42
|
|
|
41
43
|
=============
|
|
@@ -110,7 +112,7 @@ Basic Usage
|
|
|
110
112
|
if device:
|
|
111
113
|
# Access status information
|
|
112
114
|
status = device.status
|
|
113
|
-
print(f"Water Temperature: {status.dhw_temperature}
|
|
115
|
+
print(f"Water Temperature: {status.dhw_temperature}")
|
|
114
116
|
print(f"Tank Charge: {status.dhw_charge_per}%")
|
|
115
117
|
print(f"Power Consumption: {status.current_inst_power}W")
|
|
116
118
|
|
|
@@ -70,7 +70,7 @@ Basic Usage
|
|
|
70
70
|
if device:
|
|
71
71
|
# Access status information
|
|
72
72
|
status = device.status
|
|
73
|
-
print(f"Water Temperature: {status.dhw_temperature}
|
|
73
|
+
print(f"Water Temperature: {status.dhw_temperature}")
|
|
74
74
|
print(f"Tank Charge: {status.dhw_charge_per}%")
|
|
75
75
|
print(f"Power Consumption: {status.current_inst_power}W")
|
|
76
76
|
|
|
@@ -133,6 +133,14 @@ nwp500.mqtt\_events module
|
|
|
133
133
|
:show-inheritance:
|
|
134
134
|
:undoc-members:
|
|
135
135
|
|
|
136
|
+
nwp500.openei module
|
|
137
|
+
--------------------
|
|
138
|
+
|
|
139
|
+
.. automodule:: nwp500.openei
|
|
140
|
+
:members:
|
|
141
|
+
:show-inheritance:
|
|
142
|
+
:undoc-members:
|
|
143
|
+
|
|
136
144
|
nwp500.temperature module
|
|
137
145
|
-------------------------
|
|
138
146
|
|
|
@@ -499,5 +499,5 @@ See Also
|
|
|
499
499
|
|
|
500
500
|
* :doc:`../protocol/data_conversions` - Temperature field conversions (HalfCelsiusToF, DeciCelsiusToF)
|
|
501
501
|
* :doc:`../protocol/device_status` - Complete device status field reference
|
|
502
|
-
* :doc:`
|
|
502
|
+
* :doc:`scheduling` - Scheduling and automation guide
|
|
503
503
|
* :doc:`../python_api/models` - DeviceStatus model field definitions
|