Skip to content

Commit 4a4f43a

Browse files
Refine SmartSimEntity Interface (#688)
Refactor SmartSimEntity class, remove ExecutableProtocol protocol, Application becomes subclass of ABC SmartSImEntity. [ reviewed by @MattToast @mellis13 ] [ committed by @amandarichardsonn ]
1 parent 5611a16 commit 4a4f43a

22 files changed

+119
-120
lines changed

smartsim/_core/control/job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def error_report(self) -> str:
299299
warning += f"Job status at failure: {self.status} \n"
300300
warning += f"Launcher status at failure: {self.raw_status} \n"
301301
warning += f"Job returncode: {self.returncode} \n"
302-
warning += f"Error and output file located at: {self.entity.path}"
302+
# warning += f"Error and output file located at: {self.entity.path}"
303303
return warning
304304

305305
def __str__(self) -> str:

smartsim/_core/dispatch.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
if t.TYPE_CHECKING:
4141
from smartsim._core.arguments.shell import ShellLaunchArguments
42-
from smartsim._core.utils.launcher import ExecutableProtocol, LauncherProtocol
42+
from smartsim._core.utils.launcher import LauncherProtocol
4343
from smartsim.experiment import Experiment
4444
from smartsim.settings.arguments import LaunchArguments
4545

@@ -66,7 +66,7 @@
6666
FormatterType: TypeAlias = t.Callable[
6767
[
6868
_DispatchableT,
69-
"ExecutableProtocol",
69+
t.Sequence[str],
7070
WorkingDirectory,
7171
EnvironMappingType,
7272
pathlib.Path,
@@ -78,7 +78,7 @@
7878
capable of being launched by a launcher.
7979
"""
8080
_LaunchConfigType: TypeAlias = """_LauncherAdapter[
81-
ExecutableProtocol,
81+
t.Sequence[str],
8282
WorkingDirectory,
8383
EnvironMappingType,
8484
pathlib.Path,
@@ -271,7 +271,7 @@ def create_adapter_from_launcher(
271271
)
272272

273273
def format_(
274-
exe: ExecutableProtocol,
274+
exe: t.Sequence[str],
275275
path: pathlib.Path,
276276
env: EnvironMappingType,
277277
out: pathlib.Path,

smartsim/_core/launcher/dragon/dragonLauncher.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
if t.TYPE_CHECKING:
6565
from typing_extensions import Self
6666

67-
from smartsim._core.utils.launcher import ExecutableProtocol
6867
from smartsim.experiment import Experiment
6968

7069

@@ -369,7 +368,7 @@ def _assert_schema_type(obj: object, typ: t.Type[_SchemaT], /) -> _SchemaT:
369368

370369
def _as_run_request_args_and_policy(
371370
run_req_args: DragonLaunchArguments,
372-
exe: ExecutableProtocol,
371+
exe: t.Sequence[str],
373372
path: str | os.PathLike[str],
374373
env: t.Mapping[str, str | None],
375374
stdout_path: pathlib.Path,
@@ -379,7 +378,7 @@ def _as_run_request_args_and_policy(
379378
# FIXME: This type is 100% unacceptable, but I don't want to spend too much
380379
# time on fixing the dragon launcher API. Something that we need to
381380
# revisit in the future though.
382-
exe_, *args = exe.as_program_arguments()
381+
exe_, *args = exe
383382
run_args = dict[str, "int | str | float | None"](run_req_args._launch_args)
384383
policy = DragonRunPolicy.from_run_args(run_args)
385384
return (

smartsim/_core/shell/shellLauncher.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from smartsim._core.arguments.shell import ShellLaunchArguments
3838
from smartsim._core.dispatch import EnvironMappingType, FormatterType, WorkingDirectory
3939
from smartsim._core.utils import helpers
40-
from smartsim._core.utils.launcher import ExecutableProtocol, create_job_id
40+
from smartsim._core.utils.launcher import create_job_id
4141
from smartsim.error import errors
4242
from smartsim.log import get_logger
4343
from smartsim.settings.arguments.launchArguments import LaunchArguments
@@ -94,7 +94,7 @@ def make_shell_format_fn(
9494

9595
def impl(
9696
args: ShellLaunchArguments,
97-
exe: ExecutableProtocol,
97+
exe: t.Sequence[str],
9898
path: WorkingDirectory,
9999
env: EnvironMappingType,
100100
stdout_path: pathlib.Path,
@@ -105,10 +105,10 @@ def impl(
105105
run_command,
106106
*(args.format_launch_args() or ()),
107107
"--",
108-
*exe.as_program_arguments(),
108+
*exe,
109109
)
110110
if run_command is not None
111-
else exe.as_program_arguments()
111+
else exe
112112
)
113113
# pylint: disable-next=consider-using-with
114114
return ShellLauncherCommand(

smartsim/_core/utils/launcher.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ def create_job_id() -> LaunchedJobID:
4646
return LaunchedJobID(str(uuid.uuid4()))
4747

4848

49-
class ExecutableProtocol(t.Protocol):
50-
def as_program_arguments(self) -> t.Sequence[str]: ...
51-
52-
5349
class LauncherProtocol(collections.abc.Hashable, t.Protocol[_T_contra]):
5450
"""The protocol defining a launcher that can be used by a SmartSim
5551
experiment

smartsim/entity/application.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ def exe_args(self, value: t.Union[str, t.Sequence[str], None]) -> None:
112112
"""
113113
self._exe_args = self._build_exe_args(value)
114114

115+
def add_exe_args(self, args: t.Union[str, t.List[str], None]) -> None:
116+
"""Add executable arguments to executable
117+
118+
:param args: executable arguments
119+
"""
120+
args = self._build_exe_args(args)
121+
self._exe_args.extend(args)
122+
115123
@property
116124
def files(self) -> t.Optional[EntityFiles]:
117125
"""Return files to be copied, symlinked, and/or configured prior to
@@ -178,13 +186,12 @@ def key_prefixing_enabled(self, value: bool) -> None:
178186
"""
179187
self.key_prefixing_enabled = copy.deepcopy(value)
180188

181-
def add_exe_args(self, args: t.Union[str, t.List[str], None]) -> None:
182-
"""Add executable arguments to executable
189+
def as_executable_sequence(self) -> t.Sequence[str]:
190+
"""Converts the executable and its arguments into a sequence of program arguments.
183191
184-
:param args: executable arguments
192+
:return: a sequence of strings representing the executable and its arguments
185193
"""
186-
args = self._build_exe_args(args)
187-
self._exe_args.extend(args)
194+
return [self.exe, *self.exe_args]
188195

189196
def attach_generator_files(
190197
self,
@@ -242,27 +249,6 @@ def attached_files_table(self) -> str:
242249
return "No file attached to this application."
243250
return str(self.files)
244251

245-
def print_attached_files(self) -> None:
246-
"""Print a table of the attached files on std out"""
247-
print(self.attached_files_table)
248-
249-
def __str__(self) -> str: # pragma: no cover
250-
exe_args_str = "\n".join(self.exe_args)
251-
entities_str = "\n".join(str(entity) for entity in self.incoming_entities)
252-
return textwrap.dedent(f"""\
253-
Name: {self.name}
254-
Type: {self.type}
255-
Executable:
256-
{self.exe}
257-
Executable Arguments:
258-
{exe_args_str}
259-
Entity Files: {self.files}
260-
File Parameters: {self.file_parameters}
261-
Incoming Entities:
262-
{entities_str}
263-
Key Prefixing Enabled: {self.key_prefixing_enabled}
264-
""")
265-
266252
@staticmethod
267253
def _build_exe_args(exe_args: t.Union[str, t.Sequence[str], None]) -> t.List[str]:
268254
"""Check and convert exe_args input to a desired collection format
@@ -286,3 +272,24 @@ def _build_exe_args(exe_args: t.Union[str, t.Sequence[str], None]) -> t.List[str
286272
return exe_args.split()
287273

288274
return exe_args
275+
276+
def print_attached_files(self) -> None:
277+
"""Print a table of the attached files on std out"""
278+
print(self.attached_files_table)
279+
280+
def __str__(self) -> str: # pragma: no cover
281+
exe_args_str = "\n".join(self.exe_args)
282+
entities_str = "\n".join(str(entity) for entity in self.incoming_entities)
283+
return textwrap.dedent(f"""\
284+
Name: {self.name}
285+
Type: {self.type}
286+
Executable:
287+
{self.exe}
288+
Executable Arguments:
289+
{exe_args_str}
290+
Entity Files: {self.files}
291+
File Parameters: {self.file_parameters}
292+
Incoming Entities:
293+
{entities_str}
294+
Key Prefixing Enabled: {self.key_prefixing_enabled}
295+
""")

smartsim/entity/entity.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@
3434
if t.TYPE_CHECKING:
3535
from smartsim.launchable.job import Job
3636
from smartsim.settings.launchSettings import LaunchSettings
37-
from smartsim.types import TODO
38-
39-
RunSettings = TODO
4037

4138

4239
class TelemetryConfiguration:
@@ -97,8 +94,11 @@ def _on_disable(self) -> None:
9794
to perform actions when attempts to change configuration are made"""
9895

9996

100-
class SmartSimEntity:
101-
def __init__(self, name: str) -> None:
97+
class SmartSimEntity(abc.ABC):
98+
def __init__(
99+
self,
100+
name: str,
101+
) -> None:
102102
"""Initialize a SmartSim entity.
103103
104104
Each entity must have a name and path. All entities within SmartSim
@@ -107,17 +107,20 @@ def __init__(self, name: str) -> None:
107107
:param name: Name of the entity
108108
"""
109109
self.name = name
110+
"""The name of the application"""
111+
112+
@abc.abstractmethod
113+
def as_executable_sequence(self) -> t.Sequence[str]:
114+
"""Converts the executable and its arguments into a sequence of program arguments.
115+
116+
:return: a sequence of strings representing the executable and its arguments
117+
"""
110118

111119
@property
112120
def type(self) -> str:
113121
"""Return the name of the class"""
114122
return type(self).__name__
115123

116-
def set_path(self, path: str) -> None:
117-
if not isinstance(path, str):
118-
raise TypeError("path argument must be a string")
119-
self.path = path
120-
121124
def __repr__(self) -> str:
122125
return self.name
123126

smartsim/entity/entityList.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@
4141
class EntitySequence(t.Generic[_T_co]):
4242
"""Abstract class for containers for SmartSimEntities"""
4343

44-
def __init__(self, name: str, path: str, **kwargs: t.Any) -> None:
44+
def __init__(self, name: str, **kwargs: t.Any) -> None:
4545
self.name: str = name
46-
self.path: str = path
4746

4847
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
4948
# WARNING: This class cannot be made truly covariant until the
@@ -105,11 +104,6 @@ def type(self) -> str:
105104
"""Return the name of the class"""
106105
return type(self).__name__
107106

108-
def set_path(self, new_path: str) -> None:
109-
self.path = new_path
110-
for entity in self.entities:
111-
entity.path = new_path
112-
113107
def __getitem__(self, name: str) -> t.Optional[_T_co]:
114108
for entity in self.entities:
115109
if entity.name == name:
@@ -127,8 +121,8 @@ def __len__(self) -> int:
127121
class EntityList(EntitySequence[_T]):
128122
"""An invariant subclass of an ``EntitySequence`` with mutable containers"""
129123

130-
def __init__(self, name: str, path: str, **kwargs: t.Any) -> None:
131-
super().__init__(name=name, path=path, **kwargs)
124+
def __init__(self, name: str, **kwargs: t.Any) -> None:
125+
super().__init__(name=name, **kwargs)
132126
# Change container types to be invariant ``list``s
133127
self.entities: t.List[_T] = list(self.entities)
134128
self._fs_models: t.List["smartsim.entity.FSModel"] = list(self._fs_models)

smartsim/experiment.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
from .log import ctx_exp_path, get_logger, method_contextualizer
5050

5151
if t.TYPE_CHECKING:
52-
from smartsim._core.utils.launcher import ExecutableProtocol
5352
from smartsim.launchable.job import Job
5453
from smartsim.types import LaunchedJobID
5554

@@ -189,12 +188,7 @@ def _dispatch(
189188
def execute_dispatch(generator: Generator, job: Job, idx: int) -> LaunchedJobID:
190189
args = job.launch_settings.launch_args
191190
env = job.launch_settings.env_vars
192-
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
193-
# FIXME: Remove this cast after `SmartSimEntity` conforms to
194-
# protocol. For now, live with the "dangerous" type cast
195-
# ---------------------------------------------------------------------
196-
exe = t.cast("ExecutableProtocol", job.entity)
197-
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
191+
exe = job.entity.as_executable_sequence()
198192
dispatch = dispatcher.get_dispatch(args)
199193
try:
200194
# Check to see if one of the existing launchers can be

smartsim/settings/arguments/launch/lsf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from smartsim._core.arguments.shell import ShellLaunchArguments
3434
from smartsim._core.dispatch import EnvironMappingType, dispatch
3535
from smartsim._core.shell.shellLauncher import ShellLauncher, ShellLauncherCommand
36-
from smartsim._core.utils.launcher import ExecutableProtocol
3736
from smartsim.log import get_logger
3837

3938
from ...common import set_check_input
@@ -44,7 +43,7 @@
4443

4544
def _as_jsrun_command(
4645
args: ShellLaunchArguments,
47-
exe: ExecutableProtocol,
46+
exe: t.Sequence[str],
4847
path: pathlib.Path,
4948
env: EnvironMappingType,
5049
stdout_path: pathlib.Path,
@@ -56,7 +55,7 @@ def _as_jsrun_command(
5655
f"--stdio_stdout={stdout_path}",
5756
f"--stdio_stderr={stderr_path}",
5857
"--",
59-
*exe.as_program_arguments(),
58+
*exe,
6059
)
6160
return ShellLauncherCommand(
6261
env, path, subprocess.DEVNULL, subprocess.DEVNULL, command_tuple

0 commit comments

Comments
 (0)