Skip to content

Commit b0c0271

Browse files
committed
feat(models)
- refactor manifest models using annotated pattern
1 parent a2a9050 commit b0c0271

File tree

3 files changed

+250
-86
lines changed
  • packages/cm-web/src/lsst/cmservice/web/components
  • src/lsst/cmservice/models/manifests

3 files changed

+250
-86
lines changed

packages/cm-web/src/lsst/cmservice/web/components/dialog.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import yaml
1010
from nicegui import ui
1111
from nicegui.events import ClickEventArguments, GenericEventArguments, ValueChangeEventArguments
12-
from pydantic_core import PydanticUndefined, ValidationError
12+
from pydantic_core import ValidationError
1313

1414
from lsst.cmservice.common.enums import DEFAULT_NAMESPACE
1515

@@ -613,21 +613,22 @@ def ribbon(self) -> None:
613613

614614
def handle_apply_template(self) -> None:
615615
"""Applies a template spec to the editor based on the selected manifest
616-
kind.
616+
kind. If available, the first provided field example is used to build
617+
the template, otherwise a default value is used.
617618
"""
618619
if self.context.kind is None:
619620
return None
620621
template = {}
621622
kind_model = KIND_TO_SPEC[self.context.kind]
622623
for field_name, field_info in kind_model.model_fields.items():
623624
if field_info.examples:
624-
template[field_name] = field_info.examples[0]
625-
elif field_info.default is not PydanticUndefined:
626-
template[field_name] = field_info.default
627-
elif field_info.default_factory is not None:
628-
template[field_name] = field_info.default_factory() # type: ignore[call-arg]
625+
field_example = field_info.examples[0]
626+
if isinstance(field_example, dict):
627+
template[field_name] = field_example.get(field_name, field_example)
628+
else:
629+
template[field_name] = field_example
629630
else:
630-
template[field_name] = None
631+
template[field_name] = field_info.get_default(call_default_factory=True)
631632
self.editor.set_value(yaml.safe_dump(template))
632633
self.editor.update()
633634

src/lsst/cmservice/models/manifests/bps.py

Lines changed: 181 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class BpsSpec(ManifestSpec):
1919
"""
2020

2121
model_config = SPEC_CONFIG
22+
2223
pipeline_yaml: (
2324
Annotated[
2425
str,
@@ -27,84 +28,201 @@ class BpsSpec(ManifestSpec):
2728
description="The absolute path to a Pipeline YAML specification file with optional anchor. "
2829
"The path must begin with a `/` or a `${...}` environment variable.",
2930
pattern="^(/|\\$\\{.*\\})(.*)(\\.yaml)(#.*)?$",
30-
examples=[
31-
"${DRP_PIPE_DIR}/path/to/pipeline.yaml#anchor",
32-
"/absolute/path/to/file.yaml",
33-
"${CUSTOM_VAR}/file.yaml#anchor1,anchor2",
34-
],
3531
),
3632
]
3733
| Annotated[
3834
None,
3935
Field(
40-
title="No Pipeline YAML",
36+
title="Null",
4137
description="A BPS Manifest does not need to specify a pipeline YAML file, but one is "
4238
"mandatory for a Step.",
4339
),
4440
]
45-
) = None
46-
variables: dict[str, str] | None = Field(
41+
) = Field(
42+
default=None,
43+
examples=[
44+
"${DRP_PIPE_DIR}/path/to/pipeline.yaml#anchor",
45+
"/absolute/path/to/file.yaml",
46+
"${CUSTOM_VAR}/file.yaml#anchor1,anchor2",
47+
],
48+
)
49+
50+
variables: (
51+
Annotated[
52+
dict[str, str],
53+
Field(
54+
title="BPS Variables",
55+
description="A mapping of name-value string pairs used to define additional top-level BPS "
56+
"settings or substitution variables. Note that the values are quoted in the output. For "
57+
"values that should not be quoted or otherwise used literally, see `literals`",
58+
),
59+
]
60+
| Annotated[
61+
None,
62+
Field(
63+
title="Null",
64+
description="This field is optional for a BPS configuration.",
65+
),
66+
]
67+
) = Field(
4768
default=None,
48-
title="BPS Variables",
49-
description="A mapping of name-value string pairs used to define additional top-level BPS settings "
50-
"or substitution variables. Note that the values are quoted in the output. For values that should "
51-
"not be quoted or otherwise used literally, see `literals`",
5269
examples=[{"operator": "lsstsvc1"}],
5370
)
54-
include_files: list[str] | None = Field(
55-
default_factory=list,
56-
title="BPS Include Config Files",
57-
description="A list of include files added to the BPS submission file under the `includeConfigs`"
58-
" heading. This list is combined with `include_files` from other manifests.",
71+
72+
include_files: (
73+
Annotated[
74+
list[str],
75+
Field(
76+
title="BPS Include Config Files",
77+
description="A list of include files added to the BPS submission file under the "
78+
"`includeConfigs` heading. This list is combined with `include_files` from other manifests.",
79+
),
80+
]
81+
| Annotated[
82+
None,
83+
Field(
84+
title="Null",
85+
description="This field is optional for a BPS configuration.",
86+
),
87+
]
88+
) = Field(
89+
default=None,
5990
examples=[["${CTRL_BPS_DIR}/python/lsst/ctrl/bps/etc/bps_default.yaml"]],
6091
)
61-
literals: dict[str, Any] | None = Field(
92+
93+
literals: (
94+
Annotated[
95+
dict[str, Any],
96+
Field(
97+
title="BPS Configuration Literals",
98+
description="A mapping of arbitrary key-value sections to be added as additional literal "
99+
"YAML to the BPS submission file. For setting arbitrary BPS substitution variables, "
100+
"use `variables`.",
101+
),
102+
]
103+
| Annotated[
104+
None,
105+
Field(
106+
title="Null",
107+
description="This field is optional for a BPS configuration.",
108+
),
109+
]
110+
) = Field(
62111
default=None,
63-
title="BPS Configuration Literals",
64-
description="A mapping of arbitrary key-value sections to be added as additional literal YAML to "
65-
"the BPS submission file. For setting arbitrary BPS substitution variables, use `variables`. ",
66112
examples=[
67113
{
68-
"literals": {
69-
"requestMemory": 2048,
70-
"numberOfRetries": 5,
71-
"retryUnlessExit": [1, 2],
72-
"finalJob": {"command1": "echo HELLO WORLD"},
73-
}
114+
"requestMemory": 2048,
115+
"numberOfRetries": 5,
116+
"retryUnlessExit": [1, 2],
117+
"finalJob": {"command1": "echo HELLO WORLD"},
74118
}
75119
],
76120
)
77-
environment: dict[str, str] | None = Field(
78-
default=None,
79-
title="BPS Environment Variables",
80-
description="A mapping of name-value string pairs used to defined additional values under the "
81-
"`environment` heading of the BPS submission file.",
82-
min_length=1,
83-
examples=[{"environment": {"LSST_S3_USE_THREADS": 1}}],
84-
)
85-
payload: dict[str, str] | None = Field(
121+
122+
environment: (
123+
Annotated[
124+
dict[str, str],
125+
Field(
126+
title="BPS Environment Variables",
127+
description="A mapping of name-value string pairs used to defined additional values under "
128+
"the `environment` heading of the BPS submission file.",
129+
min_length=1,
130+
),
131+
]
132+
| Annotated[
133+
None,
134+
Field(
135+
title="Null",
136+
description="This field is optional for a BPS configuration.",
137+
),
138+
]
139+
) = Field(
86140
default=None,
87-
title="BPS Payload Configuration",
88-
description="A mapping of name-value string pairs used to define BPS payload options. "
89-
"Note that these values are generated from other configuration sources at runtime.",
90-
)
91-
extra_init_options: str | None = Field(
92-
default=None, description="Passthrough options added to the end of pipetaskinit"
93-
)
94-
extra_qgraph_options: str | None = Field(
95-
default=None, description="Passthrough options for QuantumGraph builder."
96-
)
97-
extra_run_quantum_options: str | None = Field(
98-
default=None, description="Passthrough options for Quantum execution", examples=["--no-versions"]
99-
)
100-
extra_update_qgraph_options: str | None = Field(
101-
default=None, description="Passthrough options for QuantumGraph updater."
141+
examples=[{"LSST_S3_USE_THREADS": 1}],
102142
)
103-
clustering: dict[str, Any] | None = Field(
143+
144+
payload: (
145+
Annotated[
146+
dict[str, str],
147+
Field(
148+
title="BPS Payload Configuration",
149+
description="A mapping of name-value string pairs used to define BPS payload options. "
150+
"Note that these values are generated from other configuration sources at runtime.",
151+
),
152+
]
153+
| Annotated[
154+
None,
155+
Field(
156+
title="Null",
157+
description="This field is optional for a BPS configuration.",
158+
),
159+
]
160+
) = None
161+
162+
extra_init_options: (
163+
Annotated[str, Field(description="Passthrough options added to the end of pipetaskinit")]
164+
| Annotated[
165+
None,
166+
Field(
167+
title="Null",
168+
description="This field is optional for a BPS configuration.",
169+
),
170+
]
171+
) = None
172+
173+
extra_qgraph_options: (
174+
Annotated[str, Field(description="Passthrough options for QuantumGraph builder.")]
175+
| Annotated[
176+
None,
177+
Field(
178+
title="Null",
179+
description="This field is optional for a BPS configuration.",
180+
),
181+
]
182+
) = None
183+
184+
extra_run_quantum_options: (
185+
Annotated[
186+
str, Field(description="Passthrough options for Quantum execution", examples=["--no-versions"])
187+
]
188+
| Annotated[
189+
None,
190+
Field(
191+
title="Null",
192+
description="This field is optional for a BPS configuration.",
193+
),
194+
]
195+
) = None
196+
197+
extra_update_qgraph_options: (
198+
Annotated[str, Field(description="Passthrough options for QuantumGraph updater.")]
199+
| Annotated[
200+
None,
201+
Field(
202+
title="Null",
203+
description="This field is optional for a BPS configuration.",
204+
),
205+
]
206+
) = None
207+
208+
clustering: (
209+
Annotated[
210+
dict[str, Any],
211+
Field(
212+
title="BPS Clustering Configuration",
213+
description="A mapping of labeled clustering directives, added as literal YAML under the "
214+
"`cluster` heading. The top-level `clusterAlgorithm` should be added to `literals`.",
215+
),
216+
]
217+
| Annotated[
218+
None,
219+
Field(
220+
title="Null",
221+
description="This field is optional for a BPS configuration.",
222+
),
223+
]
224+
) = Field(
104225
default=None,
105-
title="BPS Clustering Configuration",
106-
description="A mapping of labeled clustering directives, added as literal YAML under the `cluster` "
107-
"heading. The top-level `clusterAlgorithm` should be added to `literals`.",
108226
examples=[
109227
{
110228
"clusterLabel1": {
@@ -116,11 +234,14 @@ class BpsSpec(ManifestSpec):
116234
}
117235
],
118236
)
119-
operator: str = Field(
120-
default="cmservice",
121-
title="BPS Operator Name",
122-
description="The string name of a pilot or operator to reflect in the BPS configuration.",
123-
)
237+
238+
operator: Annotated[
239+
str,
240+
Field(
241+
title="BPS Operator Name",
242+
description="The string name of a pilot or operator to reflect in the BPS configuration.",
243+
),
244+
] = "cmservice"
124245

125246

126247
class BpsManifest(LibraryManifest[BpsSpec]): ...

0 commit comments

Comments
 (0)