tagreader 5.1.0__tar.gz → 5.1.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-5.1.0 → tagreader-5.1.2}/PKG-INFO +8 -9
- {tagreader-5.1.0 → tagreader-5.1.2}/README.md +6 -5
- {tagreader-5.1.0 → tagreader-5.1.2}/pyproject.toml +1 -4
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/clients.py +35 -7
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/utils.py +1 -1
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/web_handlers.py +56 -34
- {tagreader-5.1.0 → tagreader-5.1.2}/LICENSE +0 -0
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/__init__.py +0 -0
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/__version__.py +0 -0
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/cache.py +0 -0
- {tagreader-5.1.0 → tagreader-5.1.2}/tagreader/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tagreader
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.2
|
|
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
|
|
@@ -21,18 +21,16 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
25
|
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
25
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
27
|
Classifier: Topic :: Utilities
|
|
27
28
|
Provides-Extra: notebooks
|
|
28
29
|
Requires-Dist: certifi (>=2023.7.22,<2024.0.0)
|
|
29
|
-
Requires-Dist: cryptography (>=41.0.3,<42.0.0)
|
|
30
30
|
Requires-Dist: diskcache (>=5.6.1,<6.0.0)
|
|
31
31
|
Requires-Dist: matplotlib (>=3,<4) ; extra == "notebooks"
|
|
32
32
|
Requires-Dist: notebook (>=7.0.3,<8.0.0) ; extra == "notebooks"
|
|
33
33
|
Requires-Dist: pandas (>=1)
|
|
34
|
-
Requires-Dist: pillow (==10.0.1)
|
|
35
|
-
Requires-Dist: pyodbc (>=4,<5)
|
|
36
34
|
Requires-Dist: requests (>=2,<3)
|
|
37
35
|
Requires-Dist: requests-kerberos (>=0,<1)
|
|
38
36
|
Project-URL: Repository, https://github.com/equinor/tagreader-python
|
|
@@ -78,6 +76,8 @@ print(c.search("tag*"))
|
|
|
78
76
|
df = c.read_tags(["tag1", "tag2"], "18.06.2020 08:00:00", "18.06.2020 09:00:00", 60)
|
|
79
77
|
```
|
|
80
78
|
|
|
79
|
+
Note, you can add a timeout argument to the search method in order to avoid long-running search queries.
|
|
80
|
+
|
|
81
81
|
### Jupyter Notebook Quickstart
|
|
82
82
|
Jupyter Notebook examples can be found in /examples. In order to run these examples, you need to install the
|
|
83
83
|
optional dependencies.
|
|
@@ -90,10 +90,9 @@ The quickstart Jupyter Notebook can be found [here](https://github.com/equinor/t
|
|
|
90
90
|
|
|
91
91
|
For more details, see the [Tagreader Docs](https://equinor.github.io/tagreader-python/).
|
|
92
92
|
|
|
93
|
-
##
|
|
94
|
-
|
|
95
|
-
feature requests, and documentation. The preferred way of submitting a contribution is to either create an issue on
|
|
96
|
-
GitHub or to fork the project and make a pull request.
|
|
93
|
+
## Documentation
|
|
94
|
+
The full documentation can be found in [Tagreader Docs](https://equinor.github.io/tagreader-python/)
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
## Contribute
|
|
97
|
+
To starting contributing, please see [Tagreader Docs - Contribute](https://equinor.github.io/tagreader-python/docs/contribute/overview)
|
|
99
98
|
|
|
@@ -38,6 +38,8 @@ print(c.search("tag*"))
|
|
|
38
38
|
df = c.read_tags(["tag1", "tag2"], "18.06.2020 08:00:00", "18.06.2020 09:00:00", 60)
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
Note, you can add a timeout argument to the search method in order to avoid long-running search queries.
|
|
42
|
+
|
|
41
43
|
### Jupyter Notebook Quickstart
|
|
42
44
|
Jupyter Notebook examples can be found in /examples. In order to run these examples, you need to install the
|
|
43
45
|
optional dependencies.
|
|
@@ -50,9 +52,8 @@ The quickstart Jupyter Notebook can be found [here](https://github.com/equinor/t
|
|
|
50
52
|
|
|
51
53
|
For more details, see the [Tagreader Docs](https://equinor.github.io/tagreader-python/).
|
|
52
54
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
feature requests, and documentation. The preferred way of submitting a contribution is to either create an issue on
|
|
56
|
-
GitHub or to fork the project and make a pull request.
|
|
55
|
+
## Documentation
|
|
56
|
+
The full documentation can be found in [Tagreader Docs](https://equinor.github.io/tagreader-python/)
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
## Contribute
|
|
59
|
+
To starting contributing, please see [Tagreader Docs - Contribute](https://equinor.github.io/tagreader-python/docs/contribute/overview)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tagreader"
|
|
3
|
-
version = "5.1.
|
|
3
|
+
version = "5.1.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,15 +30,12 @@ classifiers=[
|
|
|
30
30
|
[tool.poetry.dependencies]
|
|
31
31
|
python = "^3.8"
|
|
32
32
|
pandas = ">=1"
|
|
33
|
-
pyodbc = "^4"
|
|
34
|
-
cryptography = "^41.0.3"
|
|
35
33
|
certifi = "^2023.7.22"
|
|
36
34
|
requests = "^2"
|
|
37
35
|
requests-kerberos = "^0"
|
|
38
36
|
notebook = { version = "^7.0.3", optional = true }
|
|
39
37
|
matplotlib = { version = "^3", optional = true }
|
|
40
38
|
diskcache = "^5.6.1"
|
|
41
|
-
pillow = "10.0.1" # Fix dependency to avoid vulnerability
|
|
42
39
|
|
|
43
40
|
|
|
44
41
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -5,6 +5,7 @@ from operator import itemgetter
|
|
|
5
5
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
6
6
|
from urllib.error import HTTPError
|
|
7
7
|
|
|
8
|
+
import numpy as np
|
|
8
9
|
import pandas as pd
|
|
9
10
|
import pytz
|
|
10
11
|
|
|
@@ -199,6 +200,7 @@ def get_handler(
|
|
|
199
200
|
options: Dict[str, Union[int, float, str]],
|
|
200
201
|
verifySSL: Optional[bool],
|
|
201
202
|
auth: Optional[Any],
|
|
203
|
+
cache: Optional[Union[SmartCache, BucketCache]] = None,
|
|
202
204
|
):
|
|
203
205
|
if imstype is None:
|
|
204
206
|
try:
|
|
@@ -224,6 +226,7 @@ def get_handler(
|
|
|
224
226
|
options=options,
|
|
225
227
|
verify_ssl=verifySSL,
|
|
226
228
|
auth=auth,
|
|
229
|
+
cache=cache,
|
|
227
230
|
)
|
|
228
231
|
|
|
229
232
|
if imstype == IMSType.ASPENONE:
|
|
@@ -278,6 +281,7 @@ class IMSClient:
|
|
|
278
281
|
f"timezone argument 'tz' needs to be either a valid timezone string or a tzinfo-object. Given type was {type(tz)}"
|
|
279
282
|
)
|
|
280
283
|
|
|
284
|
+
self.cache = cache
|
|
281
285
|
self.handler = get_handler(
|
|
282
286
|
imstype=imstype,
|
|
283
287
|
datasource=datasource,
|
|
@@ -285,22 +289,28 @@ class IMSClient:
|
|
|
285
289
|
options=handler_options,
|
|
286
290
|
verifySSL=verifySSL,
|
|
287
291
|
auth=auth,
|
|
292
|
+
cache=self.cache,
|
|
288
293
|
)
|
|
289
|
-
self.cache = cache
|
|
290
294
|
|
|
291
295
|
def connect(self) -> None:
|
|
292
296
|
self.handler.connect()
|
|
293
297
|
|
|
294
298
|
def search_tag(
|
|
295
|
-
self,
|
|
299
|
+
self,
|
|
300
|
+
tag: Optional[str] = None,
|
|
301
|
+
desc: Optional[str] = None,
|
|
302
|
+
timeout: Optional[int] = None,
|
|
296
303
|
) -> List[Tuple[str, str]]:
|
|
297
304
|
logger.warning("This function is deprecated. Please call 'search()' instead")
|
|
298
|
-
return self.search(tag=tag, desc=desc)
|
|
305
|
+
return self.search(tag=tag, desc=desc, timeout=timeout)
|
|
299
306
|
|
|
300
307
|
def search(
|
|
301
|
-
self,
|
|
308
|
+
self,
|
|
309
|
+
tag: Optional[str] = None,
|
|
310
|
+
desc: Optional[str] = None,
|
|
311
|
+
timeout: Optional[int] = None,
|
|
302
312
|
) -> List[Tuple[str, str]]:
|
|
303
|
-
return self.handler.search(tag=tag, desc=desc)
|
|
313
|
+
return self.handler.search(tag=tag, desc=desc, timeout=timeout)
|
|
304
314
|
|
|
305
315
|
def _get_metadata(self, tag: str):
|
|
306
316
|
return self.handler._get_tag_metadata(
|
|
@@ -487,7 +497,7 @@ class IMSClient:
|
|
|
487
497
|
tags: Union[str, List[str]],
|
|
488
498
|
start_time: Optional[Union[datetime, pd.Timestamp, str]] = None,
|
|
489
499
|
end_time: Optional[Union[datetime, pd.Timestamp, str]] = None,
|
|
490
|
-
ts: Union[timedelta, pd.Timedelta, int] = timedelta(seconds=60),
|
|
500
|
+
ts: Optional[Union[timedelta, pd.Timedelta, int]] = timedelta(seconds=60),
|
|
491
501
|
read_type: ReaderType = ReaderType.INT,
|
|
492
502
|
get_status: bool = False,
|
|
493
503
|
) -> pd.DataFrame:
|
|
@@ -540,8 +550,26 @@ class IMSClient:
|
|
|
540
550
|
|
|
541
551
|
if isinstance(ts, pd.Timedelta):
|
|
542
552
|
ts = ts.to_pytimedelta()
|
|
543
|
-
elif isinstance(
|
|
553
|
+
elif isinstance(
|
|
554
|
+
ts,
|
|
555
|
+
(
|
|
556
|
+
int,
|
|
557
|
+
float,
|
|
558
|
+
np.int32,
|
|
559
|
+
np.int64,
|
|
560
|
+
np.float32,
|
|
561
|
+
np.float64,
|
|
562
|
+
np.number,
|
|
563
|
+
np.integer,
|
|
564
|
+
),
|
|
565
|
+
):
|
|
544
566
|
ts = timedelta(seconds=int(ts))
|
|
567
|
+
elif not ts and read_type not in [ReaderType.SNAPSHOT, ReaderType.RAW]:
|
|
568
|
+
raise ValueError(
|
|
569
|
+
"ts needs to be a timedelta or an integer (number of seconds)"
|
|
570
|
+
" unless you are reading raw or snapshot data."
|
|
571
|
+
f" Given type: {type(ts)}"
|
|
572
|
+
)
|
|
545
573
|
elif not isinstance(ts, timedelta):
|
|
546
574
|
raise ValueError(
|
|
547
575
|
"ts needs to be either a None, timedelta or and integer (number of seconds)."
|
|
@@ -130,7 +130,7 @@ class ReaderType(enum.IntEnum):
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
def add_statoil_root_certificate() -> bool:
|
|
133
|
-
return add_equinor_root_certificate(True)
|
|
133
|
+
return add_equinor_root_certificate(True) or add_equinor_root_certificate(False)
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def add_equinor_root_certificate(get_equinor: bool = True) -> bool:
|
|
@@ -14,7 +14,7 @@ import requests
|
|
|
14
14
|
import urllib3
|
|
15
15
|
from requests_kerberos import OPTIONAL, HTTPKerberosAuth
|
|
16
16
|
|
|
17
|
-
from tagreader.cache import BaseCache
|
|
17
|
+
from tagreader.cache import BaseCache, BucketCache, SmartCache
|
|
18
18
|
from tagreader.logger import logger
|
|
19
19
|
from tagreader.utils import ReaderType, is_mac, is_windows, urljoin
|
|
20
20
|
|
|
@@ -115,8 +115,20 @@ class BaseHandlerWeb(ABC):
|
|
|
115
115
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
116
116
|
self.session.verify = verify_ssl if verify_ssl is not None else get_verify_ssl()
|
|
117
117
|
|
|
118
|
-
def fetch(
|
|
119
|
-
|
|
118
|
+
def fetch(
|
|
119
|
+
self,
|
|
120
|
+
url,
|
|
121
|
+
params: Optional[Union[str, Dict[str, str]]] = None,
|
|
122
|
+
timeout: Optional[int] = None,
|
|
123
|
+
) -> Dict:
|
|
124
|
+
res = self.session.get(
|
|
125
|
+
url,
|
|
126
|
+
params=params,
|
|
127
|
+
timeout=(
|
|
128
|
+
None,
|
|
129
|
+
timeout,
|
|
130
|
+
),
|
|
131
|
+
) # Noqa. Read timeout, No connect timeout.
|
|
120
132
|
res.raise_for_status()
|
|
121
133
|
|
|
122
134
|
if len(res.text) == 0:
|
|
@@ -366,7 +378,9 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
366
378
|
if v:
|
|
367
379
|
return k
|
|
368
380
|
|
|
369
|
-
def search(
|
|
381
|
+
def search(
|
|
382
|
+
self, tag: Optional[str], desc: Optional[str], timeout: Optional[int] = None
|
|
383
|
+
) -> List[Tuple[str, str]]:
|
|
370
384
|
if tag is None:
|
|
371
385
|
raise ValueError("Tag is a required argument")
|
|
372
386
|
|
|
@@ -386,7 +400,7 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
386
400
|
)
|
|
387
401
|
url = urljoin(self.base_url, "Browse?")
|
|
388
402
|
url += encoded_params
|
|
389
|
-
data = self.fetch(url)
|
|
403
|
+
data = self.fetch(url, timeout=timeout)
|
|
390
404
|
|
|
391
405
|
if "tags" not in data["data"]:
|
|
392
406
|
return []
|
|
@@ -602,6 +616,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
602
616
|
auth: Optional[Any],
|
|
603
617
|
verify_ssl: bool,
|
|
604
618
|
options: Dict[str, Union[int, float, str]],
|
|
619
|
+
cache: Optional[Union[SmartCache, BucketCache]],
|
|
605
620
|
):
|
|
606
621
|
self._max_rows = options.get("max_rows", 10000)
|
|
607
622
|
if url is None:
|
|
@@ -615,7 +630,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
615
630
|
verify_ssl=verify_ssl,
|
|
616
631
|
)
|
|
617
632
|
self._max_rows = options.get("max_rows", 10000)
|
|
618
|
-
self.web_id_cache =
|
|
633
|
+
self.web_id_cache = cache
|
|
619
634
|
|
|
620
635
|
@staticmethod
|
|
621
636
|
def _time_to_UTC_string(time: datetime) -> str:
|
|
@@ -772,7 +787,10 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
772
787
|
return False
|
|
773
788
|
|
|
774
789
|
def search(
|
|
775
|
-
self,
|
|
790
|
+
self,
|
|
791
|
+
tag: Optional[str] = None,
|
|
792
|
+
desc: Optional[str] = None,
|
|
793
|
+
timeout: Optional[int] = None,
|
|
776
794
|
) -> List[Tuple]:
|
|
777
795
|
params = self.generate_search_query(
|
|
778
796
|
tag=tag, desc=desc, datasource=self.datasource
|
|
@@ -781,7 +799,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
781
799
|
done = False
|
|
782
800
|
ret = []
|
|
783
801
|
while not done:
|
|
784
|
-
data = self.fetch(url, params=params)
|
|
802
|
+
data = self.fetch(url, params=params, timeout=timeout)
|
|
785
803
|
|
|
786
804
|
for item in data["Items"]:
|
|
787
805
|
description = item["Description"] if "Description" in item else ""
|
|
@@ -823,33 +841,37 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
823
841
|
:return: WebId
|
|
824
842
|
:rtype: str
|
|
825
843
|
"""
|
|
826
|
-
if tag
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
844
|
+
if self.web_id_cache and tag in self.web_id_cache:
|
|
845
|
+
return self.web_id_cache[tag]
|
|
846
|
+
|
|
847
|
+
params = self.generate_search_query(
|
|
848
|
+
tag=tag, datasource=self.datasource, desc=None
|
|
849
|
+
)
|
|
850
|
+
params["fields"] = "name;webid"
|
|
851
|
+
url = urljoin(self.base_url, "search", "query")
|
|
852
|
+
data = self.fetch(url, params=params)
|
|
853
|
+
|
|
854
|
+
if len(data["Errors"]) > 0:
|
|
855
|
+
msg = f"Received error from server when searching for WebId for {tag}: {data['Errors']}"
|
|
856
|
+
raise ValueError(msg)
|
|
857
|
+
|
|
858
|
+
if len(data["Items"]) > 1:
|
|
859
|
+
# Compare elements and if same, return the first
|
|
860
|
+
first = data["Items"][0]
|
|
861
|
+
for item in data["Items"][1:]:
|
|
862
|
+
if item != first:
|
|
863
|
+
raise AssertionError(
|
|
864
|
+
f"Received {len(data['Items'])} results when trying to find unique WebId for {tag}."
|
|
865
|
+
)
|
|
866
|
+
elif len(data["Items"]) == 0:
|
|
867
|
+
logger.warning(f"Tag {tag} not found")
|
|
868
|
+
return None
|
|
869
|
+
|
|
870
|
+
web_id = data["Items"][0]["WebId"]
|
|
871
|
+
|
|
872
|
+
if self.web_id_cache:
|
|
851
873
|
self.web_id_cache[tag] = web_id
|
|
852
|
-
return
|
|
874
|
+
return web_id
|
|
853
875
|
|
|
854
876
|
@staticmethod
|
|
855
877
|
def _is_summary(read_type: ReaderType) -> bool:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|