pycaldera 0.1.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.
@@ -0,0 +1,18 @@
1
+ Changelog
2
+ =========
3
+
4
+ All notable changes to package_name will be documented here.
5
+
6
+ The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_.
7
+
8
+ .. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
9
+ .. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
10
+
11
+ Categories for changes are: Added, Changed, Deprecated, Removed, Fixed, Security.
12
+
13
+
14
+ Version `0.1.0 <https://github.com/your_organisation/package_name/tree/0.1.0>`__
15
+ --------------------------------------------------------------------------------
16
+
17
+ Release date: YYYY-MM-DD.
18
+ Initial release (to be released).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mark Watson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ include CHANGELOG.rst
2
+ include LICEN[CS]E*
3
+ include pyproject.toml
4
+ include README.rst
5
+ include requirements*.txt
6
+
7
+ global-exclude *.py[co]
8
+ global-exclude __pycache__
9
+ global-exclude *~
10
+ global-exclude *.ipynb_checkpoints/*
@@ -0,0 +1,296 @@
1
+ Metadata-Version: 2.2
2
+ Name: pycaldera
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python client for Caldera Spa API
5
+ Home-page: https://github.com/mwatson2/pycaldera
6
+ Author: Mark Watson
7
+ Author-email: markwatson@cantab.net
8
+ License: MIT
9
+ Classifier: Natural Language :: English
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: aiohttp>=3.8.0
21
+ Requires-Dist: black>=23.0.0
22
+ Requires-Dist: isort>=5.12.0
23
+ Requires-Dist: mypy>=1.0.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: pylint>=2.17.0
26
+ Requires-Dist: pytest>=7.0.0
27
+ Requires-Dist: pytest-aiohttp>=1.0.0
28
+ Requires-Dist: pytest-asyncio>=0.21.0
29
+ Requires-Dist: pytest-cov>=4.0.0
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest; extra == "test"
32
+ Requires-Dist: pytest-cov; extra == "test"
33
+ Requires-Dist: pytest-flake8; extra == "test"
34
+ Provides-Extra: docs
35
+ Requires-Dist: myst-parser; extra == "docs"
36
+ Requires-Dist: pypandoc>=1.15; extra == "docs"
37
+ Requires-Dist: readthedocs-sphinx-search; python_version >= "3.6" and extra == "docs"
38
+ Requires-Dist: sphinx<6,>=3.5.4; extra == "docs"
39
+ Requires-Dist: sphinx-autobuild; extra == "docs"
40
+ Requires-Dist: sphinx_book_theme; extra == "docs"
41
+ Requires-Dist: watchdog<1.0.0; python_version < "3.6" and extra == "docs"
42
+ Provides-Extra: dev
43
+ Requires-Dist: black>=23.0.0; extra == "dev"
44
+ Requires-Dist: isort>=5.12.0; extra == "dev"
45
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
46
+ Requires-Dist: pylint>=2.17.0; extra == "dev"
47
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
48
+ Requires-Dist: pytest-aiohttp>=1.0.0; extra == "dev"
49
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
50
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
51
+ Provides-Extra: all
52
+ Requires-Dist: black>=23.0.0; extra == "all"
53
+ Requires-Dist: isort>=5.12.0; extra == "all"
54
+ Requires-Dist: mypy>=1.0.0; extra == "all"
55
+ Requires-Dist: myst-parser; extra == "all"
56
+ Requires-Dist: pylint>=2.17.0; extra == "all"
57
+ Requires-Dist: pypandoc>=1.15; extra == "all"
58
+ Requires-Dist: pytest; extra == "all"
59
+ Requires-Dist: pytest-aiohttp>=1.0.0; extra == "all"
60
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
61
+ Requires-Dist: pytest-cov; extra == "all"
62
+ Requires-Dist: pytest-cov>=4.0.0; extra == "all"
63
+ Requires-Dist: pytest-flake8; extra == "all"
64
+ Requires-Dist: pytest>=7.0.0; extra == "all"
65
+ Requires-Dist: readthedocs-sphinx-search; python_version >= "3.6" and extra == "all"
66
+ Requires-Dist: sphinx-autobuild; extra == "all"
67
+ Requires-Dist: sphinx<6,>=3.5.4; extra == "all"
68
+ Requires-Dist: sphinx_book_theme; extra == "all"
69
+ Requires-Dist: watchdog<1.0.0; python_version < "3.6" and extra == "all"
70
+ Dynamic: author
71
+ Dynamic: author-email
72
+ Dynamic: classifier
73
+ Dynamic: description
74
+ Dynamic: description-content-type
75
+ Dynamic: home-page
76
+ Dynamic: license
77
+ Dynamic: provides-extra
78
+ Dynamic: requires-dist
79
+ Dynamic: requires-python
80
+ Dynamic: summary
81
+
82
+ # pycaldera
83
+
84
+ Python client library for controlling Caldera spas via their cloud API.
85
+
86
+ ## Installation
87
+
88
+ ```bash
89
+ pip install pycaldera
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ ### Asynchronous API
95
+
96
+ ```python
97
+ import asyncio
98
+ from pycaldera import AsyncCalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
99
+
100
+
101
+ async def main():
102
+ async with AsyncCalderaClient("email@example.com", "password") as spa:
103
+ # Get current spa status
104
+ status = await spa.get_spa_status()
105
+ print(f"Current temperature: {status.ctrl_head_water_temperature}°F")
106
+
107
+ # Get detailed live settings
108
+ settings = await spa.get_live_settings()
109
+ print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
110
+
111
+ # Control the spa
112
+ await spa.set_temperature(102) # Set temperature to 102°F
113
+ await spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
114
+ await spa.set_lights(True) # Turn on the lights
115
+
116
+
117
+ asyncio.run(main())
118
+ ```
119
+
120
+ ### Synchronous API
121
+
122
+ For simpler use cases, a synchronous wrapper is also available:
123
+
124
+ ```python
125
+ from pycaldera import CalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
126
+
127
+ with CalderaClient("email@example.com", "password") as spa:
128
+ # Get current spa status
129
+ status = spa.get_spa_status()
130
+ print(f"Current temperature: {status.ctrl_head_water_temperature}°F")
131
+
132
+ # Get detailed live settings
133
+ settings = spa.get_live_settings()
134
+ print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
135
+
136
+ # Control the spa
137
+ spa.set_temperature(102) # Set temperature to 102°F
138
+ spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
139
+ spa.set_lights(True) # Turn on the lights
140
+ ```
141
+
142
+ Both clients provide identical functionality, with the synchronous client simply wrapping the async one for convenience.
143
+
144
+ ## API Reference
145
+
146
+ ### AsyncCalderaClient
147
+
148
+ The main async client class for interacting with the spa. All operations must be performed within an async context manager:
149
+
150
+ ```python
151
+ async with AsyncCalderaClient(
152
+ email="email@example.com",
153
+ password="password",
154
+ timeout=10.0, # Optional: request timeout in seconds
155
+ debug=False, # Optional: enable debug logging
156
+ ) as spa:
157
+ # All spa operations must be inside this block
158
+ await spa.get_spa_status()
159
+ await spa.set_temperature(102)
160
+ # etc...
161
+ ```
162
+
163
+ ### CalderaClient
164
+
165
+ A synchronous wrapper around AsyncCalderaClient that provides the same functionality without requiring async/await:
166
+
167
+ ```python
168
+ with CalderaClient(
169
+ email="email@example.com",
170
+ password="password",
171
+ timeout=10.0, # Optional: request timeout in seconds
172
+ debug=False, # Optional: enable debug logging
173
+ ) as spa:
174
+ # All spa operations can be called synchronously
175
+ spa.get_spa_status()
176
+ spa.set_temperature(102)
177
+ # etc...
178
+ ```
179
+
180
+ ### Error Handling
181
+
182
+ All operations can raise these base exceptions:
183
+ - `AuthenticationError`: When authentication fails or token expires
184
+ - `ConnectionError`: When network connection fails or API is unreachable
185
+ - `SpaControlError`: When the API returns an error response
186
+
187
+ ### Temperature Control
188
+
189
+ ```python
190
+ async with spa as client:
191
+ # Set temperature (80-104°F or 26.5-40°C)
192
+ try:
193
+ # Basic temperature setting
194
+ await client.set_temperature(102) # Fahrenheit
195
+ await client.set_temperature(39, "C") # Celsius
196
+
197
+ # Wait for spa to acknowledge the temperature change
198
+ await client.set_temperature(102, wait_for_ack=True)
199
+
200
+ # Control polling behavior when waiting for acknowledgment
201
+ await client.set_temperature(
202
+ 102,
203
+ wait_for_ack=True,
204
+ polling_interval=5.0, # Check every 5 seconds
205
+ polling_timeout=120.0, # Time out after 2 minutes
206
+ )
207
+
208
+ # Manually wait for temperature acknowledgment
209
+ settings = await client.wait_for_temperature_ack(
210
+ expected_temp=102, # Expected temperature in Fahrenheit
211
+ interval=5.0, # Check every 5 seconds
212
+ timeout=120.0, # Time out after 2 minutes
213
+ )
214
+ except InvalidParameterError:
215
+ # Raised when temperature is outside valid range
216
+ # (80-104°F or 26.5-40°C)
217
+ pass
218
+ except SpaControlError:
219
+ # Raised when polling times out waiting for acknowledgment
220
+ pass
221
+ ```
222
+
223
+ ### Pump Control
224
+
225
+ ```python
226
+ async with spa as client:
227
+ try:
228
+ await client.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
229
+ await client.set_pump(2, PUMP_LOW) # Set pump 2 to low speed
230
+ await client.set_pump(3, PUMP_OFF) # Turn off pump 3
231
+ except InvalidParameterError:
232
+ # Raised when:
233
+ # - pump_number is not 1, 2, or 3
234
+ # - speed is not PUMP_OFF (0), PUMP_LOW (1), or PUMP_HIGH (2)
235
+ pass
236
+ ```
237
+
238
+ ### Light Control
239
+
240
+ ```python
241
+ async with spa as client:
242
+ try:
243
+ await client.set_lights(True) # Turn lights on
244
+ await client.set_lights(False) # Turn lights off
245
+ except SpaControlError:
246
+ # Raised when light control fails
247
+ pass
248
+ ```
249
+
250
+ ### Status & Settings
251
+
252
+ ```python
253
+ async with spa as client:
254
+ try:
255
+ # Get basic spa status
256
+ status = await client.get_spa_status()
257
+ print(f"Online: {status.status == 'ONLINE'}")
258
+
259
+ # Get detailed live settings
260
+ settings = await client.get_live_settings()
261
+ print(f"Target temp: {settings.ctrl_head_set_temperature}°F")
262
+ except ConnectionError:
263
+ # Raised when spa is offline or unreachable
264
+ pass
265
+ except SpaControlError:
266
+ # Raised when API returns invalid data
267
+ pass
268
+
269
+ ## Development
270
+
271
+ 1. Clone the repository
272
+ 2. Create a virtual environment:
273
+ ```bash
274
+ python -m venv venv
275
+ source venv/bin/activate # or `venv\Scripts\activate` on Windows
276
+ ```
277
+ 3. Install development dependencies:
278
+ ```bash
279
+ pip install -r requirements-dev.txt
280
+ ```
281
+ 4. Install pre-commit hooks:
282
+ ```bash
283
+ pre-commit install
284
+ ```
285
+
286
+ The pre-commit hooks will run automatically on git commit, checking:
287
+ - Code formatting (Black)
288
+ - Import sorting (isort)
289
+ - Type checking (mypy)
290
+ - Linting (pylint, ruff)
291
+ - YAML/TOML syntax
292
+ - Trailing whitespace and file endings
293
+
294
+ ## License
295
+
296
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,215 @@
1
+ # pycaldera
2
+
3
+ Python client library for controlling Caldera spas via their cloud API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install pycaldera
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Asynchronous API
14
+
15
+ ```python
16
+ import asyncio
17
+ from pycaldera import AsyncCalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
18
+
19
+
20
+ async def main():
21
+ async with AsyncCalderaClient("email@example.com", "password") as spa:
22
+ # Get current spa status
23
+ status = await spa.get_spa_status()
24
+ print(f"Current temperature: {status.ctrl_head_water_temperature}°F")
25
+
26
+ # Get detailed live settings
27
+ settings = await spa.get_live_settings()
28
+ print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
29
+
30
+ # Control the spa
31
+ await spa.set_temperature(102) # Set temperature to 102°F
32
+ await spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
33
+ await spa.set_lights(True) # Turn on the lights
34
+
35
+
36
+ asyncio.run(main())
37
+ ```
38
+
39
+ ### Synchronous API
40
+
41
+ For simpler use cases, a synchronous wrapper is also available:
42
+
43
+ ```python
44
+ from pycaldera import CalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
45
+
46
+ with CalderaClient("email@example.com", "password") as spa:
47
+ # Get current spa status
48
+ status = spa.get_spa_status()
49
+ print(f"Current temperature: {status.ctrl_head_water_temperature}°F")
50
+
51
+ # Get detailed live settings
52
+ settings = spa.get_live_settings()
53
+ print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
54
+
55
+ # Control the spa
56
+ spa.set_temperature(102) # Set temperature to 102°F
57
+ spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
58
+ spa.set_lights(True) # Turn on the lights
59
+ ```
60
+
61
+ Both clients provide identical functionality, with the synchronous client simply wrapping the async one for convenience.
62
+
63
+ ## API Reference
64
+
65
+ ### AsyncCalderaClient
66
+
67
+ The main async client class for interacting with the spa. All operations must be performed within an async context manager:
68
+
69
+ ```python
70
+ async with AsyncCalderaClient(
71
+ email="email@example.com",
72
+ password="password",
73
+ timeout=10.0, # Optional: request timeout in seconds
74
+ debug=False, # Optional: enable debug logging
75
+ ) as spa:
76
+ # All spa operations must be inside this block
77
+ await spa.get_spa_status()
78
+ await spa.set_temperature(102)
79
+ # etc...
80
+ ```
81
+
82
+ ### CalderaClient
83
+
84
+ A synchronous wrapper around AsyncCalderaClient that provides the same functionality without requiring async/await:
85
+
86
+ ```python
87
+ with CalderaClient(
88
+ email="email@example.com",
89
+ password="password",
90
+ timeout=10.0, # Optional: request timeout in seconds
91
+ debug=False, # Optional: enable debug logging
92
+ ) as spa:
93
+ # All spa operations can be called synchronously
94
+ spa.get_spa_status()
95
+ spa.set_temperature(102)
96
+ # etc...
97
+ ```
98
+
99
+ ### Error Handling
100
+
101
+ All operations can raise these base exceptions:
102
+ - `AuthenticationError`: When authentication fails or token expires
103
+ - `ConnectionError`: When network connection fails or API is unreachable
104
+ - `SpaControlError`: When the API returns an error response
105
+
106
+ ### Temperature Control
107
+
108
+ ```python
109
+ async with spa as client:
110
+ # Set temperature (80-104°F or 26.5-40°C)
111
+ try:
112
+ # Basic temperature setting
113
+ await client.set_temperature(102) # Fahrenheit
114
+ await client.set_temperature(39, "C") # Celsius
115
+
116
+ # Wait for spa to acknowledge the temperature change
117
+ await client.set_temperature(102, wait_for_ack=True)
118
+
119
+ # Control polling behavior when waiting for acknowledgment
120
+ await client.set_temperature(
121
+ 102,
122
+ wait_for_ack=True,
123
+ polling_interval=5.0, # Check every 5 seconds
124
+ polling_timeout=120.0, # Time out after 2 minutes
125
+ )
126
+
127
+ # Manually wait for temperature acknowledgment
128
+ settings = await client.wait_for_temperature_ack(
129
+ expected_temp=102, # Expected temperature in Fahrenheit
130
+ interval=5.0, # Check every 5 seconds
131
+ timeout=120.0, # Time out after 2 minutes
132
+ )
133
+ except InvalidParameterError:
134
+ # Raised when temperature is outside valid range
135
+ # (80-104°F or 26.5-40°C)
136
+ pass
137
+ except SpaControlError:
138
+ # Raised when polling times out waiting for acknowledgment
139
+ pass
140
+ ```
141
+
142
+ ### Pump Control
143
+
144
+ ```python
145
+ async with spa as client:
146
+ try:
147
+ await client.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
148
+ await client.set_pump(2, PUMP_LOW) # Set pump 2 to low speed
149
+ await client.set_pump(3, PUMP_OFF) # Turn off pump 3
150
+ except InvalidParameterError:
151
+ # Raised when:
152
+ # - pump_number is not 1, 2, or 3
153
+ # - speed is not PUMP_OFF (0), PUMP_LOW (1), or PUMP_HIGH (2)
154
+ pass
155
+ ```
156
+
157
+ ### Light Control
158
+
159
+ ```python
160
+ async with spa as client:
161
+ try:
162
+ await client.set_lights(True) # Turn lights on
163
+ await client.set_lights(False) # Turn lights off
164
+ except SpaControlError:
165
+ # Raised when light control fails
166
+ pass
167
+ ```
168
+
169
+ ### Status & Settings
170
+
171
+ ```python
172
+ async with spa as client:
173
+ try:
174
+ # Get basic spa status
175
+ status = await client.get_spa_status()
176
+ print(f"Online: {status.status == 'ONLINE'}")
177
+
178
+ # Get detailed live settings
179
+ settings = await client.get_live_settings()
180
+ print(f"Target temp: {settings.ctrl_head_set_temperature}°F")
181
+ except ConnectionError:
182
+ # Raised when spa is offline or unreachable
183
+ pass
184
+ except SpaControlError:
185
+ # Raised when API returns invalid data
186
+ pass
187
+
188
+ ## Development
189
+
190
+ 1. Clone the repository
191
+ 2. Create a virtual environment:
192
+ ```bash
193
+ python -m venv venv
194
+ source venv/bin/activate # or `venv\Scripts\activate` on Windows
195
+ ```
196
+ 3. Install development dependencies:
197
+ ```bash
198
+ pip install -r requirements-dev.txt
199
+ ```
200
+ 4. Install pre-commit hooks:
201
+ ```bash
202
+ pre-commit install
203
+ ```
204
+
205
+ The pre-commit hooks will run automatically on git commit, checking:
206
+ - Code formatting (Black)
207
+ - Import sorting (isort)
208
+ - Type checking (mypy)
209
+ - Linting (pylint, ruff)
210
+ - YAML/TOML syntax
211
+ - Trailing whitespace and file endings
212
+
213
+ ## License
214
+
215
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,29 @@
1
+ """Python client for Caldera Spa Connexion API."""
2
+
3
+ from .__meta__ import __version__
4
+ from .async_client import AsyncCalderaClient
5
+ from .client import CalderaClient
6
+ from .const import PUMP_HIGH, PUMP_LOW, PUMP_OFF
7
+ from .exceptions import (
8
+ AuthenticationError,
9
+ CalderaError,
10
+ ConnectionError,
11
+ InvalidParameterError,
12
+ SpaControlError,
13
+ )
14
+ from .models import LiveSettings
15
+
16
+ __all__ = [
17
+ "AsyncCalderaClient",
18
+ "CalderaClient",
19
+ "LiveSettings",
20
+ "PUMP_OFF",
21
+ "PUMP_LOW",
22
+ "PUMP_HIGH",
23
+ "CalderaError",
24
+ "AuthenticationError",
25
+ "ConnectionError",
26
+ "SpaControlError",
27
+ "InvalidParameterError",
28
+ "__version__",
29
+ ]
@@ -0,0 +1,13 @@
1
+ # `name` is the name of the package as used for `pip install package`
2
+ name = "pycaldera"
3
+ # `path` is the name of the package for `import package`
4
+ path = name.lower().replace("-", "_").replace(" ", "_")
5
+ # Your version number should follow https://python.org/dev/peps/pep-0440 and
6
+ # https://semver.org
7
+ version = "0.1.0"
8
+ __version__ = version
9
+ author = "Mark Watson"
10
+ author_email = "markwatson@cantab.net"
11
+ description = "Unofficial Python client for Caldera Spa API" # One-liner
12
+ url = "https://github.com/mwatson2/pycaldera" # Add your GitHub repo URL
13
+ license = "MIT" # See https://choosealicense.com