stidapi 1.4.4__tar.gz → 1.4.5__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.4 → stidapi-1.4.5}/PKG-INFO +2 -2
- {stidapi-1.4.4 → stidapi-1.4.5}/pyproject.toml +3 -3
- {stidapi-1.4.4 → stidapi-1.4.5}/pyproject.toml.orig +3 -3
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/doc.py +12 -5
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/plant.py +2 -3
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/system.py +2 -5
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/tag.py +88 -95
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/utils.py +37 -3
- {stidapi-1.4.4 → stidapi-1.4.5}/README.md +0 -0
- {stidapi-1.4.4 → stidapi-1.4.5}/src/stidapi/__init__.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.5
|
|
4
4
|
Summary: Python client for connecting to Equinor STIDapi
|
|
5
5
|
Author: Åsmund Våge Fannemel
|
|
6
6
|
Author-email: Åsmund Våge Fannemel <asmf@equinor.com>
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Dist: eq-api-connector>=1.
|
|
14
|
+
Requires-Dist: eq-api-connector>=1.2.0,<2
|
|
15
15
|
Requires-Python: >=3.10, <4
|
|
16
16
|
Project-URL: repository, https://github.com/equinor/STIDapi-python
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stidapi"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.5"
|
|
4
4
|
description = "Python client for connecting to Equinor STIDapi"
|
|
5
5
|
requires-python = ">=3.10,<4"
|
|
6
6
|
readme = "README.md"
|
|
@@ -13,7 +13,7 @@ classifiers = [
|
|
|
13
13
|
"Programming Language :: Python :: 3.13",
|
|
14
14
|
"Programming Language :: Python :: 3.14",
|
|
15
15
|
]
|
|
16
|
-
dependencies = ["eq-api-connector>=1.
|
|
16
|
+
dependencies = ["eq-api-connector>=1.2.0,<2"]
|
|
17
17
|
|
|
18
18
|
[[project.authors]]
|
|
19
19
|
name = "Åsmund Våge Fannemel"
|
|
@@ -31,5 +31,5 @@ dev = [
|
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[build-system]
|
|
34
|
-
requires = ["uv_build>=0.11.16,<0.
|
|
34
|
+
requires = ["uv_build>=0.11.16,<0.14.0"]
|
|
35
35
|
build-backend = "uv_build"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stidapi"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.5"
|
|
4
4
|
description = "Python client for connecting to Equinor STIDapi"
|
|
5
5
|
authors = [{ name = "Åsmund Våge Fannemel", email = "asmf@equinor.com" }]
|
|
6
6
|
requires-python = ">=3.10,<4"
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3.13",
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
]
|
|
17
|
-
dependencies = ["eq-api-connector>=1.
|
|
17
|
+
dependencies = ["eq-api-connector>=1.2.0,<2"]
|
|
18
18
|
|
|
19
19
|
[project.urls]
|
|
20
20
|
repository = "https://github.com/equinor/STIDapi-python"
|
|
@@ -28,5 +28,5 @@ dev = [
|
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[build-system]
|
|
31
|
-
requires = ["uv_build>=0.11.16,<0.
|
|
31
|
+
requires = ["uv_build>=0.11.16,<0.14.0"]
|
|
32
32
|
build-backend = "uv_build"
|
|
@@ -220,7 +220,7 @@ class Doc:
|
|
|
220
220
|
raise ValueError("doc_no must be a non-empty string")
|
|
221
221
|
|
|
222
222
|
self._data: Dict[str, Any] = {}
|
|
223
|
-
self._full_data_loaded = data is None or
|
|
223
|
+
self._full_data_loaded = data is None or self._has_full_data(data)
|
|
224
224
|
|
|
225
225
|
if data is not None:
|
|
226
226
|
self._data = data
|
|
@@ -244,6 +244,15 @@ class Doc:
|
|
|
244
244
|
"""Get document number."""
|
|
245
245
|
return self._data.get("docNo", "")
|
|
246
246
|
|
|
247
|
+
@staticmethod
|
|
248
|
+
def _has_full_data(data: Dict[str, Any]) -> bool:
|
|
249
|
+
"""Return whether document data contains usable revision information."""
|
|
250
|
+
current_revision = data.get("currentRevision")
|
|
251
|
+
if isinstance(current_revision, dict) and current_revision:
|
|
252
|
+
return True
|
|
253
|
+
project_revisions = data.get("projectRevisions")
|
|
254
|
+
return isinstance(project_revisions, list) and bool(project_revisions)
|
|
255
|
+
|
|
247
256
|
def _ensure_full_data(self) -> None:
|
|
248
257
|
"""Reload document data once when initialized from a partial reference."""
|
|
249
258
|
if self._full_data_loaded:
|
|
@@ -354,10 +363,8 @@ class Doc:
|
|
|
354
363
|
if not inst_code or not doc_no:
|
|
355
364
|
return None
|
|
356
365
|
|
|
357
|
-
url =
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
return u.get_json(url)
|
|
366
|
+
url = u.build_url(f"{inst_code}/document", {"docNo": doc_no})
|
|
367
|
+
return u.get_json_dict(url)
|
|
361
368
|
|
|
362
369
|
@staticmethod
|
|
363
370
|
def from_data(data: Dict[str, Any]) -> "Doc":
|
|
@@ -91,10 +91,9 @@ class Plant:
|
|
|
91
91
|
@classmethod
|
|
92
92
|
def get_all_data(cls):
|
|
93
93
|
if len(cls._plant_list) == 0:
|
|
94
|
-
parsed_json = u.
|
|
94
|
+
parsed_json = u.get_json_list("/plants")
|
|
95
95
|
|
|
96
|
-
if isinstance(
|
|
97
|
-
cls._plant_list = [x for x in parsed_json if "instCode" in x]
|
|
96
|
+
cls._plant_list = [x for x in parsed_json if isinstance(x, dict) and "instCode" in x]
|
|
98
97
|
|
|
99
98
|
return cls._plant_list
|
|
100
99
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
|
2
|
-
from urllib.parse import urljoin
|
|
3
|
-
|
|
4
2
|
import stidapi.utils as u
|
|
5
3
|
|
|
6
4
|
|
|
@@ -50,9 +48,8 @@ class System:
|
|
|
50
48
|
Returns:
|
|
51
49
|
List of dictionaries containing system data.
|
|
52
50
|
"""
|
|
53
|
-
url =
|
|
54
|
-
|
|
55
|
-
return parsed_json if isinstance(parsed_json, list) else []
|
|
51
|
+
url = u.build_url(f"{inst_code}/system", {"isValid": is_valid})
|
|
52
|
+
return [item for item in u.get_json_list(url) if isinstance(item, dict)]
|
|
56
53
|
|
|
57
54
|
@staticmethod
|
|
58
55
|
def get_system(inst_code: str, get_statistics: bool = False) -> List["System"]:
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
2
|
-
from urllib.parse import urljoin
|
|
3
|
-
|
|
4
2
|
from stidapi import Doc
|
|
5
3
|
import stidapi.utils as u
|
|
6
4
|
|
|
@@ -41,28 +39,6 @@ class Tag:
|
|
|
41
39
|
else:
|
|
42
40
|
self._data = data
|
|
43
41
|
|
|
44
|
-
if self.is_function:
|
|
45
|
-
function_prop = [("functionBlock", "FB")]
|
|
46
|
-
for prop in function_prop:
|
|
47
|
-
if prop[0] in self._data:
|
|
48
|
-
self.__setattr__(prop[1], self._data[prop[0]])
|
|
49
|
-
|
|
50
|
-
if self.is_signal:
|
|
51
|
-
signal_prop = [("signalType", "signalType")]
|
|
52
|
-
for prop in signal_prop:
|
|
53
|
-
if prop[0] in self._data:
|
|
54
|
-
self.__setattr__(prop[1], self._data[prop[0]])
|
|
55
|
-
|
|
56
|
-
optional_prop = [
|
|
57
|
-
("setpointL", "L"),
|
|
58
|
-
("setpointLl", "LL"),
|
|
59
|
-
("setpointH", "H"),
|
|
60
|
-
("setpointHh", "HH"),
|
|
61
|
-
]
|
|
62
|
-
for prop in optional_prop:
|
|
63
|
-
if prop[0] in self._data and self._data[prop[0]] is not None:
|
|
64
|
-
self.__setattr__(prop[1], self._data[prop[0]])
|
|
65
|
-
|
|
66
42
|
@property
|
|
67
43
|
def data(self) -> Dict[str, Any]:
|
|
68
44
|
"""Get the tag data dictionary."""
|
|
@@ -107,15 +83,67 @@ class Tag:
|
|
|
107
83
|
|
|
108
84
|
@property
|
|
109
85
|
def is_function(self) -> bool:
|
|
110
|
-
"""Check if tag is a function
|
|
111
|
-
|
|
112
|
-
|
|
86
|
+
"""Check if tag is a function tag.
|
|
87
|
+
|
|
88
|
+
True when the tag category is FUNCTION or when STID flags the tag as
|
|
89
|
+
having function data (``isFunction``).
|
|
90
|
+
"""
|
|
91
|
+
return self.category_description == "FUNCTION" or bool(
|
|
92
|
+
self.data.get("isFunction", False)
|
|
113
93
|
)
|
|
114
94
|
|
|
115
95
|
@property
|
|
116
96
|
def is_signal(self) -> bool:
|
|
117
|
-
"""Check if tag is a signal.
|
|
118
|
-
|
|
97
|
+
"""Check if tag is a signal tag.
|
|
98
|
+
|
|
99
|
+
True when the tag category is SIGNAL or when STID flags the tag as
|
|
100
|
+
having signal data (``isSignal``).
|
|
101
|
+
"""
|
|
102
|
+
return self.category_description == "SIGNAL" or bool(
|
|
103
|
+
self.data.get("isSignal", False)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def FB(self) -> Optional[str]:
|
|
108
|
+
"""Get the function-block code for function tags.
|
|
109
|
+
|
|
110
|
+
Returns "" for tags identified by is_function without a defined function
|
|
111
|
+
block and None for tags that are not identified as functions.
|
|
112
|
+
"""
|
|
113
|
+
if not self.is_function:
|
|
114
|
+
return None
|
|
115
|
+
return str(self.data.get("functionBlock") or "")
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def signalType(self) -> Optional[str]:
|
|
119
|
+
"""Get the signal type for signal tags.
|
|
120
|
+
|
|
121
|
+
Returns "" for tags identified by is_signal without a defined signal
|
|
122
|
+
type and None for tags that are not identified as signals.
|
|
123
|
+
"""
|
|
124
|
+
if not self.is_signal:
|
|
125
|
+
return None
|
|
126
|
+
return str(self.data.get("signalType") or "")
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def L(self) -> Optional[str]:
|
|
130
|
+
"""Get the low setpoint, if defined."""
|
|
131
|
+
return self.data.get("setpointL")
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def LL(self) -> Optional[str]:
|
|
135
|
+
"""Get the low-low setpoint, if defined."""
|
|
136
|
+
return self.data.get("setpointLl")
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def H(self) -> Optional[str]:
|
|
140
|
+
"""Get the high setpoint, if defined."""
|
|
141
|
+
return self.data.get("setpointH")
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def HH(self) -> Optional[str]:
|
|
145
|
+
"""Get the high-high setpoint, if defined."""
|
|
146
|
+
return self.data.get("setpointHh")
|
|
119
147
|
|
|
120
148
|
@property
|
|
121
149
|
def status(self) -> str:
|
|
@@ -190,12 +218,11 @@ class Tag:
|
|
|
190
218
|
Returns:
|
|
191
219
|
List[dict]: List of dicts describing document references.
|
|
192
220
|
"""
|
|
193
|
-
url =
|
|
194
|
-
|
|
195
|
-
|
|
221
|
+
url = u.build_url(
|
|
222
|
+
f"{self.inst_code}/tag/document-refs",
|
|
223
|
+
{"tagNo": self.tag_no, "noContentAs200": True},
|
|
196
224
|
)
|
|
197
|
-
|
|
198
|
-
return json_data if isinstance(json_data, list) else []
|
|
225
|
+
return [item for item in u.get_json_list(url) if isinstance(item, dict)]
|
|
199
226
|
|
|
200
227
|
def get_functional_location(self) -> str:
|
|
201
228
|
"""Get functional location of Tagged item or empty string if it does not have a functional location.
|
|
@@ -203,11 +230,10 @@ class Tag:
|
|
|
203
230
|
Returns:
|
|
204
231
|
str: Functional location or empty string if tagged item does not have a functional location.
|
|
205
232
|
"""
|
|
206
|
-
url =
|
|
207
|
-
|
|
208
|
-
f"portal/{self.inst_code}/tag/sap-tag?tagNo={self.tag_no}",
|
|
233
|
+
url = u.build_url(
|
|
234
|
+
f"portal/{self.inst_code}/tag/sap-tag", {"tagNo": self.tag_no}
|
|
209
235
|
)
|
|
210
|
-
json_data = u.
|
|
236
|
+
json_data = u.get_json_dict(url)
|
|
211
237
|
|
|
212
238
|
if isinstance(json_data, dict) and "functionalLocation" in json_data:
|
|
213
239
|
return json_data.get("functionalLocation", "")
|
|
@@ -237,7 +263,7 @@ class Tag:
|
|
|
237
263
|
if not inst_code or not tag_no:
|
|
238
264
|
return {}
|
|
239
265
|
|
|
240
|
-
url =
|
|
266
|
+
url = u.build_url(f"{inst_code}/tag", {"tagNo": tag_no})
|
|
241
267
|
data = u.get_json(url)
|
|
242
268
|
|
|
243
269
|
if isinstance(data, list) and len(data) == 1:
|
|
@@ -279,53 +305,20 @@ class Tag:
|
|
|
279
305
|
List[Tag]: List of Tag objects with a bit less data than when getting
|
|
280
306
|
"""
|
|
281
307
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if len(tag_type) > 0:
|
|
298
|
-
tag_type = "tagType=" + tag_type + "&"
|
|
299
|
-
|
|
300
|
-
if len(system) > 0:
|
|
301
|
-
system = "system=" + system + "&"
|
|
302
|
-
|
|
303
|
-
if len(discipline_code) > 0:
|
|
304
|
-
discipline_code = "disciplineCode=" + discipline_code + "&"
|
|
305
|
-
|
|
306
|
-
skip_string = "skip=" + str(skip) + "&"
|
|
307
|
-
|
|
308
|
-
# take shall be last, thus no trailing &
|
|
309
|
-
take_string = "take=" + str(take)
|
|
310
|
-
|
|
311
|
-
url = urljoin(
|
|
312
|
-
u.get_api_url(),
|
|
313
|
-
f"/{inst_code}/tags"
|
|
314
|
-
+ "?"
|
|
315
|
-
+ str(tag_no)
|
|
316
|
-
+ str(description)
|
|
317
|
-
+ str(tag_status)
|
|
318
|
-
+ str(tag_category_string)
|
|
319
|
-
+ str(tag_type)
|
|
320
|
-
+ str(system)
|
|
321
|
-
+ str(skip_string)
|
|
322
|
-
+ str(take_string),
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
result = u.get_json(url)
|
|
326
|
-
if isinstance(result, list):
|
|
327
|
-
return [Tag(data=x) for x in result]
|
|
328
|
-
return []
|
|
308
|
+
query = {
|
|
309
|
+
"tagNo": tag_no,
|
|
310
|
+
"description": description,
|
|
311
|
+
"tagStatus": tag_status,
|
|
312
|
+
"tagCategory": tag_category if tag_category > 0 else None,
|
|
313
|
+
"tagType": tag_type,
|
|
314
|
+
"system": system,
|
|
315
|
+
"disciplineCode": discipline_code,
|
|
316
|
+
"skip": skip,
|
|
317
|
+
"take": take,
|
|
318
|
+
}
|
|
319
|
+
url = u.build_url(f"{inst_code}/tags", query)
|
|
320
|
+
|
|
321
|
+
return [Tag(data=x) for x in u.get_json_list(url) if isinstance(x, dict)]
|
|
329
322
|
|
|
330
323
|
@staticmethod
|
|
331
324
|
def search_additional_field(
|
|
@@ -341,17 +334,17 @@ class Tag:
|
|
|
341
334
|
Returns:
|
|
342
335
|
List[Tag]: List of Tag objects
|
|
343
336
|
"""
|
|
344
|
-
url =
|
|
345
|
-
|
|
346
|
-
|
|
337
|
+
url = u.build_url(
|
|
338
|
+
f"{inst_code}/tag-additional-field/{field_name}",
|
|
339
|
+
{"searchValue": value, "noContentAs200": True},
|
|
347
340
|
)
|
|
348
|
-
res = u.
|
|
341
|
+
res = u.get_json_list(url)
|
|
349
342
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
343
|
+
return [
|
|
344
|
+
Tag(inst_code=inst_code, tag_no=x["tagNo"], data=x)
|
|
345
|
+
for x in res
|
|
346
|
+
if isinstance(x, dict) and "tagNo" in x
|
|
347
|
+
]
|
|
355
348
|
|
|
356
349
|
@staticmethod
|
|
357
350
|
def get_from_additional_field(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from typing import List, Optional, Union
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from urllib.parse import urlencode, urljoin
|
|
4
5
|
from eq_api_connector import APIConnector
|
|
5
6
|
from types import SimpleNamespace
|
|
6
7
|
|
|
@@ -47,10 +48,24 @@ def set_api_client(client: STIDApiClient):
|
|
|
47
48
|
_client = client
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def get_api_url():
|
|
51
|
+
def get_api_url() -> str:
|
|
51
52
|
return get_api_client().url
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def build_url(path: str, query: Optional[Dict[str, Any]] = None) -> str:
|
|
56
|
+
"""Build an API URL with URL-encoded query parameters."""
|
|
57
|
+
url = urljoin(get_api_url(), path.lstrip("/"))
|
|
58
|
+
if query:
|
|
59
|
+
query = {
|
|
60
|
+
key: value
|
|
61
|
+
for key, value in query.items()
|
|
62
|
+
if value is not None and value != ""
|
|
63
|
+
}
|
|
64
|
+
if query:
|
|
65
|
+
url = f"{url}?{urlencode(query)}"
|
|
66
|
+
return url
|
|
67
|
+
|
|
68
|
+
|
|
54
69
|
def get_object_from_json(text: str):
|
|
55
70
|
if isinstance(text, list):
|
|
56
71
|
obj = [json.loads(x, object_hook=lambda d: SimpleNamespace(**d)) for x in text]
|
|
@@ -59,8 +74,27 @@ def get_object_from_json(text: str):
|
|
|
59
74
|
return obj
|
|
60
75
|
|
|
61
76
|
|
|
62
|
-
def get_json(url: str, raise_for_status=False):
|
|
77
|
+
def get_json(url: str, raise_for_status: bool = False) -> Any:
|
|
78
|
+
"""Fetch JSON and propagate connector errors."""
|
|
63
79
|
c = get_api_client()
|
|
64
80
|
c.raise_for_status = raise_for_status
|
|
65
81
|
|
|
66
82
|
return c.get_json(url=url)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_json_dict(
|
|
86
|
+
url: str, raise_for_status: bool = False
|
|
87
|
+
) -> Optional[Dict[str, Any]]:
|
|
88
|
+
"""Fetch JSON and return an object, or None for another response shape."""
|
|
89
|
+
data = get_json(url, raise_for_status=raise_for_status)
|
|
90
|
+
if data is None:
|
|
91
|
+
return None
|
|
92
|
+
return data if isinstance(data, dict) else None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_json_list(url: str, raise_for_status: bool = False) -> List[Any]:
|
|
96
|
+
"""Fetch JSON and return an array, or an empty list for another shape."""
|
|
97
|
+
data = get_json(url, raise_for_status=raise_for_status)
|
|
98
|
+
if data is None:
|
|
99
|
+
return []
|
|
100
|
+
return data if isinstance(data, list) else []
|
|
File without changes
|
|
File without changes
|