tagreader 6.1.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.
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: tagreader
3
- Version: 6.1.0
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
- License: MIT
5
+ License-Expression: MIT
6
+ License-File: LICENSE
6
7
  Keywords: Aspen InfoPlus.21,OSIsoft PI
7
8
  Author: Einar S. Idsø
8
9
  Author-email: eiids@equinor.com
@@ -36,6 +37,10 @@ Project-URL: Homepage, https://github.com/equinor/tagreader-python
36
37
  Project-URL: Repository, https://github.com/equinor/tagreader-python
37
38
  Description-Content-Type: text/markdown
38
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
+
39
44
  # tagreader-python <!-- omit in toc -->
40
45
 
41
46
  ![GitHub Build Status](https://github.com/equinor/tagreader-python/workflows/Test/badge.svg)
@@ -85,6 +90,3 @@ For more details, see the [Tagreader Docs](https://equinor.github.io/tagreader-p
85
90
  ## Documentation
86
91
  The full documentation can be found in [Tagreader Docs](https://equinor.github.io/tagreader-python/)
87
92
 
88
- ## Contribute
89
- To starting contributing, please see [Tagreader Docs - Contribute](https://equinor.github.io/tagreader-python/docs/contribute/overview)
90
-
@@ -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
  ![GitHub Build Status](https://github.com/equinor/tagreader-python/workflows/Test/badge.svg)
@@ -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.1.0"
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(pytz.utc).replace(tzinfo=None)
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 urllib.error import HTTPError
5
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
9
- import pytz
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=pytz.UTC)
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
- if datasource in list_aspenone_sources(
130
+ aspen_source = list_aspenone_sources(
130
131
  url=None, auth=None, verify_ssl=verify_ssl
131
- ):
132
- imstype = IMSType.ASPENONE
133
- except HTTPError as e:
134
- logger.debug(f"Could not list Aspenone sources: {e}")
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] = pytz.timezone("Europe/Oslo"),
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
- if tz in pytz.all_timezones:
196
- self.tz = pytz.timezone(tz)
197
- else:
198
- raise ValueError(f"Invalid timezone string Given type was {type(tz)}")
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 = pytz.timezone("Europe/Oslo"),
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
- date_stamp = tz.localize(date_stamp)
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
 
@@ -117,7 +129,7 @@ def add_equinor_root_certificate() -> bool:
117
129
  logger.debug(
118
130
  "Unable to locate Equinor Root CA certificate on this host. Downloading from Equinor server."
119
131
  )
120
- response = requests.get("http://pki.equinor.com/aia/ecpr.crt")
132
+ response = requests.get("http://pki.equinor.com/aia/ecpr.crt", timeout=20)
121
133
 
122
134
  if response.status_code != 200:
123
135
  logger.error(
@@ -230,7 +242,8 @@ def is_equinor() -> bool:
230
242
  if hostname.lower().startswith("eqdev") or hostname.lower().startswith("eqpc"):
231
243
  return True
232
244
  with winreg.OpenKey( # type: ignore
233
- winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\ControlSet001\Services\Tcpip\Parameters" # type: ignore
245
+ winreg.HKEY_LOCAL_MACHINE,
246
+ r"SYSTEM\ControlSet001\Services\Tcpip\Parameters", # type: ignore
234
247
  ) as key:
235
248
  domain = winreg.QueryValueEx(key, "Domain") # type: ignore
236
249
  if "statoil" in domain[0] or "equinor" in domain[0]:
@@ -282,7 +295,7 @@ def is_equinor() -> bool:
282
295
  return True
283
296
 
284
297
  s = subprocess.run(
285
- ["security", "find-certificate", "-a", "-c" "client.statoil.net"],
298
+ ["security", "find-certificate", "-a", "-cclient.statoil.net"],
286
299
  stdout=subprocess.PIPE,
287
300
  ).stdout
288
301
 
@@ -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
@@ -107,7 +106,7 @@ def list_aspenone_sources(
107
106
  url_ = urljoin(url, "DataSources")
108
107
  params = {"service": "ProcessData", "allQuotes": 1}
109
108
 
110
- res = requests.get(url_, params=params, auth=auth, verify=verify_ssl)
109
+ res = requests.get(url_, params=params, auth=auth, verify=verify_ssl, timeout=300)
111
110
  res.raise_for_status()
112
111
  try:
113
112
  source_list = [r["n"] for r in res.json()["data"] if r["t"] == "IP21"]
@@ -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,
@@ -134,7 +135,7 @@ def list_piwebapi_sources(
134
135
  urllib3.disable_warnings(InsecureRequestWarning)
135
136
 
136
137
  url_ = urljoin(url, "dataservers")
137
- res = requests.get(url_, auth=auth, verify=verify_ssl)
138
+ res = requests.get(url_, auth=auth, verify=verify_ssl, timeout=300)
138
139
 
139
140
  res.raise_for_status()
140
141
  try:
@@ -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,
@@ -162,7 +165,7 @@ def get_piwebapi_source_to_webid_dict(
162
165
  urllib3.disable_warnings(InsecureRequestWarning)
163
166
 
164
167
  url_ = urljoin(url, "dataservers")
165
- res = requests.get(url_, auth=auth, verify=verify_ssl)
168
+ res = requests.get(url_, auth=auth, verify=verify_ssl, timeout=300)
166
169
 
167
170
  res.raise_for_status()
168
171
  try:
@@ -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__(
@@ -316,12 +321,12 @@ class AspenHandlerWeb(BaseHandlerWeb):
316
321
  else:
317
322
  query = '<Q f="d" allQuotes="1">'
318
323
 
319
- query += "<Tag>" f"<N><![CDATA[{tagname}]]></N>"
324
+ query += f"<Tag><N><![CDATA[{tagname}]]></N>"
320
325
 
321
326
  if mapname:
322
327
  query += f"<M><![CDATA[{mapname}]]></M>"
323
328
 
324
- query += f"<D><![CDATA[{self.datasource}]]></D>" "<F><![CDATA[VAL]]></F>"
329
+ query += f"<D><![CDATA[{self.datasource}]]></D><F><![CDATA[VAL]]></F>"
325
330
 
326
331
  if read_type == ReaderType.SNAPSHOT:
327
332
  query += "<VS>1</VS>"
@@ -437,7 +442,7 @@ class AspenHandlerWeb(BaseHandlerWeb):
437
442
  return ret
438
443
 
439
444
  def _get_default_mapname(self, tagname: str):
440
- (tagname, _) = self.split_tagmap(tagname)
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(pytz.UTC).strftime("%d-%b-%y %H:%M:%S")
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:
@@ -843,10 +848,10 @@ class PIHandlerWeb(BaseHandlerWeb):
843
848
  if self._is_summary(read_type):
844
849
  params["selectedFields"] = "Links;Items.Value.Timestamp;Items.Value.Value"
845
850
  if get_status:
846
- params["selectedFields"] += (
847
- ";Items.Value.Good"
848
- ";Items.Value.Questionable"
849
- ";Items.Value.Substituted"
851
+ params[
852
+ "selectedFields"
853
+ ] += (
854
+ ";Items.Value.Good;Items.Value.Questionable;Items.Value.Substituted"
850
855
  )
851
856
  elif read_type in [ReaderType.INT, ReaderType.RAW, ReaderType.SHAPEPRESERVING]:
852
857
  params["selectedFields"] = "Links;Items.Timestamp;Items.Value"
@@ -994,7 +999,7 @@ class PIHandlerWeb(BaseHandlerWeb):
994
999
  if not web_id:
995
1000
  return pd.DataFrame()
996
1001
 
997
- (url, params) = self.generate_read_query(
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