SIENTIAPDE-1646

SIENTIAPDE-1646 Update dependencies and refactor OPC integration

- Replaced `opcua` with `asyncua` in `requirements-local.txt` and `requirements.txt` to utilize the async capabilities.
- Updated `values.yaml` to change the GitHub branch from `feature/SIENTIAPDE-1646` to `release/SIENTIAPDE-1646`.
- Refactored `opc.py` and `opc_repository.py` to accommodate the new `asyncua` library, ensuring compatibility with the synchronous API.
- Adjusted tests in `test_opc_repository.py` to reflect changes in the client implementation and maintain functionality.
This commit is contained in:
vitor-aignosi
2026-05-11 12:29:39 -03:00
parent 8d34228d7b
commit e22b0bc7c3
6 changed files with 26 additions and 21 deletions

View File

@@ -143,7 +143,7 @@ class OPC(SientiaMonitoring):
level=NotificationLevel.ERROR, level=NotificationLevel.ERROR,
attachment_content=trace, attachment_content=trace,
) )
raise e raise
def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool: def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool:
""" """

View File

@@ -1,8 +1,9 @@
""" """
Synchronous OPC UA client repository using python-opcua (opcua package). Synchronous OPC UA client repository using asyncua (opcua-asyncio) ``sync`` API.
Connects to OPC UA servers, optionally configures Basic256 security, validates sessions, ``asyncua.sync.Client`` runs the asyncio client on a background thread so callers
and writes node values with typed variants and Prometheus-compatible metrics. stay synchronous. Connect/disconnect, optional Basic256 security, session checks,
and typed writes mirror the previous python-opcua integration.
""" """
import json import json
@@ -12,8 +13,9 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from opcua import Client, ua from asyncua import ua
from opcua.crypto import security_policies from asyncua.crypto import security_policies
from asyncua.sync import Client
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
from sientia_do.notifications.models import NotificationLevel from sientia_do.notifications.models import NotificationLevel
from sientia_do.observability.logger import Logger from sientia_do.observability.logger import Logger
@@ -54,7 +56,7 @@ class OpcRepository(SientiaMonitoring):
url: OPC UA endpoint URL. url: OPC UA endpoint URL.
id: Server identifier used in metrics and notifications. id: Server identifier used in metrics and notifications.
server_name: Human-readable server name for labels. server_name: Human-readable server name for labels.
client: Active opcua.Client instance while connected. client: Active ``asyncua.sync.Client`` while connected.
""" """
def __init__( def __init__(
@@ -115,16 +117,17 @@ class OpcRepository(SientiaMonitoring):
if self.client is None: if self.client is None:
raise ValueError('Client must be initialized before setting security') raise ValueError('Client must be initialized before setting security')
self.client.application_uri = self.server_uri self.client.application_uri = self.server_uri or self.client.application_uri
self.logger.custom_info('Setting security...', self.metadata) self.logger.custom_info('Setting security...', self.metadata)
self.client.set_security( self.client.set_security(
security_policies.SecurityPolicyBasic256, security_policies.SecurityPolicyBasic256,
str(cert), str(cert),
str(private_key), str(private_key),
None,
str(server_cert) if server_cert else None, str(server_cert) if server_cert else None,
) )
self.client.secure_channel_timeout = 10000000 self.client.aio_obj.secure_channel_timeout = 10000000
self.client.session_timeout = 10000000 self.client.aio_obj.session_timeout = 10000000
def connect(self) -> tuple[bool, dict[str, Any]]: def connect(self) -> tuple[bool, dict[str, Any]]:
""" """
@@ -136,11 +139,11 @@ class OpcRepository(SientiaMonitoring):
self.client = Client(self.url, timeout=10) self.client = Client(self.url, timeout=10)
self.client.name = self.pod_id self.client.aio_obj.name = self.pod_id
self.client.application_name = self.pod_id self.client.aio_obj.description = self.pod_id
pod_uri = self.pod_id.replace('-', ':') pod_uri = self.pod_id.replace('-', ':')
self.client.application_uri = pod_uri self.client.application_uri = pod_uri
self.client.product_uri = pod_uri self.client.aio_obj.product_uri = pod_uri
if self.cert_path: if self.cert_path:
self.set_security() self.set_security()
@@ -336,7 +339,7 @@ class OpcRepository(SientiaMonitoring):
Write a typed value to an OPC UA node after validating connectivity. Write a typed value to an OPC UA node after validating connectivity.
Args: Args:
node: Node id string accepted by opcua Client.get_node. node: Node id string accepted by ``Client.get_node``.
value: Scalar value to encode. value: Scalar value to encode.
data_type: Key into ``data_type_map`` (e.g. float, str). data_type: Key into ``data_type_map`` (e.g. float, str).
logger: Caller logger for per-write traces. logger: Caller logger for per-write traces.

View File

@@ -1,7 +1,7 @@
temporalio temporalio
psycopg2-binary psycopg2-binary
sqlalchemy sqlalchemy
opcua asyncua==1.0.6
redis redis
#git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0 #git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0
/home/grezewave/Documents/projects/sientia/sientia-dataops-library /home/grezewave/Documents/projects/sientia/sientia-dataops-library

View File

@@ -1,7 +1,7 @@
temporalio temporalio
psycopg2-binary psycopg2-binary
sqlalchemy sqlalchemy
opcua asyncua==1.0.6
redis redis
sientia_do==1.12.0 sientia_do==1.12.0
sientia_model==0.8.2 sientia_model==0.8.2

View File

@@ -3,7 +3,7 @@ from datetime import datetime
from unittest.mock import ANY, MagicMock, Mock, patch from unittest.mock import ANY, MagicMock, Mock, patch
import pytest import pytest
from opcua.crypto import security_policies from asyncua.crypto import security_policies
from sientia_do.notifications.models import NotificationLevel from sientia_do.notifications.models import NotificationLevel
from laborious.utils.repository.opc_repository import OpcRepository from laborious.utils.repository.opc_repository import OpcRepository
@@ -39,6 +39,7 @@ def opc_repository(mock_logger):
def mock_client(): def mock_client():
with patch('laborious.utils.repository.opc_repository.Client') as mock: with patch('laborious.utils.repository.opc_repository.Client') as mock:
client_instance = MagicMock() client_instance = MagicMock()
client_instance.aio_obj = MagicMock()
mock.return_value = client_instance mock.return_value = client_instance
yield client_instance yield client_instance
@@ -75,10 +76,11 @@ def test_set_security(opc_repository, mock_client):
security_policies.SecurityPolicyBasic256, security_policies.SecurityPolicyBasic256,
'/path/to/cert.pem', '/path/to/cert.pem',
'/path/to/key.pem', '/path/to/key.pem',
None,
'/path/to/server_cert.pem', '/path/to/server_cert.pem',
) )
assert mock_client.secure_channel_timeout == 10000000 assert mock_client.aio_obj.secure_channel_timeout == 10000000
assert mock_client.session_timeout == 10000000 assert mock_client.aio_obj.session_timeout == 10000000
def test_set_security_missing_certificates(opc_repository): def test_set_security_missing_certificates(opc_repository):

View File

@@ -66,7 +66,7 @@ global:
- name: GITHUB_REPO_URL - name: GITHUB_REPO_URL
value: "git@github.com:Aignosi/sientia-dataops-laborious_temporal.git" value: "git@github.com:Aignosi/sientia-dataops-laborious_temporal.git"
- name: GITHUB_BRANCH - name: GITHUB_BRANCH
value: "feature/SIENTIAPDE-1646" value: "release/SIENTIAPDE-1646"
- name: PYTHON_APP - name: PYTHON_APP
value: "laborious.worker.worker" value: "laborious.worker.worker"
- name: PYPI_SERVER - name: PYPI_SERVER
@@ -231,7 +231,7 @@ global:
# - The runtime name is used by the worker bootstrap to resolve plugins and task queues. # - The runtime name is used by the worker bootstrap to resolve plugins and task queues.
# - Keep runtime names in sync with the plugin-store runtime names. # - Keep runtime names in sync with the plugin-store runtime names.
runtimes: runtimes:
- name: "single" - name: "basic"
replicas: 1 replicas: 1
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------