mdtpy 0.1.0__tar.gz
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.
- mdtpy-0.1.0/PKG-INFO +23 -0
- mdtpy-0.1.0/README.md +0 -0
- mdtpy-0.1.0/pyproject.toml +37 -0
- mdtpy-0.1.0/src/mdtpy/__init__.py +12 -0
- mdtpy-0.1.0/src/mdtpy/aas_misc.py +104 -0
- mdtpy-0.1.0/src/mdtpy/basyx/__init__.py +2 -0
- mdtpy-0.1.0/src/mdtpy/basyx/serde.py +18 -0
- mdtpy-0.1.0/src/mdtpy/basyx/utils.py +169 -0
- mdtpy-0.1.0/src/mdtpy/descriptor.py +113 -0
- mdtpy-0.1.0/src/mdtpy/exceptions.py +71 -0
- mdtpy-0.1.0/src/mdtpy/fa3st.py +169 -0
- mdtpy-0.1.0/src/mdtpy/http_client.py +66 -0
- mdtpy-0.1.0/src/mdtpy/instance.py +306 -0
- mdtpy-0.1.0/src/mdtpy/operation.py +233 -0
- mdtpy-0.1.0/src/mdtpy/parameter.py +58 -0
- mdtpy-0.1.0/src/mdtpy/reference.py +214 -0
- mdtpy-0.1.0/src/mdtpy/submodel.py +235 -0
- mdtpy-0.1.0/src/mdtpy/timeseries.py +269 -0
- mdtpy-0.1.0/src/mdtpy/utils.py +112 -0
- mdtpy-0.1.0/src/mdtpy/value.py +202 -0
mdtpy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mdtpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python binding for MDT Platform
|
|
5
|
+
Author: Kang-Woo Lee
|
|
6
|
+
Author-email: kwlee@etri.re.kr
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: basyx-python-sdk (>=2.0.0,<3.0.0)
|
|
14
|
+
Requires-Dist: dataclass-wizard (==0.22)
|
|
15
|
+
Requires-Dist: isodate (>=0.7.2,<0.8.0)
|
|
16
|
+
Requires-Dist: pandas (>=2.3.3,<3.0.0)
|
|
17
|
+
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
18
|
+
Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
|
|
19
|
+
Requires-Dist: tika (>=3.1.0,<4.0.0)
|
|
20
|
+
Requires-Dist: urllib3 (>=2.6.2,<3.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
|
mdtpy-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mdtpy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python binding for MDT Platform"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Kang-Woo Lee",email = "kwlee@etri.re.kr"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"basyx-python-sdk (>=2.0.0,<3.0.0)",
|
|
12
|
+
"urllib3 (>=2.6.2,<3.0.0)",
|
|
13
|
+
"requests (>=2.32.5,<3.0.0)",
|
|
14
|
+
"requests-toolbelt (>=1.0.0,<2.0.0)",
|
|
15
|
+
"dataclass-wizard (==0.22)",
|
|
16
|
+
"isodate (>=0.7.2,<0.8.0)",
|
|
17
|
+
"pandas (>=2.3.3,<3.0.0)",
|
|
18
|
+
"tika (>=3.1.0,<4.0.0)"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.poetry]
|
|
22
|
+
packages = [{include = "mdtpy", from = "src"}]
|
|
23
|
+
|
|
24
|
+
[[tool.poetry.source]]
|
|
25
|
+
name = "foo"
|
|
26
|
+
url = "https://pypi.example.org/simple/"
|
|
27
|
+
priority = "supplemental"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[[tool.poetry.source]]
|
|
31
|
+
name = "test"
|
|
32
|
+
url = "https://pypi.example.org/simple/"
|
|
33
|
+
priority = "supplemental"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
37
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
# SSL 인증서 검증 경고 억제
|
|
3
|
+
import urllib3
|
|
4
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
5
|
+
|
|
6
|
+
from .instance import *
|
|
7
|
+
from .value import *
|
|
8
|
+
from .descriptor import *
|
|
9
|
+
from .exceptions import *
|
|
10
|
+
from .timeseries import *
|
|
11
|
+
from . import aas_misc as aas
|
|
12
|
+
from . import basyx
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, cast, Optional, Iterable
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import datetime
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from dataclass_wizard import JSONWizard
|
|
10
|
+
|
|
11
|
+
from basyx.aas import model
|
|
12
|
+
from .basyx import serde as basyx_serde
|
|
13
|
+
from . import utils
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SecurityTypeEnum(Enum):
|
|
17
|
+
NONE = auto()
|
|
18
|
+
RFC_TLSA = auto()
|
|
19
|
+
W3C_DID = auto()
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class SecurityAttributeObject(JSONWizard):
|
|
23
|
+
type: SecurityTypeEnum
|
|
24
|
+
key: str
|
|
25
|
+
value: str
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class ProtocolInformation:
|
|
29
|
+
href: str|None
|
|
30
|
+
endpointProtocol: str|None = field(default=None)
|
|
31
|
+
endpointProtocolVersion: str|None = field(default=None)
|
|
32
|
+
subprotocol: str|None = field(default=None)
|
|
33
|
+
subprotocolBody: str|None = field(default=None)
|
|
34
|
+
subprotocolBody_encoding: str|None = field(default=None)
|
|
35
|
+
securityAttributes: list[SecurityAttributeObject] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
@dataclass(slots=True)
|
|
38
|
+
class Endpoint:
|
|
39
|
+
interface: str
|
|
40
|
+
protocolInformation: ProtocolInformation
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(slots=True)
|
|
44
|
+
class OperationVariable:
|
|
45
|
+
value: model.SubmodelElement
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, data: dict) -> OperationVariable:
|
|
49
|
+
return cls(value=basyx_serde.from_dict(data['value']))
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict[str, Any]:
|
|
52
|
+
return { 'value': json.loads(basyx_serde.to_json(self.value)) }
|
|
53
|
+
|
|
54
|
+
@dataclass(slots=True)
|
|
55
|
+
class OperationResult:
|
|
56
|
+
messages: Optional[list[str]]
|
|
57
|
+
execution_state: str
|
|
58
|
+
success: bool
|
|
59
|
+
output_arguments: Optional[list[OperationVariable]]
|
|
60
|
+
inoutput_arguments: Optional[list[OperationVariable]]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_dict(cls, data: dict) -> OperationResult:
|
|
64
|
+
output_arguments = data.get('outputArguments')
|
|
65
|
+
if output_arguments:
|
|
66
|
+
output_arguments = [OperationVariable.from_dict(arg) for arg in output_arguments]
|
|
67
|
+
inoutput_arguments = data.get('inoutputArguments')
|
|
68
|
+
if inoutput_arguments:
|
|
69
|
+
inoutput_arguments = [OperationVariable.from_dict(arg) for arg in inoutput_arguments]
|
|
70
|
+
|
|
71
|
+
return cls(messages = data.get('messages'),
|
|
72
|
+
execution_state = data['executionState'],
|
|
73
|
+
success = data['success'],
|
|
74
|
+
output_arguments=output_arguments,
|
|
75
|
+
inoutput_arguments=inoutput_arguments)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_json(cls, json_str: str) -> OperationResult:
|
|
79
|
+
return cls.from_dict(json.loads(json_str))
|
|
80
|
+
|
|
81
|
+
@dataclass(slots=True)
|
|
82
|
+
class OperationHandle(JSONWizard):
|
|
83
|
+
handle_id: str
|
|
84
|
+
|
|
85
|
+
@dataclass(slots=True)
|
|
86
|
+
class OperationRequest:
|
|
87
|
+
input_arguments: Iterable[OperationVariable]
|
|
88
|
+
inoutput_arguments: Iterable[OperationVariable]
|
|
89
|
+
client_timeout_duration: datetime.timedelta
|
|
90
|
+
|
|
91
|
+
def to_json(self) -> str:
|
|
92
|
+
in_opv_list = [ op_var.to_dict() for op_var in self.input_arguments ]
|
|
93
|
+
inout_opv_list = [ op_var.to_dict() for op_var in self.inoutput_arguments ]
|
|
94
|
+
return json.dumps({
|
|
95
|
+
'inputArguments': in_opv_list,
|
|
96
|
+
# 'inoutputArguments': inout_opv_list,
|
|
97
|
+
'clientTimeoutDuration': utils.timedelta_to_iso8601(self.client_timeout_duration)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
from .value import MultiLanguagePropertyValue, get_value
|
|
101
|
+
def get_first_text(mlpv:MultiLanguagePropertyValue|model.MultiLanguageProperty) -> str|None:
|
|
102
|
+
if isinstance(mlpv, model.MultiLanguageProperty):
|
|
103
|
+
mlpv = cast(MultiLanguagePropertyValue, get_value(mlpv))
|
|
104
|
+
return next(iter(mlpv.values())) if mlpv else None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from basyx.aas.adapter.json.json_deserialization import AASFromJsonDecoder
|
|
7
|
+
from basyx.aas.adapter.json.json_serialization import AASToJsonEncoder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def from_json(json_str:str) -> Any:
|
|
11
|
+
return json.loads(json_str, cls=AASFromJsonDecoder)
|
|
12
|
+
|
|
13
|
+
def from_dict(data:dict) -> Any:
|
|
14
|
+
json_str = json.dumps(data)
|
|
15
|
+
return json.loads(json_str, cls=AASFromJsonDecoder)
|
|
16
|
+
|
|
17
|
+
def to_json(obj:Any) -> str:
|
|
18
|
+
return json.dumps(obj, cls=AASToJsonEncoder)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from dateutil.relativedelta import relativedelta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_ISO8601_DURATION_RE = re.compile(
|
|
10
|
+
r"""
|
|
11
|
+
^\s*
|
|
12
|
+
(?P<sign>[+-])?
|
|
13
|
+
P
|
|
14
|
+
(?:
|
|
15
|
+
(?:(?P<years>\d+(?:[.,]\d+)?)Y)?
|
|
16
|
+
(?:(?P<months>\d+(?:[.,]\d+)?)M)?
|
|
17
|
+
(?:(?P<weeks>\d+(?:[.,]\d+)?)W)?
|
|
18
|
+
(?:(?P<days>\d+(?:[.,]\d+)?)D)?
|
|
19
|
+
)?
|
|
20
|
+
(?:T
|
|
21
|
+
(?:(?P<hours>\d+(?:[.,]\d+)?)H)?
|
|
22
|
+
(?:(?P<minutes>\d+(?:[.,]\d+)?)M)?
|
|
23
|
+
(?:(?P<seconds>\d+(?:[.,]\d+)?)S)?
|
|
24
|
+
)?
|
|
25
|
+
\s*$
|
|
26
|
+
""",
|
|
27
|
+
re.VERBOSE | re.IGNORECASE,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _to_number(s: Optional[str]) -> float:
|
|
32
|
+
if not s:
|
|
33
|
+
return 0.0
|
|
34
|
+
return float(s.replace(",", "."))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_iso8601_to_relativedelta(duration: str) -> relativedelta:
|
|
38
|
+
"""
|
|
39
|
+
Parse an ISO 8601 duration string and return a dateutil.relativedelta.relativedelta.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
- "P1Y2M3DT4H5M6S"
|
|
43
|
+
- "PT15M"
|
|
44
|
+
- "P2W"
|
|
45
|
+
- "-P3DT12H"
|
|
46
|
+
- "PT0.5S"
|
|
47
|
+
"""
|
|
48
|
+
m = _ISO8601_DURATION_RE.match(duration)
|
|
49
|
+
if not m:
|
|
50
|
+
raise ValueError(f"Invalid ISO 8601 duration: {duration!r}")
|
|
51
|
+
|
|
52
|
+
sign = -1 if (m.group("sign") == "-") else 1
|
|
53
|
+
|
|
54
|
+
years = _to_number(m.group("years"))
|
|
55
|
+
months = _to_number(m.group("months"))
|
|
56
|
+
weeks = _to_number(m.group("weeks"))
|
|
57
|
+
days = _to_number(m.group("days"))
|
|
58
|
+
hours = _to_number(m.group("hours"))
|
|
59
|
+
minutes = _to_number(m.group("minutes"))
|
|
60
|
+
seconds = _to_number(m.group("seconds"))
|
|
61
|
+
|
|
62
|
+
# ISO 8601 allows fractional values; relativedelta expects integers for Y/M
|
|
63
|
+
# (fractional months/years are ambiguous). We reject fractional Y/M explicitly.
|
|
64
|
+
if years and not years.is_integer():
|
|
65
|
+
raise ValueError(f"Fractional years not supported for relativedelta: {duration!r}")
|
|
66
|
+
if months and not months.is_integer():
|
|
67
|
+
raise ValueError(f"Fractional months not supported for relativedelta: {duration!r}")
|
|
68
|
+
|
|
69
|
+
# For weeks/days/hours/minutes/seconds, we can carry fractions down to smaller units.
|
|
70
|
+
# Convert weeks to days.
|
|
71
|
+
total_days = days + (weeks * 7.0)
|
|
72
|
+
|
|
73
|
+
# Split fractional days into seconds
|
|
74
|
+
day_int = int(total_days)
|
|
75
|
+
day_frac = total_days - day_int
|
|
76
|
+
|
|
77
|
+
total_seconds = seconds + (minutes * 60.0) + (hours * 3600.0) + (day_frac * 86400.0)
|
|
78
|
+
|
|
79
|
+
sec_int = int(total_seconds)
|
|
80
|
+
sec_frac = total_seconds - sec_int
|
|
81
|
+
microseconds = int(round(sec_frac * 1_000_000))
|
|
82
|
+
|
|
83
|
+
# Handle rounding overflow (e.g., 0.9999996 rounds to 1_000_000)
|
|
84
|
+
if microseconds == 1_000_000:
|
|
85
|
+
sec_int += 1
|
|
86
|
+
microseconds = 0
|
|
87
|
+
|
|
88
|
+
rd = relativedelta(
|
|
89
|
+
years=sign * int(years),
|
|
90
|
+
months=sign * int(months),
|
|
91
|
+
days=sign * day_int,
|
|
92
|
+
seconds=sign * sec_int,
|
|
93
|
+
microseconds=sign * microseconds,
|
|
94
|
+
)
|
|
95
|
+
return rd
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def relativedelta_to_iso8601(rd: relativedelta) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Convert dateutil.relativedelta.relativedelta to an ISO 8601 duration string.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
relativedelta(years=1, months=2, days=3,
|
|
104
|
+
hours=4, minutes=5, seconds=6)
|
|
105
|
+
-> "P1Y2M3DT4H5M6S"
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# Determine sign (ISO-8601 uses a single leading sign)
|
|
109
|
+
def _sign(x: int) -> int:
|
|
110
|
+
return -1 if x < 0 else 1
|
|
111
|
+
|
|
112
|
+
signs = [
|
|
113
|
+
_sign(rd.years),
|
|
114
|
+
_sign(rd.months),
|
|
115
|
+
_sign(rd.days),
|
|
116
|
+
_sign(rd.hours),
|
|
117
|
+
_sign(rd.minutes),
|
|
118
|
+
_sign(rd.seconds),
|
|
119
|
+
_sign(rd.microseconds),
|
|
120
|
+
]
|
|
121
|
+
sign = "-" if any(s < 0 for s in signs) else ""
|
|
122
|
+
|
|
123
|
+
# Use absolute values (ISO-8601 sign is global)
|
|
124
|
+
years = abs(rd.years)
|
|
125
|
+
months = abs(rd.months)
|
|
126
|
+
days = abs(rd.days)
|
|
127
|
+
|
|
128
|
+
hours = abs(rd.hours)
|
|
129
|
+
minutes = abs(rd.minutes)
|
|
130
|
+
|
|
131
|
+
seconds = abs(rd.seconds)
|
|
132
|
+
microseconds = abs(rd.microseconds)
|
|
133
|
+
|
|
134
|
+
# Combine seconds + microseconds
|
|
135
|
+
if microseconds:
|
|
136
|
+
seconds = seconds + microseconds / 1_000_000
|
|
137
|
+
|
|
138
|
+
date_parts = []
|
|
139
|
+
time_parts = []
|
|
140
|
+
|
|
141
|
+
if years:
|
|
142
|
+
date_parts.append(f"{years}Y")
|
|
143
|
+
if months:
|
|
144
|
+
date_parts.append(f"{months}M")
|
|
145
|
+
if days:
|
|
146
|
+
date_parts.append(f"{days}D")
|
|
147
|
+
|
|
148
|
+
if hours:
|
|
149
|
+
time_parts.append(f"{hours}H")
|
|
150
|
+
if minutes:
|
|
151
|
+
time_parts.append(f"{minutes}M")
|
|
152
|
+
if seconds:
|
|
153
|
+
# Strip trailing .0 for integers
|
|
154
|
+
if float(seconds).is_integer():
|
|
155
|
+
time_parts.append(f"{int(seconds)}S")
|
|
156
|
+
else:
|
|
157
|
+
time_parts.append(f"{seconds:.6f}".rstrip("0").rstrip(".") + "S")
|
|
158
|
+
|
|
159
|
+
# ISO-8601 requires at least one component
|
|
160
|
+
if not date_parts and not time_parts:
|
|
161
|
+
return "PT0S"
|
|
162
|
+
|
|
163
|
+
result = sign + "P"
|
|
164
|
+
result += "".join(date_parts)
|
|
165
|
+
|
|
166
|
+
if time_parts:
|
|
167
|
+
result += "T" + "".join(time_parts)
|
|
168
|
+
|
|
169
|
+
return result
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from dataclass_wizard import JSONWizard
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MDTInstanceStatus(Enum):
|
|
11
|
+
STOPPED = "STOPPED"
|
|
12
|
+
STARTING = "STARTING"
|
|
13
|
+
RUNNING = "RUNNING"
|
|
14
|
+
STOPPING = "STOPPING"
|
|
15
|
+
FAILED = "FAILED"
|
|
16
|
+
|
|
17
|
+
class MDTAssetType(Enum):
|
|
18
|
+
Machine = "Machine"
|
|
19
|
+
Process = "Process"
|
|
20
|
+
Line = "Line"
|
|
21
|
+
Factory = "Factory"
|
|
22
|
+
|
|
23
|
+
class AssetKind(Enum):
|
|
24
|
+
INSTANCE = "INSTANCE"
|
|
25
|
+
NOT_APPLICABLE = "NOT_APPLICABLE"
|
|
26
|
+
TYPE = "TYPE"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class InstanceDescriptor(JSONWizard):
|
|
31
|
+
"""
|
|
32
|
+
MDTInstance Descriptor
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
----------
|
|
36
|
+
id: str
|
|
37
|
+
The unique identifier of the MDTInstance.
|
|
38
|
+
status: MDTInstanceStatus
|
|
39
|
+
The Status of the MDTInstance.
|
|
40
|
+
base_endpoint: Optional[str]
|
|
41
|
+
The Base Endpoint of the MDTInstance.
|
|
42
|
+
aas_id: str
|
|
43
|
+
The AAS ID of the MDTInstance.
|
|
44
|
+
aas_idshort: Optional[str]
|
|
45
|
+
The AAS ID Short of the MDTInstance.
|
|
46
|
+
global_asset_id: Optional[str]
|
|
47
|
+
The Global Asset ID of the MDTInstance.
|
|
48
|
+
asset_type: Optional[MDTAssetType]
|
|
49
|
+
The Asset Type of the MDTInstance.
|
|
50
|
+
asset_kind: Optional[AssetKind]
|
|
51
|
+
The Asset Kind of the MDTInstance.
|
|
52
|
+
"""
|
|
53
|
+
id: str
|
|
54
|
+
status: MDTInstanceStatus
|
|
55
|
+
aas_id: str
|
|
56
|
+
base_endpoint: Optional[str] = field(default=None, hash=False, compare=False)
|
|
57
|
+
aas_id_short: Optional[str] = field(default=None, hash=False, compare=False)
|
|
58
|
+
global_asset_id: Optional[str] = field(default=None, hash=False, compare=False)
|
|
59
|
+
asset_type: MDTAssetType|None = field(default=None, hash=False, compare=False)
|
|
60
|
+
asset_kind: AssetKind|None = field(default=None, hash=False, compare=False)
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class MDTParameterDescriptor(JSONWizard):
|
|
64
|
+
id: str
|
|
65
|
+
value_type: str
|
|
66
|
+
reference: str
|
|
67
|
+
name: Optional[str] = None
|
|
68
|
+
endpoint: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
SEMANTIC_ID_INFOR_MODEL_SUBMODEL = "https://etri.re.kr/mdt/Submodel/InformationModel/1/1"
|
|
72
|
+
SEMANTIC_ID_AI_SUBMODEL = "https://etri.re.kr/mdt/Submodel/AI/1/1"
|
|
73
|
+
SEMANTIC_ID_SIMULATION_SUBMODEL = "https://etri.re.kr/mdt/Submodel/Simulation/1/1"
|
|
74
|
+
SEMANTIC_ID_DATA_SUBMODEL = "https://etri.re.kr/mdt/Submodel/Data/1/1"
|
|
75
|
+
SEMANTIC_ID_TIME_SERIES_SUBMODEL = 'https://admin-shell.io/idta/TimeSeries/1/1'
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True, slots=True)
|
|
78
|
+
class MDTSubmodelDescriptor(JSONWizard):
|
|
79
|
+
id: str
|
|
80
|
+
id_short: str
|
|
81
|
+
semantic_id: str
|
|
82
|
+
endpoint: Optional[str]
|
|
83
|
+
|
|
84
|
+
def is_information_model(self) -> bool:
|
|
85
|
+
return self.semantic_id == SEMANTIC_ID_INFOR_MODEL_SUBMODEL
|
|
86
|
+
|
|
87
|
+
def is_data(self) -> bool:
|
|
88
|
+
return self.semantic_id == SEMANTIC_ID_DATA_SUBMODEL
|
|
89
|
+
|
|
90
|
+
def is_simulation(self) -> bool:
|
|
91
|
+
return self.semantic_id == SEMANTIC_ID_SIMULATION_SUBMODEL
|
|
92
|
+
|
|
93
|
+
def is_ai(self) -> bool:
|
|
94
|
+
return self.semantic_id == SEMANTIC_ID_AI_SUBMODEL
|
|
95
|
+
|
|
96
|
+
def is_time_series(self) -> bool:
|
|
97
|
+
return self.semantic_id == SEMANTIC_ID_TIME_SERIES_SUBMODEL
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True, slots=True)
|
|
101
|
+
class ArgumentDescriptor(JSONWizard):
|
|
102
|
+
id: str
|
|
103
|
+
id_short_path: str
|
|
104
|
+
value_type: str
|
|
105
|
+
reference: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True, slots=True)
|
|
109
|
+
class MDTOperationDescriptor(JSONWizard):
|
|
110
|
+
id: str
|
|
111
|
+
operation_type: str
|
|
112
|
+
input_arguments: list[ArgumentDescriptor]
|
|
113
|
+
output_arguments: list[ArgumentDescriptor]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MDTException(Exception):
|
|
5
|
+
def __init__(self, details:str) -> None:
|
|
6
|
+
self.details = details
|
|
7
|
+
super().__init__(details)
|
|
8
|
+
|
|
9
|
+
def __str__(self) -> str:
|
|
10
|
+
return repr(self)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InternalError(MDTException):
|
|
14
|
+
def __init__(self, details:str) -> None:
|
|
15
|
+
super().__init__(details)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TimeoutError(MDTException):
|
|
19
|
+
def __init__(self, details:str) -> None:
|
|
20
|
+
super().__init__(details)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CancellationError(MDTException):
|
|
24
|
+
def __init__(self, details:str) -> None:
|
|
25
|
+
super().__init__(details)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OperationError(MDTException):
|
|
29
|
+
def __init__(self, details:str) -> None:
|
|
30
|
+
super().__init__(details)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RemoteError(MDTException):
|
|
34
|
+
def __init__(self, details:str) -> None:
|
|
35
|
+
super().__init__(details)
|
|
36
|
+
|
|
37
|
+
import requests
|
|
38
|
+
class MDTInstanceConnectionError(MDTException):
|
|
39
|
+
def __init__(self, details:str, cause:requests.exceptions.ConnectionError) -> None:
|
|
40
|
+
super().__init__(details)
|
|
41
|
+
self.cause = cause
|
|
42
|
+
|
|
43
|
+
def __repr__(self) -> str:
|
|
44
|
+
return f"{self.__class__.__name__}(details={self.details}, cause={self.cause})"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ResourceAlreadyExistsError(MDTException):
|
|
48
|
+
def __init__(self, details:str) -> None:
|
|
49
|
+
super().__init__(details)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def create(cls, resource_type:str, id_spec:str):
|
|
53
|
+
return ResourceAlreadyExistsError(f"Resource(type={resource_type}, {id_spec})")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ResourceNotFoundError(MDTException):
|
|
57
|
+
def __init__(self, details:str) -> None:
|
|
58
|
+
super().__init__(details)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def create(cls, resource_type:str, id_spec:str):
|
|
62
|
+
return ResourceNotFoundError(f"Resource(type={resource_type}, {id_spec})")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InvalidResourceStateError(MDTException):
|
|
66
|
+
def __init__(self, details:str) -> None:
|
|
67
|
+
super().__init__(details)
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def create(cls, resource_type:str, id_spec:str, status):
|
|
71
|
+
return InvalidResourceStateError(f"Resource(type={resource_type}, {id_spec}), status={status}")
|