Skip to content

Commit a10f41e

Browse files
committed
Add required flag to DIVE_PARAM
Pipelines can now mark a DIVE_PARAM as required by adding `required` as one of the type props in the comment header: # DIVE_PARAM ["My label", float, required] :my:key = 0.5 The parser strips it from `type_props` and sets `required: true` on the param. PipelineParamsDialog shows a red `*` on the label and blocks the Run button via vuetify form validation if the field is empty. Both the desktop common.ts parser and the server pipeline_discovery.py parser handle the keyword.
1 parent 77c22de commit a10f41e

5 files changed

Lines changed: 71 additions & 20 deletions

File tree

client/dive-common/apispec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ interface DiveParam {
4242
type_props?: string[];
4343
key: string;
4444
default: string;
45+
/** True if the user must supply a value before the pipeline can run. */
46+
required?: boolean;
4547
}
4648

4749
interface PipeMetadata {

client/dive-common/components/PipelineParamsDialog.vue

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,14 @@ export default defineComponent({
5555
strictlyPositive: (value: string | number) => Number(value) > 0 || 'Please enter a number > 0',
5656
};
5757
58-
function getRules(type: PipelineParamType): ValidationRule[] {
59-
const res = [];
58+
function getRules(type: PipelineParamType, required = false): ValidationRule[] {
59+
const res: ValidationRule[] = [];
60+
if (required) {
61+
res.push((value) => {
62+
if (value === undefined || value === null || value === '') return 'Required';
63+
return true;
64+
});
65+
}
6066
if (type.includes('int')) {
6167
res.push(rules.integer);
6268
}
@@ -131,7 +137,7 @@ export default defineComponent({
131137
:for="`input-${param.key}`"
132138
class="text-caption font-weight-bold text-uppercase text--secondary"
133139
>
134-
{{ param.label }}
140+
{{ param.label }}<span v-if="param.required" class="error--text"> *</span>
135141
</label>
136142
</div>
137143

@@ -157,7 +163,7 @@ export default defineComponent({
157163
hide-details="auto"
158164
class="mt-1"
159165
:min="param.type === 'int' ? 'none' : 0"
160-
:rules="getRules(param.type)"
166+
:rules="getRules(param.type, param.required)"
161167
/>
162168
</template>
163169

@@ -171,7 +177,7 @@ export default defineComponent({
171177
hide-details="auto"
172178
class="mt-1"
173179
:min="param.type === 'float' ? 'none' : 0"
174-
:rules="getRules(param.type)"
180+
:rules="getRules(param.type, param.required)"
175181
/>
176182
</template>
177183

@@ -193,7 +199,7 @@ export default defineComponent({
193199
:min="param.type_props?.at(0) || 0"
194200
:max="param.type_props?.at(1) || 100"
195201
:step="param.type_props?.at(2) || 1"
196-
:rules="getRules(param.type)"
202+
:rules="getRules(param.type, param.required)"
197203
outlined
198204
hide-details
199205
/>
@@ -211,6 +217,7 @@ export default defineComponent({
211217
dense
212218
hide-details="auto"
213219
class="mt-1"
220+
:rules="getRules(param.type, param.required)"
214221
/>
215222
</div>
216223
</v-form>

client/platform/desktop/backend/native/common.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,38 @@ async function extractPipeMetadata(filePath: string): Promise<PipeMetadata> {
109109
const [, label, rawArgs] = diveMatch;
110110
const args = rawArgs.split(',').map((arg) => arg.trim());
111111
const type: PipelineParamType = args[0] as PipelineParamType;
112-
const pipelineTypeArgs = args.slice(1);
112+
const restArgs = args.slice(1);
113+
// `required` is a flag keyword — strip it from type_props,
114+
// everything else stays positional for the type.
115+
const isRequired = restArgs.some((a) => a.toLowerCase() === 'required');
116+
const pipelineTypeArgs = restArgs.filter((a) => a.toLowerCase() !== 'required');
117+
118+
// `config <key> = <value>` — absolute kwiver key, no process/block prefix
119+
// applied. Used for global / cross-referenced settings.
120+
const configMatch = trimmed.match(/^config\s+([\w:.-]+)\s*=\s*([^#]+)/i);
121+
// Otherwise a regular per-process/block parameter assignment.
122+
const paramLineMatch = !configMatch
123+
? trimmed.match(/^(?:relativepath\s+)?(?::)?([\w:-]+)\s*=?\s*([^#]+)/i)
124+
: null;
125+
126+
let fullKey: string | null = null;
127+
let defaultValue: string | null = null;
128+
if (configMatch) {
129+
fullKey = configMatch[1];
130+
defaultValue = configMatch[2].trim();
131+
} else if (paramLineMatch) {
132+
fullKey = [...contextStack, paramLineMatch[1]].join(':');
133+
defaultValue = paramLineMatch[2].trim();
134+
}
113135

114-
const paramLineMatch = trimmed.match(/^(?:relativepath\s+)?(?::)?([\w:-]+)\s*=?\s*([^#]+)/i);
115-
if (paramLineMatch) {
116-
const localKey = paramLineMatch[1];
117-
const defaultValue = paramLineMatch[2].trim();
118-
const fullKey = [...contextStack, localKey].join(':');
136+
if (fullKey !== null && defaultValue !== null) {
119137
metadata.diveParams!.push({
120138
label,
121139
type,
122140
type_props: pipelineTypeArgs,
123141
key: fullKey,
124142
default: defaultValue,
143+
...(isRequired ? { required: true } : {}),
125144
});
126145
}
127146
}

server/dive_tasks/pipeline_discovery.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,43 @@ def extract_pipe_metadata(file_path: Path) -> PipeMetadata:
7272
label, raw_args = dive_match.groups()
7373
args = [arg.strip() for arg in raw_args.split(',')]
7474
param_type = args[0]
75-
pipeline_type_args = args[1:]
75+
rest_args = args[1:]
76+
# `required` is a flag keyword — strip it from type_props,
77+
# everything else stays positional for the type.
78+
is_required = any(a.lower() == 'required' for a in rest_args)
79+
pipeline_type_args = [a for a in rest_args if a.lower() != 'required']
80+
81+
# `config <key> = <value>` — absolute kwiver key, no
82+
# process/block prefix. Used for global / cross-referenced
83+
# settings.
84+
config_match = re.match(r'^config\s+([\w:.-]+)\s*=\s*([^#]+)', trimmed, re.IGNORECASE)
85+
# Otherwise a regular per-process/block parameter assignment.
86+
param_line_match = (
87+
re.match(r'^(?:relativepath\s+)?(?::)?([\w:-]+)\s*=?\s*([^#]+)', trimmed, re.IGNORECASE)
88+
if not config_match else None
89+
)
7690

77-
param_line_match = re.match(r'^(?:relativepath\s+)?(?::)?([\w:-]+)\s*=?\s*([^#]+)', trimmed,
78-
re.IGNORECASE)
79-
if param_line_match:
91+
full_key = None
92+
default_val = None
93+
if config_match:
94+
full_key = config_match.group(1)
95+
default_val = config_match.group(2).strip()
96+
elif param_line_match:
8097
local_key = param_line_match.group(1)
8198
default_val = param_line_match.group(2).strip()
8299
full_key = ":".join(context_stack + [local_key])
83100

84-
metadata["diveParams"].append({
101+
if full_key is not None and default_val is not None:
102+
param_dict = {
85103
"label": label,
86104
"type": param_type,
87105
"type_props": pipeline_type_args,
88106
"key": full_key,
89-
"default": default_val
90-
})
107+
"default": default_val,
108+
}
109+
if is_required:
110+
param_dict["required"] = True
111+
metadata["diveParams"].append(param_dict)
91112

92113
# --- Description extraction (Multiline) ---
93114
desc_start_match = re.match(r'^#\s*Description:\s*(.*)', line_raw, re.IGNORECASE)

server/dive_utils/types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ class Config:
5959
extra = 'forbid'
6060

6161

62-
class DiveParam(TypedDict):
62+
class DiveParam(TypedDict, total=False):
6363
label: str
6464
type: str
6565
type_props: list[str]
6666
key: str
6767
default: str
68+
# True if the pipeline can't run until the user supplies a value
69+
required: bool
6870

6971

7072
class PipeMetadata(TypedDict):

0 commit comments

Comments
 (0)