dstack 0.19.11rc2__py3-none-any.whl → 0.19.12rc1__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 dstack might be problematic. Click here for more details.

@@ -84,6 +84,8 @@ class OfferCommand(APIBaseCommand):
84
84
  job_plan = run_plan.job_plans[0]
85
85
 
86
86
  if args.format == "json":
87
+ # FIXME: Should use effective_run_spec from run_plan,
88
+ # since the spec can be changed by the server and plugins
87
89
  output = {
88
90
  "project": run_plan.project_name,
89
91
  "user": run_plan.user,
@@ -105,7 +105,7 @@ class BaseRunConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
105
105
  changed_fields = []
106
106
  if run_plan.action == ApplyAction.UPDATE:
107
107
  diff = diff_models(
108
- run_plan.run_spec.configuration,
108
+ run_plan.get_effective_run_spec().configuration,
109
109
  run_plan.current_resource.run_spec.configuration,
110
110
  )
111
111
  changed_fields = list(diff.keys())
@@ -2,13 +2,18 @@ import tarfile
2
2
  from pathlib import Path
3
3
  from typing import BinaryIO, Optional
4
4
 
5
+ import ignore
6
+ import ignore.overrides
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from dstack._internal.core.models.repos.base import BaseRepoInfo, Repo
10
+ from dstack._internal.utils.common import sizeof_fmt
8
11
  from dstack._internal.utils.hash import get_sha256, slugify
9
- from dstack._internal.utils.ignore import GitIgnore
12
+ from dstack._internal.utils.logging import get_logger
10
13
  from dstack._internal.utils.path import PathLike
11
14
 
15
+ logger = get_logger(__name__)
16
+
12
17
 
13
18
  class LocalRepoInfo(BaseRepoInfo):
14
19
  repo_type: Literal["local"] = "local"
@@ -69,22 +74,23 @@ class LocalRepo(Repo):
69
74
  self.run_repo_data = repo_data
70
75
 
71
76
  def write_code_file(self, fp: BinaryIO) -> str:
77
+ repo_path = Path(self.run_repo_data.repo_dir)
72
78
  with tarfile.TarFile(mode="w", fileobj=fp) as t:
73
- t.add(
74
- self.run_repo_data.repo_dir,
75
- arcname="",
76
- filter=TarIgnore(self.run_repo_data.repo_dir, globs=[".git"]),
77
- )
79
+ for entry in (
80
+ ignore.WalkBuilder(repo_path)
81
+ .overrides(ignore.overrides.OverrideBuilder(repo_path).add("!/.git/").build())
82
+ .hidden(False) # do not ignore files that start with a dot
83
+ .require_git(False) # respect git ignore rules even if not a git repo
84
+ .add_custom_ignore_filename(".dstackignore")
85
+ .build()
86
+ ):
87
+ path = entry.path().relative_to(repo_path.absolute())
88
+ if path != Path("."):
89
+ t.add(path, recursive=False)
90
+ logger.debug("Code file size: %s", sizeof_fmt(fp.tell()))
78
91
  return get_sha256(fp)
79
92
 
80
93
  def get_repo_info(self) -> LocalRepoInfo:
81
94
  return LocalRepoInfo(
82
95
  repo_dir=self.run_repo_data.repo_dir,
83
96
  )
84
-
85
-
86
- class TarIgnore(GitIgnore):
87
- def __call__(self, tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
88
- if self.ignore(tarinfo.path):
89
- return None
90
- return tarinfo
@@ -237,7 +237,7 @@ async def get_plan(
237
237
  ) -> FleetPlan:
238
238
  # Spec must be copied by parsing to calculate merged_profile
239
239
  effective_spec = FleetSpec.parse_obj(spec.dict())
240
- effective_spec = apply_plugin_policies(
240
+ effective_spec = await apply_plugin_policies(
241
241
  user=user.name,
242
242
  project=project.name,
243
243
  spec=effective_spec,
@@ -342,7 +342,7 @@ async def create_fleet(
342
342
  spec: FleetSpec,
343
343
  ) -> Fleet:
344
344
  # Spec must be copied by parsing to calculate merged_profile
345
- spec = apply_plugin_policies(
345
+ spec = await apply_plugin_policies(
346
346
  user=user.name,
347
347
  project=project.name,
348
348
  spec=spec,
@@ -140,7 +140,7 @@ async def create_gateway(
140
140
  project: ProjectModel,
141
141
  configuration: GatewayConfiguration,
142
142
  ) -> Gateway:
143
- spec = apply_plugin_policies(
143
+ spec = await apply_plugin_policies(
144
144
  user=user.name,
145
145
  project=project.name,
146
146
  # Create pseudo spec until the gateway API is updated to accept spec
@@ -5,6 +5,7 @@ from typing import Dict
5
5
  from backports.entry_points_selectable import entry_points # backport for Python 3.9
6
6
 
7
7
  from dstack._internal.core.errors import ServerClientError
8
+ from dstack._internal.utils.common import run_async
8
9
  from dstack._internal.utils.logging import get_logger
9
10
  from dstack.plugins import ApplyPolicy, ApplySpec, Plugin
10
11
 
@@ -91,11 +92,11 @@ def load_plugins(enabled_plugins: list[str]):
91
92
  logger.warning("Enabled plugins not found: %s", plugins_to_load)
92
93
 
93
94
 
94
- def apply_plugin_policies(user: str, project: str, spec: ApplySpec) -> ApplySpec:
95
+ async def apply_plugin_policies(user: str, project: str, spec: ApplySpec) -> ApplySpec:
95
96
  policies = _get_apply_policies()
96
97
  for policy in policies:
97
98
  try:
98
- spec = policy.on_apply(user=user, project=project, spec=spec)
99
+ spec = await run_async(policy.on_apply, user=user, project=project, spec=spec)
99
100
  except ValueError as e:
100
101
  msg = None
101
102
  if len(e.args) > 0:
@@ -283,7 +283,7 @@ async def get_plan(
283
283
  ) -> RunPlan:
284
284
  # Spec must be copied by parsing to calculate merged_profile
285
285
  effective_run_spec = RunSpec.parse_obj(run_spec.dict())
286
- effective_run_spec = apply_plugin_policies(
286
+ effective_run_spec = await apply_plugin_policies(
287
287
  user=user.name,
288
288
  project=project.name,
289
289
  spec=effective_run_spec,
@@ -382,7 +382,7 @@ async def apply_plan(
382
382
  force: bool,
383
383
  ) -> Run:
384
384
  run_spec = plan.run_spec
385
- run_spec = apply_plugin_policies(
385
+ run_spec = await apply_plugin_policies(
386
386
  user=user.name,
387
387
  project=project.name,
388
388
  spec=run_spec,
@@ -205,7 +205,7 @@ async def create_volume(
205
205
  user: UserModel,
206
206
  configuration: VolumeConfiguration,
207
207
  ) -> Volume:
208
- spec = apply_plugin_policies(
208
+ spec = await apply_plugin_policies(
209
209
  user=user.name,
210
210
  project=project.name,
211
211
  # Create pseudo spec until the volume API is updated to accept spec
dstack/version.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__ = "0.19.11rc2"
1
+ __version__ = "0.19.12rc1"
2
2
  __is_release__ = True
3
3
  base_image = "0.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dstack
3
- Version: 0.19.11rc2
3
+ Version: 0.19.12rc1
4
4
  Summary: dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises.
5
5
  Project-URL: Homepage, https://dstack.ai
6
6
  Project-URL: Source, https://github.com/dstackai/dstack
@@ -22,6 +22,7 @@ Requires-Dist: cursor
22
22
  Requires-Dist: filelock
23
23
  Requires-Dist: gitpython
24
24
  Requires-Dist: gpuhunt==0.1.6
25
+ Requires-Dist: ignore-python>=0.2.0
25
26
  Requires-Dist: jsonschema
26
27
  Requires-Dist: packaging
27
28
  Requires-Dist: paramiko>=3.2.0
@@ -1,5 +1,5 @@
1
1
  dstack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- dstack/version.py,sha256=IwrQj83vfz3Q3lIltKTGrhKlC1D7da7-iXWp5LG38Nw,68
2
+ dstack/version.py,sha256=hwGn7URuuYv71W9BadPvs-Et3ttZHW-XD5QkVNM2wew,68
3
3
  dstack/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  dstack/_internal/compat.py,sha256=bF9U9fTMfL8UVhCouedoUSTYFl7UAOiU0WXrnRoByxw,40
5
5
  dstack/_internal/settings.py,sha256=GOqfcLEONWC1hGU36IYPuOhJXP_qC3Y6d2SQge_NdpY,953
@@ -16,7 +16,7 @@ dstack/_internal/cli/commands/gateway.py,sha256=DcD6P_MvXbSL9aXkLX9hgGYSAzARjgY6
16
16
  dstack/_internal/cli/commands/init.py,sha256=bLhSlViNWtjflB6xNq_PuCR2o2A06h222luh1NeUgVA,1169
17
17
  dstack/_internal/cli/commands/logs.py,sha256=o8ehPAKM12Xn9thg2jjnYdr7_wKqF-00ziVry8IVVwE,1528
18
18
  dstack/_internal/cli/commands/metrics.py,sha256=ZS_dHD4_YK135J09xKpVTY_3CXzhj8QhDo18Ail9Pas,5862
19
- dstack/_internal/cli/commands/offer.py,sha256=rf10T9ohEC77GmZexdqF_RBCQkLYzVSAwTzZlxukNYM,4085
19
+ dstack/_internal/cli/commands/offer.py,sha256=6gem4Wz6WJmHur9-eiC65JwqObrGIAybROCAp5pr59k,4221
20
20
  dstack/_internal/cli/commands/project.py,sha256=V8gJwQWQGvGKQJPUyce_M7Ij_B7bOxYzo2IQgkLLHFE,6151
21
21
  dstack/_internal/cli/commands/ps.py,sha256=5RU4mcL4-FT4PcXktfKb4htCOPqia7mIuVGgfK7x9lY,1894
22
22
  dstack/_internal/cli/commands/server.py,sha256=4K0tp_tIUStCEMpWrUkRLwe27IgAt99RPpk2H1AWnXk,2817
@@ -32,7 +32,7 @@ dstack/_internal/cli/services/configurators/__init__.py,sha256=z94VPBFqybP8Zpwy3
32
32
  dstack/_internal/cli/services/configurators/base.py,sha256=bGfde2zoma28lLE8MUACO4-NKT1CdJJQJoXrzjpz0mQ,3360
33
33
  dstack/_internal/cli/services/configurators/fleet.py,sha256=jm4tNH6QQVplLdboCTlvRYUee3nZ0UYb_qLTrvtYVYM,14049
34
34
  dstack/_internal/cli/services/configurators/gateway.py,sha256=czB2s89s7IowOmWnpDwWErPAUlW3FvFMizImhrkQiBM,8927
35
- dstack/_internal/cli/services/configurators/run.py,sha256=nXNjFrM5YT6RFqPXJQa4MOiEsG6IFiANyGKP-PXILdc,25518
35
+ dstack/_internal/cli/services/configurators/run.py,sha256=PvaetsuW6p5rM-nH06uIQR7xXL4GDnbr2O8Sh9r0Dcw,25534
36
36
  dstack/_internal/cli/services/configurators/volume.py,sha256=riMXLQbgvHIIFwLKdHfad-_0iE9wE3G_rUmXU5P3ZS8,8519
37
37
  dstack/_internal/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  dstack/_internal/cli/utils/common.py,sha256=rfmzqrsgR3rXW3wj0vxDdvrhUUg2aIy4A6E9MZbd55g,1763
@@ -178,7 +178,7 @@ dstack/_internal/core/models/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
178
178
  dstack/_internal/core/models/backends/base.py,sha256=Fbhs90NBMceXhjORwj7c2_pVriapCGSUMBFwuR-wRIg,1091
179
179
  dstack/_internal/core/models/repos/__init__.py,sha256=7Qo1QgJ852LklUuM-mlCNFodp_XrQ4iqV7uRPiX_qm0,885
180
180
  dstack/_internal/core/models/repos/base.py,sha256=nErlSR3AbG9fwDE5vuJK5DIrP8JB3fG8a7mXqosJ9gY,846
181
- dstack/_internal/core/models/repos/local.py,sha256=DmW_e6qOWctZwuEpCqME-JpxYHTgH5I1gcK7zjSMfao,2394
181
+ dstack/_internal/core/models/repos/local.py,sha256=ET-b6UsncxPGeDUH97hOCr3k6j3SnL8SX34geqQDSl8,2848
182
182
  dstack/_internal/core/models/repos/remote.py,sha256=r50v39KHhoVMfdnIHe58VJEi2Abm-37Y1ttXigdjc9o,11553
183
183
  dstack/_internal/core/models/repos/virtual.py,sha256=8g4hGSQ6Z8oDKoF6cj2souJ2A5vRop5OyFCQ5z84rw4,2582
184
184
  dstack/_internal/core/services/__init__.py,sha256=UVVcj26WYVgo7jHzD07sNRqSOO7zA3QzQv-9LnCDqR4,415
@@ -372,7 +372,7 @@ dstack/_internal/server/security/permissions.py,sha256=FJ_8YPhjmebA4jQjtQoAGEaj1
372
372
  dstack/_internal/server/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
373
373
  dstack/_internal/server/services/config.py,sha256=yo8njslwfS7_blhbPPhOtzCMyg8N_mmFSw5aPvirSzw,10691
374
374
  dstack/_internal/server/services/docker.py,sha256=zAvjFHxIP03Td92NzbGEScz0piLjloE60tQ7vz0AeCA,5328
375
- dstack/_internal/server/services/fleets.py,sha256=5BQ3DYBx0XWfcJJjaOemNoYPkvlkNsZ_uqA01YjjKxY,26587
375
+ dstack/_internal/server/services/fleets.py,sha256=9UL9mHo0IJhYrxiFHQUaLUA61PAQQ8gGwYNHpknlVqU,26599
376
376
  dstack/_internal/server/services/instances.py,sha256=4feu9RfixOIVsLoFPHiYYUe0L6sf3v3h1asTBSF5cyc,18686
377
377
  dstack/_internal/server/services/locking.py,sha256=7JUgNSplKRx7dxC4LIpmWw81agUtslEDTeDiNMPbAVg,3013
378
378
  dstack/_internal/server/services/logging.py,sha256=Nu1628kW2hqB__N0Eyr07wGWjVWxfyJnczonTJ72kSM,417
@@ -380,15 +380,15 @@ dstack/_internal/server/services/metrics.py,sha256=jKLy1jSCVR_crqVu_CmsOMbvMkucW
380
380
  dstack/_internal/server/services/offers.py,sha256=4hhqLzmo14_IY84FSDG59Sib1DrmTAOk-_q8M4aYoLU,7737
381
381
  dstack/_internal/server/services/permissions.py,sha256=l7Ngdelmn65vjw13NcOdaC6lBYMRuSw6FbHzYwdK3nE,1005
382
382
  dstack/_internal/server/services/placement.py,sha256=U-7z4kJ6ZyrCXzkDychWoOhniz19ooFwiNI2InoTLls,2420
383
- dstack/_internal/server/services/plugins.py,sha256=6UeOF65IA7OzLTBiUmZO0g-07aMRh_J2npEfcpz6C0Q,3750
383
+ dstack/_internal/server/services/plugins.py,sha256=KgBqBUqxjQirRcYNuXVetE4OQj0EPkdC7ccbrNV1WQE,3825
384
384
  dstack/_internal/server/services/projects.py,sha256=Je1iWZ-ArmyFxK1yMUzod5WRXyiIDxtuVp6pHcdctTQ,14988
385
385
  dstack/_internal/server/services/prometheus.py,sha256=xq5G-Q2BJup9lS2F6__0wUVTs-k1Gr3dYclGzo2WoWo,12474
386
386
  dstack/_internal/server/services/repos.py,sha256=f9ztN7jz_2gvD9hXF5sJwWDVyG2-NHRfjIdSukowPh8,9342
387
387
  dstack/_internal/server/services/resources.py,sha256=VRFOih_cMJdc0c2m9nSGsX8vWAJQV3M6N87aqS_JXfw,699
388
- dstack/_internal/server/services/runs.py,sha256=Vm-dWX8-ZL6TANnyh0KdEg_UIMaKOky8N8SYoMSQRCQ,39167
388
+ dstack/_internal/server/services/runs.py,sha256=7nj4iQ6CZT3SEW9h3Vb8CBV65MUSSeQfZwgP_J4YlMo,39179
389
389
  dstack/_internal/server/services/storage.py,sha256=6I0xI_3_RpJNbKZwHjDnjrEwXGdHfiaeb5li15T-M1I,1884
390
390
  dstack/_internal/server/services/users.py,sha256=W-5xL7zsHNjeG7BBK54RWGvIrBOrw-FF0NcG_z9qhoE,7466
391
- dstack/_internal/server/services/volumes.py,sha256=vfKY6eZp64I58Mfdvrk9Wig7deveD2Rw4ET1cbc1Sog,16238
391
+ dstack/_internal/server/services/volumes.py,sha256=vxjFn3ijrvwi9-aXipT0iQgNjgebdwcBRn1mKFL9zAQ,16244
392
392
  dstack/_internal/server/services/backends/__init__.py,sha256=Aqo1GoqhZ_FsLEkCcBrvReKSq6E5w4QbBLrDXfGjiKU,13154
393
393
  dstack/_internal/server/services/backends/handlers.py,sha256=j-MhBxrpdepoDG7f2tApjFnE23RVO5I15-hxHyOWnew,3251
394
394
  dstack/_internal/server/services/encryption/__init__.py,sha256=3kCw_cxC3-Un1OIofdW5Gqsm0ZCXXTlGz09cULBx_uc,3155
@@ -396,7 +396,7 @@ dstack/_internal/server/services/encryption/keys/__init__.py,sha256=47DEQpj8HBSa
396
396
  dstack/_internal/server/services/encryption/keys/aes.py,sha256=2He1p2_Rg6hnCeLIGJo-Yfdsij7so_338oY49RXuL3Q,2276
397
397
  dstack/_internal/server/services/encryption/keys/base.py,sha256=mqumJiidoexUPoqxhQG6J_SpC1WGYwkdjKm1MUWnXo8,352
398
398
  dstack/_internal/server/services/encryption/keys/identity.py,sha256=ryb_YSV6u4c7W1OsVfEpzJvZCrR4zZYlzLw_GpjpD2Q,741
399
- dstack/_internal/server/services/gateways/__init__.py,sha256=Up8uFsEQDBE0yOXn7n5o7Q8MkY0XJfbMWViMFd2EIL4,21530
399
+ dstack/_internal/server/services/gateways/__init__.py,sha256=aDTpr45rkaQ2zBHnDQycwEDSQSno7bQ0buRPqTEDRxg,21536
400
400
  dstack/_internal/server/services/gateways/client.py,sha256=XIJX3fGBbZ_AG8qZMTSE8KAB_ojq5YJFa0OXoD_dofg,7493
401
401
  dstack/_internal/server/services/gateways/connection.py,sha256=ot3lV85XdmCT45vBWeyj57nLPcLPNm316zu3jMyeWjA,5625
402
402
  dstack/_internal/server/services/gateways/pool.py,sha256=0LclTl1tyx-doS78LeaAKjr-SMp98zuwh5f9s06JSd0,1914
@@ -549,7 +549,6 @@ dstack/_internal/utils/env.py,sha256=HRbIspHpKHh05fMZeV23-hrZoV6vVMuniefD08u6ey0
549
549
  dstack/_internal/utils/event_loop.py,sha256=DO2ADtWfH2z8F2hBbg_EADSWzleQYGVZ9D1XYDpH-tk,880
550
550
  dstack/_internal/utils/gpu.py,sha256=ZeWpy1nRLVh-FwBZdxbMoVjjCF0DWJlWfNoVgFhGx2w,1776
551
551
  dstack/_internal/utils/hash.py,sha256=mCERRtj9QwbpoP3vveBqbniSJiNMHG0vPSzp4fxmKv0,920
552
- dstack/_internal/utils/ignore.py,sha256=Lb5l_qTxOApHGPRu_YHkpTB9e5P0KIOFibMm8vTs_eQ,3138
553
552
  dstack/_internal/utils/interpolator.py,sha256=kLRm_DagGcjnkLPtMC9NaHioegsUvg8uk4lom38eV3I,2922
554
553
  dstack/_internal/utils/json_schema.py,sha256=oh23z6jdl_wx3qkojk_2egyKZbdmYWZs12Gd9Bk46M8,371
555
554
  dstack/_internal/utils/logging.py,sha256=j37rgL-fQh2_E3ec_RqDy6Ora-Aa79ZVV-BAgZJViME,97
@@ -588,8 +587,8 @@ dstack/plugins/builtin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
588
587
  dstack/plugins/builtin/rest_plugin/__init__.py,sha256=lgTsq8Z6Km2F2UhPRChVB4vDM5ZpWtdk1iB1aa20ypA,440
589
588
  dstack/plugins/builtin/rest_plugin/_models.py,sha256=9hgVuU6OGSxidar88XhQnNo9izYWeQvVH45ciErv-Es,1910
590
589
  dstack/plugins/builtin/rest_plugin/_plugin.py,sha256=LbtjnXqd-51pR19Cr5rYxoIyDKjSOG4hIpXmv1Mpfnc,4615
591
- dstack-0.19.11rc2.dist-info/METADATA,sha256=gW5bm3zVUlCFv1GeJCmDDaskSOyWRTkro3-diWi_avE,20480
592
- dstack-0.19.11rc2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
593
- dstack-0.19.11rc2.dist-info/entry_points.txt,sha256=GnLrMS8hx3rWAySQjA7tPNhtixV6a-brRkmal1PKoHc,58
594
- dstack-0.19.11rc2.dist-info/licenses/LICENSE.md,sha256=qDABaRGjSKVOib1U8viw2P_96sIK7Puo426784oD9f8,15976
595
- dstack-0.19.11rc2.dist-info/RECORD,,
590
+ dstack-0.19.12rc1.dist-info/METADATA,sha256=ysn5aUHpxpI_UTqDNLjGErkH2getUqLN3YMfpcVqs6s,20516
591
+ dstack-0.19.12rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
592
+ dstack-0.19.12rc1.dist-info/entry_points.txt,sha256=GnLrMS8hx3rWAySQjA7tPNhtixV6a-brRkmal1PKoHc,58
593
+ dstack-0.19.12rc1.dist-info/licenses/LICENSE.md,sha256=qDABaRGjSKVOib1U8viw2P_96sIK7Puo426784oD9f8,15976
594
+ dstack-0.19.12rc1.dist-info/RECORD,,
@@ -1,92 +0,0 @@
1
- import fnmatch
2
- from itertools import zip_longest
3
- from pathlib import Path
4
- from typing import Dict, List, Optional
5
-
6
- from dstack._internal.utils.path import PathLike
7
-
8
-
9
- class GitIgnore:
10
- def __init__(
11
- self, root_dir: PathLike, ignore_files: List[str] = None, globs: List[str] = None
12
- ):
13
- self.root_dir = Path(root_dir)
14
- self.ignore_files = (
15
- ignore_files
16
- if ignore_files is not None
17
- else [".gitignore", ".git/info/exclude", ".dstackignore"]
18
- )
19
- self.ignore_globs: Dict[str, List[str]] = {".": globs or []}
20
- self.load_recursive()
21
-
22
- def load_ignore_file(self, path: str, ignore_file: Path):
23
- if path != "." and not path.startswith("./"):
24
- path = "./" + path
25
- if path not in self.ignore_globs:
26
- self.ignore_globs[path] = []
27
- with ignore_file.open("r") as f:
28
- for line in f:
29
- line = self.rstrip(line.rstrip("\n")).rstrip("/")
30
- line = line.replace("\\ ", " ")
31
- if line.startswith("#") or not line:
32
- continue
33
- self.ignore_globs[path].append(line)
34
-
35
- def load_recursive(self, path: Optional[Path] = None):
36
- path = path or self.root_dir
37
- for ignore_file in self.ignore_files:
38
- ignore_file = path / ignore_file
39
- if ignore_file.exists():
40
- self.load_ignore_file(str(path.relative_to(self.root_dir)), ignore_file)
41
-
42
- for subdir in path.iterdir():
43
- if not subdir.is_dir() or self.ignore(subdir.relative_to(self.root_dir)):
44
- continue
45
- self.load_recursive(subdir)
46
-
47
- @staticmethod
48
- def rstrip(value: str) -> str:
49
- end = len(value) - 1
50
- while end >= 0:
51
- if not value[end].isspace():
52
- break
53
- if end > 0 and value[end - 1] == "\\":
54
- break # escaped space
55
- end -= 1
56
- else:
57
- return ""
58
- return value[: end + 1]
59
-
60
- @staticmethod
61
- def fnmatch(name: str, pattern: str, sep="/") -> bool:
62
- if pattern.startswith(sep):
63
- name = sep + name
64
- for n, p in zip_longest(
65
- reversed(name.split(sep)), reversed(pattern.split(sep)), fillvalue=None
66
- ):
67
- if p == "**":
68
- raise NotImplementedError()
69
- if p is None:
70
- return True
71
- if n is None or not fnmatch.fnmatch(n, p):
72
- return False
73
- return True
74
-
75
- def ignore(self, path: PathLike, sep="/") -> bool:
76
- if not path:
77
- return False
78
- path = Path(path)
79
- if path.is_absolute():
80
- path = path.relative_to(self.root_dir)
81
-
82
- tokens = ("." + sep + str(path)).split(sep)
83
- for i in range(1, len(tokens)):
84
- parent = sep.join(tokens[:-i])
85
- globs = self.ignore_globs.get(parent)
86
- if not globs:
87
- continue
88
- name = sep.join(tokens[-i:])
89
- for glob in globs:
90
- if self.fnmatch(name, glob, sep=sep):
91
- return True
92
- return False