AAS HTTP Client Documentation
Loading...
Searching...
No Matches
experimental_implementation.py
Go to the documentation of this file.
1"""Experimental implementation of Asset Administration Shell Registry related API calls."""
2
3import logging
4import mimetypes
5from pathlib import Path
6from typing import TYPE_CHECKING, Any
7
8from aas_http_client.utilities.constants import LogIntensity
9
10if TYPE_CHECKING:
11 from aas_http_client.classes.client.aas_client import AasHttpClient
12
13import requests
14from pydantic import BaseModel
15
16from aas_http_client.utilities.encoder import encode_base_64
18 STATUS_CODE_200,
19 STATUS_CODE_201,
20 STATUS_CODE_204,
21 STATUS_CODE_404,
22 log_response,
23)
24
25_logger = logging.getLogger(__name__)
26
27
29 """Implementation of Asset Administration Shell Registry related API calls."""
30
31 def __init__(self, client: "AasHttpClient"):
32 """Initializes the ExperimentalImplementation with the given client."""
33 self._client = client
34
35 session = client.get_session()
36 if session is None:
37 raise ValueError(
38 "HTTP session is not initialized in the client. Call 'initialize()' method of the client before creating ExperimentalImplementation instance."
39 )
40
41 self._session: requests.Session = session
42
43 # GET /concept-descriptions
45 """Retrieves concept descriptions. Experimental feature - may not be supported by all servers."""
46 url = f"{self._client.base_url}/concept-descriptions"
47
48 self._client.set_token() # ensures Authorization header is set
49
50 try:
51 response = self._session.get(url, timeout=self._client.time_out)
52 _logger.debug(f"Call REST API url '{response.url}'")
53
54 if response.status_code == STATUS_CODE_404:
55 if self._client.get_log_intensity() == LogIntensity.HIGH:
56 _logger.warning("Concept descriptions not found.")
57 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
58 _logger.debug("Concept descriptions not found.")
59
60 _logger.debug(response.text)
61 return None
62
63 if response.status_code != STATUS_CODE_200:
64 log_response(response)
65 return None
66
67 except requests.exceptions.RequestException as e:
68 _logger.error(f"Error calling REST API: {e}")
69 return None
70
71 return response.json()
72
73 # GET /concept-descriptions/{conceptDescriptionIdentifier}
74 def get_concept_description_by_id(self, concept_description_identifier: str):
75 """Retrieves a specific concept description by its identifier. Experimental feature - may not be supported by all servers.
76
77 :param concept_description_identifier: The unique identifier of the concept description
78 :return: Concept description data as a dictionary or None if not found
79 """
80 url = f"{self._client.base_url}/concept-descriptions/{concept_description_identifier}"
81
82 self._client.set_token() # ensures Authorization header is set
83
84 try:
85 response = self._session.get(url, timeout=self._client.time_out)
86 _logger.debug(f"Call REST API url '{response.url}'")
87
88 if response.status_code == STATUS_CODE_404:
89 if self._client.get_log_intensity() == LogIntensity.HIGH:
90 _logger.warning(f"Concept description with id '{concept_description_identifier}' not found.")
91 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
92 _logger.debug(f"Concept description with id '{concept_description_identifier}' not found.")
93
94 _logger.debug(response.text)
95 return None
96
97 if response.status_code != STATUS_CODE_200:
98 log_response(response)
99 return None
100
101 except requests.exceptions.RequestException as e:
102 _logger.error(f"Error calling REST API: {e}")
103 return None
104
105 return response.json()
106
107 # POST /concept-descriptions
108 def post_concept_description(self, concept_description: dict) -> bool:
109 """Creates a new concept description. Experimental feature - may not be supported by all servers.
110
111 :param concept_description: Concept description data as a dictionary
112 :return: True if creation was successful, False otherwise
113 """
114 url = f"{self._client.base_url}/concept-descriptions"
115
116 self._client.set_token() # ensures Authorization header is set
117
118 try:
119 response = self._session.post(url, json=concept_description, timeout=self._client.time_out)
120 _logger.debug(f"Call REST API url '{response.url}'")
121
122 if response.status_code == STATUS_CODE_404:
123 if self._client.get_log_intensity() == LogIntensity.HIGH:
124 _logger.warning("Concept descriptions endpoint not found.")
125 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
126 _logger.debug("Concept descriptions endpoint not found.")
127
128 _logger.debug(response.text)
129 return False
130
131 if response.status_code != STATUS_CODE_201:
132 log_response(response)
133 return False
134
135 except requests.exceptions.RequestException as e:
136 _logger.error(f"Error calling REST API: {e}")
137 return False
138
139 return True
140
141 # DELETE /concept-descriptions/{conceptDescriptionIdentifier}
142 def delete_concept_description_by_id(self, concept_description_identifier: str) -> bool:
143 """Deletes a specific concept description by its identifier. Experimental feature - may not be supported by all servers.
144
145 :param concept_description_identifier: The unique identifier of the concept description
146 :return: True if deletion was successful, False otherwise
147 """
148 url = f"{self._client.base_url}/concept-descriptions/{concept_description_identifier}"
149
150 self._client.set_token() # ensures Authorization header is set
151
152 try:
153 response = self._session.delete(url, timeout=self._client.time_out)
154 _logger.debug(f"Call REST API url '{response.url}'")
155
156 if response.status_code == STATUS_CODE_404:
157 if self._client.get_log_intensity() == LogIntensity.HIGH:
158 _logger.warning(f"Concept description with id '{concept_description_identifier}' not found.")
159 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
160 _logger.debug(f"Concept description with id '{concept_description_identifier}' not found.")
161
162 _logger.debug(response.text)
163 return False
164
165 if response.status_code != STATUS_CODE_204:
166 log_response(response)
167 return False
168
169 except requests.exceptions.RequestException as e:
170 _logger.error(f"Error calling REST API: {e}")
171 return False
172
173 return True
174
175 # GET /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
176 def get_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> bytes | None:
177 """Downloads file content from a specific submodel element from the Submodel at a specified path. Experimental feature - may not be supported by all servers.
178
179 :param submodel_identifier: The Submodels unique id
180 :param id_short_path: IdShort path to the submodel element (dot-separated)
181 :return: Attachment file data as bytes (octet-stream) or None if an error occurred
182 """
183 if not self._client.encoded_ids:
184 submodel_identifier = encode_base_64(submodel_identifier)
186 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
187
188 self._client.set_token() # ensures Authorization header is set
189
190 try:
191 response = self._session.get(url, timeout=self._client.time_out)
192 _logger.debug(f"Call REST API url '{response.url}'")
193
194 if response.status_code == STATUS_CODE_404:
195 if self._client.get_log_intensity() == LogIntensity.HIGH:
196 _logger.warning(
197 f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' or file content not found."
198 )
199 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
200 _logger.debug(
201 f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' or file content not found."
202 )
203
204 _logger.debug(response.text)
205 return None
206
207 if response.status_code != STATUS_CODE_200:
208 log_response(response)
209 return None
210
211 except requests.exceptions.RequestException as e:
212 _logger.error(f"Error calling REST API: {e}")
213 return None
214
215 return response.content
216
217 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
218 def post_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str, file: Path) -> bool:
219 """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.
220
221 :param submodel_identifier: The Submodels unique id
222 :param id_short_path: IdShort path to the submodel element (dot-separated)
223 :param file: Path to the file to upload as attachment
224 :return: Attachment data as bytes or None if an error occurred
225 """
226 if file.exists() is False or not file.is_file():
227 _logger.error(f"Attachment file '{file}' does not exist.")
228 return False
229
230 if not self._client.encoded_ids:
231 submodel_identifier = encode_base_64(submodel_identifier)
232
233 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
234
235 self._client.set_token()
236
237 try:
238 mime_type, _ = mimetypes.guess_type(file)
239
240 with file.open("rb") as f:
241 files = {"file": (file.name, f, mime_type or "application/octet-stream")}
242 response = self._session.post(url, files=files, timeout=self._client.time_out)
243
244 _logger.debug(f"Call REST API url '{response.url}'")
245
246 if response.status_code == STATUS_CODE_404:
247 if self._client.get_log_intensity() == LogIntensity.HIGH:
248 _logger.warning(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
249 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
250 _logger.debug(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
251
252 _logger.debug(response.text)
253 return False
254
255 # original dotnet server delivers 200 instead of 204
256 if response.status_code not in (STATUS_CODE_200, STATUS_CODE_204):
257 log_response(response)
258 return False
259
260 except requests.exceptions.RequestException as e:
261 _logger.error(f"Error call REST API: {e}")
262 return False
263
264 return True
265
266 # POST /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
268 self, submodel_identifier: str, id_short_path: str, file_octet_stream: Any, mime_type: str = "application/octet-stream"
269 ) -> bool:
270 """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.
271
272 :param submodel_identifier: The Submodels unique id
273 :param id_short_path: IdShort path to the submodel element (dot-separated)
274 :param file_octet_stream: File content as a byte stream
275 :return: Attachment data as bytes or None if an error occurred
276 """
277 if file_octet_stream is None:
278 _logger.error("Attachment file stream is None.")
279 return False
280
281 if not self._client.encoded_ids:
282 submodel_identifier = encode_base_64(submodel_identifier)
283
284 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
285
286 self._client.set_token()
287
288 try:
289 files = {"file": ("attachment", file_octet_stream, mime_type or "application/octet-stream")}
290 response = self._session.post(url, files=files, timeout=self._client.time_out)
291
292 _logger.debug(f"Call REST API url '{response.url}'")
293
294 if response.status_code == STATUS_CODE_404:
295 if self._client.get_log_intensity() == LogIntensity.HIGH:
296 _logger.warning(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
297 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
298 _logger.debug(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
299
300 _logger.debug(response.text)
301 return False
302
303 # original dotnet server delivers 200 instead of 204
304 if response.status_code not in (STATUS_CODE_200, STATUS_CODE_204):
305 log_response(response)
306 return False
307
308 except requests.exceptions.RequestException as e:
309 _logger.error(f"Error call REST API: {e}")
310 return False
311
312 return True
313
314 # PUT /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
315 def put_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str, file: Path) -> bool:
316 """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.
317
318 :param submodel_identifier: The Submodels unique id
319 :param id_short_path: IdShort path to the submodel element (dot-separated)
320 :param file: Path to the file to upload as attachment
321 :return: Attachment data as bytes or None if an error occurred
322 """
323 if file.exists() is False or not file.is_file():
324 _logger.error(f"Attachment file '{file}' does not exist.")
325 return False
326
327 if not self._client.encoded_ids:
328 submodel_identifier = encode_base_64(submodel_identifier)
329
330 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
331
332 self._client.set_token()
333
334 try:
335 mime_type, _ = mimetypes.guess_type(file)
336
337 with file.open("rb") as f:
338 files = {"file": (file.name, f, mime_type or "application/octet-stream")}
339 response = self._session.put(url, files=files, timeout=self._client.time_out)
340
341 _logger.debug(f"Call REST API url '{response.url}'")
342
343 if response.status_code == STATUS_CODE_404:
344 if self._client.get_log_intensity() == LogIntensity.HIGH:
345 _logger.warning(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
346 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
347 _logger.debug(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
348
349 _logger.debug(response.text)
350 return False
351
352 # original dotnet server delivers 200 instead of 204
353 if response.status_code not in (STATUS_CODE_200, STATUS_CODE_204):
354 log_response(response)
355 return False
356
357 except requests.exceptions.RequestException as e:
358 _logger.error(f"Error call REST API: {e}")
359 return False
360
361 return True
362
363 # PUT /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
365 self, submodel_identifier: str, id_short_path: str, file_octet_stream: Any, mime_type: str = "application/octet-stream"
366 ) -> bool:
367 """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.
368
369 :param submodel_identifier: The Submodels unique id
370 :param id_short_path: IdShort path to the submodel element (dot-separated)
371 :param file_octet_stream: File content as a byte stream
372 :return: Attachment data as bytes or None if an error occurred
373 """
374 if file_octet_stream is None:
375 _logger.error("Attachment file stream is None.")
376 return False
377
378 if not self._client.encoded_ids:
379 submodel_identifier = encode_base_64(submodel_identifier)
380
381 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
382
383 self._client.set_token()
384
385 try:
386 files = {"file": ("attachment", file_octet_stream, mime_type or "application/octet-stream")}
387 response = self._session.put(url, files=files, timeout=self._client.time_out)
388
389 _logger.debug(f"Call REST API url '{response.url}'")
390
391 if response.status_code == STATUS_CODE_404:
392 if self._client.get_log_intensity() == LogIntensity.HIGH:
393 _logger.warning(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
394 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
395 _logger.debug(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
396
397 _logger.debug(response.text)
398 return False
399
400 # original dotnet server delivers 200 instead of 204
401 if response.status_code not in (STATUS_CODE_200, STATUS_CODE_204):
402 log_response(response)
403 return False
404
405 except requests.exceptions.RequestException as e:
406 _logger.error(f"Error call REST API: {e}")
407 return False
408
409 return True
410
411 # DELETE /submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment
412 def delete_file_by_path_submodel_repo(self, submodel_identifier: str, id_short_path: str) -> bool:
413 """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.
414
415 :param submodel_identifier: The Submodels unique id
416 :param id_short_path: IdShort path to the submodel element (dot-separated)
417 :return: True if deletion was successful, False otherwise
418 """
419 if not self._client.encoded_ids:
420 submodel_identifier = encode_base_64(submodel_identifier)
422 url = f"{self._client.base_url}/submodels/{submodel_identifier}/submodel-elements/{id_short_path}/attachment"
423
424 self._client.set_token()
425
426 try:
427 response = self._session.delete(url, timeout=self._client.time_out)
428 _logger.debug(f"Call REST API url '{response.url}'")
429
430 if response.status_code == 404:
431 if self._client.get_log_intensity() == LogIntensity.HIGH:
432 _logger.warning(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
433 elif self._client.get_log_intensity() == LogIntensity.STANDARD:
434 _logger.debug(f"Submodel with id '{submodel_identifier}' or Submodel element with IDShort path '{id_short_path}' not found.")
435
436 return False
437
438 if response.status_code != STATUS_CODE_200:
439 log_response(response)
440 return False
441
442 except requests.exceptions.RequestException as e:
443 _logger.error(f"Error calling REST API: {e}")
444 return False
445
446 return True
447
448 def __post_multipart(self, url, files) -> requests.Response:
449 headers = dict(self._session.headers)
450 headers.pop("Content-Type", None)
451 return self._session.post(url, headers=headers, files=files)
Implementation of Asset Administration Shell Registry related API calls.
bool 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 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 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...
bytes|None 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.
get_concept_description_by_id(self, str concept_description_identifier)
Retrieves a specific concept description by its identifier.
bool 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 post_concept_description(self, dict concept_description)
Creates a new concept description.
__init__(self, "AasHttpClient" client)
Initializes the ExperimentalImplementation with the given client.
bool 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 delete_concept_description_by_id(self, str concept_description_identifier)
Deletes a specific concept description by its identifier.