|
| 1 | +# pyright: reportUninitializedInstanceVariable=false |
| 2 | +from typing import Any |
1 | 3 | from typing import cast |
2 | | -import typing_extensions |
| 4 | +from typing import Annotated |
| 5 | +from typing import get_args |
3 | 6 | from typing_extensions import override |
4 | 7 |
|
5 | 8 | import argparse |
6 | 9 | import os |
7 | 10 | import socket |
8 | 11 | import sys |
9 | 12 | import time |
| 13 | +import uuid |
10 | 14 |
|
11 | 15 | import openshift_client as oc |
12 | 16 |
|
|
19 | 23 |
|
20 | 24 |
|
21 | 25 | class CreateJobCommandArgs(argparse.Namespace): |
22 | | - gpu: str = "v100" |
23 | | - image: str = "image-registry.openshift-image-registry.svc:5000/redhat-ods-applications/csw-run-f25:latest" |
24 | | - context: bool = True |
25 | | - name: str = "job" |
26 | | - job_id: int = os.getpid() |
27 | | - job_delete: bool = True |
28 | | - wait: bool = True |
29 | | - timeout: int = 60 * 15 * 4 |
30 | | - max_sec: int = 60 * 15 |
31 | | - gpu_numreq: int = 1 |
32 | | - gpu_numlim: int = 1 |
33 | | - verbose: int = 0 |
34 | | - command: list[str] = [] |
| 26 | + """This class serves two purposes: |
| 27 | +
|
| 28 | + 1. It provides type hints that permit a properly configured IDE (or type |
| 29 | + checker) to perform type checking on command line option values. |
| 30 | +
|
| 31 | + 2. It provides default values for command line options. |
| 32 | +
|
| 33 | + We accomplish this using annotated types. The first metadata argument in |
| 34 | + the annotation provides the default value; this can be a static value, or |
| 35 | + it can be callable. This gives us the ability to perform lazy evaluation of |
| 36 | + default values. |
| 37 | + """ |
| 38 | + |
| 39 | + gpu: Annotated[str, "v100"] |
| 40 | + image: Annotated[ |
| 41 | + str, |
| 42 | + "image-registry.openshift-image-registry.svc:5000/redhat-ods-applications/csw-run-f25:latest", |
| 43 | + ] |
| 44 | + context: Annotated[bool, True] |
| 45 | + name: Annotated[str, "job"] |
| 46 | + job_id: Annotated[ |
| 47 | + str, lambda: uuid.uuid5(uuid.NAMESPACE_OID, f"{os.getpid()}-{time.time()}").hex |
| 48 | + ] |
| 49 | + job_delete: Annotated[bool, True] |
| 50 | + wait: Annotated[bool, True] |
| 51 | + timeout: Annotated[int, 60 * 15 * 4] |
| 52 | + max_sec: Annotated[int, 60 * 15] |
| 53 | + gpu_numreq: Annotated[int, 1] |
| 54 | + gpu_numlim: Annotated[int, 1] |
| 55 | + verbose: Annotated[int, 0] |
| 56 | + command: list[str] |
| 57 | + |
| 58 | + @classmethod |
| 59 | + def get_default_for(cls, name: str) -> Any: |
| 60 | + spec = get_args(cls.__annotations__[name]) |
| 61 | + if len(spec) == 1: |
| 62 | + return |
| 63 | + if callable(spec[1]): |
| 64 | + return spec[1]() |
| 65 | + else: |
| 66 | + return spec[1] |
35 | 67 |
|
36 | 68 |
|
37 | 69 | class CreateJobCommand(Command): |
@@ -75,66 +107,65 @@ def build_parser(cls, subparsers: SubParserFactory): |
75 | 107 | p = super().build_parser(subparsers) |
76 | 108 | p.add_argument( |
77 | 109 | "--gpu", |
78 | | - default=CreateJobCommandArgs.gpu, |
79 | | - help=f"Select GPU type (default {CreateJobCommandArgs.gpu})", |
| 110 | + default=CreateJobCommandArgs.get_default_for("gpu"), |
| 111 | + help="Select GPU type", |
80 | 112 | ) |
81 | 113 | p.add_argument( |
82 | 114 | "--image", |
83 | | - default=CreateJobCommandArgs.image, |
84 | | - help=f"Specify container image for job (default {CreateJobCommandArgs.image})", |
| 115 | + default=CreateJobCommandArgs.get_default_for("image"), |
| 116 | + help="Specify container image for job", |
85 | 117 | ) |
86 | 118 | p.add_argument( |
87 | 119 | "--context", |
88 | 120 | action=argparse.BooleanOptionalAction, |
89 | | - default=CreateJobCommandArgs.context, |
90 | | - help=f"Copy working directory (default {CreateJobCommandArgs.context})", |
| 121 | + default=CreateJobCommandArgs.get_default_for("context"), |
| 122 | + help="Copy working directory", |
91 | 123 | ) |
92 | 124 | p.add_argument( |
93 | 125 | "--name", |
94 | | - default=CreateJobCommandArgs.name, |
95 | | - help=f"Base job name (default {CreateJobCommandArgs.name})", |
| 126 | + default=CreateJobCommandArgs.get_default_for("name"), |
| 127 | + help="Base job name", |
96 | 128 | ) |
97 | 129 | p.add_argument( |
98 | 130 | "--job-id", |
99 | | - default=CreateJobCommandArgs.job_id, |
100 | | - type=int, |
101 | | - help="Job ID suffix (default current pid)", |
| 131 | + default=CreateJobCommandArgs.get_default_for("job_id"), |
| 132 | + help="Job ID suffix", |
102 | 133 | ) |
103 | 134 | p.add_argument( |
104 | 135 | "--job-delete", |
105 | 136 | action=argparse.BooleanOptionalAction, |
106 | | - default=CreateJobCommandArgs.job_delete, |
107 | | - help=f"Delete job on completion (default {CreateJobCommandArgs.job_delete})", |
| 137 | + default=CreateJobCommandArgs.get_default_for("job_delete"), |
| 138 | + help="Delete job on completion", |
108 | 139 | ) |
109 | 140 | p.add_argument( |
110 | 141 | "--wait", |
111 | 142 | action=argparse.BooleanOptionalAction, |
112 | | - default=CreateJobCommandArgs.wait, |
113 | | - help=f"Wait for job completion (default {CreateJobCommandArgs.wait})", |
| 143 | + default=CreateJobCommandArgs.get_default_for("wait"), |
| 144 | + help="Wait for job completion", |
114 | 145 | ) |
115 | 146 | p.add_argument( |
116 | 147 | "--timeout", |
117 | | - default=CreateJobCommandArgs.timeout, |
| 148 | + default=CreateJobCommandArgs.get_default_for("timeout"), |
118 | 149 | type=int, |
119 | | - help=f"Wait timeout in seconds (default {CreateJobCommandArgs.timeout})", |
| 150 | + help="Wait timeout in seconds", |
120 | 151 | ) |
121 | 152 | p.add_argument( |
122 | 153 | "--max-sec", |
123 | | - default=CreateJobCommandArgs.max_sec, |
| 154 | + default=CreateJobCommandArgs.get_default_for("max_sec"), |
124 | 155 | type=int, |
125 | | - help=f"Maximum execution time in seconds (default {CreateJobCommandArgs.max_sec})", |
| 156 | + help="Maximum execution time in seconds", |
126 | 157 | ) |
127 | 158 | p.add_argument( |
128 | 159 | "--gpu-numreq", |
129 | | - default=CreateJobCommandArgs.gpu_numreq, |
| 160 | + default=CreateJobCommandArgs.get_default_for("gpu_numreq"), |
130 | 161 | type=int, |
131 | | - help=f"Number of GPUs requested (default {CreateJobCommandArgs.gpu_numreq})", |
| 162 | + help="Number of GPUs requested", |
132 | 163 | ) |
133 | 164 | p.add_argument( |
134 | 165 | "--gpu-numlim", |
135 | | - default=CreateJobCommandArgs.gpu_numlim, |
| 166 | + default=CreateJobCommandArgs.get_default_for("gpu_numlim"), |
136 | 167 | type=int, |
137 | | - help=f"Number of GPUs limited (default {CreateJobCommandArgs.gpu_numlim})", |
| 168 | + help="Number of GPUs limited", |
138 | 169 | ) |
139 | 170 | p.add_argument( |
140 | 171 | "command", |
|
0 commit comments