openjd-cli 0.7.6__tar.gz → 0.7.7__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.6 → openjd_cli-0.7.7}/PKG-INFO +3 -3
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/_version.py +2 -2
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/pyproject.toml +12 -5
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_common/__init__.py +9 -2
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_common/_job_from_template.py +24 -6
- openjd_cli-0.7.7/src/openjd/cli/_run/_local_session/_actions.py +174 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_local_session/_session_manager.py +65 -23
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_run_command.py +11 -2
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_summary/_summary_command.py +1 -1
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_version.py +2 -2
- openjd_cli-0.7.6/src/openjd/cli/_run/_local_session/_actions.py +0 -126
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/.gitignore +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/LICENSE +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/NOTICE +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/README.md +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/hatch.toml +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/hatch_version_hook.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/__main__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_check/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_check/_check_command.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_common/_extensions.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_common/_validation_utils.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_create_argparser.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_help_formatter.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_local_session/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_local_session/_logs.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_schema/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_schema/_schema_command.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_summary/__init__.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_summary/_summary_output.py +0 -0
- {openjd_cli-0.7.6 → openjd_cli-0.7.7}/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.7
|
|
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.12,>=0.
|
|
26
|
-
Requires-Dist: openjd-sessions<0.
|
|
25
|
+
Requires-Dist: openjd-model<0.12,>=0.11.4
|
|
26
|
+
Requires-Dist: openjd-sessions<0.13,>=0.12.0
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
## Open Job Description - CLI
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -29,11 +29,18 @@ classifiers = [
|
|
|
29
29
|
"Intended Audience :: End Users/Desktop"
|
|
30
30
|
]
|
|
31
31
|
dependencies = [
|
|
32
|
-
# 0.
|
|
33
|
-
# Session.enter_environment
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
# 0.12.0 is the first release carrying `resolved_symtab` on all three of
|
|
33
|
+
# Session.enter_environment, Session.exit_environment and Session.run_task.
|
|
34
|
+
# It is also the release that removed `extra_let_bindings` from
|
|
35
|
+
# enter_environment: 0.10.11 through 0.11.0 have that keyword and no
|
|
36
|
+
# `resolved_symtab`, so there is no version satisfying both channels and the
|
|
37
|
+
# floor has to move rather than being feature-detected. Below 0.12.0 every
|
|
38
|
+
# call site here raises TypeError on the unexpected `resolved_symtab`.
|
|
39
|
+
"openjd-sessions >= 0.12.0,< 0.13",
|
|
40
|
+
# 0.11.4 is the first release exporting `create_job_with_symbol_tables` and
|
|
41
|
+
# `JobWithSymbolTables`. 0.11.3 and below have `create_job` only, so the
|
|
42
|
+
# step-scope resolved symbol tables cannot be obtained at all.
|
|
43
|
+
"openjd-model >= 0.11.4,< 0.12"
|
|
37
44
|
]
|
|
38
45
|
|
|
39
46
|
[project.urls]
|
|
@@ -4,7 +4,7 @@ from argparse import ArgumentParser, Namespace, _SubParsersAction
|
|
|
4
4
|
from dataclasses import asdict, dataclass
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Callable, Literal
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, Literal
|
|
8
8
|
import json
|
|
9
9
|
import yaml
|
|
10
10
|
import os
|
|
@@ -23,6 +23,10 @@ from ._validation_utils import (
|
|
|
23
23
|
)
|
|
24
24
|
from openjd.model import DecodeValidationError, Job, JobParameterValues, EnvironmentTemplate
|
|
25
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
# Annotations only; see the note in _job_from_template.py.
|
|
28
|
+
from openjd.expr import SerializedSymbolTable
|
|
29
|
+
|
|
26
30
|
__all__ = [
|
|
27
31
|
"add_extensions_argument",
|
|
28
32
|
"get_doc_type",
|
|
@@ -118,7 +122,10 @@ def generate_job(
|
|
|
118
122
|
environments: list[EnvironmentTemplate] = [],
|
|
119
123
|
*,
|
|
120
124
|
supported_extensions: list[str],
|
|
121
|
-
) -> tuple[Job, JobParameterValues]:
|
|
125
|
+
) -> tuple[Job, JobParameterValues, dict[str, "SerializedSymbolTable"]]:
|
|
126
|
+
"""Returns the job, its parameter values, and the per-step resolved symbol
|
|
127
|
+
tables keyed by step name. See :func:`job_from_template` for why the tables
|
|
128
|
+
must be carried alongside the job rather than discarded."""
|
|
122
129
|
try:
|
|
123
130
|
# Raises: RuntimeError, DecodeValidationError
|
|
124
131
|
template = read_job_template(args.path, supported_extensions=supported_extensions)
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import re
|
|
7
|
-
from typing import Optional, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
10
|
from ._validation_utils import get_doc_type
|
|
@@ -15,10 +15,17 @@ from openjd.model import (
|
|
|
15
15
|
Job,
|
|
16
16
|
JobParameterValues,
|
|
17
17
|
JobTemplate,
|
|
18
|
-
|
|
18
|
+
create_job_with_symbol_tables,
|
|
19
19
|
preprocess_job_parameters,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
# Only for annotations: `openjd.expr` is a facade over the native
|
|
24
|
+
# extension, and importing the CLI must not load it. The tables the model
|
|
25
|
+
# hands back are already instances of this type, so nothing here
|
|
26
|
+
# constructs one.
|
|
27
|
+
from openjd.expr import SerializedSymbolTable
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
def get_params_from_file(parameter_string: str) -> Union[dict, list]:
|
|
24
31
|
"""
|
|
@@ -111,10 +118,18 @@ def job_from_template(
|
|
|
111
118
|
parameter_args: list[str] | None,
|
|
112
119
|
job_template_dir: Path,
|
|
113
120
|
current_working_dir: Path,
|
|
114
|
-
) -> tuple[Job, JobParameterValues]:
|
|
121
|
+
) -> tuple[Job, JobParameterValues, dict[str, "SerializedSymbolTable"]]:
|
|
115
122
|
"""
|
|
116
123
|
Given a decoded Job Template and a user-input parameter dictionary,
|
|
117
|
-
generates a Job object
|
|
124
|
+
generates a Job object, the parameter values for running the job, and the
|
|
125
|
+
per-step resolved symbol tables, keyed by step name.
|
|
126
|
+
|
|
127
|
+
A step's template-scope `let` bindings (RFC 0005 §3.6) are evaluated once
|
|
128
|
+
here, at job creation, and their resolved values reach a session only
|
|
129
|
+
through those tables -- neither the model nor the session re-derives them
|
|
130
|
+
from the source expressions. Sessions for a step must therefore be given
|
|
131
|
+
`step_symbol_tables[step.name]`, or the step's `let` produces no bindings
|
|
132
|
+
at all.
|
|
118
133
|
|
|
119
134
|
Raises: RuntimeError if parameters are an unsupported type or don't correspond to the template
|
|
120
135
|
"""
|
|
@@ -132,11 +147,14 @@ def job_from_template(
|
|
|
132
147
|
raise RuntimeError(str(ve))
|
|
133
148
|
|
|
134
149
|
try:
|
|
135
|
-
|
|
150
|
+
# `create_job_with_symbol_tables` returns the same Job that
|
|
151
|
+
# `create_job` does; it additionally returns the symbol tables that
|
|
152
|
+
# instantiation built instead of discarding them.
|
|
153
|
+
created = create_job_with_symbol_tables(
|
|
136
154
|
job_template=template,
|
|
137
155
|
job_parameter_values=parameter_values,
|
|
138
156
|
environment_templates=environments,
|
|
139
157
|
)
|
|
140
|
-
return (job, parameter_values)
|
|
158
|
+
return (created.job, parameter_values, created.step_symbol_tables)
|
|
141
159
|
except DecodeValidationError as dve:
|
|
142
160
|
raise RuntimeError(f"Could not generate Job from template and parameters: {str(dve)}")
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from openjd.model import Step, TaskParameterSet
|
|
7
|
+
from openjd.model.v2023_09 import Environment
|
|
8
|
+
from openjd.sessions import Session
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
# Annotations only: `openjd.expr` is a facade over the native extension and
|
|
12
|
+
# importing the CLI must not load it. These tables are produced by
|
|
13
|
+
# `create_job_with_symbol_tables` and forwarded unchanged.
|
|
14
|
+
from openjd.expr import SerializedSymbolTable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EnvironmentType(str, Enum):
|
|
18
|
+
"""
|
|
19
|
+
The three different types of environment types that can be entered/exited in a session.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
EXTERNAL = "EXTERNAL"
|
|
23
|
+
JOB = "JOB"
|
|
24
|
+
STEP = "STEP"
|
|
25
|
+
ALL = "ALL"
|
|
26
|
+
|
|
27
|
+
def matches(self, other: "EnvironmentType") -> bool:
|
|
28
|
+
"""Environment types match if they are equal, or one of them is ALL."""
|
|
29
|
+
return self == other or self == EnvironmentType.ALL or other == EnvironmentType.ALL
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SessionAction:
|
|
33
|
+
_session: Session
|
|
34
|
+
duration: float
|
|
35
|
+
|
|
36
|
+
def __init__(self, session: Session):
|
|
37
|
+
self._session = session
|
|
38
|
+
|
|
39
|
+
def run(self):
|
|
40
|
+
"""
|
|
41
|
+
Subclasses of `SessionAction` should have
|
|
42
|
+
custom implementations of this depending on their type.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RunTaskAction(SessionAction):
|
|
47
|
+
_step: Step
|
|
48
|
+
_parameters: TaskParameterSet
|
|
49
|
+
_resolved_symtab: Optional["SerializedSymbolTable"]
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
session: Session,
|
|
54
|
+
step: Step,
|
|
55
|
+
parameters: TaskParameterSet,
|
|
56
|
+
resolved_symtab: Optional["SerializedSymbolTable"] = None,
|
|
57
|
+
):
|
|
58
|
+
super(RunTaskAction, self).__init__(session)
|
|
59
|
+
self._step = step
|
|
60
|
+
self._parameters = parameters
|
|
61
|
+
# The step's create-time resolved symbol table
|
|
62
|
+
# (`create_job_with_symbol_tables().step_symbol_tables[step.name]`).
|
|
63
|
+
# It is the only channel for the step's template-scope `let` values:
|
|
64
|
+
# the model does not merge them into `script.let`, and the session does
|
|
65
|
+
# not re-evaluate the source expressions.
|
|
66
|
+
self._resolved_symtab = resolved_symtab
|
|
67
|
+
|
|
68
|
+
def run(self):
|
|
69
|
+
self._session.run_task(
|
|
70
|
+
step_script=self._step.script,
|
|
71
|
+
task_parameter_values=self._parameters,
|
|
72
|
+
# RFC 0008: the step name feeds the WrappedStep.Name template
|
|
73
|
+
# variable inside an active onWrapTaskRun hook.
|
|
74
|
+
step_name=self._step.name,
|
|
75
|
+
resolved_symtab=self._resolved_symtab,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def __str__(self):
|
|
79
|
+
parameters = {name: parameter.value for name, parameter in self._parameters.items()}
|
|
80
|
+
return f"Run Step '{self._step.name}' with Task parameters '{str(parameters)}'"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class EnterEnvironmentAction(SessionAction):
|
|
84
|
+
_environment: Environment
|
|
85
|
+
_id: str
|
|
86
|
+
_resolved_symtab: Optional["SerializedSymbolTable"]
|
|
87
|
+
_step_name: Optional[str]
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
session: Session,
|
|
92
|
+
environment: Environment,
|
|
93
|
+
env_id: str,
|
|
94
|
+
resolved_symtab: Optional["SerializedSymbolTable"] = None,
|
|
95
|
+
step_name: Optional[str] = None,
|
|
96
|
+
):
|
|
97
|
+
super(EnterEnvironmentAction, self).__init__(session)
|
|
98
|
+
self._environment = environment
|
|
99
|
+
self._id = env_id
|
|
100
|
+
# RFC 0005 §3.6: a step's environments are entered with the step's
|
|
101
|
+
# create-time resolved symbol table, which carries the step's
|
|
102
|
+
# template-scope `let` values so their variables/actions can reference
|
|
103
|
+
# them. Only step-environment enters have one; job and external
|
|
104
|
+
# environment enters leave it None.
|
|
105
|
+
self._resolved_symtab = resolved_symtab
|
|
106
|
+
# RFC 0007 §7.3.1 (EXPR): the owning step's name seeds Step.Name for
|
|
107
|
+
# a step environment's `let` bindings, variables, and actions. Only
|
|
108
|
+
# step-environment enters carry a step name; job/external enters
|
|
109
|
+
# leave it None.
|
|
110
|
+
self._step_name = step_name
|
|
111
|
+
|
|
112
|
+
def run(self):
|
|
113
|
+
# `step_name` is omitted rather than passed as None when this enter has
|
|
114
|
+
# no owning step. Passing None would be equivalent today, since
|
|
115
|
+
# enter_environment skips a None `step_name`, but omitting it keeps the
|
|
116
|
+
# call site's intent legible at the boundary and it is what
|
|
117
|
+
# test_localsession_step_env_enter_receives_step_name asserts — that
|
|
118
|
+
# assertion is currently the only check that job and external enters
|
|
119
|
+
# do not seed Step.Name, because a template referencing Step.Name
|
|
120
|
+
# outside a step is rejected by static validation before any session
|
|
121
|
+
# is built, so no loadable template can observe it.
|
|
122
|
+
#
|
|
123
|
+
# `resolved_symtab` is passed unconditionally: None is its documented
|
|
124
|
+
# "no table" value and there is no analogous assertion keyed on its
|
|
125
|
+
# absence.
|
|
126
|
+
optional_kwargs: dict[str, str] = {}
|
|
127
|
+
if self._step_name is not None:
|
|
128
|
+
optional_kwargs["step_name"] = self._step_name
|
|
129
|
+
self._session.enter_environment(
|
|
130
|
+
environment=self._environment,
|
|
131
|
+
identifier=self._id,
|
|
132
|
+
resolved_symtab=self._resolved_symtab,
|
|
133
|
+
**optional_kwargs,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def __str__(self):
|
|
137
|
+
return f"Enter Environment '{self._environment.name}'"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ExitEnvironmentAction(SessionAction):
|
|
141
|
+
_id: str
|
|
142
|
+
_keep_session_running: bool
|
|
143
|
+
_resolved_symtab: Optional["SerializedSymbolTable"]
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
session: Session,
|
|
148
|
+
id: str,
|
|
149
|
+
keep_session_running: bool,
|
|
150
|
+
resolved_symtab: Optional["SerializedSymbolTable"] = None,
|
|
151
|
+
):
|
|
152
|
+
super(ExitEnvironmentAction, self).__init__(session)
|
|
153
|
+
self._id = id
|
|
154
|
+
self._keep_session_running = keep_session_running
|
|
155
|
+
# The same table the environment was entered with, so its onExit
|
|
156
|
+
# resolves in the same scope as its onEnter (what
|
|
157
|
+
# Session.exit_environment documents for this argument).
|
|
158
|
+
#
|
|
159
|
+
# Unlike the worker agent, the CLI holds the table as an object rather
|
|
160
|
+
# than a JSON string served by the service, so there is no parse step
|
|
161
|
+
# here that could fail and no wrapper degrading a parse failure to None
|
|
162
|
+
# to keep teardown unconditional. An environment entered without a
|
|
163
|
+
# table simply exits without one.
|
|
164
|
+
self._resolved_symtab = resolved_symtab
|
|
165
|
+
|
|
166
|
+
def run(self):
|
|
167
|
+
self._session.exit_environment(
|
|
168
|
+
identifier=self._id,
|
|
169
|
+
keep_session_running=self._keep_session_running,
|
|
170
|
+
resolved_symtab=self._resolved_symtab,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __str__(self):
|
|
174
|
+
return f"Exit Environment '{self._id}'"
|
{openjd_cli-0.7.6 → openjd_cli-0.7.7}/src/openjd/cli/_run/_local_session/_session_manager.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from queue import Queue
|
|
4
4
|
from threading import Event
|
|
5
5
|
import time
|
|
6
|
-
from typing import Any, Iterable, Optional, Type
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Iterable, Optional, Type
|
|
7
7
|
from types import FrameType, TracebackType
|
|
8
8
|
from signal import signal, SIGINT, SIGTERM, SIG_DFL
|
|
9
9
|
from itertools import islice
|
|
@@ -39,6 +39,10 @@ from openjd.sessions import (
|
|
|
39
39
|
PathMappingRule,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
# Annotations only; see the note in _actions.py.
|
|
44
|
+
from openjd.expr import SerializedSymbolTable
|
|
45
|
+
|
|
42
46
|
|
|
43
47
|
class LocalSessionFailed(RuntimeError):
|
|
44
48
|
"""
|
|
@@ -118,6 +122,8 @@ class LocalSession:
|
|
|
118
122
|
_path_mapping_rules: Optional[list[PathMappingRule]]
|
|
119
123
|
_environments: Optional[list[Any]]
|
|
120
124
|
_environments_entered: list[tuple[EnvironmentType, str]]
|
|
125
|
+
_step_symbol_tables: dict[str, "SerializedSymbolTable"]
|
|
126
|
+
_entered_env_symtabs: dict[str, "SerializedSymbolTable"]
|
|
121
127
|
_log_handler: LocalSessionLogHandler
|
|
122
128
|
_cleanup_called: bool
|
|
123
129
|
|
|
@@ -127,6 +133,7 @@ class LocalSession:
|
|
|
127
133
|
job: Job,
|
|
128
134
|
job_parameter_values: JobParameterValues,
|
|
129
135
|
session_id: str,
|
|
136
|
+
step_symbol_tables: Optional[dict[str, "SerializedSymbolTable"]] = None,
|
|
130
137
|
timestamp_format: LoggingTimestampFormat = LoggingTimestampFormat.RELATIVE,
|
|
131
138
|
path_mapping_rules: Optional[list[PathMappingRule]] = None,
|
|
132
139
|
environments: Optional[list[Any]] = None,
|
|
@@ -142,6 +149,21 @@ class LocalSession:
|
|
|
142
149
|
self._timestamp_format = timestamp_format
|
|
143
150
|
self._path_mapping_rules = path_mapping_rules
|
|
144
151
|
self._environments = environments
|
|
152
|
+
# The create-time resolved symbol tables from
|
|
153
|
+
# `create_job_with_symbol_tables`, keyed by step name. A step's
|
|
154
|
+
# template-scope `let` (RFC 0005 §3.6) is evaluated once at job
|
|
155
|
+
# creation, and this is the only channel its resolved values have into
|
|
156
|
+
# a session: the model does not fold them into `script.let` and the
|
|
157
|
+
# session does not re-evaluate the source expressions. A caller that
|
|
158
|
+
# omits them gets a session in which every step-level `let` is simply
|
|
159
|
+
# undefined.
|
|
160
|
+
self._step_symbol_tables = step_symbol_tables or {}
|
|
161
|
+
# The table each entered environment was entered with, keyed by
|
|
162
|
+
# environment identifier, so its exit can be given the same one --
|
|
163
|
+
# `Session.exit_environment` documents that as the way an onExit
|
|
164
|
+
# resolves in the same scope as its onEnter. Environments entered
|
|
165
|
+
# without a table are simply absent.
|
|
166
|
+
self._entered_env_symtabs = {}
|
|
145
167
|
|
|
146
168
|
# Create an OpenJD Session
|
|
147
169
|
self._openjd_session = Session(
|
|
@@ -249,7 +271,7 @@ class LocalSession:
|
|
|
249
271
|
environments: Optional[list[Any]],
|
|
250
272
|
type: EnvironmentType,
|
|
251
273
|
*,
|
|
252
|
-
|
|
274
|
+
resolved_symtab: Optional["SerializedSymbolTable"] = None,
|
|
253
275
|
step_name: Optional[str] = None,
|
|
254
276
|
):
|
|
255
277
|
"""Enter one or more environments in the session."""
|
|
@@ -268,27 +290,39 @@ class LocalSession:
|
|
|
268
290
|
session=self._openjd_session,
|
|
269
291
|
environment=env,
|
|
270
292
|
env_id=env_id,
|
|
271
|
-
# RFC
|
|
272
|
-
#
|
|
273
|
-
|
|
293
|
+
# RFC 0005 §3.6: a step's environments see the step's resolved
|
|
294
|
+
# template-scope `let` values through its symbol table.
|
|
295
|
+
resolved_symtab=resolved_symtab,
|
|
274
296
|
# RFC 0007 §7.3.1 (EXPR): a step's environments see Step.Name.
|
|
275
297
|
# Only step-environment enters carry a step name.
|
|
276
298
|
step_name=step_name,
|
|
277
299
|
)
|
|
278
300
|
self._environments_entered.append((type, env_id))
|
|
301
|
+
# Recorded before the enter runs, and deliberately not undone on
|
|
302
|
+
# failure: whether the enter succeeds or not, the exit that follows
|
|
303
|
+
# must be given the table the enter was attempted with.
|
|
304
|
+
if resolved_symtab is not None:
|
|
305
|
+
self._entered_env_symtabs[env_id] = resolved_symtab
|
|
279
306
|
try:
|
|
280
307
|
self._current_action.run()
|
|
281
308
|
except (RuntimeError, ValueError) as exc:
|
|
282
309
|
# Session.enter_environment raises (rather than reporting
|
|
283
310
|
# through the action-status callback) when it rejects the
|
|
284
|
-
# environment up front —
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
311
|
+
# environment up front, before registering it — the RFC 0008
|
|
312
|
+
# "at most one Environment defining wrap hooks" RuntimeError is
|
|
313
|
+
# the trigger that reaches us in practice. Every failure it
|
|
314
|
+
# detects *after* registering goes through
|
|
315
|
+
# _fail_action_before_start() and returns normally instead, so
|
|
316
|
+
# as of openjd-sessions 0.12.0 nothing raises post-registration
|
|
317
|
+
# and the else-branch below is defensive. It is kept because the
|
|
318
|
+
# cost of being wrong is asymmetric: if a future release does
|
|
319
|
+
# raise after registering, dropping the environment from our
|
|
320
|
+
# list would skip its onExit and desynchronize us from the
|
|
321
|
+
# session's LIFO exit-ordering check, masking the original error
|
|
322
|
+
# with "Must exit Environment X first". So: only when the session
|
|
323
|
+
# did NOT register the environment may we drop it from our
|
|
324
|
+
# entered list (cleanup must not try to exit it). If the session
|
|
325
|
+
# did register it, it must stay in our list so cleanup exits it —
|
|
292
326
|
# popping it here would skip its onExit and desynchronize us
|
|
293
327
|
# from the session's LIFO exit ordering check, masking the
|
|
294
328
|
# original error with "Must exit Environment X first".
|
|
@@ -325,7 +359,12 @@ class LocalSession:
|
|
|
325
359
|
prev_action_failed = self.failed
|
|
326
360
|
self._action_ended.clear()
|
|
327
361
|
self._current_action = ExitEnvironmentAction(
|
|
328
|
-
session=self._openjd_session,
|
|
362
|
+
session=self._openjd_session,
|
|
363
|
+
id=env_id,
|
|
364
|
+
keep_session_running=keep_session_running,
|
|
365
|
+
# The same table the enter used, so onExit resolves in the same
|
|
366
|
+
# scope as onEnter. `pop` because an environment is exited once.
|
|
367
|
+
resolved_symtab=self._entered_env_symtabs.pop(env_id, None),
|
|
329
368
|
)
|
|
330
369
|
self._current_action.run()
|
|
331
370
|
self._action_ended.wait()
|
|
@@ -345,7 +384,11 @@ class LocalSession:
|
|
|
345
384
|
|
|
346
385
|
self._action_ended.clear()
|
|
347
386
|
self._current_action = RunTaskAction(
|
|
348
|
-
session=self._openjd_session,
|
|
387
|
+
session=self._openjd_session,
|
|
388
|
+
step=step,
|
|
389
|
+
parameters=parameter_set,
|
|
390
|
+
# RFC 0005 §3.6: the step's resolved template-scope `let` values.
|
|
391
|
+
resolved_symtab=self._step_symbol_tables.get(step.name),
|
|
349
392
|
)
|
|
350
393
|
self._current_action.run()
|
|
351
394
|
self._action_ended.wait()
|
|
@@ -436,17 +479,16 @@ class LocalSession:
|
|
|
436
479
|
if task_parameters is None:
|
|
437
480
|
task_parameters = StepParameterSpaceIterator(space=step.parameterSpace)
|
|
438
481
|
|
|
439
|
-
# Enter all the step environments
|
|
440
|
-
# `let`
|
|
441
|
-
# their variables and actions
|
|
442
|
-
#
|
|
443
|
-
#
|
|
444
|
-
|
|
445
|
-
step_let_bindings = getattr(step, "let", None)
|
|
482
|
+
# Enter all the step environments with the step's create-time resolved
|
|
483
|
+
# symbol table, so a step-level `let` (RFC 0005 §3.6) is visible to
|
|
484
|
+
# their variables and actions. The source expressions are not
|
|
485
|
+
# re-evaluated anywhere downstream, so a missing table here means the
|
|
486
|
+
# step's `let` names are undefined rather than merely stale.
|
|
487
|
+
resolved_symtab = self._step_symbol_tables.get(step.name)
|
|
446
488
|
self.run_environment_enters(
|
|
447
489
|
step.stepEnvironments,
|
|
448
490
|
EnvironmentType.STEP,
|
|
449
|
-
|
|
491
|
+
resolved_symtab=resolved_symtab,
|
|
450
492
|
# RFC 0007 §7.3.1 (EXPR): Step.Name is available to a step's
|
|
451
493
|
# environments (openjd-rs threads the step's resolved symbol
|
|
452
494
|
# table into enter_environment; this is the CLI counterpart).
|
|
@@ -4,7 +4,7 @@ from argparse import ArgumentParser, Namespace
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import json
|
|
7
|
-
from typing import Iterable, Optional
|
|
7
|
+
from typing import TYPE_CHECKING, Iterable, Optional
|
|
8
8
|
import re
|
|
9
9
|
import logging
|
|
10
10
|
import time
|
|
@@ -40,6 +40,10 @@ from openjd.model import (
|
|
|
40
40
|
)
|
|
41
41
|
from openjd.sessions import PathMappingRule, LOG
|
|
42
42
|
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
# Annotations only; see the note in _local_session/_actions.py.
|
|
45
|
+
from openjd.expr import SerializedSymbolTable
|
|
46
|
+
|
|
43
47
|
|
|
44
48
|
@dataclass
|
|
45
49
|
class OpenJDRunResult(OpenJDCliResult):
|
|
@@ -321,6 +325,7 @@ def _run_local_session(
|
|
|
321
325
|
*,
|
|
322
326
|
job: Job,
|
|
323
327
|
job_parameter_values: JobParameterValues,
|
|
328
|
+
step_symbol_tables: Optional[dict[str, "SerializedSymbolTable"]] = None,
|
|
324
329
|
step_list: list[Step],
|
|
325
330
|
selected_step: Optional[Step],
|
|
326
331
|
timestamp_format: LoggingTimestampFormat,
|
|
@@ -346,6 +351,9 @@ def _run_local_session(
|
|
|
346
351
|
with LocalSession(
|
|
347
352
|
job=job,
|
|
348
353
|
job_parameter_values=job_parameter_values,
|
|
354
|
+
# RFC 0005 §3.6: each step's create-time resolved `let` values. The
|
|
355
|
+
# only channel they have into the session.
|
|
356
|
+
step_symbol_tables=step_symbol_tables,
|
|
349
357
|
timestamp_format=timestamp_format,
|
|
350
358
|
session_id="CLI-session",
|
|
351
359
|
path_mapping_rules=path_mapping_rules,
|
|
@@ -464,7 +472,7 @@ def do_run(args: Namespace) -> OpenJDCliResult:
|
|
|
464
472
|
|
|
465
473
|
try:
|
|
466
474
|
# Raises: RuntimeError
|
|
467
|
-
the_job, job_parameter_values = generate_job(
|
|
475
|
+
the_job, job_parameter_values, step_symbol_tables = generate_job(
|
|
468
476
|
args, environments, supported_extensions=extensions
|
|
469
477
|
)
|
|
470
478
|
|
|
@@ -572,6 +580,7 @@ def do_run(args: Namespace) -> OpenJDCliResult:
|
|
|
572
580
|
return _run_local_session(
|
|
573
581
|
job=the_job,
|
|
574
582
|
job_parameter_values=job_parameter_values,
|
|
583
|
+
step_symbol_tables=step_symbol_tables,
|
|
575
584
|
step_list=step_list,
|
|
576
585
|
selected_step=selected_step,
|
|
577
586
|
task_parameter_values=task_parameter_values,
|
|
@@ -35,7 +35,7 @@ def do_summary(args: Namespace) -> OpenJDCliResult:
|
|
|
35
35
|
|
|
36
36
|
try:
|
|
37
37
|
# Raises: RuntimeError
|
|
38
|
-
sample_job, _ = generate_job(args, supported_extensions=extensions)
|
|
38
|
+
sample_job, _, _ = generate_job(args, supported_extensions=extensions)
|
|
39
39
|
except RuntimeError as rte:
|
|
40
40
|
return OpenJDCliResult(status="error", message=str(rte))
|
|
41
41
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
-
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
from openjd.model import Step, TaskParameterSet
|
|
7
|
-
from openjd.model.v2023_09 import Environment
|
|
8
|
-
from openjd.sessions import Session
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class EnvironmentType(str, Enum):
|
|
12
|
-
"""
|
|
13
|
-
The three different types of environment types that can be entered/exited in a session.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
EXTERNAL = "EXTERNAL"
|
|
17
|
-
JOB = "JOB"
|
|
18
|
-
STEP = "STEP"
|
|
19
|
-
ALL = "ALL"
|
|
20
|
-
|
|
21
|
-
def matches(self, other: "EnvironmentType") -> bool:
|
|
22
|
-
"""Environment types match if they are equal, or one of them is ALL."""
|
|
23
|
-
return self == other or self == EnvironmentType.ALL or other == EnvironmentType.ALL
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SessionAction:
|
|
27
|
-
_session: Session
|
|
28
|
-
duration: float
|
|
29
|
-
|
|
30
|
-
def __init__(self, session: Session):
|
|
31
|
-
self._session = session
|
|
32
|
-
|
|
33
|
-
def run(self):
|
|
34
|
-
"""
|
|
35
|
-
Subclasses of `SessionAction` should have
|
|
36
|
-
custom implementations of this depending on their type.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class RunTaskAction(SessionAction):
|
|
41
|
-
_step: Step
|
|
42
|
-
_parameters: TaskParameterSet
|
|
43
|
-
|
|
44
|
-
def __init__(self, session: Session, step: Step, parameters: TaskParameterSet):
|
|
45
|
-
super(RunTaskAction, self).__init__(session)
|
|
46
|
-
self._step = step
|
|
47
|
-
self._parameters = parameters
|
|
48
|
-
|
|
49
|
-
def run(self):
|
|
50
|
-
self._session.run_task(
|
|
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,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def __str__(self):
|
|
59
|
-
parameters = {name: parameter.value for name, parameter in self._parameters.items()}
|
|
60
|
-
return f"Run Step '{self._step.name}' with Task parameters '{str(parameters)}'"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class EnterEnvironmentAction(SessionAction):
|
|
64
|
-
_environment: Environment
|
|
65
|
-
_id: str
|
|
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
|
-
):
|
|
77
|
-
super(EnterEnvironmentAction, self).__init__(session)
|
|
78
|
-
self._environment = environment
|
|
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
|
|
88
|
-
|
|
89
|
-
def run(self):
|
|
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
|
-
)
|
|
106
|
-
|
|
107
|
-
def __str__(self):
|
|
108
|
-
return f"Enter Environment '{self._environment.name}'"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class ExitEnvironmentAction(SessionAction):
|
|
112
|
-
_id: str
|
|
113
|
-
_keep_session_running: bool
|
|
114
|
-
|
|
115
|
-
def __init__(self, session: Session, id: str, keep_session_running: bool):
|
|
116
|
-
super(ExitEnvironmentAction, self).__init__(session)
|
|
117
|
-
self._id = id
|
|
118
|
-
self._keep_session_running = keep_session_running
|
|
119
|
-
|
|
120
|
-
def run(self):
|
|
121
|
-
self._session.exit_environment(
|
|
122
|
-
identifier=self._id, keep_session_running=self._keep_session_running
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
def __str__(self):
|
|
126
|
-
return f"Exit Environment '{self._id}'"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|