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