weheat 2025.1.14__py3-none-any.whl → 2025.1.15__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.

weheat/rest.py CHANGED
@@ -14,143 +14,133 @@
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
67
-
68
- # cert_reqs
69
- if configuration.verify_ssl:
70
- cert_reqs = ssl.CERT_REQUIRED
71
- else:
72
- cert_reqs = ssl.CERT_NONE
53
+ def __init__(self, configuration) -> None:
73
54
 
74
- addition_pool_args = {}
75
- if configuration.assert_hostname is not None:
76
- addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501
55
+ # maxsize is number of requests to host that are allowed in parallel
56
+ maxsize = configuration.connection_pool_maxsize
77
57
 
78
- if configuration.retries is not None:
79
- addition_pool_args['retries'] = configuration.retries
80
-
81
- if configuration.tls_server_name:
82
- addition_pool_args['server_hostname'] = configuration.tls_server_name
58
+ self._client_session = configuration.client_session
59
+ # only setup SSL when the session is not provided. Otherwise, it will do disk access and HA will get mad.
60
+ if not configuration.client_session:
61
+ ssl_context = ssl.create_default_context(
62
+ cafile=configuration.ssl_ca_cert
63
+ )
64
+ if configuration.cert_file:
65
+ ssl_context.load_cert_chain(
66
+ configuration.cert_file, keyfile=configuration.key_file
67
+ )
83
68
 
69
+ if not configuration.verify_ssl:
70
+ ssl_context.check_hostname = False
71
+ ssl_context.verify_mode = ssl.CERT_NONE
84
72
 
85
- if configuration.socket_options is not None:
86
- addition_pool_args['socket_options'] = configuration.socket_options
73
+ connector = aiohttp.TCPConnector(
74
+ limit=maxsize,
75
+ ssl=ssl_context
76
+ )
87
77
 
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
93
-
94
- # 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
118
- )
78
+ # https pool manager
79
+ self.pool_manager = aiohttp.ClientSession(
80
+ connector=connector,
81
+ trust_env=True
82
+ )
119
83
  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
84
+ self.pool_manager = configuration.client_session
85
+
86
+ self.proxy = configuration.proxy
87
+ self.proxy_headers = configuration.proxy_headers
88
+
89
+ retries = configuration.retries
90
+ if retries is not None:
91
+ self.retry_client = aiohttp_retry.RetryClient(
92
+ client_session=self.pool_manager,
93
+ retry_options=aiohttp_retry.ExponentialRetry(
94
+ attempts=retries,
95
+ factor=0.0,
96
+ start_timeout=0.0,
97
+ max_timeout=120.0
98
+ )
128
99
  )
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.
100
+ else:
101
+ self.retry_client = None
102
+
103
+ async def close(self):
104
+ # do not close the session if provided via client_session
105
+ if self._client_session:
106
+ return
107
+ await self.pool_manager.close()
108
+ if self.retry_client is not None:
109
+ await self.retry_client.close()
110
+
111
+ async def request(
112
+ self,
113
+ method,
114
+ url,
115
+ headers=None,
116
+ body=None,
117
+ post_params=None,
118
+ _request_timeout=None
119
+ ):
120
+ """Execute request
134
121
 
135
122
  :param method: http request method
136
123
  :param url: http request url
137
- :param query_params: query parameters in the url
138
124
  :param headers: http request headers
139
125
  :param body: request json body, for `application/json`
140
126
  :param post_params: request post parameters,
141
127
  `application/x-www-form-urlencoded`
142
128
  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
129
  :param _request_timeout: timeout setting for this request. If one
147
130
  number provided, it will be total request
148
131
  timeout. It can also be a pair (tuple) of
149
132
  (connection, read) timeouts.
150
133
  """
151
134
  method = method.upper()
152
- assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
153
- 'PATCH', 'OPTIONS']
135
+ assert method in [
136
+ 'GET',
137
+ 'HEAD',
138
+ 'DELETE',
139
+ 'POST',
140
+ 'PUT',
141
+ 'PATCH',
142
+ 'OPTIONS'
143
+ ]
154
144
 
155
145
  if post_params and body:
156
146
  raise ApiValueError(
@@ -160,168 +150,71 @@ class RESTClientObject:
160
150
  post_params = post_params or {}
161
151
  headers = headers or {}
162
152
  # 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`
153
+ timeout = _request_timeout or 5 * 60
154
+
155
+ if 'Content-Type' not in headers:
156
+ headers['Content-Type'] = 'application/json'
157
+
158
+ args = {
159
+ "method": method,
160
+ "url": url,
161
+ "timeout": timeout,
162
+ "headers": headers
163
+ }
164
+
165
+ if self.proxy:
166
+ args["proxy"] = self.proxy
167
+ if self.proxy_headers:
168
+ args["proxy_headers"] = self.proxy_headers
169
+
170
+ # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
171
+ if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
172
+ if re.search('json', headers['Content-Type'], re.IGNORECASE):
173
+ if body is not None:
174
+ body = json.dumps(body)
175
+ args["data"] = body
176
+ elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
177
+ args["data"] = aiohttp.FormData(post_params)
178
+ elif headers['Content-Type'] == 'multipart/form-data':
179
+ # must del headers['Content-Type'], or the correct
180
+ # Content-Type which generated by aiohttp
181
+ del headers['Content-Type']
182
+ data = aiohttp.FormData()
183
+ for param in post_params:
184
+ k, v = param
185
+ if isinstance(v, tuple) and len(v) == 3:
186
+ data.add_field(
187
+ k,
188
+ value=v[1],
189
+ filename=v[0],
190
+ content_type=v[2]
191
+ )
192
+ else:
193
+ data.add_field(k, v)
194
+ args["data"] = data
195
+
196
+ # Pass a `bytes` parameter directly in the body to support
197
+ # other content types than Json when `body` argument is provided
198
+ # in serialized form
199
+ elif isinstance(body, bytes):
200
+ args["data"] = body
228
201
  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)
202
+ # Cannot generate the request from given parameters
203
+ msg = """Cannot prepare a request message for provided
204
+ arguments. Please check that your arguments match
205
+ declared content type."""
206
+ raise ApiException(status=0, reason=msg)
207
+
208
+ if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
209
+ pool_manager = self.retry_client
210
+ else:
211
+ pool_manager = self.pool_manager
212
+
213
+ r = await pool_manager.request(**args)
214
+
215
+ return RESTResponse(r)
216
+
217
+
218
+
219
+
220
+
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.2
2
+ Name: weheat
3
+ Version: 2025.1.15
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=8D0zAgx7B6YmVNuyS0AarcCAl5TmKKH655jHqWrvSl0,14531
5
+ weheat/exceptions.py,sha256=jZjLtEMBKFpLTdV1GPsbQSPriG1ilgMSodGnhEKlWh4,5913
6
+ weheat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ weheat/rest.py,sha256=hSt8T6NQfdrgz_7Dxo9U-DO5ZEgUVVG38OUErThWqlU,7086
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=PrhyM29OKvCgKzWig5BAjaEF15CIcTp_AIBZg2lyJ6Y,2384
11
+ weheat/abstractions/heat_pump.py,sha256=DFgKF_4Mdv4dOVugECy9xB4y6e85B-HwZatrUBSnhPE,9920
12
+ weheat/abstractions/user.py,sha256=L48CGvBe7YrUa-wnIgmv9Hu0DOMN4IXN0fSmufrlJIc,760
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.15.dist-info/LICENSE,sha256=rWmFUq0uth2jpet-RQ2QPd2VhZkcPSUs6Dxfmbqkbis,1068
34
+ weheat-2025.1.15.dist-info/METADATA,sha256=uXTKgpecpXb3GrH0QzN_ktTloByEMScxyP2huHnTGqY,3874
35
+ weheat-2025.1.15.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
36
+ weheat-2025.1.15.dist-info/top_level.txt,sha256=hLzdyvGZ9rs4AqK7U48mdHx_-FcP5sDuTSleDUvGAZw,7
37
+ weheat-2025.1.15.dist-info/RECORD,,