tagreader 5.1.3__tar.gz → 5.2.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.
- {tagreader-5.1.3 → tagreader-5.2.0}/PKG-INFO +4 -1
- {tagreader-5.1.3 → tagreader-5.2.0}/pyproject.toml +4 -1
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/clients.py +6 -3
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/utils.py +3 -2
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/web_handlers.py +92 -22
- {tagreader-5.1.3 → tagreader-5.2.0}/LICENSE +0 -0
- {tagreader-5.1.3 → tagreader-5.2.0}/README.md +0 -0
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/__init__.py +0 -0
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/__version__.py +0 -0
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/cache.py +0 -0
- {tagreader-5.1.3 → tagreader-5.2.0}/tagreader/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tagreader
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.0
|
|
4
4
|
Summary: Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems.
|
|
5
5
|
Home-page: https://github.com/equinor/tagreader-python
|
|
6
6
|
License: MIT
|
|
@@ -29,10 +29,13 @@ Provides-Extra: notebooks
|
|
|
29
29
|
Requires-Dist: certifi (>=2023.7.22,<2024.0.0)
|
|
30
30
|
Requires-Dist: diskcache (>=5.6.1,<6.0.0)
|
|
31
31
|
Requires-Dist: matplotlib (>=3,<4) ; extra == "notebooks"
|
|
32
|
+
Requires-Dist: msal-bearer (>=0.1.0,<0.2.0)
|
|
32
33
|
Requires-Dist: notebook (>=7.0.3,<8.0.0) ; extra == "notebooks"
|
|
33
34
|
Requires-Dist: pandas (>=1)
|
|
35
|
+
Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
|
|
34
36
|
Requires-Dist: requests (>=2,<3)
|
|
35
37
|
Requires-Dist: requests-kerberos (>=0,<1)
|
|
38
|
+
Requires-Dist: requests-ntlm (>=1.2.0,<2.0.0)
|
|
36
39
|
Project-URL: Repository, https://github.com/equinor/tagreader-python
|
|
37
40
|
Description-Content-Type: text/markdown
|
|
38
41
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tagreader"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.2.0"
|
|
4
4
|
description = "Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems."
|
|
5
5
|
authors = ["Einar S. Idsø <eiids@equinor.com>", "Morten Dæhli Aslesen <masl@equinor.com"]
|
|
6
6
|
license = "MIT"
|
|
@@ -33,9 +33,12 @@ pandas = ">=1"
|
|
|
33
33
|
certifi = "^2023.7.22"
|
|
34
34
|
requests = "^2"
|
|
35
35
|
requests-kerberos = "^0"
|
|
36
|
+
msal-bearer = "^0.1.0"
|
|
36
37
|
notebook = { version = "^7.0.3", optional = true }
|
|
37
38
|
matplotlib = { version = "^3", optional = true }
|
|
38
39
|
diskcache = "^5.6.1"
|
|
40
|
+
pycryptodome = "^3.20.0"
|
|
41
|
+
requests-ntlm = "^1.2.0"
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -300,7 +300,7 @@ class IMSClient:
|
|
|
300
300
|
tag: Optional[str] = None,
|
|
301
301
|
desc: Optional[str] = None,
|
|
302
302
|
timeout: Optional[int] = None,
|
|
303
|
-
) -> List[Tuple[str, str]]:
|
|
303
|
+
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
304
304
|
logger.warning("This function is deprecated. Please call 'search()' instead")
|
|
305
305
|
return self.search(tag=tag, desc=desc, timeout=timeout)
|
|
306
306
|
|
|
@@ -309,8 +309,11 @@ class IMSClient:
|
|
|
309
309
|
tag: Optional[str] = None,
|
|
310
310
|
desc: Optional[str] = None,
|
|
311
311
|
timeout: Optional[int] = None,
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
return_desc: bool = True,
|
|
313
|
+
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
314
|
+
return self.handler.search(
|
|
315
|
+
tag=tag, desc=desc, timeout=timeout, return_desc=return_desc
|
|
316
|
+
)
|
|
314
317
|
|
|
315
318
|
def _get_metadata(self, tag: str):
|
|
316
319
|
return self.handler._get_tag_metadata(
|
|
@@ -171,13 +171,12 @@ def add_equinor_root_certificate(get_equinor: bool = True) -> bool:
|
|
|
171
171
|
logger.debug("Scanning CA certificate in Windows cert store", end="")
|
|
172
172
|
for cert in ssl.enum_certificates("CA"):
|
|
173
173
|
der = cert[0]
|
|
174
|
+
# deepcode ignore InsecureHash: <Only hashes to compare with known hash>
|
|
174
175
|
if hashlib.sha1(der).hexdigest().upper() == used_hash:
|
|
175
176
|
found = True
|
|
176
177
|
logger.debug("CA certificate found!")
|
|
177
178
|
break
|
|
178
179
|
elif is_mac():
|
|
179
|
-
import subprocess
|
|
180
|
-
|
|
181
180
|
macos_ca_certs = subprocess.run(
|
|
182
181
|
["security", "find-certificate", "-a", "-c", ca_search, "-Z"],
|
|
183
182
|
stdout=subprocess.PIPE,
|
|
@@ -186,6 +185,7 @@ def add_equinor_root_certificate(get_equinor: bool = True) -> bool:
|
|
|
186
185
|
if used_hash in str(macos_ca_certs).upper():
|
|
187
186
|
c = get_macos_equinor_certificates()
|
|
188
187
|
for cert in c:
|
|
188
|
+
# deepcode ignore InsecureHash: <Only hashes to compare with known hash>
|
|
189
189
|
if hashlib.sha1(cert).hexdigest().upper() == used_hash:
|
|
190
190
|
der = cert
|
|
191
191
|
found = True
|
|
@@ -260,6 +260,7 @@ def is_equinor() -> bool:
|
|
|
260
260
|
|
|
261
261
|
host = socket.gethostname()
|
|
262
262
|
|
|
263
|
+
# deepcode ignore IdenticalBranches: Not an error. First test is just more precise.
|
|
263
264
|
if host + ".client.statoil.net" in str(s):
|
|
264
265
|
return True
|
|
265
266
|
elif "client.statoil.net" in host and host in str(s):
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import json
|
|
2
3
|
import re
|
|
3
4
|
import urllib.parse
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from datetime import datetime, timedelta
|
|
7
|
+
from hashlib import new as hashlib_new_method
|
|
6
8
|
from json.decoder import JSONDecodeError
|
|
7
|
-
from pathlib import Path
|
|
8
9
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
@@ -12,13 +13,42 @@ import pandas as pd
|
|
|
12
13
|
import pytz
|
|
13
14
|
import requests
|
|
14
15
|
import urllib3
|
|
16
|
+
from Crypto.Hash import MD4 as _MD4
|
|
15
17
|
from requests_kerberos import OPTIONAL, HTTPKerberosAuth
|
|
18
|
+
from urllib3.exceptions import InsecureRequestWarning
|
|
16
19
|
|
|
17
|
-
from tagreader.cache import
|
|
20
|
+
from tagreader.cache import BucketCache, SmartCache
|
|
18
21
|
from tagreader.logger import logger
|
|
19
22
|
from tagreader.utils import ReaderType, is_mac, is_windows, urljoin
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
class MD4:
|
|
26
|
+
def __init__(self, data=None):
|
|
27
|
+
self._hash_obj = _MD4.new()
|
|
28
|
+
if data is not None:
|
|
29
|
+
self._hash_obj.update(data)
|
|
30
|
+
|
|
31
|
+
def update(self, data):
|
|
32
|
+
self._hash_obj.update(data)
|
|
33
|
+
|
|
34
|
+
def digest(self):
|
|
35
|
+
return self._hash_obj.digest()
|
|
36
|
+
|
|
37
|
+
def hexdigest(self):
|
|
38
|
+
return self._hash_obj.hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def patched_hashlib_new(name, data=b""):
|
|
42
|
+
if name.lower() == "md4":
|
|
43
|
+
return MD4(data)
|
|
44
|
+
else:
|
|
45
|
+
return hashlib_new_method(name, data)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Monkey-patch md4 in hashlib.new due to missing support for md4 in later releases of Python:
|
|
49
|
+
hashlib.new = patched_hashlib_new
|
|
50
|
+
|
|
51
|
+
|
|
22
52
|
def get_verify_ssl() -> Union[bool, str]:
|
|
23
53
|
if is_windows() or is_mac():
|
|
24
54
|
return True
|
|
@@ -33,13 +63,28 @@ def get_url_pi() -> str:
|
|
|
33
63
|
return r"https://piwebapi.equinor.com/piwebapi"
|
|
34
64
|
|
|
35
65
|
|
|
36
|
-
def get_auth_aspen(
|
|
37
|
-
|
|
66
|
+
def get_auth_aspen(use_internal: bool = True):
|
|
67
|
+
if use_internal:
|
|
68
|
+
return HTTPKerberosAuth(mutual_authentication=OPTIONAL)
|
|
69
|
+
|
|
70
|
+
from msal_bearer.BearerAuth import BearerAuth
|
|
71
|
+
|
|
72
|
+
tenantID = "3aa4a235-b6e2-48d5-9195-7fcf05b459b0"
|
|
73
|
+
clientID = "7adaaa99-897f-428c-8a5f-4053db565b32"
|
|
74
|
+
scopes = [
|
|
75
|
+
"https://ewepwapa1pep04-statoilsrm.msappproxy.net/ProcessExplorer/ProcessData//user_impersonation"
|
|
76
|
+
]
|
|
77
|
+
return BearerAuth.get_auth(
|
|
78
|
+
tenantID=tenantID, clientID=clientID, scopes=scopes, verbose=True
|
|
79
|
+
)
|
|
38
80
|
|
|
39
81
|
|
|
40
|
-
def get_url_aspen() -> str:
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
def get_url_aspen(use_internal: bool = False) -> str:
|
|
83
|
+
if use_internal:
|
|
84
|
+
# internal url (redirects to url including AtProcessDataREST.dll)
|
|
85
|
+
return r"https://aspenone.api.equinor.com"
|
|
86
|
+
|
|
87
|
+
return r"https://ewepwapa1pep04-statoilsrm.msappproxy.net/ProcessExplorer/ProcessData/AtProcessDataREST.dll"
|
|
43
88
|
|
|
44
89
|
|
|
45
90
|
def list_aspenone_sources(
|
|
@@ -57,7 +102,7 @@ def list_aspenone_sources(
|
|
|
57
102
|
verify_ssl = get_verify_ssl()
|
|
58
103
|
|
|
59
104
|
if verify_ssl is False:
|
|
60
|
-
urllib3.disable_warnings(
|
|
105
|
+
urllib3.disable_warnings(InsecureRequestWarning)
|
|
61
106
|
|
|
62
107
|
url_ = urljoin(url, "DataSources")
|
|
63
108
|
params = {"service": "ProcessData", "allQuotes": 1}
|
|
@@ -86,7 +131,7 @@ def list_piwebapi_sources(
|
|
|
86
131
|
verify_ssl = get_verify_ssl()
|
|
87
132
|
|
|
88
133
|
if verify_ssl is False:
|
|
89
|
-
urllib3.disable_warnings(
|
|
134
|
+
urllib3.disable_warnings(InsecureRequestWarning)
|
|
90
135
|
|
|
91
136
|
url_ = urljoin(url, "dataservers")
|
|
92
137
|
res = requests.get(url_, auth=auth, verify=verify_ssl)
|
|
@@ -112,7 +157,7 @@ class BaseHandlerWeb(ABC):
|
|
|
112
157
|
self.session = requests.Session()
|
|
113
158
|
self.session.auth = auth if auth is not None else get_auth_aspen()
|
|
114
159
|
if verify_ssl is False:
|
|
115
|
-
urllib3.disable_warnings(
|
|
160
|
+
urllib3.disable_warnings(InsecureRequestWarning)
|
|
116
161
|
self.session.verify = verify_ssl if verify_ssl is not None else get_verify_ssl()
|
|
117
162
|
|
|
118
163
|
def fetch(
|
|
@@ -162,8 +207,7 @@ class BaseHandlerWeb(ABC):
|
|
|
162
207
|
) from None
|
|
163
208
|
|
|
164
209
|
@abstractmethod
|
|
165
|
-
def verify_connection(self, datasource: str):
|
|
166
|
-
...
|
|
210
|
+
def verify_connection(self, datasource: str): ...
|
|
167
211
|
|
|
168
212
|
|
|
169
213
|
class AspenHandlerWeb(BaseHandlerWeb):
|
|
@@ -379,8 +423,24 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
379
423
|
return k
|
|
380
424
|
|
|
381
425
|
def search(
|
|
382
|
-
self,
|
|
383
|
-
|
|
426
|
+
self,
|
|
427
|
+
tag: Optional[str],
|
|
428
|
+
desc: Optional[str],
|
|
429
|
+
timeout: Optional[int] = None,
|
|
430
|
+
return_desc: bool = True,
|
|
431
|
+
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
432
|
+
ret = []
|
|
433
|
+
|
|
434
|
+
if tag is None and desc is None:
|
|
435
|
+
raise ValueError("Tag is a required argument")
|
|
436
|
+
# return ret
|
|
437
|
+
elif tag is None and len(desc) == 0:
|
|
438
|
+
raise ValueError("Tag is a required argument")
|
|
439
|
+
# return ret
|
|
440
|
+
elif desc is None and len(tag) == 0:
|
|
441
|
+
raise ValueError("Tag is a required argument")
|
|
442
|
+
# return ret
|
|
443
|
+
|
|
384
444
|
if tag is None:
|
|
385
445
|
raise ValueError("Tag is a required argument")
|
|
386
446
|
|
|
@@ -403,19 +463,24 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
403
463
|
data = self.fetch(url, timeout=timeout)
|
|
404
464
|
|
|
405
465
|
if "tags" not in data["data"]:
|
|
406
|
-
return
|
|
466
|
+
return ret
|
|
407
467
|
|
|
408
|
-
ret = []
|
|
409
468
|
for item in data["data"]["tags"]:
|
|
410
469
|
tagname = item["t"]
|
|
411
|
-
|
|
412
|
-
|
|
470
|
+
if not desc and not return_desc:
|
|
471
|
+
ret.append(tagname)
|
|
472
|
+
else:
|
|
473
|
+
description = self._get_tag_description(tagname)
|
|
474
|
+
ret.append((tagname, description))
|
|
413
475
|
|
|
414
476
|
if not desc:
|
|
415
|
-
|
|
477
|
+
pass
|
|
478
|
+
else:
|
|
479
|
+
r = re.compile(desc)
|
|
480
|
+
ret = [x for x in ret if r.search(x[1])]
|
|
481
|
+
if not return_desc:
|
|
482
|
+
ret = [x[0] for x in ret]
|
|
416
483
|
|
|
417
|
-
r = re.compile(desc)
|
|
418
|
-
ret = [x for x in ret if r.search(x[1])]
|
|
419
484
|
return ret
|
|
420
485
|
|
|
421
486
|
def _get_tag_metadata(self, tag: str):
|
|
@@ -791,7 +856,8 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
791
856
|
tag: Optional[str] = None,
|
|
792
857
|
desc: Optional[str] = None,
|
|
793
858
|
timeout: Optional[int] = None,
|
|
794
|
-
|
|
859
|
+
return_desc: bool = True,
|
|
860
|
+
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
795
861
|
params = self.generate_search_query(
|
|
796
862
|
tag=tag, desc=desc, datasource=self.datasource
|
|
797
863
|
)
|
|
@@ -809,6 +875,10 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
809
875
|
params["start"] = next_start # noqa
|
|
810
876
|
else:
|
|
811
877
|
done = True
|
|
878
|
+
|
|
879
|
+
if not return_desc:
|
|
880
|
+
ret = [x[0] for x in ret]
|
|
881
|
+
|
|
812
882
|
return ret
|
|
813
883
|
|
|
814
884
|
def _get_tag_metadata(self, tag: str) -> Dict[str, str]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|