torchx-nightly 2025.9.2__py3-none-any.whl → 2025.9.4__py3-none-any.whl

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.

Potentially problematic release.


This version of torchx-nightly might be problematic. Click here for more details.

torchx/cli/cmd_run.py CHANGED
@@ -7,16 +7,17 @@
7
7
  # pyre-strict
8
8
 
9
9
  import argparse
10
+ import json
10
11
  import logging
11
12
  import os
12
13
  import sys
13
14
  import threading
14
15
  from collections import Counter
15
- from dataclasses import asdict
16
+ from dataclasses import asdict, dataclass, field, fields, MISSING as DATACLASS_MISSING
16
17
  from itertools import groupby
17
18
  from pathlib import Path
18
19
  from pprint import pformat
19
- from typing import Dict, List, Optional, Tuple
20
+ from typing import Any, Dict, List, Optional, Tuple
20
21
 
21
22
  import torchx.specs as specs
22
23
  from torchx.cli.argparse_util import ArgOnceAction, torchxconfig_run
@@ -25,6 +26,7 @@ from torchx.cli.cmd_log import get_logs
25
26
  from torchx.runner import config, get_runner, Runner
26
27
  from torchx.runner.config import load_sections
27
28
  from torchx.schedulers import get_default_scheduler_name, get_scheduler_factories
29
+ from torchx.specs import CfgVal
28
30
  from torchx.specs.finder import (
29
31
  _Component,
30
32
  ComponentNotFoundException,
@@ -40,10 +42,81 @@ MISSING_COMPONENT_ERROR_MSG = (
40
42
  "missing component name, either provide it from the CLI or in .torchxconfig"
41
43
  )
42
44
 
45
+ LOCAL_SCHEDULER_WARNING_MSG = (
46
+ "`local` scheduler is deprecated and will be"
47
+ " removed in the near future,"
48
+ " please use other variants of the local scheduler"
49
+ " (e.g. `local_cwd`)"
50
+ )
43
51
 
44
52
  logger: logging.Logger = logging.getLogger(__name__)
45
53
 
46
54
 
55
+ @dataclass
56
+ class TorchXRunArgs:
57
+ component_name: str
58
+ scheduler: str
59
+ scheduler_args: Dict[str, Any]
60
+ scheduler_cfg: Dict[str, CfgVal] = field(default_factory=dict)
61
+ dryrun: bool = False
62
+ wait: bool = False
63
+ log: bool = False
64
+ workspace: str = ""
65
+ parent_run_id: Optional[str] = None
66
+ tee_logs: bool = False
67
+ component_args: Dict[str, Any] = field(default_factory=dict)
68
+ component_args_str: List[str] = field(default_factory=list)
69
+
70
+
71
+ def torchx_run_args_from_json(json_data: Dict[str, Any]) -> TorchXRunArgs:
72
+ all_fields = [f.name for f in fields(TorchXRunArgs)]
73
+ required_fields = {
74
+ f.name
75
+ for f in fields(TorchXRunArgs)
76
+ if f.default is DATACLASS_MISSING and f.default_factory is DATACLASS_MISSING
77
+ }
78
+ missing_fields = required_fields - json_data.keys()
79
+ if missing_fields:
80
+ raise ValueError(
81
+ f"The following required fields are missing: {', '.join(missing_fields)}"
82
+ )
83
+
84
+ # Fail if there are fields that aren't part of the run command
85
+ filtered_json_data = {k: v for k, v in json_data.items() if k in all_fields}
86
+ extra_fields = set(json_data.keys()) - set(all_fields)
87
+ if extra_fields:
88
+ raise ValueError(
89
+ f"The following fields are not part of the run command: {', '.join(extra_fields)}.",
90
+ "Please check your JSON and try launching again.",
91
+ )
92
+
93
+ torchx_args = TorchXRunArgs(**filtered_json_data)
94
+ if torchx_args.workspace == "":
95
+ torchx_args.workspace = f"file://{Path.cwd()}"
96
+ return torchx_args
97
+
98
+
99
+ def torchx_run_args_from_argparse(
100
+ args: argparse.Namespace,
101
+ component_name: str,
102
+ component_args: List[str],
103
+ scheduler_cfg: Dict[str, CfgVal],
104
+ ) -> TorchXRunArgs:
105
+ return TorchXRunArgs(
106
+ component_name=component_name,
107
+ scheduler=args.scheduler,
108
+ scheduler_args={},
109
+ scheduler_cfg=scheduler_cfg,
110
+ dryrun=args.dryrun,
111
+ wait=args.wait,
112
+ log=args.log,
113
+ workspace=args.workspace,
114
+ parent_run_id=args.parent_run_id,
115
+ tee_logs=args.tee_logs,
116
+ component_args_str=component_args,
117
+ )
118
+
119
+
47
120
  def _parse_component_name_and_args(
48
121
  component_name_and_args: List[str],
49
122
  subparser: argparse.ArgumentParser,
@@ -193,35 +266,35 @@ class CmdRun(SubCommand):
193
266
  default=False,
194
267
  help="Add additional prefix to log lines to indicate which replica is printing the log",
195
268
  )
269
+ subparser.add_argument(
270
+ "--stdin",
271
+ action="store_true",
272
+ default=False,
273
+ help="Read JSON input from stdin to parse into torchx run args and run the component.",
274
+ )
196
275
  subparser.add_argument(
197
276
  "component_name_and_args",
198
277
  nargs=argparse.REMAINDER,
199
278
  )
200
279
 
201
- def _run(self, runner: Runner, args: argparse.Namespace) -> None:
280
+ def _run_inner(self, runner: Runner, args: TorchXRunArgs) -> None:
202
281
  if args.scheduler == "local":
203
- logger.warning(
204
- "`local` scheduler is deprecated and will be"
205
- " removed in the near future,"
206
- " please use other variants of the local scheduler"
207
- " (e.g. `local_cwd`)"
208
- )
282
+ logger.warning(LOCAL_SCHEDULER_WARNING_MSG)
209
283
 
210
- cfg = dict(runner.cfg_from_str(args.scheduler, args.scheduler_args))
211
- config.apply(scheduler=args.scheduler, cfg=cfg)
212
-
213
- component, component_args = _parse_component_name_and_args(
214
- args.component_name_and_args,
215
- none_throws(self._subparser),
284
+ config.apply(scheduler=args.scheduler, cfg=args.scheduler_cfg)
285
+ component_args = (
286
+ args.component_args_str
287
+ if args.component_args_str != []
288
+ else args.component_args
216
289
  )
217
290
  try:
218
291
  if args.dryrun:
219
292
  dryrun_info = runner.dryrun_component(
220
- component,
293
+ args.component_name,
221
294
  component_args,
222
295
  args.scheduler,
223
296
  workspace=args.workspace,
224
- cfg=cfg,
297
+ cfg=args.scheduler_cfg,
225
298
  parent_run_id=args.parent_run_id,
226
299
  )
227
300
  print(
@@ -232,11 +305,11 @@ class CmdRun(SubCommand):
232
305
  print("\n=== SCHEDULER REQUEST ===\n" f"{dryrun_info}")
233
306
  else:
234
307
  app_handle = runner.run_component(
235
- component,
308
+ args.component_name,
236
309
  component_args,
237
310
  args.scheduler,
238
311
  workspace=args.workspace,
239
- cfg=cfg,
312
+ cfg=args.scheduler_cfg,
240
313
  parent_run_id=args.parent_run_id,
241
314
  )
242
315
  # DO NOT delete this line. It is used by slurm tests to retrieve the app id
@@ -257,7 +330,9 @@ class CmdRun(SubCommand):
257
330
  )
258
331
 
259
332
  except (ComponentValidationException, ComponentNotFoundException) as e:
260
- error_msg = f"\nFailed to run component `{component}` got errors: \n {e}"
333
+ error_msg = (
334
+ f"\nFailed to run component `{args.component_name}` got errors: \n {e}"
335
+ )
261
336
  logger.error(error_msg)
262
337
  sys.exit(1)
263
338
  except specs.InvalidRunConfigException as e:
@@ -272,6 +347,86 @@ class CmdRun(SubCommand):
272
347
  print(error_msg % (e, args.scheduler, args.scheduler), file=sys.stderr)
273
348
  sys.exit(1)
274
349
 
350
+ def _run_from_cli_args(self, runner: Runner, args: argparse.Namespace) -> None:
351
+ scheduler_opts = runner.scheduler_run_opts(args.scheduler)
352
+ cfg = scheduler_opts.cfg_from_str(args.scheduler_args)
353
+
354
+ component, component_args = _parse_component_name_and_args(
355
+ args.component_name_and_args,
356
+ none_throws(self._subparser),
357
+ )
358
+ torchx_run_args = torchx_run_args_from_argparse(
359
+ args, component, component_args, cfg
360
+ )
361
+ self._run_inner(runner, torchx_run_args)
362
+
363
+ def _run_from_stdin_args(self, runner: Runner, stdin_data: Dict[str, Any]) -> None:
364
+ torchx_run_args = torchx_run_args_from_json(stdin_data)
365
+ scheduler_opts = runner.scheduler_run_opts(torchx_run_args.scheduler)
366
+ cfg = scheduler_opts.cfg_from_json_repr(
367
+ json.dumps(torchx_run_args.scheduler_args)
368
+ )
369
+ torchx_run_args.scheduler_cfg = cfg
370
+ self._run_inner(runner, torchx_run_args)
371
+
372
+ def torchx_json_from_stdin(self) -> Dict[str, Any]:
373
+ try:
374
+ stdin_data_json = json.load(sys.stdin)
375
+ if not isinstance(stdin_data_json, dict):
376
+ logger.error(
377
+ "Invalid JSON input for `torchx run` command. Expected a dictionary."
378
+ )
379
+ sys.exit(1)
380
+ return stdin_data_json
381
+ except (json.JSONDecodeError, EOFError):
382
+ logger.error(
383
+ "Unable to parse JSON input for `torchx run` command, please make sure it's a valid JSON input."
384
+ )
385
+ sys.exit(1)
386
+
387
+ def verify_no_extra_args(self, args: argparse.Namespace) -> None:
388
+ """
389
+ Verifies that only --stdin was provided when using stdin mode.
390
+ """
391
+ if not args.stdin:
392
+ return
393
+
394
+ subparser = none_throws(self._subparser)
395
+ conflicting_args = []
396
+
397
+ # Check each argument against its default value
398
+ for action in subparser._actions:
399
+ if action.dest == "stdin": # Skip stdin itself
400
+ continue
401
+ if action.dest == "help": # Skip help
402
+ continue
403
+
404
+ current_value = getattr(args, action.dest, None)
405
+ default_value = action.default
406
+
407
+ # For arguments that differ from default
408
+ if current_value != default_value:
409
+ # Handle special cases where non-default doesn't mean explicitly set
410
+ if action.dest == "component_name_and_args" and current_value == []:
411
+ continue # Empty list is still default
412
+ print(f"*********\n {default_value} = {current_value}")
413
+ conflicting_args.append(f"--{action.dest.replace('_', '-')}")
414
+
415
+ if conflicting_args:
416
+ subparser.error(
417
+ f"Cannot specify {', '.join(conflicting_args)} when using --stdin. "
418
+ "All configuration should be provided in JSON input."
419
+ )
420
+
421
+ def _run(self, runner: Runner, args: argparse.Namespace) -> None:
422
+ # Verify no conflicting arguments when using to loop over the stdin
423
+ self.verify_no_extra_args(args)
424
+ if args.stdin:
425
+ stdin_data_json = self.torchx_json_from_stdin()
426
+ self._run_from_stdin_args(runner, stdin_data_json)
427
+ else:
428
+ self._run_from_cli_args(runner, args)
429
+
275
430
  def run(self, args: argparse.Namespace) -> None:
276
431
  os.environ["TORCHX_CONTEXT_NAME"] = os.getenv("TORCHX_CONTEXT_NAME", "cli_run")
277
432
  component_defaults = load_sections(prefix="component")
torchx/runner/api.py CHANGED
@@ -25,6 +25,7 @@ from typing import (
25
25
  Type,
26
26
  TYPE_CHECKING,
27
27
  TypeVar,
28
+ Union,
28
29
  )
29
30
 
30
31
  from torchx.runner.events import log_event
@@ -164,22 +165,10 @@ class Runner:
164
165
  for scheduler in self._scheduler_instances.values():
165
166
  scheduler.close()
166
167
 
167
- def build_standalone_workspace(
168
- self,
169
- workspace_builder: WorkspaceBuilder[S, T],
170
- sync: bool = True,
171
- ) -> PkgInfo[S]:
172
- """
173
- Build a standalone workspace for the given role.
174
- This method is used to build a workspace for a role independent of the scheduler and
175
- also enables asynchronous workspace building using the Role overrides.
176
- """
177
- return workspace_builder.build_workspace(sync)
178
-
179
168
  def run_component(
180
169
  self,
181
170
  component: str,
182
- component_args: List[str],
171
+ component_args: Union[list[str], dict[str, Any]],
183
172
  scheduler: str,
184
173
  cfg: Optional[Mapping[str, CfgVal]] = None,
185
174
  workspace: Optional[str] = None,
@@ -238,7 +227,7 @@ class Runner:
238
227
  def dryrun_component(
239
228
  self,
240
229
  component: str,
241
- component_args: List[str],
230
+ component_args: Union[list[str], dict[str, Any]],
242
231
  scheduler: str,
243
232
  cfg: Optional[Mapping[str, CfgVal]] = None,
244
233
  workspace: Optional[str] = None,
@@ -249,10 +238,13 @@ class Runner:
249
238
  component, but just returns what "would" have run.
250
239
  """
251
240
  component_def = get_component(component)
241
+ args_from_cli = component_args if isinstance(component_args, list) else []
242
+ args_from_json = component_args if isinstance(component_args, dict) else {}
252
243
  app = materialize_appdef(
253
244
  component_def.fn,
254
- component_args,
245
+ args_from_cli,
255
246
  self._component_defaults.get(component, None),
247
+ args_from_json,
256
248
  )
257
249
  return self.dryrun(
258
250
  app,
torchx/specs/__init__.py CHANGED
@@ -225,5 +225,8 @@ __all__ = [
225
225
  "make_app_handle",
226
226
  "materialize_appdef",
227
227
  "parse_mounts",
228
+ "torchx_run_args_from_argparse",
229
+ "torchx_run_args_from_json",
230
+ "TorchXRunArgs",
228
231
  "ALL",
229
232
  ]
torchx/specs/builders.py CHANGED
@@ -213,7 +213,11 @@ def component_args_from_str(
213
213
  arg_value = getattr(parsed_args, param_name)
214
214
  parameter_type = parameter.annotation
215
215
  parameter_type = decode_optional(parameter_type)
216
- arg_value = decode(arg_value, parameter_type)
216
+ if (
217
+ parameter_type != arg_value.__class__
218
+ and parameter.kind != inspect.Parameter.VAR_POSITIONAL
219
+ ):
220
+ arg_value = decode(arg_value, parameter_type)
217
221
  if parameter.kind == inspect.Parameter.VAR_POSITIONAL:
218
222
  var_args = arg_value
219
223
  elif parameter.kind == inspect.Parameter.KEYWORD_ONLY:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: torchx-nightly
3
- Version: 2025.9.2
3
+ Version: 2025.9.4
4
4
  Summary: TorchX SDK and Components
5
5
  Home-page: https://github.com/pytorch/torchx
6
6
  Author: TorchX Devs
@@ -16,7 +16,7 @@ torchx/cli/cmd_configure.py,sha256=1kTv0qbsbV44So74plAySwWu56pQrqjhfW_kbfdC3Rw,1
16
16
  torchx/cli/cmd_describe.py,sha256=E5disbHoKTsqYKp2s3DaFW9GDLCCOgdOc3pQoHKoyCs,1283
17
17
  torchx/cli/cmd_list.py,sha256=4Y1ZOq-kqJbztoBt56hAW_InJEaJuDAjpKWgMhBw4II,1507
18
18
  torchx/cli/cmd_log.py,sha256=v-EZYUDOcG95rEgTnrsmPJMUyxM9Mk8YFAJtUxtgViE,5475
19
- torchx/cli/cmd_run.py,sha256=dFVzlVBSxqBfTRz80VL8R_r2GVayKZ4xDVBQLu275M0,12285
19
+ torchx/cli/cmd_run.py,sha256=BtLl-FBVnf1B5VjyCMFgJrL20-PQN9k9akN6Oa3RGgg,18126
20
20
  torchx/cli/cmd_runopts.py,sha256=NWZiP8XpQjfTDJgays2c6MgL_8wxFoeDge6NstaZdKk,1302
21
21
  torchx/cli/cmd_status.py,sha256=22IAEmKs0qkG6kJi83u9dRX2Q-ntT7yehVx7FxtY-vQ,2114
22
22
  torchx/cli/cmd_tracker.py,sha256=RfLxE4Cq1wfk7k051RtZ8RPJp0pEKSCa3KmTeRs3LF8,5218
@@ -56,7 +56,7 @@ torchx/pipelines/kfp/__init__.py,sha256=8iJ8lql_fxwuk9VCYSxXnX6tPL228fB5mDZpOs-k
56
56
  torchx/pipelines/kfp/adapter.py,sha256=5GeHULjb1kxG6wJtYVLpNkgdzUi4iYEaR42VFOwT6fY,9045
57
57
  torchx/pipelines/kfp/version.py,sha256=mYBxd6bm4MeR34D--xo-JLQ9wHeAl_ZQLwbItCf9tr0,539
58
58
  torchx/runner/__init__.py,sha256=x8Sz7s_tLxPgJgvWIhK4ju9BNZU61uBFywGwDY6CqJs,315
59
- torchx/runner/api.py,sha256=UzQP25TVKcQoK5RUjf0AaH4dfXKFWSbE1YOSXH2Uo_w,30766
59
+ torchx/runner/api.py,sha256=CJmTjoV2kB0FVqeE9B-bYaFyiMuQsZCY32kY13CIk6I,30559
60
60
  torchx/runner/config.py,sha256=CBuYQCBj52fs4NclxJbdx5xtDdgNnfDd7XSdHPE1IGo,18267
61
61
  torchx/runner/events/__init__.py,sha256=1_y0bojXl3FL0zlAj7BI4Dg5cXKXUmaa2jZbVH0EDUA,5268
62
62
  torchx/runner/events/api.py,sha256=pPLfowWTXtN_XcrEDNI45pE6Ijvdc_Gdxq76RduqgGE,2664
@@ -82,9 +82,9 @@ torchx/schedulers/streams.py,sha256=8_SLezgnWgfv_zXUsJCUM34-h2dtv25NmZuxEwkzmxw,
82
82
  torchx/schedulers/ray/__init__.py,sha256=fE0IHi1JJpxsNVBNzWNee2thrNXFFRhY94c80RxNSIE,231
83
83
  torchx/schedulers/ray/ray_common.py,sha256=pyNYFvTKVwdjDAeCBNbPwAWwVNmlLOJWExfn90XY8u8,610
84
84
  torchx/schedulers/ray/ray_driver.py,sha256=RdaCLfth16ky-5PDVOWRe_RuheWJu9xufWux2F9T7iw,12302
85
- torchx/specs/__init__.py,sha256=c2ALDbqHIhNBhrYxwXXURRwu1Rg5jcwukWF8emEO1Bk,6347
85
+ torchx/specs/__init__.py,sha256=Gw_2actqR_oWFtxEkGXCxGk_yrWK5JDZzwysyyqmXao,6438
86
86
  torchx/specs/api.py,sha256=wkhHOxeWH_tFO3npKqPhNg4VX2NH5gPIFEylkPBo3AU,41315
87
- torchx/specs/builders.py,sha256=R60CdQvHhpGJ83_cwnJDVa_WAf1E_0Sqm92htJKFZsQ,13665
87
+ torchx/specs/builders.py,sha256=aozVl4q3h0mY5DDJCY1M1CyLC9SW66KJy8JIih8bZJo,13810
88
88
  torchx/specs/file_linter.py,sha256=QCwob5STTBuy8RsxaevTI-Dk6R8siDJn81LyaOwazes,12333
89
89
  torchx/specs/finder.py,sha256=lMCS1hUR-x7j4sAjfJZnKFeS1RlkFTeJtit4EL_ErIo,18182
90
90
  torchx/specs/named_resources_aws.py,sha256=ISjHtifRJqB8u7PeAMiyLyO_S0WCaZiK-CFF3qe6JDU,11415
@@ -115,9 +115,9 @@ torchx/workspace/__init__.py,sha256=FqN8AN4VhR1C_SBY10MggQvNZmyanbbuPuE-JCjkyUY,
115
115
  torchx/workspace/api.py,sha256=PtDkGTC5lX03pRoYpuMz2KCmM1ZOycRP1UknqvNb97Y,6341
116
116
  torchx/workspace/dir_workspace.py,sha256=npNW_IjUZm_yS5r-8hrRkH46ndDd9a_eApT64m1S1T4,2268
117
117
  torchx/workspace/docker_workspace.py,sha256=PFu2KQNVC-0p2aKJ-W_BKA9ZOmXdCY2ABEkCExp3udQ,10269
118
- torchx_nightly-2025.9.2.dist-info/LICENSE,sha256=WVHfXhFC0Ia8LTKt_nJVYobdqTJVg_4J3Crrfm2A8KQ,1721
119
- torchx_nightly-2025.9.2.dist-info/METADATA,sha256=PoRTkx3TroTJyt0d6VVrd67Csx9euRsRag76hLzJ-uI,6103
120
- torchx_nightly-2025.9.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
121
- torchx_nightly-2025.9.2.dist-info/entry_points.txt,sha256=T328AMXeKI3JZnnxfkEew2ZcMN1oQDtkXjMz7lkV-P4,169
122
- torchx_nightly-2025.9.2.dist-info/top_level.txt,sha256=pxew3bc2gsiViS0zADs0jb6kC5v8o_Yy_85fhHj_J1A,7
123
- torchx_nightly-2025.9.2.dist-info/RECORD,,
118
+ torchx_nightly-2025.9.4.dist-info/LICENSE,sha256=WVHfXhFC0Ia8LTKt_nJVYobdqTJVg_4J3Crrfm2A8KQ,1721
119
+ torchx_nightly-2025.9.4.dist-info/METADATA,sha256=JbjR1Xl4KB9UbvnpQyD7EXmVS9xI376IdmQh8L7Hdqc,6103
120
+ torchx_nightly-2025.9.4.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
121
+ torchx_nightly-2025.9.4.dist-info/entry_points.txt,sha256=T328AMXeKI3JZnnxfkEew2ZcMN1oQDtkXjMz7lkV-P4,169
122
+ torchx_nightly-2025.9.4.dist-info/top_level.txt,sha256=pxew3bc2gsiViS0zADs0jb6kC5v8o_Yy_85fhHj_J1A,7
123
+ torchx_nightly-2025.9.4.dist-info/RECORD,,