aas-http-client 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

aas_http_client/client.py CHANGED
@@ -510,6 +510,32 @@ class AasHttpClient(BaseModel):
510
510
  content = response.content.decode("utf-8")
511
511
  return json.loads(content)
512
512
 
513
+ def post_submodel_element_by_path_submodel_repo(self, submodel_id: str, submodel_element_path: str, submodel_element_data: dict) -> dict | None:
514
+ """Creates a new submodel element at a specified path within submodel elements hierarchy.
515
+
516
+ :param submodel_id: Encoded ID of the Submodel to create elements for
517
+ :param submodel_element_path: Path within the Submodel elements hierarchy
518
+ :param submodel_element_data: Data for the new Submodel element
519
+ :return: Submodel element data or None if an error occurred
520
+ """
521
+ decoded_submodel_id: str = decode_base_64(submodel_id)
522
+ url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements/{submodel_element_path}"
523
+
524
+ try:
525
+ response = self._session.post(url, headers=HEADERS, json=submodel_element_data, timeout=self.time_out)
526
+ logger.debug(f"Call REST API url '{response.url}'")
527
+
528
+ if response.status_code != STATUS_CODE_201:
529
+ log_response_errors(response)
530
+ return None
531
+
532
+ except requests.exceptions.RequestException as e:
533
+ logger.error(f"Error call REST API: {e}")
534
+ return None
535
+
536
+ content = response.content.decode("utf-8")
537
+ return json.loads(content)
538
+
513
539
  def get_submodel_element_by_path_submodel_repo(self, submodel_id: str, submodel_element_path: str) -> dict | None:
514
540
  """Returns a specific submodel element from the Submodel at a specified path.
515
541
 
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from basyx.aas import model
7
7
 
8
8
  from aas_http_client.client import AasHttpClient, create_client_by_config
9
- from aas_http_client.utilities import model_builder
9
+ from aas_http_client.utilities import model_builder, sdk_tools
10
10
  from aas_http_client.wrapper.sdk_wrapper import SdkWrapper, create_wrapper_by_config
11
11
 
12
12
  logger = logging.getLogger(__name__)
@@ -17,6 +17,7 @@ def start() -> None:
17
17
  # create a submodel element
18
18
  sme_short_id: str = model_builder.create_unique_short_id("poc_sme")
19
19
  sme = model_builder.create_base_submodel_element_property(sme_short_id, model.datatypes.String, "Sample Value")
20
+ clone = model_builder.clone_submodel_element_property(sme)
20
21
 
21
22
  # create a submodel
22
23
  sm_short_id: str = model_builder.create_unique_short_id("poc_sm")
@@ -29,7 +30,7 @@ def start() -> None:
29
30
  aas = model_builder.create_base_ass(aas_short_id)
30
31
 
31
32
  # add submodel to AAS
32
- model_builder.add_submodel_to_aas(aas, submodel)
33
+ sdk_tools.add_submodel_to_aas(aas, submodel)
33
34
 
34
35
  wrapper = _create_sdk_wrapper(Path("./aas_http_client/demo/python_server_config.yml"))
35
36
  # dotnet_sdk_wrapper = _create_sdk_wrapper(Path("./aas_http_client/demo/dotnet_server_config.yml"))
@@ -0,0 +1,177 @@
1
+ """
2
+ Logging handler.
3
+
4
+ This module contains all methods and functions to handle the logging.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import queue
10
+ import sys
11
+ import uuid
12
+ from datetime import UTC, datetime
13
+ from logging.handlers import QueueHandler
14
+ from pathlib import Path
15
+ from typing import ClassVar
16
+
17
+ from pythonjsonlogger import jsonlogger
18
+
19
+ LOG_FOLDER: str = "./_log"
20
+ LOG_FILE_SUFFIX: str = "_log.json"
21
+
22
+
23
+ class ColorCodes:
24
+ """Define the color codes for the console output."""
25
+
26
+ grey = "\x1b[38;21m"
27
+ green = "\x1b[1;32m"
28
+ yellow = "\x1b[33;21m"
29
+ red = "\x1b[31;21m"
30
+ bold_red = "\x1b[31;1m"
31
+ blue = "\x1b[1;34m"
32
+ light_blue = "\x1b[1;36m"
33
+ purple = "\x1b[1;35m"
34
+ reset = "\x1b[0m"
35
+
36
+
37
+ class CustomConsoleFormatter(logging.Formatter):
38
+ """Custom console formatter for logging with colored level.
39
+
40
+ :param logging: formatter
41
+ """
42
+
43
+ FORMATS: ClassVar[dict] = {
44
+ logging.DEBUG: ColorCodes.blue + "%(levelname)s" + ColorCodes.reset + ": %(message)s (%(filename)s:%(lineno)d)",
45
+ logging.INFO: ColorCodes.green + "%(levelname)s" + ColorCodes.reset + ": %(message)s",
46
+ logging.WARNING: ColorCodes.yellow + "%(levelname)s" + ColorCodes.reset + ": %(message)s",
47
+ logging.ERROR: ColorCodes.red + "%(levelname)s" + ColorCodes.reset + ": %(message)s (%(filename)s:%(lineno)d)",
48
+ logging.CRITICAL: ColorCodes.bold_red + "%(levelname)s: %(message)s (%(filename)s:%(lineno)d)" + ColorCodes.reset,
49
+ }
50
+
51
+ def format(self, record) -> str:
52
+ """Format the log record.
53
+
54
+ :param record: record to format
55
+ :return: formatted record
56
+ """
57
+ log_fmt = self.FORMATS.get(record.levelno)
58
+ formatter = logging.Formatter(log_fmt)
59
+ return formatter.format(record)
60
+
61
+
62
+ def _handle_file_rotation(log_file_path: Path, max_file_count: int = 5) -> None:
63
+ log_folder: Path = log_file_path.resolve()
64
+
65
+ if max_file_count < 1:
66
+ return
67
+
68
+ if not log_folder.exists():
69
+ return
70
+
71
+ existing_log_files: list[Path] = [file for file in log_folder.iterdir() if file.name.endswith(LOG_FILE_SUFFIX)]
72
+
73
+ if len(existing_log_files) < max_file_count:
74
+ return
75
+
76
+ existing_log_files.sort(key=lambda x: x.stat().st_ctime)
77
+
78
+ files_to_delete: int = len(existing_log_files) - (max_file_count - 1)
79
+
80
+ for file in existing_log_files[:files_to_delete]:
81
+ file.unlink()
82
+
83
+ return
84
+
85
+
86
+ def initialize_logging(console_level=logging.INFO) -> Path:
87
+ """Initialize the standard logging.
88
+
89
+ :param debug_mode_status: Status of the debug mode
90
+ :param log_file_name: Name of the (path and extension)
91
+ """
92
+ log_path = Path(LOG_FOLDER).resolve()
93
+ log_path.mkdir(parents=True, exist_ok=True)
94
+
95
+ log_file_path = log_path / "api.log"
96
+
97
+ log_file_format = "%(asctime)s %(levelname)s: %(message)s (%(filename)s:%(lineno)d)"
98
+ logging.basicConfig(
99
+ filename=log_file_path,
100
+ level=logging.DEBUG,
101
+ format=log_file_format,
102
+ filemode="w",
103
+ )
104
+
105
+ # set console logging
106
+ console_handler = logging.StreamHandler()
107
+ console_handler.setLevel(console_level)
108
+ console_handler.setFormatter(CustomConsoleFormatter())
109
+ logging.getLogger("").addHandler(console_handler)
110
+
111
+ # set queue logging
112
+ log_queue: queue.Queue = queue.Queue(-1) # Use default max size
113
+ queue_handler = QueueHandler(log_queue)
114
+ logging.getLogger("").addHandler(queue_handler)
115
+
116
+ logger = logging.getLogger(__name__)
117
+ script_path = Path(sys.argv[0])
118
+ python_version = sys.version.replace("\n", "")
119
+
120
+ print("")
121
+ logger.info(f"Run script '{script_path.name.replace('.py', '')}'")
122
+ logger.info(f"Script executed by Python v{python_version}")
123
+ logger.info("Logging initialized")
124
+
125
+ return log_file_path.resolve()
126
+
127
+
128
+ def read_log_file_as_list(log_file_path: Path) -> list[dict]:
129
+ """Read the log file as a list of dictionaries (Json conform).
130
+
131
+ :param log_file_path: Path to the log file
132
+ :return: list of dictionaries (Json conform)
133
+ """
134
+ with Path.open(log_file_path, "r", encoding="utf-8") as f:
135
+ return [json.loads(line) for line in f if line.strip()]
136
+
137
+
138
+ def set_log_file(
139
+ max_log_files: int = 10,
140
+ ) -> Path:
141
+ """Set the log file.
142
+
143
+ :param max_log_files: max number of log files in folder, defaults to 5
144
+ :return: log file path
145
+ """
146
+ logger = logging.getLogger() # Get the root logger
147
+
148
+ # Remove all existing file handlers
149
+ for handler in logger.handlers[:]:
150
+ if isinstance(handler, logging.FileHandler):
151
+ logger.removeHandler(handler)
152
+ handler.close()
153
+
154
+ now = datetime.now(tz=UTC)
155
+ time_string = now.strftime("%Y-%m-%d_%H-%M-%S")
156
+
157
+ # handle log file and folder
158
+ log_path: Path = Path(LOG_FOLDER).resolve()
159
+ log_path = Path(LOG_FOLDER, "runtime").resolve()
160
+
161
+ log_path.mkdir(parents=True, exist_ok=True)
162
+ log_file_name = f"{uuid.uuid4().hex}{LOG_FILE_SUFFIX}"
163
+ log_file_path = log_path / f"{time_string}_{log_file_name}"
164
+
165
+ _handle_file_rotation(log_path, max_log_files)
166
+
167
+ # Add a new file handler with the new log file path
168
+ json_formatter = jsonlogger.JsonFormatter("%(asctime)s %(levelname)s %(name)s %(message)s %(filename)s %(lineno)d")
169
+ json_file_handler = logging.FileHandler(log_file_path, mode="w")
170
+ json_file_handler.setFormatter(json_formatter)
171
+ json_file_handler.setLevel(logging.DEBUG)
172
+ logger.addHandler(json_file_handler)
173
+
174
+ logging.info(f"Maximum log file number is: {max_log_files}") # noqa: LOG015
175
+ logging.info(f"Write log file to: '{log_file_path}'") # noqa: LOG015
176
+
177
+ return log_file_path.resolve()
@@ -39,6 +39,27 @@ def create_base_submodel_element_property(
39
39
  return sme
40
40
 
41
41
 
42
+ def clone_submodel_element_property(property: model.Property) -> model.Property:
43
+ """Clone a SubmodelElement of type Property."""
44
+ sme = model.Property(id_short=property.id_short, value_type=property.value_type, value=property.value)
45
+ sme.description = property.description
46
+ sme.display_name = property.display_name
47
+ sme.value = property.value
48
+ sme.value_type = property.value_type
49
+ sme.semantic_id = property.semantic_id
50
+ sme.qualifier = property.qualifier
51
+ sme.category = property.category
52
+ sme.extension = property.extension
53
+ sme.embedded_data_specifications = property.embedded_data_specifications
54
+ sme.namespace_element_sets = property.namespace_element_sets
55
+ sme.supplemental_semantic_id = property.supplemental_semantic_id
56
+ sme.value_id = property.value_id
57
+ sme.value_type = property.value_type
58
+ sme.parent = property.parent
59
+
60
+ return sme
61
+
62
+
42
63
  def create_base_submodel(identifier: str, id_short: str, display_name: str = "", description: str = "") -> model.Submodel:
43
64
  """Create a basic Submodel.
44
65
 
@@ -16,10 +16,18 @@ def add_submodel_to_aas(aas: model.AssetAdministrationShell, submodel: model.Sub
16
16
  :param aas: provided AssetAdministrationShell to which the Submodel should be added
17
17
  :param submodel: given Submodel to add
18
18
  """
19
- # aas.submodel.add(submodel)
20
19
  aas.submodel.add(model.ModelReference.from_referable(submodel))
21
20
 
22
21
 
22
+ def remove_submodel_from_aas(aas: model.AssetAdministrationShell, submodel: model.Submodel) -> None:
23
+ """Remove a given Submodel correctly from a provided AssetAdministrationShell.
24
+
25
+ :param aas: provided AssetAdministrationShell from which the Submodel should be removed
26
+ :param submodel: given Submodel to remove
27
+ """
28
+ aas.submodel.remove(model.ModelReference.from_referable(submodel))
29
+
30
+
23
31
  def convert_to_object(content: dict) -> Any | None:
24
32
  """Convert a dictionary to a BaSyx SDK framework object.
25
33
 
@@ -257,7 +257,7 @@ class SdkWrapper:
257
257
  return submodel_elements
258
258
 
259
259
  def post_submodel_element_submodel_repo(self, submodel_id: str, submodel_element: model.SubmodelElement) -> model.SubmodelElement | None:
260
- """Creates a new submodel element. !!!Serialization to model.SubmodelElements currently not possible.
260
+ """Creates a new submodel element.
261
261
 
262
262
  :param submodel_id: Encoded ID of the submodel to create elements for
263
263
  :param submodel_element: Submodel element to create
@@ -267,6 +267,20 @@ class SdkWrapper:
267
267
  content: dict = self._client.post_submodel_element_submodel_repo(submodel_id, sme_data)
268
268
  return _to_object(content)
269
269
 
270
+ def post_submodel_element_by_path_submodel_repo(
271
+ self, submodel_id: str, submodel_element_path: str, submodel_element: model.SubmodelElement
272
+ ) -> model.SubmodelElement | None:
273
+ """Creates a new submodel element at a specified path within submodel elements hierarchy.
274
+
275
+ :param submodel_id: Encoded ID of the submodel to create elements for
276
+ :param submodel_element_path: Path within the Submodel elements hierarchy
277
+ :param submodel_element: The new Submodel element
278
+ :return: Submodel element object or None if an error occurred
279
+ """
280
+ sme_data = _to_dict(submodel_element)
281
+ content: dict = self._client.post_submodel_element_by_path_submodel_repo(submodel_id, submodel_element_path, sme_data)
282
+ return _to_object(content)
283
+
270
284
  def get_submodel_element_by_path_submodel_repo(self, submodel_id: str, submodel_element_path: str) -> model.SubmodelElement | None:
271
285
  """Returns a specific submodel element from the Submodel at a specified path.
272
286
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aas-http-client
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Generic python HTTP client for communication with various types of AAS servers
5
5
  Author-email: Daniel Klein <daniel.klein@em.ag>
6
6
  License: # :em engineering methods AG Software License
@@ -0,0 +1,15 @@
1
+ aas_http_client/__init__.py,sha256=bBfrdXUHvukisXIj0CcnNUHUw8_7nrdnfQRve8nM_3U,982
2
+ aas_http_client/client.py,sha256=mWM1cfynFii0eZFrfSK4w10BXFemU2_aOwgdE0iCYo0,28484
3
+ aas_http_client/core/encoder.py,sha256=FS7P0FPakzFsGz70eRFDHQZFA_2nlKLlWIxavtnFrPg,660
4
+ aas_http_client/core/version_check.py,sha256=9dR0Q6jCFygH_ctj4vyrjerpHvolT87ayengZFlBWCw,708
5
+ aas_http_client/demo/demo_process.py,sha256=KkYMLdQYmGdAVfIwd1f8Zn0NqOTQUrvSLXJDjGGAz5M,3369
6
+ aas_http_client/demo/logging_handler.py,sha256=VJtZ4u3x_LhYZQtfNck7FuXhGFZm7gid0uDhvf9GjJ8,5596
7
+ aas_http_client/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ aas_http_client/utilities/model_builder.py,sha256=SxAv8DJkKksykw_2gtTV2jHu-MRzevOVzspn807_7VA,4680
9
+ aas_http_client/utilities/sdk_tools.py,sha256=CDD0mus8jOi-irgPO6dQHulmEyu8BSG_05Mol_nirK0,2008
10
+ aas_http_client/wrapper/sdk_wrapper.py,sha256=FaftHLQ97HvxNUtFO_1JizSgWbu_rsfegaMkUMWgS6c,15540
11
+ aas_http_client-0.4.2.dist-info/licenses/LICENSE,sha256=ayt4HY-Tjoe1Uvj47j6UdNq8mEufKcKFangurChIHxQ,5990
12
+ aas_http_client-0.4.2.dist-info/METADATA,sha256=Rp_HjtoKXohEC71BBe7FGhO-QHfNki-YtkisCR5PvPY,10467
13
+ aas_http_client-0.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ aas_http_client-0.4.2.dist-info/top_level.txt,sha256=vzvoz2vjeTLwpuz-Y-eEfoQ7T3byoaKshVlFMFH5NaM,16
15
+ aas_http_client-0.4.2.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- aas_http_client/__init__.py,sha256=bBfrdXUHvukisXIj0CcnNUHUw8_7nrdnfQRve8nM_3U,982
2
- aas_http_client/client.py,sha256=CtX1nlIo65-Um9AlXZ3gOfEMJwcLDJNOssxkGXAlsg0,27205
3
- aas_http_client/core/encoder.py,sha256=FS7P0FPakzFsGz70eRFDHQZFA_2nlKLlWIxavtnFrPg,660
4
- aas_http_client/core/version_check.py,sha256=9dR0Q6jCFygH_ctj4vyrjerpHvolT87ayengZFlBWCw,708
5
- aas_http_client/demo/demo_process.py,sha256=s60SBx0le1G2BU_uJ8vC5i6zTcSGQprSnYafZPUtOtE,3299
6
- aas_http_client/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- aas_http_client/utilities/model_builder.py,sha256=6pZ0mcfofkuCu24wp71vhBKyaLXJcHpU8N4PBHx_Xpo,3782
8
- aas_http_client/utilities/sdk_tools.py,sha256=QPpItZdhw8y13IgpS015vTAb8e8QfPeidUuE6-K0JIo,1637
9
- aas_http_client/wrapper/sdk_wrapper.py,sha256=67RiQ5qAgFq84817QurGX-GJBsM3QI3k4-uBC7sOQio,14807
10
- aas_http_client-0.4.0.dist-info/licenses/LICENSE,sha256=ayt4HY-Tjoe1Uvj47j6UdNq8mEufKcKFangurChIHxQ,5990
11
- aas_http_client-0.4.0.dist-info/METADATA,sha256=o5uysJOtlvq_E-ALEMnnw0oEAbfHXYqGOhT2xpRKmVc,10467
12
- aas_http_client-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- aas_http_client-0.4.0.dist-info/top_level.txt,sha256=vzvoz2vjeTLwpuz-Y-eEfoQ7T3byoaKshVlFMFH5NaM,16
14
- aas_http_client-0.4.0.dist-info/RECORD,,