Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate black to ruff #749

Merged
merged 12 commits into from
Jun 21, 2024
30 changes: 13 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,23 +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 @@ -66,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
15 changes: 9 additions & 6 deletions PIconnect/AFSDK.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
""" 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
Expand All @@ -16,7 +15,11 @@
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 as _af
from ._typing import AF_SDK_VERSION as _AF_SDK_version
Expand All @@ -32,7 +35,7 @@ def __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 Down Expand Up @@ -60,7 +63,7 @@ def __fallback():
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))


Expand Down
48 changes: 24 additions & 24 deletions PIconnect/PI.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
""" 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

Expand All @@ -12,6 +11,7 @@
__all__ = ["PIServer", "PIPoint"]

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


def _lookup_servers() -> Dict[str, AF.PI.PIServer]:
Expand All @@ -25,6 +25,7 @@ def _lookup_servers() -> Dict[str, AF.PI.PIServer]:
f"Failed loading server data for {server.Name} "
f"with error {type(cast(Exception, e)).__qualname__}",
InitialisationWarning,
stacklevel=2,
)
return servers

Expand All @@ -34,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 @@ -67,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 Down Expand Up @@ -110,11 +112,10 @@ def __init__(

if timeout:
# System.TimeSpan(hours, minutes, seconds)
self.connection.ConnectionInfo.OperationTimeOut = System.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 @@ -124,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
57 changes: 30 additions & 27 deletions PIconnect/PIAF.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
""" PIAF
Core containers for connections to the PI Asset Framework.
"""
"""PIAF - Core containers for connections to the PI Asset Framework."""

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

from PIconnect import AF, PIAFBase, PIConsts, _time
from PIconnect.AFSDK import System
from PIconnect import AF, PIAFAttribute, PIAFBase, PIConsts, _time
from PIconnect._utils import InitialisationWarning
from PIconnect import PIAFAttribute
from PIconnect.AFSDK import System

_DEFAULT_EVENTFRAME_SEARCH_MODE = PIConsts.EventFrameSearchMode.STARTING_AFTER


@dataclasses.dataclass(frozen=True)
class PIAFServer:
"""Reference to a PI AF server and its databases."""

server: AF.PISystem
databases: Dict[str, AF.AFDatabase] = dataclasses.field(default_factory=dict)

def __getitem__(self, attr: str) -> Union[AF.PISystem, Dict[str, AF.AFDatabase]]:
"""Allow access to attributes as if they were dictionary items."""
return getattr(self, attr)


Expand All @@ -36,17 +39,19 @@ def _lookup_servers() -> Dict[str, ServerSpec]:
f"Failed loading database data for {d.Name} on {s.Name} "
f"with error {type(cast(Exception, e)).__qualname__}",
InitialisationWarning,
stacklevel=2,
)
except (Exception, System.Exception) as e: # type: ignore
warnings.warn(
f"Failed loading server data for {s.Name} "
f"with error {type(cast(Exception, e)).__qualname__}",
InitialisationWarning,
stacklevel=2,
)
return {
server_name: {
"server": server.server,
"databases": {db_name: db for db_name, db in server.databases.items()},
"databases": dict(server.databases.items()),
}
for server_name, server in servers.items()
}
Expand All @@ -63,19 +68,14 @@ def _lookup_default_server() -> Optional[ServerSpec]:


class PIAFDatabase(object):
"""PIAFDatabase

Context manager for connections to the PI Asset Framework database.
"""
"""Context manager for connections to the PI Asset Framework database."""

version = "0.2.0"

servers: Dict[str, ServerSpec] = _lookup_servers()
default_server: Optional[ServerSpec] = _lookup_default_server()

def __init__(
self, server: Optional[str] = None, database: Optional[str] = None
) -> None:
def __init__(self, server: Optional[str] = None, database: Optional[str] = None) -> None:
server_spec = self._initialise_server(server)
self.server: AF.PISystem = server_spec["server"] # type: ignore
self.database: AF.AFDatabase = self._initialise_database(server_spec, database)
Expand All @@ -88,11 +88,11 @@ def _initialise_server(self, server: Optional[str]) -> ServerSpec:

if server not in self.servers:
if self.default_server is None:
raise ValueError(
f'Server "{server}" not found and no default server found.'
)
raise ValueError(f'Server "{server}" not found and no default server 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=2
)
return self.default_server

return self.servers[server]
Expand All @@ -108,28 +108,27 @@ def _initialise_database(
if database not in databases:
message = 'Database "{database}" not found, using the default database.'
warnings.warn(
message=message.format(database=database), category=UserWarning
message=message.format(database=database), category=UserWarning, stacklevel=2
)
return default_db

return databases[database]

def __enter__(self) -> "PIAFDatabase":
"""Open the PI AF server connection context."""
self.server.Connect()
return self

def __exit__(self, *args: Any) -> None:
"""Close the PI AF server connection context."""
pass
# Disabled disconnecting because garbage collection sometimes impedes
# connecting to another server later
# self.server.Disconnect()

def __repr__(self) -> str:
return "%s(\\\\%s\\%s)" % (
self.__class__.__name__,
self.server_name,
self.database_name,
)
"""Return a representation of the PI AF database connection."""
return f"{self.__class__.__qualname__}(\\\\{self.server_name}\\{self.database_name})"

@property
def server_name(self) -> str:
Expand All @@ -151,7 +150,9 @@ def descendant(self, path: str) -> "PIAFElement":
return PIAFElement(self.database.Elements.get_Item(path))

def search(self, query: Union[str, List[str]]) -> List[PIAFAttribute.PIAFAttribute]:
"""return a list of PIAFAttributes directly from a list of element|attribute path strings
"""Search PIAFAttributes by element|attribute path strings.

Return a list of PIAFAttributes directly from a list of element|attribute path strings

like this:

Expand All @@ -177,9 +178,10 @@ def event_frames(
start_time: _time.TimeLike = "",
start_index: int = 0,
max_count: int = 1000,
search_mode: PIConsts.EventFrameSearchMode = PIConsts.EventFrameSearchMode.STARTING_AFTER,
search_mode: PIConsts.EventFrameSearchMode = _DEFAULT_EVENTFRAME_SEARCH_MODE,
search_full_hierarchy: bool = False,
) -> Dict[str, "PIAFEventFrame"]:
"""Search for event frames in the database."""
_start_time = _time.to_af_time(start_time)
_search_mode = AF.EventFrame.AFEventFrameSearchMode(int(search_mode))
return {
Expand Down Expand Up @@ -229,6 +231,7 @@ class PIAFEventFrame(PIAFBase.PIAFBaseElement[AF.EventFrame.AFEventFrame]):

@property
def event_frame(self) -> AF.EventFrame.AFEventFrame:
"""Return the underlying AF Event Frame object."""
return self.element

@property
Expand Down
Loading
Loading