konduktor-nightly 0.1.0.dev20250409105017__py3-none-any.whl → 0.1.0.dev20250411104646__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.
@@ -16,7 +16,7 @@ from multiprocessing import pool
16
16
  from typing import Any, Callable, Dict, List, Optional, Tuple
17
17
 
18
18
  from konduktor import logging
19
- from konduktor.adaptors import gcp
19
+ from konduktor.adaptors import aws, gcp
20
20
  from konduktor.utils import exceptions, log_utils, ux_utils
21
21
 
22
22
  Client = Any
@@ -36,6 +36,30 @@ def split_gcs_path(gcs_path: str) -> Tuple[str, str]:
36
36
  return bucket, key
37
37
 
38
38
 
39
+ def split_s3_path(s3_path: str) -> Tuple[str, str]:
40
+ """Splits S3 Path into Bucket name and Relative Path to Bucket
41
+
42
+ Args:
43
+ s3_path: str; S3 Path, e.g. s3://imagenet/train/
44
+ """
45
+ path_parts = s3_path.replace('s3://', '').split('/')
46
+ bucket = path_parts.pop(0)
47
+ key = '/'.join(path_parts)
48
+ return bucket, key
49
+
50
+
51
+ def create_s3_client(region: Optional[str] = None) -> Client:
52
+ """Helper method that connects to Boto3 client for S3 Bucket
53
+
54
+ Args:
55
+ region: str; Region name, e.g. us-west-1, us-east-2. If None, default
56
+ region us-east-1 is used.
57
+ """
58
+ if region is None:
59
+ region = 'us-east-1'
60
+ return aws.client('s3', region_name=region)
61
+
62
+
39
63
  def verify_gcs_bucket(name: str) -> bool:
40
64
  """Helper method that checks if the GCS bucket exists
41
65
 
@@ -49,6 +73,27 @@ def verify_gcs_bucket(name: str) -> bool:
49
73
  return False
50
74
 
51
75
 
76
+ def verify_s3_bucket(name: str) -> bool:
77
+ """Helper method that checks if the S3 bucket exists
78
+
79
+ Args:
80
+ name: str; Name of the S3 Bucket (without s3:// prefix)
81
+ """
82
+ import boto3
83
+ from botocore.exceptions import ClientError
84
+
85
+ s3 = boto3.client('s3')
86
+ try:
87
+ s3.head_bucket(Bucket=name)
88
+ return True
89
+ except ClientError as e:
90
+ error_code = int(e.response['Error']['Code'])
91
+ if error_code == 404:
92
+ return False
93
+ # In case of permissions issues or other errors, you can log and assume False
94
+ return False
95
+
96
+
52
97
  def is_cloud_store_url(url):
53
98
  result = urllib.parse.urlsplit(url)
54
99
  # '' means non-cloud URLs.
@@ -1,11 +1,12 @@
1
+ from konduktor.data.aws import S3CloudStorage
1
2
  from konduktor.data.gcp import GcsCloudStorage
2
3
 
3
4
  # Maps bucket's URIs prefix(scheme) to its corresponding storage class
4
5
 
5
6
  _REGISTRY = {
6
7
  'gs': GcsCloudStorage(),
8
+ 's3': S3CloudStorage(),
7
9
  # TODO(asaiacai): Add other cloud stores here
8
- # 's3': S3CloudStorage(),
9
10
  # 'r2': R2CloudStorage(),
10
11
  # 'cos': IBMCosCloudStorage(),
11
12
  # 'oci': OciCloudStorage(),
konduktor/data/storage.py CHANGED
@@ -28,7 +28,7 @@ import urllib.parse
28
28
  from typing import Any, Dict, List, Literal, Optional, Tuple, Type, Union
29
29
 
30
30
  from konduktor import check, config, logging
31
- from konduktor.data import constants, data_utils, gcp, registry, storage_utils
31
+ from konduktor.data import aws, constants, data_utils, gcp, registry, storage_utils
32
32
  from konduktor.utils import annotations, common_utils, exceptions, schemas, ux_utils
33
33
 
34
34
  logger = logging.get_logger(__file__)
@@ -79,12 +79,15 @@ class StoreType(enum.Enum):
79
79
  """Enum for the different types of stores."""
80
80
 
81
81
  GCS = 'GCS'
82
+ S3 = 'S3'
82
83
 
83
84
  @classmethod
84
85
  def from_cloud(cls, cloud: str) -> 'StoreType':
85
86
  # these need to match the cloud store classes in konduktor/cloud_stores.py
86
87
  if cloud.lower() == 'gs':
87
88
  return StoreType.GCS
89
+ elif cloud.lower() == 's3':
90
+ return StoreType.S3
88
91
  else:
89
92
  with ux_utils.print_exception_no_traceback():
90
93
  raise ValueError(f'Unknown cloud: {cloud}')
@@ -93,6 +96,8 @@ class StoreType(enum.Enum):
93
96
  def from_store(cls, store: 'storage_utils.AbstractStore') -> 'StoreType':
94
97
  if store.__repr__() == 'GcsStore':
95
98
  return StoreType.GCS
99
+ elif store.__repr__() == 'S3Store':
100
+ return StoreType.S3
96
101
  else:
97
102
  with ux_utils.print_exception_no_traceback():
98
103
  raise ValueError(f'Unknown store type: {store}')
@@ -100,6 +105,8 @@ class StoreType(enum.Enum):
100
105
  def store_prefix(self) -> str:
101
106
  if self == StoreType.GCS:
102
107
  return 'gs://'
108
+ elif self == StoreType.S3:
109
+ return 's3://'
103
110
  else:
104
111
  with ux_utils.print_exception_no_traceback():
105
112
  raise ValueError(f'Unknown store type: {self}')
@@ -123,6 +130,8 @@ class StoreType(enum.Enum):
123
130
  if store_url.startswith(store_type.store_prefix()):
124
131
  if store_type == StoreType.GCS:
125
132
  bucket_name, sub_path = data_utils.split_gcs_path(store_url)
133
+ elif store_type == StoreType.S3:
134
+ bucket_name, sub_path = data_utils.split_s3_path(store_url)
126
135
  return store_type, bucket_name, sub_path, storage_account_name, region
127
136
  raise ValueError(f'Unknown store URL: {store_url}')
128
137
 
@@ -143,7 +152,7 @@ class StoreType(enum.Enum):
143
152
 
144
153
 
145
154
  # this should match the above StoreType enum
146
- STORE_TYPES = Literal[StoreType.GCS]
155
+ STORE_TYPES = Literal[StoreType.GCS, StoreType.S3]
147
156
 
148
157
 
149
158
  class Storage(object):
@@ -310,6 +319,8 @@ class Storage(object):
310
319
  if isinstance(self.source, str):
311
320
  if self.source.startswith('gs://'):
312
321
  self.add_store(StoreType.GCS)
322
+ elif self.source.startswith('s3://'):
323
+ self.add_store(StoreType.S3)
313
324
 
314
325
  @staticmethod
315
326
  def _validate_source(
@@ -533,12 +544,13 @@ class Storage(object):
533
544
  # When initializing from global_user_state, we override the
534
545
  # source from the YAML
535
546
  try:
536
- # if s_type == StoreType.S3:
537
- # store = S3Store.from_metadata(
538
- # s_metadata,
539
- # source=self.source,
540
- # sync_on_reconstruction=self.sync_on_reconstruction)
541
- if s_type == StoreType.GCS:
547
+ if s_type == StoreType.S3:
548
+ store = aws.S3Store.from_metadata(
549
+ s_metadata,
550
+ source=self.source,
551
+ sync_on_reconstruction=self.sync_on_reconstruction,
552
+ )
553
+ elif s_type == StoreType.GCS:
542
554
  store = gcp.GcsStore.from_metadata(
543
555
  s_metadata,
544
556
  source=self.source,
@@ -622,6 +634,8 @@ class Storage(object):
622
634
  store_cls: Type['storage_utils.AbstractStore']
623
635
  if store_type == StoreType.GCS:
624
636
  store_cls = gcp.GcsStore
637
+ elif store_type == StoreType.S3:
638
+ store_cls = aws.S3Store
625
639
  else:
626
640
  with ux_utils.print_exception_no_traceback():
627
641
  raise exceptions.StorageSpecError(
konduktor/task.py CHANGED
@@ -653,6 +653,19 @@ class Task:
653
653
  if storage.mode == storage_lib.StorageMode.COPY:
654
654
  store_type = storage_plans[storage]
655
655
  # TODO(asaiacai): add back other stores here
656
+ elif store_type is storage_lib.StoreType.S3:
657
+ if isinstance(storage.source, str) and storage.source.startswith(
658
+ 's3://'
659
+ ):
660
+ blob_path = storage.source
661
+ else:
662
+ assert storage.name is not None, storage
663
+ blob_path = 's3://' + storage.name
664
+ self.update_file_mounts(
665
+ {
666
+ mnt_path: blob_path,
667
+ }
668
+ )
656
669
  elif store_type is storage_lib.StoreType.GCS:
657
670
  if isinstance(storage.source, str) and storage.source.startswith(
658
671
  'gs://'
@@ -79,7 +79,7 @@ kubernetes:
79
79
  {% if 'curl' in run_cmd %}
80
80
  PACKAGES="$PACKAGES curl";
81
81
  {% endif %}
82
- {% if 'gs' in mount_secrets %}
82
+ {% if 'gs' in mount_secrets or 's3' in mount_secrets %}
83
83
  PACKAGES="$PACKAGES unzip wget";
84
84
  {% endif %}
85
85
  {% if 'git' in run_cmd %}
@@ -129,6 +129,10 @@ kubernetes:
129
129
  $(prefix_cmd) echo "Unpacking GCP secret"
130
130
  $(prefix_cmd) mkdir -p ~/.config
131
131
  $(prefix_cmd) unzip /run/konduktor/gs-secret/gcpcredentials -d ~/.config/gcloud
132
+ {% elif secret_type == "s3" %}
133
+ $(prefix_cmd) echo "Unpacking AWS secret"
134
+ $(prefix_cmd) mkdir -p ~/.aws
135
+ $(prefix_cmd) unzip /run/konduktor/s3-secret/awscredentials -d ~/.aws
132
136
  {% endif %}
133
137
  {% endfor %}
134
138
  end_epoch=$(date +%s);
@@ -17,6 +17,7 @@ import getpass
17
17
  import hashlib
18
18
  import inspect
19
19
  import os
20
+ import random
20
21
  import re
21
22
  import socket
22
23
  import sys
@@ -391,3 +392,31 @@ def format_exception(
391
392
  if use_bracket:
392
393
  return f'[{class_fullname(e.__class__)}] {e}'
393
394
  return f'{class_fullname(e.__class__)}: {e}'
395
+
396
+
397
+ class Backoff:
398
+ """Exponential backoff with jittering."""
399
+
400
+ MULTIPLIER = 1.6
401
+ JITTER = 0.4
402
+
403
+ def __init__(self, initial_backoff: float = 5, max_backoff_factor: int = 5):
404
+ self._initial = True
405
+ self._backoff = 0.0
406
+ self._initial_backoff = initial_backoff
407
+ self._max_backoff = max_backoff_factor * self._initial_backoff
408
+
409
+ # https://github.com/grpc/grpc/blob/2d4f3c56001cd1e1f85734b2f7c5ce5f2797c38a/doc/connection-backoff.md
410
+ # https://github.com/grpc/grpc/blob/5fc3ff82032d0ebc4bf252a170ebe66aacf9ed9d/src/core/lib/backoff/backoff.cc
411
+
412
+ def current_backoff(self) -> float:
413
+ """Backs off once and returns the current backoff in seconds."""
414
+ if self._initial:
415
+ self._initial = False
416
+ self._backoff = min(self._initial_backoff, self._max_backoff)
417
+ else:
418
+ self._backoff = min(self._backoff * self.MULTIPLIER, self._max_backoff)
419
+ self._backoff += random.uniform(
420
+ -self.JITTER * self._backoff, self.JITTER * self._backoff
421
+ )
422
+ return self._backoff
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: konduktor-nightly
3
- Version: 0.1.0.dev20250409105017
3
+ Version: 0.1.0.dev20250411104646
4
4
  Summary: GPU Cluster Health Management
5
5
  Author: Andrew Aikawa
6
6
  Author-email: asai@berkeley.edu
@@ -10,6 +10,10 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Provides-Extra: s3
14
+ Requires-Dist: awscli (>=1.32.84,<2.0.0) ; extra == "s3"
15
+ Requires-Dist: boto3 (>=1.34.84,<2.0.0) ; extra == "s3"
16
+ Requires-Dist: botocore (>=1.34.84,<2.0.0) ; extra == "s3"
13
17
  Requires-Dist: click (>=8.1.7,<9.0.0)
14
18
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
15
19
  Requires-Dist: filelock (>=3.18.0,<4.0.0)
@@ -1,5 +1,6 @@
1
- konduktor/__init__.py,sha256=Ka1B4zUTmoP9pKaZEEQhTtqNHimV85pb9tDNBCwCQi0,1540
1
+ konduktor/__init__.py,sha256=u5KeTUXAp2DsBVVDw9q05NbgGGQpL0p0jSD72wlqKBg,1540
2
2
  konduktor/adaptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ konduktor/adaptors/aws.py,sha256=s47Ra-GaqCQibzVfmD0pmwEWHif1EGO5opMbwkLxTCU,8244
3
4
  konduktor/adaptors/common.py,sha256=mYb_6c3u5MghtiFfiW5OO-EH6t7cIR5npbkgUmz6FYE,3517
4
5
  konduktor/adaptors/gcp.py,sha256=pOQA2q8fFyr97Htn8EqvNM0XT-Ao8UwvExviiLaDats,4746
5
6
  konduktor/backends/__init__.py,sha256=1Q6sqqdeMYarpTX_U-QVywJYf7idiUTRsyP-E4BQSOw,129
@@ -7,7 +8,7 @@ konduktor/backends/backend.py,sha256=qh0bp94lzoTYZkzyQv2-CVrB5l91FkG2vclXg24UFC0
7
8
  konduktor/backends/jobset.py,sha256=lh_PihQgM0tmVryCpjSsZjWug8hBnJr7ua9lqk0qEAM,8251
8
9
  konduktor/backends/jobset_utils.py,sha256=NIwTvJdGhbDnXEceabiuUm9aHZ29LK3jVHfQzutB_ec,17297
9
10
  konduktor/check.py,sha256=JennyWoaqSKhdyfUldd266KwVXTPJpcYQa4EED4a_BA,7569
10
- konduktor/cli.py,sha256=90bnh3nIobfBkzqS_SXgw9Z8Zqh4ouwpLDj0kx_6kL8,23562
11
+ konduktor/cli.py,sha256=Ii9-2mrc-1f2ksLasA-xRb-JnEi_9ZeCXZ3lJ1GG8H8,23515
11
12
  konduktor/config.py,sha256=J50JxC6MsXMnlrJPXdDUMr38C89xvOO7mR8KJ6fyils,15520
12
13
  konduktor/constants.py,sha256=T3AeXXxuQHINW_bAWyztvDeS8r4g8kXBGIwIq13cys0,1814
13
14
  konduktor/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -48,14 +49,16 @@ konduktor/dashboard/frontend/postcss.config.mjs,sha256=rDHiqV72T-J860Ek4QFnUnMQe
48
49
  konduktor/dashboard/frontend/server.js,sha256=jcp6_Ww9YJD3uKY07jR3KMlAM6n1QZdxZnVY6Kh-J6A,1789
49
50
  konduktor/dashboard/frontend/tailwind.config.js,sha256=fCnc48wvioIDOe5ldQ_6RE7F76cP7aU7pDrxBPJx-Fk,366
50
51
  konduktor/data/__init__.py,sha256=KMR2i3E9YcIpiIuCxtRdS7BQ1w2vUAbbve7agziJrLo,213
52
+ konduktor/data/aws/__init__.py,sha256=_6zWfNNAK1QGgyKqg_yPYWcXlnffchyvIMErYa6tw_U,331
53
+ konduktor/data/aws/s3.py,sha256=E6JdT5K0nhy0ExQQN6M5vjemDSfBi6Cc3PCgjyCMJoQ,47777
51
54
  konduktor/data/constants.py,sha256=yXVEoTI2we1xOjVSU-bjRCQCLpVvpEvJ0GedXvSwEfw,127
52
- konduktor/data/data_utils.py,sha256=aIv3q2H1GSiN2w8WNjZgVaglm-hoiHSb4KR-MAiKKXs,8383
55
+ konduktor/data/data_utils.py,sha256=yrnu8_cY63TXqfWfFG3yqY2w_tE9UQK9jIQAFQCDVg0,9668
53
56
  konduktor/data/gcp/__init__.py,sha256=rlQxACBC_Vu36mdgPyJgUy4mGc_6Nt_a96JAuaPz2pQ,489
54
57
  konduktor/data/gcp/constants.py,sha256=dMfOiFccM8O6rUi9kClJcbvw1K1VnS1JzzQk3apq8ho,1483
55
58
  konduktor/data/gcp/gcs.py,sha256=kDbUzf8ALYzsw_G3sBRn_enQ8fjI-UKV0jeWuFZiULA,42018
56
59
  konduktor/data/gcp/utils.py,sha256=FJQcMXZqtMIzjZ98b3lTTc0UbdPUKTDLsOsfJaaH5-s,214
57
- konduktor/data/registry.py,sha256=eLs8Wr5ugwOfXGPtg1utTvGIqdbVLsCf-a3PFS1NELc,652
58
- konduktor/data/storage.py,sha256=sm0ZfGZUZRiChza_jMRQY1xDIWtZpFQqwPuVOF8PM_Y,34742
60
+ konduktor/data/registry.py,sha256=CUbMsN_Q17Pf4wRHkqZrycErEjTP7cLEdgcfwVGcEpc,696
61
+ konduktor/data/storage.py,sha256=SDKRWDd7PCT9ytuz4cH0CejZj5QmWG_EZhUMVoTzWsc,35308
59
62
  konduktor/data/storage_utils.py,sha256=n4GivkN0KMqmyOTDznF0Z-hzsJvm7KCEh5i5HgFAT-4,20806
60
63
  konduktor/execution.py,sha256=UaHUdBmDaIYgiAXkRKJQOHniYPVIR4sr4yUbIqpgMrQ,18401
61
64
  konduktor/kube_client.py,sha256=aqwjDfNSneB5NOxV6CtqhkBeNl0UQNUt730R3ujG9Ow,6156
@@ -65,16 +68,16 @@ konduktor/manifests/dashboard_deployment.yaml,sha256=xJLd4FbPMAosI0fIv5_8y7dV9bw
65
68
  konduktor/manifests/dmesg_daemonset.yaml,sha256=pSWt7YOeTYjS0l0iki1fvHOs7MhY-sH-RQfVW6JJyno,1391
66
69
  konduktor/manifests/pod_cleanup_controller.yaml,sha256=hziL1Ka1kCAEL9R7Tjvpb80iw1vcq9_3gwHCu75Bi0A,3939
67
70
  konduktor/resource.py,sha256=68z8gC8Ivqktwv0R6ylMn9ZNocgkcRT0yIRGGKOdwcM,18491
68
- konduktor/task.py,sha256=edHgMLYECGux6WLCilqsNZNYr3dEcw_miWvu4FYpu5U,34713
71
+ konduktor/task.py,sha256=Vu1TzYtLvSBz-HyHY2gsM2cMcUhMNQu44L3CWmYRXKE,35232
69
72
  konduktor/templates/jobset.yaml.j2,sha256=onYiHtXAgk-XBtji994hPu_g0hxnLzvmfxwjbdKdeZc,960
70
- konduktor/templates/pod.yaml.j2,sha256=24g0onMUl4BDhPO2ftTdZB5l_wvMjwCuDQ_fTdwBxD8,8474
73
+ konduktor/templates/pod.yaml.j2,sha256=q_kFyuYP-RlHocQUV8Vsnh-hYurWO7Y_pwl0gytGMl4,8728
71
74
  konduktor/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
75
  konduktor/usage/constants.py,sha256=gCL8afIHZhO0dcxbJGpESE9sCC1cBSbeRnQ8GwNOY4M,612
73
76
  konduktor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
77
  konduktor/utils/accelerator_registry.py,sha256=lzUqkod1U3hDo8ETFU0CBXESXhPNZI8e-CJt4Oqf3Ko,550
75
78
  konduktor/utils/annotations.py,sha256=oy2-BLydkFt3KWkXDuaGY84d6b7iISuy4eAT9uXk0Fc,2225
76
79
  konduktor/utils/base64_utils.py,sha256=mF-Tw98mFRG70YE4w6s9feuQSCYZHOb8YatBZwMugyI,3130
77
- konduktor/utils/common_utils.py,sha256=1_j-nRikKmTnB8BFE0xQb7LquKVAOLaJnsy4LxZlNbI,13869
80
+ konduktor/utils/common_utils.py,sha256=F5x7k4AdBB44u8PYRkaugORnZKnK3JLqGn1jHOKgUYo,14960
78
81
  konduktor/utils/constants.py,sha256=1DneiTR21lvKUcWdBGwC4I4fD4uPjbjLUilEnJS7rzA,216
79
82
  konduktor/utils/env_options.py,sha256=T41Slzf4Mzl-n45CGXXqdy2fCrYhPNZQ7RP5vmnN4xc,2258
80
83
  konduktor/utils/exceptions.py,sha256=GBOFIkk9nikqWGR0FXGXOWVVImoH7nWnMl_L3Oux3fo,6581
@@ -87,8 +90,8 @@ konduktor/utils/schemas.py,sha256=Gv7SEhFpv-eO5izqRz8d-eQ9z-lVmY05akm6HEXIIdc,17
87
90
  konduktor/utils/subprocess_utils.py,sha256=WoFkoFhGecPR8-rF8WJxbIe-YtV94LXz9UG64SDhCY4,9448
88
91
  konduktor/utils/ux_utils.py,sha256=NPNu3Igu2Z9Oq77ghJhy_fIxQZTXWr9BtKyxN3Wslzo,7164
89
92
  konduktor/utils/validator.py,sha256=tgBghVyedyzGx84-U2Qfoh_cJBE3oUk9gclMW90ORks,691
90
- konduktor_nightly-0.1.0.dev20250409105017.dist-info/LICENSE,sha256=MuuqTZbHvmqXR_aNKAXzggdV45ANd3wQ5YI7tnpZhm0,6586
91
- konduktor_nightly-0.1.0.dev20250409105017.dist-info/METADATA,sha256=TQwygSbHFhQFKOiGfeW3g3Eyc5YO9mrqQoamc9TtQro,4112
92
- konduktor_nightly-0.1.0.dev20250409105017.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
93
- konduktor_nightly-0.1.0.dev20250409105017.dist-info/entry_points.txt,sha256=k3nG5wDFIJhNqsZWrHk4d0irIB2Ns9s47cjRWYsTCT8,48
94
- konduktor_nightly-0.1.0.dev20250409105017.dist-info/RECORD,,
93
+ konduktor_nightly-0.1.0.dev20250411104646.dist-info/LICENSE,sha256=MuuqTZbHvmqXR_aNKAXzggdV45ANd3wQ5YI7tnpZhm0,6586
94
+ konduktor_nightly-0.1.0.dev20250411104646.dist-info/METADATA,sha256=kQVOo2kIGvVIPA7rzLAF-k7FW_C6FvrY4MIxuuZlgl8,4303
95
+ konduktor_nightly-0.1.0.dev20250411104646.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
96
+ konduktor_nightly-0.1.0.dev20250411104646.dist-info/entry_points.txt,sha256=k3nG5wDFIJhNqsZWrHk4d0irIB2Ns9s47cjRWYsTCT8,48
97
+ konduktor_nightly-0.1.0.dev20250411104646.dist-info/RECORD,,