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.
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: tagreader
3
- Version: 6.0.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.7.4,<2025.0.0)
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.0,<4.0.0) ; extra == "notebooks"
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.0,<8.0.0) ; extra == "notebooks"
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.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.7.4"
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.0", optional = true }
38
- matplotlib = { version = "^3.7.0", optional = true }
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
- if self.cache is not None:
446
- r = self.cache.get_metadata(key=tag, properties="unit")
447
- if r is not None and "unit" in r:
448
- units[tag] = r["unit"]
449
- if tag not in units:
450
- unit = self.handler._get_tag_unit(tag)
451
- if self.cache is not None and unit is not None:
452
- self.cache.put_metadata(key=tag, value={"unit": unit})
453
- units[tag] = unit
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
- if self.cache is not None:
462
- r = self.cache.get_metadata(key=tag, properties="description")
463
- if r is not None and "description" in r:
464
- descriptions[tag] = r["description"]
465
- if tag not in descriptions:
466
- desc = self.handler._get_tag_description(tag)
467
- if self.cache is not None and desc is not None:
468
- self.cache.put_metadata(key=tag, value={"description": desc})
469
- descriptions[tag] = desc
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
- return hashlib_new_method(name, data)
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
- description = self._get_tag_description(tagname)
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
- attr_data = data["data"]["tags"][0]["attrData"]
523
- except KeyError as e:
524
- logger.error(f"Error. I got this: {data}")
525
- raise e
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
- desc = ""
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
- res_text = self.fetch_text(url, params=params)
702
- # For now just return result as text regardless of value of parse
707
+
708
+ res = self.session.get(url, params=params)
709
+ res.raise_for_status()
710
+
703
711
  if parse:
704
- raise NotImplementedError(
705
- "Use parse=False to receive and handle text result instead"
706
- )
707
- return res_text
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