Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/fastcs/attributes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .attr_rw import AttrRW as AttrRW
from .attr_w import AttrW as AttrW
from .attribute import Attribute as Attribute
from .attribute_info import AttributeInfo as AttributeInfo
from .attribute_io import AnyAttributeIO as AnyAttributeIO
from .attribute_io import AttributeIO as AttributeIO
from .attribute_io_ref import AttributeIORef as AttributeIORef
Expand Down
10 changes: 9 additions & 1 deletion src/fastcs/attributes/attribute.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Callable
from typing import Generic

from fastcs.attributes.attribute_io_ref import AttributeIORefT
from fastcs.attributes import AttributeInfo, AttributeIORefT
from fastcs.datatypes import DataType, DType, DType_T
from fastcs.logging import bind_logger
from fastcs.tracer import Tracer
Expand Down Expand Up @@ -100,6 +100,14 @@ def set_path(self, path: list[str]):

self._path = path

def add_info(self, info: AttributeInfo):
"""Apply info fields"""
if info.description:
self.description = info.description

if info.group:
self._group = info.group

def __repr__(self):
name = self.__class__.__name__
path = ".".join(self._path + [self._name]) or None
Expand Down
9 changes: 9 additions & 0 deletions src/fastcs/attributes/attribute_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from dataclasses import dataclass


@dataclass(kw_only=True)
class AttributeInfo:
"""Fields to apply to hinted attributes during introspection"""

description: str | None = None
group: str | None = None
2 changes: 2 additions & 0 deletions src/fastcs/attributes/hinted_attribute.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass

from fastcs.attributes.attribute import Attribute
from fastcs.attributes.attribute_info import AttributeInfo
from fastcs.datatypes import DType


Expand All @@ -16,3 +17,4 @@ class HintedAttribute:
"""The type of the `Attribute` in the type hint - e.g. `AttrR`"""
dtype: type[DType] | None
"""The dtype of the `Attribute` in the type hint, if any - e.g. `int`"""
info: AttributeInfo | None
42 changes: 34 additions & 8 deletions src/fastcs/controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
from collections import Counter
from collections.abc import Sequence
from copy import deepcopy
from typing import _GenericAlias, get_args, get_origin, get_type_hints # type: ignore
from typing import (
Annotated,
_GenericAlias, # type: ignore
get_args,
get_origin,
get_type_hints,
)

from fastcs.attributes import (
Attribute,
AttributeInfo,
AttributeIO,
AttributeIORefT,
AttrR,
Expand Down Expand Up @@ -67,7 +74,13 @@ def __init__(

def _find_type_hints(self):
"""Find `Attribute` and `Controller` type hints for introspection validation"""
for name, hint in get_type_hints(type(self)).items():
for name, hint in get_type_hints(type(self), include_extras=True).items():
# Annotated[AttrR[int], AttributeInfo(...)]
metadata = None
if isinstance(origin := get_origin(hint), type) and origin is Annotated:
args = get_args(hint)
hint, metadata = args[0], args[1:]

if isinstance(hint, _GenericAlias): # e.g. AttrR[int]
args = get_args(hint)
hint = get_origin(hint)
Expand All @@ -78,16 +91,25 @@ def _find_type_hints(self):
if args is None:
dtype = None
else:
if len(args) == 2:
dtype = args[0]
else:
if len(args) != 2:
raise TypeError(
f"Invalid type hint for attribute {name}: {hint}"
)

self.__hinted_attributes[name] = HintedAttribute(
attr_type=hint, dtype=dtype
)
dtype, _io_ref = args
if metadata is not None:
if not isinstance(metadata[0], AttributeInfo):
raise TypeError(
f"Invalid annotation for attribute {name}: {hint}"
)
else:
info = metadata[0]
else:
info = None

self.__hinted_attributes[name] = HintedAttribute(
attr_type=origin, dtype=dtype, info=info
)

elif isinstance(hint, type) and issubclass(hint, BaseController):
self.__hinted_sub_controllers[name] = hint
Expand Down Expand Up @@ -259,6 +281,10 @@ def add_attribute(self, name, attr: Attribute):
f"Expected '{hint.dtype.__name__}', "
f"got '{attr.datatype.dtype.__name__}'."
)

if hint.info is not None:
attr.add_info(hint.info)

elif name in self.__sub_controllers.keys():
raise ValueError(
f"Cannot add attribute {attr}. "
Expand Down