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.
@@ -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.3
1
+ Metadata-Version: 2.4
2
2
  Name: lamin_cli
3
- Version: 1.12.0
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.10.1
2
+ Generator: flit 3.12.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any