Skip to content

Commit

Permalink
Merge pull request #752 from Hugovdberg/develop
Browse files Browse the repository at this point in the history
chore: release v0.11.0
  • Loading branch information
Hugovdberg authored Jun 21, 2024
2 parents cf41317 + 634379d commit f708432
Show file tree
Hide file tree
Showing 42 changed files with 1,678 additions and 1,752 deletions.
31 changes: 14 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ on:
- cron: "27 19 * * 3"

jobs:
build:
run_tests:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: [
'3.8'
, '3.9'
'3.9'
, '3.10'
# , '3.11'
, '3.11'
, '3.12'
]

steps:
Expand All @@ -36,22 +36,16 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest wheel
python -m pip install pytest wheel
python -m pip install pytest-cov
python -m pip install --exists-action=w --no-cache-dir -r requirements_dev.txt
python -m pip install .
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
check_format:
lint_and_check_format:
runs-on: windows-latest

steps:
Expand All @@ -65,12 +59,15 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install black
python -m pip install ruff
- name: Lint with ruff
run: |
ruff check .
- name: Check formatting with black
- name: Check formatting with ruff
run: |
# stop the build if there are Python syntax errors or undefined names
black --check --diff PIconnect
ruff format --check
# codacy-security-scan:
# name: Codacy Security Scan
Expand Down
63 changes: 0 additions & 63 deletions .pyup.yml

This file was deleted.

12 changes: 8 additions & 4 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
version: 2

build:
image: latest
os: ubuntu-lts-latest
tools:
python: "3.12"

formats: all

sphinx:
builder: html
configuration: docs/conf.py
fail_on_warning: true

python:
version: "3.8"
install:
- requirements: requirements_dev.txt
- method: setuptools
path: .
35 changes: 20 additions & 15 deletions PIconnect/AFSDK.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
""" AFSDK
Loads the .NET libraries from the OSIsoft AF SDK
"""
"""AFSDK - Loads the .NET libraries from the OSIsoft AF SDK."""

import logging
import os
import sys
import typing

import pythonnet

__all__ = ["AF", "AF_SDK_VERSION"]
__all__ = ["AF", "System", "AF_SDK_VERSION"]

logger = logging.getLogger(__name__)

pythonnet.load() # Required for the import of System in other modules
# pragma pylint: disable=import-outside-toplevel


def __fallback():
import warnings

warnings.warn("Can't import the PI AF SDK, running in test mode", ImportWarning)
warnings.warn(
"Can't import the PI AF SDK, running in test mode",
ImportWarning,
stacklevel=2,
)

from ._typing import AF, AF_SDK_VERSION
from ._typing import AF as _af
from ._typing import AF_SDK_VERSION as _AF_SDK_version
from ._typing import dotnet as _System

return AF, AF_SDK_VERSION
return _af, _System, _AF_SDK_version


if (
os.getenv("GITHUB_ACTIONS", "false").lower() == "true"
or os.getenv("TF_BUILD", "false").lower() == "true"
or os.getenv("READTHEDOCS", "false").lower() == "true"
):
_af, _AF_SDK_version = __fallback()
_af, _System, _AF_SDK_version = __fallback()
else:
import clr
import clr # type: ignore

# Get the installation directory from the environment variable or fall back
# to the Windows default installation path
Expand All @@ -57,16 +60,18 @@ def __fallback():

clr.AddReference("OSIsoft.AFSDK") # type: ignore ; pylint: disable=no-member

from OSIsoft import AF as _af # type: ignore ; pylint: wrong-import-position
import System as _System # type: ignore
from OSIsoft import AF as _af # type: ignore

_AF_SDK_version: str = _af.PISystems().Version # type: ignore ; pylint: disable=no-member
_AF_SDK_version = typing.cast(str, _af.PISystems().Version) # type: ignore ; pylint: disable=no-member
print("OSIsoft(r) AF SDK Version: {}".format(_AF_SDK_version))


if typing.TYPE_CHECKING:
# This branch is separate from previous one as otherwise no typechecking takes place
# on the main logic.
_af, _AF_SDK_version = __fallback()
_af, _System, _AF_SDK_version = __fallback()

AF = _af
System = _System
AF_SDK_VERSION = _AF_SDK_version
64 changes: 29 additions & 35 deletions PIconnect/PI.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
""" PI
Core containers for connections to PI databases
"""
"""PI - Core containers for connections to PI databases."""

import warnings
from typing import Any, Dict, List, Optional, Union, cast

import PIconnect._typing.Generic as _dotNetGeneric
import PIconnect.PIPoint as PIPoint_
from PIconnect import AF, PIConsts
from PIconnect._utils import InitialisationWarning
from PIconnect.AFSDK import System

__all__ = ["PIServer", "PIPoint"]

PIPoint = PIPoint_.PIPoint
_DEFAULT_AUTH_MODE = PIConsts.AuthenticationMode.PI_USER_AUTHENTICATION


def _lookup_servers() -> Dict[str, AF.PI.PIServer]:
servers: Dict[str, AF.PI.PIServer] = {}
from System import Exception as dotNetException # type: ignore

for server in AF.PI.PIServers():
try:
servers[server.Name] = server
except (Exception, dotNetException) as e: # type: ignore
except (Exception, System.Exception) as e: # type: ignore
warnings.warn(
f"Failed loading server data for {server.Name} "
f"with error {type(cast(Exception, e)).__qualname__}",
InitialisationWarning,
stacklevel=2,
)
return servers

Expand All @@ -35,14 +35,15 @@ def _lookup_default_server() -> Optional[AF.PI.PIServer]:
try:
default_server = AF.PI.PIServers().DefaultPIServer
except Exception:
warnings.warn("Could not load the default PI Server", ResourceWarning)
warnings.warn("Could not load the default PI Server", ResourceWarning, stacklevel=2)
return default_server


class PIServer(object): # pylint: disable=useless-object-inheritance
"""PIServer is a connection to an OSIsoft PI Server
"""PIServer is a connection to an OSIsoft PI Server.
Args:
Parameters
----------
server (str, optional): Name of the server to connect to, defaults to None
username (str, optional): can be used only with password as well
password (str, optional): -//-
Expand All @@ -68,22 +69,22 @@ def __init__(
username: Optional[str] = None,
password: Optional[str] = None,
domain: Optional[str] = None,
authentication_mode: PIConsts.AuthenticationMode = PIConsts.AuthenticationMode.PI_USER_AUTHENTICATION,
authentication_mode: PIConsts.AuthenticationMode = _DEFAULT_AUTH_MODE,
timeout: Optional[int] = None,
) -> None:
if server is None:
if self.default_server is None:
raise ValueError(
"No server was specified and no default server was found."
)
raise ValueError("No server was specified and no default server was found.")
self.connection = self.default_server
elif server not in self.servers:
if self.default_server is None:
raise ValueError(
f"Server '{server}' not found and no default server was found."
)
message = 'Server "{server}" not found, using the default server.'
warnings.warn(message=message.format(server=server), category=UserWarning)
warnings.warn(
message=message.format(server=server), category=UserWarning, stacklevel=1
)
self.connection = self.default_server
else:
self.connection = self.servers[server]
Expand All @@ -97,30 +98,24 @@ def __init__(
"A domain can only specified together with a username and password."
)
if username:
from System.Net import NetworkCredential # type: ignore
from System.Security import SecureString # type: ignore

secure_pass = cast(_dotNetGeneric.SecureString, SecureString())
secure_pass = System.Security.SecureString()
if password is not None:
for c in password:
secure_pass.AppendChar(c)
cred = [username, secure_pass] + ([domain] if domain else [])
cred = (username, secure_pass) + ((domain,) if domain else ())
self._credentials = (
cast(_dotNetGeneric.NetworkCredential, NetworkCredential(*cred)),
System.Net.NetworkCredential(cred[0], cred[1], *cred[2:]),
AF.PI.PIAuthenticationMode(int(authentication_mode)),
)
else:
self._credentials = None

if timeout:
from System import TimeSpan # type: ignore

# System.TimeSpan(hours, minutes, seconds)
self.connection.ConnectionInfo.OperationTimeOut = cast(
_dotNetGeneric.TimeSpan, TimeSpan(0, 0, timeout)
)
self.connection.ConnectionInfo.OperationTimeOut = System.TimeSpan(0, 0, timeout)

def __enter__(self):
"""Open connection context with the PI Server."""
if self._credentials:
self.connection.Connect(*self._credentials)
else:
Expand All @@ -130,31 +125,30 @@ def __enter__(self):
return self

def __exit__(self, *args: Any):
"""Close connection context with the PI Server."""
self.connection.Disconnect()

def __repr__(self) -> str:
return "%s(\\\\%s)" % (self.__class__.__name__, self.server_name)
"""Representation of the PIServer object."""
return f"{self.__class__.__qualname__}(\\\\{self.server_name})"

@property
def server_name(self):
"""server_name
Name of the connected server
"""
"""Name of the connected server."""
return self.connection.Name

def search(
self, query: Union[str, List[str]], source: Optional[str] = None
) -> List[PIPoint_.PIPoint]:
"""search
Search PIPoints on the PIServer
"""Search PIPoints on the PIServer.
Args:
Parameters
----------
query (str or [str]): String or list of strings with queries
source (str, optional): Defaults to None. Point source to limit the results
Returns:
Returns
-------
list: A list of :class:`PIPoint` objects as a result of the query
.. todo::
Expand Down
Loading

0 comments on commit f708432

Please sign in to comment.