aas-http-client 0.1.4__tar.gz → 0.1.6__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.

Potentially problematic release.


This version of aas-http-client might be problematic. Click here for more details.

Files changed (22) hide show
  1. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/PKG-INFO +5 -4
  2. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/README.md +4 -3
  3. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client/__init__.py +9 -3
  4. aas_http_client-0.1.6/aas_http_client/client.py +545 -0
  5. aas_http_client-0.1.6/aas_http_client/demo/demo_process.py +74 -0
  6. aas_http_client-0.1.6/aas_http_client/demo/logging_handler.py +177 -0
  7. aas_http_client-0.1.6/aas_http_client/utilities/__init__.py +0 -0
  8. aas_http_client-0.1.6/aas_http_client/utilities/model_builder.py +120 -0
  9. aas_http_client-0.1.6/aas_http_client/wrapper/sdk_wrapper.py +272 -0
  10. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client.egg-info/PKG-INFO +5 -4
  11. aas_http_client-0.1.6/aas_http_client.egg-info/SOURCES.txt +19 -0
  12. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/pyproject.toml +1 -1
  13. aas_http_client-0.1.6/tests/test_client_dotnet_server.py +290 -0
  14. aas_http_client-0.1.6/tests/test_client_java_server.py +263 -0
  15. aas_http_client-0.1.4/aas_http_client.egg-info/SOURCES.txt +0 -11
  16. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/LICENSE +0 -0
  17. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client/core/encoder.py +0 -0
  18. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client/core/version_check.py +0 -0
  19. /aas_http_client-0.1.4/aas_http_client/client.py → /aas_http_client-0.1.6/aas_http_client/wrapper/python_sdk_wrapper_tmp.py +0 -0
  20. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client.egg-info/dependency_links.txt +0 -0
  21. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/aas_http_client.egg-info/top_level.txt +0 -0
  22. {aas_http_client-0.1.4 → aas_http_client-0.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aas-http-client
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Generic HTTP client for communicating with various types of AAS servers
5
5
  Author-email: Daniel Klein <daniel.klein@em.ag>
6
6
  License: MIT License
@@ -32,20 +32,19 @@ Dynamic: license-file
32
32
 
33
33
  <!-- TODO: Go through the readme and enter the information here -->
34
34
 
35
- # BaSyx Python PoC
35
+ # AAS HTTP Client
36
36
 
37
37
  <div align="center">
38
38
  <!-- change this to your projects logo if you have on.
39
39
  If you don't have one it might be worth trying chatgpt dall-e to create one for you...
40
40
  -->
41
- <img src="docs/assets/fluid_logo.svg" alt="basyx_python_poc" width=500 />
41
+ <img src="docs/assets/fluid_logo.svg" alt="aas_http_client" width=500 />
42
42
  </div>
43
43
 
44
44
  ---
45
45
 
46
46
  [![License: em](https://img.shields.io/badge/license-emSL-%23f8a602?label=License&labelColor=%23992b2e)](LICENSES/LicenseRef-em.txt)
47
47
  [![CI](https://github.com/engineering-methods/basyx_python_poc/actions/workflows/ci.yml/badge.svg?branch=master&cache-bust=1)](https://github.com/engineering-methods/basyx_python_poc/actions)
48
- [![codecov](https://codecov.io/gh/engineering-methods/basyx_python_poc/branch/master/graph/badge.svg)](https://codecov.io/gh/engineering-methods/basyx_python_poc)
49
48
 
50
49
  Proof of concept for a AAS application using the BaSyx Python SDK and Python AAS Server.
51
50
 
@@ -58,3 +57,5 @@ Proof of concept for a AAS application using the BaSyx Python SDK and Python AAS
58
57
  👨‍⚕️ [Troubleshooting](docs/troubleshooting.md)
59
58
 
60
59
  🤖 [Releases](https://github.com/engineering-methods/basyx_python_poc/releases)
60
+
61
+ 📦 [Pypi Packages](https://pypi.org/project/aas-http-client/)
@@ -1,19 +1,18 @@
1
1
  <!-- TODO: Go through the readme and enter the information here -->
2
2
 
3
- # BaSyx Python PoC
3
+ # AAS HTTP Client
4
4
 
5
5
  <div align="center">
6
6
  <!-- change this to your projects logo if you have on.
7
7
  If you don't have one it might be worth trying chatgpt dall-e to create one for you...
8
8
  -->
9
- <img src="docs/assets/fluid_logo.svg" alt="basyx_python_poc" width=500 />
9
+ <img src="docs/assets/fluid_logo.svg" alt="aas_http_client" width=500 />
10
10
  </div>
11
11
 
12
12
  ---
13
13
 
14
14
  [![License: em](https://img.shields.io/badge/license-emSL-%23f8a602?label=License&labelColor=%23992b2e)](LICENSES/LicenseRef-em.txt)
15
15
  [![CI](https://github.com/engineering-methods/basyx_python_poc/actions/workflows/ci.yml/badge.svg?branch=master&cache-bust=1)](https://github.com/engineering-methods/basyx_python_poc/actions)
16
- [![codecov](https://codecov.io/gh/engineering-methods/basyx_python_poc/branch/master/graph/badge.svg)](https://codecov.io/gh/engineering-methods/basyx_python_poc)
17
16
 
18
17
  Proof of concept for a AAS application using the BaSyx Python SDK and Python AAS Server.
19
18
 
@@ -26,3 +25,5 @@ Proof of concept for a AAS application using the BaSyx Python SDK and Python AAS
26
25
  👨‍⚕️ [Troubleshooting](docs/troubleshooting.md)
27
26
 
28
27
  🤖 [Releases](https://github.com/engineering-methods/basyx_python_poc/releases)
28
+
29
+ 📦 [Pypi Packages](https://pypi.org/project/aas-http-client/)
@@ -4,13 +4,19 @@ import importlib.metadata
4
4
  __copyright__ = f"Copyright (C) {datetime.now().year} :em engineering methods AG. All rights reserved."
5
5
  __author__ = "Daniel Klein"
6
6
 
7
- __version__ = importlib.metadata.version(__name__)
7
+ try:
8
+ __version__ = importlib.metadata.version(__name__)
9
+ except importlib.metadata.PackageNotFoundError:
10
+ __version__ = "0.0.0-dev"
11
+
8
12
  __project__ = "aas-http-client"
9
13
  __package__ = "aas-http-client"
10
14
 
11
15
  from aas_http_client.core.version_check import check_for_update
12
- from aas_http_client.client import create_client_by_config, create_client_by_url, AasxServerInterface
16
+ from aas_http_client.client import create_client_by_config, create_client_by_url, AasHttpClient
17
+ from aas_http_client.utilities import model_builder
18
+ from aas_http_client.wrapper.sdk_wrapper import create_wrapper_by_config, create_wrapper_by_url, SdkWrapper
13
19
 
14
20
  check_for_update()
15
21
 
16
- __all__ = ["create_client_by_config", "create_client_by_url", "AasxServerInterface"]
22
+ __all__ = ["create_client_by_config", "create_client_by_url", "AasHttpClient", "model_builder", "create_wrapper_by_config", "create_wrapper_by_url", "SdkWrapper"]
@@ -0,0 +1,545 @@
1
+ """Client for HTTP API communication with AAS server."""
2
+ import json
3
+ import logging
4
+ import time
5
+ from pathlib import Path
6
+
7
+ import basyx.aas.adapter.json
8
+ import basyx.aas.adapter.json.json_serialization as js
9
+ import requests
10
+ from basyx.aas.model import Reference, Submodel
11
+ from aas_http_client.core.encoder import decode_base_64
12
+ from pydantic import BaseModel, PrivateAttr, ValidationError
13
+ from requests import Session
14
+ from requests.auth import HTTPBasicAuth
15
+ from requests.models import Response
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ STATUS_CODE_200 = 200
20
+ STATUS_CODE_201 = 201
21
+ STATUS_CODE_202 = 202
22
+ STATUS_CODE_204 = 204
23
+ HEADERS = {"Content-Type": "application/json"}
24
+
25
+
26
+ def log_response_errors(response: Response):
27
+ """Create error messages from the response and log them.
28
+
29
+ :param response: response
30
+ """
31
+ result_error_messages: list[str] = []
32
+
33
+ try:
34
+ response_content_dict: dict = json.loads(response.content)
35
+
36
+ if "detail" in response_content_dict:
37
+ detail: dict = response_content_dict.get("detail", {})
38
+ if "error" in detail:
39
+ error: str = detail.get("error", "")
40
+ result_error_messages.append(f"{error}")
41
+ else:
42
+ result_error_messages.append(f"{detail}")
43
+
44
+ elif "messages" in response_content_dict or "Messages" in response_content_dict:
45
+ messages: list = response_content_dict.get("messages", [])
46
+
47
+ if not messages:
48
+ messages = response_content_dict.get("Messages", [])
49
+
50
+ for message in messages:
51
+ if isinstance(message, dict) and "message" in message:
52
+ result_error_messages.append(message["message"])
53
+ else:
54
+ result_error_messages.append(str(message))
55
+ elif "error" in response_content_dict:
56
+ result_error_messages.append(response_content_dict.get("error", ""))
57
+
58
+ except json.JSONDecodeError:
59
+ result_error_messages.append(response.content)
60
+
61
+ logger.error(f"Status code: {response.status_code}")
62
+ for result_error_message in result_error_messages:
63
+ logger.error(result_error_message)
64
+
65
+
66
+ class AasHttpClient(BaseModel):
67
+ """Represents a AasHttpClient to communicate with a REST API."""
68
+
69
+ base_url: str = "http://javaaasserver:5060/"
70
+ api_base_path: str = ""
71
+ username: str | None = None
72
+ _password: str | None = PrivateAttr(default=None)
73
+ https_proxy: str | None = None
74
+ http_proxy: str | None = None
75
+ time_out: int = 200
76
+ connection_time_out: int = 100
77
+ ssl_verify: bool = True
78
+ _session: Session = PrivateAttr(default=None)
79
+
80
+ def initialize(self, password: str):
81
+ """Initialize the AasHttpClient with the given URL, username and password.
82
+
83
+ :param password: password
84
+ """
85
+ self._password = password
86
+
87
+ if self.base_url.endswith("/"):
88
+ self.base_url = self.base_url[:-1]
89
+
90
+ self._session = requests.Session()
91
+ self._session.auth = HTTPBasicAuth(self.username, self._password)
92
+ self._session.verify = self.ssl_verify
93
+
94
+ if self.https_proxy:
95
+ self._session.proxies.update({"https": self.https_proxy})
96
+ if self.http_proxy:
97
+ self._session.proxies.update({"http": self.http_proxy})
98
+
99
+ def get_root(self) -> dict | None:
100
+ """Get the root of the REST API.
101
+
102
+ :return: root data as a dictionary or None if an error occurred
103
+ """
104
+ url = f"{self.base_url}/shells"
105
+
106
+ try:
107
+ response = self._session.get(url, headers=HEADERS, timeout=2)
108
+ logger.debug(f"Call REST API url '{response.url}'")
109
+
110
+ if response.status_code != STATUS_CODE_200:
111
+ log_response_errors(response)
112
+ return None
113
+
114
+ except requests.exceptions.RequestException as e:
115
+ logger.error(f"Error call REST API: {e}")
116
+ return None
117
+
118
+ content = response.content.decode("utf-8")
119
+ return json.loads(content)
120
+
121
+ def post_shells(self, aas_data: dict) -> dict | None:
122
+ """Post an Asset Administration Shell (AAS) to the REST API.
123
+
124
+ :param aas_data: Json data of the Asset Administration Shell to post
125
+ :return: Response data as a dictionary or None if an error occurred
126
+ """
127
+ url = f"{self.base_url}/shells"
128
+ logger.debug(f"Call REST API url '{url}'")
129
+
130
+ try:
131
+ response = self._session.post(url, headers=HEADERS, json=aas_data, timeout=self.time_out)
132
+ logger.debug(f"Call REST API url '{response.url}'")
133
+
134
+ if response.status_code not in (STATUS_CODE_201, STATUS_CODE_202):
135
+ log_response_errors(response)
136
+ return None
137
+
138
+ except requests.exceptions.RequestException as e:
139
+ logger.error(f"Error call REST API: {e}")
140
+ return None
141
+
142
+ content = response.content.decode("utf-8")
143
+ return json.loads(content)
144
+
145
+ def put_shells(self, identifier: str, aas_data: dict) -> bool:
146
+ """Update an Asset Administration Shell (AAS) by its ID in the REST API.
147
+
148
+ :param identifier: Identifier of the AAS to update
149
+ :param aas_data: Json data of the Asset Administration Shell data to update
150
+ :return: True if the update was successful, False otherwise
151
+ """
152
+ decoded_identifier: str = decode_base_64(identifier)
153
+ url = f"{self.base_url}/shells/{decoded_identifier}"
154
+
155
+ try:
156
+ response = self._session.put(url, headers=HEADERS, json=aas_data, timeout=self.time_out)
157
+ logger.debug(f"Call REST API url '{response.url}'")
158
+
159
+ if response.status_code is not STATUS_CODE_204:
160
+ log_response_errors(response)
161
+ return False
162
+
163
+ except requests.exceptions.RequestException as e:
164
+ logger.error(f"Error call REST API: {e}")
165
+ return False
166
+
167
+ return True
168
+
169
+ def put_shells_submodels_by_id(self, aas_id: str, submodel_id: str, submodel_data: dict) -> bool:
170
+ """Update a submodel by its ID for a specific Asset Administration Shell (AAS).
171
+
172
+ :param aas_id: ID of the AAS to update the submodel for
173
+ :param submodel_data: Json data to the Submodel to update
174
+ :return: True if the update was successful, False otherwise
175
+ """
176
+ decoded_aas_id: str = decode_base_64(aas_id)
177
+ decoded_submodel_id: str = decode_base_64(submodel_id)
178
+ url = f"{self.base_url}/shells/{decoded_aas_id}/submodels/{decoded_submodel_id}"
179
+
180
+ try:
181
+ response = self._session.put(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
182
+ logger.debug(f"Call REST API url '{response.url}'")
183
+
184
+ if response.status_code != STATUS_CODE_204:
185
+ log_response_errors(response)
186
+ return False
187
+
188
+ except requests.exceptions.RequestException as e:
189
+ logger.error(f"Error call REST API: {e}")
190
+ return False
191
+
192
+ return True
193
+
194
+ def get_shells(self) -> list[dict] | None:
195
+ """Get all Asset Administration Shells (AAS) from the REST API.
196
+
197
+ :return: List of paginated Asset Administration Shells data or None if an error occurred
198
+ """
199
+ url = f"{self.base_url}/shells"
200
+
201
+ try:
202
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
203
+ logger.debug(f"Call REST API url '{response.url}'")
204
+
205
+ if response.status_code != STATUS_CODE_200:
206
+ log_response_errors(response)
207
+ return None
208
+
209
+ except requests.exceptions.RequestException as e:
210
+ logger.error(f"Error call REST API: {e}")
211
+ return None
212
+
213
+ content = response.content.decode("utf-8")
214
+ return json.loads(content)
215
+
216
+ def get_shells_by_id(self, aas_id: str) -> dict | None:
217
+ """Get an Asset Administration Shell (AAS) by its ID from the REST API.
218
+
219
+ :param aas_id: ID of the AAS to retrieve
220
+ :return: Asset Administration Shells data or None if an error occurred
221
+ """
222
+ decoded_aas_id: str = decode_base_64(aas_id)
223
+ url = f"{self.base_url}/shells/{decoded_aas_id}"
224
+
225
+ try:
226
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
227
+ logger.debug(f"Call REST API url '{response.url}'")
228
+
229
+ if response.status_code != STATUS_CODE_200:
230
+ log_response_errors(response)
231
+ return None
232
+
233
+ except requests.exceptions.RequestException as e:
234
+ logger.error(f"Error call REST API: {e}")
235
+ return None
236
+
237
+ content = response.content.decode("utf-8")
238
+ return json.loads(content)
239
+
240
+
241
+ def get_shells_reference_by_id(self, aas_id: str) -> Reference | None:
242
+ decoded_aas_id: str = decode_base_64(aas_id)
243
+ url = f"{self.base_url}/shells/{decoded_aas_id}/$reference"
244
+
245
+ try:
246
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
247
+ logger.debug(f"Call REST API url '{response.url}'")
248
+
249
+ if response.status_code != STATUS_CODE_200:
250
+ log_response_errors(response)
251
+ return None
252
+
253
+ except requests.exceptions.RequestException as e:
254
+ logger.error(f"Error call REST API: {e}")
255
+ return None
256
+
257
+ ref_dict_string = response.content.decode("utf-8")
258
+ return json.loads(ref_dict_string, cls=basyx.aas.adapter.json.AASFromJsonDecoder)
259
+
260
+ def get_shells_submodels_by_id(self, aas_id: str, submodel_id: str) -> Submodel | None:
261
+ """Get a submodel by its ID for a specific Asset Administration Shell (AAS).
262
+
263
+ :param aas_id: ID of the AAS to retrieve the submodel from
264
+ :param submodel_id: ID of the submodel to retrieve
265
+ :return: Submodel object or None if an error occurred
266
+ """
267
+ decoded_aas_id: str = decode_base_64(aas_id)
268
+ decoded_submodel_id: str = decode_base_64(submodel_id)
269
+
270
+ url = f"{self.base_url}/shells/{decoded_aas_id}/submodels/{decoded_submodel_id}"
271
+ #/shells/{aasIdentifier}/submodels/{submodelIdentifier}
272
+
273
+ try:
274
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
275
+ logger.debug(f"Call REST API url '{response.url}'")
276
+
277
+ if response.status_code != STATUS_CODE_200:
278
+ log_response_errors(response)
279
+ return None
280
+
281
+ except requests.exceptions.RequestException as e:
282
+ logger.error(f"Error call REST API: {e}")
283
+ return None
284
+
285
+ content = response.content.decode("utf-8")
286
+ return json.loads(content)
287
+
288
+ def delete_shells_by_id(self, aas_id: str) -> bool:
289
+ """Get an Asset Administration Shell (AAS) by its ID from the REST API.
290
+
291
+ :param aas_id: ID of the AAS to retrieve
292
+ :return: True if the deletion was successful, False otherwise
293
+ """
294
+ decoded_aas_id: str = decode_base_64(aas_id)
295
+ url = f"{self.base_url}/shells/{decoded_aas_id}"
296
+
297
+ try:
298
+ response = self._session.delete(url, headers=HEADERS, timeout=self.time_out)
299
+ logger.debug(f"Call REST API url '{response.url}'")
300
+
301
+ if response.status_code != STATUS_CODE_204:
302
+ log_response_errors(response)
303
+ return False
304
+
305
+ except requests.exceptions.RequestException as e:
306
+ logger.error(f"Error call REST API: {e}")
307
+ return False
308
+
309
+ return True
310
+
311
+ def post_submodels(self, submodel_data: dict) -> dict:
312
+ """Post a submodel to the REST API.
313
+
314
+ :param submodel_data: Json data of the Submodel to post
315
+ :return: Response data as a dictionary or None if an error occurred
316
+ """
317
+ url = f"{self.base_url}/submodels"
318
+
319
+ try:
320
+ response = self._session.post(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
321
+ logger.debug(f"Call REST API url '{response.url}'")
322
+
323
+ if response.status_code not in (STATUS_CODE_201, STATUS_CODE_202):
324
+ log_response_errors(response)
325
+ return False
326
+
327
+ except requests.exceptions.RequestException as e:
328
+ logger.error(f"Error call REST API: {e}")
329
+ return False
330
+
331
+ content = response.content.decode("utf-8")
332
+ return json.loads(content)
333
+
334
+ def put_submodels_by_id(self, identifier: str, submodel_data: dict) -> bool:
335
+ """Update a submodel by its ID in the REST API.
336
+
337
+ :param identifier: Identifier of the submodel to update
338
+ :param submodel_data: Json data of the Submodel to update
339
+ :return: True if the update was successful, False otherwise
340
+ """
341
+ decoded_identifier: str = decode_base_64(identifier)
342
+ url = f"{self.base_url}/submodels/{decoded_identifier}"
343
+
344
+ try:
345
+ response = self._session.put(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
346
+ logger.debug(f"Call REST API url '{response.url}'")
347
+
348
+ if response.status_code != STATUS_CODE_204:
349
+ log_response_errors(response)
350
+ return False
351
+
352
+ except requests.exceptions.RequestException as e:
353
+ logger.error(f"Error call REST API: {e}")
354
+ return False
355
+
356
+ return True
357
+
358
+ def get_submodels(self) -> list[dict] | None:
359
+ """Get all submodels from the REST API.
360
+
361
+ :return: Submodel objects or None if an error occurred
362
+ """
363
+ url = f"{self.base_url}/submodels"
364
+
365
+ try:
366
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
367
+ logger.debug(f"Call REST API url '{response.url}'")
368
+
369
+ if response.status_code != STATUS_CODE_200:
370
+ log_response_errors(response)
371
+ return None
372
+
373
+ except requests.exceptions.RequestException as e:
374
+ logger.error(f"Error call REST API: {e}")
375
+ return None
376
+
377
+ content = response.content.decode("utf-8")
378
+ return json.loads(content)
379
+
380
+ def get_submodels_by_id(self, submodel_id: str) -> dict | None:
381
+ """Get a submodel by its ID from the REST API.
382
+
383
+ :param submodel_id: ID of the submodel to retrieve
384
+ :return: Submodel object or None if an error occurred
385
+ """
386
+ decoded_submodel_id: str = decode_base_64(submodel_id)
387
+ url = f"{self.base_url}/submodels/{decoded_submodel_id}"
388
+
389
+ try:
390
+ response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
391
+ logger.debug(f"Call REST API url '{response.url}'")
392
+
393
+ if response.status_code != STATUS_CODE_200:
394
+ log_response_errors(response)
395
+ return None
396
+
397
+ except requests.exceptions.RequestException as e:
398
+ logger.error(f"Error call REST API: {e}")
399
+ return None
400
+
401
+ content = response.content.decode("utf-8")
402
+ return json.loads(content)
403
+
404
+ def patch_submodel_by_id(self, submodel_id: str, submodel_data: dict):
405
+ decoded_submodel_id: str = decode_base_64(submodel_id)
406
+ url = f"{self.base_url}/submodels/{decoded_submodel_id}"
407
+
408
+ try:
409
+ response = self._session.patch(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
410
+ logger.debug(f"Call REST API url '{response.url}'")
411
+
412
+ if response.status_code != STATUS_CODE_204:
413
+ log_response_errors(response)
414
+ return False
415
+
416
+ except requests.exceptions.RequestException as e:
417
+ logger.error(f"Error call REST API: {e}")
418
+ return False
419
+
420
+ return True
421
+
422
+ def delete_submodels_by_id(self, submodel_id: str) -> bool:
423
+ """Delete a submodel by its ID from the REST API.
424
+
425
+ :param submodel_id: ID of the submodel to delete
426
+ :return: True if the deletion was successful, False otherwise
427
+ """
428
+ decoded_submodel_id: str = decode_base_64(submodel_id)
429
+ url = f"{self.base_url}/submodels/{decoded_submodel_id}"
430
+
431
+ try:
432
+ response = self._session.delete(url, headers=HEADERS, timeout=self.time_out)
433
+ logger.debug(f"Call REST API url '{response.url}'")
434
+
435
+ if response.status_code != STATUS_CODE_204:
436
+ log_response_errors(response)
437
+ return False
438
+
439
+ except requests.exceptions.RequestException as e:
440
+ logger.error(f"Error call REST API: {e}")
441
+ return False
442
+
443
+ return True
444
+
445
+
446
+ def create_client_by_url(
447
+ base_url: str,
448
+ api_base_path: str = "",
449
+ username: str = "",
450
+ password: str = "",
451
+ http_proxy: str = "",
452
+ https_proxy: str = "",
453
+ time_out: int = 200,
454
+ connection_time_out: int = 60,
455
+ ssl_verify: str = True, # noqa: FBT002
456
+ ) -> AasHttpClient | None:
457
+ """Create a AAS HTTP client from the given parameters.
458
+
459
+ :param base_url: base URL of the BaSyx server, e.g. "http://basyx_python_server:80/"_
460
+ :param username: username for the BaSyx server interface client, defaults to ""_
461
+ :param password: password for the BaSyx server interface client, defaults to ""_
462
+ :param http_proxy: http proxy URL, defaults to ""_
463
+ :param https_proxy: https proxy URL, defaults to ""_
464
+ :param time_out: timeout for the API calls, defaults to 200
465
+ :param connection_time_out: timeout for the connection to the API, defaults to 60
466
+ :param ssl_verify: whether to verify SSL certificates, defaults to True
467
+ :return: An instance of AasHttpClient initialized with the provided parameters.
468
+ """
469
+ logger.info(f"Create BaSyx server interface client from URL '{base_url}'")
470
+ config_dict: dict[str, str] = {}
471
+ config_dict["base_url"] = base_url
472
+ config_dict["api_base_path"] = api_base_path
473
+ config_dict["username"] = username
474
+ config_dict["http_proxy"] = http_proxy
475
+ config_dict["https_proxy"] = https_proxy
476
+ config_dict["time_out"] = time_out
477
+ config_dict["connection_time_out"] = connection_time_out
478
+ config_dict["ssl_verify"] = ssl_verify
479
+ config_string = json.dumps(config_dict, indent=4)
480
+ return _create_client(config_string, password)
481
+
482
+
483
+ def create_client_by_config(config_file: Path, password: str = "") -> AasHttpClient | None:
484
+ """Create a AAS HTTP client from the given parameters.
485
+
486
+ :param config_file: Path to the configuration file containing the BaSyx server connection settings.
487
+ :param password: password for the BaSyx server interface client, defaults to ""_
488
+ :return: An instance of HttpClient initialized with the provided parameters.
489
+ """
490
+ logger.info(f"Create BaSyx server interface client from config file '{config_file}'")
491
+ if not config_file.exists():
492
+ config_string = "{}"
493
+ logger.warning(f"Server config file '{config_file}' not found. Using default config.")
494
+ else:
495
+ config_string = config_file.read_text(encoding="utf-8")
496
+ logger.debug(f"Server config file '{config_file}' found.")
497
+
498
+ return _create_client(config_string, password)
499
+
500
+
501
+ def _create_client(config_string: str, password) -> AasHttpClient | None:
502
+ try:
503
+ connection_settings = AasHttpClient.model_validate_json(config_string)
504
+ client = AasHttpClient(**connection_settings.model_dump())
505
+ except ValidationError as ve:
506
+ raise ValidationError(f"Invalid BaSyx server connection file: {ve}") from ve
507
+
508
+ logger.info(
509
+ f"Using server configuration: '{client.base_url}' | "
510
+ f"API base path: '{client.api_base_path}' | "
511
+ f"timeout: '{client.time_out}' | "
512
+ f"username: '{client.username}' | "
513
+ f"https_proxy: '{client.https_proxy}' | "
514
+ f"http_proxy: '{client.http_proxy}' | "
515
+ f"connection_timeout: '{client.connection_time_out}'"
516
+ )
517
+ client.initialize(password)
518
+
519
+ # test the connection to the REST API
520
+ connected = _connect_to_api(client)
521
+
522
+ if not connected:
523
+ return None
524
+
525
+ return client
526
+
527
+
528
+ def _connect_to_api(client: AasHttpClient) -> bool:
529
+ start_time = time.time()
530
+ logger.debug(f"Try to connect to REST API '{client.base_url}' for {client.connection_time_out} seconds")
531
+ counter: int = 0
532
+ while True:
533
+ try:
534
+ root = client.get_root()
535
+ if root:
536
+ logger.info(f"Connected to REST API at '{client.base_url}' successfully.")
537
+ return True
538
+ except requests.exceptions.ConnectionError:
539
+ pass
540
+ if time.time() - start_time > client.connection_time_out:
541
+ raise TimeoutError(f"Connection to REST API timed out after {client.connection_time_out} seconds.")
542
+
543
+ counter += 1
544
+ logger.warning(f"Retrying connection (attempt: {counter})")
545
+ time.sleep(1)