aas-http-client 0.1.3__py3-none-any.whl → 0.1.5__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/__init__.py +10 -3
- aas_http_client/client.py +62 -136
- aas_http_client/core/version_check.py +17 -0
- aas_http_client/demo/demo_process.py +74 -0
- aas_http_client/demo/logging_handler.py +177 -0
- aas_http_client/utilities/__init__.py +0 -0
- aas_http_client/utilities/model_builder.py +114 -0
- aas_http_client/wrapper/python_sdk_wrapper_tmp.py +641 -0
- aas_http_client/wrapper/sdk_wrapper.py +272 -0
- {aas_http_client-0.1.3.dist-info → aas_http_client-0.1.5.dist-info}/METADATA +1 -1
- aas_http_client-0.1.5.dist-info/RECORD +15 -0
- aas_http_client-0.1.3.dist-info/RECORD +0 -8
- {aas_http_client-0.1.3.dist-info → aas_http_client-0.1.5.dist-info}/WHEEL +0 -0
- {aas_http_client-0.1.3.dist-info → aas_http_client-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {aas_http_client-0.1.3.dist-info → aas_http_client-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -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()
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Model builder module.
|
|
2
|
+
|
|
3
|
+
Provides some helper methods for easier work with basyx sdk data model
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from basyx.aas.model import (
|
|
9
|
+
AssetAdministrationShell,
|
|
10
|
+
AssetInformation,
|
|
11
|
+
AssetKind,
|
|
12
|
+
Key,
|
|
13
|
+
ModelReference,
|
|
14
|
+
MultiLanguageTextType,
|
|
15
|
+
Submodel,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_unique_short_id(id_short: str) -> str:
|
|
20
|
+
"""Generate a unique identifier string by appending a UUID to the provided ID short.
|
|
21
|
+
|
|
22
|
+
:param id_short: provided ID short
|
|
23
|
+
:return: unique identifier
|
|
24
|
+
"""
|
|
25
|
+
return f"{id_short}_{str(uuid.uuid4()).replace('-', '_')}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_base_submodel(id_short: str, namespace: str = "basyx_python_aas_server", display_name: str = "", description: str = "") -> Submodel:
|
|
29
|
+
"""Create a basic Submodel.
|
|
30
|
+
|
|
31
|
+
:param id_short: ID short of the Submodel
|
|
32
|
+
:param namespace: namespace of the Submodel , defaults to "basyx_python_aas_server"
|
|
33
|
+
:param display_name: display name of the Submodel, defaults to ""
|
|
34
|
+
:param description: description of the Submodel, defaults to ""
|
|
35
|
+
:return: Submodel instance
|
|
36
|
+
"""
|
|
37
|
+
identifier = f"{namespace}/{id_short}"
|
|
38
|
+
sm = Submodel(identifier)
|
|
39
|
+
sm.id_short = id_short
|
|
40
|
+
|
|
41
|
+
if not description:
|
|
42
|
+
description = f"This is the submodel with ID short '{id_short}'"
|
|
43
|
+
|
|
44
|
+
description_text = {"en": f"{description}"}
|
|
45
|
+
sm.description = MultiLanguageTextType(description_text)
|
|
46
|
+
|
|
47
|
+
if not display_name:
|
|
48
|
+
display_name = "POC AAS"
|
|
49
|
+
|
|
50
|
+
display_name_text = {"en": f"{display_name}"}
|
|
51
|
+
sm.display_name = MultiLanguageTextType(display_name_text)
|
|
52
|
+
|
|
53
|
+
return sm
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_base_ass(
|
|
57
|
+
id_short: str, namespace: str = "basyx_python_aas_server", display_name: str = "", description: str = ""
|
|
58
|
+
) -> AssetAdministrationShell:
|
|
59
|
+
"""Create a basic AAS.
|
|
60
|
+
|
|
61
|
+
:param id_short: ID short of the AAS
|
|
62
|
+
:param namespace: namespace of the AAS, defaults to "basyx_python_aas_server"
|
|
63
|
+
:param display_name: display name of the AAS, defaults to ""
|
|
64
|
+
:param description: description of the AAS, defaults to ""
|
|
65
|
+
:return: AssetAdministrationShell instance
|
|
66
|
+
"""
|
|
67
|
+
asset_info = create_base_asset_information(id_short, namespace)
|
|
68
|
+
|
|
69
|
+
aas = AssetAdministrationShell(id_=asset_info.global_asset_id, asset_information=asset_info)
|
|
70
|
+
aas.id_short = id_short
|
|
71
|
+
|
|
72
|
+
if not description:
|
|
73
|
+
description = f"This is the asset administration shell with ID short '{id_short}'"
|
|
74
|
+
|
|
75
|
+
description_text = {"en": f"{description}"}
|
|
76
|
+
aas.description = MultiLanguageTextType(description_text)
|
|
77
|
+
|
|
78
|
+
if not display_name:
|
|
79
|
+
display_name = "POC AAS"
|
|
80
|
+
|
|
81
|
+
display_name_text = {"en": f"{display_name}"}
|
|
82
|
+
aas.display_name = MultiLanguageTextType(display_name_text)
|
|
83
|
+
|
|
84
|
+
return aas
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def add_submodel_to_aas(aas: AssetAdministrationShell, submodel: Submodel) -> None:
|
|
88
|
+
"""Add a given Submodel correctly to a provided AssetAdministrationShell.
|
|
89
|
+
|
|
90
|
+
:param aas: provided AssetAdministrationShell to which the Submodel should be added
|
|
91
|
+
:param submodel: given Submodel to add
|
|
92
|
+
"""
|
|
93
|
+
# aas.submodel.add(submodel)
|
|
94
|
+
aas.submodel.add(ModelReference.from_referable(submodel))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_base_asset_information(id_short: str, namespace: str = "basyx_python_aas_server") -> AssetInformation:
|
|
98
|
+
"""Return a basic AssetInformation instance.
|
|
99
|
+
|
|
100
|
+
:param id_short: short ID of the AssetInformation
|
|
101
|
+
:param namespace: namespace of the AssetInformation, defaults to "basyx_python_aas_server"
|
|
102
|
+
:return: AssetInformation instance
|
|
103
|
+
"""
|
|
104
|
+
identifier = f"{namespace}/{id_short}"
|
|
105
|
+
return AssetInformation(AssetKind.INSTANCE, identifier)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_reference(id: str) -> ModelReference:
|
|
109
|
+
"""Create a ModelReference.
|
|
110
|
+
|
|
111
|
+
:param id: ID of the Submodel to reference
|
|
112
|
+
:return: ModelReference instance
|
|
113
|
+
"""
|
|
114
|
+
return ModelReference.from_referable(Submodel(id))
|