torchx-nightly 2025.10.17__py3-none-any.whl → 2025.10.18__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/runner/api.py CHANGED
@@ -427,41 +427,25 @@ class Runner:
427
427
  sched._pre_build_validate(app, scheduler, resolved_cfg)
428
428
 
429
429
  if isinstance(sched, WorkspaceMixin):
430
- for i, role in enumerate(app.roles):
431
- role_workspace = role.workspace
432
-
433
- if i == 0 and workspace:
434
- # NOTE: torchx originally took workspace as a runner arg and only applied the workspace to role[0]
435
- # later, torchx added support for the workspace attr in Role
436
- # for BC, give precedence to the workspace argument over the workspace attr for role[0]
437
- if role_workspace:
438
- logger.info(
439
- f"Using workspace={workspace} over role[{i}].workspace={role_workspace} for role[{i}]={role.name}."
440
- " To use the role's workspace attr pass: --workspace='' from CLI or workspace=None programmatically." # noqa: B950
441
- )
442
- role_workspace = workspace
443
-
444
- if role_workspace:
445
- old_img = role.image
430
+ if workspace:
431
+ # NOTE: torchx originally took workspace as a runner arg and only applied the workspace to role[0]
432
+ # later, torchx added support for the workspace attr in Role
433
+ # for BC, give precedence to the workspace argument over the workspace attr for role[0]
434
+ if app.roles[0].workspace:
446
435
  logger.info(
447
- f"Checking for changes in workspace `{role_workspace}` for role[{i}]={role.name}..."
448
- )
449
- # TODO kiuk@ once we deprecate the `workspace` argument in runner APIs we can simplify the signature of
450
- # build_workspace_and_update_role2() to just taking the role and resolved_cfg
451
- sched.build_workspace_and_update_role2(
452
- role, role_workspace, resolved_cfg
436
+ "Overriding role[%d] (%s) workspace to `%s`"
437
+ "To use the role's workspace attr pass: --workspace='' from CLI or workspace=None programmatically.",
438
+ 0,
439
+ role.name,
440
+ str(app.roles[0].workspace),
453
441
  )
442
+ app.roles[0].workspace = (
443
+ Workspace.from_str(workspace)
444
+ if isinstance(workspace, str)
445
+ else workspace
446
+ )
454
447
 
455
- if old_img != role.image:
456
- logger.info(
457
- f"Built new image `{role.image}` based on original image `{old_img}`"
458
- f" and changes in workspace `{role_workspace}` for role[{i}]={role.name}."
459
- )
460
- else:
461
- logger.info(
462
- f"Reusing original image `{old_img}` for role[{i}]={role.name}."
463
- " Either a patch was built or no changes to workspace was detected."
464
- )
448
+ sched.build_workspaces(app.roles, resolved_cfg)
465
449
 
466
450
  sched._validate(app, scheduler, resolved_cfg)
467
451
  dryrun_info = sched.submit_dryrun(app, resolved_cfg)
torchx/schedulers/api.py CHANGED
@@ -131,7 +131,7 @@ class Scheduler(abc.ABC, Generic[T, A, D]):
131
131
  self,
132
132
  app: A,
133
133
  cfg: T,
134
- workspace: Optional[Union[Workspace, str]] = None,
134
+ workspace: str | Workspace | None = None,
135
135
  ) -> str:
136
136
  """
137
137
  Submits the application to be run by the scheduler.
@@ -145,7 +145,12 @@ class Scheduler(abc.ABC, Generic[T, A, D]):
145
145
  resolved_cfg = self.run_opts().resolve(cfg)
146
146
  if workspace:
147
147
  assert isinstance(self, WorkspaceMixin)
148
- self.build_workspace_and_update_role2(app.roles[0], workspace, resolved_cfg)
148
+
149
+ if isinstance(workspace, str):
150
+ workspace = Workspace.from_str(workspace)
151
+
152
+ app.roles[0].workspace = workspace
153
+ self.build_workspaces(app.roles, resolved_cfg)
149
154
 
150
155
  # pyre-fixme: submit_dryrun takes Generic type for resolved_cfg
151
156
  dryrun_info = self.submit_dryrun(app, resolved_cfg)
torchx/specs/api.py CHANGED
@@ -14,6 +14,7 @@ import logging as logger
14
14
  import os
15
15
  import pathlib
16
16
  import re
17
+ import shutil
17
18
  import typing
18
19
  import warnings
19
20
  from dataclasses import asdict, dataclass, field
@@ -381,6 +382,16 @@ class Workspace:
381
382
  """False if no projects mapping. Lets us use workspace object in an if-statement"""
382
383
  return bool(self.projects)
383
384
 
385
+ def __eq__(self, other: object) -> bool:
386
+ if not isinstance(other, Workspace):
387
+ return False
388
+ return self.projects == other.projects
389
+
390
+ def __hash__(self) -> int:
391
+ # makes it possible to use Workspace as the key in the workspace build cache
392
+ # see WorkspaceMixin.caching_build_workspace_and_update_role
393
+ return hash(frozenset(self.projects.items()))
394
+
384
395
  def is_unmapped_single_project(self) -> bool:
385
396
  """
386
397
  Returns ``True`` if this workspace only has 1 project
@@ -388,6 +399,39 @@ class Workspace:
388
399
  """
389
400
  return len(self.projects) == 1 and not next(iter(self.projects.values()))
390
401
 
402
+ def merge_into(self, outdir: str | pathlib.Path) -> None:
403
+ """
404
+ Copies each project dir of this workspace into the specified ``outdir``.
405
+ Each project dir is copied into ``{outdir}/{target}`` where ``target`` is
406
+ the target mapping of the project dir.
407
+
408
+ For example:
409
+
410
+ .. code-block:: python
411
+ from os.path import expanduser
412
+
413
+ workspace = Workspace(
414
+ projects={
415
+ expanduser("~/workspace/torch"): "torch",
416
+ expanduser("~/workspace/my_project": "")
417
+ }
418
+ )
419
+ workspace.merge_into(expanduser("~/tmp"))
420
+
421
+ Copies:
422
+
423
+ * ``~/workspace/torch/**`` into ``~/tmp/torch/**``
424
+ * ``~/workspace/my_project/**`` into ``~/tmp/**``
425
+
426
+ """
427
+
428
+ for src, dst in self.projects.items():
429
+ dst_path = pathlib.Path(outdir) / dst
430
+ if pathlib.Path(src).is_file():
431
+ shutil.copy2(src, dst_path)
432
+ else: # src is dir
433
+ shutil.copytree(src, dst_path, dirs_exist_ok=True)
434
+
391
435
  @staticmethod
392
436
  def from_str(workspace: str | None) -> "Workspace":
393
437
  import yaml
torchx/workspace/api.py CHANGED
@@ -8,26 +8,17 @@
8
8
 
9
9
  import abc
10
10
  import fnmatch
11
+ import logging
11
12
  import posixpath
12
- import shutil
13
13
  import tempfile
14
14
  import warnings
15
15
  from dataclasses import dataclass
16
- from pathlib import Path
17
- from typing import (
18
- Any,
19
- Dict,
20
- Generic,
21
- Iterable,
22
- Mapping,
23
- Tuple,
24
- TYPE_CHECKING,
25
- TypeVar,
26
- Union,
27
- )
16
+ from typing import Any, Dict, Generic, Iterable, Mapping, Tuple, TYPE_CHECKING, TypeVar
28
17
 
29
18
  from torchx.specs import AppDef, CfgVal, Role, runopts, Workspace
30
19
 
20
+ logger: logging.Logger = logging.getLogger(__name__)
21
+
31
22
  if TYPE_CHECKING:
32
23
  from fsspec import AbstractFileSystem
33
24
 
@@ -113,45 +104,72 @@ class WorkspaceMixin(abc.ABC, Generic[T]):
113
104
  """
114
105
  return runopts()
115
106
 
116
- def build_workspace_and_update_role2(
107
+ def build_workspaces(self, roles: list[Role], cfg: Mapping[str, CfgVal]) -> None:
108
+ """
109
+ NOTE: this method MUTATES the passed roles!
110
+
111
+ Builds the workspaces (if any) for each role and updates the role to reflect the built workspace.
112
+ Typically ``role.image`` is updated with the newly built image that reflects the local workspace.
113
+ Some workspace implementations may add extra environment variables to make it easier for other
114
+ parts of the program to access the workspace. For example a ``WORKSPACE_DIR`` env var may be added
115
+ to ``role.env`` that scripts can use to refert to the workspace directory in the container.
116
+ """
117
+
118
+ build_cache: dict[object, object] = {}
119
+
120
+ for i, role in enumerate(roles):
121
+ if role.workspace:
122
+ old_img = role.image
123
+ self.caching_build_workspace_and_update_role(role, cfg, build_cache)
124
+
125
+ if old_img != role.image:
126
+ logger.info(
127
+ "role[%d]=%s updated with new image to include workspace changes",
128
+ i,
129
+ role.name,
130
+ )
131
+
132
+ def caching_build_workspace_and_update_role(
117
133
  self,
118
134
  role: Role,
119
- workspace: Union[Workspace, str],
120
135
  cfg: Mapping[str, CfgVal],
136
+ build_cache: dict[object, object],
121
137
  ) -> None:
122
138
  """
123
- Same as :py:meth:`build_workspace_and_update_role` but operates
124
- on :py:class:`Workspace` (supports multi-project workspaces)
125
- as well as ``str`` (for backwards compatibility).
139
+ Same as :py:meth:`build_workspace_and_update_role` but takes
140
+ a ``build_cache`` that can be used to cache pointers to build artifacts
141
+ between building workspace for each role.
126
142
 
127
- If ``workspace`` is a ``str`` this method simply calls
143
+ This is useful when an appdef has multiple roles where the image and workspace
144
+ of the roles are the same but other attributes such as entrypoint or args are different.
145
+
146
+ NOTE: ``build_cache``'s lifetime is within :py:meth:`build_workspace_and_update_roles`
147
+ NOTE: the workspace implementation decides what to cache
148
+
149
+ Workspace subclasses should prefer implementing this method over
128
150
  :py:meth:`build_workspace_and_update_role`.
129
151
 
130
- If ``workspace`` is :py:class:`Workspace` then the default
131
- impl copies all the projects into a tmp directory and passes the tmp dir to
132
- :py:meth:`build_workspace_and_update_role`
152
+ The default implementation of this method simply calls the (deprecated) non-caching
153
+ :py:meth:`build_workspace_and_update_role` and deals with multi-dir workspaces by
154
+ merging them into a single tmpdir before passing it down.
133
155
 
134
- Subclasses can override this method to customize multi-project
135
- workspace building logic.
136
156
  """
137
- if isinstance(workspace, Workspace):
138
- if not workspace.is_unmapped_single_project():
139
- with tempfile.TemporaryDirectory(suffix="torchx_workspace_") as outdir:
140
- for src, dst in workspace.projects.items():
141
- dst_path = Path(outdir) / dst
142
- if Path(src).is_file():
143
- shutil.copy2(src, dst_path)
144
- else: # src is dir
145
- shutil.copytree(src, dst_path, dirs_exist_ok=True)
146
-
147
- self.build_workspace_and_update_role(role, outdir, cfg)
148
- return
149
- else: # single project workspace with no target mapping (treat like a str workspace)
150
- workspace = str(workspace)
151
-
152
- self.build_workspace_and_update_role(role, workspace, cfg)
153
157
 
154
- @abc.abstractmethod
158
+ workspace = role.workspace
159
+
160
+ if not workspace:
161
+ return
162
+
163
+ if workspace.is_unmapped_single_project():
164
+ # single-dir workspace with no target map; no need to copy to a tmp dir
165
+ self.build_workspace_and_update_role(role, str(workspace), cfg)
166
+ else:
167
+ # multi-dirs or single-dir with a target map;
168
+ # copy all dirs to a tmp dir and treat the tmp dir as a single-dir workspace
169
+ with tempfile.TemporaryDirectory(suffix="torchx_workspace_") as outdir:
170
+ workspace.merge_into(outdir)
171
+ self.build_workspace_and_update_role(role, outdir, cfg)
172
+
155
173
  def build_workspace_and_update_role(
156
174
  self,
157
175
  role: Role,
@@ -159,6 +177,9 @@ class WorkspaceMixin(abc.ABC, Generic[T]):
159
177
  cfg: Mapping[str, CfgVal],
160
178
  ) -> None:
161
179
  """
180
+ .. note:: DEPRECATED: Workspace subclasses should implement
181
+ :py:meth:`caching_build_workspace_and_update_role` over this method.
182
+
162
183
  Builds the specified ``workspace`` with respect to ``img``
163
184
  and updates the ``role`` to reflect the built workspace artifacts.
164
185
  In the simplest case, this method builds a new image and updates
@@ -167,7 +188,7 @@ class WorkspaceMixin(abc.ABC, Generic[T]):
167
188
 
168
189
  Note: this method mutates the passed ``role``.
169
190
  """
170
- ...
191
+ raise NotImplementedError("implement `caching_build_workspace_and_update_role`")
171
192
 
172
193
  def dryrun_push_images(self, app: AppDef, cfg: Mapping[str, CfgVal]) -> T:
173
194
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: torchx-nightly
3
- Version: 2025.10.17
3
+ Version: 2025.10.18
4
4
  Summary: TorchX SDK and Components
5
5
  Home-page: https://github.com/meta-pytorch/torchx
6
6
  Author: TorchX Devs
@@ -48,7 +48,7 @@ torchx/examples/apps/lightning/profiler.py,sha256=SSSihnwjeUTkBoz0E3qn1b-wbkfUIo
48
48
  torchx/examples/apps/lightning/train.py,sha256=0wvvshGHvZowePB4LfclXwn40X7i9euM0ReETWBcPSo,6253
49
49
  torchx/pipelines/__init__.py,sha256=2MbRVk5xwRjg-d2qPemeXpEhDsocMQumPQ53lsesZAI,606
50
50
  torchx/runner/__init__.py,sha256=x8Sz7s_tLxPgJgvWIhK4ju9BNZU61uBFywGwDY6CqJs,315
51
- torchx/runner/api.py,sha256=jxtgOl7nNOqpzG-sjUJngXhIOachqaVfKu9rF8YqHWI,31271
51
+ torchx/runner/api.py,sha256=s-Fh9FJoZ9JiEFXyGVF0vdJuXMQ-Wa2v4yLjvWJKBX8,30134
52
52
  torchx/runner/config.py,sha256=SaKOB50d79WaMFPWK8CC4as6UaNFaRGhrBkfajq3KC4,18311
53
53
  torchx/runner/events/__init__.py,sha256=cMiNjnr4eUNQ2Nxxtu4nsvN5lu56b-a6nJ-ct3i7DQk,5536
54
54
  torchx/runner/events/api.py,sha256=bvxKBAYK8LzbrBNaNLgL1x0aivtfANmWo1EMGOrSR8k,2668
@@ -57,7 +57,7 @@ torchx/runtime/__init__.py,sha256=Wxje2BryzeQneFu5r6P9JJiEKG-_C9W1CcZ_JNrKT6g,59
57
57
  torchx/runtime/tracking/__init__.py,sha256=dYnAPnrXYREfPXkpHhdOFkcYIODWEbA13PdD-wLQYBo,3055
58
58
  torchx/runtime/tracking/api.py,sha256=SmUQyUKZqG3KlAhT7CJOGqRz1O274E4m63wQeOVq3CU,5472
59
59
  torchx/schedulers/__init__.py,sha256=FQN9boQM4mwOD3sK9LZ3GBgw-gJ7Vx4MFj6z6ATQIrc,2211
60
- torchx/schedulers/api.py,sha256=5Amli1httEl82XebAqd8vl3dM8zMKwYfRgfd0mEq3is,14538
60
+ torchx/schedulers/api.py,sha256=smoUv1ocfqsBRmesXbz9i1F86zBOixZ8QHxYmI_MzgQ,14649
61
61
  torchx/schedulers/aws_batch_scheduler.py,sha256=-HpjNVhSFBDxZo3cebK-3YEguB49dxoaud2gz30cAVM,29437
62
62
  torchx/schedulers/aws_sagemaker_scheduler.py,sha256=flN8GumKE2Dz4X_foAt6Jnvt-ZVojWs6pcyrHwB0hz0,20921
63
63
  torchx/schedulers/devices.py,sha256=RjVcu22ZRl_9OKtOtmA1A3vNXgu2qD6A9ST0L0Hsg4I,1734
@@ -70,7 +70,7 @@ torchx/schedulers/lsf_scheduler.py,sha256=YS6Yel8tXJqLPxbcGz95lZG2nCi36AQXdNDyuB
70
70
  torchx/schedulers/slurm_scheduler.py,sha256=vypGaCZe61bkyNkqRlK4Iwmk_NaAUQi-DsspaWd6BZw,31873
71
71
  torchx/schedulers/streams.py,sha256=8_SLezgnWgfv_zXUsJCUM34-h2dtv25NmZuxEwkzmxw,2007
72
72
  torchx/specs/__init__.py,sha256=SXS4r_roOkbbAL-p7EY5fl5ou-AG7S9Ck-zKtRBdHOk,6760
73
- torchx/specs/api.py,sha256=mRX6sqBQOpEVuyR3M-tK3LqSGNNVzqoOSIWM6FolqZQ,51118
73
+ torchx/specs/api.py,sha256=6_Q5SOUqfMRqQdnlvem36dBusHpynp4PCa9uTNwvzwo,52630
74
74
  torchx/specs/builders.py,sha256=Ye3of4MupJ-da8vLaX6_-nzGo_FRw1BFpYsX6dAZCNk,13730
75
75
  torchx/specs/file_linter.py,sha256=z0c4mKJv47BWiPaWCdUM0A8kHwnj4b1s7oTmESuD9Tc,14407
76
76
  torchx/specs/finder.py,sha256=gWQNEFrLYqrZoI0gMMhQ70YAC4sxqS0ZFpoWAmcVi44,17438
@@ -99,12 +99,12 @@ torchx/util/shlex.py,sha256=eXEKu8KC3zIcd8tEy9_s8Ds5oma8BORr-0VGWNpG2dk,463
99
99
  torchx/util/strings.py,sha256=7Ef1loz2IYMrzeJ6Lewywi5cBIc3X3g7lSPbT1Tn_z4,664
100
100
  torchx/util/types.py,sha256=E9dxAWQnsJkIDuHtg-poeOJ4etucSI_xP_Z5kNJX8uI,9229
101
101
  torchx/workspace/__init__.py,sha256=FqN8AN4VhR1C_SBY10MggQvNZmyanbbuPuE-JCjkyUY,798
102
- torchx/workspace/api.py,sha256=h2SaC-pYPBLuo3XtkXJ0APMoro-C-ry7KucI7r3EUf4,8753
102
+ torchx/workspace/api.py,sha256=UESQ4qgxXjsb6Y1wP9OGv2ixaFgaTs3SqghmNuOJIZM,10235
103
103
  torchx/workspace/dir_workspace.py,sha256=npNW_IjUZm_yS5r-8hrRkH46ndDd9a_eApT64m1S1T4,2268
104
104
  torchx/workspace/docker_workspace.py,sha256=PFu2KQNVC-0p2aKJ-W_BKA9ZOmXdCY2ABEkCExp3udQ,10269
105
- torchx_nightly-2025.10.17.dist-info/LICENSE,sha256=WVHfXhFC0Ia8LTKt_nJVYobdqTJVg_4J3Crrfm2A8KQ,1721
106
- torchx_nightly-2025.10.17.dist-info/METADATA,sha256=qg3D7hiG2TX50v4beQLMAQ8dRszPGKUVYke16hnu87Q,5046
107
- torchx_nightly-2025.10.17.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
108
- torchx_nightly-2025.10.17.dist-info/entry_points.txt,sha256=T328AMXeKI3JZnnxfkEew2ZcMN1oQDtkXjMz7lkV-P4,169
109
- torchx_nightly-2025.10.17.dist-info/top_level.txt,sha256=pxew3bc2gsiViS0zADs0jb6kC5v8o_Yy_85fhHj_J1A,7
110
- torchx_nightly-2025.10.17.dist-info/RECORD,,
105
+ torchx_nightly-2025.10.18.dist-info/LICENSE,sha256=WVHfXhFC0Ia8LTKt_nJVYobdqTJVg_4J3Crrfm2A8KQ,1721
106
+ torchx_nightly-2025.10.18.dist-info/METADATA,sha256=uFXR5-5aPu-A24rIhwXi5QhcJUfQEWX0Vz5Ohb66JcA,5046
107
+ torchx_nightly-2025.10.18.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
108
+ torchx_nightly-2025.10.18.dist-info/entry_points.txt,sha256=T328AMXeKI3JZnnxfkEew2ZcMN1oQDtkXjMz7lkV-P4,169
109
+ torchx_nightly-2025.10.18.dist-info/top_level.txt,sha256=pxew3bc2gsiViS0zADs0jb6kC5v8o_Yy_85fhHj_J1A,7
110
+ torchx_nightly-2025.10.18.dist-info/RECORD,,