AAS HTTP Client Documentation
Loading...
Searching...
No Matches
sdk_wrapper.py
Go to the documentation of this file.
1"""BaSyx Server interface for REST API communication."""
2
3import json
4import logging
5from enum import Enum
6from pathlib import Path
7from typing import Any
8
9import puremagic
10from basyx.aas import model
11
12from aas_http_client.classes.client.aas_client import AasHttpClient, _create_client
13from aas_http_client.classes.wrapper.attachment import Attachment
14from aas_http_client.classes.wrapper.pagination import (
15 ReferencePaginatedData,
16 ShellPaginatedData,
17 SubmodelElementPaginatedData,
18 SubmodelPaginatedData,
19 create_reference_paging_data,
20 create_shell_paging_data,
21 create_submodel_element_paging_data,
22 create_submodel_paging_data,
23)
24from aas_http_client.utilities.constants import LogIntensity
25from aas_http_client.utilities.sdk_tools import convert_to_dict as _to_dict
26from aas_http_client.utilities.sdk_tools import convert_to_object as _to_object
27
28_logger = logging.getLogger(__name__)
29
30
31class IdEncoding(Enum):
32 """Determines the ID encoding mode for API requests."""
33
34 default = 0
35 encoded = 1
36 decoded = 2
38 def __str__(self) -> str:
39 """String representation of the IdMode enum."""
40 if self == IdEncoding.encoded:
41 return "encoded"
42 if self == IdEncoding.decoded:
43 return "decoded"
44
45 return ""
46
47
48class Level(Enum):
49 """Determines the structural depth of the respective resource content."""
50
51 default = 0
52 core = 1
53 deep = 2
55 def __str__(self) -> str:
56 """String representation of the Level enum."""
57 if self == Level.core:
58 return "core"
59 if self == Level.deep:
60 return "deep"
61
62 return ""
63
64
65class Extent(Enum):
66 """Determines to which extent the resource is being serialized."""
67
68 default = 0
69 with_blob_value = 1
70 without_blob_value = 2
72 def __str__(self) -> str:
73 """String representation of the Extent enum."""
74 if self == Extent.with_blob_value:
75 return "withBlobValue"
76 if self == Extent.without_blob_value:
77 return "withoutBlobValue"
78
79 return ""
80
81
82class AssetKind(Enum):
83 """Determines to which asset kind the resource is being serialized."""
84
85 default = 0
86 instance = 1
87 not_applicable = 2
88 type = 3
90 def __str__(self) -> str:
91 """String representation of the Extent enum."""
92 if self == AssetKind.instance:
93 return "Instance"
94 if self == AssetKind.not_applicable:
95 return "NotApplicable"
96 if self == AssetKind.type:
97 return "Type"
98
99 return ""
100
101
102# region SdkWrapper
103
104
105class SdkWrapper:
106 """Represents a wrapper for the BaSyx Python SDK to communicate with a REST API."""
107
108 _client: AasHttpClient
109 base_url: str = ""
110
111 def __init__(self, configuration: dict, basic_auth_password: str = "", o_auth_client_secret: str = "", bearer_auth_token: str = ""):
112 """Initializes the wrapper with the given configuration.
113
114 :param configuration: Dictionary containing the BaSyx server connection settings.
115 :param basic_auth_password: Password for the BaSyx server interface client, defaults to "".
116 :param o_auth_client_secret: Client secret for OAuth authentication, defaults to "".
117 :param bearer_auth_token: Bearer token for authentication, defaults to "".
118 """
119 client = _create_client(configuration, basic_auth_password, o_auth_client_secret, bearer_auth_token)
120
121 if not client:
122 raise ValueError("Failed to create AAS HTTP client with the provided configuration.")
123
124 self._client_client = client
125 self.base_urlbase_url = client.base_url
126
127 def set_log_intensity(self, intensity: LogIntensity):
128 """Sets the log intensity level for the client.
129
130 :param intensity: LogIntensity level to set (Standard or High)
131 """
132 self._client_client.set_log_intensity(intensity)
133
134 def set_encoded_ids(self, encoded_ids: IdEncoding):
135 """Sets whether to use encoded IDs for API requests.
136
137 :param encoded_ids: If enabled, all IDs used in API requests have to be base64-encoded
138 """
139 if encoded_ids == IdEncoding.encoded:
140 self._client_client.encoded_ids = True
141 else:
142 self._client_client.encoded_ids = False
143
144 def get_encoded_ids(self) -> IdEncoding:
145 """Gets whether encoded IDs are used for API requests.
146
147 :return: True if encoded IDs are used, False otherwise
148 """
149 if self._client_client.encoded_ids:
150 return IdEncoding.encoded
152 return IdEncoding.decoded
153
154 def get_client(self) -> AasHttpClient:
155 """Returns the underlying AAS HTTP client.
156
157 :return: The AAS HTTP client instance.
158 """
159 return self._client_client
160
161 # endregion
162
163 # region shells
164
165 # GET /shells/{aasIdentifier}
166 def get_asset_administration_shell_by_id(self, aas_identifier: str) -> model.AssetAdministrationShell | None:
167 """Returns a specific Asset Administration Shell.
168
169 :param aas_identifier: The Asset Administration Shells unique id (decoded)
170 :return: Asset Administration Shells or None if an error occurred
171 """
172 if not self._client_client.shells:
173 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
174 return None
175
176 content = self._client_client.shells.get_asset_administration_shell_by_id(aas_identifier)
177
178 if not content:
179 _logger.warning(f"No shell found with ID '{aas_identifier}' on server.")
180 return None
181
182 return _to_object(content)
183
184 # PUT /shells/{aasIdentifier}
185 def put_asset_administration_shell_by_id(self, aas_identifier: str, aas: model.AssetAdministrationShell) -> bool:
186 """Creates or replaces an existing Asset Administration Shell.
187
188 :param aas_identifier: The Asset Administration Shells unique id (decoded)
189 :param aas: Asset Administration Shell to put
190 :return: True if the update was successful, False otherwise
191 """
192 if not self._client_client.shells:
193 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
194 return False
195
196 aas_data = _to_dict(aas)
197
198 if aas_data is None:
199 _logger.error(f"Failed to serialize Asset Administration Shell with ID '{aas_identifier}' to dictionary.")
200 return False
201
202 return self._client_client.shells.put_asset_administration_shell_by_id(aas_identifier, aas_data)
203
204 # DELETE /shells/{aasIdentifier}
205 def delete_asset_administration_shell_by_id(self, aas_identifier: str) -> bool:
206 """Deletes an Asset Administration Shell.
207
208 :param aas_identifier: The Asset Administration Shells unique id (decoded)
209 :return: True if the deletion was successful, False otherwise
210 """
211 if not self._client_client.shells:
212 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
213 return False
214
215 return self._client_client.shells.delete_asset_administration_shell_by_id(aas_identifier)
216
217 # GET /shells/{aasIdentifier}/asset-information/thumbnail
218 def get_thumbnail_aas_repository(self, aas_identifier: str) -> Attachment | None:
219 """Downloads the thumbnail of a specific Asset Administration Shell.
220
221 :param aas_identifier: The Asset Administration Shells unique id (decoded)
222 :return: Attachment object with thumbnail content as bytes (octet-stream) or None if an error occurred
223 """
224 if not self._client_client.shells:
225 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
226 return None
227
228 byte_content = self._client_client.shells.get_thumbnail_aas_repository(aas_identifier)
229
230 if not byte_content:
231 _logger.warning(f"No thumbnail found for AAS with ID '{aas_identifier}' on server.")
232 return None
233
234 return Attachment(
235 content=byte_content,
236 content_type=puremagic.from_string(byte_content, mime=True),
237 filename="thumbnail",
238 )
239
240 # PUT /shells/{aasIdentifier}/asset-information/thumbnail
241 def put_thumbnail_aas_repository(self, aas_identifier: str, file_name: str, file: Path) -> bool:
242 """Creates or updates the thumbnail of the Asset Administration Shell.
243
244 :param aas_identifier: The Asset Administration Shells unique id
245 :param file_name: The name of the thumbnail file
246 :param file: Path to the thumbnail file to upload as attachment
247 :return: True if the update was successful, False otherwise
248 """
249 if not self._client_client.shells:
250 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
251 return False
252
253 return self._client_client.shells.put_thumbnail_aas_repository(aas_identifier, file_name, file)
254
255 # DELETE /shells/{aasIdentifier}/asset-information/thumbnail
256 def delete_thumbnail_aas_repository(self, aas_identifier: str) -> bool:
257 """Deletes the thumbnail of a specific Asset Administration Shell.
258
259 :param aas_identifier: The Asset Administration Shells unique id (decoded)
260 :return: True if the deletion was successful, False otherwise
261 """
262 if not self._client_client.shells:
263 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
264 return False
265
266 return self._client_client.shells.delete_thumbnail_aas_repository(aas_identifier)
267
268 # GET /shells
270 self, asset_ids: list[dict] | None = None, id_short: str = "", limit: int = 100, cursor: str = ""
271 ) -> ShellPaginatedData | None:
272 """Returns all Asset Administration Shells.
273
274 :param assetIds: A list of specific Asset identifiers (format: {"identifier": "string", "encodedIdentifier": "string"})
275 :param idShort: The Asset Administration Shell's IdShort
276 :param limit: The maximum number of elements in the response array
277 :param cursor: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue
278 :return: List of paginated Asset Administration Shells or None if an error occurred
279 """
280 if not self._client_client.shells:
281 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
282 return None
283
284 content = self._client_client.shells.get_all_asset_administration_shells(asset_ids, id_short, limit, cursor)
285
286 if not content:
287 return None
288
289 return create_shell_paging_data(content)
290
291 # POST /shells
292 def post_asset_administration_shell(self, aas: model.AssetAdministrationShell) -> model.AssetAdministrationShell | None:
293 """Creates a new Asset Administration Shell.
294
295 :param aas: Asset Administration Shell to post
296 :return: Asset Administration Shell or None if an error occurred
297 """
298 if not self._client_client.shells:
299 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
300 return None
301
302 aas_data = _to_dict(aas)
303 if aas_data is None:
304 return None
305
306 content = self._client_client.shells.post_asset_administration_shell(aas_data)
307 if not content:
308 return None
309
310 return _to_object(content)
311
312 # GET /shells/{aasIdentifier}/submodel-refs
313 def get_all_submodel_references_aas_repository(self, aas_identifier: str, limit: int = 100, cursor: str = "") -> ReferencePaginatedData | None:
314 """Returns all submodel references.
315
316 :param aas_identifier: The Asset Administration Shells unique id
317 :param limit: The maximum number of elements in the response array
318 :param cursor: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue
319 :return: List of paginated Submodel References or None if an error occurred
320 """
321 if not self._client_client.shells:
322 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
323 return None
324
325 references_result = self._client_client.shells.get_all_submodel_references_aas_repository(aas_identifier, limit, cursor)
326
327 if not references_result:
328 return None
329
330 return create_reference_paging_data(references_result)
331
332 # POST /shells/{aasIdentifier}/submodel-refs
333 def post_submodel_reference_aas_repository(self, aas_identifier: str, submodel_reference: model.ModelReference) -> model.ModelReference | None:
334 """Creates a submodel reference at the Asset Administration Shell.
335
336 :param aas_identifier: The Asset Administration Shells unique id
337 :param submodel_reference: Reference to the Submodel
338 :return: Reference Submodel object or None if an error occurred
339 """
340 if not self._client_client.shells:
341 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
342 return None
343
344 ref_data = _to_dict(submodel_reference)
345 if ref_data is None:
346 return None
347
348 content = self._client_client.shells.post_submodel_reference_aas_repository(aas_identifier, ref_data)
349 if not content:
350 return None
351
352 return _to_object(content)
353
354 # DELETE /shells/{aasIdentifier}/submodel-refs/{submodelIdentifier}
355 def delete_submodel_reference_by_id_aas_repository(self, aas_identifier: str, submodel_identifier: str) -> bool:
356 """Deletes the submodel reference from the Asset Administration Shell. Does not delete the submodel itself.
357
358 :param aas_identifier: The Asset Administration Shells unique id
359 :param submodel_identifier: The Submodels unique id
360 :return: True if the deletion was successful, False otherwise
361 """
362 if not self._client_client.shells:
363 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
364 return False
365
366 return self._client_client.shells.delete_submodel_reference_by_id_aas_repository(aas_identifier, submodel_identifier)
367
368 # not supported by Java Server
369
370 # PUT /shells/{aasIdentifier}/submodels/{submodelIdentifier}
371 def put_submodel_by_id_aas_repository(self, aas_identifier: str, submodel_identifier: str, submodel: model.Submodel) -> bool:
372 """Updates the Submodel.
373
374 :param aas_identifier: The Asset Administration Shells unique id (decoded)
375 :param submodel_identifier: ID of the submodel to put
376 :param submodel: Submodel to put
377 :return: True if the update was successful, False otherwise
378 """
379 if not self._client_client.shells:
380 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
381 return False
382
383 sm_data = _to_dict(submodel)
384
385 if sm_data is None:
386 return False
387
388 return self._client_client.shells.put_submodel_by_id_aas_repository(aas_identifier, submodel_identifier, sm_data)
389
390 # GET /shells/{aasIdentifier}/$reference
391 def get_asset_administration_shell_by_id_reference_aas_repository(self, aas_identifier: str) -> model.Reference | None:
392 """Returns a specific Asset Administration Shell as a Reference.
393
394 :param aas_identifier: ID of the AAS reference to retrieve
395 :return: Asset Administration Shells reference object or None if an error occurred
396 """
397 # workaround because serialization not working
398 aas = self.get_asset_administration_shell_by_id(aas_identifier)
400 if not aas:
401 return None
402
403 return model.ModelReference.from_referable(aas)
404
405 # GET /shells/{aasIdentifier}/submodels/{submodelIdentifier}
406 def get_submodel_by_id_aas_repository(self, aas_identifier: str, submodel_identifier: str) -> model.Submodel | None:
407 """Returns the Submodel.
408
409 :param aas_identifier: ID of the AAS to retrieve the submodel from
410 :param submodel_identifier: ID of the submodel to retrieve
411 :return: Submodel or None if an error occurred
412 """
413 if not self._client_client.shells:
414 _logger.error("Shell API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
415 return None
416
417 content = self._client_client.shells.get_submodel_by_id_aas_repository(aas_identifier, submodel_identifier)
418
419 if not content:
420 return None
421
422 return _to_object(content)
423
424 # endregion
425
426 # region submodels
427
428 # GET /submodels/{submodelIdentifier}
429 def get_submodel_by_id(self, submodel_identifier: str, level: Level = Level.default, extent: Extent = Extent.default) -> model.Submodel | None:
430 """Returns a specific Submodel.
431
432 :param submodel_identifier: Encoded ID of the Submodel to retrieve
433 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
434 :param extent: Determines to which extent the resource is being serialized. Available values : withBlobValue, withoutBlobValue
435 :return: Submodel data or None if an error occurred
436 """
437 if not self._client_client.submodels:
438 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
439 return None
440
441 content = self._client_client.submodels.get_submodel_by_id(submodel_identifier, str(level), str(extent))
442
443 if not content:
444 return None
445
446 return _to_object(content)
447
448 # PUT /submodels/{submodelIdentifier}
449 def put_submodels_by_id(self, submodel_identifier: str, submodel: model.Submodel) -> bool:
450 """Updates a existing Submodel.
451
452 :param submodel_identifier: Identifier of the submodel to update
453 :param submodel: Submodel data to update
454 :return: True if the update was successful, False otherwise
455 """
456 if not self._client_client.submodels:
457 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
458 return False
459
460 sm_data = _to_dict(submodel)
461
462 if sm_data is None:
463 return False
464
465 return self._client_client.submodels.put_submodels_by_id(submodel_identifier, sm_data)
466
467 # DELETE /submodels/{submodelIdentifier}
468 def delete_submodel_by_id(self, submodel_identifier: str) -> bool:
469 """Deletes a Submodel.
470
471 :param submodel_identifier: ID of the submodel to delete
472 :return: True if the deletion was successful, False otherwise
473 """
474 if not self._client_client.submodels:
475 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
476 return False
477
478 return self._client_client.submodels.delete_submodel_by_id(submodel_identifier)
479
480 # GET /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}
482 self, submodel_identifier: str, id_short_path: str, level: Level = Level.default, extent: Extent = Extent.default
483 ) -> model.SubmodelElement | None:
484 """Returns a specific submodel element from the Submodel at a specified path.
485
486 :param submodel_identifier: Encoded ID of the Submodel to retrieve element from
487 :param id_short_path: Path of the Submodel element to retrieve
488 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
489 :param extent: Determines to which extent the resource is being serialized. Available values : withBlobValue, withoutBlobValue
490 :return: Submodel element data or None if an error occurred
491 """
492 if not self._client_client.submodels:
493 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
494 return None
495
496 content = self._client_client.submodels.get_submodel_element_by_path_submodel_repo(submodel_identifier, id_short_path, str(level), str(extent))
497
498 if not content:
499 return None
500
501 return _to_object(content)
502
503 # PUT /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}
505 self, submodel_identifier: str, id_short_path: str, submodel_element: model.SubmodelElement, level: Level = Level.default
506 ) -> bool:
507 """Updates a submodel element at a specified path within the submodel elements hierarchy.
508
509 :param submodel_identifier: Encoded ID of the Submodel to update element for
510 :param id_short_path: Path of the Submodel element to update
511 :param request_body: Submodel element data to update as dictionary
512 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
513 :return: True if the update was successful, False otherwise
514 """
515 if not self._client_client.submodels:
516 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
517 return False
518
519 sme_data = _to_dict(submodel_element)
520
521 if sme_data is None:
522 return False
523
524 return self._client_client.submodels.put_submodel_element_by_path_submodel_repo(submodel_identifier, id_short_path, sme_data, str(level))
525
526 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}
528 self,
529 submodel_identifier: str,
530 id_short_path: str,
531 submodel_element: model.SubmodelElement,
532 level: Level = Level.default,
533 extent: Extent = Extent.default,
534 ) -> model.SubmodelElement | None:
535 """Creates a new submodel element at a specified path within submodel elements hierarchy.
536
537 :param submodel_identifier: Encoded ID of the submodel to create elements for
538 :param id_short_path: Path within the Submodel elements hierarchy
539 :param submodel_element: The new Submodel element
540 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
541 :param extent: Determines to which extent the resource is being serialized. Available values : withBlobValue, withoutBlobValue
542 :return: Submodel element object or None if an error occurred
543 """
544 if not self._client_client.submodels:
545 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
546 return None
547
548 sme_data = _to_dict(submodel_element)
549
550 if sme_data is None:
551 return None
552
553 content = self._client_client.submodels.post_submodel_element_by_path_submodel_repo(
554 submodel_identifier, id_short_path, sme_data, str(level), str(extent)
555 )
556
557 if not content:
558 return None
559
560 return _to_object(content)
561
562 # DELETE /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}
563 # TODO write test
564 def delete_submodel_element_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> bool:
565 """Deletes a submodel element at a specified path within the submodel elements hierarchy.
566
567 :param submodel_identifier: Encoded ID of the Submodel to delete submodel element from
568 :param id_short_path: Path of the Submodel element to delete
569 :return: True if the deletion was successful, False otherwise
570 """
571 if not self._client_client.submodels:
572 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
573 return False
574
575 return self._client_client.submodels.delete_submodel_element_by_path_submodel_repo(submodel_identifier, id_short_path)
576
577 # GET /submodels
579 self,
580 semantic_id: str = "",
581 id_short: str = "",
582 limit: int = 0,
583 cursor: str = "",
584 level: Level = Level.default,
585 extent: Extent = Extent.default,
586 ) -> SubmodelPaginatedData | None:
587 """Returns all Submodels.
588
589 :param semantic_id: The value of the semantic id reference (UTF8-BASE64-URL-encoded)
590 :param id_short: The idShort of the Submodel
591 :param limit: Maximum number of Submodels to return
592 :param cursor: Cursor for pagination
593 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
594 :param extent: Determines to which extent the resource is being serialized. Available values : withBlobValue, withoutBlobValue
595 :return: List of Submodel or None if an error occurred
596 """
597 if not self._client_client.submodels:
598 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
599 return None
600
601 content = self._client_client.submodels.get_all_submodels(semantic_id, id_short, limit, cursor, str(level), str(extent))
602
603 if not content:
604 return None
605
606 return create_submodel_paging_data(content)
607
608 # POST /submodels
609 def post_submodel(self, submodel: model.Submodel) -> model.Submodel | None:
610 """Creates a new Submodel.
611
612 :param submodel: Submodel to post
613 :return: Submodel or None if an error occurred
614 """
615 if not self._client_client.submodels:
616 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
617 return None
618
619 sm_data = _to_dict(submodel)
620
621 if sm_data is None:
622 return None
623
624 content = self._client_client.submodels.post_submodel(sm_data)
625
626 if not content:
627 return None
628
629 return _to_object(content)
630
631 # GET /submodels/{submodelIdentifier}/submodel-elements
633 self,
634 submodel_identifier: str,
635 ) -> SubmodelElementPaginatedData | None:
636 """Returns all submodel elements including their hierarchy. !!!Serialization to model.SubmodelElement currently not possible.
637
638 :param submodel_identifier: Encoded ID of the Submodel to retrieve elements from
639 :return: List of Submodel elements or None if an error occurred
640 """
641 if not self._client_client.submodels:
642 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
643 return None
644
645 content = self._client_client.submodels.get_all_submodel_elements_submodel_repository(submodel_identifier)
646
647 if not content:
648 return None
649
650 return create_submodel_element_paging_data(content)
651
652 # POST /submodels/{submodelIdentifier}/submodel-elements
653 def post_submodel_element_submodel_repo(self, submodel_identifier: str, submodel_element: model.SubmodelElement) -> model.SubmodelElement | None:
654 """Creates a new submodel element.
655
656 :param submodel_identifier: Encoded ID of the Submodel to create elements for
657 :param request_body: Submodel element
658 :return: Submodel or None if an error occurred
659 """
660 if not self._client_client.submodels:
661 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
662 return None
663
664 sme_data = _to_dict(submodel_element)
665
666 if sme_data is None:
667 return None
668
669 content = self._client_client.submodels.post_submodel_element_submodel_repo(submodel_identifier, sme_data)
670
671 if not content:
672 return None
673
674 return _to_object(content)
675
676 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/invoke
677 def invoke_operation_submodel_repo(self, submodel_identifier: str, id_short_path: str, request_body: dict, async_: str = "async") -> dict | None:
678 """Synchronously invokes an Operation at a specified path.
679
680 :param submodel_identifier: The Submodels unique id
681 :param id_short_path: IdShort path to the operation element (dot-separated)
682 :param request_body: Input parameters for the operation
683 :param async_: Determines whether an operation invocation is performed asynchronously or synchronously
684 :return: Operation result or None if an error occurred
685 """
686 if not self._client_client.submodels:
687 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
688 return None
689
690 content = self._client_client.submodels.invoke_operation_submodel_repo(submodel_identifier, id_short_path, request_body, async_)
691
692 if not content:
693 return None
694
695 return content
696
697 def get_submodel_element_by_path_value_only_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> str | None:
698 """Retrieves the value of a specific SubmodelElement.
699
700 :param submodel_identifier: The Submodels unique id
701 :param id_short_path: IdShort path to the submodel element (dot-separated)
702 :return: Submodel element value or None if an error occurred
703 """
704 if not self._client_client.submodels:
705 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
706 return None
707
708 return self._client_client.submodels.get_submodel_element_by_path_value_only_submodel_repo(submodel_identifier, id_short_path)
709
710 # PATCH /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/$value
711 def patch_submodel_element_by_path_value_only_submodel_repo(self, submodel_identifier: str, submodel_element_path: str, value: str) -> bool:
712 """Updates the value of an existing SubmodelElement.
713
714 :param submodel_identifier: Encoded ID of the Submodel to update submodel element for
715 :param submodel_element_path: Path of the Submodel element to update
716 :param value: Submodel element value to update as string
717 :return: True if the patch was successful, False otherwise
718 """
719 if not self._client_client.submodels:
720 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
721 return False
722
723 return self._client_client.submodels.patch_submodel_element_by_path_value_only_submodel_repo(submodel_identifier, submodel_element_path, value)
724
725 # GET /submodels/{submodelIdentifier}/$value
726 def get_submodel_by_id_value_only(self, submodel_identifier: str, level: Level = Level.default, extent: Extent = Extent.default) -> dict | None:
727 """Returns the value of a specific Submodel.
728
729 :param submodel_identifier: Encoded ID of the Submodel to retrieve
730 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
731 :param extent: Determines to which extent the resource is being serialized. Available values : withBlobValue, withoutBlobValue
732 :return: Submodel value as dictionary or None if an error occurred
733 """
734 if not self._client_client.submodels:
735 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
736 return None
737
738 content = self._client_client.submodels.get_submodel_by_id_value_only(submodel_identifier, str(level), str(extent))
739
740 if not content:
741 return None
742
743 return content
744
745 # PATCH /submodels/{submodelIdentifier}/$value
746 def patch_submodel_by_id_value_only(self, submodel_identifier: str, request_body: dict, level: Level = Level.default) -> bool:
747 """Updates the values of an existing Submodel.
748
749 :param submodel_identifier: The Submodels unique id
750 :param request_body: Submodel values to update as dict
751 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
752 :return: True if the patch was successful, False otherwise
753 """
754 if not self._client_client.submodels:
755 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
756 return False
757
758 return self._client_client.submodels.patch_submodel_by_id_value_only(submodel_identifier, request_body, str(level))
759
760 # GET /submodels/{submodelIdentifier}/$metadata
761 def get_submodel_by_id_metadata(self, submodel_identifier: str, level: str = "") -> dict | None:
762 """Returns the metadata attributes of a specific Submodel.
763
764 :param submodel_identifier: The Submodels unique id
765 :param level: Determines the structural depth of the respective resource content. Available values : deep, core
766 :return: Metadata attributes of the Submodel as dict or None if an error occurred
767 """
768 if not self._client_client.submodels:
769 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
770 return None
771
772 content = self._client_client.submodels.get_submodel_by_id_metadata(submodel_identifier, str(level))
773
774 if not content:
775 return None
776
777 return content
778
779 # not supported by Java Server
780
781 # PATCH /submodels/{submodelIdentifier}
782 def patch_submodel_by_id(self, submodel_identifier: str, submodel: model.Submodel) -> bool:
783 """Updates an existing Submodel.
784
785 :param submodel_identifier: Encoded ID of the Submodel to delete
786 :return: True if the patch was successful, False otherwise
787 """
788 if not self._client_client.submodels:
789 _logger.error("Submodel API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
790 return False
791
792 sm_data = _to_dict(submodel)
793
794 if sm_data is None:
795 return False
796
797 return self._client_client.submodels.patch_submodel_by_id(submodel_identifier, sm_data)
798
799 # endregion
800
801 # region shell registry
802
803 # currently no SDK implementation for descriptor classes -> no implementation for wrapper
804
805 # endregion
806
807 # region experimental
808
809 # GET /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
810 def experimental_get_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> Attachment | None:
811 """Downloads file content from a specific submodel element from the Submodel at a specified path. Experimental feature - may not be supported by all servers.
812
813 :param submodel_identifier: The Submodels unique id
814 :param id_short_path: IdShort path to the submodel element (dot-separated)
815 :return: Attachment object with file content as bytes (octet-stream) or None if an error occurred
816 """
817 if not self._client_client.experimental:
818 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
819 return None
820
821 sme = self.get_submodel_element_by_path_submodel_repo(submodel_identifier, id_short_path)
822
823 if not sme or not isinstance(sme, model.File):
824 _logger.warning(f"No submodel element found at path '{id_short_path}' in submodel '{submodel_identifier}' on server.")
825 return None
826
827 byte_content = self._client_client.experimental.get_file_by_path_submodel_repo(submodel_identifier, id_short_path)
828
829 if not byte_content:
830 _logger.warning(f"No file found at path '{id_short_path}' in submodel '{submodel_identifier}' on server.")
831 return None
832
833 return Attachment(
834 content=byte_content,
835 content_type=puremagic.from_string(byte_content, mime=True),
836 filename=sme.value,
837 )
838
839 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
840 def experimental_post_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str, file: Path) -> bool:
841 """Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy. Experimental feature - may not be supported by all servers.
842
843 :param submodel_identifier: The Submodels unique id
844 :param id_short_path: IdShort path to the submodel element (dot-separated)
845 :param file: Path to the file to upload as attachment
846 :return: Attachment data as bytes or None if an error occurred
847 """
848 if not self._client_client.experimental:
849 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
850 return False
851
852 return self._client_client.experimental.post_file_by_path_submodel_repo(submodel_identifier, id_short_path, file)
853
854 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
856 self, submodel_identifier: str, id_short_path: str, file_octet_stream: Any, mime_type: str = "application/octet-stream"
857 ) -> bool:
858 """Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy. Experimental feature - may not be supported by all servers.
859
860 :param submodel_identifier: The Submodels unique id
861 :param id_short_path: IdShort path to the submodel element (dot-separated)
862 :param file_octet_stream: File content as a byte stream
863 :param mime_type: MIME type of the file content, defaults to "application/octet-stream"
864 :return: Attachment data as bytes or None if an error occurred
865 """
866 if not self._client_client.experimental:
867 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
868 return False
869
870 return self._client_client.experimental.post_file_by_path_submodel_repo_stream(submodel_identifier, id_short_path, file_octet_stream, mime_type)
871
872 # PUT /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
873 def experimental_put_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str, file: Path) -> bool:
874 """Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy. Experimental feature - may not be supported by all servers.
875
876 :param submodel_identifier: The Submodels unique id
877 :param id_short_path: IdShort path to the submodel element (dot-separated)
878 :param file: Path to the file to upload as attachment
879 :return: Attachment data as bytes or None if an error occurred
880 """
881 if not self._client_client.experimental:
882 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
883 return False
884
885 return self._client_client.experimental.put_file_by_path_submodel_repo(submodel_identifier, id_short_path, file)
886
887 # PUT /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
889 self, submodel_identifier: str, id_short_path: str, file_octet_stream: Any, mime_type: str = "application/octet-stream"
890 ) -> bool:
891 """Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy. Experimental feature - may not be supported by all servers.
892
893 :param submodel_identifier: The Submodels unique id
894 :param id_short_path: IdShort path to the submodel element (dot-separated)
895 :param file: Path to the file to upload as attachment
896 :return: Attachment data as bytes or None if an error occurred
897 """
898 if not self._client_client.experimental:
899 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
900 return False
901
902 return self._client_client.experimental.put_file_by_path_submodel_repo_stream(submodel_identifier, id_short_path, file_octet_stream, mime_type)
903
904 def experimental_delete_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> bool:
905 """Deletes file content of an existing submodel element at a specified path within submodel elements hierarchy. Experimental feature - may not be supported by all servers.
906
907 :param submodel_identifier: The Submodels unique id
908 :param id_short_path: IdShort path to the submodel element (dot-separated)
909 :return: True if deletion was successful, False otherwise
910 """
911 if not self._client_client.experimental:
912 _logger.error("Experimental API is not initialized in the client. Call 'initialize()' method of the client before calling this method.")
913 return False
914
915 return self._client_client.experimental.delete_file_by_path_submodel_repo(submodel_identifier, id_short_path)
916
917 # endregion
918
919
920# region wrapper
921
922
923def create_by_url(
924 base_url: str,
925 basic_auth_username: str = "",
926 basic_auth_password: str = "",
927 o_auth_client_id: str = "",
928 o_auth_client_secret: str = "",
929 o_auth_token_url: str = "",
930 bearer_auth_token: str = "",
931 http_proxy: str = "",
932 https_proxy: str = "",
933 time_out: int = 200,
934 connection_time_out: int = 60,
935 ssl_verify: bool = True, # noqa: FBT001, FBT002
936 trust_env: bool = True, # noqa: FBT001, FBT002
937 encoded_ids: bool = True, # noqa: FBT001, FBT002
938) -> SdkWrapper | None:
939 """Create a wrapper for a AAS server connection from the given parameters.
940
941 :param base_url: Base URL of the AAS server, e.g. "http://basyx_python_server:80/"
942 :param basic_auth_username: Username for the AAS server basic authentication, defaults to ""
943 :param basic_auth_password: Password for the AAS server basic authentication, defaults to ""
944 :param o_auth_client_id: Client ID for OAuth authentication, defaults to ""
945 :param o_auth_client_secret: Client secret for OAuth authentication, defaults to ""
946 :param o_auth_token_url: Token URL for OAuth authentication, defaults to ""
947 :param bearer_auth_token: Bearer token for authentication, defaults to ""
948 :param http_proxy: HTTP proxy URL, defaults to ""
949 :param https_proxy: HTTPS proxy URL, defaults to ""
950 :param time_out: Timeout for the API calls, defaults to 200
951 :param connection_time_out: Timeout for the connection to the API, defaults to 60
952 :param ssl_verify: Whether to verify SSL certificates, defaults to True
953 :param trust_env: Whether to trust environment variables for proxy settings, defaults to True
954 :param encoded_ids: If enabled, all IDs used in API requests have to be base64-encoded
955 :return: An instance of SdkWrapper initialized with the provided parameters or None if initialization fails
956 """
957 _logger.debug(f"Create AAS server http client from URL '{base_url}'.")
958 config_dict: dict[str, Any] = {}
959 config_dict["BaseUrl"] = base_url
960 config_dict["HttpProxy"] = http_proxy
961 config_dict["HttpsProxy"] = https_proxy
962 config_dict["TimeOut"] = str(time_out)
963 config_dict["ConnectionTimeOut"] = str(connection_time_out)
964 config_dict["SslVerify"] = str(ssl_verify)
965 config_dict["TrustEnv"] = str(trust_env)
966 config_dict["EncodedIds"] = str(encoded_ids)
967
968 config_dict["AuthenticationSettings"] = {
969 "BasicAuth": {"Username": basic_auth_username},
970 "OAuth": {
971 "ClientId": o_auth_client_id,
972 "TokenUrl": o_auth_token_url,
973 },
974 }
975
976 return create_by_dict(config_dict, basic_auth_password, o_auth_client_secret, bearer_auth_token)
977
978
979def create_by_dict(
980 configuration: dict, basic_auth_password: str = "", o_auth_client_secret: str = "", bearer_auth_token: str = ""
981) -> SdkWrapper | None:
982 """Create a wrapper for a AAS server connection from the given configuration.
983
984 :param configuration: Dictionary containing the AAS server connection settings
985 :param basic_auth_password: Password for the AAS server basic authentication, defaults to ""
986 :param o_auth_client_secret: Client secret for OAuth authentication, defaults to ""
987 :param bearer_auth_token: Bearer token for authentication, defaults to ""
988 :return: An instance of SdkWrapper initialized with the provided parameters or None if initialization fails
989 """
990 _logger.debug("Create AAS server wrapper from dictionary.")
991 return SdkWrapper(configuration, basic_auth_password, o_auth_client_secret, bearer_auth_token)
992
993
994def create_by_config(
995 config_file: Path, basic_auth_password: str = "", o_auth_client_secret: str = "", bearer_auth_token: str = ""
996) -> SdkWrapper | None:
997 """Create a wrapper for a AAS server connection from a given configuration file.
998
999 :param config_file: Path to the configuration file containing the AAS server connection settings
1000 :param basic_auth_password: Password for the AAS server basic authentication, defaults to ""
1001 :param o_auth_client_secret: Client secret for OAuth authentication, defaults to ""
1002 :param bearer_auth_token: Bearer token for authentication, defaults to ""
1003 :return: An instance of SdkWrapper initialized with the provided parameters or None if initialization fails
1004 """
1005 _logger.debug(f"Create AAS wrapper client from configuration file '{config_file}'.")
1006 if not config_file.exists():
1007 configuration = {}
1008 _logger.warning(f"Configuration file '{config_file}' not found. Using default config.")
1009 else:
1010 config_string = config_file.read_text(encoding="utf-8")
1011 try:
1012 configuration = json.loads(config_string)
1013 except json.JSONDecodeError as e:
1014 _logger.error(f"Configuration file '{config_file}' is not a valid JSON file: {e}")
1015 return None
1016 _logger.debug(f"Configuration file '{config_file}' found.")
1017 return SdkWrapper(configuration, basic_auth_password, o_auth_client_secret, bearer_auth_token)
1018
1019
1020# endregion
Determines to which asset kind the resource is being serialized.
str __str__(self)
String representation of the Extent enum.
Determines to which extent the resource is being serialized.
str __str__(self)
String representation of the Extent enum.
Determines the ID encoding mode for API requests.
str __str__(self)
String representation of the IdMode enum.
Determines the structural depth of the respective resource content.
str __str__(self)
String representation of the Level enum.
Represents a wrapper for the BaSyx Python SDK to communicate with a REST API.
bool experimental_put_file_by_path_submodel_repo(self, str submodel_identifier, str id_short_path, Path file)
Uploads file content to an existing submodel element at a specified path within submodel elements hie...
bool put_thumbnail_aas_repository(self, str aas_identifier, str file_name, Path file)
Creates or updates the thumbnail of the Asset Administration Shell.
bool experimental_delete_file_by_path_submodel_repo(self, str submodel_identifier, str id_short_path)
Deletes file content of an existing submodel element at a specified path within submodel elements hie...
bool put_asset_administration_shell_by_id(self, str aas_identifier, model.AssetAdministrationShell aas)
Creates or replaces an existing Asset Administration Shell.
bool delete_asset_administration_shell_by_id(self, str aas_identifier)
Deletes an Asset Administration Shell.
bool put_submodel_element_by_path_submodel_repo(self, str submodel_identifier, str id_short_path, model.SubmodelElement submodel_element, Level level=Level.default)
Updates a submodel element at a specified path within the submodel elements hierarchy.
bool put_submodels_by_id(self, str submodel_identifier, model.Submodel submodel)
Updates a existing Submodel.
model.SubmodelElement|None get_submodel_element_by_path_submodel_repo(self, str submodel_identifier, str id_short_path, Level level=Level.default, Extent extent=Extent.default)
Returns a specific submodel element from the Submodel at a specified path.
bool delete_thumbnail_aas_repository(self, str aas_identifier)
Deletes the thumbnail of a specific Asset Administration Shell.
bool patch_submodel_element_by_path_value_only_submodel_repo(self, str submodel_identifier, str submodel_element_path, str value)
Updates the value of an existing SubmodelElement.
model.Submodel|None get_submodel_by_id(self, str submodel_identifier, Level level=Level.default, Extent extent=Extent.default)
Returns a specific Submodel.
AasHttpClient get_client(self)
Returns the underlying AAS HTTP client.
dict|None invoke_operation_submodel_repo(self, str submodel_identifier, str id_short_path, dict request_body, str async_="async")
Synchronously invokes an Operation at a specified path.
bool patch_submodel_by_id_value_only(self, str submodel_identifier, dict request_body, Level level=Level.default)
Updates the values of an existing Submodel.
model.AssetAdministrationShell|None get_asset_administration_shell_by_id(self, str aas_identifier)
Returns a specific Asset Administration Shell.
ReferencePaginatedData|None get_all_submodel_references_aas_repository(self, str aas_identifier, int limit=100, str cursor="")
Returns all submodel references.
Attachment|None experimental_get_file_by_path_submodel_repo(self, str submodel_identifier, str id_short_path)
Downloads file content from a specific submodel element from the Submodel at a specified path.
SubmodelElementPaginatedData|None get_all_submodel_elements_submodel_repository(self, str submodel_identifier)
Returns all submodel elements including their hierarchy.
bool experimental_put_file_by_path_submodel_repo_stream(self, str submodel_identifier, str id_short_path, Any file_octet_stream, str mime_type="application/octet-stream")
Uploads file content to an existing submodel element at a specified path within submodel elements hie...
bool experimental_post_file_by_path_submodel_repo_stream(self, str submodel_identifier, str id_short_path, Any file_octet_stream, str mime_type="application/octet-stream")
Uploads file content to an existing submodel element at a specified path within submodel elements hie...
__init__(self, dict configuration, str basic_auth_password="", str o_auth_client_secret="", str bearer_auth_token="")
Initializes the wrapper with the given configuration.
set_encoded_ids(self, IdEncoding encoded_ids)
Sets whether to use encoded IDs for API requests.
SubmodelPaginatedData|None get_all_submodels(self, str semantic_id="", str id_short="", int limit=0, str cursor="", Level level=Level.default, Extent extent=Extent.default)
Returns all Submodels.
model.Reference|None get_asset_administration_shell_by_id_reference_aas_repository(self, str aas_identifier)
Returns a specific Asset Administration Shell as a Reference.
bool patch_submodel_by_id(self, str submodel_identifier, model.Submodel submodel)
Updates an existing Submodel.
model.SubmodelElement|None post_submodel_element_submodel_repo(self, str submodel_identifier, model.SubmodelElement submodel_element)
Creates a new submodel element.
IdEncoding get_encoded_ids(self)
Gets whether encoded IDs are used for API requests.
AasHttpClient _client
dict|None get_submodel_by_id_value_only(self, str submodel_identifier, Level level=Level.default, Extent extent=Extent.default)
Returns the value of a specific Submodel.
model.SubmodelElement|None post_submodel_element_by_path_submodel_repo(self, str submodel_identifier, str id_short_path, model.SubmodelElement submodel_element, Level level=Level.default, Extent extent=Extent.default)
Creates a new submodel element at a specified path within submodel elements hierarchy.
str|None get_submodel_element_by_path_value_only_submodel_repo(self, str submodel_identifier, str id_short_path)
Retrieves the value of a specific SubmodelElement.
Attachment|None get_thumbnail_aas_repository(self, str aas_identifier)
Downloads the thumbnail of a specific Asset Administration Shell.
bool put_submodel_by_id_aas_repository(self, str aas_identifier, str submodel_identifier, model.Submodel submodel)
Updates the Submodel.
model.Submodel|None post_submodel(self, model.Submodel submodel)
Creates a new Submodel.
set_log_intensity(self, LogIntensity intensity)
Sets the log intensity level for the client.
model.AssetAdministrationShell|None post_asset_administration_shell(self, model.AssetAdministrationShell aas)
Creates a new Asset Administration Shell.
model.ModelReference|None post_submodel_reference_aas_repository(self, str aas_identifier, model.ModelReference submodel_reference)
Creates a submodel reference at the Asset Administration Shell.
dict|None get_submodel_by_id_metadata(self, str submodel_identifier, str level="")
Returns the metadata attributes of a specific Submodel.
bool delete_submodel_by_id(self, str submodel_identifier)
Deletes a Submodel.
model.Submodel|None get_submodel_by_id_aas_repository(self, str aas_identifier, str submodel_identifier)
Returns the Submodel.
bool experimental_post_file_by_path_submodel_repo(self, str submodel_identifier, str id_short_path, Path file)
Uploads file content to an existing submodel element at a specified path within submodel elements hie...
bool delete_submodel_element_by_path_submodel_repo(self, str submodel_identifier, str id_short_path)
Deletes a submodel element at a specified path within the submodel elements hierarchy.
ShellPaginatedData|None get_all_asset_administration_shells(self, list[dict]|None asset_ids=None, str id_short="", int limit=100, str cursor="")
Returns all Asset Administration Shells.
bool delete_submodel_reference_by_id_aas_repository(self, str aas_identifier, str submodel_identifier)
Deletes the submodel reference from the Asset Administration Shell.