openjd-cli 0.7.4__tar.gz → 0.7.6__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.
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/PKG-INFO +3 -3
- openjd_cli-0.7.6/_version.py +24 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/hatch.toml +0 -3
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/hatch_version_hook.py +0 -1
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/pyproject.toml +5 -2
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/__main__.py +0 -1
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_check/_check_command.py +4 -1
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_common/_extensions.py +12 -3
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_common/_validation_utils.py +17 -3
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_create_argparser.py +0 -1
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_local_session/_actions.py +41 -4
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_local_session/_session_manager.py +119 -17
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_run_command.py +25 -4
- openjd_cli-0.7.6/src/openjd/cli/_version.py +24 -0
- openjd_cli-0.7.4/_version.py +0 -34
- openjd_cli-0.7.4/src/openjd/cli/_version.py +0 -34
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/.gitignore +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/LICENSE +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/NOTICE +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/README.md +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_check/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_common/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_common/_job_from_template.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_help_formatter.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_local_session/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_local_session/_logs.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_schema/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_schema/_schema_command.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_summary/__init__.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_summary/_summary_command.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_summary/_summary_output.py +0 -0
- {openjd_cli-0.7.4 → openjd_cli-0.7.6}/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.
|
|
3
|
+
Version: 0.7.6
|
|
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
|
|
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
|
-
Requires-Dist: openjd-model<0.
|
|
26
|
-
Requires-Dist: openjd-sessions<0.11,>=0.10.
|
|
25
|
+
Requires-Dist: openjd-model<0.12,>=0.9
|
|
26
|
+
Requires-Dist: openjd-sessions<0.11,>=0.10.11
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
## Open Job Description - CLI
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.7.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 6)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -7,7 +7,6 @@ from dataclasses import dataclass
|
|
|
7
7
|
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
_logger = logging.Logger(__name__, logging.INFO)
|
|
12
11
|
_stdout_handler = logging.StreamHandler(sys.stdout)
|
|
13
12
|
_stdout_handler.addFilter(lambda record: record.levelno <= logging.INFO)
|
|
@@ -29,8 +29,11 @@ classifiers = [
|
|
|
29
29
|
"Intended Audience :: End Users/Desktop"
|
|
30
30
|
]
|
|
31
31
|
dependencies = [
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# 0.10.11 is the first release with `step_name` on both Session.run_task and
|
|
33
|
+
# Session.enter_environment (RFC 0007/0008). Below it, run_task() raises
|
|
34
|
+
# TypeError on every task run, since this CLI passes the keyword unconditionally.
|
|
35
|
+
"openjd-sessions >= 0.10.11,< 0.11",
|
|
36
|
+
"openjd-model >= 0.9,< 0.12"
|
|
34
37
|
]
|
|
35
38
|
|
|
36
39
|
[project.urls]
|
|
@@ -41,7 +41,10 @@ def do_check(args: Namespace) -> OpenJDCliResult:
|
|
|
41
41
|
if TemplateSpecificationVersion.is_job_template(template_version):
|
|
42
42
|
decode_job_template(template=template_object, supported_extensions=extensions)
|
|
43
43
|
elif TemplateSpecificationVersion.is_environment_template(template_version):
|
|
44
|
-
|
|
44
|
+
# Pass `extensions` so env templates can declare e.g.
|
|
45
|
+
# WRAP_ACTIONS (RFC 0008) — without this the model rejects
|
|
46
|
+
# the extension declaration even though the CLI accepts it.
|
|
47
|
+
decode_environment_template(template=template_object, supported_extensions=extensions)
|
|
45
48
|
else:
|
|
46
49
|
return OpenJDCliResult(
|
|
47
50
|
status="error",
|
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
from argparse import ArgumentParser
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
# This is the list of Open Job Description extensions with implemented support
|
|
7
|
-
|
|
6
|
+
# This is the list of Open Job Description extensions with implemented support.
|
|
7
|
+
# EXPR (RFCs 0005/0006) and WRAP_ACTIONS (RFC 0008) are added so the CLI can run
|
|
8
|
+
# templates that declare them. WRAP_ACTIONS implies EXPR; the model library
|
|
9
|
+
# handles that implication when it parses the template.
|
|
10
|
+
SUPPORTED_EXTENSIONS = [
|
|
11
|
+
"TASK_CHUNKING",
|
|
12
|
+
"REDACTED_ENV_VARS",
|
|
13
|
+
"FEATURE_BUNDLE_1",
|
|
14
|
+
"EXPR",
|
|
15
|
+
"WRAP_ACTIONS",
|
|
16
|
+
]
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
def add_extensions_argument(run_parser: ArgumentParser):
|
|
11
20
|
run_parser.add_argument(
|
|
12
21
|
"--extensions",
|
|
13
|
-
help=f"A comma-separated list of Open Job Description extension names
|
|
22
|
+
help=f"A comma-separated list of Open Job Description extension names that the CLI will accept from job templates. Templates declare which extensions they use. Defaults to all that are implemented: {','.join(SUPPORTED_EXTENSIONS)}",
|
|
14
23
|
)
|
|
15
24
|
|
|
16
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from openjd.model import (
|
|
@@ -70,15 +70,29 @@ def read_job_template(template_file: Path, *, supported_extensions: list[str]) -
|
|
|
70
70
|
return template
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def read_environment_template(
|
|
73
|
+
def read_environment_template(
|
|
74
|
+
template_file: Path,
|
|
75
|
+
*,
|
|
76
|
+
supported_extensions: Optional[list[str]] = None,
|
|
77
|
+
) -> EnvironmentTemplate:
|
|
74
78
|
"""Open a JSON or YAML-formatted file and attempt to parse it into an EnvironmentTemplate object.
|
|
75
79
|
Raises a RuntimeError if the file doesn't exist or can't be opened, and raises a
|
|
76
80
|
DecodeValidationError if its contents can't be parsed into a valid EnvironmentTemplate.
|
|
81
|
+
|
|
82
|
+
Environment templates may declare extensions just like job templates do
|
|
83
|
+
(e.g. ``WRAP_ACTIONS`` for RFC 0008's wrap hooks). Pass the CLI's allow-list
|
|
84
|
+
via ``supported_extensions`` so the model accepts those declarations;
|
|
85
|
+
omitting the argument preserves the legacy behavior of accepting only the
|
|
86
|
+
default model surface.
|
|
77
87
|
"""
|
|
78
88
|
# Raises RuntimeError
|
|
79
89
|
template_object = read_template(template_file)
|
|
80
90
|
|
|
91
|
+
decode_kwargs: dict[str, list[str]] = {}
|
|
92
|
+
if supported_extensions is not None:
|
|
93
|
+
decode_kwargs["supported_extensions"] = supported_extensions
|
|
94
|
+
|
|
81
95
|
# Raises: DecodeValidationError
|
|
82
|
-
template = decode_environment_template(template=template_object)
|
|
96
|
+
template = decode_environment_template(template=template_object, **decode_kwargs)
|
|
83
97
|
|
|
84
98
|
return template
|
|
@@ -14,7 +14,6 @@ from ._summary import populate_argparser as populate_summary_subparser
|
|
|
14
14
|
from ._run import populate_argparser as populate_run_subparser
|
|
15
15
|
from ._schema import populate_argparser as populate_schema_subparser
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
# Our CLI subcommand construction requires that all leaf subcommands define a default
|
|
19
18
|
# 'func' property which is a Callable[[],None] that implements the subcommand.
|
|
20
19
|
# After parsing, we call that `func` argument of the resulting args object.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
|
+
from typing import Any, Optional
|
|
4
5
|
|
|
5
6
|
from openjd.model import Step, TaskParameterSet
|
|
6
7
|
from openjd.model.v2023_09 import Environment
|
|
@@ -47,7 +48,11 @@ class RunTaskAction(SessionAction):
|
|
|
47
48
|
|
|
48
49
|
def run(self):
|
|
49
50
|
self._session.run_task(
|
|
50
|
-
step_script=self._step.script,
|
|
51
|
+
step_script=self._step.script,
|
|
52
|
+
task_parameter_values=self._parameters,
|
|
53
|
+
# RFC 0008: the step name feeds the WrappedStep.Name template
|
|
54
|
+
# variable inside an active onWrapTaskRun hook.
|
|
55
|
+
step_name=self._step.name,
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
def __str__(self):
|
|
@@ -58,14 +63,46 @@ class RunTaskAction(SessionAction):
|
|
|
58
63
|
class EnterEnvironmentAction(SessionAction):
|
|
59
64
|
_environment: Environment
|
|
60
65
|
_id: str
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
_extra_let_bindings: Optional[list[str]]
|
|
67
|
+
_step_name: Optional[str]
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
session: Session,
|
|
72
|
+
environment: Environment,
|
|
73
|
+
env_id: str,
|
|
74
|
+
extra_let_bindings: Optional[list[str]] = None,
|
|
75
|
+
step_name: Optional[str] = None,
|
|
76
|
+
):
|
|
63
77
|
super(EnterEnvironmentAction, self).__init__(session)
|
|
64
78
|
self._environment = environment
|
|
65
79
|
self._id = env_id
|
|
80
|
+
# RFC 0007: a step's environments are entered with the step-level
|
|
81
|
+
# `let` bindings so their variables/actions can reference them.
|
|
82
|
+
self._extra_let_bindings = extra_let_bindings
|
|
83
|
+
# RFC 0007 §7.3.1 (EXPR): the owning step's name seeds Step.Name for
|
|
84
|
+
# a step environment's `let` bindings, variables, and actions. Only
|
|
85
|
+
# step-environment enters carry a step name; job/external enters
|
|
86
|
+
# leave it None.
|
|
87
|
+
self._step_name = step_name
|
|
66
88
|
|
|
67
89
|
def run(self):
|
|
68
|
-
|
|
90
|
+
# Both keywords are guaranteed by this package's `openjd-sessions`
|
|
91
|
+
# floor (>= 0.10.11), so neither is feature-detected. They are still
|
|
92
|
+
# only forwarded when they carry something: a step with no `let`
|
|
93
|
+
# bindings means "no extra bindings", and job/external environment
|
|
94
|
+
# enters have no owning step, so `Step.Name` must stay undefined for
|
|
95
|
+
# them rather than being seeded with None.
|
|
96
|
+
optional_kwargs: dict[str, Any] = {}
|
|
97
|
+
if self._extra_let_bindings:
|
|
98
|
+
optional_kwargs["extra_let_bindings"] = self._extra_let_bindings
|
|
99
|
+
if self._step_name is not None:
|
|
100
|
+
optional_kwargs["step_name"] = self._step_name
|
|
101
|
+
self._session.enter_environment(
|
|
102
|
+
environment=self._environment,
|
|
103
|
+
identifier=self._id,
|
|
104
|
+
**optional_kwargs,
|
|
105
|
+
)
|
|
69
106
|
|
|
70
107
|
def __str__(self):
|
|
71
108
|
return f"Enter Environment '{self._environment.name}'"
|
{openjd_cli-0.7.4 → openjd_cli-0.7.6}/src/openjd/cli/_run/_local_session/_session_manager.py
RENAMED
|
@@ -8,6 +8,7 @@ from types import FrameType, TracebackType
|
|
|
8
8
|
from signal import signal, SIGINT, SIGTERM, SIG_DFL
|
|
9
9
|
from itertools import islice
|
|
10
10
|
from datetime import datetime, timedelta, timezone
|
|
11
|
+
from math import isfinite
|
|
11
12
|
|
|
12
13
|
from ._actions import (
|
|
13
14
|
EnterEnvironmentAction,
|
|
@@ -49,6 +50,50 @@ class LocalSessionFailed(RuntimeError):
|
|
|
49
50
|
super().__init__(f"Action failed: {failed_action}")
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def _calculate_adaptive_chunk_size(
|
|
54
|
+
*,
|
|
55
|
+
current_chunk_size: int,
|
|
56
|
+
completed_task_count: int,
|
|
57
|
+
completed_task_duration: float,
|
|
58
|
+
target_runtime_seconds: float,
|
|
59
|
+
) -> Optional[int]:
|
|
60
|
+
"""The adaptive-chunking size estimate, or ``None`` to defer adjusting.
|
|
61
|
+
|
|
62
|
+
Implements steps 3-7 of the adaptive chunking algorithm in the Open Job
|
|
63
|
+
Description CLI specification (``specs/cli/run.md`` in openjd-rs): if the
|
|
64
|
+
cumulative duration is zero or non-finite, keep the current chunk size and
|
|
65
|
+
wait for a measurable sample.
|
|
66
|
+
|
|
67
|
+
Deferring matters because a zero ``duration_per_task`` makes the ideal size
|
|
68
|
+
unrepresentable. The two implementations fail differently without this
|
|
69
|
+
guard: the Rust CLI produces ``inf``, which saturates to a huge chunk size,
|
|
70
|
+
while Python raises ``ZeroDivisionError`` out of the run. Neither is
|
|
71
|
+
reachable through a normal ``openjd run`` today -- ``run_task`` always
|
|
72
|
+
spawns and waits on a real subprocess, so a measured chunk duration of
|
|
73
|
+
exactly ``0.0`` would need that round trip to complete inside one
|
|
74
|
+
``perf_counter`` tick (~42 ns here) -- so this is spec conformance and
|
|
75
|
+
hardening rather than a fix for an observed failure.
|
|
76
|
+
|
|
77
|
+
Extracted as a module-level function, mirroring openjd-rs's
|
|
78
|
+
``calculate_adaptive_chunk_size``, so the deferral is unit-testable without
|
|
79
|
+
needing to provoke an unreachable timing condition end to end.
|
|
80
|
+
"""
|
|
81
|
+
if (
|
|
82
|
+
completed_task_count <= 0
|
|
83
|
+
or completed_task_duration <= 0.0
|
|
84
|
+
or not isfinite(completed_task_duration)
|
|
85
|
+
):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
duration_per_task = completed_task_duration / completed_task_count
|
|
89
|
+
adaptive_chunk_size = target_runtime_seconds / duration_per_task
|
|
90
|
+
if completed_task_count < 10 and adaptive_chunk_size > current_chunk_size:
|
|
91
|
+
# When we have data about only a few tasks, gradually blend in the new
|
|
92
|
+
# estimate instead of cutting over immediately.
|
|
93
|
+
adaptive_chunk_size = 0.75 * current_chunk_size + 0.25 * adaptive_chunk_size
|
|
94
|
+
return max(int(adaptive_chunk_size), 1)
|
|
95
|
+
|
|
96
|
+
|
|
52
97
|
class LocalSession:
|
|
53
98
|
"""
|
|
54
99
|
A class to manage a `Session` object from the `sessions` module,
|
|
@@ -102,6 +147,8 @@ class LocalSession:
|
|
|
102
147
|
self._openjd_session = Session(
|
|
103
148
|
session_id=self.session_id,
|
|
104
149
|
job_parameter_values=job_parameter_values,
|
|
150
|
+
# RFC 0007 §7.3.1 (EXPR): seeds the Job.Name template variable.
|
|
151
|
+
job_name=str(job.name),
|
|
105
152
|
path_mapping_rules=self._path_mapping_rules,
|
|
106
153
|
callback=self._action_callback,
|
|
107
154
|
retain_working_dir=retain_working_dir,
|
|
@@ -197,7 +244,14 @@ class LocalSession:
|
|
|
197
244
|
LOG.info("Interruption signal recieved.")
|
|
198
245
|
self.cancel()
|
|
199
246
|
|
|
200
|
-
def run_environment_enters(
|
|
247
|
+
def run_environment_enters(
|
|
248
|
+
self,
|
|
249
|
+
environments: Optional[list[Any]],
|
|
250
|
+
type: EnvironmentType,
|
|
251
|
+
*,
|
|
252
|
+
extra_let_bindings: Optional[list[str]] = None,
|
|
253
|
+
step_name: Optional[str] = None,
|
|
254
|
+
):
|
|
201
255
|
"""Enter one or more environments in the session."""
|
|
202
256
|
if environments is None:
|
|
203
257
|
return
|
|
@@ -211,10 +265,43 @@ class LocalSession:
|
|
|
211
265
|
env_id = f"{type.name} - {env.name}"
|
|
212
266
|
self._action_ended.clear()
|
|
213
267
|
self._current_action = EnterEnvironmentAction(
|
|
214
|
-
session=self._openjd_session,
|
|
268
|
+
session=self._openjd_session,
|
|
269
|
+
environment=env,
|
|
270
|
+
env_id=env_id,
|
|
271
|
+
# RFC 0007: a step's environments see the step-level `let`
|
|
272
|
+
# bindings.
|
|
273
|
+
extra_let_bindings=extra_let_bindings,
|
|
274
|
+
# RFC 0007 §7.3.1 (EXPR): a step's environments see Step.Name.
|
|
275
|
+
# Only step-environment enters carry a step name.
|
|
276
|
+
step_name=step_name,
|
|
215
277
|
)
|
|
216
278
|
self._environments_entered.append((type, env_id))
|
|
217
|
-
|
|
279
|
+
try:
|
|
280
|
+
self._current_action.run()
|
|
281
|
+
except (RuntimeError, ValueError) as exc:
|
|
282
|
+
# Session.enter_environment raises (rather than reporting
|
|
283
|
+
# through the action-status callback) when it rejects the
|
|
284
|
+
# environment up front — e.g. the RFC 0008 "at most one wrap
|
|
285
|
+
# environment" RuntimeError, or a ValueError from the extra
|
|
286
|
+
# `let` bindings — but it can also raise *after* registering
|
|
287
|
+
# the environment (e.g. an environment `variables` expression
|
|
288
|
+
# that fails to evaluate). Only when the session did NOT
|
|
289
|
+
# register the environment may we drop it from our entered
|
|
290
|
+
# list (cleanup must not try to exit it). If the session did
|
|
291
|
+
# register it, it must stay in our list so cleanup exits it —
|
|
292
|
+
# popping it here would skip its onExit and desynchronize us
|
|
293
|
+
# from the session's LIFO exit ordering check, masking the
|
|
294
|
+
# original error with "Must exit Environment X first".
|
|
295
|
+
if env_id not in self._openjd_session.environments_entered:
|
|
296
|
+
self._environments_entered.pop()
|
|
297
|
+
LOG.info(
|
|
298
|
+
msg=f"Open Job Description CLI: ERROR entering environment '{env.name}': {exc}",
|
|
299
|
+
extra={"session_id": self.session_id},
|
|
300
|
+
)
|
|
301
|
+
self.failed = True
|
|
302
|
+
self._failed_action = self._current_action
|
|
303
|
+
self._current_action = None
|
|
304
|
+
raise LocalSessionFailed(self._failed_action) from exc
|
|
218
305
|
self._action_ended.wait()
|
|
219
306
|
if self.failed:
|
|
220
307
|
self._failed_action = self._current_action
|
|
@@ -300,27 +387,28 @@ class LocalSession:
|
|
|
300
387
|
# Estimate a chunk size based on the statistics, and update the iterator. Note that this
|
|
301
388
|
# logic is very simple, providing a good starting point that behaves reasonably for other implementations
|
|
302
389
|
# to follow.
|
|
303
|
-
|
|
304
|
-
|
|
390
|
+
new_chunk_size = _calculate_adaptive_chunk_size(
|
|
391
|
+
current_chunk_size=task_parameters.chunks_default_task_count, # type: ignore
|
|
392
|
+
completed_task_count=completed_task_count,
|
|
393
|
+
completed_task_duration=completed_task_duration,
|
|
394
|
+
target_runtime_seconds=target_runtime_seconds,
|
|
395
|
+
)
|
|
396
|
+
# `None` means there is no measurable sample yet: keep the current chunk
|
|
397
|
+
# size and wait for one. Deliberately not `continue` -- the maximum-task
|
|
398
|
+
# countdown below must still run for this completed chunk.
|
|
305
399
|
if (
|
|
306
|
-
|
|
307
|
-
and
|
|
400
|
+
new_chunk_size is not None
|
|
401
|
+
and new_chunk_size != task_parameters.chunks_default_task_count
|
|
308
402
|
):
|
|
309
|
-
# When we have data about only a few tasks, gradually blend in the new estimate instead of cutting over immediately
|
|
310
|
-
adaptive_chunk_size = (
|
|
311
|
-
0.75 * task_parameters.chunks_default_task_count + 0.25 * adaptive_chunk_size # type: ignore
|
|
312
|
-
)
|
|
313
|
-
adaptive_chunk_size = max(int(adaptive_chunk_size), 1)
|
|
314
|
-
if adaptive_chunk_size != task_parameters.chunks_default_task_count:
|
|
315
403
|
LOG.info(
|
|
316
404
|
msg=f"Open Job Description CLI: Ran {completed_task_count} tasks in {timedelta(seconds=completed_task_duration)}, average {timedelta(seconds=completed_task_duration / completed_task_count)}",
|
|
317
405
|
extra={"session_id": self.session_id},
|
|
318
406
|
)
|
|
319
407
|
LOG.info(
|
|
320
|
-
msg=f"Open Job Description CLI: Adjusting chunk size from {task_parameters.chunks_default_task_count} to {
|
|
408
|
+
msg=f"Open Job Description CLI: Adjusting chunk size from {task_parameters.chunks_default_task_count} to {new_chunk_size}",
|
|
321
409
|
extra={"session_id": self.session_id},
|
|
322
410
|
)
|
|
323
|
-
task_parameters.chunks_default_task_count =
|
|
411
|
+
task_parameters.chunks_default_task_count = new_chunk_size
|
|
324
412
|
|
|
325
413
|
# If a maximum task count was specified, count them down
|
|
326
414
|
if maximum_tasks and maximum_tasks > 0:
|
|
@@ -348,8 +436,22 @@ class LocalSession:
|
|
|
348
436
|
if task_parameters is None:
|
|
349
437
|
task_parameters = StepParameterSpaceIterator(space=step.parameterSpace)
|
|
350
438
|
|
|
351
|
-
# Enter all the step environments
|
|
352
|
-
|
|
439
|
+
# Enter all the step environments. When the step defines step-level
|
|
440
|
+
# `let` bindings (RFC 0007), its environments are entered with them so
|
|
441
|
+
# their variables and actions can reference them.
|
|
442
|
+
# getattr guard: requires an openjd-model with Step.let on the
|
|
443
|
+
# instantiated Job (openjd-model PR #318+); collapse to plain
|
|
444
|
+
# `step.let` once the version pin floor guarantees it.
|
|
445
|
+
step_let_bindings = getattr(step, "let", None)
|
|
446
|
+
self.run_environment_enters(
|
|
447
|
+
step.stepEnvironments,
|
|
448
|
+
EnvironmentType.STEP,
|
|
449
|
+
extra_let_bindings=step_let_bindings or None,
|
|
450
|
+
# RFC 0007 §7.3.1 (EXPR): Step.Name is available to a step's
|
|
451
|
+
# environments (openjd-rs threads the step's resolved symbol
|
|
452
|
+
# table into enter_environment; this is the CLI counterpart).
|
|
453
|
+
step_name=step.name,
|
|
454
|
+
)
|
|
353
455
|
|
|
354
456
|
try:
|
|
355
457
|
# Run the tasks
|
|
@@ -338,6 +338,7 @@ def _run_local_session(
|
|
|
338
338
|
Creates a Session object and listens for log messages to synchronously end the session.
|
|
339
339
|
"""
|
|
340
340
|
|
|
341
|
+
error_message = "Session ended with errors; see Task logs for details"
|
|
341
342
|
try:
|
|
342
343
|
start_seconds = time.perf_counter()
|
|
343
344
|
|
|
@@ -367,6 +368,14 @@ def _run_local_session(
|
|
|
367
368
|
except LocalSessionFailed:
|
|
368
369
|
duration = time.perf_counter() - start_seconds
|
|
369
370
|
session = None
|
|
371
|
+
except (RuntimeError, ValueError) as exc:
|
|
372
|
+
# Exceptions raised by openjd.sessions from within session actions
|
|
373
|
+
# (e.g. the RFC 0008 "at most one wrap environment" RuntimeError)
|
|
374
|
+
# rather than reported through the action-status callback. Report
|
|
375
|
+
# them as a clean error result instead of a raw traceback.
|
|
376
|
+
duration = time.perf_counter() - start_seconds
|
|
377
|
+
session = None
|
|
378
|
+
error_message = f"Session ended with errors: {exc}"
|
|
370
379
|
|
|
371
380
|
preserved_message: str = ""
|
|
372
381
|
if retain_working_dir and session is not None:
|
|
@@ -377,7 +386,7 @@ def _run_local_session(
|
|
|
377
386
|
if session is None or session.failed:
|
|
378
387
|
return OpenJDRunResult(
|
|
379
388
|
status="error",
|
|
380
|
-
message=
|
|
389
|
+
message=error_message + preserved_message,
|
|
381
390
|
job_name=job.name,
|
|
382
391
|
step_name=step_name,
|
|
383
392
|
duration=duration,
|
|
@@ -415,7 +424,11 @@ def do_run(args: Namespace) -> OpenJDCliResult:
|
|
|
415
424
|
filename = Path(env).expanduser()
|
|
416
425
|
try:
|
|
417
426
|
# Raises: RuntimeError, DecodeValidationError
|
|
418
|
-
|
|
427
|
+
# Pass `extensions` so env templates can declare e.g.
|
|
428
|
+
# WRAP_ACTIONS (RFC 0008) — without this they're parsed
|
|
429
|
+
# against the default model surface and reject the
|
|
430
|
+
# extension declaration.
|
|
431
|
+
env_template = read_environment_template(filename, supported_extensions=extensions)
|
|
419
432
|
environments.append(env_template)
|
|
420
433
|
except (RuntimeError, DecodeValidationError) as e:
|
|
421
434
|
return OpenJDCliResult(status="error", message=str(e))
|
|
@@ -543,9 +556,17 @@ def do_run(args: Namespace) -> OpenJDCliResult:
|
|
|
543
556
|
return OpenJDCliResult(status="error", message=str(rte))
|
|
544
557
|
|
|
545
558
|
# Create a RevisionExtensions object with the default specification version and enabled extensions
|
|
546
|
-
# We use the default v2023_09 since that's what we're currently supporting
|
|
559
|
+
# We use the default v2023_09 since that's what we're currently supporting.
|
|
560
|
+
# Extension behaviors activate only when declared by the templates being
|
|
561
|
+
# run (the job template and any external environment templates) — the
|
|
562
|
+
# CLI's --extensions list is what the CLI *accepts*, not what is enabled.
|
|
563
|
+
declared_extensions: list[str] = list(the_job.extensions or [])
|
|
564
|
+
for env_template in environments or []:
|
|
565
|
+
for ext_name in env_template.extensions or []:
|
|
566
|
+
if ext_name not in declared_extensions:
|
|
567
|
+
declared_extensions.append(ext_name)
|
|
547
568
|
revision_extensions = RevisionExtensions(
|
|
548
|
-
spec_rev=the_job.revision, supported_extensions=
|
|
569
|
+
spec_rev=the_job.revision, supported_extensions=declared_extensions
|
|
549
570
|
)
|
|
550
571
|
|
|
551
572
|
return _run_local_session(
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.7.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 6)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
openjd_cli-0.7.4/_version.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools-scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"__version__",
|
|
6
|
-
"__version_tuple__",
|
|
7
|
-
"version",
|
|
8
|
-
"version_tuple",
|
|
9
|
-
"__commit_id__",
|
|
10
|
-
"commit_id",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
|
-
version: str
|
|
25
|
-
__version__: str
|
|
26
|
-
__version_tuple__: VERSION_TUPLE
|
|
27
|
-
version_tuple: VERSION_TUPLE
|
|
28
|
-
commit_id: COMMIT_ID
|
|
29
|
-
__commit_id__: COMMIT_ID
|
|
30
|
-
|
|
31
|
-
__version__ = version = '0.7.4'
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 7, 4)
|
|
33
|
-
|
|
34
|
-
__commit_id__ = commit_id = None
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools-scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"__version__",
|
|
6
|
-
"__version_tuple__",
|
|
7
|
-
"version",
|
|
8
|
-
"version_tuple",
|
|
9
|
-
"__commit_id__",
|
|
10
|
-
"commit_id",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
|
-
version: str
|
|
25
|
-
__version__: str
|
|
26
|
-
__version_tuple__: VERSION_TUPLE
|
|
27
|
-
version_tuple: VERSION_TUPLE
|
|
28
|
-
commit_id: COMMIT_ID
|
|
29
|
-
__commit_id__: COMMIT_ID
|
|
30
|
-
|
|
31
|
-
__version__ = version = '0.7.4'
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 7, 4)
|
|
33
|
-
|
|
34
|
-
__commit_id__ = commit_id = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|