tagreader 6.0.3__tar.gz → 6.0.4__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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tagreader
3
- Version: 6.0.3
3
+ Version: 6.0.4
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: MIT
6
6
  Keywords: Aspen InfoPlus.21,OSIsoft PI
@@ -32,6 +32,7 @@ Requires-Dist: matplotlib (>=3.7.5,<4.0.0) ; extra == "notebooks"
32
32
  Requires-Dist: msal-bearer (>=1.3.0,<2.0.0)
33
33
  Requires-Dist: notebook (>=7.2.2,<8.0.0) ; extra == "notebooks"
34
34
  Requires-Dist: pandas (>=1)
35
+ Requires-Dist: platformdirs (>=4.3.7,<5.0.0)
35
36
  Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
36
37
  Requires-Dist: requests (>=2,<3)
37
38
  Requires-Dist: requests-kerberos (>=0,<1)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tagreader"
3
- version = "6.0.3"
3
+ version = "6.0.4"
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"
@@ -39,6 +39,7 @@ 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"
42
+ platformdirs = "^4.3.7"
42
43
 
43
44
  [tool.poetry.group.dev.dependencies]
44
45
  pre-commit = "^3"
@@ -1,4 +1,3 @@
1
- import os
2
1
  from datetime import datetime, timedelta, timezone, tzinfo
3
2
  from itertools import groupby
4
3
  from operator import itemgetter
@@ -16,9 +15,6 @@ from tagreader.utils import (
16
15
  ReaderType,
17
16
  convert_to_pydatetime,
18
17
  ensure_datetime_with_tz,
19
- find_registry_key,
20
- find_registry_key_from_name,
21
- is_windows,
22
18
  )
23
19
  from tagreader.web_handlers import (
24
20
  AspenHandlerWeb,
@@ -119,83 +115,6 @@ def get_next_timeslice(
119
115
  return start, calc_end
120
116
 
121
117
 
122
- def get_server_address_aspen(datasource: str) -> Optional[Tuple[str, int]]:
123
- """Data sources are listed under
124
- HKEY_CURRENT_USER\\Software\\AspenTech\\ADSA\\Caches\\AspenADSA\\username.
125
- For each data source there are multiple keys with Host entries. We start by
126
- identifying the correct key to use by locating the UUID for Aspen SQLplus
127
- services located under Aspen SQLplus service component. Then we find the
128
- host and port based on the path above and the UUID.
129
- """
130
-
131
- # todo: is obsolete after removing ODBC
132
-
133
- if not is_windows():
134
- return None
135
- import winreg
136
-
137
- regkey_clsid = winreg.OpenKey(
138
- winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Classes\Wow6432Node\CLSID"
139
- )
140
- regkey, _ = find_registry_key_from_name(
141
- regkey_clsid, "Aspen SQLplus service component"
142
- )
143
- regkey_implemented_categories = winreg.OpenKeyEx(regkey, "Implemented Categories")
144
-
145
- _, aspen_uuid = find_registry_key_from_name(
146
- regkey_implemented_categories, "Aspen SQLplus services"
147
- )
148
-
149
- reg_adsa = winreg.OpenKey(
150
- winreg.HKEY_CURRENT_USER,
151
- r"Software\AspenTech\ADSA\Caches\AspenADSA\\" + os.getlogin(),
152
- )
153
-
154
- try:
155
- reg_site_key = winreg.OpenKey(reg_adsa, datasource + "\\" + aspen_uuid)
156
- host = winreg.QueryValueEx(reg_site_key, "Host")[0]
157
- port = int(winreg.QueryValueEx(reg_site_key, "Port")[0])
158
- return host, port
159
- except FileNotFoundError:
160
- return None
161
-
162
-
163
- def get_server_address_pi(datasource: str) -> Optional[Tuple[str, int]]:
164
- """
165
- PI data sources are listed under
166
- HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\PISystem\\PI-SDK\\x.x\\ServerHandles or
167
- \\Software\\PISystem\\PI-SDK\\x.x\\ServerHandles.
168
-
169
- :param datasource: Name of data source
170
- :return: host, port
171
- :type: tuple(string, int)
172
- """
173
- # todo: is obsolete after removing ODBC
174
-
175
- if not is_windows():
176
- return None
177
- import winreg
178
-
179
- try:
180
- reg_key = winreg.OpenKey(
181
- winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\PISystem\PI-SDK"
182
- )
183
- reg_key_handles = find_registry_key(reg_key, "ServerHandles")
184
- reg_site_key = find_registry_key(reg_key_handles, datasource)
185
- if reg_site_key is None:
186
- reg_key = winreg.OpenKey(
187
- winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\PISystem\PI-SDK"
188
- )
189
- reg_key_handles = find_registry_key(reg_key, "ServerHandles")
190
- reg_site_key = find_registry_key(reg_key_handles, datasource)
191
- if reg_site_key is not None:
192
- host = winreg.QueryValueEx(reg_site_key, "path")[0]
193
- port = int(winreg.QueryValueEx(reg_site_key, "port")[0])
194
- return host, port
195
- except FileNotFoundError:
196
- return None
197
-
198
-
199
118
  def get_handler(
200
119
  imstype: Optional[IMSType],
201
120
  datasource: str,
@@ -1,13 +1,18 @@
1
1
  import enum
2
+ import hashlib
2
3
  import logging
3
4
  import platform
4
- import warnings
5
+ import ssl
5
6
  from datetime import datetime, tzinfo
6
7
  from enum import Enum
8
+ from pathlib import Path
7
9
  from typing import Union
8
10
 
11
+ import certifi
9
12
  import pandas as pd
10
13
  import pytz
14
+ import requests
15
+ from platformdirs import user_data_dir
11
16
 
12
17
  from tagreader.logger import logger
13
18
 
@@ -27,47 +32,11 @@ def is_linux() -> bool:
27
32
  if is_windows():
28
33
  import winreg
29
34
 
30
-
31
35
  if is_mac():
32
36
  import socket
33
37
  import subprocess
34
38
 
35
39
 
36
- def find_registry_key(base_key, search_key_name: str):
37
- search_key_name = search_key_name.lower()
38
- if base_key is not None:
39
- num_keys, _, _ = winreg.QueryInfoKey(base_key)
40
- for i in range(0, num_keys):
41
- key_name = winreg.EnumKey(base_key, i)
42
- if key_name.lower() == search_key_name:
43
- return winreg.OpenKey(base_key, key_name)
44
- else:
45
- key = find_registry_key(
46
- winreg.OpenKey(base_key, key_name), search_key_name
47
- )
48
- if key is not None:
49
- return key
50
- return None
51
-
52
-
53
- def find_registry_key_from_name(base_key, search_key_name: str):
54
- search_key_name = search_key_name.lower()
55
- num_keys, _, _ = winreg.QueryInfoKey(base_key)
56
- key = key_string = None
57
- for i in range(0, num_keys):
58
- try:
59
- key_string = winreg.EnumKey(base_key, i)
60
- key = winreg.OpenKey(base_key, key_string)
61
- _, num_vals, _ = winreg.QueryInfoKey(key)
62
- if num_vals > 0:
63
- (_, key_name, _) = winreg.EnumValue(key, 0)
64
- if str(key_name).lower() == search_key_name:
65
- break
66
- except Exception as err:
67
- logging.error("{}: {}".format(i, err))
68
- return key, key_string
69
-
70
-
71
40
  def convert_to_pydatetime(date_stamp: Union[datetime, str, pd.Timestamp]) -> datetime:
72
41
  if isinstance(date_stamp, datetime):
73
42
  return date_stamp
@@ -138,64 +107,82 @@ def add_equinor_root_certificate() -> bool:
138
107
  running on Equinor host.
139
108
 
140
109
  NB! This needs to be repeated after updating the cacert module.
141
- NB! Will not download certificates.
142
110
 
143
111
  Returns:
144
112
  bool: True if function completes successfully
145
113
  """
146
- import hashlib
147
- import ssl
114
+ certificate = find_local_equinor_root_certificate()
148
115
 
149
- import certifi
116
+ # If certificate is not found locally, we download it from the Equinor server
117
+ if certificate == "":
118
+ logger.debug(
119
+ "Unable to locate Equinor Root CA certificate on this host. Downloading from Equinor server."
120
+ )
121
+ response = requests.get("http://pki.equinor.com/aia/ecpr.crt")
150
122
 
151
- EQUINOR_root_PEM_HASH = "5A206332CE73CED1D44C8A99C4C43B7CEE03DF5F"
152
- ca_search = "Equinor Root CA"
123
+ if response.status_code != 200:
124
+ logger.error(
125
+ "Unable to find Equinor Root CA certificate locally and on Equinor server."
126
+ )
127
+ return False
153
128
 
154
- found = False
155
- der = None
129
+ certificate = response.text.replace("\r", "")
156
130
 
157
- if is_linux():
131
+ # Write result to user data so we can read the cert from there next time
132
+ filepath = Path(user_data_dir("tagreader")) / "equinor_root_ca.crt"
133
+ try:
134
+ filepath.parent.mkdir(parents=True, exist_ok=True)
135
+ filepath.write_text(certificate)
136
+ logger.debug("Equinor Root CA certificate written to cache")
137
+ except Exception as e:
138
+ logger.debug(f"Failed to write Equinor Root CA certificate to cache: {e}")
139
+
140
+ if certificate in certifi.contents():
141
+ logger.debug("Equinor Root Certificate already exists in certifi store")
158
142
  return True
159
- elif is_windows():
160
- logger.debug("Scanning CA certificate in Windows cert store", end="")
143
+
144
+ ca_file = certifi.where()
145
+ with open(ca_file, "ab") as f:
146
+ f.write(bytes(certificate, "ascii"))
147
+ logger.debug("Equinor Root Certificate added to certifi store")
148
+
149
+
150
+ def find_local_equinor_root_certificate() -> str:
151
+ equinor_root_pem_hash = "5A206332CE73CED1D44C8A99C4C43B7CEE03DF5F"
152
+ ca_search = "Equinor Root CA"
153
+
154
+ if is_windows():
155
+ logger.debug("Checking for Equinor Root CA in Windows certificate store")
161
156
  for cert in ssl.enum_certificates("CA"):
162
- der = cert[0]
157
+ found_cert = cert[0]
163
158
  # deepcode ignore InsecureHash: <Only hashes to compare with known hash>
164
- if hashlib.sha1(der).hexdigest().upper() == EQUINOR_root_PEM_HASH:
165
- found = True
166
- logger.debug("CA certificate found!")
167
- break
159
+ if hashlib.sha1(found_cert).hexdigest().upper() == equinor_root_pem_hash:
160
+ return ssl.DER_cert_to_PEM_cert(found_cert)
161
+
168
162
  elif is_mac():
163
+ logger.debug("Checking for Equinor Root CA in MacOS certificate store")
169
164
  macos_ca_certs = subprocess.run(
170
165
  ["security", "find-certificate", "-a", "-c", ca_search, "-Z"],
171
166
  stdout=subprocess.PIPE,
172
167
  ).stdout
173
168
 
174
- if EQUINOR_root_PEM_HASH in str(macos_ca_certs).upper():
169
+ if equinor_root_pem_hash in str(macos_ca_certs).upper():
175
170
  c = get_macos_equinor_certificates()
176
171
  for cert in c:
177
172
  # deepcode ignore InsecureHash: <Only hashes to compare with known hash>
178
- if hashlib.sha1(cert).hexdigest().upper() == EQUINOR_root_PEM_HASH:
179
- der = cert
180
- found = True
181
- break
182
-
183
- if found and der:
184
- pem = ssl.DER_cert_to_PEM_cert(der)
185
- if pem in certifi.contents():
186
- logger.debug(
187
- "CA Certificate already exists in certifi store. Nothing to do."
188
- )
189
- else:
190
- logger.debug("Writing certificate to certifi store.")
191
- ca_file = certifi.where()
192
- with open(ca_file, "ab") as f:
193
- f.write(bytes(pem, "ascii"))
194
- logger.debug("Completed")
195
- else:
196
- warnings.warn("Unable to locate root certificate on this host.")
173
+ if hashlib.sha1(cert).hexdigest().upper() == equinor_root_pem_hash:
174
+ return ssl.DER_cert_to_PEM_cert(cert)
175
+
176
+ # If the certificate is not found in the local cert store, look in the tagreader cache
177
+ filepath = Path(user_data_dir("tagreader")) / "equinor_root_ca.crt"
178
+
179
+ try:
180
+ if filepath.exists():
181
+ return filepath.read_text()
182
+ except Exception as e:
183
+ logger.debug(f"Failed to read Equinor Root CA certificate from cache: {e}")
197
184
 
198
- return found
185
+ return ""
199
186
 
200
187
 
201
188
  def get_macos_equinor_certificates():
@@ -19,7 +19,7 @@ from urllib3.exceptions import InsecureRequestWarning
19
19
 
20
20
  from tagreader.cache import BucketCache, SmartCache
21
21
  from tagreader.logger import logger
22
- from tagreader.utils import IMSType, ReaderType, is_mac, is_windows, urljoin
22
+ from tagreader.utils import ReaderType, is_mac, is_windows, urljoin
23
23
 
24
24
 
25
25
  class MD4:
@@ -258,11 +258,6 @@ class AspenHandlerWeb(BaseHandlerWeb):
258
258
  self._max_rows = options.get("max_rows", 100000)
259
259
  self._connection_string = "" # Used for raw SQL queries
260
260
 
261
- @staticmethod
262
- def generate_connection_string(host, *_, **__):
263
- # todo: is obsolete after removing ODBC
264
- raise NotImplementedError
265
-
266
261
  @staticmethod
267
262
  def generate_search_query(
268
263
  tag: Optional[str],
@@ -670,27 +665,6 @@ class AspenHandlerWeb(BaseHandlerWeb):
670
665
  connection_string += f"<![CDATA[{query}]]></SQL>"
671
666
  return connection_string
672
667
 
673
- def initialize_connection_string(
674
- # todo: is obsolete after removing ODBC
675
- self,
676
- host: Optional[str] = None,
677
- port: int = 10014,
678
- connection_string: Optional[str] = None,
679
- ):
680
- # todo: is obsolete after removing ODBC
681
- if connection_string:
682
- self._connection_string = connection_string
683
- else:
684
- if host is None:
685
- from tagreader.clients import get_server_address_aspen
686
-
687
- host = get_server_address_aspen(self.datasource)
688
- self._connection_string = (
689
- f"DRIVER=AspenTech SQLPlus;HOST={host};"
690
- f"PORT={port};CHARINT=N;CHARFLOAT=N;"
691
- "CHARTIME=N;CONVERTERRORS=N"
692
- )
693
-
694
668
  def query_sql(self, query: str, parse: bool = True) -> Union[str, pd.DataFrame]:
695
669
  url = urljoin(self.base_url, "SQL")
696
670
  if self._connection_string is None:
@@ -758,11 +732,6 @@ class PIHandlerWeb(BaseHandlerWeb):
758
732
  def _time_to_UTC_string(time: datetime) -> str:
759
733
  return time.astimezone(pytz.UTC).strftime("%d-%b-%y %H:%M:%S")
760
734
 
761
- @staticmethod
762
- def generate_connection_string(host, *_, **__):
763
- # todo: is obsolete after removing ODBC
764
- raise NotImplementedError
765
-
766
735
  @staticmethod
767
736
  def escape(s: str) -> str:
768
737
  # https://techsupport.osisoft.com/Documentation/PI-Web-API/help/topics/search-queries.html
File without changes
File without changes
File without changes
File without changes