stidapi 1.0.1__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.0.1/PKG-INFO +58 -0
- stidapi-1.0.1/README.md +37 -0
- stidapi-1.0.1/pyproject.toml +24 -0
- stidapi-1.0.1/stidapi/Doc.py +152 -0
- stidapi-1.0.1/stidapi/Plant.py +113 -0
- stidapi-1.0.1/stidapi/System.py +40 -0
- stidapi-1.0.1/stidapi/Tag.py +262 -0
- stidapi-1.0.1/stidapi/utils.py +48 -0
stidapi-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stidapi
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Python client for connecting to Equinor STIDapi
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Author: Åsmund Våge Fannemel
|
|
7
|
+
Author-email: asmf@equinor.com
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: certifi (>=2023.11.17,<2024.0.0)
|
|
16
|
+
Requires-Dist: msal-bearer (>=0.2.1,<1.1.0)
|
|
17
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
18
|
+
Project-URL: repository, https://github.com/equinor/STIDapi-python
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# STIDapi-python
|
|
22
|
+
|
|
23
|
+
A simple wrapper package to interface Equinor [STIDapi](https://stidapi.equinor.com/).
|
|
24
|
+
Used to get plant, systen and tag and (future) doc data from STID rest api.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Use
|
|
28
|
+
|
|
29
|
+
Try it out by running the [demo](examples/demo.py).
|
|
30
|
+
|
|
31
|
+
## Installing
|
|
32
|
+
|
|
33
|
+
To **install** call the following from your project environment:
|
|
34
|
+
`pip install git+https://github.com/equinor/STIDapi-python.git`
|
|
35
|
+
|
|
36
|
+
To **upgrade** call the following from your project environment:
|
|
37
|
+
`pip install --upgrade git+https://github.com/equinor/STIDapi-python.git`
|
|
38
|
+
|
|
39
|
+
To include in your project, add the following to your requirements file:
|
|
40
|
+
`git+https://github.com/equinor/STIDapi-python.git`
|
|
41
|
+
|
|
42
|
+
or in requirements setup.py:
|
|
43
|
+
`'stidapi @ git+https://github.com/equinor/STIDapi-python'`
|
|
44
|
+
|
|
45
|
+
or in pyproject.toml:
|
|
46
|
+
`stidapi = { git = "https://github.com/Equinor/stidapi-python" }`
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Developing / testing
|
|
50
|
+
|
|
51
|
+
Poetry is preferred for developers. Install with required packages for testing and coverage:
|
|
52
|
+
`poetry install`
|
|
53
|
+
|
|
54
|
+
Call `poetry run pytest` to run tests.
|
|
55
|
+
|
|
56
|
+
To generate coverage report in html run `poetry run pytest --cov=stidapi tests/ --cov-report html`
|
|
57
|
+
|
|
58
|
+
|
stidapi-1.0.1/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# STIDapi-python
|
|
2
|
+
|
|
3
|
+
A simple wrapper package to interface Equinor [STIDapi](https://stidapi.equinor.com/).
|
|
4
|
+
Used to get plant, systen and tag and (future) doc data from STID rest api.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Use
|
|
8
|
+
|
|
9
|
+
Try it out by running the [demo](examples/demo.py).
|
|
10
|
+
|
|
11
|
+
## Installing
|
|
12
|
+
|
|
13
|
+
To **install** call the following from your project environment:
|
|
14
|
+
`pip install git+https://github.com/equinor/STIDapi-python.git`
|
|
15
|
+
|
|
16
|
+
To **upgrade** call the following from your project environment:
|
|
17
|
+
`pip install --upgrade git+https://github.com/equinor/STIDapi-python.git`
|
|
18
|
+
|
|
19
|
+
To include in your project, add the following to your requirements file:
|
|
20
|
+
`git+https://github.com/equinor/STIDapi-python.git`
|
|
21
|
+
|
|
22
|
+
or in requirements setup.py:
|
|
23
|
+
`'stidapi @ git+https://github.com/equinor/STIDapi-python'`
|
|
24
|
+
|
|
25
|
+
or in pyproject.toml:
|
|
26
|
+
`stidapi = { git = "https://github.com/Equinor/stidapi-python" }`
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Developing / testing
|
|
30
|
+
|
|
31
|
+
Poetry is preferred for developers. Install with required packages for testing and coverage:
|
|
32
|
+
`poetry install`
|
|
33
|
+
|
|
34
|
+
Call `poetry run pytest` to run tests.
|
|
35
|
+
|
|
36
|
+
To generate coverage report in html run `poetry run pytest --cov=stidapi tests/ --cov-report html`
|
|
37
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "stidapi"
|
|
3
|
+
version = "1.0.1"
|
|
4
|
+
description = "Python client for connecting to Equinor STIDapi"
|
|
5
|
+
authors = ["Åsmund Våge Fannemel <asmf@equinor.com>"]
|
|
6
|
+
license = "Proprietary"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.urls]
|
|
10
|
+
"repository" = "https://github.com/equinor/STIDapi-python"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.9"
|
|
14
|
+
certifi = "^2023.11.17"
|
|
15
|
+
msal-bearer = ">=0.2.1,<1.1.0"
|
|
16
|
+
requests = "^2.31.0"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.group.test.dependencies]
|
|
19
|
+
pytest = "^7.4.4"
|
|
20
|
+
pytest-cov = "^4.1.0"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,152 @@
|
|
|
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 tag 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)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
import stidapi.utils as u
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Plant:
|
|
8
|
+
_plant_list = []
|
|
9
|
+
|
|
10
|
+
def __init__(self, code: Union[str, int] = "", data=None):
|
|
11
|
+
self.inst_code = ""
|
|
12
|
+
self.sap_id = ""
|
|
13
|
+
self.description = ""
|
|
14
|
+
self.business_area = ""
|
|
15
|
+
self.ims = ""
|
|
16
|
+
|
|
17
|
+
if isinstance(code, int):
|
|
18
|
+
code = str(code)
|
|
19
|
+
|
|
20
|
+
if isinstance(code, str) and len(code) > 0:
|
|
21
|
+
self.inst_code = code
|
|
22
|
+
if data is None:
|
|
23
|
+
data = Plant._get_data(code)
|
|
24
|
+
|
|
25
|
+
if isinstance(data, dict) and len(data) > 0:
|
|
26
|
+
self._data = data
|
|
27
|
+
self.inst_code = data["instCode"]
|
|
28
|
+
self.sap_id = data["sapPlantId"]
|
|
29
|
+
self.description = data["description"]
|
|
30
|
+
self.business_area = data["businessArea"]
|
|
31
|
+
self.ims = data["imsPlant"]
|
|
32
|
+
else:
|
|
33
|
+
self._data = {}
|
|
34
|
+
|
|
35
|
+
self.plant_code = self.inst_code
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
if not isinstance(self.inst_code, str) or len(self.inst_code) == 0:
|
|
39
|
+
return "Empty plant object"
|
|
40
|
+
|
|
41
|
+
if len(self._data) > 0:
|
|
42
|
+
return (
|
|
43
|
+
"Plant "
|
|
44
|
+
+ str(self.inst_code)
|
|
45
|
+
+ " - "
|
|
46
|
+
+ str(self.description)
|
|
47
|
+
+ " with data"
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
return (
|
|
51
|
+
"Plant "
|
|
52
|
+
+ str(self.inst_code)
|
|
53
|
+
+ " - "
|
|
54
|
+
+ str(self.description)
|
|
55
|
+
+ " with no data"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def get_all_inst_code(cls) -> List[str]:
|
|
60
|
+
return [x["instCode"] for x in Plant.get_all_data()]
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def get_all_data(cls):
|
|
64
|
+
if len(cls._plant_list) == 0:
|
|
65
|
+
url = urljoin(u.get_api_url(), "plants")
|
|
66
|
+
parsed_json = u.get_json(url)
|
|
67
|
+
|
|
68
|
+
if isinstance(parsed_json, list):
|
|
69
|
+
cls._plant_list = [x for x in parsed_json if "instCode" in x]
|
|
70
|
+
|
|
71
|
+
return cls._plant_list
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def _get_data(cls, code: Union[str, int]):
|
|
75
|
+
if (
|
|
76
|
+
code is None
|
|
77
|
+
or (isinstance(code, str) and len(code) == 0)
|
|
78
|
+
or (isinstance(code, int) and code < 1000)
|
|
79
|
+
):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
plant_data_list = Plant.get_all_data()
|
|
83
|
+
|
|
84
|
+
if len(plant_data_list) == 0:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
if isinstance(code, str):
|
|
88
|
+
plant_data_list = [
|
|
89
|
+
x
|
|
90
|
+
for x in plant_data_list
|
|
91
|
+
if x["instCode"].lower() == code.lower() or x["sapPlantId"] == code
|
|
92
|
+
]
|
|
93
|
+
elif isinstance(code, int):
|
|
94
|
+
plant_data_list = [x for x in plant_data_list if x["sapPlantId"] == code]
|
|
95
|
+
else:
|
|
96
|
+
return ValueError("Input code must be string or int.")
|
|
97
|
+
|
|
98
|
+
if len(plant_data_list) > 0:
|
|
99
|
+
return plant_data_list[0]
|
|
100
|
+
else:
|
|
101
|
+
print("Warning: did not get data for Plant " + str(code))
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def get_plant(cls, inst_code: Union[str, int]) -> "Plant":
|
|
106
|
+
plant_data = Plant._get_data(inst_code)
|
|
107
|
+
return Plant(inst_code, plant_data)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def get_plants(cls) -> List["Plant"]:
|
|
111
|
+
plant_data_list = Plant.get_all_data()
|
|
112
|
+
|
|
113
|
+
return [Plant(x["instCode"], x) for x in plant_data_list]
|
|
@@ -0,0 +1,40 @@
|
|
|
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]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
from stidapi.Doc import Doc
|
|
5
|
+
from stidapi.Plant import Plant
|
|
6
|
+
import stidapi.utils as u
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Tag:
|
|
10
|
+
def __init__(self, inst_code: str, tag_no: str):
|
|
11
|
+
"""
|
|
12
|
+
Object initializer for Tag
|
|
13
|
+
:param inst_code: STID installation code
|
|
14
|
+
:param tag_no: Tag number as found in STID
|
|
15
|
+
|
|
16
|
+
Initializes object and gets data from STID if available.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
self.inst_code = ""
|
|
20
|
+
self.no = ""
|
|
21
|
+
self.description = ""
|
|
22
|
+
self.category = ""
|
|
23
|
+
self.discipline = ""
|
|
24
|
+
self.is_function = False
|
|
25
|
+
self.is_signal = False
|
|
26
|
+
|
|
27
|
+
self._data = Tag.get(inst_code=inst_code, tag_no=tag_no)
|
|
28
|
+
|
|
29
|
+
common_prop = [
|
|
30
|
+
("instCode", "inst_code"),
|
|
31
|
+
("tagNo", "no"),
|
|
32
|
+
("description", "description"),
|
|
33
|
+
("tagCategoryDescription", "category"),
|
|
34
|
+
("disciplineCodeDescription", "discipline"),
|
|
35
|
+
]
|
|
36
|
+
for prop in common_prop:
|
|
37
|
+
if prop[0] in self._data:
|
|
38
|
+
self.__setattr__(prop[1], self._data[prop[0]])
|
|
39
|
+
else:
|
|
40
|
+
self.__setattr__(prop[1], "")
|
|
41
|
+
|
|
42
|
+
self.tag_no = self.no
|
|
43
|
+
|
|
44
|
+
if "isFunction" in self._data and self._data["isFunction"]:
|
|
45
|
+
self.is_function = True
|
|
46
|
+
function_prop = [("functionBlock", "FB")]
|
|
47
|
+
for prop in function_prop:
|
|
48
|
+
if prop[0] in self._data:
|
|
49
|
+
self.__setattr__(prop[1], self._data[prop[0]])
|
|
50
|
+
|
|
51
|
+
if "isSignal" in self._data and self._data["isSignal"]:
|
|
52
|
+
self.is_signal = True
|
|
53
|
+
signal_prop = [("signalType", "signalType")]
|
|
54
|
+
for prop in signal_prop:
|
|
55
|
+
if prop[0] in self._data:
|
|
56
|
+
self.__setattr__(prop[1], self._data[prop[0]])
|
|
57
|
+
|
|
58
|
+
optional_prop = [
|
|
59
|
+
("setpointL", "L"),
|
|
60
|
+
("setpointLl", "LL"),
|
|
61
|
+
("setpointH", "H"),
|
|
62
|
+
("setpointHh", "HH"),
|
|
63
|
+
]
|
|
64
|
+
for prop in optional_prop:
|
|
65
|
+
if prop[0] in self._data and self._data[prop[0]] is not None:
|
|
66
|
+
self.__setattr__(prop[1], self._data[prop[0]])
|
|
67
|
+
|
|
68
|
+
def get_addition_field(self, field: str = "") -> List[dict]:
|
|
69
|
+
"""Get list of additional fields. Optionally filter by type.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
field (str, optional): Filter by field type. Defaults to "" which returns all.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List[dict]: List of additional fields.
|
|
76
|
+
"""
|
|
77
|
+
if field is None or len(field) == 0:
|
|
78
|
+
return self._data["additionalFields"]
|
|
79
|
+
|
|
80
|
+
return [x for x in self._data["additionalFields"] if x["type"] == field]
|
|
81
|
+
|
|
82
|
+
def get_doc(self) -> List[Doc]:
|
|
83
|
+
"""Get list of documents that tag refers to.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List[Doc]: List of referred Documents
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
return [Doc(x["instCode"], x["docNo"]) for x in self.get_doc_references()]
|
|
90
|
+
|
|
91
|
+
def get_doc_references(self) -> List[dict]:
|
|
92
|
+
"""Get doc references of tag from stidapi as dicts.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List[dict]: List of dicts describing document references.
|
|
96
|
+
"""
|
|
97
|
+
url = urljoin(
|
|
98
|
+
u.get_api_url(),
|
|
99
|
+
f"{str(self.inst_code)}/tag/document-refs?tagNo={str(self.tag_no)}&noContentAs200=true",
|
|
100
|
+
)
|
|
101
|
+
json = u.get_json(url)
|
|
102
|
+
return json
|
|
103
|
+
|
|
104
|
+
def get_functional_location(self) -> str:
|
|
105
|
+
"""Get functional location of Tagged item or empty string if it does not have a functional location.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
str: Functional location or empty string if tagged item does not have a functional location.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
url = urljoin(
|
|
112
|
+
u.get_api_url(),
|
|
113
|
+
f"portal/{self.inst_code}/tag/sap-tag?tagNo={self.tag_no}",
|
|
114
|
+
)
|
|
115
|
+
json = u.get_json(url)
|
|
116
|
+
|
|
117
|
+
if isinstance(json, dict) and "functionalLocation" in json.keys():
|
|
118
|
+
return json["functionalLocation"]
|
|
119
|
+
else:
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
def __str__(self):
|
|
123
|
+
"""
|
|
124
|
+
Overload string representation of object.
|
|
125
|
+
:return: Pretty-print string describing Tag object
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(self._data, dict) and len(self._data) > 0:
|
|
128
|
+
return str(self.tag_no) + "@" + str(self.inst_code) + " with data"
|
|
129
|
+
else:
|
|
130
|
+
return str(self.tag_no) + "@" + str(self.inst_code) + " with no data"
|
|
131
|
+
|
|
132
|
+
def prune_empty_data(self):
|
|
133
|
+
"""
|
|
134
|
+
Remove all empty, i.e., containing None, entries in data dictionary.
|
|
135
|
+
"""
|
|
136
|
+
if isinstance(self._data, dict) and len(self._data) > 0:
|
|
137
|
+
self._data = {k: v for k, v in self._data.items() if v is not None}
|
|
138
|
+
else:
|
|
139
|
+
print("Tag has no data to prune")
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def get(inst_code: str, tag_no: str):
|
|
143
|
+
"""
|
|
144
|
+
Get tag data from STID for a single tag.
|
|
145
|
+
:param inst_code: STID installation code
|
|
146
|
+
:param tag_no: STID tag number
|
|
147
|
+
:return: Dictionary of data from STID for tag.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
url = urljoin(u.get_api_url(), str(inst_code) + "/tag?tagNo=" + str(tag_no))
|
|
151
|
+
return u.get_json(url)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def search(
|
|
155
|
+
inst_code: str,
|
|
156
|
+
tag_no: str = "",
|
|
157
|
+
description: str = "",
|
|
158
|
+
tag_status: str = "",
|
|
159
|
+
tag_category: int = -1,
|
|
160
|
+
tag_type: str = "",
|
|
161
|
+
system: str = "",
|
|
162
|
+
discipline_code: str = "",
|
|
163
|
+
skip: int = 0,
|
|
164
|
+
take: int = 50,
|
|
165
|
+
):
|
|
166
|
+
# string subSystem, string mainSystem, string projectCode, string poNo,
|
|
167
|
+
# string contrCode, string locationCode, string plantId,
|
|
168
|
+
if len(tag_no) > 0:
|
|
169
|
+
tag_no = "tagNo=" + tag_no + "&"
|
|
170
|
+
|
|
171
|
+
if len(description) > 0:
|
|
172
|
+
description = "description=" + description + "&"
|
|
173
|
+
|
|
174
|
+
if len(tag_status) > 0:
|
|
175
|
+
tag_status = "tagStatus=" + tag_status + "&"
|
|
176
|
+
|
|
177
|
+
if tag_category > 0:
|
|
178
|
+
tag_category_string = "tagCategory=" + str(tag_category) + "&"
|
|
179
|
+
else:
|
|
180
|
+
tag_category_string = ""
|
|
181
|
+
|
|
182
|
+
if len(tag_type) > 0:
|
|
183
|
+
tag_type = "tagType=" + tag_type + "&"
|
|
184
|
+
|
|
185
|
+
if len(system) > 0:
|
|
186
|
+
system = "system=" + system + "&"
|
|
187
|
+
|
|
188
|
+
if len(discipline_code) > 0:
|
|
189
|
+
discipline_code = "disciplineCode=" + discipline_code + "&"
|
|
190
|
+
|
|
191
|
+
skip_string = "skip=" + str(skip) + "&"
|
|
192
|
+
|
|
193
|
+
# take shall be last, thus no trailing &
|
|
194
|
+
take_string = "take=" + str(take)
|
|
195
|
+
|
|
196
|
+
url = urljoin(
|
|
197
|
+
u.get_api_url(),
|
|
198
|
+
f"/{inst_code}/tags"
|
|
199
|
+
+ "?"
|
|
200
|
+
+ str(tag_no)
|
|
201
|
+
+ str(description)
|
|
202
|
+
+ str(tag_status)
|
|
203
|
+
+ str(tag_category_string)
|
|
204
|
+
+ str(tag_type)
|
|
205
|
+
+ str(system)
|
|
206
|
+
+ str(skip_string)
|
|
207
|
+
+ str(take_string),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return u.get_json(url)
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def search_additional_field(
|
|
214
|
+
inst_code: str, field_name: str, value: str
|
|
215
|
+
) -> List[dict]:
|
|
216
|
+
url = urljoin(
|
|
217
|
+
u.get_api_url(),
|
|
218
|
+
f"/{inst_code}/tag-additional-field/{field_name}?searchValue={value}&noContentAs200=true",
|
|
219
|
+
)
|
|
220
|
+
return u.get_json(url)
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def get_from_additional_field(
|
|
224
|
+
field_name: str,
|
|
225
|
+
value: str,
|
|
226
|
+
inst_code: Union[str, List[str]] = None,
|
|
227
|
+
stop_first=True,
|
|
228
|
+
) -> List["Tag"]:
|
|
229
|
+
"""Get List of Tag objects by searching for additional fields
|
|
230
|
+
field_name: Name of additional field
|
|
231
|
+
value: Value of additional field
|
|
232
|
+
inst_code: STID plant code, or list of codes, to limit search to.
|
|
233
|
+
Defaults to empty which will search all.
|
|
234
|
+
stop_first: Set to False to get matches from all provided inst_code,
|
|
235
|
+
else will stop at first match. Defaults to False.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
(List["Tag"]): List of Tag objects for matches.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
if inst_code is None or (isinstance(inst_code, str) and len(inst_code) == 0):
|
|
242
|
+
inst_code = Plant.get_all_inst_code()
|
|
243
|
+
|
|
244
|
+
if isinstance(inst_code, str):
|
|
245
|
+
inst_code = [inst_code]
|
|
246
|
+
|
|
247
|
+
res = []
|
|
248
|
+
|
|
249
|
+
for code in inst_code:
|
|
250
|
+
data = [
|
|
251
|
+
Tag(inst_code=x["instCode"], tag_no=x["tagNo"])
|
|
252
|
+
for x in Tag.search_additional_field(
|
|
253
|
+
inst_code=code, field_name=field_name, value=value
|
|
254
|
+
)
|
|
255
|
+
]
|
|
256
|
+
if len(data) > 0:
|
|
257
|
+
res.extend(data)
|
|
258
|
+
|
|
259
|
+
if stop_first:
|
|
260
|
+
break
|
|
261
|
+
|
|
262
|
+
return res
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_api_url():
|
|
8
|
+
return "https://stidapi.equinor.com/"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_auth():
|
|
12
|
+
from msal_bearer.BearerAuth import BearerAuth, get_login_name
|
|
13
|
+
|
|
14
|
+
tenantID = "3aa4a235-b6e2-48d5-9195-7fcf05b459b0"
|
|
15
|
+
clientID = "1a35a8df-b48d-40df-987b-267968b1b198" # stidapi-python
|
|
16
|
+
scopes = ["1734406c-3449-4192-a50d-7c3a63d3f57d/user_impersonation"]
|
|
17
|
+
auth = BearerAuth.get_auth(
|
|
18
|
+
tenantID=tenantID,
|
|
19
|
+
clientID=clientID,
|
|
20
|
+
scopes=scopes,
|
|
21
|
+
username=f"{get_login_name()}@equinor.com",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return auth
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_object_from_json(text: str):
|
|
28
|
+
if isinstance(text, list):
|
|
29
|
+
obj = [json.loads(x, object_hook=lambda d: SimpleNamespace(**d)) for x in text]
|
|
30
|
+
else:
|
|
31
|
+
obj = json.loads(text, object_hook=lambda d: SimpleNamespace(**d))
|
|
32
|
+
return obj
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_json(url: str):
|
|
36
|
+
response = requests.get(url, auth=get_auth())
|
|
37
|
+
# response.raise_for_status()
|
|
38
|
+
if response.status_code == 200:
|
|
39
|
+
try:
|
|
40
|
+
return response.json()
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
print(
|
|
43
|
+
f"Warning: {str(url)} returned successfully, but not with a valid json response"
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
print(f"Warning: {str(url)} returned status code {response.status_code}")
|
|
47
|
+
|
|
48
|
+
return []
|