Skip to content

Commit

Permalink
Fix pydantic Field type hints
Browse files Browse the repository at this point in the history
Use Annotated[..., Field] instead of setting default value
  • Loading branch information
GDYendell committed Nov 21, 2024
1 parent 55ca992 commit be2cfe3
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 69 deletions.
20 changes: 11 additions & 9 deletions src/pvi/_convert/_asyn_convert.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from typing import Any, ClassVar, cast
from typing import Annotated, Any, ClassVar, cast

from pydantic import Field

Expand Down Expand Up @@ -78,14 +78,16 @@ class AsynParameter(Named):
"""Base class for all Asyn Parameters to inherit from"""

type_strings: ClassVar[TypeStrings]
read_record: AsynRecord | None = Field(
default=None,
description="A read AsynRecord, if not given then use $(name)_RBV as read PV",
)
write_record: AsynRecord | None = Field(
default=None,
description="A write AsynRecord, if not given then use $(name) as write PV",
)
read_record: Annotated[
AsynRecord | None,
Field(description="A read AsynRecord ($(name)_RBV if not given)"),
] = None
write_record: Annotated[
AsynRecord | None,
Field(
description="A write AsynRecord, if not given then use $(name) as write PV",
),
] = None
read_widget: ReadWidgetUnion = TextRead()
write_widget: WriteWidgetUnion = TextWrite()

Expand Down
7 changes: 4 additions & 3 deletions src/pvi/_convert/_parameters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
from enum import Enum
from functools import cached_property
from typing import Annotated

from pydantic import BaseModel, Field

Expand All @@ -10,9 +11,9 @@
class TypeStrings(BaseModel):
"""The type strings for record dtypes and parameter names"""

asyn_read: str = Field(description="e.g. asynInt32, asynOctetRead")
asyn_write: str = Field(description="e.g. asynInt32, asynOctetWrite")
asyn_param: str = Field(description="e.g. asynParamInt32, asynParamOctet")
asyn_read: Annotated[str, Field(description="e.g. asynInt32, asynOctetRead")]
asyn_write: Annotated[str, Field(description="e.g. asynInt32, asynOctetWrite")]
asyn_param: Annotated[str, Field(description="e.g. asynParamInt32, asynParamOctet")]


class Access(Enum):
Expand Down
15 changes: 9 additions & 6 deletions src/pvi/_format/aps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
from typing import Annotated

from pydantic import Field

Expand All @@ -24,12 +25,14 @@


class APSFormatter(Formatter):
spacing: int = Field(5, description="Spacing between widgets")
title_height: int = Field(25, description="Height of screen title bar")
max_height: int = Field(900, description="Max height of the screen")
label_width: int = Field(205, description="Width of the widget description labels")
widget_width: int = Field(100, description="Width of the widgets")
widget_height: int = Field(20, description="Height of the widgets")
spacing: Annotated[int, Field(description="Spacing between widgets")] = 5
title_height: Annotated[int, Field(description="Height of screen title bar")] = 25
max_height: Annotated[int, Field(description="Max height of the screen")] = 900
label_width: Annotated[
int, Field(description="Width of the widget description labels")
] = 205
widget_width: Annotated[int, Field(description="Width of the widgets")] = 100
widget_height: Annotated[int, Field(description="Height of the widgets")] = 20

def format(self, device: Device, path: Path) -> None:
assert path.suffix == ".adl", "Can only write adl files"
Expand Down
13 changes: 7 additions & 6 deletions src/pvi/_format/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, Union
from typing import Annotated, Any, Union

from pydantic import Field, TypeAdapter

Expand All @@ -11,11 +11,12 @@
class IndexEntry(TypedModel):
"""A structure defining an index button to launch a UI with some macros."""

label: str = Field(
"Button label. This will be converted to PascalCase if it is not already."
)
ui: str = Field("File name of UI to open with button")
macros: dict[str, str] = Field("Macros to launch UI with")
label: Annotated[
str,
Field(description="Button label (this will be converted to PascalCase)"),
]
ui: Annotated[str, Field(description="File name of UI to open with button")]
macros: Annotated[dict[str, str], Field(description="Macros to launch UI with")]


class Formatter(TypedModel, YamlValidatorMixin):
Expand Down
106 changes: 61 additions & 45 deletions src/pvi/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,13 @@ class LED(ReadWidget):
class BitField(ReadWidget):
"""LED and label for each bit of an int PV"""

labels: Sequence[str] | None = Field(default=None, description="Label for each bit")
number_of_bits: int = Field(
default=8, description="Number of bits to display", gt=0
)
labels: Annotated[
Sequence[str] | None,
Field(description="Label for each bit"),
] = None
number_of_bits: Annotated[
int, Field(description="Number of bits to display", gt=0)
] = 8


class ProgressBar(ReadWidget):
Expand All @@ -119,8 +122,8 @@ class TextRead(ReadWidget):

model_config = ConfigDict(use_enum_values=True) # Use Enum value when dumping

lines: int | None = Field(default=None, description="Number of lines to display")
format: TextFormat | None = Field(default=None, description="Display format")
lines: Annotated[int | None, Field(description="Number of lines to display")] = None
format: Annotated[TextFormat | None, Field(description="Display format")] = None

def get_lines(self):
return self.lines or 1
Expand All @@ -129,13 +132,16 @@ def get_lines(self):
class ArrayTrace(ReadWidget):
"""Trace of the array in a plot view"""

axis: str = Field(
description=(
"Traces with same axis name will appear on same axis, "
"plotted against 'x' trace if it exists, or indexes if not. "
"Only one traces with axis='x' allowed."
axis: Annotated[
str,
Field(
description=(
"Traces with same axis name will appear on same axis, "
"plotted against 'x' trace if it exists, or indexes if not. "
"Only one traces with axis='x' allowed."
),
),
)
]


class ImageRead(ReadWidget):
Expand Down Expand Up @@ -164,7 +170,7 @@ class ComboBox(WriteWidget):
"""Selection of an enum PV"""

choices: Annotated[
Sequence[str] | None, Field(default=None, description="Choices to select from")
Sequence[str] | None, Field(description="Choices to select from")
] = None

def get_choices(self) -> list[str]:
Expand All @@ -179,18 +185,19 @@ class ButtonPanel(WriteWidget):
"""

actions: dict[PascalStr, str] = Field(
default={"Go": "1"}, description="PV poker buttons"
)
actions: Annotated[
dict[PascalStr, str],
Field(description="PV poker buttons"),
] = {"Go": "1"}


class TextWrite(WriteWidget):
"""Text control of any PV"""

model_config = ConfigDict(use_enum_values=True) # Use Enum value when dumping

lines: int | None = Field(default=None, description="Number of lines to display")
format: TextFormat | None = Field(default=None, description="Display format")
lines: Annotated[int | None, Field(description="Number of lines to display")] = None
format: Annotated[TextFormat | None, Field(description="Display format")] = None

def get_lines(self):
return self.lines or 1
Expand All @@ -208,25 +215,31 @@ def get_lines(self):
class ArrayWrite(WriteWidget):
"""Control of an array PV"""

widget: _RowWriteUnion = Field(
description="What widget should be used for each item"
)
widget: Annotated[
_RowWriteUnion, Field(description="What widget should be used for each item")
]


class TableRead(ReadWidget):
"""A read-only tabular view of an NTTable."""

widgets: Sequence[_RowReadUnion] = Field(
[], description="For each column, what widget should be repeated for every row"
)
widgets: Annotated[
Sequence[_RowReadUnion],
Field(
description="For each column, what widget should be repeated for every row",
),
] = []


class TableWrite(WriteWidget):
"""A writeable tabular view of an NTTable."""

widgets: Sequence[_RowWriteUnion] = Field(
[], description="For each column, what widget should be repeated for every row"
)
widgets: Annotated[
Sequence[_RowWriteUnion],
Field(
description="For each column, what widget should be repeated for every row",
),
] = []


class Layout(TypedModel):
Expand All @@ -240,10 +253,9 @@ class Plot(Layout):
class Row(Layout):
"""Children are columns in the row"""

header: Sequence[str] | None = Field(
None,
description="Labels for the items in the row",
)
header: Annotated[
Sequence[str] | None, Field(description="Labels for the items in the row")
] = None


class Grid(Layout):
Expand All @@ -257,7 +269,7 @@ class Grid(Layout):
class SubScreen(Layout):
"""Children are displayed on another screen opened with a button."""

labelled: bool = Field(default=True, description="Display labels for components")
labelled: Annotated[bool, Field(description="Display labels for components")] = True


LayoutUnion = Plot | Row | Grid | SubScreen
Expand Down Expand Up @@ -286,7 +298,9 @@ class SubScreen(Layout):


class Named(TypedModel):
name: PascalStr = Field(description="PascalCase name to uniquely identify")
name: Annotated[
PascalStr, Field(description="PascalCase name to uniquely identify")
]


class Component(Named):
Expand All @@ -313,7 +327,7 @@ class SignalR(Signal):

_access_mode = "r"

read_pv: str = Field(description="PV to use for readback")
read_pv: Annotated[str, Field(description="PV to use for readback")]
read_widget: Annotated[
ReadWidgetUnion | None,
Field(
Expand All @@ -328,7 +342,7 @@ class SignalW(Signal):

_access_mode = "w"

write_pv: str = Field(description="PV to use for demand")
write_pv: Annotated[str, Field(description="PV to use for demand")]
write_widget: Annotated[
WriteWidgetUnion,
Field(description="Widget to use for control"),
Expand Down Expand Up @@ -403,11 +417,11 @@ def check_write_widget(self) -> Self:
class DeviceRef(Component):
"""Reference to another Device."""

pv: str = Field(description="Child device PVI PV")
ui: str = Field(description="UI file to open for referenced Device")
macros: dict[str, str] = Field(
default={}, description="Macro-value pairs for UI file"
)
pv: Annotated[str, Field(description="Child device PVI PV")]
ui: Annotated[str, Field(description="UI file to open for referenced Device")]
macros: Annotated[
dict[str, str], Field(description="Macro-value pairs for UI file")
] = {}


class SignalRef(Component):
Expand All @@ -417,8 +431,10 @@ class SignalRef(Component):
class Group(Component):
"""Group of child components in a Layout"""

layout: LayoutUnion = Field(description="How to layout children on screen")
children: Tree = Field(description="Child Components")
layout: Annotated[
LayoutUnion, Field(description="How to layout children on screen")
]
children: Annotated[Tree, Field(description="Child Components")]


ComponentUnion = Group | SignalR | SignalW | SignalRW | SignalX | SignalRef | DeviceRef
Expand All @@ -441,9 +457,9 @@ def walk(tree: Tree) -> Iterator[ComponentUnion]:
class Device(TypedModel, YamlValidatorMixin):
"""Collection of Components"""

label: str = Field(description="Label for screen")
parent: Annotated[str, "The parent device (basename of yaml file)"] | None = None
children: Tree = Field([], description="Child Components")
label: Annotated[str, Field(description="Label for screen")]
parent: Annotated[str | None, "The parent device (basename of yaml file)"] = None
children: Annotated[Tree, Field(description="Child Components")] = []

def _to_dict(self) -> dict[str, Any]:
"""Serialize a `Device` instance to a `dict`.
Expand Down

0 comments on commit be2cfe3

Please sign in to comment.