Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11"]
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -19,7 +19,7 @@ jobs:
- name: Install required Linux library for pykerberos
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get install libkrb5-dev
sudo apt-get update && sudo apt-get install libkrb5-dev

- name: Install Poetry
run: |
Expand Down
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,17 @@
[![Downloads](https://pepy.tech/badge/tagreader)](https://pepy.tech/project/tagreader)

Tagreader is a Python package for reading timeseries data from the OSIsoft PI and Aspen Infoplus.21
Information Manufacturing Systems (IMS) systems. It is intended to be easy to use, and present as similar interfaces
as possible to the backend historians.
Information Management Systems (IMS). It is intended to be easy to use, and present as similar interfaces
as possible to the backend plant historians.

## Installation
You can install tagreader directly into your project from pypi by using pip
or another package manager. The only requirement is Python version 3.8 or above.
or another package manager. Supports Python version 3.9.2 and above.

```shell
pip install tagreader
```

The following are required and will be installed:

* pandas
* requests
* requests-kerberos
* certifi
* diskcache

## Usage
Tagreader is easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
you simply need to provide the corresponding IMS service URLs and IMSType.
Expand Down
2,116 changes: 1,045 additions & 1,071 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,18 @@ classifiers=[
]

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9.2"
pandas = ">=1"
certifi = "^2024.12.14"
requests = "^2"
requests-kerberos = "^0"
msal-bearer = ">=0.2.1,<1.2.0"
msal-bearer = "^1.3.0"
notebook = { version = "^7.2.2", optional = true }
matplotlib = { version = "^3.7.5", optional = true }
diskcache = "^5.6.1"
pycryptodome = "^3.20.0"
requests-ntlm = ">=1.1,<=2.0"


[tool.poetry.group.dev.dependencies]
pre-commit = "^3"
pytest = ">=7,<9"
Expand Down
7 changes: 5 additions & 2 deletions tagreader/clients.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from datetime import datetime, timedelta, tzinfo
from datetime import datetime, timedelta, timezone, tzinfo
from itertools import groupby
from operator import itemgetter
from typing import Any, Dict, List, Optional, Tuple, Union
Expand Down Expand Up @@ -128,6 +128,8 @@ def get_server_address_aspen(datasource: str) -> Optional[Tuple[str, int]]:
host and port based on the path above and the UUID.
"""

# todo: is obsolete after removing ODBC

if not is_windows():
return None
import winreg
Expand Down Expand Up @@ -168,6 +170,7 @@ def get_server_address_pi(datasource: str) -> Optional[Tuple[str, int]]:
:return: host, port
:type: tuple(string, int)
"""
# todo: is obsolete after removing ODBC

if not is_windows():
return None
Expand Down Expand Up @@ -557,7 +560,7 @@ def read(
except ValueError:
start = convert_to_pydatetime(start)
if end is None:
end = datetime.utcnow()
end = datetime.now(timezone.utc)
elif isinstance(end, (str, pd.Timestamp)):
end = convert_to_pydatetime(end)

Expand Down
23 changes: 14 additions & 9 deletions tagreader/web_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def get_auth_aspen(use_internal: bool = True):
if use_internal:
return HTTPKerberosAuth(mutual_authentication=OPTIONAL)

from msal_bearer.BearerAuth import BearerAuth
from msal_bearer import BearerAuth

tenantID = "3aa4a235-b6e2-48d5-9195-7fcf05b459b0"
clientID = "7adaaa99-897f-428c-8a5f-4053db565b32"
Expand Down Expand Up @@ -260,6 +260,7 @@ def __init__(

@staticmethod
def generate_connection_string(host, *_, **__):
# todo: is obsolete after removing ODBC
raise NotImplementedError

@staticmethod
Expand Down Expand Up @@ -670,11 +671,13 @@ def generate_sql_query(
return connection_string

def initialize_connection_string(
# todo: is obsolete after removing ODBC
self,
host: Optional[str] = None,
port: int = 10014,
connection_string: Optional[str] = None,
):
# todo: is obsolete after removing ODBC
if connection_string:
self._connection_string = connection_string
else:
Expand Down Expand Up @@ -712,16 +715,17 @@ def query_sql(self, query: str, parse: bool = True) -> Union[str, pd.DataFrame]:
parsed_dict = res.json()["data"][0]

cols = []
for i in parsed_dict["cols"]:
cols.append(i["n"])
if "cols" in parsed_dict.keys():
for i in parsed_dict["cols"]:
cols.append(i["n"])

rows = []

for i in parsed_dict["rows"]:
element = []
for j in i["fld"]:
element.append(j["v"])
rows.append(element)
if "rows" in parsed_dict.keys():
for i in parsed_dict["rows"]:
element = []
for j in i["fld"]:
element.append(j["v"])
rows.append(element)
return pd.DataFrame(data=rows, columns=cols)
return res.text

Expand Down Expand Up @@ -756,6 +760,7 @@ def _time_to_UTC_string(time: datetime) -> str:

@staticmethod
def generate_connection_string(host, *_, **__):
# todo: is obsolete after removing ODBC
raise NotImplementedError

@staticmethod
Expand Down
1 change: 1 addition & 0 deletions tests/test_AspenHandlerREST.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ def test_generate_sql_query(aspen_handler: AspenHandlerWeb) -> None:


def test_initialize_connection_string(aspen_handler: AspenHandlerWeb) -> None:
# todo: is obsolete after removing ODBC
aspen_handler.initialize_connection_string(
host="my_host", port=999, connection_string="my_connection_string"
)
Expand Down
27 changes: 13 additions & 14 deletions tests/test_AspenHandlerREST_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from datetime import datetime, timedelta
from typing import Generator

import pandas as pd
import pytest
from pytest import raises

from tagreader.clients import IMSClient, list_sources
from tagreader.utils import IMSType
Expand Down Expand Up @@ -137,18 +137,17 @@ def test_query_sql(client: IMSClient) -> None:
# The % causes WC_E_SYNTAX error in result. Tried "everything" but no go.
# Leaving it for now.
# query = "SELECT name, ip_description FROM ip_analogdef WHERE name LIKE 'ATC%'"
query = "Select name, ip_description from ip_analogdef where name = 'atcai'"
query = "Select name, ip_description from ip_analogdef where name = 'atc'"
res = client.query_sql(query=query, parse=False)
print(res)
# print(res)
assert isinstance(res, str)
with raises(NotImplementedError):
res = client.query_sql(query=query, parse=True)
assert isinstance(res, str)
client.handler.initialize_connection_string()
query = "Select name, ip_description from ip_analogdef where name = 'atcai'"
res = client.query_sql(query=query, parse=False)
print(res)
assert isinstance(res, str)
with raises(NotImplementedError):
res = client.query_sql(query=query, parse=True)
assert isinstance(res, str)

res = client.query_sql(query=query, parse=True)
assert isinstance(res, pd.DataFrame)
assert res.empty

query = "Select name, ip_description from ip_analogdef where name = 'AverageCPUTimeVals'"
res = client.query_sql(query=query, parse=True)
assert isinstance(res, pd.DataFrame)
assert len(res.index.values) == 1
assert res.index.values[0] == 0
Loading