tagreader 6.2.0__tar.gz → 6.2.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.
- {tagreader-6.2.0 → tagreader-6.2.1}/PKG-INFO +5 -4
- {tagreader-6.2.0 → tagreader-6.2.1}/README.md +4 -3
- {tagreader-6.2.0 → tagreader-6.2.1}/pyproject.toml +1 -1
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/cache.py +2 -3
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/clients.py +37 -17
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/utils.py +17 -5
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/web_handlers.py +10 -5
- {tagreader-6.2.0 → tagreader-6.2.1}/LICENSE +0 -0
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/__init__.py +0 -0
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/__version__.py +0 -0
- {tagreader-6.2.0 → tagreader-6.2.1}/tagreader/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tagreader
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.1
|
|
4
4
|
Summary: Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -37,6 +37,10 @@ Project-URL: Homepage, https://github.com/equinor/tagreader-python
|
|
|
37
37
|
Project-URL: Repository, https://github.com/equinor/tagreader-python
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
|
+
⚠️ **Important:** **We are phasing out TagReader, and the code will no longer be maintained.**
|
|
41
|
+
|
|
42
|
+
For employees in Equinor we recommend using Omnia Timeseries
|
|
43
|
+
|
|
40
44
|
# tagreader-python <!-- omit in toc -->
|
|
41
45
|
|
|
42
46
|

|
|
@@ -86,6 +90,3 @@ For more details, see the [Tagreader Docs](https://equinor.github.io/tagreader-p
|
|
|
86
90
|
## Documentation
|
|
87
91
|
The full documentation can be found in [Tagreader Docs](https://equinor.github.io/tagreader-python/)
|
|
88
92
|
|
|
89
|
-
## Contribute
|
|
90
|
-
To starting contributing, please see [Tagreader Docs - Contribute](https://equinor.github.io/tagreader-python/docs/contribute/overview)
|
|
91
|
-
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
⚠️ **Important:** **We are phasing out TagReader, and the code will no longer be maintained.**
|
|
2
|
+
|
|
3
|
+
For employees in Equinor we recommend using Omnia Timeseries
|
|
4
|
+
|
|
1
5
|
# tagreader-python <!-- omit in toc -->
|
|
2
6
|
|
|
3
7
|

|
|
@@ -46,6 +50,3 @@ For more details, see the [Tagreader Docs](https://equinor.github.io/tagreader-p
|
|
|
46
50
|
|
|
47
51
|
## Documentation
|
|
48
52
|
The full documentation can be found in [Tagreader Docs](https://equinor.github.io/tagreader-python/)
|
|
49
|
-
|
|
50
|
-
## Contribute
|
|
51
|
-
To starting contributing, please see [Tagreader Docs - Contribute](https://equinor.github.io/tagreader-python/docs/contribute/overview)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tagreader"
|
|
3
|
-
version = "6.2.
|
|
3
|
+
version = "6.2.1"
|
|
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 = [
|
|
6
6
|
{ name = "Einar S. Idsø", email = "eiids@equinor.com" },
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Dict, List, Optional, Tuple, Union, cast
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
-
import pytz
|
|
7
6
|
from diskcache import Cache
|
|
8
7
|
|
|
9
8
|
from tagreader.logger import logger
|
|
@@ -21,7 +20,7 @@ def safe_tagname(tagname: str) -> str:
|
|
|
21
20
|
def timestamp_to_epoch(timestamp: datetime) -> int:
|
|
22
21
|
origin = datetime(1970, 1, 1)
|
|
23
22
|
if timestamp.tzinfo is not None:
|
|
24
|
-
timestamp = timestamp.astimezone(
|
|
23
|
+
timestamp = timestamp.astimezone(timezone.utc).replace(tzinfo=None)
|
|
25
24
|
return (timestamp - origin) // timedelta(seconds=1)
|
|
26
25
|
|
|
27
26
|
|
|
@@ -2,11 +2,11 @@ from datetime import datetime, timedelta, timezone, tzinfo
|
|
|
2
2
|
from itertools import groupby
|
|
3
3
|
from operator import itemgetter
|
|
4
4
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
5
|
-
from
|
|
5
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
|
-
import
|
|
9
|
+
import requests
|
|
10
10
|
|
|
11
11
|
from tagreader.cache import BucketCache, SmartCache
|
|
12
12
|
from tagreader.logger import logger
|
|
@@ -21,11 +21,12 @@ from tagreader.web_handlers import (
|
|
|
21
21
|
PIHandlerWeb,
|
|
22
22
|
get_auth_aspen,
|
|
23
23
|
get_auth_pi,
|
|
24
|
+
get_url_aspen,
|
|
24
25
|
list_aspenone_sources,
|
|
25
26
|
list_piwebapi_sources,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
|
-
NONE_START_TIME = datetime(1970, 1, 1, tzinfo=
|
|
29
|
+
NONE_START_TIME = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def list_sources(
|
|
@@ -49,8 +50,6 @@ def list_sources(
|
|
|
49
50
|
auth = get_auth_pi()
|
|
50
51
|
return list_piwebapi_sources(url=url, auth=auth, verify_ssl=verify_ssl)
|
|
51
52
|
elif imstype == IMSType.ASPENONE:
|
|
52
|
-
if auth is None:
|
|
53
|
-
auth = get_auth_aspen()
|
|
54
53
|
return list_aspenone_sources(url=url, auth=auth, verify_ssl=verify_ssl)
|
|
55
54
|
elif imstype in [IMSType.PI, IMSType.ASPEN, IMSType.IP21]:
|
|
56
55
|
raise ValueError(
|
|
@@ -121,24 +120,41 @@ def get_handler(
|
|
|
121
120
|
url: Optional[str],
|
|
122
121
|
options: Dict[str, Union[int, float, str]],
|
|
123
122
|
verify_ssl: Optional[Union[bool, str]],
|
|
124
|
-
auth: Optional[Any],
|
|
123
|
+
auth: Optional[Any] = None,
|
|
125
124
|
cache: Optional[Union[SmartCache, BucketCache]] = None,
|
|
126
125
|
):
|
|
127
126
|
if imstype is None:
|
|
127
|
+
orig_auth = auth
|
|
128
|
+
orig_url = url
|
|
128
129
|
try:
|
|
129
|
-
|
|
130
|
+
aspen_source = list_aspenone_sources(
|
|
130
131
|
url=None, auth=None, verify_ssl=verify_ssl
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
)
|
|
133
|
+
except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError):
|
|
134
|
+
# Try again using app registration auth
|
|
135
|
+
try:
|
|
136
|
+
auth = get_auth_aspen(False)
|
|
137
|
+
url = get_url_aspen(False)
|
|
138
|
+
aspen_source = list_aspenone_sources(
|
|
139
|
+
auth=auth, url=url, verify_ssl=verify_ssl
|
|
140
|
+
)
|
|
141
|
+
except requests.exceptions.HTTPError as e:
|
|
142
|
+
logger.debug(f"Could not list Aspenone sources: {e}")
|
|
143
|
+
aspen_source = []
|
|
144
|
+
|
|
145
|
+
if datasource in aspen_source:
|
|
146
|
+
imstype = IMSType.ASPENONE
|
|
147
|
+
else:
|
|
148
|
+
auth = orig_auth
|
|
149
|
+
url = orig_url
|
|
150
|
+
|
|
135
151
|
if imstype is None:
|
|
136
152
|
try:
|
|
137
153
|
if datasource in list_piwebapi_sources(
|
|
138
154
|
url=None, auth=None, verify_ssl=verify_ssl
|
|
139
155
|
):
|
|
140
156
|
imstype = IMSType.PIWEBAPI
|
|
141
|
-
except HTTPError as e:
|
|
157
|
+
except requests.exceptions.HTTPError as e:
|
|
142
158
|
logger.debug(f"Could not list PI sources: {e}")
|
|
143
159
|
|
|
144
160
|
if imstype == IMSType.PIWEBAPI:
|
|
@@ -175,7 +191,7 @@ class IMSClient:
|
|
|
175
191
|
self,
|
|
176
192
|
datasource: str,
|
|
177
193
|
imstype: Optional[Union[str, IMSType]] = None,
|
|
178
|
-
tz: Union[tzinfo, str] =
|
|
194
|
+
tz: Optional[Union[tzinfo, str]] = None,
|
|
179
195
|
url: Optional[str] = None,
|
|
180
196
|
handler_options: Dict[str, Union[int, float, str]] = {}, # noqa:
|
|
181
197
|
verify_ssl: Optional[Union[bool, str]] = True,
|
|
@@ -191,11 +207,15 @@ class IMSClient:
|
|
|
191
207
|
f" We suggest to use the tagreader.IMSType enumerator when initiating a client."
|
|
192
208
|
)
|
|
193
209
|
|
|
210
|
+
if tz is None or tz == "":
|
|
211
|
+
tz = ZoneInfo("Europe/Oslo")
|
|
194
212
|
if isinstance(tz, str):
|
|
195
|
-
|
|
196
|
-
self.tz =
|
|
197
|
-
|
|
198
|
-
raise ValueError(
|
|
213
|
+
try:
|
|
214
|
+
self.tz = ZoneInfo(tz)
|
|
215
|
+
except ZoneInfoNotFoundError:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Invalid timezone string. Given tz was {tz}"
|
|
218
|
+
) from None
|
|
199
219
|
elif isinstance(tz, tzinfo):
|
|
200
220
|
self.tz = tz
|
|
201
221
|
else:
|
|
@@ -3,14 +3,14 @@ import hashlib
|
|
|
3
3
|
import platform
|
|
4
4
|
import socket
|
|
5
5
|
import ssl
|
|
6
|
-
from datetime import datetime, tzinfo
|
|
6
|
+
from datetime import datetime, timezone, tzinfo
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Union
|
|
9
|
+
from typing import Optional, Union
|
|
10
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
10
11
|
|
|
11
12
|
import certifi
|
|
12
13
|
import pandas as pd
|
|
13
|
-
import pytz
|
|
14
14
|
import requests
|
|
15
15
|
from platformdirs import user_data_dir
|
|
16
16
|
|
|
@@ -50,12 +50,24 @@ def convert_to_pydatetime(date_stamp: Union[datetime, str, pd.Timestamp]) -> dat
|
|
|
50
50
|
|
|
51
51
|
def ensure_datetime_with_tz(
|
|
52
52
|
date_stamp: Union[datetime, str, pd.Timestamp],
|
|
53
|
-
tz: tzinfo =
|
|
53
|
+
tz: Optional[tzinfo] = None,
|
|
54
54
|
) -> datetime:
|
|
55
|
+
if tz is None:
|
|
56
|
+
try:
|
|
57
|
+
tz = ZoneInfo("Europe/Oslo")
|
|
58
|
+
except ZoneInfoNotFoundError:
|
|
59
|
+
logger.warning(
|
|
60
|
+
"Unable to load timezone 'Europe/Oslo'. Falling back to UTC."
|
|
61
|
+
)
|
|
62
|
+
tz = timezone.utc
|
|
55
63
|
date_stamp = convert_to_pydatetime(date_stamp)
|
|
56
64
|
|
|
57
65
|
if not date_stamp.tzinfo:
|
|
58
|
-
|
|
66
|
+
localize = getattr(tz, "localize", None)
|
|
67
|
+
if callable(localize):
|
|
68
|
+
date_stamp = localize(date_stamp)
|
|
69
|
+
else:
|
|
70
|
+
date_stamp = date_stamp.replace(tzinfo=tz)
|
|
59
71
|
|
|
60
72
|
return date_stamp
|
|
61
73
|
|
|
@@ -3,14 +3,13 @@ import json
|
|
|
3
3
|
import re
|
|
4
4
|
import urllib.parse
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
-
from datetime import datetime, timedelta
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
7
|
from hashlib import new as hashlib_new_method
|
|
8
8
|
from json.decoder import JSONDecodeError
|
|
9
9
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pandas as pd
|
|
13
|
-
import pytz
|
|
14
13
|
import requests
|
|
15
14
|
import urllib3
|
|
16
15
|
from Crypto.Hash import MD4 as _MD4
|
|
@@ -115,6 +114,8 @@ def list_aspenone_sources(
|
|
|
115
114
|
except JSONDecodeError as e:
|
|
116
115
|
logger.error(f"Could not decode JSON response: {e}")
|
|
117
116
|
|
|
117
|
+
return []
|
|
118
|
+
|
|
118
119
|
|
|
119
120
|
def list_piwebapi_sources(
|
|
120
121
|
url: Optional[str] = None,
|
|
@@ -143,6 +144,8 @@ def list_piwebapi_sources(
|
|
|
143
144
|
except JSONDecodeError as e:
|
|
144
145
|
logger.error(f"Could not decode JSON response: {e}")
|
|
145
146
|
|
|
147
|
+
return []
|
|
148
|
+
|
|
146
149
|
|
|
147
150
|
def get_piwebapi_source_to_webid_dict(
|
|
148
151
|
url: Optional[str] = None,
|
|
@@ -170,6 +173,8 @@ def get_piwebapi_source_to_webid_dict(
|
|
|
170
173
|
except JSONDecodeError as e:
|
|
171
174
|
logger.error(f"Could not decode JSON response: {e}")
|
|
172
175
|
|
|
176
|
+
return []
|
|
177
|
+
|
|
173
178
|
|
|
174
179
|
class BaseHandlerWeb(ABC):
|
|
175
180
|
def __init__(
|
|
@@ -437,7 +442,7 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
437
442
|
return ret
|
|
438
443
|
|
|
439
444
|
def _get_default_mapname(self, tagname: str):
|
|
440
|
-
|
|
445
|
+
tagname, _ = self.split_tagmap(tagname)
|
|
441
446
|
all_maps = self._get_maps(tagname)
|
|
442
447
|
for k, v in all_maps.items():
|
|
443
448
|
if v:
|
|
@@ -729,7 +734,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
729
734
|
|
|
730
735
|
@staticmethod
|
|
731
736
|
def _time_to_UTC_string(time: datetime) -> str:
|
|
732
|
-
return time.astimezone(
|
|
737
|
+
return time.astimezone(timezone.utc).strftime("%d-%b-%y %H:%M:%S")
|
|
733
738
|
|
|
734
739
|
@staticmethod
|
|
735
740
|
def escape(s: str) -> str:
|
|
@@ -994,7 +999,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
994
999
|
if not web_id:
|
|
995
1000
|
return pd.DataFrame()
|
|
996
1001
|
|
|
997
|
-
|
|
1002
|
+
url, params = self.generate_read_query(
|
|
998
1003
|
tag=web_id,
|
|
999
1004
|
start=start,
|
|
1000
1005
|
end=end,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|