python-bsblan 3.1.6__tar.gz → 4.0.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-3.1.6 → python_bsblan-4.0.0}/.github/copilot-instructions.md +48 -0
- python_bsblan-4.0.0/.github/prompts/add-parameter.prompt.md +32 -0
- python_bsblan-4.0.0/.github/prompts/code-review.prompt.md +40 -0
- python_bsblan-4.0.0/.github/skills/bsblan-parameters/SKILL.md +107 -0
- python_bsblan-4.0.0/.github/skills/bsblan-testing/SKILL.md +135 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/codeql.yaml +2 -2
- python_bsblan-4.0.0/.github/workflows/release-drafter.yaml +37 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/PKG-INFO +2 -1
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/examples/control.py +12 -5
- python_bsblan-4.0.0/examples/discovery.py +266 -0
- python_bsblan-4.0.0/examples/profile_init.py +422 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/pyproject.toml +4 -3
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/bsblan.py +209 -22
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_api_validation.py +190 -1
- python_bsblan-4.0.0/tests/test_hot_water_additional.py +573 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_hotwater_state.py +3 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_initialization.py +57 -17
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_state.py +1 -1
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_temperature_validation.py +19 -9
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/uv.lock +286 -264
- python_bsblan-3.1.6/.github/workflows/release-drafter.yaml +0 -19
- python_bsblan-3.1.6/tests/test_hot_water_additional.py +0 -289
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.editorconfig +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.gitattributes +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/labels.yml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/release-drafter.yml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/renovate.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/auto-approve-renovate.yml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/labels.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/linting.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/release.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/stale.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/tests.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.github/workflows/typing.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.gitignore +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.nvmrc +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.pre-commit-config.yaml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.prettierignore +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/.yamllint +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/AGENTS.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/CLAUDE.md +0 -0
- {python_bsblan-3.1.6/.github → python_bsblan-4.0.0}/LICENSE.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/README.md +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/examples/ruff.toml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/package-lock.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/package.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/sonar-project.properties +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/__init__.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/constants.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/models.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/py.typed +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/src/bsblan/utility.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/__init__.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/conftest.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/device.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/info.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/password.txt +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/state.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/thermostat_hvac.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/fixtures/time.json +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/ruff.toml +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_api_initialization.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_auth.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_backoff_retry.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_bsblan.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_configuration.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_constants.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_context_manager.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_device.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_entity_info.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_info.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_reset_validation.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_schedule_models.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_sensor.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_set_hot_water_schedule.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_set_hotwater.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_static_state.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_thermostat.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_time.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility_additional.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-3.1.6 → python_bsblan-4.0.0}/tests/test_version_errors.py +0 -0
|
@@ -111,6 +111,21 @@ Parameters are organized into polling categories based on how frequently they ch
|
|
|
111
111
|
- Device identification
|
|
112
112
|
- Min/max temperature limits
|
|
113
113
|
|
|
114
|
+
## Hot Water Parameter Groups
|
|
115
|
+
|
|
116
|
+
Hot water parameters are split into groups for granular lazy loading:
|
|
117
|
+
|
|
118
|
+
| Group | Params | Method | Use Case |
|
|
119
|
+
|-------|--------|--------|----------|
|
|
120
|
+
| essential | 5 | `hot_water_state()` | Frequent polling |
|
|
121
|
+
| config | 16 | `hot_water_config()` | Advanced settings |
|
|
122
|
+
| schedule | 8 | `hot_water_schedule()` | Time programs |
|
|
123
|
+
|
|
124
|
+
Defined in `constants.py`:
|
|
125
|
+
- `HOT_WATER_ESSENTIAL_PARAMS` - operating_mode, nominal_setpoint, etc.
|
|
126
|
+
- `HOT_WATER_CONFIG_PARAMS` - legionella settings, eco mode, etc.
|
|
127
|
+
- `HOT_WATER_SCHEDULE_PARAMS` - daily time programs
|
|
128
|
+
|
|
114
129
|
## Data Models
|
|
115
130
|
|
|
116
131
|
### Model Pattern
|
|
@@ -143,6 +158,39 @@ async with BSBLAN(host="192.168.1.100") as client:
|
|
|
143
158
|
await client.set_hot_water(nominal_setpoint=55.0)
|
|
144
159
|
```
|
|
145
160
|
|
|
161
|
+
### Lazy Loading Architecture
|
|
162
|
+
The library uses lazy loading for optimal performance:
|
|
163
|
+
- **Initialization**: Only fetches firmware version (fast startup)
|
|
164
|
+
- **Section validation**: Deferred until section is first accessed
|
|
165
|
+
- **Hot water granular loading**: Each method validates only its param group
|
|
166
|
+
- **Race condition prevention**: Per-section/group asyncio locks
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# Initialize() is fast - only fetches firmware
|
|
170
|
+
await client.initialize() # ~0.02s
|
|
171
|
+
|
|
172
|
+
# Section validated on first access
|
|
173
|
+
await client.state() # Validates heating section on first call
|
|
174
|
+
|
|
175
|
+
# Hot water methods validate only their param groups:
|
|
176
|
+
await client.hot_water_state() # 5 essential params only
|
|
177
|
+
await client.hot_water_config() # 16 config params only
|
|
178
|
+
await client.hot_water_schedule() # 8 schedule params only
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Concurrency & Locking
|
|
182
|
+
The library uses asyncio locks to prevent race conditions during lazy loading:
|
|
183
|
+
- `_section_locks`: Per-section locks (heating, sensor, etc.)
|
|
184
|
+
- `_hot_water_group_locks`: Per-group locks (essential, config, schedule)
|
|
185
|
+
|
|
186
|
+
Double-checked locking pattern:
|
|
187
|
+
1. Fast path: Check if validated (no lock)
|
|
188
|
+
2. Acquire lock for specific section/group
|
|
189
|
+
3. Double-check after acquiring lock
|
|
190
|
+
4. Perform validation inside the lock
|
|
191
|
+
|
|
192
|
+
This prevents duplicate network requests when concurrent calls access the same section before validation completes.
|
|
193
|
+
|
|
146
194
|
### Error Handling
|
|
147
195
|
- Use `BSBLANError` for general errors
|
|
148
196
|
- Use `BSBLANConnectionError` for connection issues
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: agent
|
|
3
|
+
description: Add a new BSB-LAN parameter to the library
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add BSB-LAN Parameter
|
|
7
|
+
|
|
8
|
+
Add a new parameter to the python-bsblan library following the established patterns.
|
|
9
|
+
|
|
10
|
+
## Required Information
|
|
11
|
+
|
|
12
|
+
- Parameter ID (numeric, e.g., "1645")
|
|
13
|
+
- Parameter name (snake_case, e.g., "legionella_function_setpoint")
|
|
14
|
+
- Category: state (fast poll), config (slow poll), or static
|
|
15
|
+
- Is it settable? (yes/no)
|
|
16
|
+
- Data type (float, int, string, enum)
|
|
17
|
+
|
|
18
|
+
## Files to Modify
|
|
19
|
+
|
|
20
|
+
1. `src/bsblan/constants.py` - Add parameter ID mapping
|
|
21
|
+
2. `src/bsblan/models.py` - Add field to appropriate model
|
|
22
|
+
3. `src/bsblan/bsblan.py` - Add to setter method if settable
|
|
23
|
+
4. `tests/test_*.py` - Add tests for the new parameter
|
|
24
|
+
|
|
25
|
+
## Validation
|
|
26
|
+
|
|
27
|
+
After changes, run:
|
|
28
|
+
```bash
|
|
29
|
+
uv run pre-commit run --all-files
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Coverage must be 95%+ total and 100% for new code.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: agent
|
|
3
|
+
description: Review code changes for python-bsblan library
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Code Review Checklist
|
|
7
|
+
|
|
8
|
+
Review the changes against the python-bsblan coding standards.
|
|
9
|
+
|
|
10
|
+
## Checklist
|
|
11
|
+
|
|
12
|
+
### Code Quality
|
|
13
|
+
- [ ] Type hints on all functions
|
|
14
|
+
- [ ] Docstrings on public methods
|
|
15
|
+
- [ ] Line length under 88 characters
|
|
16
|
+
- [ ] Consistent parameter naming (snake_case)
|
|
17
|
+
|
|
18
|
+
### Testing
|
|
19
|
+
- [ ] Tests added for new functionality
|
|
20
|
+
- [ ] Test coverage 95%+ total
|
|
21
|
+
- [ ] Patch coverage 100%
|
|
22
|
+
|
|
23
|
+
### Patterns
|
|
24
|
+
- [ ] Uses `mashumaro` for JSON serialization
|
|
25
|
+
- [ ] Uses `aiohttp` for async HTTP
|
|
26
|
+
- [ ] Follows existing parameter naming conventions
|
|
27
|
+
- [ ] Error handling uses custom exceptions (`BSBLANError`, `BSBLANConnectionError`)
|
|
28
|
+
|
|
29
|
+
### Pre-commit
|
|
30
|
+
- [ ] Ruff passes (linting + formatting)
|
|
31
|
+
- [ ] MyPy passes (type checking)
|
|
32
|
+
- [ ] Pylint passes (code analysis)
|
|
33
|
+
- [ ] Pytest passes (tests)
|
|
34
|
+
|
|
35
|
+
## Run Validation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv run pre-commit run --all-files
|
|
39
|
+
uv run pytest --cov=src/bsblan --cov-report=term-missing
|
|
40
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bsblan-parameters
|
|
3
|
+
description: Add new BSB-LAN parameters to the python-bsblan library. Use this skill when adding parameter IDs, updating models, or extending the API to support new heating controller parameters.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding BSB-LAN Parameters
|
|
7
|
+
|
|
8
|
+
This skill guides you through adding new parameters to the python-bsblan library.
|
|
9
|
+
|
|
10
|
+
## Parameter Naming Conventions
|
|
11
|
+
|
|
12
|
+
- Use `snake_case` for all parameter names
|
|
13
|
+
- Group related parameters with common prefixes
|
|
14
|
+
- Legionella-related parameters use `legionella_function_*` prefix
|
|
15
|
+
- DHW (Domestic Hot Water) parameters use `dhw_*` prefix
|
|
16
|
+
|
|
17
|
+
## Steps to Add a New Parameter
|
|
18
|
+
|
|
19
|
+
### 1. Add to `constants.py`
|
|
20
|
+
|
|
21
|
+
Add the parameter ID mapping:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
BASE_HOT_WATER_PARAMS: Final[dict[str, str]] = {
|
|
25
|
+
"1645": "legionella_function_setpoint", # Parameter ID: name
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Add to Model in `models.py`
|
|
30
|
+
|
|
31
|
+
Add the field to the appropriate dataclass:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
@dataclass
|
|
35
|
+
class HotWaterConfig(DataClassORJSONMixin):
|
|
36
|
+
legionella_function_setpoint: ParameterValue | None = None
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Update Method in `bsblan.py` (if settable)
|
|
40
|
+
|
|
41
|
+
Add parameter to the method signature:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
async def set_hot_water(
|
|
45
|
+
self,
|
|
46
|
+
legionella_function_setpoint: float | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 4. Add Tests
|
|
51
|
+
|
|
52
|
+
Create tests in `tests/test_*.py`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
57
|
+
"""Test setting BSBLAN hot water state."""
|
|
58
|
+
await mock_bsblan.set_hot_water(nominal_setpoint=60.0)
|
|
59
|
+
mock_bsblan._request.assert_awaited_with(
|
|
60
|
+
base_path="/JS",
|
|
61
|
+
data={"Parameter": "1610", "Value": "60.0", "Type": "1"},
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Polling Categories
|
|
66
|
+
|
|
67
|
+
Parameters are organized by update frequency:
|
|
68
|
+
|
|
69
|
+
- **Fast Poll (State)**: Current temperatures, HVAC action/state, pump states
|
|
70
|
+
- **Slow Poll (Config)**: Operating modes, setpoints, legionella settings, time programs
|
|
71
|
+
- **Static**: Device identification, min/max temperature limits
|
|
72
|
+
|
|
73
|
+
## Hot Water Parameter Groups
|
|
74
|
+
|
|
75
|
+
Hot water parameters use granular lazy loading. When adding a new hot water param, add it to the appropriate group in `constants.py`:
|
|
76
|
+
|
|
77
|
+
| Group | Constant | Method |
|
|
78
|
+
| -------------------- | ----------------------------- | ---------------------- |
|
|
79
|
+
| Essential (5 params) | `HOT_WATER_ESSENTIAL_PARAMS` | `hot_water_state()` |
|
|
80
|
+
| Config (16 params) | `HOT_WATER_CONFIG_PARAMS` | `hot_water_config()` |
|
|
81
|
+
| Schedule (8 params) | `HOT_WATER_SCHEDULE_PARAMS` | `hot_water_schedule()` |
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# In constants.py - add to appropriate group set:
|
|
85
|
+
HOT_WATER_ESSENTIAL_PARAMS: Final[set[str]] = {"1600", "1610", ...}
|
|
86
|
+
HOT_WATER_CONFIG_PARAMS: Final[set[str]] = {"1601", "1614", ...}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Concurrency Safety
|
|
90
|
+
|
|
91
|
+
The library uses asyncio locks to prevent race conditions:
|
|
92
|
+
|
|
93
|
+
- `_section_locks`: Per-section locks for lazy loading
|
|
94
|
+
- `_hot_water_group_locks`: Per-group locks for hot water validation
|
|
95
|
+
|
|
96
|
+
When adding new sections or groups, the lock is created automatically on first access.
|
|
97
|
+
|
|
98
|
+
## Validation
|
|
99
|
+
|
|
100
|
+
Always run after changes:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
uv run pre-commit run --all-files
|
|
104
|
+
uv run pytest --cov=src/bsblan --cov-report=term-missing
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Coverage requirements**: 95%+ total, 100% patch coverage.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bsblan-testing
|
|
3
|
+
description: Write and run tests for the python-bsblan library. Use this skill when creating unit tests, working with fixtures, or ensuring code coverage requirements are met.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testing python-bsblan
|
|
7
|
+
|
|
8
|
+
This skill guides you through testing practices for the python-bsblan library.
|
|
9
|
+
|
|
10
|
+
## Test Structure
|
|
11
|
+
|
|
12
|
+
Tests are located in `tests/` and use pytest with async support.
|
|
13
|
+
|
|
14
|
+
### Basic Test Pattern
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import pytest
|
|
18
|
+
from bsblan import BSBLAN
|
|
19
|
+
|
|
20
|
+
@pytest.mark.asyncio
|
|
21
|
+
async def test_feature_name(mock_bsblan: BSBLAN) -> None:
|
|
22
|
+
"""Test description."""
|
|
23
|
+
# Arrange
|
|
24
|
+
expected_value = "expected"
|
|
25
|
+
|
|
26
|
+
# Act
|
|
27
|
+
result = await mock_bsblan.some_method()
|
|
28
|
+
|
|
29
|
+
# Assert
|
|
30
|
+
assert result == expected_value
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Using Fixtures
|
|
34
|
+
|
|
35
|
+
Test fixtures (JSON responses) are in `tests/fixtures/`. Common fixtures:
|
|
36
|
+
|
|
37
|
+
- `device.json` - Device information
|
|
38
|
+
- `state.json` - Current state
|
|
39
|
+
- `hot_water_state.json` - Hot water state
|
|
40
|
+
- `sensor.json` - Sensor readings
|
|
41
|
+
|
|
42
|
+
Load fixtures using the `load_fixture` helper from `conftest.py`.
|
|
43
|
+
|
|
44
|
+
## Coverage Requirements
|
|
45
|
+
|
|
46
|
+
- **Total coverage**: 95%+ required
|
|
47
|
+
- **Patch coverage**: 100% required (all new/modified code must be tested)
|
|
48
|
+
|
|
49
|
+
Check coverage:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run pytest --cov=src/bsblan --cov-report=term-missing
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Running Tests
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Run all tests
|
|
59
|
+
uv run pytest
|
|
60
|
+
|
|
61
|
+
# Run specific test file
|
|
62
|
+
uv run pytest tests/test_bsblan.py
|
|
63
|
+
|
|
64
|
+
# Run with verbose output
|
|
65
|
+
uv run pytest -v
|
|
66
|
+
|
|
67
|
+
# Run specific test
|
|
68
|
+
uv run pytest tests/test_bsblan.py::test_function_name
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Pre-commit Hooks
|
|
72
|
+
|
|
73
|
+
Always run before committing:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv run pre-commit run --all-files
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This runs:
|
|
80
|
+
|
|
81
|
+
- **Ruff**: Linting and formatting (88 char line limit)
|
|
82
|
+
- **MyPy**: Static type checking
|
|
83
|
+
- **Pylint**: Code analysis
|
|
84
|
+
- **Pytest**: Test execution with coverage
|
|
85
|
+
|
|
86
|
+
## Mock Patterns
|
|
87
|
+
|
|
88
|
+
For API calls, use `mock_bsblan` fixture and verify calls:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
mock_bsblan._request.assert_awaited_with(
|
|
92
|
+
base_path="/JS",
|
|
93
|
+
data={"Parameter": "1610", "Value": "60.0", "Type": "1"},
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Testing Lazy Loading
|
|
98
|
+
|
|
99
|
+
When testing hot water methods, mark param groups as validated to skip network calls:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
@pytest.mark.asyncio
|
|
103
|
+
async def test_hot_water_no_params_error(monkeypatch: Any) -> None:
|
|
104
|
+
"""Test error when no parameters available."""
|
|
105
|
+
bsblan = BSBLAN(config, session=session)
|
|
106
|
+
|
|
107
|
+
# Set empty cache and mark group as validated
|
|
108
|
+
bsblan.set_hot_water_cache({})
|
|
109
|
+
bsblan._validated_hot_water_groups.add("essential") # Skip validation
|
|
110
|
+
|
|
111
|
+
with pytest.raises(BSBLANError, match="No essential hot water"):
|
|
112
|
+
await bsblan.hot_water_state()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For full integration tests with mocked responses:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Mark group as validated to use cached params
|
|
119
|
+
bsblan._validated_hot_water_groups.add("config")
|
|
120
|
+
bsblan.set_hot_water_cache({"1601": "eco_mode_selection", ...})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Testing Concurrent Access
|
|
124
|
+
|
|
125
|
+
The library uses asyncio locks for race condition prevention. When testing:
|
|
126
|
+
|
|
127
|
+
- Locks are created per-section/group automatically
|
|
128
|
+
- Access `_section_locks` and `_hot_water_group_locks` dicts if needed
|
|
129
|
+
- The double-checked locking pattern prevents duplicate validations
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Locks are stored in these dictionaries:
|
|
133
|
+
bsblan._section_locks # {"heating": Lock(), "sensor": Lock(), ...}
|
|
134
|
+
bsblan._hot_water_group_locks # {"essential": Lock(), ...}
|
|
135
|
+
```
|
|
@@ -19,6 +19,6 @@ jobs:
|
|
|
19
19
|
- name: ⤵️ Check out code from GitHub
|
|
20
20
|
uses: actions/checkout@v6.0.1
|
|
21
21
|
- name: 🏗 Initialize CodeQL
|
|
22
|
-
uses: github/codeql-action/init@
|
|
22
|
+
uses: github/codeql-action/init@v4.31.9
|
|
23
23
|
- name: 🚀 Perform CodeQL Analysis
|
|
24
|
-
uses: github/codeql-action/analyze@
|
|
24
|
+
uses: github/codeql-action/analyze@v4.31.9
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Release Drafter
|
|
3
|
+
|
|
4
|
+
# yamllint disable-line rule:truthy
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
prerelease:
|
|
12
|
+
description: "Create a pre-release (beta)"
|
|
13
|
+
required: false
|
|
14
|
+
type: boolean
|
|
15
|
+
default: false
|
|
16
|
+
prerelease_identifier:
|
|
17
|
+
description: "Pre-release identifier"
|
|
18
|
+
required: false
|
|
19
|
+
type: choice
|
|
20
|
+
default: "beta"
|
|
21
|
+
options:
|
|
22
|
+
- beta
|
|
23
|
+
- alpha
|
|
24
|
+
- rc
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
update_release_draft:
|
|
28
|
+
name: ✏️ Draft release
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: 🚀 Run Release Drafter
|
|
32
|
+
uses: release-drafter/release-drafter@v6.1.0
|
|
33
|
+
with:
|
|
34
|
+
prerelease: ${{ github.event.inputs.prerelease == 'true' }}
|
|
35
|
+
prerelease-identifier: ${{ github.event.inputs.prerelease_identifier }}
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-bsblan
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
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
|
|
@@ -10,6 +10,7 @@ Project-URL: Changelog, https://github.com/liudger/python-bsblan/releases
|
|
|
10
10
|
Author-email: Willem-Jan van Rootselaar <liudgervr@gmail.com>
|
|
11
11
|
Maintainer-email: Willem-Jan van Rootselaar <liudgervr@gmail.com>
|
|
12
12
|
License: MIT
|
|
13
|
+
License-File: LICENSE.md
|
|
13
14
|
Keywords: api,async,bsblan,client,thermostat
|
|
14
15
|
Classifier: Development Status :: 3 - Alpha
|
|
15
16
|
Classifier: Framework :: AsyncIO
|
|
@@ -12,7 +12,6 @@ This three-tier approach reduces API calls by 79% for regular monitoring.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import asyncio
|
|
15
|
-
import os
|
|
16
15
|
from datetime import datetime
|
|
17
16
|
from typing import Any
|
|
18
17
|
|
|
@@ -33,6 +32,7 @@ from bsblan import (
|
|
|
33
32
|
get_hvac_action_category,
|
|
34
33
|
)
|
|
35
34
|
from bsblan.models import DHWTimeSwitchPrograms
|
|
35
|
+
from discovery import get_bsblan_host, get_config_from_env
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
async def get_attribute(
|
|
@@ -302,12 +302,19 @@ async def print_hot_water_schedule(hot_water_schedule: HotWaterSchedule) -> None
|
|
|
302
302
|
|
|
303
303
|
async def main() -> None:
|
|
304
304
|
"""Show example on controlling your BSBLan device."""
|
|
305
|
+
# Get host from environment variable or mDNS discovery
|
|
306
|
+
host, port = await get_bsblan_host()
|
|
307
|
+
|
|
308
|
+
# Get credentials from environment
|
|
309
|
+
env_config = get_config_from_env()
|
|
310
|
+
|
|
305
311
|
# Create a configuration object
|
|
306
312
|
config = BSBLANConfig(
|
|
307
|
-
host=
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
313
|
+
host=host,
|
|
314
|
+
port=port,
|
|
315
|
+
passkey=env_config.get("passkey"), # type: ignore[arg-type]
|
|
316
|
+
username=env_config.get("username"), # type: ignore[arg-type]
|
|
317
|
+
password=env_config.get("password"), # type: ignore[arg-type]
|
|
311
318
|
)
|
|
312
319
|
|
|
313
320
|
# Initialize BSBLAN with the configuration object
|