pyocat 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.
pyocat-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WATERCryst GmbH
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.
pyocat-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyocat
3
+ Version: 0.1.0
4
+ Summary:
5
+ License-File: LICENSE
6
+ Author: Mathias Hörtnagl
7
+ Author-email: mathias.hoertnagl@watercryst.com
8
+ Requires-Python: >=3.12
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: httpx (>=0.28.1,<0.29.0)
14
+ Requires-Dist: pydantic (>=2.13.3,<3.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ Execute to download dependencies:
18
+
19
+ ```bash
20
+ poetry install
21
+ ```
22
+
23
+ Execute to run the unit tests:
24
+
25
+ ```bash
26
+ poetry run pytest
27
+ ```
28
+
29
+ Execute to build the library:
30
+
31
+ ```bash
32
+ poetry build
33
+ ```
34
+
pyocat-0.1.0/README.md ADDED
@@ -0,0 +1,17 @@
1
+ Execute to download dependencies:
2
+
3
+ ```bash
4
+ poetry install
5
+ ```
6
+
7
+ Execute to run the unit tests:
8
+
9
+ ```bash
10
+ poetry run pytest
11
+ ```
12
+
13
+ Execute to build the library:
14
+
15
+ ```bash
16
+ poetry build
17
+ ```
@@ -0,0 +1,5 @@
1
+ from .auth import Auth # type: ignore
2
+ from .api_client import ApiClient # type: ignore
3
+
4
+ from .async_auth import AsyncAuth # type: ignore
5
+ from .async_api_client import AsyncApiClient # type: ignore
@@ -0,0 +1,193 @@
1
+ from typing import Literal
2
+
3
+ from .auth import Auth
4
+
5
+ from .models import MeasurementResponse
6
+ from .models import StatisticsResponse
7
+ from .models import StateResponse
8
+
9
+
10
+ class ApiClient:
11
+ """
12
+ Synchronous Biocat REST API v1 client.
13
+ """
14
+
15
+ def __init__(self, auth: Auth):
16
+ self.auth = auth
17
+
18
+
19
+ def acknowledge_event(self):
20
+ """
21
+ Acknowledges the current device warning or error.
22
+ """
23
+ self.auth.get('v1/ackevent')
24
+
25
+
26
+ def enable_absence(self):
27
+ """
28
+ Enables absence mode and raises leakage detector sensitivity.
29
+ """
30
+ self.auth.get('v1/absence/enable')
31
+
32
+
33
+ def disable_absence(self):
34
+ """
35
+ Disables absence mode and reverts leakage detector
36
+ sensitivity to its default level.
37
+ """
38
+ self.auth.get('v1/absence/disable')
39
+
40
+
41
+ def pause_leakage_protection(
42
+ self,
43
+ minutes: int
44
+ ):
45
+ """
46
+ Pauses leakage protection for a given duration of minutes.
47
+
48
+ Parameters
49
+ ----------
50
+ minutes : int
51
+ The pause duration in minutes. Must be within the range
52
+ [1 .. 4320].
53
+ """
54
+ self.auth.get('v1/leakageprotection/pause', { 'minutes': minutes })
55
+
56
+
57
+ def unpause_leakage_protection(self):
58
+ """
59
+ Reactivates leakage protection.
60
+ """
61
+ self.auth.get('v1/leakageprotection/unpause')
62
+
63
+
64
+ def start_self_test(self):
65
+ """
66
+ Starts the self test routine. Automatically checks all
67
+ actuators and sensors and fills the active unit with drinking
68
+ water over a defined flushing time.
69
+
70
+ Requires 2 minutes for completion.
71
+
72
+ The device will return to water treatment if no errors could
73
+ be detected.
74
+
75
+ In the case of errors, the current error will be reported
76
+ via the webhook endpoint. Additionally it can be queried
77
+ at any time with `GET v1/state`.
78
+ """
79
+ self.auth.get('v1/selftest')
80
+
81
+
82
+ def get_measurements(self) -> MeasurementResponse:
83
+ """
84
+ Fetches current measurement data.
85
+
86
+ Returns
87
+ -------
88
+ The current measurement data.
89
+ """
90
+ response = self.auth.get('v1/measurements/direct')
91
+ return MeasurementResponse.model_validate(response.json())
92
+
93
+
94
+ def start_micro_leakage_measurement(self):
95
+ """
96
+ Starts the micro-leakage measurement to check the leak-
97
+ tightness of the piping. This allows the detection of micro-
98
+ leaks such as dripping taps or pipe fittings.
99
+ For this measurements, water supply is briefly shut off.
100
+
101
+ An unexpected water consumption during the measuring process,
102
+ e.g. flushing the toilet or opening a tap, is automatically
103
+ detected and the water supply is restored within a few seconds.
104
+ However, the test fails and the API call has to be repeated.
105
+
106
+ ### Notice
107
+
108
+ If you use a drip irrigation system in your household, this
109
+ can be detected as a micro leak.
110
+ """
111
+ self.auth.get('v1/mlmeasurement/start')
112
+
113
+
114
+ def get_daily_statistics(self) -> StatisticsResponse:
115
+ """
116
+ Fetches the daily statistics.
117
+
118
+ Returns
119
+ -------
120
+ Water consumption statistics of the trailing 30 days.
121
+ """
122
+ response = self.auth.get('v1/statistics/daily/direct')
123
+ return StatisticsResponse.model_validate(response.json())
124
+
125
+
126
+ def get_todays_consumption(self) -> float:
127
+ """
128
+ Fetches the total consumption for today.
129
+
130
+ Returns
131
+ -------
132
+ Todays total water consumption in [L].
133
+ """
134
+ response = self.auth.get('v1/statistics/cumulative/daily')
135
+ return float(response.json())
136
+
137
+
138
+ def get_total_consumption(self) -> float:
139
+ """
140
+ Fetches the total water consumption since the device was
141
+ installed.
142
+
143
+ Returns
144
+ -------
145
+ Total water consumption in [L].
146
+ """
147
+ response = self.auth.get('v1/statistics/cumulative/total')
148
+ return float(response.json())
149
+
150
+
151
+ def open_water_supply(self):
152
+ """
153
+ Connects the device with the water supply.
154
+ Confirms pending warnings and errors.
155
+ """
156
+ self.auth.get('v1/watersupply/open')
157
+
158
+
159
+ def close_water_supply(self):
160
+ """
161
+ Disconnects the device from the water supply.
162
+ """
163
+ self.auth.get('v1/watersupply/close')
164
+
165
+
166
+ def get_state(
167
+ self,
168
+ locale: str = 'de',
169
+ format: Literal['plain', 'md', 'html'] = 'plain'
170
+ ) -> StateResponse:
171
+ """
172
+ Returns the current state of the device.
173
+
174
+ Parameters
175
+ ----------
176
+ locale : str
177
+ The language of the event message. Defaults to `de`.
178
+ format : `plain` | `md` | `html`
179
+ The format of the event message. Defaults to `plain`.
180
+ * `plain` - Renders the event message as plain text.
181
+ * `md` - Renders the event message as Markdown.
182
+ * `html` - Renders the event message as HTML.
183
+
184
+ Returns
185
+ -------
186
+ The current device state.
187
+ """
188
+ response = self.auth.get(
189
+ path='v1/state',
190
+ params={ 'locale': locale, 'format': format }
191
+ )
192
+ return StateResponse.model_validate(response.json())
193
+
@@ -0,0 +1,193 @@
1
+ from typing import Literal
2
+
3
+ from .async_auth import AsyncAuth
4
+
5
+ from .models import MeasurementResponse
6
+ from .models import StatisticsResponse
7
+ from .models import StateResponse
8
+
9
+
10
+ class AsyncApiClient:
11
+ """
12
+ Asynchronous Biocat REST API v1 client.
13
+ """
14
+
15
+ def __init__(self, auth: AsyncAuth):
16
+ self.auth = auth
17
+
18
+
19
+ async def acknowledge_event(self):
20
+ """
21
+ Acknowledges the current device warning or error.
22
+ """
23
+ await self.auth.get('v1/ackevent')
24
+
25
+
26
+ async def enable_absence(self):
27
+ """
28
+ Enables absence mode and raises leakage detector sensitivity.
29
+ """
30
+ await self.auth.get('v1/absence/enable')
31
+
32
+
33
+ async def disable_absence(self):
34
+ """
35
+ Disables absence mode and reverts leakage detector
36
+ sensitivity to its default level.
37
+ """
38
+ await self.auth.get('v1/absence/disable')
39
+
40
+
41
+ async def pause_leakage_protection(
42
+ self,
43
+ minutes: int
44
+ ):
45
+ """
46
+ Pauses leakage protection for a given duration of minutes.
47
+
48
+ Parameters
49
+ ----------
50
+ minutes : int
51
+ The pause duration in minutes. Must be within the range
52
+ [1 .. 4320].
53
+ """
54
+ await self.auth.get('v1/leakageprotection/pause', { 'minutes': minutes })
55
+
56
+
57
+ async def unpause_leakage_protection(self):
58
+ """
59
+ Reactivates leakage protection.
60
+ """
61
+ await self.auth.get('v1/leakageprotection/unpause')
62
+
63
+
64
+ async def start_self_test(self):
65
+ """
66
+ Starts the self test routine. Automatically checks all
67
+ actuators and sensors and fills the active unit with drinking
68
+ water over a defined flushing time.
69
+
70
+ Requires 2 minutes for completion.
71
+
72
+ The device will return to water treatment if no errors could
73
+ be detected.
74
+
75
+ In the case of errors, the current error will be reported
76
+ via the webhook endpoint. Additionally it can be queried
77
+ at any time with `GET v1/state`.
78
+ """
79
+ await self.auth.get('v1/selftest')
80
+
81
+
82
+ async def get_measurements(self) -> MeasurementResponse:
83
+ """
84
+ Fetches current measurement data.
85
+
86
+ Returns
87
+ -------
88
+ The current measurement data.
89
+ """
90
+ response = await self.auth.get('v1/measurements/direct')
91
+ return MeasurementResponse.model_validate(response.json())
92
+
93
+
94
+ async def start_micro_leakage_measurement(self):
95
+ """
96
+ Starts the micro-leakage measurement to check the leak-
97
+ tightness of the piping. This allows the detection of micro-
98
+ leaks such as dripping taps or pipe fittings.
99
+ For this measurements, water supply is briefly shut off.
100
+
101
+ An unexpected water consumption during the measuring process,
102
+ e.g. flushing the toilet or opening a tap, is automatically
103
+ detected and the water supply is restored within a few seconds.
104
+ However, the test fails and the API call has to be repeated.
105
+
106
+ ### Notice
107
+
108
+ If you use a drip irrigation system in your household, this
109
+ can be detected as a micro leak.
110
+ """
111
+ await self.auth.get('v1/mlmeasurement/start')
112
+
113
+
114
+ async def get_daily_statistics(self) -> StatisticsResponse:
115
+ """
116
+ Fetches the daily statistics.
117
+
118
+ Returns
119
+ -------
120
+ Water consumption statistics of the trailing 30 days.
121
+ """
122
+ response = await self.auth.get('v1/statistics/daily/direct')
123
+ return StatisticsResponse.model_validate(response.json())
124
+
125
+
126
+ async def get_todays_consumption(self) -> float:
127
+ """
128
+ Fetches the total consumption for today.
129
+
130
+ Returns
131
+ -------
132
+ Todays total water consumption in [L].
133
+ """
134
+ response = await self.auth.get('v1/statistics/cumulative/daily')
135
+ return float(response.json())
136
+
137
+
138
+ async def get_total_consumption(self) -> float:
139
+ """
140
+ Fetches the total water consumption since the device was
141
+ installed.
142
+
143
+ Returns
144
+ -------
145
+ Total water consumption in [L].
146
+ """
147
+ response = await self.auth.get('v1/statistics/cumulative/total')
148
+ return float(response.json())
149
+
150
+
151
+ async def open_water_supply(self):
152
+ """
153
+ Connects the device with the water supply.
154
+ Confirms pending warnings and errors.
155
+ """
156
+ await self.auth.get('v1/watersupply/open')
157
+
158
+
159
+ async def close_water_supply(self):
160
+ """
161
+ Disconnects the device from the water supply.
162
+ """
163
+ await self.auth.get('v1/watersupply/close')
164
+
165
+
166
+ async def get_state(
167
+ self,
168
+ locale: str = 'de',
169
+ format: Literal['plain', 'md', 'html'] = 'plain'
170
+ ) -> StateResponse:
171
+ """
172
+ Returns the current state of the device.
173
+
174
+ Parameters
175
+ ----------
176
+ locale : str
177
+ The language of the event message. Defaults to `de`.
178
+ format : `plain` | `md` | `html`
179
+ The format of the event message. Defaults to `plain`.
180
+ * `plain` - Renders the event message as plain text.
181
+ * `md` - Renders the event message as Markdown.
182
+ * `html` - Renders the event message as HTML.
183
+
184
+ Returns
185
+ -------
186
+ The current device state.
187
+ """
188
+ response = await self.auth.get(
189
+ path='v1/state',
190
+ params={ 'locale': locale, 'format': format }
191
+ )
192
+ return StateResponse.model_validate(response.json())
193
+
@@ -0,0 +1,63 @@
1
+ from typing import Mapping, Union
2
+
3
+ from httpx import AsyncClient, Response
4
+
5
+ class AsyncAuth:
6
+ """
7
+ Make asynchronous authenticated requests.
8
+ """
9
+
10
+ def __init__(
11
+ self,
12
+ client: AsyncClient,
13
+ api_key: str,
14
+ host: str = 'https://appapi.watercryst.com'
15
+ ):
16
+ """
17
+ Creates a new authenticated request sender.
18
+
19
+ Parameters
20
+ ----------
21
+ client: `httpx.AsyncClient`
22
+ Instance of an asynchronous HTTP client.
23
+ api_key : str
24
+ The api key.
25
+ host : str
26
+ The Biocat SmartHome API host.
27
+ Defaults to `https://appapi.watercryst.com`.
28
+ """
29
+ self.client = client
30
+ self.api_key = api_key
31
+ self.host = host
32
+
33
+
34
+ async def get(
35
+ self,
36
+ path: str,
37
+ params: Mapping[str, Union[str, int, float, bool]] | None = None,
38
+ ) -> Response:
39
+ """
40
+ Send an asynchronous `GET` request.
41
+
42
+ Parameters
43
+ ----------
44
+ path : str
45
+ The relative path.
46
+ params : Mapping[str, Union[str, int, float, bool]]
47
+ Optional query parameters.
48
+
49
+ Returns
50
+ -------
51
+ An `httpx.Response`.
52
+ """
53
+ headers = dict[str, str]()
54
+ headers['X-API-KEY'] = self.api_key
55
+
56
+ response = await self.client.get(
57
+ url=f'{self.host}/{path}',
58
+ params=params,
59
+ headers=headers
60
+ )
61
+ response.raise_for_status()
62
+
63
+ return response
@@ -0,0 +1,63 @@
1
+ from typing import Mapping, Union
2
+
3
+ from httpx import Client, Response
4
+
5
+ class Auth:
6
+ """
7
+ Make authenticated requests.
8
+ """
9
+
10
+ def __init__(
11
+ self,
12
+ client: Client,
13
+ api_key: str,
14
+ host: str = 'https://appapi.watercryst.com'
15
+ ):
16
+ """
17
+ Creates a new authenticated request sender.
18
+
19
+ Parameters
20
+ ----------
21
+ client: `httpx.Client`
22
+ Instance of a synchronous HTTP client.
23
+ api_key : str
24
+ The api key.
25
+ host : str
26
+ The Biocat SmartHome API host.
27
+ Defaults to `https://appapi.watercryst.com`.
28
+ """
29
+ self.client = client
30
+ self.api_key = api_key
31
+ self.host = host
32
+
33
+
34
+ def get(
35
+ self,
36
+ path: str,
37
+ params: Mapping[str, Union[str, int, float, bool]] | None = None,
38
+ ) -> Response:
39
+ """
40
+ Send a `GET` request.
41
+
42
+ Parameters
43
+ ----------
44
+ path : str
45
+ The relative path.
46
+ params : Mapping[str, Union[str, int, float, bool]]
47
+ Optional query parameters.
48
+
49
+ Returns
50
+ -------
51
+ An `httpx.Response`.
52
+ """
53
+ headers = dict[str, str]()
54
+ headers['X-API-KEY'] = self.api_key
55
+
56
+ response = self.client.get(
57
+ url=f'{self.host}/{path}',
58
+ params=params,
59
+ headers=headers
60
+ )
61
+ response.raise_for_status()
62
+
63
+ return response
@@ -0,0 +1,7 @@
1
+ from .event_response import EventResponse, EventCategory # type: ignore
2
+ from .measurement_response import MeasurementResponse # type: ignore
3
+ from .message_response import MessageResponse # type: ignore
4
+ from .mode_response import ModeResponse, ModeId # type: ignore
5
+ from .state_response import StateResponse, MlState # type: ignore
6
+ from .statistics_response import StatisticsResponse # type: ignore
7
+ from .water_protection_response import WaterProtectionResponse # type: ignore
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+ from typing import Annotated, Literal
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ EventCategory = Literal['error', 'warning', 'info']
7
+
8
+
9
+ class EventResponse(BaseModel):
10
+ """
11
+ Represents an event.
12
+
13
+ Attributes
14
+ ----------
15
+ type : str
16
+ Denotes the type of the response.
17
+ event_id : int
18
+ Identifies the type of the event.
19
+ category : Literal['error', 'warning', 'info']
20
+ The event category.
21
+ title : str
22
+ Event summary.
23
+ description : str
24
+ Detailed description.
25
+ timestamp : datetime
26
+ UTC date time of the event.
27
+ """
28
+
29
+ type: Annotated[str, Field(alias='type')]
30
+ event_id: Annotated[int, Field(alias='eventId')]
31
+ category: Annotated[EventCategory, Field(alias='category')]
32
+ title: Annotated[str, Field(alias='title')]
33
+ description: Annotated[str, Field(alias='description')]
34
+ timestamp: Annotated[datetime, Field(alias='timestamp')]
@@ -0,0 +1,27 @@
1
+ from typing import Annotated, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class MeasurementResponse(BaseModel):
6
+ """
7
+ Represents current measurement data.
8
+
9
+ Attributes
10
+ ----------
11
+ water_temp : int
12
+ Water temperature in degree celsius [°C].
13
+ pressure : float
14
+ Pressure in [bar].
15
+ flow_rate : float
16
+ Flow rate in liters per minute [L/min].
17
+ last_water_tap_volume : float
18
+ Volume of the last water tapping in liters [L].
19
+ last_water_tap_duration : float
20
+ Duration of the last water tapping in seconds [sec].
21
+ """
22
+
23
+ water_temp: Annotated[Union[int, None], Field(alias='waterTemp')] = None
24
+ pressure: Annotated[Union[float, None], Field(alias='pressure')] = None
25
+ flow_rate: Annotated[Union[float, None], Field(alias='flowRate')] = None
26
+ last_water_tap_volume: Annotated[Union[float, None], Field(alias='lastWaterTapVolume')] = None
27
+ last_water_tap_duration: Annotated[Union[float, None], Field(alias='lastWaterTapDuration')] = None
@@ -0,0 +1,23 @@
1
+ from datetime import datetime
2
+ from typing import Annotated
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class MessageResponse(BaseModel):
7
+ """
8
+ Represents a message.
9
+
10
+ Attributes
11
+ ----------
12
+ type : str
13
+ Denotes the type of the response.
14
+ absence_mode_enabled : bool
15
+ Indicates the state of the absence mode.
16
+ pause_leakage_protection_until_utc : datetime
17
+ UTC date time string when the leakage protection
18
+ will be active again.
19
+ """
20
+
21
+ type: Annotated[str, Field(alias='type')]
22
+ absence_mode_enabled: Annotated[bool, Field(alias='absenceModeEnabled')]
23
+ pause_leakage_protection_until_utc: Annotated[datetime, Field(alias='pauseLeakageProtectionUntilUTC')]
@@ -0,0 +1,21 @@
1
+ from typing import Annotated, Literal
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ ModeId = Literal['ER', 'FS', 'MC', 'RS', 'ST', 'TD', 'UD', 'WO', 'WT']
6
+
7
+
8
+ class ModeResponse(BaseModel):
9
+ """
10
+ Represents the current mode of operation.
11
+
12
+ Attributes
13
+ ----------
14
+ id : Literal['ER', 'FS', 'MC', 'RS', 'ST', 'TD', 'UD', 'WO', 'WT']
15
+ Mode identifier.
16
+ name : str
17
+ Mode display name.
18
+ """
19
+
20
+ id: Annotated[ModeId, Field(alias='id')]
21
+ name: Annotated[str, Field(alias='name')]
@@ -0,0 +1,44 @@
1
+ from typing import Annotated, Literal, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+ from .event_response import EventResponse
5
+ from .mode_response import ModeResponse
6
+ from .water_protection_response import WaterProtectionResponse
7
+
8
+
9
+ MlState = Literal[
10
+ "cancelled",
11
+ "failure-pressure-drop",
12
+ "failure-start-pressure",
13
+ "failure-unknown",
14
+ "failure-water-tap",
15
+ "idle",
16
+ "leakage",
17
+ "running",
18
+ "success"
19
+ ]
20
+
21
+
22
+ class StateResponse(BaseModel):
23
+ """
24
+ Represents the current device state.
25
+
26
+ Attributes
27
+ ----------
28
+ online : bool
29
+ Indicates whether the device is online or offline right now.
30
+ mode : ModeResponse
31
+ Represents the current mode of operation.
32
+ event : EventResponse
33
+ Represents an event.
34
+ water_protection : WaterProtectionResponse | None
35
+ Represents the current state of the water protection subsystem.
36
+ ml_state : Literal["cancelled", "failure-pressure-drop", "failure-start-pressure", "failure-unknown", "failure-water-tap", "idle", "leakage", "running", "success"] | None
37
+ The state of the current (or last) micro leakage measurement.
38
+ """
39
+
40
+ online: Annotated[bool, Field(alias='online')]
41
+ mode: Annotated[ModeResponse, Field(alias='mode')]
42
+ event: Annotated[EventResponse, Field(alias='event')]
43
+ water_protection: Annotated[Union[WaterProtectionResponse, None], Field(alias='waterProtection')] = None
44
+ ml_state: Annotated[Union[MlState, None], Field(alias='mlState')] = None
@@ -0,0 +1,35 @@
1
+ from datetime import datetime
2
+ from typing import Annotated
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class StatisticsResponseEntry(BaseModel):
7
+ """
8
+ A consumption statistics data point.
9
+
10
+ Attributes
11
+ ----------
12
+ consumption : float
13
+ Water consumption for this day in liters [L].
14
+ date : datetime
15
+ UTC date of the measurement.
16
+ """
17
+
18
+ consumption: Annotated[float, Field(alias='consumption')]
19
+ date: Annotated[datetime, Field(alias='date')]
20
+
21
+
22
+ class StatisticsResponse(BaseModel):
23
+ """
24
+ Represents a list of consumption statistics data points.
25
+
26
+ Attributes
27
+ ----------
28
+ type : str
29
+ Denotes the type of the response.
30
+ entries : list[StatisticsResponseEntry]
31
+ List of data points.
32
+ """
33
+
34
+ type: Annotated[str, Field(alias='type')]
35
+ entries: Annotated[list[StatisticsResponseEntry], Field(alias='entries')] = []
@@ -0,0 +1,19 @@
1
+ from datetime import datetime
2
+ from typing import Annotated
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class WaterProtectionResponse(BaseModel):
7
+ """
8
+ Represents the current state of the water protection subsystem.
9
+
10
+ Attributes
11
+ ----------
12
+ absence_mode_enabled : bool
13
+ Indicates the state of the absence mode.
14
+ pause_leakage_protection_until_utc : datetime
15
+ UTC date time when the leakage protection will be active again.
16
+ """
17
+
18
+ absence_mode_enabled: Annotated[bool, Field(alias='absenceModeEnabled')]
19
+ pause_leakage_protection_until_utc: Annotated[datetime, Field(alias='pauseLeakageProtectionUntilUTC')]
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "pyocat"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = [
6
+ { name = "Mathias Hörtnagl", email = "mathias.hoertnagl@watercryst.com" },
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "httpx (>=0.28.1,<0.29.0)",
12
+ "pydantic (>=2.13.3,<3.0.0)"
13
+ ]
14
+
15
+ [tool.poetry.scripts]
16
+ main = "python_biocat_rest_api_client:main"
17
+
18
+ [build-system]
19
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
20
+ build-backend = "poetry.core.masonry.api"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest (>=9.0.3,<10.0.0)",
25
+ "pytest-asyncio (>=1.3.0,<2.0.0)",
26
+ "pytest-httpx (>=0.36.2,<0.37.0)"
27
+ ]