python-bsblan 6.1.11__tar.gz → 6.2.0__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.
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/codeql.yaml +2 -2
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/release.yaml +1 -1
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/scorecard.yml +2 -2
- python_bsblan-6.2.0/.nvmrc +1 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/PKG-INFO +1 -1
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/control.py +3 -5
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/pyproject.toml +2 -2
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/bsblan.py +15 -22
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/constants.py +1 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/utility.py +0 -60
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_pps.py +3 -2
- python_bsblan-6.2.0/tests/test_time.py +112 -0
- python_bsblan-6.2.0/tests/test_utility.py +173 -0
- python_bsblan-6.1.11/.nvmrc +0 -1
- python_bsblan-6.1.11/tests/test_time.py +0 -182
- python_bsblan-6.1.11/tests/test_utility.py +0 -398
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.editorconfig +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.gitattributes +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/SECURITY.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/agents/pr-review.agent.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/agents/security-reviewer.agent.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/agents/tdd-refactor.agent.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/copilot-instructions.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/labels.yml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/add-parameter.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/code-review.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/opsx-apply.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/opsx-archive.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/opsx-explore.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/opsx-propose.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/prompts/opsx-sync.prompt.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/release-drafter.yml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/renovate.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/bsblan-parameters/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/bsblan-testing/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/feature-doc-updates/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/openspec-apply-change/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/openspec-archive-change/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/openspec-explore/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/openspec-propose/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/skills/openspec-sync-specs/SKILL.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/auto-approve-renovate.yml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/dependency-review.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/docs.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/labels.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/linting.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/release-drafter.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/stale.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/tests.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/workflows/typing.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.github/zizmor.yml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.gitignore +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.pre-commit-config.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.prettierignore +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/.yamllint +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/AGENTS.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/CLAUDE.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/LICENSE.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/Makefile +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/README.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/api/client.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/api/constants.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/api/exceptions.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/api/models.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/getting-started.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/docs/index.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/discovery.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/fake_bsblan_server.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/fetch_param.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/profile_init.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/ruff.toml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/examples/speed_test.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/mkdocs.yml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/.openspec.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/design.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/proposal.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/specs/cooling-operating-mode/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/tasks.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/.openspec.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/design.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/proposal.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/specs/circuit-discovery/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/tasks.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/.openspec.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/design.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/proposal.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/specs/client-retry-policy/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/tasks.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/.openspec.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/design.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/proposal.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/specs/error-classification/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-12-distinguish-schedule-error-types/tasks.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/.openspec.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/design.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/proposal.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/specs/lazy-validation-completeness/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/changes/archive/2026-07-16-fix-partial-validation-state/tasks.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/config.yaml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/specs/circuit-discovery/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/specs/client-retry-policy/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/specs/cooling-operating-mode/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/specs/error-classification/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/openspec/specs/lazy-validation-completeness/spec.md +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/package-lock.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/package.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/sonar-project.properties +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/__init__.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_hot_water.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_parameters.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_schedules.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_temperature.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_thermostat.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_transport.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_validation.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/_version.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/models.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/src/bsblan/py.typed +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/__init__.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/conftest.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/device.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/info.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/password.txt +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/pps_device.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/pps_state.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/pps_static_values.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/state.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/state_circuit2.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/static_state_circuit2.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/thermostat_hvac.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/fixtures/time.json +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/ruff.toml +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_api_initialization.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_api_validation.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_auth.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_backoff_retry.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_bsblan.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_circuit.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_configuration.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_constants.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_context_manager.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_device.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_entity_info.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_entity_info_ha.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_heating_schedule.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_hot_water_additional.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_hotwater_state.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_include_parameter.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_info.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_initialization.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_read_parameters.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_reset_validation.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_response_encoding.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_schedule_models.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_sensor.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_set_heating_schedule.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_set_hot_water_schedule.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_set_hotwater.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_state.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_static_state.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_temperature_validation.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_thermostat.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_utility_additional.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-6.1.11 → python_bsblan-6.2.0}/tests/test_version_errors.py +0 -0
|
@@ -27,6 +27,6 @@ jobs:
|
|
|
27
27
|
with:
|
|
28
28
|
persist-credentials: false
|
|
29
29
|
- name: 🏗 Initialize CodeQL
|
|
30
|
-
uses: github/codeql-action/init@
|
|
30
|
+
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
|
31
31
|
- name: 🚀 Perform CodeQL Analysis
|
|
32
|
-
uses: github/codeql-action/analyze@
|
|
32
|
+
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
with:
|
|
55
55
|
subject-path: ./dist/*
|
|
56
56
|
- name: 🚀 Publish to PyPi
|
|
57
|
-
uses: pypa/gh-action-pypi-publish@
|
|
57
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
58
58
|
with:
|
|
59
59
|
verbose: true
|
|
60
60
|
print-hash: true
|
|
@@ -40,7 +40,7 @@ jobs:
|
|
|
40
40
|
|
|
41
41
|
- name: "Run analysis"
|
|
42
42
|
# yamllint disable-line rule:line-length
|
|
43
|
-
uses: ossf/scorecard-action@
|
|
43
|
+
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
|
|
44
44
|
with:
|
|
45
45
|
results_file: results.sarif
|
|
46
46
|
results_format: sarif
|
|
@@ -66,6 +66,6 @@ jobs:
|
|
|
66
66
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
|
67
67
|
- name: "Upload to code-scanning"
|
|
68
68
|
# yamllint disable-line rule:line-length
|
|
69
|
-
uses: github/codeql-action/upload-sarif@
|
|
69
|
+
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
|
70
70
|
with:
|
|
71
71
|
sarif_file: results.sarif
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24.22.1
|
|
@@ -409,11 +409,9 @@ async def main() -> None:
|
|
|
409
409
|
# Example: Set device time
|
|
410
410
|
if bsblan.supports_time_sync:
|
|
411
411
|
print("\nSetting device time to current system time")
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
print(f"Current system time: {formatted_time}")
|
|
416
|
-
await bsblan.set_time(formatted_time)
|
|
412
|
+
current_time = datetime.now() # noqa: DTZ005
|
|
413
|
+
print(f"Current system time: {current_time:%d.%m.%Y %H:%M:%S}")
|
|
414
|
+
await bsblan.set_time(current_time)
|
|
417
415
|
else:
|
|
418
416
|
print("\nSkipping device time sync for this bus type")
|
|
419
417
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "6.
|
|
3
|
+
version = "6.2.0"
|
|
4
4
|
description = "Asynchronous Python client for BSBLAN API"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
|
|
@@ -197,7 +197,7 @@ dev = [
|
|
|
197
197
|
# hatch is required to support type hinting and proper packaging of the py.typed file.
|
|
198
198
|
"hatch>=1.14.1",
|
|
199
199
|
"isort==8.0.1",
|
|
200
|
-
"ty==0.0.
|
|
200
|
+
"ty==0.0.90",
|
|
201
201
|
"prek>=0.3.3",
|
|
202
202
|
"pre-commit-hooks==6.0.0",
|
|
203
203
|
"pylint==4.1.3",
|
|
@@ -51,9 +51,10 @@ from .models import (
|
|
|
51
51
|
State,
|
|
52
52
|
StaticState,
|
|
53
53
|
)
|
|
54
|
-
from .utility import is_param_value_active
|
|
54
|
+
from .utility import is_param_value_active
|
|
55
55
|
|
|
56
56
|
if TYPE_CHECKING:
|
|
57
|
+
from datetime import datetime
|
|
57
58
|
from typing import Self
|
|
58
59
|
|
|
59
60
|
from aiohttp.client import ClientSession
|
|
@@ -862,24 +863,31 @@ class BSBLAN:
|
|
|
862
863
|
time_data = {"time": data["0"]}
|
|
863
864
|
return DeviceTime.model_validate(time_data)
|
|
864
865
|
|
|
865
|
-
async def set_time(self,
|
|
866
|
+
async def set_time(self, value: datetime) -> None:
|
|
866
867
|
"""Set the time on the BSB-LAN device.
|
|
867
868
|
|
|
868
869
|
Args:
|
|
869
|
-
|
|
870
|
-
|
|
870
|
+
value (datetime): The local time of the heating system. Its date and
|
|
871
|
+
time are written as they are, without timezone conversion.
|
|
871
872
|
|
|
872
873
|
Raises:
|
|
873
|
-
BSBLANInvalidParameterError: If the
|
|
874
|
+
BSBLANInvalidParameterError: If the year is out of range.
|
|
875
|
+
BSBLANError: If the device does not accept the new time.
|
|
874
876
|
|
|
875
877
|
"""
|
|
876
878
|
await self._ensure_device_metadata()
|
|
877
879
|
self._validate_time_sync_supported()
|
|
878
|
-
|
|
880
|
+
if not Validation.MIN_YEAR <= value.year <= Validation.MAX_YEAR:
|
|
881
|
+
msg = f"Invalid year: {value.year}"
|
|
882
|
+
raise BSBLANInvalidParameterError(msg)
|
|
883
|
+
# BSB-LAN 4.1 rejects a space between the date and the time
|
|
879
884
|
response = await self._request(
|
|
880
|
-
base_path="/JS",
|
|
885
|
+
base_path="/JS",
|
|
886
|
+
data=self._set_payload("0", value.strftime("%d.%m.%Y_%H:%M:%S")),
|
|
881
887
|
)
|
|
882
888
|
logger.debug("Response for setting time: %s", response)
|
|
889
|
+
if response.get("0", {}).get("status") != 1:
|
|
890
|
+
raise BSBLANError(ErrorMsg.TIME_SYNC_FAILED)
|
|
883
891
|
|
|
884
892
|
async def thermostat( # noqa: PLR0913 # pylint: disable=too-many-arguments
|
|
885
893
|
self,
|
|
@@ -992,21 +1000,6 @@ class BSBLAN:
|
|
|
992
1000
|
circuit,
|
|
993
1001
|
)
|
|
994
1002
|
|
|
995
|
-
def _validate_time_format(self, time_value: str) -> None:
|
|
996
|
-
"""Validate the time format.
|
|
997
|
-
|
|
998
|
-
Args:
|
|
999
|
-
time_value (str): The time value to validate.
|
|
1000
|
-
|
|
1001
|
-
Raises:
|
|
1002
|
-
BSBLANInvalidParameterError: If the time format is invalid.
|
|
1003
|
-
|
|
1004
|
-
"""
|
|
1005
|
-
try:
|
|
1006
|
-
validate_time_format(time_value, Validation.MIN_YEAR, Validation.MAX_YEAR)
|
|
1007
|
-
except ValueError as err:
|
|
1008
|
-
raise BSBLANInvalidParameterError(str(err)) from err
|
|
1009
|
-
|
|
1010
1003
|
def _set_payload(
|
|
1011
1004
|
self, parameter: str, value: str, type_: str = "1"
|
|
1012
1005
|
) -> dict[str, Any]:
|
|
@@ -561,6 +561,7 @@ class ErrorMsg:
|
|
|
561
561
|
)
|
|
562
562
|
NO_HEATING_SCHEDULE_PARAMS = "No heating schedule parameters available"
|
|
563
563
|
TIME_SYNC_NOT_SUPPORTED = "Time synchronization is not supported by this device"
|
|
564
|
+
TIME_SYNC_FAILED = "The device did not accept the new time"
|
|
564
565
|
BUS_WRITE_NOT_SUPPORTED = "Writing parameters is not supported by this device"
|
|
565
566
|
|
|
566
567
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
import re
|
|
7
6
|
from dataclasses import dataclass, field
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
@@ -12,65 +11,6 @@ from .constants import CircuitConfig
|
|
|
12
11
|
logger = logging.getLogger(__name__)
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
def validate_time_format(
|
|
16
|
-
time_value: str,
|
|
17
|
-
min_year: int,
|
|
18
|
-
max_year: int,
|
|
19
|
-
) -> None:
|
|
20
|
-
"""Validate the BSB-LAN time format.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
time_value: The time value to validate in format DD.MM.YYYY HH:MM:SS.
|
|
24
|
-
min_year: Minimum valid year.
|
|
25
|
-
max_year: Maximum valid year.
|
|
26
|
-
|
|
27
|
-
Raises:
|
|
28
|
-
ValueError: If the time format is invalid.
|
|
29
|
-
|
|
30
|
-
"""
|
|
31
|
-
# BSB-LAN supports format: DD.MM.YYYY HH:MM:SS (e.g., "13.08.2025 10:25:55")
|
|
32
|
-
pattern = r"^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2}):(\d{2})$"
|
|
33
|
-
|
|
34
|
-
match = re.match(pattern, time_value)
|
|
35
|
-
if not match:
|
|
36
|
-
msg = f"Invalid time format: {time_value}. Expected DD.MM.YYYY HH:MM:SS"
|
|
37
|
-
raise ValueError(msg)
|
|
38
|
-
|
|
39
|
-
day, month, year, hour, minute, second = map(int, match.groups())
|
|
40
|
-
|
|
41
|
-
# Validate ranges
|
|
42
|
-
if not (1 <= day <= 31):
|
|
43
|
-
msg = f"Invalid day: {day}"
|
|
44
|
-
raise ValueError(msg)
|
|
45
|
-
if not (1 <= month <= 12):
|
|
46
|
-
msg = f"Invalid month: {month}"
|
|
47
|
-
raise ValueError(msg)
|
|
48
|
-
if not (min_year <= year <= max_year):
|
|
49
|
-
msg = f"Invalid year: {year}"
|
|
50
|
-
raise ValueError(msg)
|
|
51
|
-
if not (0 <= hour <= 23):
|
|
52
|
-
msg = f"Invalid hour: {hour}"
|
|
53
|
-
raise ValueError(msg)
|
|
54
|
-
if not (0 <= minute <= 59):
|
|
55
|
-
msg = f"Invalid minute: {minute}"
|
|
56
|
-
raise ValueError(msg)
|
|
57
|
-
if not (0 <= second <= 59):
|
|
58
|
-
msg = f"Invalid second: {second}"
|
|
59
|
-
raise ValueError(msg)
|
|
60
|
-
|
|
61
|
-
# Additional validation for days per month
|
|
62
|
-
if month in (4, 6, 9, 11) and day > 30:
|
|
63
|
-
msg = f"Invalid day {day} for month {month}"
|
|
64
|
-
raise ValueError(msg)
|
|
65
|
-
if month == 2:
|
|
66
|
-
# Leap year check
|
|
67
|
-
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
|
|
68
|
-
max_day = 29 if is_leap else 28
|
|
69
|
-
if day > max_day:
|
|
70
|
-
msg = f"Invalid day {day} for February in year {year}"
|
|
71
|
-
raise ValueError(msg)
|
|
72
|
-
|
|
73
|
-
|
|
74
14
|
def is_param_value_active(param: dict[str, Any] | None) -> bool:
|
|
75
15
|
"""Check whether a parameter payload carries an active value.
|
|
76
16
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
from datetime import UTC, datetime
|
|
8
9
|
from typing import TYPE_CHECKING, Any
|
|
9
10
|
from unittest.mock import AsyncMock
|
|
10
11
|
|
|
@@ -126,7 +127,7 @@ async def test_pps_set_time_raises_without_posting(pps_bsblan: BSBLAN) -> None:
|
|
|
126
127
|
pps_bsblan._request = request_mock # type: ignore[method-assign]
|
|
127
128
|
|
|
128
129
|
with pytest.raises(BSBLANError, match=ErrorMsg.TIME_SYNC_NOT_SUPPORTED):
|
|
129
|
-
await pps_bsblan.set_time(
|
|
130
|
+
await pps_bsblan.set_time(datetime(2024, 1, 1, 12, 30, 45, tzinfo=UTC))
|
|
130
131
|
|
|
131
132
|
request_mock.assert_not_awaited()
|
|
132
133
|
|
|
@@ -178,7 +179,7 @@ async def test_set_time_fetches_device_metadata_before_sync() -> None:
|
|
|
178
179
|
bsblan._request = request_mock # type: ignore[method-assign]
|
|
179
180
|
|
|
180
181
|
with pytest.raises(BSBLANError, match=ErrorMsg.TIME_SYNC_NOT_SUPPORTED):
|
|
181
|
-
await bsblan.set_time(
|
|
182
|
+
await bsblan.set_time(datetime(2024, 1, 1, 12, 30, 45, tzinfo=UTC))
|
|
182
183
|
|
|
183
184
|
request_mock.assert_awaited_once_with(base_path="/JI")
|
|
184
185
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Tests for device time functionality."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=duplicate-code
|
|
4
|
+
# pylint: disable=protected-access
|
|
5
|
+
# file deepcode ignore W0212: this is a testfile
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from datetime import UTC, datetime, timedelta, timezone
|
|
9
|
+
from unittest.mock import AsyncMock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from bsblan import BSBLAN
|
|
14
|
+
from bsblan.constants import ErrorMsg
|
|
15
|
+
from bsblan.exceptions import BSBLANError, BSBLANInvalidParameterError
|
|
16
|
+
from bsblan.models import Device, DeviceTime, EntityInfo
|
|
17
|
+
from tests import load_fixture
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_bsb_device_metadata(bsblan: BSBLAN) -> None:
|
|
21
|
+
"""Seed cached BSB device metadata for time tests."""
|
|
22
|
+
bsblan._device = Device.model_validate(json.loads(load_fixture("device.json")))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_time_status(bsblan: BSBLAN, status: int) -> None:
|
|
26
|
+
"""Make BSB-LAN answer a time write with the given status."""
|
|
27
|
+
assert isinstance(bsblan._request, AsyncMock)
|
|
28
|
+
bsblan._request.return_value = {"0": {"status": status}}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_get_time(mock_bsblan: BSBLAN) -> None:
|
|
33
|
+
"""Test getting device time.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
mock_bsblan (BSBLAN): The mock BSBLAN instance.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
# Load the time response from fixture
|
|
40
|
+
time_response = json.loads(load_fixture("time.json"))
|
|
41
|
+
|
|
42
|
+
assert isinstance(mock_bsblan._request, AsyncMock)
|
|
43
|
+
mock_bsblan._request.return_value = time_response
|
|
44
|
+
set_bsb_device_metadata(mock_bsblan)
|
|
45
|
+
|
|
46
|
+
# Test getting device time
|
|
47
|
+
device_time = await mock_bsblan.time()
|
|
48
|
+
|
|
49
|
+
# Verify the request was made correctly
|
|
50
|
+
mock_bsblan._request.assert_awaited_with(params={"Parameter": "0"})
|
|
51
|
+
|
|
52
|
+
# Verify the response structure
|
|
53
|
+
assert isinstance(device_time, DeviceTime)
|
|
54
|
+
assert isinstance(device_time.time, EntityInfo)
|
|
55
|
+
assert device_time.time.name == "Current date / Current time of day"
|
|
56
|
+
assert device_time.time.value == "13.08.2025 11:51:37"
|
|
57
|
+
assert device_time.time.desc == ""
|
|
58
|
+
assert device_time.time.data_type == 5
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_set_time(mock_bsblan: BSBLAN) -> None:
|
|
63
|
+
"""Test setting device time."""
|
|
64
|
+
set_bsb_device_metadata(mock_bsblan)
|
|
65
|
+
set_time_status(mock_bsblan, 1)
|
|
66
|
+
|
|
67
|
+
await mock_bsblan.set_time(datetime(2024, 1, 1, 12, 30, 45, tzinfo=UTC))
|
|
68
|
+
|
|
69
|
+
assert isinstance(mock_bsblan._request, AsyncMock)
|
|
70
|
+
mock_bsblan._request.assert_awaited_with(
|
|
71
|
+
base_path="/JS",
|
|
72
|
+
data={"Parameter": "0", "Value": "01.01.2024_12:30:45", "Type": "1"},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_set_time_without_timezone_conversion(mock_bsblan: BSBLAN) -> None:
|
|
78
|
+
"""Test the date and time are written as they are in the given timezone."""
|
|
79
|
+
set_bsb_device_metadata(mock_bsblan)
|
|
80
|
+
set_time_status(mock_bsblan, 1)
|
|
81
|
+
utc_plus_two = timezone(timedelta(hours=2))
|
|
82
|
+
|
|
83
|
+
await mock_bsblan.set_time(datetime(2025, 8, 13, 10, 25, 55, tzinfo=utc_plus_two))
|
|
84
|
+
|
|
85
|
+
assert isinstance(mock_bsblan._request, AsyncMock)
|
|
86
|
+
mock_bsblan._request.assert_awaited_with(
|
|
87
|
+
base_path="/JS",
|
|
88
|
+
data={"Parameter": "0", "Value": "13.08.2025_10:25:55", "Type": "1"},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_set_time_rejected(mock_bsblan: BSBLAN) -> None:
|
|
94
|
+
"""Test setting device time raises when BSB-LAN rejects it."""
|
|
95
|
+
set_bsb_device_metadata(mock_bsblan)
|
|
96
|
+
set_time_status(mock_bsblan, 0)
|
|
97
|
+
|
|
98
|
+
with pytest.raises(BSBLANError, match=ErrorMsg.TIME_SYNC_FAILED):
|
|
99
|
+
await mock_bsblan.set_time(datetime(2024, 1, 1, 12, 30, 45, tzinfo=UTC))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.asyncio
|
|
103
|
+
@pytest.mark.parametrize("year", [1899, 2101])
|
|
104
|
+
async def test_set_time_invalid_year(mock_bsblan: BSBLAN, year: int) -> None:
|
|
105
|
+
"""Test setting device time outside the supported years raises."""
|
|
106
|
+
set_bsb_device_metadata(mock_bsblan)
|
|
107
|
+
|
|
108
|
+
with pytest.raises(BSBLANInvalidParameterError, match=f"Invalid year: {year}"):
|
|
109
|
+
await mock_bsblan.set_time(datetime(year, 1, 1, 12, 30, 45, tzinfo=UTC))
|
|
110
|
+
|
|
111
|
+
assert isinstance(mock_bsblan._request, AsyncMock)
|
|
112
|
+
mock_bsblan._request.assert_not_awaited()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Test cases for utility module."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=redefined-outer-name
|
|
4
|
+
# pylint: disable=protected-access
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from bsblan.utility import APIValidator, is_param_value_active
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def mock_request_data() -> dict[str, Any]:
|
|
18
|
+
"""Fixture for mock request data."""
|
|
19
|
+
return {
|
|
20
|
+
"700": {
|
|
21
|
+
"name": "Operating mode",
|
|
22
|
+
"error": 0,
|
|
23
|
+
"value": "3",
|
|
24
|
+
"desc": "Comfort",
|
|
25
|
+
"dataType": 1,
|
|
26
|
+
"readonly": 0,
|
|
27
|
+
"unit": "",
|
|
28
|
+
},
|
|
29
|
+
"710": {
|
|
30
|
+
"name": "Comfort setpoint",
|
|
31
|
+
"error": 0,
|
|
32
|
+
"value": "20.0",
|
|
33
|
+
"desc": "",
|
|
34
|
+
"dataType": 0,
|
|
35
|
+
"readonly": 0,
|
|
36
|
+
"unit": "°C",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def validator() -> APIValidator:
|
|
43
|
+
"""Fixture for APIValidator instance."""
|
|
44
|
+
api_config = {
|
|
45
|
+
"heating": {
|
|
46
|
+
"700": "hvac_mode",
|
|
47
|
+
"710": "target_temperature",
|
|
48
|
+
# Add other parameters as needed
|
|
49
|
+
},
|
|
50
|
+
"staticValues": {
|
|
51
|
+
"712": "min_temp",
|
|
52
|
+
"714": "heating_protective_setpoint",
|
|
53
|
+
"716": "max_temp",
|
|
54
|
+
# Add other parameters as needed
|
|
55
|
+
},
|
|
56
|
+
"hot_water": {
|
|
57
|
+
"8830": "dhw_actual_value_top_temperature",
|
|
58
|
+
"8820": "state_dhw_pump",
|
|
59
|
+
# Add other parameters as needed
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
validated_sections: set[str] = set()
|
|
63
|
+
return APIValidator(api_config=api_config, validated_sections=validated_sections)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_is_param_value_active() -> None:
|
|
67
|
+
"""Test the shared active-value predicate."""
|
|
68
|
+
assert is_param_value_active({"value": "20.0"}) is True
|
|
69
|
+
|
|
70
|
+
assert is_param_value_active({"value": None}) is False
|
|
71
|
+
assert is_param_value_active({"value": "---"}) is False
|
|
72
|
+
assert is_param_value_active({}) is False
|
|
73
|
+
assert is_param_value_active(None) is False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_is_valid_param(validator: APIValidator) -> None:
|
|
77
|
+
"""Test parameter validation logic."""
|
|
78
|
+
valid_param: dict[str, Any] = {
|
|
79
|
+
"value": "20.0",
|
|
80
|
+
}
|
|
81
|
+
assert validator._is_valid_param(valid_param) is True
|
|
82
|
+
|
|
83
|
+
invalid_params: list[dict[str, Any]] = [
|
|
84
|
+
{"value": None}, # None value
|
|
85
|
+
{"value": "---"}, # Invalid value
|
|
86
|
+
{}, # Empty dict
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
for param in invalid_params:
|
|
90
|
+
assert validator._is_valid_param(param) is False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_get_section_params(validator: APIValidator) -> None:
|
|
94
|
+
"""Test getting section parameters."""
|
|
95
|
+
# Test existing section
|
|
96
|
+
heating_params = validator.get_section_params("heating")
|
|
97
|
+
assert isinstance(heating_params, dict)
|
|
98
|
+
assert "700" in heating_params
|
|
99
|
+
assert heating_params["700"] == "hvac_mode"
|
|
100
|
+
|
|
101
|
+
# Test non-existent section
|
|
102
|
+
empty_params = validator.get_section_params("non_existent")
|
|
103
|
+
assert isinstance(empty_params, dict)
|
|
104
|
+
assert len(empty_params) == 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_is_section_validated(validator: APIValidator) -> None:
|
|
108
|
+
"""Test section validation status checking."""
|
|
109
|
+
assert validator.is_section_validated("heating") is False
|
|
110
|
+
|
|
111
|
+
validator.validated_sections.add("heating")
|
|
112
|
+
assert validator.is_section_validated("heating") is True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_reset_validation(validator: APIValidator) -> None:
|
|
116
|
+
"""Test validation reset functionality."""
|
|
117
|
+
# Add some validated sections
|
|
118
|
+
validator.validated_sections.update({"heating", "sensor"})
|
|
119
|
+
validator.reset_validation()
|
|
120
|
+
assert len(validator.validated_sections) == 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_api_validator_with_empty_sections() -> None:
|
|
124
|
+
"""Test APIValidator with empty API config sections."""
|
|
125
|
+
empty_config: dict[str, dict[str, Any]] = {
|
|
126
|
+
"heating": {},
|
|
127
|
+
"sensor": {},
|
|
128
|
+
"device": {},
|
|
129
|
+
"staticValues": {},
|
|
130
|
+
"hot_water": {},
|
|
131
|
+
}
|
|
132
|
+
validator = APIValidator(empty_config)
|
|
133
|
+
|
|
134
|
+
assert validator.get_section_params("heating") == {}
|
|
135
|
+
assert not validator.is_section_validated("heating")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_api_validator_error_handling(
|
|
139
|
+
validator: APIValidator,
|
|
140
|
+
caplog: pytest.LogCaptureFixture,
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Test error handling in APIValidator."""
|
|
143
|
+
with caplog.at_level(logging.INFO):
|
|
144
|
+
# Test with invalid request data
|
|
145
|
+
validator.validate_section("heating", {})
|
|
146
|
+
assert "Parameter" in caplog.text
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_api_validator_logging(
|
|
150
|
+
validator: APIValidator,
|
|
151
|
+
mock_request_data: dict[str, Any],
|
|
152
|
+
caplog: pytest.LogCaptureFixture,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Test logging functionality in APIValidator."""
|
|
155
|
+
with caplog.at_level(logging.DEBUG):
|
|
156
|
+
validator.validate_section("heating", mock_request_data)
|
|
157
|
+
|
|
158
|
+
# Verify debug logs
|
|
159
|
+
assert "Validated section 'heating'" in caplog.text
|
|
160
|
+
assert "removed" in caplog.text
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_api_validator_concurrent_validation(
|
|
164
|
+
validator: APIValidator,
|
|
165
|
+
mock_request_data: dict[str, Any],
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Test concurrent validation of multiple sections."""
|
|
168
|
+
# Simulate concurrent validation attempts
|
|
169
|
+
validator.validate_section("heating", mock_request_data)
|
|
170
|
+
validator.validate_section("heating", mock_request_data)
|
|
171
|
+
|
|
172
|
+
assert len(validator.validated_sections) == 1
|
|
173
|
+
assert "heating" in validator.validated_sections
|
python_bsblan-6.1.11/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
24.22.0
|