python-bsblan 3.1.2__tar.gz → 3.1.4__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 (92) hide show
  1. python_bsblan-3.1.4/.github/workflows/auto-approve-renovate.yml +20 -0
  2. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/codeql.yaml +1 -1
  3. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/labels.yaml +1 -1
  4. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/linting.yaml +13 -13
  5. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/release.yaml +3 -3
  6. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/stale.yaml +1 -1
  7. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/tests.yaml +6 -6
  8. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/typing.yaml +2 -2
  9. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/PKG-INFO +1 -1
  10. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/package-lock.json +4 -4
  11. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/package.json +1 -1
  12. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/pyproject.toml +6 -5
  13. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/__init__.py +8 -0
  14. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/bsblan.py +93 -11
  15. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/constants.py +1 -0
  16. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/models.py +169 -3
  17. python_bsblan-3.1.4/tests/test_backoff_retry.py +213 -0
  18. python_bsblan-3.1.4/tests/test_schedule_models.py +229 -0
  19. python_bsblan-3.1.4/tests/test_sensor.py +107 -0
  20. python_bsblan-3.1.4/tests/test_set_hot_water_schedule.py +174 -0
  21. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/uv.lock +123 -46
  22. python_bsblan-3.1.2/tests/test_sensor.py +0 -52
  23. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.editorconfig +0 -0
  24. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.gitattributes +0 -0
  25. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
  26. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/CONTRIBUTING.md +0 -0
  27. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
  28. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  30. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/LICENSE.md +0 -0
  31. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/copilot-instructions.md +0 -0
  32. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/labels.yml +0 -0
  33. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/release-drafter.yml +0 -0
  34. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/renovate.json +0 -0
  35. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/lock.yaml +0 -0
  36. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/pr-labels.yaml +0 -0
  37. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/release-drafter.yaml +0 -0
  38. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.gitignore +0 -0
  39. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.nvmrc +0 -0
  40. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.pre-commit-config.yaml +0 -0
  41. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.prettierignore +0 -0
  42. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.yamllint +0 -0
  43. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/AGENTS.md +0 -0
  44. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/CLAUDE.md +0 -0
  45. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/README.md +0 -0
  46. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/examples/control.py +0 -0
  47. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/examples/ruff.toml +0 -0
  48. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/sonar-project.properties +0 -0
  49. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/exceptions.py +0 -0
  50. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/py.typed +0 -0
  51. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/utility.py +0 -0
  52. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/__init__.py +0 -0
  53. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/conftest.py +0 -0
  54. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/device.json +0 -0
  55. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/dict_version.json +0 -0
  56. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/hot_water_state.json +0 -0
  57. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/info.json +0 -0
  58. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/password.txt +0 -0
  59. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/sensor.json +0 -0
  60. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/state.json +0 -0
  61. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/static_state.json +0 -0
  62. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/thermostat_hvac.json +0 -0
  63. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/thermostat_temp.json +0 -0
  64. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/time.json +0 -0
  65. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/ruff.toml +0 -0
  66. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_api_initialization.py +0 -0
  67. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_api_validation.py +0 -0
  68. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_auth.py +0 -0
  69. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_bsblan.py +0 -0
  70. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_bsblan_edge_cases.py +0 -0
  71. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_configuration.py +0 -0
  72. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_constants.py +0 -0
  73. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_context_manager.py +0 -0
  74. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_device.py +0 -0
  75. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_dhw_time_switch.py +0 -0
  76. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_entity_info.py +0 -0
  77. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_hot_water_additional.py +0 -0
  78. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_hotwater_state.py +0 -0
  79. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_info.py +0 -0
  80. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_initialization.py +0 -0
  81. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_reset_validation.py +0 -0
  82. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_set_hotwater.py +0 -0
  83. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_state.py +0 -0
  84. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_static_state.py +0 -0
  85. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_temperature_unit.py +0 -0
  86. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_temperature_validation.py +0 -0
  87. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_thermostat.py +0 -0
  88. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_time.py +0 -0
  89. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility.py +0 -0
  90. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility_additional.py +0 -0
  91. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility_edge_cases.py +0 -0
  92. {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_version_errors.py +0 -0
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Auto-approve Renovate PRs
3
+
4
+ # yamllint disable-line rule:truthy
5
+ on:
6
+ pull_request_target:
7
+ types:
8
+ - opened
9
+ - synchronize
10
+ - reopened
11
+
12
+ permissions:
13
+ pull-requests: write
14
+
15
+ jobs:
16
+ auto-approve:
17
+ runs-on: ubuntu-latest
18
+ if: github.actor == 'renovate[bot]'
19
+ steps:
20
+ - uses: hmarr/auto-approve-action@v4
@@ -17,7 +17,7 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
19
  - name: ⤵️ Check out code from GitHub
20
- uses: actions/checkout@v5.0.1
20
+ uses: actions/checkout@v6.0.1
21
21
  - name: 🏗 Initialize CodeQL
22
22
  uses: github/codeql-action/init@v3.31.6
23
23
  - name: 🚀 Perform CodeQL Analysis
@@ -16,7 +16,7 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - name: ⤵️ Check out code from GitHub
19
- uses: actions/checkout@v5.0.1
19
+ uses: actions/checkout@v6.0.1
20
20
  - name: 🚀 Run Label Syncer
21
21
  uses: micnncim/action-label-syncer@v1.3.0
22
22
  env:
@@ -16,14 +16,14 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - name: ⤵️ Check out code from GitHub
19
- uses: actions/checkout@v5.0.1
19
+ uses: actions/checkout@v6.0.1
20
20
  - name: 🏗 Set up uv
21
21
  uses: astral-sh/setup-uv@v6
22
22
  with:
23
23
  enable-cache: true
24
24
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
25
25
  id: python
26
- uses: actions/setup-python@v6.0.0
26
+ uses: actions/setup-python@v6.1.0
27
27
  with:
28
28
  python-version: ${{ env.DEFAULT_PYTHON }}
29
29
  - name: 🏗 Install Python dependencies
@@ -36,14 +36,14 @@ jobs:
36
36
  runs-on: ubuntu-latest
37
37
  steps:
38
38
  - name: ⤵️ Check out code from GitHub
39
- uses: actions/checkout@v5.0.1
39
+ uses: actions/checkout@v6.0.1
40
40
  - name: 🏗 Set up uv
41
41
  uses: astral-sh/setup-uv@v6
42
42
  with:
43
43
  enable-cache: true
44
44
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
45
45
  id: python
46
- uses: actions/setup-python@v6.0.0
46
+ uses: actions/setup-python@v6.1.0
47
47
  with:
48
48
  python-version: ${{ env.DEFAULT_PYTHON }}
49
49
  - name: 🏗 Install Python dependencies
@@ -58,14 +58,14 @@ jobs:
58
58
  runs-on: ubuntu-latest
59
59
  steps:
60
60
  - name: ⤵️ Check out code from GitHub
61
- uses: actions/checkout@v5.0.1
61
+ uses: actions/checkout@v6.0.1
62
62
  - name: 🏗 Set up uv
63
63
  uses: astral-sh/setup-uv@v6
64
64
  with:
65
65
  enable-cache: true
66
66
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
67
67
  id: python
68
- uses: actions/setup-python@v6.0.0
68
+ uses: actions/setup-python@v6.1.0
69
69
  with:
70
70
  python-version: ${{ env.DEFAULT_PYTHON }}
71
71
  - name: 🏗 Install Python dependencies
@@ -102,14 +102,14 @@ jobs:
102
102
  runs-on: ubuntu-latest
103
103
  steps:
104
104
  - name: ⤵️ Check out code from GitHub
105
- uses: actions/checkout@v5.0.1
105
+ uses: actions/checkout@v6.0.1
106
106
  - name: 🏗 Set up uv
107
107
  uses: astral-sh/setup-uv@v6
108
108
  with:
109
109
  enable-cache: true
110
110
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
111
111
  id: python
112
- uses: actions/setup-python@v6.0.0
112
+ uses: actions/setup-python@v6.1.0
113
113
  with:
114
114
  python-version: ${{ env.DEFAULT_PYTHON }}
115
115
  - name: 🏗 Install Python dependencies
@@ -122,14 +122,14 @@ jobs:
122
122
  runs-on: ubuntu-latest
123
123
  steps:
124
124
  - name: ⤵️ Check out code from GitHub
125
- uses: actions/checkout@v5.0.1
125
+ uses: actions/checkout@v6.0.1
126
126
  - name: 🏗 Set up uv
127
127
  uses: astral-sh/setup-uv@v6
128
128
  with:
129
129
  enable-cache: true
130
130
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
131
131
  id: python
132
- uses: actions/setup-python@v6.0.0
132
+ uses: actions/setup-python@v6.1.0
133
133
  with:
134
134
  python-version: ${{ env.DEFAULT_PYTHON }}
135
135
  - name: 🏗 Install Python dependencies
@@ -142,20 +142,20 @@ jobs:
142
142
  runs-on: ubuntu-latest
143
143
  steps:
144
144
  - name: ⤵️ Check out code from GitHub
145
- uses: actions/checkout@v5.0.1
145
+ uses: actions/checkout@v6.0.1
146
146
  - name: 🏗 Set up uv
147
147
  uses: astral-sh/setup-uv@v6
148
148
  with:
149
149
  enable-cache: true
150
150
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
151
151
  id: python
152
- uses: actions/setup-python@v6.0.0
152
+ uses: actions/setup-python@v6.1.0
153
153
  with:
154
154
  python-version: ${{ env.DEFAULT_PYTHON }}
155
155
  - name: 🏗 Install Python dependencies
156
156
  run: uv sync --dev
157
157
  - name: 🏗 Set up Node.js
158
- uses: actions/setup-node@v4.4.0
158
+ uses: actions/setup-node@v6.1.0
159
159
  with:
160
160
  node-version-file: ".nvmrc"
161
161
  cache: "npm"
@@ -22,14 +22,14 @@ jobs:
22
22
  id-token: write
23
23
  steps:
24
24
  - name: ⤵️ Check out code from GitHub
25
- uses: actions/checkout@v5.0.1
25
+ uses: actions/checkout@v6.0.1
26
26
  - name: 🏗 Set up uv
27
27
  uses: astral-sh/setup-uv@v6
28
28
  with:
29
29
  enable-cache: true
30
30
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
31
31
  id: python
32
- uses: actions/setup-python@v6.0.0
32
+ uses: actions/setup-python@v6.1.0
33
33
  with:
34
34
  python-version: ${{ env.DEFAULT_PYTHON }}
35
35
  - name: 🏗 Install dependencies
@@ -48,7 +48,7 @@ jobs:
48
48
  verbose: true
49
49
  print-hash: true
50
50
  - name: ✍️ Sign published artifacts
51
- uses: sigstore/gh-action-sigstore-python@v3.1.0
51
+ uses: sigstore/gh-action-sigstore-python@v3.2.0
52
52
  with:
53
53
  inputs: ./dist/*.tar.gz ./dist/*.whl
54
54
  release-signing-artifacts: false
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - name: 🚀 Run stale
16
- uses: actions/stale@v10.1.0
16
+ uses: actions/stale@v10.1.1
17
17
  with:
18
18
  repo-token: ${{ secrets.GITHUB_TOKEN }}
19
19
  days-before-stale: 30
@@ -19,14 +19,14 @@ jobs:
19
19
  python: ["3.12", "3.13", "3.14"]
20
20
  steps:
21
21
  - name: ⤵️ Check out code from GitHub
22
- uses: actions/checkout@v5.0.1
22
+ uses: actions/checkout@v6.0.1
23
23
  - name: 🏗 Set up uv
24
24
  uses: astral-sh/setup-uv@v6
25
25
  with:
26
26
  enable-cache: true
27
27
  - name: 🏗 Set up Python ${{ matrix.python }}
28
28
  id: python
29
- uses: actions/setup-python@v6.0.0
29
+ uses: actions/setup-python@v6.1.0
30
30
  with:
31
31
  python-version: ${{ matrix.python }}
32
32
  - name: 🏗 Install dependencies
@@ -34,7 +34,7 @@ jobs:
34
34
  - name: 🚀 Run pytest
35
35
  run: uv run pytest --cov=bsblan tests
36
36
  - name: ⬆️ Upload coverage artifact
37
- uses: actions/upload-artifact@v4.6.2
37
+ uses: actions/upload-artifact@v5.0.0
38
38
  with:
39
39
  name: coverage-${{ matrix.python }}
40
40
  include-hidden-files: true
@@ -45,18 +45,18 @@ jobs:
45
45
  needs: pytest
46
46
  steps:
47
47
  - name: ⤵️ Check out code from GitHub
48
- uses: actions/checkout@v5.0.1
48
+ uses: actions/checkout@v6.0.1
49
49
  with:
50
50
  fetch-depth: 0
51
51
  - name: ⬇️ Download coverage data
52
- uses: actions/download-artifact@v5.0.0
52
+ uses: actions/download-artifact@v6.0.0
53
53
  - name: 🏗 Set up uv
54
54
  uses: astral-sh/setup-uv@v6
55
55
  with:
56
56
  enable-cache: true
57
57
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
58
58
  id: python
59
- uses: actions/setup-python@v6.0.0
59
+ uses: actions/setup-python@v6.1.0
60
60
  with:
61
61
  python-version: ${{ env.DEFAULT_PYTHON }}
62
62
  - name: 🏗 Install dependencies
@@ -16,14 +16,14 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - name: ⤵️ Check out code from GitHub
19
- uses: actions/checkout@v5.0.1
19
+ uses: actions/checkout@v6.0.1
20
20
  - name: 🏗 Set up uv
21
21
  uses: astral-sh/setup-uv@v6
22
22
  with:
23
23
  enable-cache: true
24
24
  - name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
25
25
  id: python
26
- uses: actions/setup-python@v6.0.0
26
+ uses: actions/setup-python@v6.1.0
27
27
  with:
28
28
  python-version: ${{ env.DEFAULT_PYTHON }}
29
29
  - name: 🏗 Install dependencies
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bsblan
3
- Version: 3.1.2
3
+ Version: 3.1.4
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
@@ -12,13 +12,13 @@
12
12
  "url-parse": "1.5.10"
13
13
  },
14
14
  "devDependencies": {
15
- "prettier": "3.6.2"
15
+ "prettier": "3.7.4"
16
16
  }
17
17
  },
18
18
  "node_modules/prettier": {
19
- "version": "3.6.2",
20
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
21
- "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
19
+ "version": "3.7.4",
20
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
21
+ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
22
22
  "dev": true,
23
23
  "license": "MIT",
24
24
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "author": "WJL van Rootselaar <liudgervr@gmail.com>",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "prettier": "3.6.2"
12
+ "prettier": "3.7.4"
13
13
  },
14
14
  "dependencies": {
15
15
  "url-parse": "1.5.10"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-bsblan"
3
- version = "3.1.2"
3
+ version = "3.1.4"
4
4
  description = "Asynchronous Python client for BSBLAN API"
5
5
  authors = [
6
6
  {name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
@@ -71,7 +71,7 @@ exclude_lines = [
71
71
  # free to run mypy on Windows, Linux, or macOS and get consistent
72
72
  # results.
73
73
  platform = "linux"
74
- python_version = "3.11"
74
+ python_version = "3.12"
75
75
 
76
76
  # show error messages from unrelated files
77
77
  follow_imports = "normal"
@@ -135,7 +135,7 @@ min-similarity-lines = 4 # Minimum lines number of a similarity.
135
135
  max-line-length=88
136
136
 
137
137
  [tool.pytest.ini_options]
138
- addopts = "--cov"
138
+ addopts = "--cov -n auto"
139
139
  asyncio_mode = "auto"
140
140
 
141
141
  [tool.ruff.lint]
@@ -189,15 +189,16 @@ dev = [
189
189
  # hatch is required to support type hinting and proper packaging of the py.typed file.
190
190
  "hatch>=1.14.1",
191
191
  "isort==6.1.0",
192
- "mypy==1.18.2",
192
+ "mypy==1.19.0",
193
193
  "pre-commit==4.5.0",
194
194
  "pre-commit-hooks==6.0.0",
195
195
  "pylint==3.3.9",
196
196
  "pytest>=8.3.5",
197
197
  "pytest-asyncio==1.3.0",
198
198
  "pytest-cov==7.0.0",
199
+ "pytest-xdist>=3.8.0",
199
200
  "pyupgrade==3.21.2",
200
- "ruff==0.14.6",
201
+ "ruff==0.14.8",
201
202
  "safety==3.7.0",
202
203
  "vulture==2.14",
203
204
  "yamllint==1.37.1",
@@ -3,9 +3,12 @@
3
3
  from .bsblan import BSBLAN, BSBLANConfig
4
4
  from .exceptions import BSBLANAuthError, BSBLANConnectionError, BSBLANError
5
5
  from .models import (
6
+ DaySchedule,
6
7
  Device,
7
8
  DeviceTime,
9
+ DHWSchedule,
8
10
  DHWTimeSwitchPrograms,
11
+ EntityInfo,
9
12
  HotWaterConfig,
10
13
  HotWaterSchedule,
11
14
  HotWaterState,
@@ -14,6 +17,7 @@ from .models import (
14
17
  SetHotWaterParam,
15
18
  State,
16
19
  StaticState,
20
+ TimeSlot,
17
21
  )
18
22
 
19
23
  __all__ = [
@@ -22,9 +26,12 @@ __all__ = [
22
26
  "BSBLANConfig",
23
27
  "BSBLANConnectionError",
24
28
  "BSBLANError",
29
+ "DHWSchedule",
25
30
  "DHWTimeSwitchPrograms",
31
+ "DaySchedule",
26
32
  "Device",
27
33
  "DeviceTime",
34
+ "EntityInfo",
28
35
  "HotWaterConfig",
29
36
  "HotWaterSchedule",
30
37
  "HotWaterState",
@@ -33,4 +40,5 @@ __all__ = [
33
40
  "SetHotWaterParam",
34
41
  "State",
35
42
  "StaticState",
43
+ "TimeSlot",
36
44
  ]
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
11
11
  from collections.abc import Mapping
12
12
 
13
13
  import aiohttp
14
+ import backoff
14
15
  from aiohttp.hdrs import METH_POST
15
16
  from aiohttp.helpers import BasicAuth
16
17
  from packaging import version as pkg_version
@@ -29,6 +30,7 @@ from .constants import (
29
30
  MAX_VALID_YEAR,
30
31
  MIN_VALID_YEAR,
31
32
  MULTI_PARAMETER_ERROR_MSG,
33
+ NO_SCHEDULE_ERROR_MSG,
32
34
  NO_STATE_ERROR_MSG,
33
35
  SESSION_NOT_INITIALIZED_ERROR_MSG,
34
36
  SETTABLE_HOT_WATER_PARAMS,
@@ -45,8 +47,10 @@ from .exceptions import (
45
47
  BSBLANVersionError,
46
48
  )
47
49
  from .models import (
50
+ DaySchedule,
48
51
  Device,
49
52
  DeviceTime,
53
+ DHWSchedule,
50
54
  HotWaterConfig,
51
55
  HotWaterSchedule,
52
56
  HotWaterState,
@@ -409,6 +413,9 @@ class BSBLAN:
409
413
  ) -> dict[str, Any]:
410
414
  """Handle a request to a BSBLAN device.
411
415
 
416
+ Uses exponential backoff to retry on transient connection errors.
417
+ Will not retry on authentication errors (401, 403) or not found (404).
418
+
412
419
  Args:
413
420
  method (str): The HTTP method to use for the request.
414
421
  base_path (str): The base path for the URL.
@@ -420,9 +427,47 @@ class BSBLAN:
420
427
  dict[str, Any]: The JSON response from the BSBLAN device.
421
428
 
422
429
  Raises:
423
- BSBLANConnectionError: If there is a connection error.
430
+ BSBLANConnectionError: If there is a connection error after retries.
431
+ BSBLANAuthError: If authentication fails (not retried).
424
432
  BSBLANError: If there is an error with the request.
425
433
 
434
+ """
435
+ try:
436
+ return await self._request_with_retry(method, base_path, data, params)
437
+ except TimeoutError as e:
438
+ raise BSBLANConnectionError(BSBLANConnectionError.message_timeout) from e
439
+ except aiohttp.ClientError as e:
440
+ raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
441
+
442
+ @backoff.on_exception(
443
+ backoff.expo,
444
+ (TimeoutError, aiohttp.ClientError),
445
+ max_tries=3,
446
+ max_time=30,
447
+ giveup=lambda e: isinstance(e, aiohttp.ClientResponseError) and e.status == 404,
448
+ logger=logger,
449
+ )
450
+ async def _request_with_retry(
451
+ self,
452
+ method: str,
453
+ base_path: str,
454
+ data: dict[str, object] | None,
455
+ params: Mapping[str, str | int] | str | None,
456
+ ) -> dict[str, Any]:
457
+ """Execute HTTP request with retry logic.
458
+
459
+ This internal method handles the actual HTTP request and is decorated
460
+ with backoff for automatic retries on transient failures.
461
+
462
+ Args:
463
+ method: The HTTP method to use.
464
+ base_path: The base path for the URL.
465
+ data: The data to send in the request body.
466
+ params: The query parameters to include.
467
+
468
+ Returns:
469
+ dict[str, Any]: The JSON response from the BSBLAN device.
470
+
426
471
  """
427
472
  if self.session is None:
428
473
  raise BSBLANError(SESSION_NOT_INITIALIZED_ERROR_MSG)
@@ -443,14 +488,10 @@ class BSBLAN:
443
488
  response.raise_for_status()
444
489
  response_data = cast("dict[str, Any]", await response.json())
445
490
  return self._process_response(response_data, base_path)
446
- except TimeoutError as e:
447
- raise BSBLANConnectionError(BSBLANConnectionError.message_timeout) from e
448
491
  except aiohttp.ClientResponseError as e:
449
492
  if e.status in (401, 403):
450
493
  raise BSBLANAuthError from e
451
- raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
452
- except aiohttp.ClientError as e:
453
- raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
494
+ raise
454
495
  except (ValueError, UnicodeDecodeError) as e:
455
496
  # Handle JSON decode errors and other parsing issues
456
497
  error_msg = f"Invalid response format from BSB-LAN device: {e!s}"
@@ -589,9 +630,8 @@ class BSBLAN:
589
630
  State: The current state of the BSBLAN device.
590
631
 
591
632
  Note:
592
- The hvac_mode.value is returned as a raw integer from the device.
593
- Use HVAC_MODE_DICT from bsblan.constants to convert to string
594
- representation (e.g., "off", "auto", "eco", "heat").
633
+ The hvac_mode.value is returned as a raw integer from the device:
634
+ 0=off, 1=auto, 2=eco, 3=heat.
595
635
 
596
636
  """
597
637
  return await self._fetch_section_data("heating", State)
@@ -677,8 +717,6 @@ class BSBLAN:
677
717
  target_temperature (str | None): The target temperature to set.
678
718
  hvac_mode (int | None): The HVAC mode to set as raw integer value.
679
719
  Valid values: 0=off, 1=auto, 2=eco, 3=heat.
680
- Use HVAC_MODE_DICT_REVERSE from bsblan.constants to convert
681
- string names to integers.
682
720
 
683
721
  """
684
722
  await self._initialize_temperature_range()
@@ -930,6 +968,50 @@ class BSBLAN:
930
968
  state = self._prepare_hot_water_state(params)
931
969
  await self._set_device_state(state)
932
970
 
971
+ async def set_hot_water_schedule(self, schedule: DHWSchedule) -> None:
972
+ """Set hot water time program schedules.
973
+
974
+ This method allows setting weekly DHW schedules using a type-safe
975
+ interface with TimeSlot and DaySchedule objects.
976
+
977
+ Example:
978
+ schedule = DHWSchedule(
979
+ monday=DaySchedule(slots=[
980
+ TimeSlot(time(6, 0), time(8, 0)),
981
+ TimeSlot(time(17, 0), time(21, 0)),
982
+ ]),
983
+ tuesday=DaySchedule(slots=[
984
+ TimeSlot(time(6, 0), time(8, 0)),
985
+ ])
986
+ )
987
+ await client.set_hot_water_schedule(schedule)
988
+
989
+ Args:
990
+ schedule: DHWSchedule object containing the weekly schedule.
991
+
992
+ Raises:
993
+ BSBLANError: If no schedule is provided.
994
+
995
+ """
996
+ if not schedule.has_any_schedule():
997
+ raise BSBLANError(NO_SCHEDULE_ERROR_MSG)
998
+
999
+ # Invert DHW_TIME_PROGRAM_PARAMS to get day_name -> param_id mapping
1000
+ # Exclude standard_values as it's not a day of the week
1001
+ day_param_map = {
1002
+ v: k for k, v in DHW_TIME_PROGRAM_PARAMS.items() if v != "standard_values"
1003
+ }
1004
+
1005
+ for day_name, param_id in day_param_map.items():
1006
+ day_schedule: DaySchedule | None = getattr(schedule, day_name)
1007
+ if day_schedule is not None:
1008
+ state = {
1009
+ "Parameter": param_id,
1010
+ "Value": day_schedule.to_bsblan_format(),
1011
+ "Type": "1",
1012
+ }
1013
+ await self._set_device_state(state)
1014
+
933
1015
  def _prepare_hot_water_state(
934
1016
  self,
935
1017
  params: SetHotWaterParam,
@@ -133,6 +133,7 @@ VALID_HVAC_MODES: Final[set[int]] = {0, 1, 2, 3}
133
133
 
134
134
  # Error Messages
135
135
  NO_STATE_ERROR_MSG: Final[str] = "No state provided."
136
+ NO_SCHEDULE_ERROR_MSG: Final[str] = "No schedule provided."
136
137
  VERSION_ERROR_MSG: Final[str] = "Version not supported"
137
138
  FIRMWARE_VERSION_ERROR_MSG: Final[str] = "Firmware version not available"
138
139
  TEMPERATURE_RANGE_ERROR_MSG: Final[str] = "Temperature range not initialized"