python-bsblan 6.1.5__tar.gz → 6.1.6__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.
Files changed (167) hide show
  1. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/PKG-INFO +1 -1
  2. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/pyproject.toml +2 -2
  3. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_transport.py +29 -1
  4. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_bsblan_edge_cases.py +2 -2
  5. python_bsblan-6.1.6/tests/test_response_encoding.py +62 -0
  6. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.editorconfig +0 -0
  7. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.gitattributes +0 -0
  8. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/CODE_OF_CONDUCT.md +0 -0
  9. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/CONTRIBUTING.md +0 -0
  10. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  11. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/SECURITY.md +0 -0
  14. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/agents/pr-review.agent.md +0 -0
  15. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/agents/security-reviewer.agent.md +0 -0
  16. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/agents/tdd-refactor.agent.md +0 -0
  17. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/copilot-instructions.md +0 -0
  18. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/hooks/run-validation-after-edits.json +0 -0
  19. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/hooks/run_validation_after_edits.sh +0 -0
  20. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/labels.yml +0 -0
  21. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/add-parameter.prompt.md +0 -0
  22. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/code-review.prompt.md +0 -0
  23. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/opsx-apply.prompt.md +0 -0
  24. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/opsx-archive.prompt.md +0 -0
  25. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/opsx-explore.prompt.md +0 -0
  26. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/opsx-propose.prompt.md +0 -0
  27. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/prompts/opsx-sync.prompt.md +0 -0
  28. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/release-drafter.yml +0 -0
  29. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/renovate.json +0 -0
  30. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/bsblan-parameters/SKILL.md +0 -0
  31. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/bsblan-testing/SKILL.md +0 -0
  32. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/feature-doc-updates/SKILL.md +0 -0
  33. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/openspec-apply-change/SKILL.md +0 -0
  34. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/openspec-archive-change/SKILL.md +0 -0
  35. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/openspec-explore/SKILL.md +0 -0
  36. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/openspec-propose/SKILL.md +0 -0
  37. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/skills/openspec-sync-specs/SKILL.md +0 -0
  38. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/auto-approve-renovate.yml +0 -0
  39. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/codeql.yaml +0 -0
  40. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/dependency-review.yaml +0 -0
  41. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/docs.yaml +0 -0
  42. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/labels.yaml +0 -0
  43. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/linting.yaml +0 -0
  44. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/lock.yaml +0 -0
  45. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/pr-labels.yaml +0 -0
  46. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/release-drafter.yaml +0 -0
  47. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/release.yaml +0 -0
  48. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/scorecard.yml +0 -0
  49. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/stale.yaml +0 -0
  50. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/tests.yaml +0 -0
  51. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/workflows/typing.yaml +0 -0
  52. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.github/zizmor.yml +0 -0
  53. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.gitignore +0 -0
  54. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.nvmrc +0 -0
  55. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.pre-commit-config.yaml +0 -0
  56. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.prettierignore +0 -0
  57. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/.yamllint +0 -0
  58. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/AGENTS.md +0 -0
  59. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/CLAUDE.md +0 -0
  60. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/LICENSE.md +0 -0
  61. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/Makefile +0 -0
  62. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/README.md +0 -0
  63. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/api/client.md +0 -0
  64. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/api/constants.md +0 -0
  65. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/api/exceptions.md +0 -0
  66. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/api/models.md +0 -0
  67. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/getting-started.md +0 -0
  68. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/docs/index.md +0 -0
  69. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/control.py +0 -0
  70. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/discovery.py +0 -0
  71. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/fetch_param.py +0 -0
  72. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/profile_init.py +0 -0
  73. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/ruff.toml +0 -0
  74. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/examples/speed_test.py +0 -0
  75. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/mkdocs.yml +0 -0
  76. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/.openspec.yaml +0 -0
  77. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/design.md +0 -0
  78. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/proposal.md +0 -0
  79. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/specs/cooling-operating-mode/spec.md +0 -0
  80. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-03-add-cooling-operating-mode/tasks.md +0 -0
  81. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/.openspec.yaml +0 -0
  82. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/design.md +0 -0
  83. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/proposal.md +0 -0
  84. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/specs/circuit-discovery/spec.md +0 -0
  85. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-harden-circuit-discovery-consistency/tasks.md +0 -0
  86. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/.openspec.yaml +0 -0
  87. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/design.md +0 -0
  88. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/proposal.md +0 -0
  89. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/specs/client-retry-policy/spec.md +0 -0
  90. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/changes/archive/2026-07-04-improve-client-readability-domain-slices/tasks.md +0 -0
  91. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/config.yaml +0 -0
  92. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/specs/circuit-discovery/spec.md +0 -0
  93. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/specs/client-retry-policy/spec.md +0 -0
  94. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/openspec/specs/cooling-operating-mode/spec.md +0 -0
  95. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/package-lock.json +0 -0
  96. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/package.json +0 -0
  97. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/sonar-project.properties +0 -0
  98. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/__init__.py +0 -0
  99. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_hot_water.py +0 -0
  100. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_parameters.py +0 -0
  101. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_schedules.py +0 -0
  102. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_temperature.py +0 -0
  103. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_thermostat.py +0 -0
  104. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_validation.py +0 -0
  105. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/_version.py +0 -0
  106. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/bsblan.py +0 -0
  107. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/constants.py +0 -0
  108. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/exceptions.py +0 -0
  109. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/models.py +0 -0
  110. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/py.typed +0 -0
  111. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/src/bsblan/utility.py +0 -0
  112. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/__init__.py +0 -0
  113. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/conftest.py +0 -0
  114. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/device.json +0 -0
  115. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/dict_version.json +0 -0
  116. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/hot_water_state.json +0 -0
  117. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/info.json +0 -0
  118. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/password.txt +0 -0
  119. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/pps_device.json +0 -0
  120. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/pps_state.json +0 -0
  121. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/pps_static_values.json +0 -0
  122. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/sensor.json +0 -0
  123. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/state.json +0 -0
  124. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/state_circuit2.json +0 -0
  125. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/static_state.json +0 -0
  126. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/static_state_circuit2.json +0 -0
  127. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/thermostat_hvac.json +0 -0
  128. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/thermostat_temp.json +0 -0
  129. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/fixtures/time.json +0 -0
  130. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/ruff.toml +0 -0
  131. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_api_initialization.py +0 -0
  132. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_api_validation.py +0 -0
  133. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_auth.py +0 -0
  134. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_backoff_retry.py +0 -0
  135. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_bsblan.py +0 -0
  136. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_circuit.py +0 -0
  137. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_configuration.py +0 -0
  138. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_constants.py +0 -0
  139. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_context_manager.py +0 -0
  140. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_device.py +0 -0
  141. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_dhw_time_switch.py +0 -0
  142. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_entity_info.py +0 -0
  143. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_entity_info_ha.py +0 -0
  144. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_heating_schedule.py +0 -0
  145. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_hot_water_additional.py +0 -0
  146. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_hotwater_state.py +0 -0
  147. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_include_parameter.py +0 -0
  148. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_info.py +0 -0
  149. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_initialization.py +0 -0
  150. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_pps.py +0 -0
  151. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_read_parameters.py +0 -0
  152. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_reset_validation.py +0 -0
  153. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_schedule_models.py +0 -0
  154. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_sensor.py +0 -0
  155. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_set_heating_schedule.py +0 -0
  156. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_set_hot_water_schedule.py +0 -0
  157. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_set_hotwater.py +0 -0
  158. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_state.py +0 -0
  159. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_static_state.py +0 -0
  160. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_temperature_unit.py +0 -0
  161. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_temperature_validation.py +0 -0
  162. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_thermostat.py +0 -0
  163. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_time.py +0 -0
  164. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_utility.py +0 -0
  165. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_utility_additional.py +0 -0
  166. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_utility_edge_cases.py +0 -0
  167. {python_bsblan-6.1.5 → python_bsblan-6.1.6}/tests/test_version_errors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bsblan
3
- Version: 6.1.5
3
+ Version: 6.1.6
4
4
  Summary: Asynchronous Python client for BSBLAN API
5
5
  Project-URL: Homepage, https://github.com/liudger/python-bsblan
6
6
  Project-URL: Repository, https://github.com/liudger/python-bsblan
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-bsblan"
3
- version = "6.1.5"
3
+ version = "6.1.6"
4
4
  description = "Asynchronous Python client for BSBLAN API"
5
5
  authors = [
6
6
  {name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
@@ -196,7 +196,7 @@ dev = [
196
196
  # hatch is required to support type hinting and proper packaging of the py.typed file.
197
197
  "hatch>=1.14.1",
198
198
  "isort==8.0.1",
199
- "ty==0.0.55",
199
+ "ty==0.0.56",
200
200
  "prek>=0.3.3",
201
201
  "pre-commit-hooks==6.0.0",
202
202
  "pylint==4.0.5",
@@ -9,6 +9,7 @@ stable ``_request`` facade that delegates here.
9
9
  from __future__ import annotations
10
10
 
11
11
  import asyncio
12
+ import json
12
13
  import logging
13
14
  from typing import TYPE_CHECKING, Any, cast
14
15
 
@@ -131,7 +132,7 @@ class BSBLANTransport:
131
132
  headers=headers,
132
133
  ) as response:
133
134
  response.raise_for_status()
134
- response_data = cast("dict[str, Any]", await response.json())
135
+ response_data = await self._read_json(response)
135
136
  return self._process_response(response_data, base_path)
136
137
  except aiohttp.ClientResponseError as e:
137
138
  if e.status in HTTP_AUTH_STATUSES:
@@ -142,6 +143,33 @@ class BSBLANTransport:
142
143
  msg = ErrorMsg.INVALID_RESPONSE.format(e)
143
144
  raise BSBLANError(msg) from e
144
145
 
146
+ @staticmethod
147
+ async def _read_json(response: aiohttp.ClientResponse) -> dict[str, Any]:
148
+ """Decode a BSB-LAN JSON response tolerant of non-UTF-8 encodings.
149
+
150
+ Some BSB-LAN firmwares serve custom parameter descriptions using
151
+ Latin-1 (ISO-8859-1) bytes (for example the ``§`` or ``°`` characters)
152
+ while declaring no charset. ``aiohttp`` assumes UTF-8 and raises
153
+ ``UnicodeDecodeError``. Read the raw body and fall back to Latin-1,
154
+ which can decode any byte sequence, before parsing the JSON.
155
+
156
+ Args:
157
+ response: The active aiohttp response to read.
158
+
159
+ Returns:
160
+ dict[str, Any]: The parsed JSON payload.
161
+
162
+ Raises:
163
+ ValueError: If the body is not valid JSON.
164
+
165
+ """
166
+ raw = await response.read()
167
+ try:
168
+ text = raw.decode("utf-8")
169
+ except UnicodeDecodeError:
170
+ text = raw.decode("latin-1")
171
+ return cast("dict[str, Any]", json.loads(text))
172
+
145
173
  def _process_response(
146
174
  self, response_data: dict[str, Any], base_path: str
147
175
  ) -> dict[str, Any]:
@@ -71,13 +71,13 @@ async def test_value_error_path(monkeypatch: Any) -> None:
71
71
  # Mock a successful response but with invalid JSON processing
72
72
  mock_response = MagicMock()
73
73
  mock_response.raise_for_status = MagicMock()
74
- mock_response.json = AsyncMock(side_effect=ValueError("Invalid JSON"))
74
+ mock_response.read = AsyncMock(return_value=b"Invalid JSON")
75
75
  mock_response.__aenter__ = AsyncMock(return_value=mock_response)
76
76
  mock_response.__aexit__ = AsyncMock(return_value=None)
77
77
 
78
78
  monkeypatch.setattr(session, "request", MagicMock(return_value=mock_response))
79
79
 
80
- with pytest.raises(BSBLANError, match="Invalid JSON"):
80
+ with pytest.raises(BSBLANError, match="Invalid response format"):
81
81
  await bsblan._request()
82
82
 
83
83
 
@@ -0,0 +1,62 @@
1
+ """Tests for tolerant decoding of non-UTF-8 BSB-LAN responses."""
2
+
3
+ # file deepcode ignore W0212: this is a testfile
4
+ # pylint: disable=protected-access
5
+
6
+ import aiohttp
7
+ import pytest
8
+ from aresponses import ResponsesMockServer
9
+
10
+ from bsblan import BSBLAN
11
+ from bsblan.bsblan import BSBLANConfig
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_request_decodes_latin1_response(
16
+ aresponses: ResponsesMockServer,
17
+ ) -> None:
18
+ """Latin-1 responses (e.g. custom descriptions with '§') are decoded."""
19
+ # "§" is byte 0xa7 in Latin-1 and is an invalid UTF-8 start byte.
20
+ payload = '{"700": {"name": "x", "desc": "Betrieb \u00a7", "value": "1"}}'
21
+ aresponses.add(
22
+ "example.com",
23
+ "/JQ",
24
+ "POST",
25
+ aresponses.Response(
26
+ status=200,
27
+ headers={"Content-Type": "application/json"},
28
+ body=payload.encode("latin-1"),
29
+ ),
30
+ )
31
+
32
+ async with aiohttp.ClientSession() as session:
33
+ config = BSBLANConfig(host="example.com")
34
+ bsblan = BSBLAN(config, session=session)
35
+ response = await bsblan._request()
36
+
37
+ assert response["700"]["desc"] == "Betrieb \u00a7"
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_request_decodes_utf8_response(
42
+ aresponses: ResponsesMockServer,
43
+ ) -> None:
44
+ """Valid UTF-8 responses keep decoding correctly."""
45
+ payload = '{"700": {"name": "x", "desc": "Außentemperatur", "value": "1"}}'
46
+ aresponses.add(
47
+ "example.com",
48
+ "/JQ",
49
+ "POST",
50
+ aresponses.Response(
51
+ status=200,
52
+ headers={"Content-Type": "application/json"},
53
+ body=payload.encode("utf-8"),
54
+ ),
55
+ )
56
+
57
+ async with aiohttp.ClientSession() as session:
58
+ config = BSBLANConfig(host="example.com")
59
+ bsblan = BSBLAN(config, session=session)
60
+ response = await bsblan._request()
61
+
62
+ assert response["700"]["desc"] == "Außentemperatur"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes