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
10 changes: 5 additions & 5 deletions .github/workflows/release-commhandler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ jobs:
fi
echo "Semver: $SEMVER"
echo "Tag: $TAG"
echo "::set-output name=original_tag::$TAG"
echo "::set-output name=semver::$SEMVER"
echo "original_tag=${TAG}" >> $GITHUB_OUTPUT
echo "semver=${SEMVER}" >> $GITHUB_OUTPUT

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.x'
python-version: '3.9'

- name: Install Dependencies
run: |
Expand All @@ -60,7 +60,7 @@ jobs:
python --version

- name: Checkout SGrPython Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ steps.set_tag.outputs.original_tag }}

Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release-specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ jobs:
fi
echo "Semver: $SEMVER"
echo "Tag: $TAG"
echo "::set-output name=original_tag::$TAG"
echo "::set-output name=semver::$SEMVER"
echo "original_tag=${TAG}" >> $GITHUB_OUTPUT
echo "semver=${SEMVER}" >> $GITHUB_OUTPUT

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.x'
python-version: '3.9'

- name: Install Dependencies
run: |
Expand All @@ -60,14 +60,14 @@ jobs:
pip show xsdata

- name: Checkout SGrSpecifications Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: 'SmartGridready/SGrSpecifications'
ref: ${{ steps.set_tag.outputs.original_tag }}
path: SGrSpecifications

- name: Checkout SGrPython Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: SGrPython

Expand Down
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"python.analysis.typeCheckingMode": "off"
"python.analysis.typeCheckingMode": "off",
"python.analysis.extraPaths": [
"./commhandler/src"
]
}
11 changes: 11 additions & 0 deletions commhandler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.1] - 2025-10-20

### Fixed

- Ensured code compatibility with Python 3.9, as defined in project

### Changed

- removed `Protocol` as base class of CommHandler APIs, refactored class inheritance


## [0.4.0] - 2025-10-01

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions commhandler/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

SGr CommHandler documentation
SGr CommHandler Documentation
=============================

Add your content using ``reStructuredText`` syntax. See the
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
documentation for details.
This is the API documentation of the `sgr-commhandler <https://pypi.org/project/sgr-commhandler/>`_ Python package,
providing the *SmartGridready communications handler library*.

See `SGrPython <https://github.com/SmartGridready/SGrPython>`_ Github repository for details.


.. toctree::
:maxdepth: 2
:caption: Contents:

2 changes: 1 addition & 1 deletion commhandler/requirements-doc.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
sphinx>=8.2.0,<9.0.0
sphinx>=7.4.0,<8.0.0
sphinx-autoapi>=3.6.0,<4.0.0
sphinx-rtd-theme>=3.0.0,<4.0.0
6 changes: 6 additions & 0 deletions commhandler/src/sgr_commhandler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
Provides the CommHandler library.

Users can utilize the device_builder and declaration_library modules to
create devices and access the declaration library.
"""
6 changes: 5 additions & 1 deletion commhandler/src/sgr_commhandler/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Contains the CommHandler API, which is intended to be utilized by the user.
"""

__all__ = [
"SGrBaseInterface",
"FunctionalProfile",
Expand Down Expand Up @@ -26,4 +30,4 @@
)
from sgr_commhandler.api.functional_profile_api import (
FunctionalProfile
)
)
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Provides device configuration parameters.
"""

from typing import Optional

from sgr_specification.v0.product import (
Expand All @@ -14,7 +18,7 @@ def build_configuration_parameters(params: Optional[ConfigurationList]) -> list[
----------
params : Optional[ConfigurationList]
The configuration list of an EID

Returns
-------
list[ConfigurationParameter]
Expand All @@ -40,7 +44,7 @@ def __init__(self, parameter: ConfigurationListElement):
----------
parameter : ConfigurationListElement
A configuration list element of the SGr specification
"""
"""
translation = parameter.configuration_description
self.label = translation[0].label
self.name = parameter.name
Expand Down
86 changes: 67 additions & 19 deletions commhandler/src/sgr_commhandler/api/data_point_api.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
"""
Provides the data-point-level API.
"""

from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import Any, Generic, NoReturn, Optional, Protocol, TypeVar
from typing import Any, Generic, NoReturn, Optional, TypeVar

from sgr_specification.v0.generic import DataDirectionProduct, Units, DataPointBase
from sgr_specification.v0.generic import DataDirectionProduct, Units, DataPointBase, FunctionalProfileBase

from sgr_commhandler.api.dynamic_parameter import DynamicParameter
from sgr_commhandler.api.dynamic_parameter import DynamicParameter, build_dynamic_parameters
from sgr_commhandler.api.data_types import DataTypes

"""Defines a generic data type."""

TFpSpec = TypeVar('TFpSpec', covariant=True, bound=FunctionalProfileBase)
"""Defines a generic functional profile data type."""

TDpSpec = TypeVar('TDpSpec', covariant=True, bound=DataPointBase)
"""Defines a generic data point data type."""


class DataPointValidator(Protocol):
class DataPointValidator(object):
"""
Implements a base class for data point validators.
"""
Expand Down Expand Up @@ -59,21 +68,49 @@ def options(self) -> list[Any]:
return []


class DataPointProtocol(Protocol[TDpSpec]):
class DataPointProtocol(ABC, Generic[TFpSpec, TDpSpec]):
"""
Implements a base class for data point protocols.
"""

_fp_spec: TFpSpec
_dp_spec: TDpSpec
_fp_name: str
_dp_name: str
_dynamic_parameters: list[DynamicParameter]

def __init__(self, fp_spec: TFpSpec, dp_spec: TDpSpec):
self._fp_spec = fp_spec
self._dp_spec = dp_spec

self._fp_name = fp_spec.functional_profile.functional_profile_name if (
fp_spec.functional_profile is not None
and fp_spec.functional_profile.functional_profile_name is not None
) else ''

self._dp_name = dp_spec.data_point.data_point_name if (
dp_spec.data_point is not None
and dp_spec.data_point.data_point_name is not None
) else ''

self._dynamic_parameters = build_dynamic_parameters(
self._dp_spec.data_point.parameter_list
if self._dp_spec.data_point
else None
)

def get_specification(self) -> TDpSpec:
"""
Gets the data point specification.

Returns
-------
TDpSpec
the interface-specific specification
"""
...
return self._dp_spec

@abstractmethod
async def set_val(self, value: Any):
"""
Writes the data point value.
Expand All @@ -85,6 +122,7 @@ async def set_val(self, value: Any):
"""
...

@abstractmethod
async def get_val(self, parameters: Optional[dict[str, str]] = None, skip_cache: bool = False) -> Any:
"""
Reads the data point value.
Expand All @@ -95,7 +133,7 @@ async def get_val(self, parameters: Optional[dict[str, str]] = None, skip_cache:
optional dynamic parameters of the request
skip_cache : bool
does not use cache if true

Returns
-------
Any
Expand All @@ -112,7 +150,7 @@ def name(self) -> tuple[str, str]:
tuple[str, str]
the functional profile and data point names as tuple
"""
...
return (self._fp_name, self._dp_name)

def direction(self) -> DataDirectionProduct:
"""
Expand All @@ -123,7 +161,12 @@ def direction(self) -> DataDirectionProduct:
DataDirectionProduct
the data point direction
"""
...
if (
self._dp_spec.data_point is None
or self._dp_spec.data_point.data_direction is None
):
raise Exception('missing data direction')
return self._dp_spec.data_point.data_direction

def unit(self) -> Units:
"""
Expand All @@ -134,7 +177,12 @@ def unit(self) -> Units:
Units
the unit
"""
...
if (
self._dp_spec.data_point is None
or self._dp_spec.data_point.unit is None
):
return Units.NONE
return self._dp_spec.data_point.unit

def dynamic_parameters(self) -> list[DynamicParameter]:
"""
Expand All @@ -145,7 +193,7 @@ def dynamic_parameters(self) -> list[DynamicParameter]:
list[DynamicParameter]
the dynamic parameters
"""
return []
return self._dynamic_parameters

def can_subscribe(self) -> bool:
"""
Expand Down Expand Up @@ -176,20 +224,20 @@ def unsubscribe(self):
raise Exception('unsubscribe() is not supported')


class DataPoint(Generic[TDpSpec]):
class DataPoint(Generic[TFpSpec, TDpSpec]):
"""
Implements a data point of a generic data type.
"""

def __init__(
self, protocol: DataPointProtocol[TDpSpec], validator: DataPointValidator
self, protocol: DataPointProtocol[TFpSpec, TDpSpec], validator: DataPointValidator
):
"""
Constructs a data point.

Parameters
----------
protocol : DataPointProtocol[TDpSpec]
protocol : DataPointProtocol[TFpSpec, TDpSpec]
the underlying protocol
validator : DataPointValidator
the data point's value validator
Expand All @@ -208,15 +256,15 @@ def name(self) -> tuple[str, str]:
"""
return self._protocol.name()

async def get_value_async(self, parameters: Optional[dict[str, str]] = None, skip_cache = False) -> Any:
async def get_value_async(self, parameters: Optional[dict[str, str]] = None, skip_cache: bool = False) -> Any:
"""
Gets the data point value asynchronously.

Returns
-------
Any
the data point value

Raises
------
Exception
Expand Down Expand Up @@ -280,7 +328,7 @@ def data_type(self) -> DataTypes:
the data point data type
"""
return self._validator.data_type()

def unit(self) -> Units:
"""
Gets the unit of measurement of the data point.
Expand Down
4 changes: 4 additions & 0 deletions commhandler/src/sgr_commhandler/api/data_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Provides SGr data types.
"""

from enum import Enum


Expand Down
Loading