tagreader 6.0.0__tar.gz → 6.0.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.
- {tagreader-6.0.0 → tagreader-6.0.2}/PKG-INFO +9 -8
- {tagreader-6.0.0 → tagreader-6.0.2}/README.md +2 -2
- {tagreader-6.0.0 → tagreader-6.0.2}/pyproject.toml +4 -4
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/clients.py +28 -18
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/web_handlers.py +47 -27
- {tagreader-6.0.0 → tagreader-6.0.2}/LICENSE +0 -0
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/__init__.py +0 -0
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/__version__.py +0 -0
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/cache.py +0 -0
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/logger.py +0 -0
- {tagreader-6.0.0 → tagreader-6.0.2}/tagreader/utils.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: tagreader
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.0.2
|
|
4
4
|
Summary: Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems.
|
|
5
|
-
Home-page: https://github.com/equinor/tagreader-python
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: Aspen InfoPlus.21,OSIsoft PI
|
|
8
7
|
Author: Einar S. Idsø
|
|
@@ -22,20 +21,22 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
25
|
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
26
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
27
|
Classifier: Topic :: Utilities
|
|
28
28
|
Provides-Extra: notebooks
|
|
29
|
-
Requires-Dist: certifi (>=2024.
|
|
29
|
+
Requires-Dist: certifi (>=2024.12.14,<2025.0.0)
|
|
30
30
|
Requires-Dist: diskcache (>=5.6.1,<6.0.0)
|
|
31
|
-
Requires-Dist: matplotlib (>=3.7.
|
|
31
|
+
Requires-Dist: matplotlib (>=3.7.5,<4.0.0) ; extra == "notebooks"
|
|
32
32
|
Requires-Dist: msal-bearer (>=0.2.1,<1.2.0)
|
|
33
|
-
Requires-Dist: notebook (>=7.2.
|
|
33
|
+
Requires-Dist: notebook (>=7.2.2,<8.0.0) ; extra == "notebooks"
|
|
34
34
|
Requires-Dist: pandas (>=1)
|
|
35
35
|
Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
|
|
36
36
|
Requires-Dist: requests (>=2,<3)
|
|
37
37
|
Requires-Dist: requests-kerberos (>=0,<1)
|
|
38
38
|
Requires-Dist: requests-ntlm (>=1.1,<=2.0)
|
|
39
|
+
Project-URL: Homepage, https://github.com/equinor/tagreader-python
|
|
39
40
|
Project-URL: Repository, https://github.com/equinor/tagreader-python
|
|
40
41
|
Description-Content-Type: text/markdown
|
|
41
42
|
|
|
@@ -54,7 +55,7 @@ as possible to the backend historians.
|
|
|
54
55
|
You can install tagreader directly into your project from pypi by using pip
|
|
55
56
|
or another package manager. The only requirement is Python version 3.8 or above.
|
|
56
57
|
|
|
57
|
-
```shell
|
|
58
|
+
```shell
|
|
58
59
|
pip install tagreader
|
|
59
60
|
```
|
|
60
61
|
|
|
@@ -67,7 +68,7 @@ The following are required and will be installed:
|
|
|
67
68
|
* diskcache
|
|
68
69
|
|
|
69
70
|
## Usage
|
|
70
|
-
Tagreader easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
71
|
+
Tagreader is easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
71
72
|
you simply need to provide the corresponding IMS service URLs and IMSType.
|
|
72
73
|
See [data source](https://equinor.github.io/tagreader-python/docs/about/usage/data-source) for details.
|
|
73
74
|
|
|
@@ -13,7 +13,7 @@ as possible to the backend historians.
|
|
|
13
13
|
You can install tagreader directly into your project from pypi by using pip
|
|
14
14
|
or another package manager. The only requirement is Python version 3.8 or above.
|
|
15
15
|
|
|
16
|
-
```shell
|
|
16
|
+
```shell
|
|
17
17
|
pip install tagreader
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -26,7 +26,7 @@ The following are required and will be installed:
|
|
|
26
26
|
* diskcache
|
|
27
27
|
|
|
28
28
|
## Usage
|
|
29
|
-
Tagreader easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
29
|
+
Tagreader is easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
30
30
|
you simply need to provide the corresponding IMS service URLs and IMSType.
|
|
31
31
|
See [data source](https://equinor.github.io/tagreader-python/docs/about/usage/data-source) for details.
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tagreader"
|
|
3
|
-
version = "6.0.
|
|
3
|
+
version = "6.0.2"
|
|
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"
|
|
@@ -30,12 +30,12 @@ classifiers=[
|
|
|
30
30
|
[tool.poetry.dependencies]
|
|
31
31
|
python = "^3.8"
|
|
32
32
|
pandas = ">=1"
|
|
33
|
-
certifi = "^2024.
|
|
33
|
+
certifi = "^2024.12.14"
|
|
34
34
|
requests = "^2"
|
|
35
35
|
requests-kerberos = "^0"
|
|
36
36
|
msal-bearer = ">=0.2.1,<1.2.0"
|
|
37
|
-
notebook = { version = "^7.2.
|
|
38
|
-
matplotlib = { version = "^3.7.
|
|
37
|
+
notebook = { version = "^7.2.2", optional = true }
|
|
38
|
+
matplotlib = { version = "^3.7.5", optional = true }
|
|
39
39
|
diskcache = "^5.6.1"
|
|
40
40
|
pycryptodome = "^3.20.0"
|
|
41
41
|
requests-ntlm = ">=1.1,<=2.0"
|
|
@@ -442,15 +442,20 @@ class IMSClient:
|
|
|
442
442
|
tags = [tags]
|
|
443
443
|
units = {}
|
|
444
444
|
for tag in tags:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
self.cache
|
|
453
|
-
|
|
445
|
+
try:
|
|
446
|
+
if self.cache is not None:
|
|
447
|
+
r = self.cache.get_metadata(key=tag, properties="unit")
|
|
448
|
+
if r is not None and "unit" in r:
|
|
449
|
+
units[tag] = r["unit"]
|
|
450
|
+
if tag not in units:
|
|
451
|
+
unit = self.handler._get_tag_unit(tag)
|
|
452
|
+
if self.cache is not None and unit is not None:
|
|
453
|
+
self.cache.put_metadata(key=tag, value={"unit": unit})
|
|
454
|
+
units[tag] = unit
|
|
455
|
+
except Exception:
|
|
456
|
+
if self.search(tag) == []: # check for nonexisting string
|
|
457
|
+
logger.warning(f"Tag not found: {tag}")
|
|
458
|
+
continue
|
|
454
459
|
return units
|
|
455
460
|
|
|
456
461
|
def get_descriptions(self, tags: Union[str, List[str]]) -> Dict[str, str]:
|
|
@@ -458,15 +463,20 @@ class IMSClient:
|
|
|
458
463
|
tags = [tags]
|
|
459
464
|
descriptions = {}
|
|
460
465
|
for tag in tags:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
self.cache
|
|
469
|
-
|
|
466
|
+
try:
|
|
467
|
+
if self.cache is not None:
|
|
468
|
+
r = self.cache.get_metadata(key=tag, properties="description")
|
|
469
|
+
if r is not None and "description" in r:
|
|
470
|
+
descriptions[tag] = r["description"]
|
|
471
|
+
if tag not in descriptions:
|
|
472
|
+
desc = self.handler._get_tag_description(tag)
|
|
473
|
+
if self.cache is not None and desc is not None:
|
|
474
|
+
self.cache.put_metadata(key=tag, value={"description": desc})
|
|
475
|
+
descriptions[tag] = desc
|
|
476
|
+
except Exception:
|
|
477
|
+
if self.search(tag) == []: # check for nonexisting string
|
|
478
|
+
logger.warning(f"Tag not found: {tag}")
|
|
479
|
+
continue
|
|
470
480
|
return descriptions
|
|
471
481
|
|
|
472
482
|
def read_tags(
|
|
@@ -38,11 +38,16 @@ class MD4:
|
|
|
38
38
|
return self._hash_obj.hexdigest()
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def patched_hashlib_new(name, data=b""):
|
|
41
|
+
def patched_hashlib_new(name, data=b"", usedforsecurity=True):
|
|
42
42
|
if name.lower() == "md4":
|
|
43
43
|
return MD4(data)
|
|
44
44
|
else:
|
|
45
|
-
|
|
45
|
+
# Try / Catch easier than detecting python version
|
|
46
|
+
try:
|
|
47
|
+
return hashlib_new_method(name, data=data, usedforsecurity=usedforsecurity)
|
|
48
|
+
except TypeError:
|
|
49
|
+
# Required for python 3.8
|
|
50
|
+
return hashlib_new_method(name, data=data)
|
|
46
51
|
|
|
47
52
|
|
|
48
53
|
# Monkey-patch md4 in hashlib.new due to missing support for md4 in later releases of Python:
|
|
@@ -182,6 +187,7 @@ class BaseHandlerWeb(ABC):
|
|
|
182
187
|
self.datasource = datasource
|
|
183
188
|
self.base_url = url
|
|
184
189
|
self.session = requests.Session()
|
|
190
|
+
self.auth = auth
|
|
185
191
|
self.session.auth = auth if auth is not None else get_auth_aspen()
|
|
186
192
|
if verify_ssl is False:
|
|
187
193
|
urllib3.disable_warnings(InsecureRequestWarning)
|
|
@@ -217,14 +223,6 @@ class BaseHandlerWeb(ABC):
|
|
|
217
223
|
txt = res.text.replace('"v":nan', '"v":NaN').replace('"v":-nan', '"v":NaN')
|
|
218
224
|
return json.loads(txt)
|
|
219
225
|
|
|
220
|
-
def fetch_text(
|
|
221
|
-
self, url, params: Optional[Union[str, Dict[str, str]]] = None
|
|
222
|
-
) -> str:
|
|
223
|
-
res = self.session.get(url, params=params)
|
|
224
|
-
res.raise_for_status()
|
|
225
|
-
|
|
226
|
-
return res.text
|
|
227
|
-
|
|
228
226
|
def connect(self):
|
|
229
227
|
try:
|
|
230
228
|
self.verify_connection(self.datasource)
|
|
@@ -498,7 +496,10 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
498
496
|
if not desc and not return_desc:
|
|
499
497
|
ret.append(tagname)
|
|
500
498
|
else:
|
|
501
|
-
|
|
499
|
+
try:
|
|
500
|
+
description = self._get_tag_description(tagname)
|
|
501
|
+
except KeyError:
|
|
502
|
+
description = ""
|
|
502
503
|
ret.append((tagname, description))
|
|
503
504
|
|
|
504
505
|
if not desc:
|
|
@@ -518,11 +519,11 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
518
519
|
query = self.generate_get_unit_query(tag)
|
|
519
520
|
url = urljoin(self.base_url, "TagInfo")
|
|
520
521
|
data = self.fetch(url, params=query)
|
|
521
|
-
try:
|
|
522
|
-
|
|
523
|
-
except KeyError as e:
|
|
524
|
-
|
|
525
|
-
|
|
522
|
+
# try:
|
|
523
|
+
attr_data = data["data"]["tags"][0]["attrData"]
|
|
524
|
+
# except KeyError as e:
|
|
525
|
+
# logger.error(f"Error. I got this: {data}")
|
|
526
|
+
# raise e
|
|
526
527
|
unit = ""
|
|
527
528
|
for a in attr_data:
|
|
528
529
|
if a["g"] == "Units":
|
|
@@ -554,8 +555,8 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
554
555
|
try:
|
|
555
556
|
data = self.fetch(url, params=query)
|
|
556
557
|
desc = data["data"]["tags"][0]["attrData"][0]["samples"][0]["v"]
|
|
557
|
-
except KeyError:
|
|
558
|
-
|
|
558
|
+
# except KeyError:
|
|
559
|
+
# desc = ""
|
|
559
560
|
except JSONDecodeError:
|
|
560
561
|
desc = ""
|
|
561
562
|
return desc
|
|
@@ -659,6 +660,11 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
659
660
|
'dso="CHARINT=N;CHARFLOAT=N;CHARTIME=N;CONVERTERRORS=N" '
|
|
660
661
|
f'm="{max_rows}" to="30" s="1">'
|
|
661
662
|
)
|
|
663
|
+
query = query.replace("\t", " ").replace(
|
|
664
|
+
"\n", " "
|
|
665
|
+
) # Replace new lines and tabs that are typical in formatted SQL queries with spaces.
|
|
666
|
+
|
|
667
|
+
# Need a solution to LIKE comments. These have a % symbol in the query that does not seem to pass through the request
|
|
662
668
|
|
|
663
669
|
connection_string += f"<![CDATA[{query}]]></SQL>"
|
|
664
670
|
return connection_string
|
|
@@ -698,13 +704,26 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
698
704
|
max_rows=self._max_rows,
|
|
699
705
|
datasource=None,
|
|
700
706
|
)
|
|
701
|
-
|
|
702
|
-
|
|
707
|
+
|
|
708
|
+
res = self.session.get(url, params=params)
|
|
709
|
+
res.raise_for_status()
|
|
710
|
+
|
|
703
711
|
if parse:
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
712
|
+
parsed_dict = res.json()["data"][0]
|
|
713
|
+
|
|
714
|
+
cols = []
|
|
715
|
+
for i in parsed_dict["cols"]:
|
|
716
|
+
cols.append(i["n"])
|
|
717
|
+
|
|
718
|
+
rows = []
|
|
719
|
+
|
|
720
|
+
for i in parsed_dict["rows"]:
|
|
721
|
+
element = []
|
|
722
|
+
for j in i["fld"]:
|
|
723
|
+
element.append(j["v"])
|
|
724
|
+
rows.append(element)
|
|
725
|
+
return pd.DataFrame(data=rows, columns=cols)
|
|
726
|
+
return res.text
|
|
708
727
|
|
|
709
728
|
|
|
710
729
|
class PIHandlerWeb(BaseHandlerWeb):
|
|
@@ -772,6 +791,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
772
791
|
tag: Optional[str],
|
|
773
792
|
desc: Optional[str],
|
|
774
793
|
datasource: Optional[str],
|
|
794
|
+
auth: Optional[Any] = None,
|
|
775
795
|
) -> Dict[str, str]:
|
|
776
796
|
q = []
|
|
777
797
|
if tag is not None:
|
|
@@ -783,7 +803,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
783
803
|
|
|
784
804
|
if datasource is not None:
|
|
785
805
|
params["dataserverwebid"] = (
|
|
786
|
-
f"{get_piwebapi_source_to_webid_dict()[datasource]}"
|
|
806
|
+
f"{get_piwebapi_source_to_webid_dict(auth=auth)[datasource]}"
|
|
787
807
|
)
|
|
788
808
|
|
|
789
809
|
return params
|
|
@@ -895,7 +915,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
895
915
|
return_desc: bool = True,
|
|
896
916
|
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
897
917
|
params = self.generate_search_params(
|
|
898
|
-
tag=tag, desc=desc, datasource=self.datasource
|
|
918
|
+
tag=tag, desc=desc, datasource=self.datasource, auth=self.auth
|
|
899
919
|
)
|
|
900
920
|
url = urljoin(self.base_url, "points", "search")
|
|
901
921
|
done = False
|
|
@@ -951,7 +971,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
951
971
|
return self.web_id_cache[tag]
|
|
952
972
|
|
|
953
973
|
params = self.generate_search_params(
|
|
954
|
-
tag=tag, datasource=self.datasource, desc=None
|
|
974
|
+
tag=tag, datasource=self.datasource, desc=None, auth=self.auth
|
|
955
975
|
)
|
|
956
976
|
url = urljoin(self.base_url, "points", "search")
|
|
957
977
|
data = self.fetch(url, params=params)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|