weheat 2025.1.14rc1__py3-none-any.whl → 2025.1.15rc1__py3-none-any.whl

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.

Potentially problematic release.


This version of weheat might be problematic. Click here for more details.

Files changed (36) hide show
  1. weheat/__init__.py +7 -2
  2. weheat/abstractions/discovery.py +2 -4
  3. weheat/abstractions/heat_pump.py +6 -13
  4. weheat/abstractions/user.py +3 -5
  5. weheat/api/__init__.py +1 -0
  6. weheat/api/energy_log_api.py +306 -132
  7. weheat/api/heat_pump_api.py +521 -369
  8. weheat/api/heat_pump_log_api.py +836 -359
  9. weheat/api/user_api.py +243 -115
  10. weheat/api_client.py +234 -261
  11. weheat/api_response.py +10 -18
  12. weheat/configuration.py +4 -8
  13. weheat/exceptions.py +59 -25
  14. weheat/models/__init__.py +6 -0
  15. weheat/models/boiler_type.py +8 -3
  16. weheat/models/device_state.py +9 -4
  17. weheat/models/dhw_type.py +8 -3
  18. weheat/models/energy_view_dto.py +81 -66
  19. weheat/models/heat_pump_log_view_dto.py +527 -481
  20. weheat/models/heat_pump_model.py +8 -3
  21. weheat/models/heat_pump_status_enum.py +8 -3
  22. weheat/models/heat_pump_type.py +8 -3
  23. weheat/models/raw_heat_pump_log_dto.py +353 -315
  24. weheat/models/read_all_heat_pump_dto.py +64 -48
  25. weheat/models/read_heat_pump_dto.py +59 -43
  26. weheat/models/read_user_dto.py +54 -39
  27. weheat/models/read_user_me_dto.py +124 -0
  28. weheat/models/role.py +10 -4
  29. weheat/rest.py +142 -257
  30. weheat-2025.1.15rc1.dist-info/METADATA +115 -0
  31. weheat-2025.1.15rc1.dist-info/RECORD +37 -0
  32. weheat-2025.1.14rc1.dist-info/METADATA +0 -117
  33. weheat-2025.1.14rc1.dist-info/RECORD +0 -36
  34. {weheat-2025.1.14rc1.dist-info → weheat-2025.1.15rc1.dist-info}/LICENSE +0 -0
  35. {weheat-2025.1.14rc1.dist-info → weheat-2025.1.15rc1.dist-info}/WHEEL +0 -0
  36. {weheat-2025.1.14rc1.dist-info → weheat-2025.1.15rc1.dist-info}/top_level.txt +0 -0
weheat/rest.py CHANGED
@@ -14,143 +14,125 @@
14
14
 
15
15
  import io
16
16
  import json
17
- import logging
18
17
  import re
19
18
  import ssl
20
19
 
21
- from urllib.parse import urlencode, quote_plus
22
- import urllib3
20
+ import aiohttp
21
+ import aiohttp_retry
23
22
 
24
- from weheat.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError, BadRequestException
23
+ from weheat.exceptions import ApiException, ApiValueError
25
24
 
25
+ RESTResponseType = aiohttp.ClientResponse
26
26
 
27
- logger = logging.getLogger(__name__)
28
-
29
- SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"}
30
-
31
-
32
- def is_socks_proxy_url(url):
33
- if url is None:
34
- return False
35
- split_section = url.split("://")
36
- if len(split_section) < 2:
37
- return False
38
- else:
39
- return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES
40
-
27
+ ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'})
41
28
 
42
29
  class RESTResponse(io.IOBase):
43
30
 
44
31
  def __init__(self, resp) -> None:
45
- self.urllib3_response = resp
32
+ self.response = resp
46
33
  self.status = resp.status
47
34
  self.reason = resp.reason
48
- self.data = resp.data
35
+ self.data = None
36
+
37
+ async def read(self):
38
+ if self.data is None:
39
+ self.data = await self.response.read()
40
+ return self.data
49
41
 
50
42
  def getheaders(self):
51
- """Returns a dictionary of the response headers."""
52
- return self.urllib3_response.headers
43
+ """Returns a CIMultiDictProxy of the response headers."""
44
+ return self.response.headers
53
45
 
54
46
  def getheader(self, name, default=None):
55
47
  """Returns a given response header."""
56
- return self.urllib3_response.headers.get(name, default)
48
+ return self.response.headers.get(name, default)
57
49
 
58
50
 
59
51
  class RESTClientObject:
60
52
 
61
- def __init__(self, configuration, pools_size=4, maxsize=None) -> None:
62
- # urllib3.PoolManager will pass all kw parameters to connectionpool
63
- # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501
64
- # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501
65
- # maxsize is the number of requests to host that are allowed in parallel # noqa: E501
66
- # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501
53
+ def __init__(self, configuration) -> None:
67
54
 
68
- # cert_reqs
69
- if configuration.verify_ssl:
70
- cert_reqs = ssl.CERT_REQUIRED
71
- else:
72
- cert_reqs = ssl.CERT_NONE
55
+ # maxsize is number of requests to host that are allowed in parallel
56
+ maxsize = configuration.connection_pool_maxsize
73
57
 
74
- addition_pool_args = {}
75
- if configuration.assert_hostname is not None:
76
- addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501
77
-
78
- if configuration.retries is not None:
79
- addition_pool_args['retries'] = configuration.retries
58
+ ssl_context = ssl.create_default_context(
59
+ cafile=configuration.ssl_ca_cert
60
+ )
61
+ if configuration.cert_file:
62
+ ssl_context.load_cert_chain(
63
+ configuration.cert_file, keyfile=configuration.key_file
64
+ )
80
65
 
81
- if configuration.tls_server_name:
82
- addition_pool_args['server_hostname'] = configuration.tls_server_name
66
+ if not configuration.verify_ssl:
67
+ ssl_context.check_hostname = False
68
+ ssl_context.verify_mode = ssl.CERT_NONE
83
69
 
70
+ connector = aiohttp.TCPConnector(
71
+ limit=maxsize,
72
+ ssl=ssl_context
73
+ )
84
74
 
85
- if configuration.socket_options is not None:
86
- addition_pool_args['socket_options'] = configuration.socket_options
87
-
88
- if maxsize is None:
89
- if configuration.connection_pool_maxsize is not None:
90
- maxsize = configuration.connection_pool_maxsize
91
- else:
92
- maxsize = 4
75
+ self.proxy = configuration.proxy
76
+ self.proxy_headers = configuration.proxy_headers
93
77
 
94
78
  # https pool manager
95
- if configuration.proxy:
96
- if is_socks_proxy_url(configuration.proxy):
97
- from urllib3.contrib.socks import SOCKSProxyManager
98
- self.pool_manager = SOCKSProxyManager(
99
- cert_reqs=cert_reqs,
100
- ca_certs=configuration.ssl_ca_cert,
101
- cert_file=configuration.cert_file,
102
- key_file=configuration.key_file,
103
- proxy_url=configuration.proxy,
104
- headers=configuration.proxy_headers,
105
- **addition_pool_args
106
- )
107
- else:
108
- self.pool_manager = urllib3.ProxyManager(
109
- num_pools=pools_size,
110
- maxsize=maxsize,
111
- cert_reqs=cert_reqs,
112
- ca_certs=configuration.ssl_ca_cert,
113
- cert_file=configuration.cert_file,
114
- key_file=configuration.key_file,
115
- proxy_url=configuration.proxy,
116
- proxy_headers=configuration.proxy_headers,
117
- **addition_pool_args
79
+ self.pool_manager = aiohttp.ClientSession(
80
+ connector=connector,
81
+ trust_env=True
82
+ )
83
+
84
+ retries = configuration.retries
85
+ if retries is not None:
86
+ self.retry_client = aiohttp_retry.RetryClient(
87
+ client_session=self.pool_manager,
88
+ retry_options=aiohttp_retry.ExponentialRetry(
89
+ attempts=retries,
90
+ factor=0.0,
91
+ start_timeout=0.0,
92
+ max_timeout=120.0
118
93
  )
119
- else:
120
- self.pool_manager = urllib3.PoolManager(
121
- num_pools=pools_size,
122
- maxsize=maxsize,
123
- cert_reqs=cert_reqs,
124
- ca_certs=configuration.ssl_ca_cert,
125
- cert_file=configuration.cert_file,
126
- key_file=configuration.key_file,
127
- **addition_pool_args
128
94
  )
129
-
130
- def request(self, method, url, query_params=None, headers=None,
131
- body=None, post_params=None, _preload_content=True,
132
- _request_timeout=None):
133
- """Perform requests.
95
+ else:
96
+ self.retry_client = None
97
+
98
+ async def close(self):
99
+ await self.pool_manager.close()
100
+ if self.retry_client is not None:
101
+ await self.retry_client.close()
102
+
103
+ async def request(
104
+ self,
105
+ method,
106
+ url,
107
+ headers=None,
108
+ body=None,
109
+ post_params=None,
110
+ _request_timeout=None
111
+ ):
112
+ """Execute request
134
113
 
135
114
  :param method: http request method
136
115
  :param url: http request url
137
- :param query_params: query parameters in the url
138
116
  :param headers: http request headers
139
117
  :param body: request json body, for `application/json`
140
118
  :param post_params: request post parameters,
141
119
  `application/x-www-form-urlencoded`
142
120
  and `multipart/form-data`
143
- :param _preload_content: if False, the urllib3.HTTPResponse object will
144
- be returned without reading/decoding response
145
- data. Default is True.
146
121
  :param _request_timeout: timeout setting for this request. If one
147
122
  number provided, it will be total request
148
123
  timeout. It can also be a pair (tuple) of
149
124
  (connection, read) timeouts.
150
125
  """
151
126
  method = method.upper()
152
- assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
153
- 'PATCH', 'OPTIONS']
127
+ assert method in [
128
+ 'GET',
129
+ 'HEAD',
130
+ 'DELETE',
131
+ 'POST',
132
+ 'PUT',
133
+ 'PATCH',
134
+ 'OPTIONS'
135
+ ]
154
136
 
155
137
  if post_params and body:
156
138
  raise ApiValueError(
@@ -160,168 +142,71 @@ class RESTClientObject:
160
142
  post_params = post_params or {}
161
143
  headers = headers or {}
162
144
  # url already contains the URL query string
163
- # so reset query_params to empty dict
164
- query_params = {}
165
-
166
- timeout = None
167
- if _request_timeout:
168
- if isinstance(_request_timeout, (int,float)): # noqa: E501,F821
169
- timeout = urllib3.Timeout(total=_request_timeout)
170
- elif (isinstance(_request_timeout, tuple) and
171
- len(_request_timeout) == 2):
172
- timeout = urllib3.Timeout(
173
- connect=_request_timeout[0], read=_request_timeout[1])
174
-
175
- try:
176
- # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
177
- if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
178
-
179
- # no content type provided or payload is json
180
- if not headers.get('Content-Type') or re.search('json', headers['Content-Type'], re.IGNORECASE):
181
- request_body = None
182
- if body is not None:
183
- request_body = json.dumps(body)
184
- r = self.pool_manager.request(
185
- method, url,
186
- body=request_body,
187
- preload_content=_preload_content,
188
- timeout=timeout,
189
- headers=headers)
190
- elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501
191
- r = self.pool_manager.request(
192
- method, url,
193
- fields=post_params,
194
- encode_multipart=False,
195
- preload_content=_preload_content,
196
- timeout=timeout,
197
- headers=headers)
198
- elif headers['Content-Type'] == 'multipart/form-data':
199
- # must del headers['Content-Type'], or the correct
200
- # Content-Type which generated by urllib3 will be
201
- # overwritten.
202
- del headers['Content-Type']
203
- r = self.pool_manager.request(
204
- method, url,
205
- fields=post_params,
206
- encode_multipart=True,
207
- preload_content=_preload_content,
208
- timeout=timeout,
209
- headers=headers)
210
- # Pass a `string` parameter directly in the body to support
211
- # other content types than Json when `body` argument is
212
- # provided in serialized form
213
- elif isinstance(body, str) or isinstance(body, bytes):
214
- request_body = body
215
- r = self.pool_manager.request(
216
- method, url,
217
- body=request_body,
218
- preload_content=_preload_content,
219
- timeout=timeout,
220
- headers=headers)
221
- else:
222
- # Cannot generate the request from given parameters
223
- msg = """Cannot prepare a request message for provided
224
- arguments. Please check that your arguments match
225
- declared content type."""
226
- raise ApiException(status=0, reason=msg)
227
- # For `GET`, `HEAD`
145
+ timeout = _request_timeout or 5 * 60
146
+
147
+ if 'Content-Type' not in headers:
148
+ headers['Content-Type'] = 'application/json'
149
+
150
+ args = {
151
+ "method": method,
152
+ "url": url,
153
+ "timeout": timeout,
154
+ "headers": headers
155
+ }
156
+
157
+ if self.proxy:
158
+ args["proxy"] = self.proxy
159
+ if self.proxy_headers:
160
+ args["proxy_headers"] = self.proxy_headers
161
+
162
+ # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
163
+ if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
164
+ if re.search('json', headers['Content-Type'], re.IGNORECASE):
165
+ if body is not None:
166
+ body = json.dumps(body)
167
+ args["data"] = body
168
+ elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
169
+ args["data"] = aiohttp.FormData(post_params)
170
+ elif headers['Content-Type'] == 'multipart/form-data':
171
+ # must del headers['Content-Type'], or the correct
172
+ # Content-Type which generated by aiohttp
173
+ del headers['Content-Type']
174
+ data = aiohttp.FormData()
175
+ for param in post_params:
176
+ k, v = param
177
+ if isinstance(v, tuple) and len(v) == 3:
178
+ data.add_field(
179
+ k,
180
+ value=v[1],
181
+ filename=v[0],
182
+ content_type=v[2]
183
+ )
184
+ else:
185
+ data.add_field(k, v)
186
+ args["data"] = data
187
+
188
+ # Pass a `bytes` parameter directly in the body to support
189
+ # other content types than Json when `body` argument is provided
190
+ # in serialized form
191
+ elif isinstance(body, bytes):
192
+ args["data"] = body
228
193
  else:
229
- r = self.pool_manager.request(method, url,
230
- fields={},
231
- preload_content=_preload_content,
232
- timeout=timeout,
233
- headers=headers)
234
- except urllib3.exceptions.SSLError as e:
235
- msg = "{0}\n{1}".format(type(e).__name__, str(e))
236
- raise ApiException(status=0, reason=msg)
237
-
238
- if _preload_content:
239
- r = RESTResponse(r)
240
-
241
- # log response body
242
- logger.debug("response body: %s", r.data)
243
-
244
- if not 200 <= r.status <= 299:
245
- if r.status == 400:
246
- raise BadRequestException(http_resp=r)
247
-
248
- if r.status == 401:
249
- raise UnauthorizedException(http_resp=r)
250
-
251
- if r.status == 403:
252
- raise ForbiddenException(http_resp=r)
253
-
254
- if r.status == 404:
255
- raise NotFoundException(http_resp=r)
256
-
257
- if 500 <= r.status <= 599:
258
- raise ServiceException(http_resp=r)
259
-
260
- raise ApiException(http_resp=r)
261
-
262
- return r
263
-
264
- def get_request(self, url, headers=None, query_params=None, _preload_content=True,
265
- _request_timeout=None):
266
- return self.request("GET", url,
267
- headers=headers,
268
- _preload_content=_preload_content,
269
- _request_timeout=_request_timeout,
270
- query_params=query_params)
271
-
272
- def head_request(self, url, headers=None, query_params=None, _preload_content=True,
273
- _request_timeout=None):
274
- return self.request("HEAD", url,
275
- headers=headers,
276
- _preload_content=_preload_content,
277
- _request_timeout=_request_timeout,
278
- query_params=query_params)
279
-
280
- def options_request(self, url, headers=None, query_params=None, post_params=None,
281
- body=None, _preload_content=True, _request_timeout=None):
282
- return self.request("OPTIONS", url,
283
- headers=headers,
284
- query_params=query_params,
285
- post_params=post_params,
286
- _preload_content=_preload_content,
287
- _request_timeout=_request_timeout,
288
- body=body)
289
-
290
- def delete_request(self, url, headers=None, query_params=None, body=None,
291
- _preload_content=True, _request_timeout=None):
292
- return self.request("DELETE", url,
293
- headers=headers,
294
- query_params=query_params,
295
- _preload_content=_preload_content,
296
- _request_timeout=_request_timeout,
297
- body=body)
298
-
299
- def post_request(self, url, headers=None, query_params=None, post_params=None,
300
- body=None, _preload_content=True, _request_timeout=None):
301
- return self.request("POST", url,
302
- headers=headers,
303
- query_params=query_params,
304
- post_params=post_params,
305
- _preload_content=_preload_content,
306
- _request_timeout=_request_timeout,
307
- body=body)
308
-
309
- def put_request(self, url, headers=None, query_params=None, post_params=None,
310
- body=None, _preload_content=True, _request_timeout=None):
311
- return self.request("PUT", url,
312
- headers=headers,
313
- query_params=query_params,
314
- post_params=post_params,
315
- _preload_content=_preload_content,
316
- _request_timeout=_request_timeout,
317
- body=body)
318
-
319
- def patch_request(self, url, headers=None, query_params=None, post_params=None,
320
- body=None, _preload_content=True, _request_timeout=None):
321
- return self.request("PATCH", url,
322
- headers=headers,
323
- query_params=query_params,
324
- post_params=post_params,
325
- _preload_content=_preload_content,
326
- _request_timeout=_request_timeout,
327
- body=body)
194
+ # Cannot generate the request from given parameters
195
+ msg = """Cannot prepare a request message for provided
196
+ arguments. Please check that your arguments match
197
+ declared content type."""
198
+ raise ApiException(status=0, reason=msg)
199
+
200
+ if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
201
+ pool_manager = self.retry_client
202
+ else:
203
+ pool_manager = self.pool_manager
204
+
205
+ r = await pool_manager.request(**args)
206
+
207
+ return RESTResponse(r)
208
+
209
+
210
+
211
+
212
+
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.2
2
+ Name: weheat
3
+ Version: 2025.1.15rc1
4
+ Summary: Weheat Backend client
5
+ Home-page: https://github.com/wefabricate/wh-python
6
+ Author: Jesper Raemaekers
7
+ Author-email: jesper.raemaekers@wefabricate.com
8
+ License: MIT
9
+ Keywords: OpenAPI,OpenAPI-Generator,Weheat Backend
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: urllib3<2.1.0,>=1.25.3
13
+ Requires-Dist: python-dateutil
14
+ Requires-Dist: aiohttp>=3.0.0
15
+ Requires-Dist: aiohttp-retry>=2.8.3
16
+ Requires-Dist: pydantic>=2
17
+ Requires-Dist: typing-extensions>=4.7.1
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: keywords
24
+ Dynamic: license
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
27
+
28
+ # Weheat backend client
29
+
30
+ This is a client for the Weheat backend. It is automatically generated from the OpenAPI specification.
31
+
32
+ ## Requirements.
33
+
34
+ Python 3.7+
35
+
36
+ ## Installation & Usage
37
+
38
+ You can install directly using:
39
+
40
+ ```sh
41
+ pip install weheat
42
+ ```
43
+
44
+ Then import the package:
45
+
46
+ ```python
47
+ import weheat
48
+ ```
49
+
50
+
51
+
52
+ ## Getting Started
53
+
54
+ After installation, you can now use the client to interact with the Weheat backend. Note that all methods are async as of version 2025.1.15.
55
+
56
+ ```python
57
+ import asyncio
58
+ import datetime
59
+ from keycloak import KeycloakOpenID # install with pip install python-keycloak
60
+ from weheat import ApiClient, Configuration, HeatPumpApi, HeatPumpLogApi, EnergyLogApi, UserApi
61
+
62
+ auth_url = 'https://auth.weheat.nl/auth/'
63
+ api_url = 'https://api.weheat.nl'
64
+ realm_name = 'Weheat'
65
+ my_client_id = 'WeheatCommunityAPI' # client ID and secret provided by Weheat
66
+ my_client_secret = ''
67
+ username = '' # username and password used for the online portal
68
+ password = ''
69
+ my_heat_pump_id = '' # your heat pump UUID
70
+
71
+
72
+ async def demo():
73
+ keycloak_open_id = KeycloakOpenID(server_url=auth_url,
74
+ client_id=my_client_id,
75
+ realm_name=realm_name,
76
+ client_secret_key=my_client_secret)
77
+
78
+ token_response = keycloak_open_id.token(username, password)
79
+ keycloak_open_id.logout(token_response['refresh_token'])
80
+
81
+ config = Configuration(host=api_url, access_token=token_response['access_token'])
82
+ async with ApiClient(configuration=config) as client:
83
+ response = await UserApi(client).api_v1_users_me_get_with_http_info()
84
+
85
+ if response.status_code == 200:
86
+ print(f'My user: {response.data}')
87
+
88
+ response = await HeatPumpApi(client).api_v1_heat_pumps_get_with_http_info()
89
+
90
+ if response.status_code == 200:
91
+ print(f'My heat pump: {response.data}')
92
+
93
+ response = await HeatPumpLogApi(client).api_v1_heat_pumps_heat_pump_id_logs_latest_get_with_http_info(
94
+ heat_pump_id=my_heat_pump_id)
95
+
96
+ if response.status_code == 200:
97
+ print(f'My heat pump logs: {response.data}')
98
+
99
+ response = await EnergyLogApi(client).api_v1_energy_logs_heat_pump_id_get_with_http_info(heat_pump_id=my_heat_pump_id,
100
+ start_time=datetime.datetime(2024, 6,
101
+ 22, 0, 0,
102
+ 0),
103
+ end_time=datetime.datetime(2024, 6, 22,
104
+ 15, 0, 0),
105
+ interval='Hour')
106
+
107
+ if response.status_code == 200:
108
+ print(f'My energy logs: {response.data}')
109
+
110
+ asyncio.get_event_loop().run_until_complete(demo())
111
+
112
+ ```
113
+
114
+
115
+
@@ -0,0 +1,37 @@
1
+ weheat/__init__.py,sha256=C24j_uAbbyEzqh8DmQyORUrVMdFDlzEv4jGClf4TY34,1765
2
+ weheat/api_client.py,sha256=Bh5xHchRGCcsHbT3rXrcmhKCMiumi1_2L6n0Q57KODw,24803
3
+ weheat/api_response.py,sha256=A7O_XgliD6y7jEv82fgIaxR3T8KiwaOqHR6djpZyh_o,674
4
+ weheat/configuration.py,sha256=bSjeNWmg5SuxyULRbUR9KCuPK9yHSYCbrZlIbY6UiAo,14278
5
+ weheat/exceptions.py,sha256=jZjLtEMBKFpLTdV1GPsbQSPriG1ilgMSodGnhEKlWh4,5913
6
+ weheat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ weheat/rest.py,sha256=r0ot19pStSTb4Nwco98UeSsppIQAR8FxQecABO4y9aY,6598
8
+ weheat/abstractions/__init__.py,sha256=cRdA_kyTIooo39I13_mqShSfZMqdzNGHbmrnITqgx6A,161
9
+ weheat/abstractions/auth.py,sha256=VCAxJ4OIj7bsYttqJl5-juU0VUlSd3xPu7kUjtHZr3U,979
10
+ weheat/abstractions/discovery.py,sha256=Z1_tLFJzZkbA8CYIs2KpxoMB7S6Xc-V8jRk-LQeX1UE,2287
11
+ weheat/abstractions/heat_pump.py,sha256=MwOXkUSJaq820clvM-iYaeMmYn4YfR7k6ML3pPR5nbY,9787
12
+ weheat/abstractions/user.py,sha256=gQ5hSthG7c434lqsliI91uBOIkUQ8uTdRmCV-e7Y_Gk,663
13
+ weheat/api/__init__.py,sha256=zZ_Xqek8VY6gARsJK6hRess0qqGii-Ls1uXm92k0jPE,244
14
+ weheat/api/energy_log_api.py,sha256=plZ9YoQ9O39qufzr3B6CduuzJMY1K6iJS5f5AXN-vUo,17587
15
+ weheat/api/heat_pump_api.py,sha256=tyFA-Xf-Ld4cAVSYblCq1s5I1CDRQmKJx9UBJivnS0Y,28211
16
+ weheat/api/heat_pump_log_api.py,sha256=mz3KuVuNv0e1zadTpGYCnXtrCMMHIV6xjQ6TfZCib-s,45295
17
+ weheat/api/user_api.py,sha256=r08h_5HTwfY7M5eyf9FJs4VTSxIQCafqY-zYR78KTM4,12438
18
+ weheat/models/__init__.py,sha256=oG2r1nESdcGRBOBqEOLDCLUWCUs6KY2aePwjfQFd5H8,1092
19
+ weheat/models/boiler_type.py,sha256=ebfHlTCNRns2c85imgwWBB5whZxeQ0jgiZYM98dSjB4,1011
20
+ weheat/models/device_state.py,sha256=rM99B_K1dEbn6Wk610MbvROBl5Dv4YJ2mKLjIzEZXn8,1151
21
+ weheat/models/dhw_type.py,sha256=J-_bh0JZnbOG5o6_pn7oXctmZh0wIs4bnqJrNlF_lc0,928
22
+ weheat/models/energy_view_dto.py,sha256=pjQp9PSbJ32NvOe5bgPOdshDeCd-CZiWquuxMdVCC5Y,9139
23
+ weheat/models/heat_pump_log_view_dto.py,sha256=liughLpkpeTY3AoqTWNOk-zZXMhSn0gLm5c88apCvZE,65838
24
+ weheat/models/heat_pump_model.py,sha256=9rPJuJ-xLxJyqdAdj1EfJric4EqMof6ICOeZSsLoSlo,1178
25
+ weheat/models/heat_pump_status_enum.py,sha256=HxgJJgOWnP_aVHMUWEHwEOxP5LMxJTt7hHhzd9KwCMg,1106
26
+ weheat/models/heat_pump_type.py,sha256=WnOTgYf-NXlKNSsQwri9CThSYoW5Q1CK_jlhPMKKW4c,919
27
+ weheat/models/raw_heat_pump_log_dto.py,sha256=7IEV_l0sgPZrBTgPP-jt0HrXPOTGxDyKWuk7qC5shHw,38697
28
+ weheat/models/read_all_heat_pump_dto.py,sha256=44vte5hTUysrT7gkBFNYxz09TWSsuH10KP4-w03ax4M,5670
29
+ weheat/models/read_heat_pump_dto.py,sha256=7jH2MwMVKv_dOoJUUsKE8sTR5mROqco71OzOAFjgWTY,5111
30
+ weheat/models/read_user_dto.py,sha256=OIsWQZcdByN94ViSv0DjFHORRsMnkQ93jc-gJuudRdg,4018
31
+ weheat/models/read_user_me_dto.py,sha256=Ger6qKlbZBBvG_4MStl6aEMkrBBJIjQtV6pvK1-lw28,4441
32
+ weheat/models/role.py,sha256=6KUInAmkhoxEdWxBo9jBHXiy_nqJHrKXVeh8RvQPEYM,1101
33
+ weheat-2025.1.15rc1.dist-info/LICENSE,sha256=rWmFUq0uth2jpet-RQ2QPd2VhZkcPSUs6Dxfmbqkbis,1068
34
+ weheat-2025.1.15rc1.dist-info/METADATA,sha256=6-x-xTzWRV5bFUC4lUNjN1PULmzeH7IpHtnsHu3F8cM,3877
35
+ weheat-2025.1.15rc1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
36
+ weheat-2025.1.15rc1.dist-info/top_level.txt,sha256=hLzdyvGZ9rs4AqK7U48mdHx_-FcP5sDuTSleDUvGAZw,7
37
+ weheat-2025.1.15rc1.dist-info/RECORD,,