-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmodels.py
More file actions
166 lines (133 loc) · 4.67 KB
/
models.py
File metadata and controls
166 lines (133 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import logging
import re
from typing import Annotated, Literal
from pydantic import (
BaseModel,
ConfigDict,
Field,
RootModel,
StringConstraints,
computed_field,
field_validator,
)
LOGGER = logging.getLogger(__name__)
# Patterns:
# long: 'bl23b'
# short: 'b23', 'ixx-1'
_DLS_PREFIX_RE = re.compile(
r"""
^ # start of string
(?= # lookahead to ensure the following pattern matches
[A-Za-z0-9*?\[\]-]{2,16} # match 2-16 alphanumeric chars,-/wildcards
[:A-Za-z0-9*?[\]]* # match >=0 colons or alphanumeric chars/wildcards
[.A-Za-z0-9*?[\]] # match a dot or alphanumeric chars or wildcards
)
(?!.*--) # negative lookahead to ensure no double hyphens
(?!.*:\..) # negative lookahead to ensure no colon followed by a dot
( # start of capture group 1
(?:[A-Za-z0-9*?,\[\]-])* # match >1 alphanumeric characters followed
# by a hyphen, repeated 3 times
[\d*] # match zero or more digits
[^:]? # match zero or one non-colon character
)
(?::([a-zA-Z0-9*:]*))? # match zero or one colon followed by zero or more
# alphanumeric characters or colons (capture group 2)
(?:\.([a-zA-Z0-9*]+))? # match zero or one dot followed by one or more
# alphanumeric characters (capture group 3)
$ # end of string
""",
re.VERBOSE | re.UNICODE,
)
_LONG_DOM_RE = re.compile(r"^[a-zA-Z]{2}\d{2}[a-zA-Z]$")
_SHORT_DOM_RE = re.compile(r"^[a-zA-Z]{1}\d{2}(-[0-9]{1})?$")
class Beamline(BaseModel):
short_dom: str = Field(description="Short BL domain e.g. b23, ixx-1")
long_dom: str = Field(description="Full BL domain e.g. bl23b")
desc: str = Field(description="Description")
model_config = ConfigDict(extra="forbid")
@field_validator("short_dom")
@classmethod
def normalize_short_dom(cls, v: str) -> str:
v = v.strip().lower()
if _SHORT_DOM_RE.fullmatch(v):
# e.g. b23 -> bl23b
return v
raise ValueError("Invalid short dom.")
@field_validator("long_dom")
@classmethod
def normalize_long_dom(cls, v: str) -> str:
v = v.strip().lower()
if _LONG_DOM_RE.fullmatch(v):
# already long: bl23b
return v
raise ValueError("Invalid long dom.")
class Component(BaseModel):
prefix: str
desc: str | None = None
extras: list[str] | None = None
file: str | None = None
model_config = ConfigDict(extra="forbid")
@field_validator("prefix")
@classmethod
def _check_prefix(cls, v: str) -> str:
if not _DLS_PREFIX_RE.match(v):
raise ValueError(f"prefix '{v}' does not match DLS prefix pattern")
return v
@field_validator("extras")
@classmethod
def _check_extras(cls, v: list[str]) -> list[str]:
for p in v:
if not _DLS_PREFIX_RE.match(p):
raise ValueError(f"extras item '{p}' does not match DLS prefix pattern")
# ensure unique (schema enforces too)
if len(set(v)) != len(v):
raise ValueError("extras must contain unique items")
return v
@computed_field
@property
def P(self) -> str | None:
match = re.match(_DLS_PREFIX_RE, self.prefix)
if match:
return match.group(1)
@computed_field
@property
def R(self) -> str | None:
match = re.match(_DLS_PREFIX_RE, self.prefix)
if match:
return match.group(2)
@computed_field
@property
def attribute(self) -> str | None:
match = re.match(_DLS_PREFIX_RE, self.prefix)
if match:
return match.group(3)
class TechUi(BaseModel):
beamline: Beamline
components: dict[str, Component]
model_config = ConfigDict(extra="forbid")
"""
techui_support mapping models
"""
BobPath = Annotated[
str, StringConstraints(pattern=r"^(?:[A-Za-z0-9_.-]+/)*[A-Za-z0-9_.-]+\.bob$")
]
# Must contain at least one $(NAME) macro
MacroString = Annotated[
str,
StringConstraints(pattern=r"^[A-Za-z0-9_:\-./\s\$\(\)]+$"),
]
ScreenType = Literal["embedded", "related"]
class GuiComponentEntry(BaseModel):
file: BobPath
prefix: MacroString
type: ScreenType
model_config = ConfigDict(extra="forbid")
GuiComponentUnion = list[GuiComponentEntry] | GuiComponentEntry
class GuiComponents(RootModel[dict[str, GuiComponentUnion]]):
pass
class Entity(BaseModel):
type: str
P: str
desc: str | None = None
M: str | None
R: str | None