lamin_cli 1.12.0__py2.py3-none-any.whl → 1.12.1__py2.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.
- lamin_cli/__init__.py +27 -27
- lamin_cli/__main__.py +826 -826
- lamin_cli/_annotate.py +47 -47
- lamin_cli/_cache.py +49 -41
- lamin_cli/_context.py +76 -76
- lamin_cli/_delete.py +85 -85
- lamin_cli/_io.py +147 -147
- lamin_cli/_load.py +203 -203
- lamin_cli/_migration.py +50 -50
- lamin_cli/_save.py +350 -350
- lamin_cli/_settings.py +154 -154
- lamin_cli/clone/_clone_verification.py +56 -56
- lamin_cli/clone/create_sqlite_clone_and_import_db.py +53 -53
- lamin_cli/compute/modal.py +174 -174
- lamin_cli/urls.py +10 -10
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info}/METADATA +3 -2
- lamin_cli-1.12.1.dist-info/RECORD +22 -0
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info}/WHEEL +1 -1
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info/licenses}/LICENSE +201 -201
- lamin_cli-1.12.0.dist-info/RECORD +0 -22
- {lamin_cli-1.12.0.dist-info → lamin_cli-1.12.1.dist-info}/entry_points.txt +0 -0
lamin_cli/compute/modal.py
CHANGED
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
import threading
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
import lamindb_setup as ln_setup
|
|
8
|
-
import modal
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def run_script(path: Path) -> dict:
|
|
12
|
-
"""Takes a path to a script for running it as a function through Modal."""
|
|
13
|
-
result = {"success": False, "output": "", "error": ""}
|
|
14
|
-
|
|
15
|
-
def stream_output(stream, capture_list):
|
|
16
|
-
"""Read from stream line by line and print in real-time while also capturing to a list."""
|
|
17
|
-
for line in iter(stream.readline, ""):
|
|
18
|
-
print(line, end="") # Print in real-time
|
|
19
|
-
capture_list.append(line)
|
|
20
|
-
stream.close()
|
|
21
|
-
|
|
22
|
-
if not path.exists():
|
|
23
|
-
raise FileNotFoundError(f"Script file not found: {path}")
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
# Run the script using subprocess
|
|
27
|
-
process = subprocess.Popen(
|
|
28
|
-
[sys.executable, path.as_posix()],
|
|
29
|
-
stdout=subprocess.PIPE,
|
|
30
|
-
stderr=subprocess.PIPE,
|
|
31
|
-
text=True,
|
|
32
|
-
bufsize=1, # Line buffered
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
# Capture output and error while streaming stdout in real-time
|
|
36
|
-
stdout_lines: list[str] = []
|
|
37
|
-
stderr_lines: list[str] = []
|
|
38
|
-
|
|
39
|
-
# Create threads to handle stdout and stderr streams
|
|
40
|
-
stdout_thread = threading.Thread(
|
|
41
|
-
target=stream_output, args=(process.stdout, stdout_lines)
|
|
42
|
-
)
|
|
43
|
-
stderr_thread = threading.Thread(
|
|
44
|
-
target=stream_output, args=(process.stderr, stderr_lines)
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Set as daemon threads so they exit when the main program exits
|
|
48
|
-
stdout_thread.daemon = True
|
|
49
|
-
stderr_thread.daemon = True
|
|
50
|
-
|
|
51
|
-
# Start the threads
|
|
52
|
-
stdout_thread.start()
|
|
53
|
-
stderr_thread.start()
|
|
54
|
-
|
|
55
|
-
# Wait for the process to complete
|
|
56
|
-
return_code = process.wait()
|
|
57
|
-
|
|
58
|
-
# Wait for the threads to finish
|
|
59
|
-
stdout_thread.join()
|
|
60
|
-
stderr_thread.join()
|
|
61
|
-
|
|
62
|
-
# Join the captured output
|
|
63
|
-
stdout_output = "".join(stdout_lines)
|
|
64
|
-
stderr_output = "".join(stderr_lines)
|
|
65
|
-
|
|
66
|
-
# Check return code
|
|
67
|
-
if return_code == 0:
|
|
68
|
-
result["success"] = True
|
|
69
|
-
result["output"] = stdout_output
|
|
70
|
-
else:
|
|
71
|
-
result["error"] = stderr_output
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
import traceback
|
|
75
|
-
|
|
76
|
-
result["error"] = str(e) + "\n" + traceback.format_exc()
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class Runner:
|
|
81
|
-
def __init__(
|
|
82
|
-
self,
|
|
83
|
-
app_name: str,
|
|
84
|
-
local_mount_dir: str | Path = "./scripts",
|
|
85
|
-
remote_mount_dir: str | Path = "/scripts",
|
|
86
|
-
image_url: str | None = None,
|
|
87
|
-
packages: list[str] | None = None,
|
|
88
|
-
cpu: float | None = None,
|
|
89
|
-
gpu: str | None = None,
|
|
90
|
-
):
|
|
91
|
-
self.app_name = app_name # we use the LaminDB project name as the app name
|
|
92
|
-
self.app = self.create_modal_app(app_name)
|
|
93
|
-
|
|
94
|
-
self.local_mount_dir = local_mount_dir
|
|
95
|
-
self.remote_mount_dir = remote_mount_dir
|
|
96
|
-
|
|
97
|
-
self.image = self.create_modal_image(
|
|
98
|
-
local_dir=local_mount_dir, packages=packages, image_url=image_url
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
local_secrets = self._configure_local_secrets()
|
|
102
|
-
|
|
103
|
-
self.modal_function = self.app.function(
|
|
104
|
-
image=self.image, cpu=cpu, gpu=gpu, secrets=[local_secrets]
|
|
105
|
-
)(run_script)
|
|
106
|
-
|
|
107
|
-
def run(self, script_local_path: Path) -> None:
|
|
108
|
-
script_remote_path = self.local_to_remote_path(str(script_local_path))
|
|
109
|
-
with modal.enable_output(show_progress=True): # Prints out modal logs
|
|
110
|
-
with self.app.run():
|
|
111
|
-
self.modal_function.remote(Path(script_remote_path))
|
|
112
|
-
|
|
113
|
-
def create_modal_app(self, app_name: str) -> modal.App:
|
|
114
|
-
app = modal.App(app_name)
|
|
115
|
-
return app
|
|
116
|
-
|
|
117
|
-
def local_to_remote_path(self, local_path: str | Path) -> str:
|
|
118
|
-
local_path = Path(local_path).absolute()
|
|
119
|
-
local_mount_dir = Path(self.local_mount_dir).absolute()
|
|
120
|
-
remote_mount_dir = Path(self.remote_mount_dir)
|
|
121
|
-
|
|
122
|
-
# Check if local_path is inside local_mount_dir
|
|
123
|
-
try:
|
|
124
|
-
# This will raise ValueError if local_path is not relative to local_mount_dir
|
|
125
|
-
relative_path = local_path.relative_to(local_mount_dir)
|
|
126
|
-
except ValueError as err:
|
|
127
|
-
raise ValueError(
|
|
128
|
-
f"Local path '{local_path}' is not inside the mount directory '{local_mount_dir}'"
|
|
129
|
-
) from err
|
|
130
|
-
|
|
131
|
-
# Join remote_mount_dir with the relative path
|
|
132
|
-
remote_path = remote_mount_dir / relative_path
|
|
133
|
-
|
|
134
|
-
# Return as string with normalized separators
|
|
135
|
-
return remote_path.as_posix()
|
|
136
|
-
|
|
137
|
-
def _configure_local_secrets(self) -> dict:
|
|
138
|
-
if ln_setup.settings.user.api_key is None:
|
|
139
|
-
raise ValueError("Please authenticate via: lamin login")
|
|
140
|
-
|
|
141
|
-
all_env_variables = {
|
|
142
|
-
"LAMIN_API_KEY": ln_setup.settings.user.api_key,
|
|
143
|
-
"LAMIN_CURRENT_PROJECT": self.app_name,
|
|
144
|
-
"LAMIN_CURRENT_INSTANCE": ln_setup.settings.instance.slug,
|
|
145
|
-
}
|
|
146
|
-
local_secrets = modal.Secret.from_dict(all_env_variables)
|
|
147
|
-
return local_secrets
|
|
148
|
-
|
|
149
|
-
def create_modal_image(
|
|
150
|
-
self,
|
|
151
|
-
python_version: str = "3.12",
|
|
152
|
-
packages: list[str] | None = None,
|
|
153
|
-
local_dir: str | Path = "./scripts",
|
|
154
|
-
remote_dir: str = "/scripts/",
|
|
155
|
-
image_url: str | None = None,
|
|
156
|
-
env_variables: dict | None = None,
|
|
157
|
-
) -> modal.Image:
|
|
158
|
-
if env_variables is None:
|
|
159
|
-
env_variables = {}
|
|
160
|
-
base_packages = ["lamindb", "httpx_retries"]
|
|
161
|
-
if packages is None:
|
|
162
|
-
packages = base_packages
|
|
163
|
-
else:
|
|
164
|
-
packages += base_packages
|
|
165
|
-
if image_url is None:
|
|
166
|
-
image = modal.Image.debian_slim(python_version=python_version)
|
|
167
|
-
else:
|
|
168
|
-
image = modal.Image.from_registry(image_url, add_python=python_version)
|
|
169
|
-
return (
|
|
170
|
-
image.pip_install(packages)
|
|
171
|
-
.env(env_variables)
|
|
172
|
-
.add_local_python_source("lamindb", "lamindb_setup", copy=True)
|
|
173
|
-
.add_local_dir(local_dir, remote_dir)
|
|
174
|
-
)
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import lamindb_setup as ln_setup
|
|
8
|
+
import modal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_script(path: Path) -> dict:
|
|
12
|
+
"""Takes a path to a script for running it as a function through Modal."""
|
|
13
|
+
result = {"success": False, "output": "", "error": ""}
|
|
14
|
+
|
|
15
|
+
def stream_output(stream, capture_list):
|
|
16
|
+
"""Read from stream line by line and print in real-time while also capturing to a list."""
|
|
17
|
+
for line in iter(stream.readline, ""):
|
|
18
|
+
print(line, end="") # Print in real-time
|
|
19
|
+
capture_list.append(line)
|
|
20
|
+
stream.close()
|
|
21
|
+
|
|
22
|
+
if not path.exists():
|
|
23
|
+
raise FileNotFoundError(f"Script file not found: {path}")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
# Run the script using subprocess
|
|
27
|
+
process = subprocess.Popen(
|
|
28
|
+
[sys.executable, path.as_posix()],
|
|
29
|
+
stdout=subprocess.PIPE,
|
|
30
|
+
stderr=subprocess.PIPE,
|
|
31
|
+
text=True,
|
|
32
|
+
bufsize=1, # Line buffered
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Capture output and error while streaming stdout in real-time
|
|
36
|
+
stdout_lines: list[str] = []
|
|
37
|
+
stderr_lines: list[str] = []
|
|
38
|
+
|
|
39
|
+
# Create threads to handle stdout and stderr streams
|
|
40
|
+
stdout_thread = threading.Thread(
|
|
41
|
+
target=stream_output, args=(process.stdout, stdout_lines)
|
|
42
|
+
)
|
|
43
|
+
stderr_thread = threading.Thread(
|
|
44
|
+
target=stream_output, args=(process.stderr, stderr_lines)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Set as daemon threads so they exit when the main program exits
|
|
48
|
+
stdout_thread.daemon = True
|
|
49
|
+
stderr_thread.daemon = True
|
|
50
|
+
|
|
51
|
+
# Start the threads
|
|
52
|
+
stdout_thread.start()
|
|
53
|
+
stderr_thread.start()
|
|
54
|
+
|
|
55
|
+
# Wait for the process to complete
|
|
56
|
+
return_code = process.wait()
|
|
57
|
+
|
|
58
|
+
# Wait for the threads to finish
|
|
59
|
+
stdout_thread.join()
|
|
60
|
+
stderr_thread.join()
|
|
61
|
+
|
|
62
|
+
# Join the captured output
|
|
63
|
+
stdout_output = "".join(stdout_lines)
|
|
64
|
+
stderr_output = "".join(stderr_lines)
|
|
65
|
+
|
|
66
|
+
# Check return code
|
|
67
|
+
if return_code == 0:
|
|
68
|
+
result["success"] = True
|
|
69
|
+
result["output"] = stdout_output
|
|
70
|
+
else:
|
|
71
|
+
result["error"] = stderr_output
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
import traceback
|
|
75
|
+
|
|
76
|
+
result["error"] = str(e) + "\n" + traceback.format_exc()
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Runner:
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
app_name: str,
|
|
84
|
+
local_mount_dir: str | Path = "./scripts",
|
|
85
|
+
remote_mount_dir: str | Path = "/scripts",
|
|
86
|
+
image_url: str | None = None,
|
|
87
|
+
packages: list[str] | None = None,
|
|
88
|
+
cpu: float | None = None,
|
|
89
|
+
gpu: str | None = None,
|
|
90
|
+
):
|
|
91
|
+
self.app_name = app_name # we use the LaminDB project name as the app name
|
|
92
|
+
self.app = self.create_modal_app(app_name)
|
|
93
|
+
|
|
94
|
+
self.local_mount_dir = local_mount_dir
|
|
95
|
+
self.remote_mount_dir = remote_mount_dir
|
|
96
|
+
|
|
97
|
+
self.image = self.create_modal_image(
|
|
98
|
+
local_dir=local_mount_dir, packages=packages, image_url=image_url
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
local_secrets = self._configure_local_secrets()
|
|
102
|
+
|
|
103
|
+
self.modal_function = self.app.function(
|
|
104
|
+
image=self.image, cpu=cpu, gpu=gpu, secrets=[local_secrets]
|
|
105
|
+
)(run_script)
|
|
106
|
+
|
|
107
|
+
def run(self, script_local_path: Path) -> None:
|
|
108
|
+
script_remote_path = self.local_to_remote_path(str(script_local_path))
|
|
109
|
+
with modal.enable_output(show_progress=True): # Prints out modal logs
|
|
110
|
+
with self.app.run():
|
|
111
|
+
self.modal_function.remote(Path(script_remote_path))
|
|
112
|
+
|
|
113
|
+
def create_modal_app(self, app_name: str) -> modal.App:
|
|
114
|
+
app = modal.App(app_name)
|
|
115
|
+
return app
|
|
116
|
+
|
|
117
|
+
def local_to_remote_path(self, local_path: str | Path) -> str:
|
|
118
|
+
local_path = Path(local_path).absolute()
|
|
119
|
+
local_mount_dir = Path(self.local_mount_dir).absolute()
|
|
120
|
+
remote_mount_dir = Path(self.remote_mount_dir)
|
|
121
|
+
|
|
122
|
+
# Check if local_path is inside local_mount_dir
|
|
123
|
+
try:
|
|
124
|
+
# This will raise ValueError if local_path is not relative to local_mount_dir
|
|
125
|
+
relative_path = local_path.relative_to(local_mount_dir)
|
|
126
|
+
except ValueError as err:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"Local path '{local_path}' is not inside the mount directory '{local_mount_dir}'"
|
|
129
|
+
) from err
|
|
130
|
+
|
|
131
|
+
# Join remote_mount_dir with the relative path
|
|
132
|
+
remote_path = remote_mount_dir / relative_path
|
|
133
|
+
|
|
134
|
+
# Return as string with normalized separators
|
|
135
|
+
return remote_path.as_posix()
|
|
136
|
+
|
|
137
|
+
def _configure_local_secrets(self) -> dict:
|
|
138
|
+
if ln_setup.settings.user.api_key is None:
|
|
139
|
+
raise ValueError("Please authenticate via: lamin login")
|
|
140
|
+
|
|
141
|
+
all_env_variables = {
|
|
142
|
+
"LAMIN_API_KEY": ln_setup.settings.user.api_key,
|
|
143
|
+
"LAMIN_CURRENT_PROJECT": self.app_name,
|
|
144
|
+
"LAMIN_CURRENT_INSTANCE": ln_setup.settings.instance.slug,
|
|
145
|
+
}
|
|
146
|
+
local_secrets = modal.Secret.from_dict(all_env_variables)
|
|
147
|
+
return local_secrets
|
|
148
|
+
|
|
149
|
+
def create_modal_image(
|
|
150
|
+
self,
|
|
151
|
+
python_version: str = "3.12",
|
|
152
|
+
packages: list[str] | None = None,
|
|
153
|
+
local_dir: str | Path = "./scripts",
|
|
154
|
+
remote_dir: str = "/scripts/",
|
|
155
|
+
image_url: str | None = None,
|
|
156
|
+
env_variables: dict | None = None,
|
|
157
|
+
) -> modal.Image:
|
|
158
|
+
if env_variables is None:
|
|
159
|
+
env_variables = {}
|
|
160
|
+
base_packages = ["lamindb", "httpx_retries"]
|
|
161
|
+
if packages is None:
|
|
162
|
+
packages = base_packages
|
|
163
|
+
else:
|
|
164
|
+
packages += base_packages
|
|
165
|
+
if image_url is None:
|
|
166
|
+
image = modal.Image.debian_slim(python_version=python_version)
|
|
167
|
+
else:
|
|
168
|
+
image = modal.Image.from_registry(image_url, add_python=python_version)
|
|
169
|
+
return (
|
|
170
|
+
image.pip_install(packages)
|
|
171
|
+
.env(env_variables)
|
|
172
|
+
.add_local_python_source("lamindb", "lamindb_setup", copy=True)
|
|
173
|
+
.add_local_dir(local_dir, remote_dir)
|
|
174
|
+
)
|
lamin_cli/urls.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
def decompose_url(url: str) -> tuple[str, str, str]:
|
|
2
|
-
assert any(
|
|
3
|
-
keyword in url for keyword in ["transform", "artifact", "collection", "record"]
|
|
4
|
-
)
|
|
5
|
-
for entity in ["transform", "artifact", "collection", "record"]:
|
|
6
|
-
if entity in url:
|
|
7
|
-
break
|
|
8
|
-
uid = url.split(f"{entity}/")[1]
|
|
9
|
-
instance_slug = "/".join(url.split("/")[3:5])
|
|
10
|
-
return instance_slug, entity, uid
|
|
1
|
+
def decompose_url(url: str) -> tuple[str, str, str]:
|
|
2
|
+
assert any(
|
|
3
|
+
keyword in url for keyword in ["transform", "artifact", "collection", "record"]
|
|
4
|
+
)
|
|
5
|
+
for entity in ["transform", "artifact", "collection", "record"]:
|
|
6
|
+
if entity in url:
|
|
7
|
+
break
|
|
8
|
+
uid = url.split(f"{entity}/")[1]
|
|
9
|
+
instance_slug = "/".join(url.split("/")[3:5])
|
|
10
|
+
return instance_slug, entity, uid
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: lamin_cli
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.1
|
|
4
4
|
Summary: Lamin CLI.
|
|
5
5
|
Author-email: Lamin Labs <open-source@lamin.ai>
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
7
8
|
Requires-Dist: rich-click>=1.7
|
|
8
9
|
Project-URL: Home, https://github.com/laminlabs/lamin-cli
|
|
9
10
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
lamin_cli/__init__.py,sha256=Y2VF3ydcKBUCWFHYlmXXl0KXH8Xo6OuvgzIKp8dAtx0,632
|
|
2
|
+
lamin_cli/__main__.py,sha256=09tbCQQx-6DgDpV84BfRQ0_PW_WAb3hLBcQBE7t62wI,28753
|
|
3
|
+
lamin_cli/_annotate.py,sha256=LCjZuxz1HuUhWjlSVl5vxOcmtctaYwpyVUOs8P2tL7I,1886
|
|
4
|
+
lamin_cli/_cache.py,sha256=HuNRa4yb2KibBoE2FVqtFmUvb3b3UnX78EowWEt025s,1096
|
|
5
|
+
lamin_cli/_context.py,sha256=hTcKialdtaaQC6b1RIQP0_uw927ycRlXepOrTB9H_Ws,2468
|
|
6
|
+
lamin_cli/_delete.py,sha256=_HewMItVM95_03TPkl-fkfcOW60CvUUlR3bF_cKYTRA,3146
|
|
7
|
+
lamin_cli/_io.py,sha256=5YF9-D999m6QB79J5pWA7agxZ52i30y2AzyWF_qsHBs,5322
|
|
8
|
+
lamin_cli/_load.py,sha256=8BPRdG2VBiGdmiOVvxCE3u4vHe5ORMCbfKcnVjCr9-I,8648
|
|
9
|
+
lamin_cli/_migration.py,sha256=GFOpiqh0esy7frR-sLymyY49KnQ-ATFCu1QXf1GXo7U,1289
|
|
10
|
+
lamin_cli/_save.py,sha256=SnM-SoeErYKg20sqfeWufXsqdam3MM9J6haAmqxFJqo,14156
|
|
11
|
+
lamin_cli/_settings.py,sha256=1GvwUscOWgT69uMGfo8z9IRlOYMdRessvl9E80pZB7E,4935
|
|
12
|
+
lamin_cli/urls.py,sha256=s1qi1X-IEbyIqBlYOuBtMMEdKdz-g9RBmXZO9WY6c0o,411
|
|
13
|
+
lamin_cli/clone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
lamin_cli/clone/_clone_verification.py,sha256=vaEj4FdCNCumZrC7gnoCImOa1nwWSf3cZ30B1zyowAg,1891
|
|
15
|
+
lamin_cli/clone/create_sqlite_clone_and_import_db.py,sha256=8ydAJTyZkDm5t6pRKDEb4K4S8zVzJYuwX61KGGlufGM,1629
|
|
16
|
+
lamin_cli/compute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
lamin_cli/compute/modal.py,sha256=HIsfBbKV4RMiZcoi8Uk2YOS-0O-QBygSWNLMYHwAwS0,6129
|
|
18
|
+
lamin_cli-1.12.1.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
19
|
+
lamin_cli-1.12.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
20
|
+
lamin_cli-1.12.1.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
21
|
+
lamin_cli-1.12.1.dist-info/METADATA,sha256=A0lBle_Wj9JGHNlG7NxijJ1tFHUZiycPGqgF6XfLMGk,360
|
|
22
|
+
lamin_cli-1.12.1.dist-info/RECORD,,
|