stidapi 1.4.0__tar.gz → 1.4.2__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.
- {stidapi-1.4.0 → stidapi-1.4.2}/PKG-INFO +2 -2
- {stidapi-1.4.0 → stidapi-1.4.2}/pyproject.toml +5 -5
- stidapi-1.4.2/stidapi/doc.py +260 -0
- stidapi-1.4.2/stidapi/system.py +69 -0
- {stidapi-1.4.0 → stidapi-1.4.2}/stidapi/tag.py +88 -45
- {stidapi-1.4.0 → stidapi-1.4.2}/stidapi/utils.py +3 -4
- stidapi-1.4.0/stidapi/doc.py +0 -152
- stidapi-1.4.0/stidapi/system.py +0 -40
- {stidapi-1.4.0 → stidapi-1.4.2}/README.md +0 -0
- {stidapi-1.4.0 → stidapi-1.4.2}/stidapi/__init__.py +0 -0
- {stidapi-1.4.0 → stidapi-1.4.2}/stidapi/plant.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stidapi
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Python client for connecting to Equinor STIDapi
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Åsmund Våge Fannemel
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
-
Requires-Dist: eq-api-connector (>=1.0
|
|
16
|
+
Requires-Dist: eq-api-connector (>=1.1.0,<2.0.0)
|
|
17
17
|
Project-URL: repository, https://github.com/equinor/STIDapi-python
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "stidapi"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.2"
|
|
4
4
|
description = "Python client for connecting to Equinor STIDapi"
|
|
5
5
|
authors = ["Åsmund Våge Fannemel <asmf@equinor.com>"]
|
|
6
6
|
license = "Proprietary"
|
|
@@ -11,12 +11,12 @@ readme = "README.md"
|
|
|
11
11
|
|
|
12
12
|
[tool.poetry.dependencies]
|
|
13
13
|
python = "^3.10"
|
|
14
|
-
eq-api-connector = "^1.0
|
|
14
|
+
eq-api-connector = "^1.1.0"
|
|
15
15
|
|
|
16
16
|
[tool.poetry.group.dev.dependencies]
|
|
17
|
-
pytest = "^
|
|
18
|
-
pytest-cov = "^
|
|
19
|
-
black = "^
|
|
17
|
+
pytest = "^9.0.0"
|
|
18
|
+
pytest-cov = "^7.0.0"
|
|
19
|
+
black = "^25.10.0"
|
|
20
20
|
diff-cover = "^10.2.0"
|
|
21
21
|
|
|
22
22
|
[build-system]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
import stidapi.utils as u
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class File:
|
|
7
|
+
"""Class for internal use. Copy of File as returned by document endpoint."""
|
|
8
|
+
|
|
9
|
+
# "id": 0,
|
|
10
|
+
# "instCode": "string",
|
|
11
|
+
# "fileName": "string",
|
|
12
|
+
# "objectType": "string",
|
|
13
|
+
# "description": "string",
|
|
14
|
+
# "fileOrder": 0,
|
|
15
|
+
# "prodViewCode": "string",
|
|
16
|
+
# "insertedDate": "2024-04-09T11:46:07.501Z",
|
|
17
|
+
# "thumbnail": "string",
|
|
18
|
+
# "fileSize": 0,
|
|
19
|
+
# "blobId": "string"
|
|
20
|
+
|
|
21
|
+
def __init__(self, data: Dict[str, Any]) -> None:
|
|
22
|
+
"""Initialize File object from data dictionary.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data: Dictionary containing file properties.
|
|
26
|
+
"""
|
|
27
|
+
self._description: str = ""
|
|
28
|
+
|
|
29
|
+
for prop in data.keys():
|
|
30
|
+
self.__setattr__(prop, data[prop])
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def inst_code(self) -> str:
|
|
34
|
+
"""Get installation code."""
|
|
35
|
+
return getattr(self, "instCode", "")
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def file_name(self) -> str:
|
|
39
|
+
"""Get file name."""
|
|
40
|
+
return getattr(self, "fileName", "")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def description(self) -> str:
|
|
44
|
+
"""Get file description."""
|
|
45
|
+
return self._description
|
|
46
|
+
|
|
47
|
+
@description.setter
|
|
48
|
+
def description(self, value: str) -> None:
|
|
49
|
+
"""Set file description."""
|
|
50
|
+
self._description = value
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def object_type(self) -> str:
|
|
54
|
+
"""Get object type."""
|
|
55
|
+
return getattr(self, "objectType", "")
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return f"InstCode: {self.inst_code}, FileName: {self.file_name}, Description: {self.description}, ObjectType: {self.object_type}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Revision:
|
|
62
|
+
"""Class for internal use. Copy of Revision as returned by document endpoint."""
|
|
63
|
+
|
|
64
|
+
# "revNo": "string",
|
|
65
|
+
# "revStatus": "string",
|
|
66
|
+
# "revStatusDescription": "string",
|
|
67
|
+
# "revDate": "2024-04-09T11:46:07.501Z",
|
|
68
|
+
# "isCurrent": true,
|
|
69
|
+
# "projectCode": "string",
|
|
70
|
+
# "projectCodeDescription": "string",
|
|
71
|
+
# "reasonForIssue": "string",
|
|
72
|
+
# "acceptanceCode": 0,
|
|
73
|
+
# "acceptanceCodeDescription": "string",
|
|
74
|
+
# "files": [File objects]
|
|
75
|
+
|
|
76
|
+
def __init__(self, data: Dict[str, Any]) -> None:
|
|
77
|
+
"""Initialize Revision object from data dictionary.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
data: Dictionary containing revision properties.
|
|
81
|
+
"""
|
|
82
|
+
self._data: Dict[str, Any] = data
|
|
83
|
+
for prop in data.keys():
|
|
84
|
+
if prop == "files":
|
|
85
|
+
continue
|
|
86
|
+
self.__setattr__(prop, data[prop])
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def rev_date(self) -> str:
|
|
90
|
+
"""Get revision date."""
|
|
91
|
+
return self._data.get("revDate", "")
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def rev_no(self) -> str:
|
|
95
|
+
"""Get revision number."""
|
|
96
|
+
return self._data.get("revNo", "")
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def rev_status(self) -> str:
|
|
100
|
+
"""Get revision status."""
|
|
101
|
+
return self._data.get("revStatus", "")
|
|
102
|
+
|
|
103
|
+
def get_files(self) -> List[File]:
|
|
104
|
+
"""Get list of files in this revision.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of File objects.
|
|
108
|
+
"""
|
|
109
|
+
return [File(x) for x in self._data.get("files", [])]
|
|
110
|
+
|
|
111
|
+
def __str__(self) -> str:
|
|
112
|
+
return f"RevNo: {self.rev_no}, RevStatus: {self.rev_status}, RevDate: {self.rev_date}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Doc:
|
|
116
|
+
"""Represents a technical document in STID."""
|
|
117
|
+
|
|
118
|
+
# "instCode": "string", "instCodeDescription": "string",
|
|
119
|
+
# "docNo": "string", "docTitle": "string",
|
|
120
|
+
# "docCategory": "string", "docCategoryDescription": "string",
|
|
121
|
+
# "docType": "string", "docTypeDescription": "string",
|
|
122
|
+
# "projectCode": "string", "projectCodeDescription": "string",
|
|
123
|
+
# "contrCode": "string", "contrCodeDescription": "string",
|
|
124
|
+
# "disciplineCode": "string","disciplineCodeDescription": "string",
|
|
125
|
+
# "locationCode": "string","locationCodeDescription": "string",
|
|
126
|
+
# "docClass": "string","docClassName": "string", "docClassDescription": "string",
|
|
127
|
+
# "poNo": "string","poNoDescription": "string",
|
|
128
|
+
# "system": "string","systemDescription": "string",
|
|
129
|
+
# "remark": "string",
|
|
130
|
+
# "supplDocNo": "string",
|
|
131
|
+
# "companyCode": "string",
|
|
132
|
+
# "source": "string",
|
|
133
|
+
# "size": "string",
|
|
134
|
+
# "priority": "string", "priorityDescription": "string",
|
|
135
|
+
# "weightBearing": "string",
|
|
136
|
+
# "productCode": "string",
|
|
137
|
+
# "insulationClass": "string", "insulationClassDescription": "string",
|
|
138
|
+
# "tagRefCount": 0, "docRefCount": 0,
|
|
139
|
+
# "currentRevision": { Revision object },
|
|
140
|
+
# "projectRevisions": [ { Revision object }],
|
|
141
|
+
# "additionalFields": [{"type": "string","typeDescription": "string","value": "string","description": "string"}],
|
|
142
|
+
# "projects": [{"instCode": "string","projectCode": "string","description": "string","stidDeliveryCode": "string",
|
|
143
|
+
# "insertedDate": "2024-04-09T11:46:07.501Z","isPrimary": true,"isValid": true,}],
|
|
144
|
+
# "purchaseOrders": [
|
|
145
|
+
# {"instCode": "string","poNo": "string","description": "string","insertedDate": "2024-04-09T11:46:07.501Z","isPrimary": true,"isValid": true}],
|
|
146
|
+
# "apiResponseTime": "string",
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self, inst_code: str, doc_no: str, data: Optional[Dict[str, Any]] = None
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Initialize Doc object.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
inst_code: STID installation code.
|
|
155
|
+
doc_no: Document number as found in STID.
|
|
156
|
+
data: (Optional) Dictionary of data as returned from Doc.get().
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If inst_code or doc_no are empty/invalid.
|
|
160
|
+
"""
|
|
161
|
+
if not inst_code or not isinstance(inst_code, str):
|
|
162
|
+
raise ValueError("inst_code must be a non-empty string")
|
|
163
|
+
if not doc_no or not isinstance(doc_no, str):
|
|
164
|
+
raise ValueError("doc_no must be a non-empty string")
|
|
165
|
+
|
|
166
|
+
self._data: Dict[str, Any] = {}
|
|
167
|
+
|
|
168
|
+
if data is not None:
|
|
169
|
+
self._data = data
|
|
170
|
+
else:
|
|
171
|
+
fetched_data = Doc.get(inst_code=inst_code, doc_no=doc_no)
|
|
172
|
+
if fetched_data:
|
|
173
|
+
self._data = fetched_data
|
|
174
|
+
else:
|
|
175
|
+
raise ValueError(f"No document found for {inst_code}/{doc_no}")
|
|
176
|
+
|
|
177
|
+
self.docNo = self.no
|
|
178
|
+
self.description = self.title
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def inst_code(self) -> str:
|
|
182
|
+
"""Get installation code."""
|
|
183
|
+
return self._data.get("instCode", "")
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def no(self) -> str:
|
|
187
|
+
"""Get document number."""
|
|
188
|
+
return self._data.get("docNo", "")
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def category(self) -> str:
|
|
192
|
+
"""Get document category description."""
|
|
193
|
+
return self._data.get("docCategoryDescription", "")
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def discipline(self) -> str:
|
|
197
|
+
"""Get discipline code description."""
|
|
198
|
+
return self._data.get("disciplineCodeDescription", "")
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def type(self) -> str:
|
|
202
|
+
"""Get document type description."""
|
|
203
|
+
return self._data.get("docTypeDescription", "")
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def title(self) -> str:
|
|
207
|
+
"""Get document title."""
|
|
208
|
+
return self._data.get("docTitle", "")
|
|
209
|
+
|
|
210
|
+
def get_current_revision(self) -> Revision:
|
|
211
|
+
"""Get the current revision of this document.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Revision object for the current revision.
|
|
215
|
+
"""
|
|
216
|
+
return Revision(self._data.get("currentRevision", {}))
|
|
217
|
+
|
|
218
|
+
def get_files(self) -> List[File]:
|
|
219
|
+
"""Get files from the current revision.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
List of File objects from the current revision.
|
|
223
|
+
"""
|
|
224
|
+
return self.get_current_revision().get_files()
|
|
225
|
+
|
|
226
|
+
def __str__(self) -> str:
|
|
227
|
+
return f"InstCode: {self.inst_code}, No: {self.no}, Title: {self.title}"
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def get(inst_code: str, doc_no: str) -> Optional[Dict[str, Any]]:
|
|
231
|
+
"""Get data from STID for a single document.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
inst_code: STID installation code.
|
|
235
|
+
doc_no: STID document number.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dictionary of data from STID for the document, or None if not found.
|
|
239
|
+
"""
|
|
240
|
+
if not inst_code or not doc_no:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
url = urljoin(
|
|
244
|
+
u.get_api_url(), str(inst_code) + "/document?docNo=" + str(doc_no)
|
|
245
|
+
)
|
|
246
|
+
return u.get_json(url)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def from_data(data: Dict[str, Any]) -> "Doc":
|
|
250
|
+
"""Create Doc object from data dictionary.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
data: Dictionary of data for document.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Doc object created from data.
|
|
257
|
+
"""
|
|
258
|
+
return Doc(
|
|
259
|
+
inst_code=data.get("instCode", ""), doc_no=data.get("docNo", ""), data=data
|
|
260
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
import stidapi.utils as u
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class System:
|
|
8
|
+
"""Represents a system within a STID plant."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, data: Optional[Dict[str, Any]] = None) -> None:
|
|
11
|
+
"""Initialize System object.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
inst_code: Installation code.
|
|
15
|
+
data: (Optional) Dictionary of system data.
|
|
16
|
+
"""
|
|
17
|
+
self._data: Dict[str, Any] = {}
|
|
18
|
+
|
|
19
|
+
if isinstance(data, dict) and len(data) > 0:
|
|
20
|
+
self._data = data
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def inst_code(self) -> str:
|
|
24
|
+
"""Get installation code."""
|
|
25
|
+
return self._data.get("instCode", "")
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def no(self) -> str:
|
|
29
|
+
"""Get system number."""
|
|
30
|
+
return self._data.get("system", "")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def description(self) -> str:
|
|
34
|
+
"""Get system description."""
|
|
35
|
+
return self._data.get("description", "")
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def valid(self) -> bool:
|
|
39
|
+
"""Get system validity flag."""
|
|
40
|
+
return self._data.get("validFlg", False)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def get_all_data(inst_code: str, is_valid: bool = False) -> List[Dict[str, Any]]:
|
|
44
|
+
"""Get all system data for an installation.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
inst_code: Installation code.
|
|
48
|
+
is_valid: If True, return only valid systems.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of dictionaries containing system data.
|
|
52
|
+
"""
|
|
53
|
+
url = urljoin(u.get_api_url(), f"{inst_code}/system?isValid={is_valid}")
|
|
54
|
+
parsed_json = u.get_json(url)
|
|
55
|
+
return parsed_json if isinstance(parsed_json, list) else []
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def get_system(inst_code: str, get_statistics: bool = False) -> List["System"]:
|
|
59
|
+
"""Get all System objects for an installation.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
inst_code: Installation code.
|
|
63
|
+
get_statistics: (Unused) Placeholder for future extension.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of System objects.
|
|
67
|
+
"""
|
|
68
|
+
plant_data = System.get_all_data(inst_code, get_statistics)
|
|
69
|
+
return [System(x) for x in plant_data if isinstance(x, dict)]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional, Tuple, Union
|
|
1
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
2
2
|
from urllib.parse import urljoin
|
|
3
3
|
|
|
4
4
|
from stidapi import Doc
|
|
@@ -6,18 +6,38 @@ import stidapi.utils as u
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Tag:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
:
|
|
14
|
-
:
|
|
9
|
+
"""Represents an instrumentation tag in STID."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
inst_code: str = "",
|
|
14
|
+
tag_no: str = "",
|
|
15
|
+
data: Optional[Dict[str, Any]] = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Initialize Tag object.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
inst_code: (Optional) STID installation code.
|
|
21
|
+
tag_no: (Optional) Tag number as found in STID.
|
|
22
|
+
data: (Optional) Dictionary of data as returned from Tag.get() or Tag.search().
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
Raises:
|
|
25
|
+
ValueError: If neither data nor valid combination of inst_code and tag_no is provided.
|
|
17
26
|
"""
|
|
18
27
|
|
|
28
|
+
self._data: Dict[str, Any] = {}
|
|
29
|
+
|
|
19
30
|
if not data:
|
|
20
|
-
|
|
31
|
+
if not inst_code or not isinstance(inst_code, str):
|
|
32
|
+
raise ValueError("inst_code must be a non-empty string")
|
|
33
|
+
if not tag_no or not isinstance(tag_no, str):
|
|
34
|
+
raise ValueError("tag_no must be a non-empty string")
|
|
35
|
+
|
|
36
|
+
fetched_data = Tag.get(inst_code=inst_code, tag_no=tag_no)
|
|
37
|
+
if fetched_data:
|
|
38
|
+
self._data = fetched_data
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"No tag found for {inst_code}/{tag_no}")
|
|
21
41
|
else:
|
|
22
42
|
self._data = data
|
|
23
43
|
|
|
@@ -44,54 +64,65 @@ class Tag:
|
|
|
44
64
|
self.__setattr__(prop[1], self._data[prop[0]])
|
|
45
65
|
|
|
46
66
|
@property
|
|
47
|
-
def data(self) ->
|
|
67
|
+
def data(self) -> Dict[str, Any]:
|
|
68
|
+
"""Get the tag data dictionary."""
|
|
48
69
|
if hasattr(self, "_data"):
|
|
49
70
|
return self._data
|
|
50
71
|
return {}
|
|
51
72
|
|
|
52
73
|
@property
|
|
53
74
|
def tag_no(self) -> str:
|
|
75
|
+
"""Get tag number."""
|
|
54
76
|
return self.no
|
|
55
77
|
|
|
56
78
|
@property
|
|
57
79
|
def inst_code(self) -> str:
|
|
80
|
+
"""Get installation code."""
|
|
58
81
|
return str(self.data.get("instCode", ""))
|
|
59
82
|
|
|
60
83
|
@property
|
|
61
|
-
def no(self):
|
|
84
|
+
def no(self) -> str:
|
|
85
|
+
"""Get tag number."""
|
|
62
86
|
return str(self.data.get("tagNo", ""))
|
|
63
87
|
|
|
64
88
|
@property
|
|
65
89
|
def description(self) -> str:
|
|
90
|
+
"""Get tag description."""
|
|
66
91
|
return str(self.data.get("description", ""))
|
|
67
92
|
|
|
68
93
|
@property
|
|
69
|
-
def category(self):
|
|
94
|
+
def category(self) -> str:
|
|
95
|
+
"""Get tag category."""
|
|
70
96
|
return self.data.get("tagCategory", "")
|
|
71
97
|
|
|
72
98
|
@property
|
|
73
99
|
def category_description(self) -> str:
|
|
100
|
+
"""Get tag category description."""
|
|
74
101
|
return str(self.data.get("tagCategoryDescription", "")).upper()
|
|
75
102
|
|
|
76
103
|
@property
|
|
77
|
-
def discipline_description(self):
|
|
104
|
+
def discipline_description(self) -> str:
|
|
105
|
+
"""Get discipline code description."""
|
|
78
106
|
return self.data.get("disciplineCodeDescription", "")
|
|
79
107
|
|
|
80
108
|
@property
|
|
81
109
|
def is_function(self) -> bool:
|
|
110
|
+
"""Check if tag is a function block."""
|
|
82
111
|
return bool(
|
|
83
112
|
self.data.get("isFunction", self.category_description == "FUNCTION")
|
|
84
113
|
)
|
|
85
114
|
|
|
86
115
|
@property
|
|
87
116
|
def is_signal(self) -> bool:
|
|
117
|
+
"""Check if tag is a signal."""
|
|
88
118
|
return bool(self.data.get("isSignal", self.category_description == "SIGNAL"))
|
|
89
119
|
|
|
90
120
|
@property
|
|
91
|
-
def status(self):
|
|
121
|
+
def status(self) -> str:
|
|
122
|
+
"""Get tag status."""
|
|
92
123
|
return str(self.data.get("tagStatus", ""))
|
|
93
124
|
|
|
94
|
-
def get_addition_field(self, field: str = "") -> List[
|
|
125
|
+
def get_addition_field(self, field: str = "") -> List[Dict[str, Any]]:
|
|
95
126
|
"""Get list of additional fields. Optionally filter by type.
|
|
96
127
|
|
|
97
128
|
Args:
|
|
@@ -102,7 +133,7 @@ class Tag:
|
|
|
102
133
|
"""
|
|
103
134
|
return self.get_additional_field(field=field)
|
|
104
135
|
|
|
105
|
-
def get_additional_field(self, field: Optional[str] = "") -> List[
|
|
136
|
+
def get_additional_field(self, field: Optional[str] = "") -> List[Dict[str, Any]]:
|
|
106
137
|
"""Get list of additional fields. Optionally filter by type.
|
|
107
138
|
|
|
108
139
|
Args:
|
|
@@ -112,12 +143,14 @@ class Tag:
|
|
|
112
143
|
List[dict]: List of additional fields.
|
|
113
144
|
"""
|
|
114
145
|
if field is None or len(field) == 0:
|
|
115
|
-
return self._data
|
|
146
|
+
return self._data.get("additionalFields", [])
|
|
116
147
|
|
|
117
|
-
return [
|
|
148
|
+
return [
|
|
149
|
+
x for x in self._data.get("additionalFields", []) if x.get("type") == field
|
|
150
|
+
]
|
|
118
151
|
|
|
119
152
|
def get_additional_field_value(self, field: str) -> List[Tuple[str, str]]:
|
|
120
|
-
"""Get list of tuples of value and description for additional field of a given type
|
|
153
|
+
"""Get list of tuples of value and description for additional field of a given type.
|
|
121
154
|
|
|
122
155
|
Args:
|
|
123
156
|
field (str): Name of additional field to get
|
|
@@ -127,21 +160,32 @@ class Tag:
|
|
|
127
160
|
"""
|
|
128
161
|
f = self.get_additional_field(field=field)
|
|
129
162
|
if len(f) > 0:
|
|
130
|
-
return [(x
|
|
163
|
+
return [(x.get("value", ""), x.get("description", "")) for x in f]
|
|
131
164
|
|
|
132
165
|
return []
|
|
133
166
|
|
|
134
|
-
def
|
|
167
|
+
def get_ims(self) -> dict[str, str]:
|
|
168
|
+
"""Get IMS tags related to tag if available.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
str: IMS code or empty string if not available.
|
|
172
|
+
"""
|
|
173
|
+
fields = self.get_additional_field_value(field="IMS")
|
|
174
|
+
d = {}
|
|
175
|
+
[d.update({v: k}) for k, v in fields if k is not None and v is not None]
|
|
176
|
+
|
|
177
|
+
return d
|
|
178
|
+
|
|
179
|
+
def get_doc(self) -> List[Doc]:
|
|
135
180
|
"""Get list of documents that tag refers to.
|
|
136
181
|
|
|
137
182
|
Returns:
|
|
138
183
|
List[Doc]: List of referred Documents
|
|
139
184
|
"""
|
|
185
|
+
return [Doc.from_data(x) for x in self.get_doc_references()]
|
|
140
186
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def get_doc_references(self) -> List[dict]:
|
|
144
|
-
"""Get doc references of tag from stidapi as dicts.
|
|
187
|
+
def get_doc_references(self) -> List[Dict[str, Any]]:
|
|
188
|
+
"""Get doc references of tag from STID API as dictionaries.
|
|
145
189
|
|
|
146
190
|
Returns:
|
|
147
191
|
List[dict]: List of dicts describing document references.
|
|
@@ -150,8 +194,8 @@ class Tag:
|
|
|
150
194
|
u.get_api_url(),
|
|
151
195
|
f"{str(self.inst_code)}/tag/document-refs?tagNo={str(self.tag_no)}&noContentAs200=true",
|
|
152
196
|
)
|
|
153
|
-
|
|
154
|
-
return
|
|
197
|
+
json_data = u.get_json(url)
|
|
198
|
+
return json_data if isinstance(json_data, list) else []
|
|
155
199
|
|
|
156
200
|
def get_functional_location(self) -> str:
|
|
157
201
|
"""Get functional location of Tagged item or empty string if it does not have a functional location.
|
|
@@ -159,32 +203,26 @@ class Tag:
|
|
|
159
203
|
Returns:
|
|
160
204
|
str: Functional location or empty string if tagged item does not have a functional location.
|
|
161
205
|
"""
|
|
162
|
-
|
|
163
206
|
url = urljoin(
|
|
164
207
|
u.get_api_url(),
|
|
165
208
|
f"portal/{self.inst_code}/tag/sap-tag?tagNo={self.tag_no}",
|
|
166
209
|
)
|
|
167
|
-
|
|
210
|
+
json_data = u.get_json(url)
|
|
168
211
|
|
|
169
|
-
if isinstance(
|
|
170
|
-
return
|
|
212
|
+
if isinstance(json_data, dict) and "functionalLocation" in json_data:
|
|
213
|
+
return json_data.get("functionalLocation", "")
|
|
171
214
|
else:
|
|
172
215
|
return ""
|
|
173
216
|
|
|
174
|
-
def __str__(self):
|
|
175
|
-
"""
|
|
176
|
-
Overload string representation of object.
|
|
177
|
-
:return: Pretty-print string describing Tag object
|
|
178
|
-
"""
|
|
217
|
+
def __str__(self) -> str:
|
|
218
|
+
"""Get string representation of Tag object."""
|
|
179
219
|
if isinstance(self._data, dict) and len(self._data) > 0:
|
|
180
220
|
return str(self.tag_no) + "@" + str(self.inst_code) + " with data"
|
|
181
221
|
else:
|
|
182
222
|
return str(self.tag_no) + "@" + str(self.inst_code) + " with no data"
|
|
183
223
|
|
|
184
|
-
def prune_empty_data(self):
|
|
185
|
-
"""
|
|
186
|
-
Remove all empty, i.e., containing None, entries in data dictionary.
|
|
187
|
-
"""
|
|
224
|
+
def prune_empty_data(self) -> None:
|
|
225
|
+
"""Remove all empty (None) entries from data dictionary."""
|
|
188
226
|
if isinstance(self._data, dict) and len(self._data) > 0:
|
|
189
227
|
self._data = {k: v for k, v in self._data.items() if v is not None}
|
|
190
228
|
|
|
@@ -196,6 +234,8 @@ class Tag:
|
|
|
196
234
|
:param tag_no: STID tag number
|
|
197
235
|
:return: Dictionary of data from STID for tag.
|
|
198
236
|
"""
|
|
237
|
+
if not inst_code or not tag_no:
|
|
238
|
+
return {}
|
|
199
239
|
|
|
200
240
|
url = urljoin(u.get_api_url(), str(inst_code) + "/tag?tagNo=" + str(tag_no))
|
|
201
241
|
data = u.get_json(url)
|
|
@@ -206,7 +246,7 @@ class Tag:
|
|
|
206
246
|
if isinstance(data, dict):
|
|
207
247
|
return data
|
|
208
248
|
|
|
209
|
-
|
|
249
|
+
return {}
|
|
210
250
|
|
|
211
251
|
@staticmethod
|
|
212
252
|
def search(
|
|
@@ -239,8 +279,7 @@ class Tag:
|
|
|
239
279
|
List[Tag]: List of Tag objects with a bit less data than when getting
|
|
240
280
|
"""
|
|
241
281
|
|
|
242
|
-
#
|
|
243
|
-
# string contrCode, string locationCode, string plantId,
|
|
282
|
+
# Build query parameters
|
|
244
283
|
if len(tag_no) > 0:
|
|
245
284
|
tag_no = "tagNo=" + tag_no + "&"
|
|
246
285
|
|
|
@@ -283,7 +322,10 @@ class Tag:
|
|
|
283
322
|
+ str(take_string),
|
|
284
323
|
)
|
|
285
324
|
|
|
286
|
-
|
|
325
|
+
result = u.get_json(url)
|
|
326
|
+
if isinstance(result, list):
|
|
327
|
+
return [Tag(data=x) for x in result]
|
|
328
|
+
return []
|
|
287
329
|
|
|
288
330
|
@staticmethod
|
|
289
331
|
def search_additional_field(
|
|
@@ -306,7 +348,8 @@ class Tag:
|
|
|
306
348
|
res = u.get_json(url)
|
|
307
349
|
|
|
308
350
|
if isinstance(res, list):
|
|
309
|
-
return [Tag(inst_code=inst_code, tag_no=x["tagNo"]) for x in res]
|
|
351
|
+
return [Tag(inst_code=inst_code, tag_no=x["tagNo"], data=x) for x in res]
|
|
352
|
+
return []
|
|
310
353
|
|
|
311
354
|
raise ValueError(f"Invalid data {res} returned from query {url}")
|
|
312
355
|
|
|
@@ -4,7 +4,6 @@ from typing import List, Optional, Union
|
|
|
4
4
|
from eq_api_connector import APIConnector
|
|
5
5
|
from types import SimpleNamespace
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
logger = logging.getLogger(__name__)
|
|
9
8
|
|
|
10
9
|
_client = None
|
|
@@ -29,7 +28,7 @@ class STIDApiClient(APIConnector):
|
|
|
29
28
|
user_name=user_name,
|
|
30
29
|
user_assertion=user_assertion,
|
|
31
30
|
)
|
|
32
|
-
self.
|
|
31
|
+
self.url_prod = "https://stidapi.equinor.com/"
|
|
33
32
|
# todo: set dev url
|
|
34
33
|
|
|
35
34
|
|
|
@@ -49,7 +48,7 @@ def set_api_client(client: STIDApiClient):
|
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
def get_api_url():
|
|
52
|
-
return get_api_client().
|
|
51
|
+
return get_api_client().url
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
def get_object_from_json(text: str):
|
|
@@ -62,6 +61,6 @@ def get_object_from_json(text: str):
|
|
|
62
61
|
|
|
63
62
|
def get_json(url: str, raise_for_status=False):
|
|
64
63
|
c = get_api_client()
|
|
65
|
-
c.
|
|
64
|
+
c.raise_for_status = raise_for_status
|
|
66
65
|
|
|
67
66
|
return c.get_json(url=url)
|
stidapi-1.4.0/stidapi/doc.py
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
from urllib.parse import urljoin
|
|
3
|
-
import stidapi.utils as u
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class File:
|
|
7
|
-
"""Class for internal use. Copy of File as returned by document endpoint."""
|
|
8
|
-
|
|
9
|
-
# "id": 0,
|
|
10
|
-
# "instCode": "string",
|
|
11
|
-
# "fileName": "string",
|
|
12
|
-
# "objectType": "string",
|
|
13
|
-
# "description": "string",
|
|
14
|
-
# "fileOrder": 0,
|
|
15
|
-
# "prodViewCode": "string",
|
|
16
|
-
# "insertedDate": "2024-04-09T11:46:07.501Z",
|
|
17
|
-
# "thumbnail": "string",
|
|
18
|
-
# "fileSize": 0,
|
|
19
|
-
# "blobId": "string"
|
|
20
|
-
|
|
21
|
-
def __init__(self, data: dict):
|
|
22
|
-
for prop in data.keys():
|
|
23
|
-
self.__setattr__(prop, data[prop])
|
|
24
|
-
|
|
25
|
-
def __str__(self):
|
|
26
|
-
return f"InstCode: {self.instCode}, FileName: {self.fileName}, Description: {self.description}, ObjectType: {self.objectType}"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class Revision:
|
|
30
|
-
"""Class for internal use. Copy of Revision as returned by document endpoint."""
|
|
31
|
-
|
|
32
|
-
# "revNo": "string",
|
|
33
|
-
# "revStatus": "string",
|
|
34
|
-
# "revStatusDescription": "string",
|
|
35
|
-
# "revDate": "2024-04-09T11:46:07.501Z",
|
|
36
|
-
# "isCurrent": true,
|
|
37
|
-
# "projectCode": "string",
|
|
38
|
-
# "projectCodeDescription": "string",
|
|
39
|
-
# "reasonForIssue": "string",
|
|
40
|
-
# "acceptanceCode": 0,
|
|
41
|
-
# "acceptanceCodeDescription": "string",
|
|
42
|
-
# "files": [File objects]
|
|
43
|
-
|
|
44
|
-
def __init__(self, data: dict):
|
|
45
|
-
self._data = data
|
|
46
|
-
for prop in data.keys():
|
|
47
|
-
if prop == "files":
|
|
48
|
-
continue
|
|
49
|
-
self.__setattr__(prop, data[prop])
|
|
50
|
-
|
|
51
|
-
def get_files(self):
|
|
52
|
-
return [File(x) for x in self._data["files"]]
|
|
53
|
-
|
|
54
|
-
def __str__(self):
|
|
55
|
-
return (
|
|
56
|
-
f"RevNo: {self.revNo}, RevStatus: {self.revStatus}, RevDate: {self.revDate}"
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class Doc:
|
|
61
|
-
def __init__(self, inst_code: str, doc_no: str):
|
|
62
|
-
"""
|
|
63
|
-
Object initializer for Doc
|
|
64
|
-
:param inst_code: STID installation code
|
|
65
|
-
:param doc_no: Doc number as found in STID
|
|
66
|
-
|
|
67
|
-
Initializes object and gets data from STID if available.
|
|
68
|
-
"""
|
|
69
|
-
self.inst_code = ""
|
|
70
|
-
self.no = ""
|
|
71
|
-
self.title = ""
|
|
72
|
-
|
|
73
|
-
self.category = ""
|
|
74
|
-
self.discipline = ""
|
|
75
|
-
self.type = ""
|
|
76
|
-
|
|
77
|
-
self._data = Doc.get(inst_code=inst_code, doc_no=doc_no)
|
|
78
|
-
|
|
79
|
-
common_prop = [
|
|
80
|
-
("instCode", "inst_code"),
|
|
81
|
-
("docNo", "no"),
|
|
82
|
-
("docTitle", "title"),
|
|
83
|
-
("docCategoryDescription", "category"),
|
|
84
|
-
("disciplineCodeDescription", "discipline"),
|
|
85
|
-
("docTypeDescription", "type"),
|
|
86
|
-
]
|
|
87
|
-
for prop in common_prop:
|
|
88
|
-
if prop[0] in self._data:
|
|
89
|
-
self.__setattr__(prop[1], self._data[prop[0]])
|
|
90
|
-
else:
|
|
91
|
-
self.__setattr__(prop[1], "")
|
|
92
|
-
|
|
93
|
-
self.docNo = self.no
|
|
94
|
-
self.description = self.title
|
|
95
|
-
|
|
96
|
-
optional_prop = []
|
|
97
|
-
for prop in optional_prop:
|
|
98
|
-
if prop[0] in self._data and self._data[prop[0]] is not None:
|
|
99
|
-
self.__setattr__(prop[1], self._data[prop[0]])
|
|
100
|
-
|
|
101
|
-
def get_current_revision(self) -> Revision:
|
|
102
|
-
return Revision(self._data["currentRevision"])
|
|
103
|
-
|
|
104
|
-
def get_files(self) -> List[File]:
|
|
105
|
-
return self.get_current_revision().get_files()
|
|
106
|
-
|
|
107
|
-
def __str__(self):
|
|
108
|
-
return f"InstCode: {self.inst_code}, No: {self.no}, Title: {self.title}"
|
|
109
|
-
|
|
110
|
-
# "instCode": "string", "instCodeDescription": "string",
|
|
111
|
-
# "docNo": "string", "docTitle": "string",
|
|
112
|
-
# "docCategory": "string", "docCategoryDescription": "string",
|
|
113
|
-
# "docType": "string", "docTypeDescription": "string",
|
|
114
|
-
# "projectCode": "string", "projectCodeDescription": "string",
|
|
115
|
-
# "contrCode": "string", "contrCodeDescription": "string",
|
|
116
|
-
# "disciplineCode": "string","disciplineCodeDescription": "string",
|
|
117
|
-
# "locationCode": "string","locationCodeDescription": "string",
|
|
118
|
-
# "docClass": "string","docClassName": "string", "docClassDescription": "string",
|
|
119
|
-
# "poNo": "string","poNoDescription": "string",
|
|
120
|
-
# "system": "string","systemDescription": "string",
|
|
121
|
-
# "remark": "string",
|
|
122
|
-
# "supplDocNo": "string",
|
|
123
|
-
# "companyCode": "string",
|
|
124
|
-
# "source": "string",
|
|
125
|
-
# "size": "string",
|
|
126
|
-
# "priority": "string", "priorityDescription": "string",
|
|
127
|
-
# "weightBearing": "string",
|
|
128
|
-
# "productCode": "string",
|
|
129
|
-
# "insulationClass": "string", "insulationClassDescription": "string",
|
|
130
|
-
# "tagRefCount": 0, "docRefCount": 0,
|
|
131
|
-
# "currentRevision": { Revision object },
|
|
132
|
-
# "projectRevisions": [ { Revision object }],
|
|
133
|
-
# "additionalFields": [{"type": "string","typeDescription": "string","value": "string","description": "string"}],
|
|
134
|
-
# "projects": [{"instCode": "string","projectCode": "string","description": "string","stidDeliveryCode": "string",
|
|
135
|
-
# "insertedDate": "2024-04-09T11:46:07.501Z","isPrimary": true,"isValid": true,}],
|
|
136
|
-
# "purchaseOrders": [
|
|
137
|
-
# {"instCode": "string","poNo": "string","description": "string","insertedDate": "2024-04-09T11:46:07.501Z","isPrimary": true,"isValid": true}],
|
|
138
|
-
# "apiResponseTime": "string",
|
|
139
|
-
|
|
140
|
-
@staticmethod
|
|
141
|
-
def get(inst_code: str, doc_no: str):
|
|
142
|
-
"""
|
|
143
|
-
Get data from STID for a single doc.
|
|
144
|
-
:param inst_code: STID installation code
|
|
145
|
-
:param doc_no: STID doc number
|
|
146
|
-
:return: Dictionary of data from STID for doc.
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
url = urljoin(
|
|
150
|
-
u.get_api_url(), str(inst_code) + "/document?docNo=" + str(doc_no)
|
|
151
|
-
)
|
|
152
|
-
return u.get_json(url)
|
stidapi-1.4.0/stidapi/system.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from urllib.parse import urljoin
|
|
2
|
-
|
|
3
|
-
import stidapi.utils as u
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class System:
|
|
7
|
-
def __init__(self, inst_code: str = "", data=None):
|
|
8
|
-
if isinstance(inst_code, str) and len(inst_code) > 0:
|
|
9
|
-
self.inst_code = inst_code
|
|
10
|
-
|
|
11
|
-
self._data = {}
|
|
12
|
-
self.no = ""
|
|
13
|
-
self.description = ""
|
|
14
|
-
self.inst_code = ""
|
|
15
|
-
self.valid = None
|
|
16
|
-
|
|
17
|
-
if isinstance(data, dict) and len(data) > 0:
|
|
18
|
-
self._data = data
|
|
19
|
-
self.inst_code = data["instCode"]
|
|
20
|
-
self.no = data["system"]
|
|
21
|
-
self.description = data["description"]
|
|
22
|
-
self.valid = data["validFlg"]
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def get_all_data(inst_code, is_valid: bool = False):
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
:param get_statistics:
|
|
29
|
-
:return:
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
url = urljoin(u.get_api_url(), f"{inst_code}/system?isValid={is_valid}")
|
|
33
|
-
parsed_json = u.get_json(url)
|
|
34
|
-
|
|
35
|
-
return parsed_json
|
|
36
|
-
|
|
37
|
-
@staticmethod
|
|
38
|
-
def get_system(inst_code: str, get_statistics: bool = False):
|
|
39
|
-
plant_data = System.get_all_data(inst_code, get_statistics)
|
|
40
|
-
return [System(inst_code, x) for x in plant_data]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|