openjd-cli 0.7.2__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/PKG-INFO +1 -1
  2. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/_version.py +2 -2
  3. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/pyproject.toml +1 -0
  4. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_common/_validation_utils.py +3 -2
  5. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/__init__.py +11 -0
  6. openjd_cli-0.7.4/src/openjd/cli/_run/_help_formatter.py +353 -0
  7. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/_run_command.py +27 -3
  8. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_version.py +2 -2
  9. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/.gitignore +0 -0
  10. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/LICENSE +0 -0
  11. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/NOTICE +0 -0
  12. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/README.md +0 -0
  13. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/hatch.toml +0 -0
  14. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/hatch_version_hook.py +0 -0
  15. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/__main__.py +0 -0
  16. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/__init__.py +0 -0
  17. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_check/__init__.py +0 -0
  18. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_check/_check_command.py +0 -0
  19. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_common/__init__.py +0 -0
  20. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_common/_extensions.py +0 -0
  21. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_common/_job_from_template.py +0 -0
  22. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_create_argparser.py +0 -0
  23. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/_local_session/__init__.py +0 -0
  24. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/_local_session/_actions.py +0 -0
  25. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/_local_session/_logs.py +0 -0
  26. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_run/_local_session/_session_manager.py +0 -0
  27. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_schema/__init__.py +0 -0
  28. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_schema/_schema_command.py +0 -0
  29. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_summary/__init__.py +0 -0
  30. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_summary/_summary_command.py +0 -0
  31. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/_summary/_summary_output.py +0 -0
  32. {openjd_cli-0.7.2 → openjd_cli-0.7.4}/src/openjd/cli/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openjd-cli
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Provides a command-line interface for working with Open Job Description templates.
5
5
  Project-URL: Homepage, https://github.com/OpenJobDescription/openjd-cli
6
6
  Project-URL: Source, https://github.com/OpenJobDescription/openjd-cli
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.7.2'
32
- __version_tuple__ = version_tuple = (0, 7, 2)
31
+ __version__ = version = '0.7.4'
32
+ __version_tuple__ = version_tuple = (0, 7, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -190,6 +190,7 @@ patch_tags = [
190
190
  "feat",
191
191
  "fix",
192
192
  "refactor",
193
+ "perf",
193
194
  ]
194
195
 
195
196
  [tool.semantic_release.publish]
@@ -15,11 +15,12 @@ from openjd.model import (
15
15
 
16
16
 
17
17
  def get_doc_type(filepath: Path) -> DocumentType:
18
+ # If the file has a .json extension, treat it strictly
19
+ # as JSON, otherwise treat it as YAML.
18
20
  if filepath.suffix.lower() == ".json":
19
21
  return DocumentType.JSON
20
- elif filepath.suffix.lower() in (".yaml", ".yml"):
22
+ else:
21
23
  return DocumentType.YAML
22
- raise RuntimeError(f"'{str(filepath)}' is not JSON or YAML.")
23
24
 
24
25
 
25
26
  def read_template(template_file: Path) -> dict[str, Any]:
@@ -1,6 +1,7 @@
1
1
  # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
 
3
3
  from ._run_command import add_run_arguments, do_run
4
+ from ._help_formatter import JobTemplateHelpAction
4
5
  from .._common import add_common_arguments, CommonArgument, SubparserGroup
5
6
 
6
7
 
@@ -10,7 +11,17 @@ def populate_argparser(subcommands: SubparserGroup) -> None:
10
11
  "run",
11
12
  description="Takes a Job Template and runs the entire job or a selected Step from the job.",
12
13
  usage="openjd run JOB_TEMPLATE_PATH [arguments]",
14
+ add_help=False, # Disable default help to use custom action
13
15
  )
14
16
  add_common_arguments(run_parser, {CommonArgument.PATH, CommonArgument.JOB_PARAMS})
15
17
  add_run_arguments(run_parser)
18
+
19
+ # Add custom help action that provides context-aware help based on job template
20
+ run_parser.add_argument(
21
+ "-h",
22
+ "--help",
23
+ action=JobTemplateHelpAction,
24
+ help="Show help message. When a job template path is provided, displays job-specific help including parameter definitions.",
25
+ )
26
+
16
27
  run_parser.set_defaults(func=do_run)
@@ -0,0 +1,353 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+
3
+ """
4
+ Help formatter module for generating context-aware help text for job templates.
5
+ """
6
+
7
+ from argparse import Action, ArgumentParser, Namespace
8
+ from pathlib import Path
9
+ import sys
10
+ import textwrap
11
+ from typing import Any, Dict, Optional
12
+
13
+ from openjd.model import DecodeValidationError, JobTemplate
14
+ from .._common import read_job_template, process_extensions_argument
15
+
16
+
17
+ def format_parameter_info(param_def: Dict[str, Any]) -> str:
18
+ """
19
+ Format a single parameter definition for help display.
20
+
21
+ This function converts a parameter definition from a job template into
22
+ readable help text that includes the parameter's type, constraints, and
23
+ default value (if any).
24
+
25
+ Args:
26
+ param_def: Parameter definition dictionary from job template containing:
27
+ - name (str): Parameter name
28
+ - type (str): Parameter type (STRING, INT, FLOAT, PATH)
29
+ - description (str, optional): Parameter description
30
+ - default (Any, optional): Default value
31
+ - minValue (int|float, optional): Minimum value for numeric types
32
+ - maxValue (int|float, optional): Maximum value for numeric types
33
+ - minLength (int, optional): Minimum length for string types
34
+ - maxLength (int, optional): Maximum length for string types
35
+ - allowedValues (list, optional): List of allowed values
36
+
37
+ Returns:
38
+ Formatted string describing the parameter in argparse-style help format.
39
+
40
+ Example output formats:
41
+ "ParamName (STRING) [required]"
42
+ "ParamName (INT) [default: 42]"
43
+ "ParamName (FLOAT) [default: 3.14] (range: 0.0 to 10.0)"
44
+ "ParamName (STRING) [default: 'hello'] (allowed: 'hello', 'world')"
45
+ """
46
+ param_name = param_def.get("name", "")
47
+ param_type = param_def.get("type", "STRING")
48
+ description = param_def.get("description", "")
49
+ default_value = param_def.get("default")
50
+
51
+ # Start building the parameter info string
52
+ parts = []
53
+
54
+ # Add parameter name and type
55
+ type_info = f"{param_name} ({param_type})"
56
+ parts.append(type_info)
57
+
58
+ # Add required/default status
59
+ has_multiline_default = False
60
+ if default_value is not None:
61
+ # Check if default value contains newlines (multi-line string)
62
+ if (
63
+ param_type in ("STRING", "PATH")
64
+ and isinstance(default_value, str)
65
+ and "\n" in default_value
66
+ ):
67
+ has_multiline_default = True
68
+ parts.append("[default: see below]")
69
+ else:
70
+ # Format default value based on type
71
+ if param_type in ("STRING", "PATH"):
72
+ default_str = f"[default: '{default_value}']"
73
+ else:
74
+ default_str = f"[default: {default_value}]"
75
+ parts.append(default_str)
76
+ else:
77
+ parts.append("[required]")
78
+
79
+ # Build the first line with name, type, and default/required
80
+ first_line = " ".join(parts)
81
+
82
+ # Build constraint information
83
+ constraints = []
84
+
85
+ # Handle numeric constraints (minValue, maxValue)
86
+ min_val = param_def.get("minValue")
87
+ max_val = param_def.get("maxValue")
88
+
89
+ if min_val is not None and max_val is not None:
90
+ constraints.append(f"range: {min_val} to {max_val}")
91
+ elif min_val is not None:
92
+ constraints.append(f"minimum: {min_val}")
93
+ elif max_val is not None:
94
+ constraints.append(f"maximum: {max_val}")
95
+
96
+ # Handle string length constraints
97
+ min_len = param_def.get("minLength")
98
+ max_len = param_def.get("maxLength")
99
+
100
+ if min_len is not None and max_len is not None:
101
+ constraints.append(f"length: {min_len} to {max_len} characters")
102
+ elif min_len is not None:
103
+ constraints.append(f"minimum length: {min_len} characters")
104
+ elif max_len is not None:
105
+ constraints.append(f"maximum length: {max_len} characters")
106
+
107
+ # Handle allowed values constraint
108
+ allowed_values = param_def.get("allowedValues")
109
+ if allowed_values:
110
+ # Format allowed values based on type
111
+ if param_type in ("STRING", "PATH"):
112
+ formatted_values = ", ".join(f"'{v}'" for v in allowed_values)
113
+ else:
114
+ formatted_values = ", ".join(str(v) for v in allowed_values)
115
+ constraints.append(f"allowed: {formatted_values}")
116
+
117
+ # Combine everything
118
+ result_lines = [first_line]
119
+
120
+ # Add description if present
121
+ if description:
122
+ result_lines.append(f" {description}")
123
+
124
+ # Add constraints if present
125
+ if constraints:
126
+ constraint_str = " (" + ", ".join(constraints) + ")"
127
+ result_lines[0] += constraint_str
128
+
129
+ # Add multi-line default value if present
130
+ if has_multiline_default and isinstance(default_value, str):
131
+ result_lines.append(" Default value:")
132
+ # Indent each line of the default value
133
+ result_lines.append(textwrap.indent(default_value, " "))
134
+
135
+ return "\n".join(result_lines)
136
+
137
+
138
+ def generate_job_template_help(
139
+ template: JobTemplate, parser: ArgumentParser, template_path: Path
140
+ ) -> str:
141
+ """
142
+ Generate help text for a specific job template.
143
+
144
+ This function creates formatted help text that includes the job name,
145
+ description (if present), parameter definitions with their types and
146
+ constraints, and standard command options from the argument parser.
147
+
148
+ Args:
149
+ template: The decoded job template object (JobTemplate from openjd.model)
150
+ parser: The argument parser for the run command
151
+ template_path: Path to the template file
152
+
153
+ Returns:
154
+ Formatted help text string in argparse-style format
155
+
156
+ Example output:
157
+ usage: openjd run my-template.yaml [arguments]
158
+
159
+ Job: my-job
160
+ This is a sample job that demonstrates parameter usage.
161
+
162
+ Job Parameters (-p/--job-param PARAM_NAME=VALUE):
163
+ Message (STRING) [default: 'Hello, world!']
164
+ A message to display
165
+
166
+ Standard Options:
167
+ --step STEP_NAME The name of the Step in the Job to run Tasks from.
168
+ ...
169
+ """
170
+ lines = []
171
+
172
+ # Add usage line with actual template path instead of symbolic placeholder
173
+ usage = parser.format_usage().strip()
174
+ # Replace the symbolic JOB_TEMPLATE_PATH with the actual path provided
175
+ usage = usage.replace("JOB_TEMPLATE_PATH", str(template_path))
176
+ lines.append(usage)
177
+ lines.append("")
178
+
179
+ # Print the job name and description (if present)
180
+ lines.append(f"Job: {template.name}")
181
+ if template.description:
182
+ lines.append(template.description)
183
+ lines.append("")
184
+
185
+ # Extract parameter definitions (optional field)
186
+ param_definitions = template.parameterDefinitions
187
+
188
+ if param_definitions:
189
+ lines.append("Job Parameters (-p/--job-param PARAM_NAME=VALUE):")
190
+
191
+ for param_def in param_definitions:
192
+ param_dict: Dict[str, Any] = {
193
+ "name": param_def.name,
194
+ "type": param_def.type.value,
195
+ "description": param_def.description,
196
+ "default": param_def.default,
197
+ }
198
+
199
+ # Add optional constraint fields if they exist
200
+ for constraint in [
201
+ "minValue",
202
+ "maxValue",
203
+ "minLength",
204
+ "maxLength",
205
+ "allowedValues",
206
+ ]:
207
+ if hasattr(param_def, constraint):
208
+ value = getattr(param_def, constraint)
209
+ if value is not None:
210
+ param_dict[constraint] = value
211
+
212
+ # Format the parameter info
213
+ param_info = format_parameter_info(param_dict)
214
+
215
+ # Indent the parameter info with 2 spaces
216
+ lines.append(textwrap.indent(param_info, " "))
217
+
218
+ lines.append("")
219
+
220
+ # Add standard options section
221
+ lines.append("Standard Options:")
222
+
223
+ # Get the help text for all other arguments
224
+ full_help = parser.format_help()
225
+
226
+ # Split into lines and find where the arguments section starts
227
+ help_lines = full_help.split("\n")
228
+
229
+ # Skip usage and positional arguments, capture optional arguments
230
+ in_options = False
231
+ for line in help_lines:
232
+ # Look for optional arguments section or specific argument patterns
233
+ if "optional arguments:" in line.lower() or "options:" in line.lower():
234
+ in_options = True
235
+ continue
236
+ elif "positional arguments:" in line.lower():
237
+ continue
238
+ elif in_options:
239
+ # Add all option lines
240
+ lines.append(line)
241
+
242
+ return "\n".join(lines)
243
+
244
+
245
+ class JobTemplateHelpAction(Action):
246
+ """
247
+ Custom argparse Action that intercepts help requests (-h/--help) and generates
248
+ context-aware help text based on the job template file provided.
249
+
250
+ This action is triggered when the user invokes the run command with a job template
251
+ path and the -h or --help flag. It loads and validates the template, then generates
252
+ and displays help text that includes job-specific information (name, description,
253
+ parameters) alongside standard command options.
254
+
255
+ The action handles errors gracefully, displaying user-friendly error messages for
256
+ issues like missing files, invalid syntax, or schema validation failures.
257
+ """
258
+
259
+ def __init__(
260
+ self,
261
+ option_strings,
262
+ dest,
263
+ default=False,
264
+ required=False,
265
+ help=None,
266
+ ):
267
+ """
268
+ Initialize the custom help action.
269
+
270
+ Args:
271
+ option_strings: List of option strings (e.g., ['-h', '--help'])
272
+ dest: Destination attribute name in the namespace
273
+ default: Default value for the action
274
+ required: Whether this argument is required
275
+ help: Help text for this option
276
+ """
277
+ super().__init__(
278
+ option_strings=option_strings,
279
+ dest=dest,
280
+ default=default,
281
+ nargs=0,
282
+ required=required,
283
+ help=help,
284
+ )
285
+
286
+ def __call__(
287
+ self,
288
+ parser: ArgumentParser,
289
+ namespace: Namespace,
290
+ values: Any,
291
+ option_string: Optional[str] = None,
292
+ ) -> None:
293
+ """
294
+ Invoked when -h or --help is encountered.
295
+
296
+ This method intercepts the help request, checks if a job template path
297
+ has been provided, loads and validates the template, generates custom
298
+ help text, and displays it before exiting.
299
+
300
+ Args:
301
+ parser: The argument parser instance
302
+ namespace: The namespace containing parsed arguments so far
303
+ values: The value associated with the action (unused for help)
304
+ option_string: The option string that triggered this action ('-h' or '--help')
305
+
306
+ Exits:
307
+ - Code 0: Help displayed successfully
308
+ - Code 1: Error occurred (file not found, validation failed, etc.)
309
+ """
310
+ # Check if a template path has been provided
311
+ template_path = getattr(namespace, "path", None)
312
+
313
+ if template_path is None:
314
+ # No template path provided, show standard help
315
+ parser.print_help()
316
+ sys.exit(0)
317
+
318
+ # Convert to Path object if it's a string
319
+ if isinstance(template_path, str):
320
+ template_path = Path(template_path)
321
+
322
+ try:
323
+ # Process extensions argument (defaults to all supported extensions if not provided)
324
+ extensions_arg = getattr(namespace, "extensions", None)
325
+ supported_extensions = process_extensions_argument(extensions_arg)
326
+
327
+ # Load and validate the job template
328
+ # This will raise RuntimeError for file issues or DecodeValidationError for validation issues
329
+ template = read_job_template(template_path, supported_extensions=supported_extensions)
330
+
331
+ # Generate the custom help text
332
+ help_text = generate_job_template_help(template, parser, template_path)
333
+
334
+ # Display the help text
335
+ print(help_text)
336
+
337
+ # Exit successfully
338
+ sys.exit(0)
339
+
340
+ except RuntimeError as e:
341
+ # Handle file not found, parse errors, etc.
342
+ print(f"Error: {str(e)}", file=sys.stderr)
343
+ sys.exit(1)
344
+
345
+ except DecodeValidationError as e:
346
+ # Handle template validation errors
347
+ print(f"Error: Invalid job template: {str(e)}", file=sys.stderr)
348
+ sys.exit(1)
349
+
350
+ except Exception as e:
351
+ # Catch any unexpected errors
352
+ print(f"Error: Failed to generate help: {str(e)}", file=sys.stderr)
353
+ sys.exit(1)
@@ -415,8 +415,8 @@ def do_run(args: Namespace) -> OpenJDCliResult:
415
415
  filename = Path(env).expanduser()
416
416
  try:
417
417
  # Raises: RuntimeError, DecodeValidationError
418
- template = read_environment_template(filename)
419
- environments.append(template)
418
+ env_template = read_environment_template(filename)
419
+ environments.append(env_template)
420
420
  except (RuntimeError, DecodeValidationError) as e:
421
421
  return OpenJDCliResult(status="error", message=str(e))
422
422
 
@@ -504,7 +504,31 @@ def do_run(args: Namespace) -> OpenJDCliResult:
504
504
  task_parameter_values = []
505
505
 
506
506
  except RuntimeError as rte:
507
- return OpenJDCliResult(status="error", message=str(rte))
507
+ error_message = str(rte)
508
+ # Print the help information along with the error
509
+ from ._help_formatter import generate_job_template_help
510
+ from .._common import read_job_template, add_common_arguments, CommonArgument
511
+
512
+ try:
513
+ # Load the template to generate help
514
+ job_template = read_job_template(args.path, supported_extensions=extensions)
515
+ # Create a minimal parser for help generation with the same usage format
516
+ temp_parser = ArgumentParser(
517
+ prog="openjd run",
518
+ usage="openjd run JOB_TEMPLATE_PATH [arguments]",
519
+ add_help=False,
520
+ )
521
+ temp_parser.add_argument("path")
522
+ add_common_arguments(temp_parser, {CommonArgument.PATH, CommonArgument.JOB_PARAMS})
523
+ add_run_arguments(temp_parser)
524
+
525
+ help_text = generate_job_template_help(job_template, temp_parser, args.path)
526
+ error_message = f"{error_message}\n\n{help_text}"
527
+ except Exception:
528
+ # If we can't generate help, just show the original error
529
+ pass
530
+
531
+ return OpenJDCliResult(status="error", message=error_message)
508
532
 
509
533
  step_list: list[Step] = []
510
534
  try:
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.7.2'
32
- __version_tuple__ = version_tuple = (0, 7, 2)
31
+ __version__ = version = '0.7.4'
32
+ __version_tuple__ = version_tuple = (0, 7, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
File without changes
File without changes
File without changes
File without changes
File without changes