lamin_cli 0.17.6__tar.gz → 0.17.8__tar.gz
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-0.17.6 → lamin_cli-0.17.8}/PKG-INFO +1 -1
- lamin_cli-0.17.8/lamin_cli/__init__.py +3 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/lamin_cli/__main__.py +111 -94
- lamin_cli-0.17.8/lamin_cli/_load.py +110 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/lamin_cli/_migration.py +1 -1
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/lamin_cli/_save.py +20 -17
- lamin_cli-0.17.8/lamin_cli/_settings.py +41 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +1 -2
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +1 -1
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/scripts/run-track-and-finish-sync-git.py +2 -2
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/scripts/run-track-and-finish.py +3 -2
- lamin_cli-0.17.8/tests/scripts/run-track-with-params.py +21 -0
- lamin_cli-0.17.6/tests/test_get.py → lamin_cli-0.17.8/tests/test_load.py +13 -9
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_migrate.py +4 -2
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_save_notebooks.py +7 -8
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_save_scripts.py +89 -12
- lamin_cli-0.17.6/lamin_cli/__init__.py +0 -3
- lamin_cli-0.17.6/lamin_cli/_get.py +0 -82
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/.github/workflows/doc-changes.yml +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/.gitignore +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/.pre-commit-config.yaml +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/LICENSE +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/README.md +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/lamin_cli/_cache.py +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/pyproject.toml +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/conftest.py +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/notebooks/not-initialized.ipynb +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/scripts/merely-import-lamindb.py +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_cli.py +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_multi_process.py +0 -0
- {lamin_cli-0.17.6 → lamin_cli-0.17.8}/tests/test_save_files.py +0 -0
|
@@ -35,26 +35,28 @@ else:
|
|
|
35
35
|
COMMAND_GROUPS = {
|
|
36
36
|
"lamin": [
|
|
37
37
|
{
|
|
38
|
-
"name": "
|
|
38
|
+
"name": "Connect to an instance",
|
|
39
39
|
"commands": [
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"load",
|
|
40
|
+
"connect",
|
|
41
|
+
"disconnect",
|
|
43
42
|
"info",
|
|
44
|
-
"
|
|
43
|
+
"init",
|
|
45
44
|
],
|
|
46
45
|
},
|
|
47
46
|
{
|
|
48
|
-
"name": "
|
|
49
|
-
"commands": ["
|
|
47
|
+
"name": "Read & write data",
|
|
48
|
+
"commands": ["load", "save", "get", "delete"],
|
|
50
49
|
},
|
|
51
50
|
{
|
|
52
|
-
"name": "
|
|
53
|
-
"commands": ["cache", "
|
|
51
|
+
"name": "Configure",
|
|
52
|
+
"commands": ["cache", "settings", "migrate"],
|
|
54
53
|
},
|
|
55
54
|
{
|
|
56
|
-
"name": "
|
|
57
|
-
"commands": [
|
|
55
|
+
"name": "Auth",
|
|
56
|
+
"commands": [
|
|
57
|
+
"login",
|
|
58
|
+
"logout",
|
|
59
|
+
],
|
|
58
60
|
},
|
|
59
61
|
]
|
|
60
62
|
}
|
|
@@ -63,7 +65,7 @@ else:
|
|
|
63
65
|
@click.rich_config(
|
|
64
66
|
help_config=click.RichHelpConfiguration(
|
|
65
67
|
command_groups=COMMAND_GROUPS,
|
|
66
|
-
style_commands_table_column_width_ratio=(1,
|
|
68
|
+
style_commands_table_column_width_ratio=(1, 10),
|
|
67
69
|
)
|
|
68
70
|
)
|
|
69
71
|
@click.group()
|
|
@@ -77,6 +79,7 @@ else:
|
|
|
77
79
|
from click import Command, Context
|
|
78
80
|
from lamindb_setup._silence_loggers import silence_loggers
|
|
79
81
|
|
|
82
|
+
from lamin_cli._settings import settings
|
|
80
83
|
from lamin_cli._cache import cache
|
|
81
84
|
from lamin_cli._migration import migrate
|
|
82
85
|
|
|
@@ -96,44 +99,34 @@ def main():
|
|
|
96
99
|
@main.command()
|
|
97
100
|
@click.argument("user", type=str, default=None, required=False)
|
|
98
101
|
@click.option("--key", type=str, default=None, help="The API key.")
|
|
99
|
-
|
|
100
|
-
def login(user: str, key: Optional[str], logout: bool = False):
|
|
102
|
+
def login(user: str, key: Optional[str]):
|
|
101
103
|
"""Log into LaminHub.
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
lamin login myemail@acme.com --key YOUR_API_KEY
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
You'll find your API key on LaminHub in the top right corner under "Settings".
|
|
110
|
-
|
|
111
|
-
After this, you can either use `lamin login myhandle` or `lamin login myemail@acme.com`
|
|
105
|
+
`lamin login` prompts for your API key unless you set it via environment variable `LAMIN_API_KEY`.
|
|
112
106
|
|
|
113
|
-
You
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
lamin login
|
|
117
|
-
```
|
|
107
|
+
You find your API key in your account settings on LaminHub (top right corner).
|
|
118
108
|
|
|
119
|
-
|
|
109
|
+
After authenticating once, you can re-authenticate and switch between accounts via `lamin login myhandle`.
|
|
120
110
|
"""
|
|
121
|
-
|
|
122
|
-
from lamindb_setup._setup_user import logout as logout_func
|
|
111
|
+
from lamindb_setup._setup_user import login as login_
|
|
123
112
|
|
|
124
|
-
|
|
113
|
+
if user is None:
|
|
114
|
+
if "LAMIN_API_KEY" in os.environ:
|
|
115
|
+
api_key = os.environ["LAMIN_API_KEY"]
|
|
116
|
+
else:
|
|
117
|
+
api_key = input("Your API key: ")
|
|
125
118
|
else:
|
|
126
|
-
|
|
119
|
+
api_key = None
|
|
127
120
|
|
|
128
|
-
|
|
129
|
-
if "LAMIN_API_KEY" in os.environ:
|
|
130
|
-
api_key = os.environ["LAMIN_API_KEY"]
|
|
131
|
-
else:
|
|
132
|
-
api_key = input("Your API key: ")
|
|
133
|
-
else:
|
|
134
|
-
api_key = None
|
|
121
|
+
return login_(user, key=key, api_key=api_key)
|
|
135
122
|
|
|
136
|
-
|
|
123
|
+
|
|
124
|
+
@main.command()
|
|
125
|
+
def logout():
|
|
126
|
+
"""Log out of LaminHub."""
|
|
127
|
+
from lamindb_setup import logout as logout_
|
|
128
|
+
|
|
129
|
+
return logout_()
|
|
137
130
|
|
|
138
131
|
|
|
139
132
|
# fmt: off
|
|
@@ -144,7 +137,7 @@ def login(user: str, key: Optional[str], logout: bool = False):
|
|
|
144
137
|
@click.option("--name", type=str, default=None, help="The instance name.")
|
|
145
138
|
# fmt: on
|
|
146
139
|
def init(storage: str, db: Optional[str], schema: Optional[str], name: Optional[str]):
|
|
147
|
-
"""Init
|
|
140
|
+
"""Init an instance."""
|
|
148
141
|
from lamindb_setup._init_instance import init as init_
|
|
149
142
|
|
|
150
143
|
return init_(storage=storage, db=db, schema=schema, name=name)
|
|
@@ -152,26 +145,32 @@ def init(storage: str, db: Optional[str], schema: Optional[str], name: Optional[
|
|
|
152
145
|
|
|
153
146
|
# fmt: off
|
|
154
147
|
@main.command()
|
|
155
|
-
@click.argument("instance", type=str
|
|
156
|
-
@click.option("--unload", is_flag=True, help="Unload the current instance.")
|
|
148
|
+
@click.argument("instance", type=str)
|
|
157
149
|
# fmt: on
|
|
158
|
-
def
|
|
159
|
-
"""
|
|
150
|
+
def connect(instance: str):
|
|
151
|
+
"""Connect to an instance.
|
|
152
|
+
|
|
153
|
+
Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
|
|
160
154
|
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
`lamin connect` switches
|
|
156
|
+
{attr}`~lamindb.setup.core.SetupSettings.auto_connect` to `True` so that you
|
|
157
|
+
auto-connect in a Python session upon importing `lamindb`.
|
|
163
158
|
"""
|
|
164
|
-
|
|
165
|
-
from lamindb_setup._close import close as close_
|
|
159
|
+
from lamindb_setup import settings as settings_, connect as connect_
|
|
166
160
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if instance is None:
|
|
170
|
-
raise click.UsageError("INSTANCE is required when loading an instance.")
|
|
171
|
-
from lamindb_setup import settings, connect
|
|
161
|
+
settings_.auto_connect = True
|
|
162
|
+
return connect_(slug=instance)
|
|
172
163
|
|
|
173
|
-
|
|
174
|
-
|
|
164
|
+
|
|
165
|
+
@main.command()
|
|
166
|
+
def disconnect():
|
|
167
|
+
"""Disconnect from an instance.
|
|
168
|
+
|
|
169
|
+
Is the opposite of connecting to an instance.
|
|
170
|
+
"""
|
|
171
|
+
from lamindb_setup import close as close_
|
|
172
|
+
|
|
173
|
+
return close_()
|
|
175
174
|
|
|
176
175
|
|
|
177
176
|
@main.command()
|
|
@@ -181,12 +180,12 @@ def info(schema: bool):
|
|
|
181
180
|
if schema:
|
|
182
181
|
from lamindb_setup._schema import view
|
|
183
182
|
|
|
184
|
-
|
|
183
|
+
click.echo("Open in browser: http://127.0.0.1:8000/schema/")
|
|
185
184
|
return view()
|
|
186
185
|
else:
|
|
187
|
-
import
|
|
186
|
+
from lamindb_setup import settings as settings_
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
click.echo(settings_)
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
# fmt: off
|
|
@@ -195,7 +194,10 @@ def info(schema: bool):
|
|
|
195
194
|
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation.") # noqa: E501
|
|
196
195
|
# fmt: on
|
|
197
196
|
def delete(instance: str, force: bool = False):
|
|
198
|
-
"""Delete an
|
|
197
|
+
"""Delete an entity.
|
|
198
|
+
|
|
199
|
+
Currently only supports instance deletion.
|
|
200
|
+
"""
|
|
199
201
|
from lamindb_setup._delete import delete
|
|
200
202
|
|
|
201
203
|
return delete(instance, force=force)
|
|
@@ -208,23 +210,52 @@ def delete(instance: str, force: bool = False):
|
|
|
208
210
|
@click.option(
|
|
209
211
|
"--with-env", is_flag=True, help="Also return the environment for a tranform."
|
|
210
212
|
)
|
|
211
|
-
def
|
|
212
|
-
"""
|
|
213
|
+
def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
214
|
+
"""Load a file or folder.
|
|
213
215
|
|
|
214
216
|
Pass a URL, `artifact`, or `transform`. For example:
|
|
215
217
|
|
|
216
218
|
```
|
|
217
|
-
lamin
|
|
218
|
-
lamin
|
|
219
|
-
lamin
|
|
220
|
-
lamin
|
|
221
|
-
lamin
|
|
222
|
-
lamin
|
|
219
|
+
lamin load https://lamin.ai/account/instance/artifact/e2G7k9EVul4JbfsEYAy5
|
|
220
|
+
lamin load artifact --key mydatasets/mytable.parquet
|
|
221
|
+
lamin load artifact --uid e2G7k9EVul4JbfsEYAy5
|
|
222
|
+
lamin load transform --key analysis.ipynb
|
|
223
|
+
lamin load transform --uid Vul4JbfsEYAy5
|
|
224
|
+
lamin load transform --uid Vul4JbfsEYAy5 --with-env
|
|
223
225
|
```
|
|
224
226
|
"""
|
|
225
|
-
|
|
227
|
+
is_slug = entity.count("/") == 1
|
|
228
|
+
if is_slug:
|
|
229
|
+
from lamindb_setup import settings as settings_, connect
|
|
230
|
+
|
|
231
|
+
# can decide whether we want to actually deprecate
|
|
232
|
+
# click.echo(
|
|
233
|
+
# f"! please use: lamin connect {entity}"
|
|
234
|
+
# )
|
|
235
|
+
settings_.auto_connect = True
|
|
236
|
+
return connect(slug=entity)
|
|
237
|
+
else:
|
|
238
|
+
from lamin_cli._load import load as load_
|
|
226
239
|
|
|
227
|
-
|
|
240
|
+
return load_(entity, uid=uid, key=key, with_env=with_env)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@main.command()
|
|
244
|
+
@click.argument("entity", type=str)
|
|
245
|
+
@click.option("--uid", help="The uid for the entity.")
|
|
246
|
+
@click.option("--key", help="The key for the entity.")
|
|
247
|
+
@click.option(
|
|
248
|
+
"--with-env", is_flag=True, help="Also return the environment for a tranform."
|
|
249
|
+
)
|
|
250
|
+
def get(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
251
|
+
"""Query metadata about an entity.
|
|
252
|
+
|
|
253
|
+
Currently only works for artifact & transform and behaves like `lamin load`.
|
|
254
|
+
"""
|
|
255
|
+
from lamin_cli._load import load as load_
|
|
256
|
+
|
|
257
|
+
click.echo(f"! to load a file or folder, please use: lamin load {entity}")
|
|
258
|
+
return load_(entity, uid=uid, key=key, with_env=with_env)
|
|
228
259
|
|
|
229
260
|
|
|
230
261
|
@main.command()
|
|
@@ -235,36 +266,22 @@ def get(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
235
266
|
@click.option("--description", type=str, default=None)
|
|
236
267
|
@click.option("--registry", type=str, default=None)
|
|
237
268
|
def save(filepath: str, key: str, description: str, registry: str):
|
|
238
|
-
"""Save file or folder.
|
|
269
|
+
"""Save a file or folder.
|
|
270
|
+
|
|
271
|
+
Defaults to saving `.py` and `.ipynb` as :class:`~lamindb.Transform` and
|
|
272
|
+
other file types and folders as :class:`~lamindb.Artifact`.
|
|
273
|
+
|
|
274
|
+
You can save a `.py` or `.ipynb` file as an :class:`~lamindb.Artifact` by
|
|
275
|
+
passing `--registry artifact`.
|
|
276
|
+
"""
|
|
239
277
|
from lamin_cli._save import save_from_filepath_cli
|
|
240
278
|
|
|
241
279
|
if save_from_filepath_cli(filepath, key, description, registry) is not None:
|
|
242
280
|
sys.exit(1)
|
|
243
281
|
|
|
244
282
|
|
|
283
|
+
main.add_command(settings)
|
|
245
284
|
main.add_command(cache)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
@main.command(name="set")
|
|
249
|
-
@click.argument(
|
|
250
|
-
"setting",
|
|
251
|
-
type=click.Choice(["auto-connect", "private-django-api"], case_sensitive=False),
|
|
252
|
-
)
|
|
253
|
-
@click.argument("value", type=click.BOOL)
|
|
254
|
-
def set_(setting: str, value: bool):
|
|
255
|
-
"""Update settings.
|
|
256
|
-
|
|
257
|
-
- `auto-connect` → {attr}`~lamindb.setup.core.SetupSettings.auto_connect`
|
|
258
|
-
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
259
|
-
"""
|
|
260
|
-
from lamindb_setup import settings
|
|
261
|
-
|
|
262
|
-
if setting == "auto-connect":
|
|
263
|
-
settings.auto_connect = value
|
|
264
|
-
if setting == "private-django-api":
|
|
265
|
-
settings.private_django_api = value
|
|
266
|
-
|
|
267
|
-
|
|
268
285
|
main.add_command(migrate)
|
|
269
286
|
|
|
270
287
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
from lamin_utils import logger
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def decompose_url(url: str) -> Tuple[str, str, str]:
|
|
8
|
+
assert "transform" in url or "artifact" in url
|
|
9
|
+
for entity in ["transform", "artifact"]:
|
|
10
|
+
if entity in url:
|
|
11
|
+
break
|
|
12
|
+
uid = url.split(f"{entity}/")[1]
|
|
13
|
+
instance_slug = "/".join(url.split("/")[3:5])
|
|
14
|
+
return instance_slug, entity, uid
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
18
|
+
import lamindb_setup as ln_setup
|
|
19
|
+
|
|
20
|
+
if entity.startswith("https://") and "lamin" in entity:
|
|
21
|
+
url = entity
|
|
22
|
+
instance, entity, uid = decompose_url(url)
|
|
23
|
+
elif entity not in {"artifact", "transform"}:
|
|
24
|
+
raise SystemExit("Entity has to be a laminhub URL or 'artifact' or 'transform'")
|
|
25
|
+
else:
|
|
26
|
+
instance = ln_setup.settings.instance.slug
|
|
27
|
+
|
|
28
|
+
ln_setup.connect(instance)
|
|
29
|
+
from lnschema_core import models as ln
|
|
30
|
+
|
|
31
|
+
def script_to_notebook(
|
|
32
|
+
transform: ln.Transform, notebook_path: Path, bump_revision: bool = False
|
|
33
|
+
) -> None:
|
|
34
|
+
import jupytext
|
|
35
|
+
from lamin_utils._base62 import increment_base62
|
|
36
|
+
|
|
37
|
+
py_content = transform.source_code.replace(
|
|
38
|
+
"# # transform.name", f"# # {transform.name}"
|
|
39
|
+
)
|
|
40
|
+
if bump_revision:
|
|
41
|
+
uid = transform.uid
|
|
42
|
+
new_uid = f"{uid[:-4]}{increment_base62(uid[-4:])}"
|
|
43
|
+
py_content = py_content.replace(uid, new_uid)
|
|
44
|
+
logger.important(f"updated uid: {uid} → {new_uid}")
|
|
45
|
+
notebook = jupytext.reads(py_content, fmt="py:percent")
|
|
46
|
+
jupytext.write(notebook, notebook_path)
|
|
47
|
+
|
|
48
|
+
if entity == "transform":
|
|
49
|
+
transform = (
|
|
50
|
+
ln.Transform.objects.get(uid=uid)
|
|
51
|
+
if uid is not None
|
|
52
|
+
# if below, we take is_latest=True as the criterion, we might get draft notebooks
|
|
53
|
+
# hence, we use source_code__isnull=False and order by created_at instead
|
|
54
|
+
else ln.Transform.objects.filter(key=key, source_code__isnull=False)
|
|
55
|
+
.order_by("-created_at")
|
|
56
|
+
.first()
|
|
57
|
+
)
|
|
58
|
+
target_filename = transform.key
|
|
59
|
+
if Path(target_filename).exists():
|
|
60
|
+
response = input(f"! {target_filename} exists: replace? (y/n)")
|
|
61
|
+
if response != "y":
|
|
62
|
+
raise SystemExit("Aborted.")
|
|
63
|
+
if transform._source_code_artifact_id is not None: # backward compat
|
|
64
|
+
# need lamindb here to have .cache() available
|
|
65
|
+
import lamindb as ln
|
|
66
|
+
|
|
67
|
+
ln.settings.track_run_inputs = False
|
|
68
|
+
filepath_cache = transform._source_code_artifact.cache()
|
|
69
|
+
if not target_filename.endswith(transform._source_code_artifact.suffix):
|
|
70
|
+
target_filename += transform._source_code_artifact.suffix
|
|
71
|
+
filepath_cache.rename(target_filename)
|
|
72
|
+
elif transform.source_code is not None:
|
|
73
|
+
if transform.key.endswith(".ipynb"):
|
|
74
|
+
script_to_notebook(transform, target_filename, bump_revision=True)
|
|
75
|
+
else:
|
|
76
|
+
Path(target_filename).write_text(transform.source_code)
|
|
77
|
+
else:
|
|
78
|
+
raise SystemExit("No source code available for this transform.")
|
|
79
|
+
logger.important(f"{transform.type} is here: {target_filename}")
|
|
80
|
+
if with_env:
|
|
81
|
+
import lamindb as ln
|
|
82
|
+
|
|
83
|
+
ln.settings.track_run_inputs = False
|
|
84
|
+
if (
|
|
85
|
+
transform.latest_run is not None
|
|
86
|
+
and transform.latest_run.environment is not None
|
|
87
|
+
):
|
|
88
|
+
filepath_env_cache = transform.latest_run.environment.cache()
|
|
89
|
+
target_env_filename = (
|
|
90
|
+
".".join(target_filename.split(".")[:-1]) + "__requirements.txt"
|
|
91
|
+
)
|
|
92
|
+
filepath_env_cache.rename(target_env_filename)
|
|
93
|
+
logger.important(target_env_filename)
|
|
94
|
+
else:
|
|
95
|
+
logger.warning("latest transform run with environment doesn't exist")
|
|
96
|
+
return target_filename
|
|
97
|
+
elif entity == "artifact":
|
|
98
|
+
import lamindb as ln
|
|
99
|
+
|
|
100
|
+
ln.settings.track_run_inputs = False
|
|
101
|
+
artifact = (
|
|
102
|
+
ln.Artifact.get(uid)
|
|
103
|
+
if uid is not None
|
|
104
|
+
else ln.Artifact.filter(key=key, source_code__isnull=False)
|
|
105
|
+
.order_by("-created_at")
|
|
106
|
+
.first()
|
|
107
|
+
)
|
|
108
|
+
cache_path = artifact.cache()
|
|
109
|
+
logger.important(f"artifact is here: {cache_path}")
|
|
110
|
+
return cache_path
|
|
@@ -6,20 +6,20 @@ from lamin_utils import logger
|
|
|
6
6
|
import re
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
9
|
+
def parse_uid_from_code(
|
|
10
|
+
content: str, suffix: str
|
|
11
11
|
) -> tuple[str | None, str | None, str | None]:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
content = file.read()
|
|
15
|
-
|
|
16
|
-
if file_path.suffix == ".py":
|
|
12
|
+
if suffix == ".py":
|
|
13
|
+
track_pattern = re.compile(r'ln\.track\(\s*(?:uid\s*=\s*)?["\']([^"\']+)["\']')
|
|
17
14
|
uid_pattern = re.compile(r'\.context\.uid\s*=\s*["\']([^"\']+)["\']')
|
|
18
15
|
stem_uid_pattern = re.compile(
|
|
19
16
|
r'\.transform\.stem_uid\s*=\s*["\']([^"\']+)["\']'
|
|
20
17
|
)
|
|
21
18
|
version_pattern = re.compile(r'\.transform\.version\s*=\s*["\']([^"\']+)["\']')
|
|
22
|
-
elif
|
|
19
|
+
elif suffix == ".ipynb":
|
|
20
|
+
track_pattern = re.compile(
|
|
21
|
+
r'ln\.track\(\s*(?:uid\s*=\s*)?\\["\']([^"\']+)\\["\']'
|
|
22
|
+
)
|
|
23
23
|
uid_pattern = re.compile(r'\.context\.uid\s*=\s*\\["\']([^"\']+)\\["\']')
|
|
24
24
|
stem_uid_pattern = re.compile(
|
|
25
25
|
r'\.transform\.stem_uid\s*=\s*\\["\']([^"\']+)\\["\']'
|
|
@@ -31,7 +31,10 @@ def get_stem_uid_and_version_from_file(
|
|
|
31
31
|
raise ValueError("Only .py and .ipynb files are supported.")
|
|
32
32
|
|
|
33
33
|
# Search for matches in the entire file content
|
|
34
|
-
uid_match =
|
|
34
|
+
uid_match = track_pattern.search(content)
|
|
35
|
+
uid = uid_match.group(1) if uid_match else None
|
|
36
|
+
if uid is None:
|
|
37
|
+
uid_match = uid_pattern.search(content)
|
|
35
38
|
stem_uid_match = stem_uid_pattern.search(content)
|
|
36
39
|
version_match = version_pattern.search(content)
|
|
37
40
|
|
|
@@ -42,8 +45,8 @@ def get_stem_uid_and_version_from_file(
|
|
|
42
45
|
|
|
43
46
|
if uid is None and (stem_uid is None or version is None):
|
|
44
47
|
raise SystemExit(
|
|
45
|
-
"
|
|
46
|
-
|
|
48
|
+
"Cannot infer transform uid."
|
|
49
|
+
"\nCall `ln.track()` and copy/paste the output"
|
|
47
50
|
" into the notebook"
|
|
48
51
|
)
|
|
49
52
|
return uid, stem_uid, version
|
|
@@ -86,20 +89,20 @@ def save_from_filepath_cli(
|
|
|
86
89
|
logger.important(f"go to: https://lamin.ai/{slug}/artifact/{artifact.uid}")
|
|
87
90
|
return None
|
|
88
91
|
elif registry == "transform":
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
with open(filepath) as file:
|
|
93
|
+
content = file.read()
|
|
94
|
+
uid, stem_uid, version = parse_uid_from_code(content, filepath.suffix)
|
|
95
|
+
logger.important(f"mapped '{filepath}' on uid '{uid}'")
|
|
91
96
|
if uid is not None:
|
|
92
97
|
transform = ln.Transform.filter(uid=uid).one_or_none()
|
|
93
98
|
if transform is None:
|
|
94
99
|
logger.error(
|
|
95
100
|
f"Did not find uid '{uid}'"
|
|
96
|
-
" in Transform registry. Did you run ln.
|
|
101
|
+
" in Transform registry. Did you run `ln.track()`?"
|
|
97
102
|
)
|
|
98
103
|
return "not-tracked-in-transform-registry"
|
|
99
104
|
else:
|
|
100
|
-
transform = ln.Transform.get(
|
|
101
|
-
uid__startswith=stem_uid, version=transform_version
|
|
102
|
-
)
|
|
105
|
+
transform = ln.Transform.get(uid__startswith=stem_uid, version=version)
|
|
103
106
|
# latest run of this transform by user
|
|
104
107
|
run = ln.Run.filter(transform=transform).order_by("-started_at").first()
|
|
105
108
|
if run.created_by.id != ln_setup.settings.user.id:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
if os.environ.get("NO_RICH"):
|
|
5
|
+
import click as click
|
|
6
|
+
else:
|
|
7
|
+
import rich_click as click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(invoke_without_command=True)
|
|
11
|
+
@click.pass_context
|
|
12
|
+
def settings(ctx):
|
|
13
|
+
"""Manage settings.
|
|
14
|
+
|
|
15
|
+
Call without subcommands and options to show settings.
|
|
16
|
+
"""
|
|
17
|
+
if ctx.invoked_subcommand is None:
|
|
18
|
+
from lamindb_setup import settings as settings_
|
|
19
|
+
|
|
20
|
+
click.echo("Configure: see `lamin settings --help`")
|
|
21
|
+
click.echo(settings_)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@settings.command("set")
|
|
25
|
+
@click.argument(
|
|
26
|
+
"setting",
|
|
27
|
+
type=click.Choice(["auto-connect", "private-django-api"], case_sensitive=False),
|
|
28
|
+
)
|
|
29
|
+
@click.argument("value", type=click.BOOL)
|
|
30
|
+
def set(setting: str, value: bool):
|
|
31
|
+
"""Update settings.
|
|
32
|
+
|
|
33
|
+
- `auto-connect` → {attr}`~lamindb.setup.core.SetupSettings.auto_connect`
|
|
34
|
+
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
35
|
+
"""
|
|
36
|
+
from lamindb_setup import settings as settings_
|
|
37
|
+
|
|
38
|
+
if setting == "auto-connect":
|
|
39
|
+
settings_.auto_connect = value
|
|
40
|
+
if setting == "private-django-api":
|
|
41
|
+
settings_.private_django_api = value
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import lamindb as ln
|
|
2
2
|
|
|
3
3
|
ln.settings.sync_git_repo = "https://github.com/laminlabs/lamin-cli"
|
|
4
|
-
ln.context.uid = "m5uCHTTpJnjQ0000"
|
|
5
4
|
ln.context.name = "My good script"
|
|
5
|
+
ln.track("m5uCHTTpJnjQ0000")
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
if __name__ == "__main__":
|
|
9
9
|
# we're using new_run here to mock the notebook situation
|
|
10
10
|
# and cover the look up of an existing run in the tests
|
|
11
11
|
# new_run = True is trivial
|
|
12
|
-
ln.
|
|
12
|
+
ln.track(new_run=False)
|
|
13
13
|
|
|
14
14
|
print("hello!")
|
|
15
15
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import lamindb as ln
|
|
2
2
|
|
|
3
|
-
ln.context.uid = "VFYCIuaw2GsX0000"
|
|
4
3
|
ln.context.name = "My good script 2"
|
|
4
|
+
ln.track("VFYCIuaw2GsX0000")
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
if __name__ == "__main__":
|
|
7
8
|
# we're using new_run here to mock the notebook situation
|
|
8
9
|
# and cover the look up of an existing run in the tests
|
|
9
10
|
# new_run = True is trivial
|
|
10
|
-
ln.
|
|
11
|
+
ln.track(new_run=False)
|
|
11
12
|
|
|
12
13
|
print("hello!")
|
|
13
14
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import lamindb as ln
|
|
3
|
+
|
|
4
|
+
if __name__ == "__main__":
|
|
5
|
+
p = argparse.ArgumentParser()
|
|
6
|
+
p.add_argument("--dataset-key", type=str)
|
|
7
|
+
p.add_argument("--downsample", action="store_true")
|
|
8
|
+
p.add_argument("--learning-rate", type=float)
|
|
9
|
+
args = p.parse_args()
|
|
10
|
+
|
|
11
|
+
params = {
|
|
12
|
+
"dataset_key": args.dataset_key,
|
|
13
|
+
"learning_rate": args.learning_rate,
|
|
14
|
+
"downsample": args.downsample,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ln.track("JjRF4mACd9m00000", params=params)
|
|
18
|
+
|
|
19
|
+
# actual code
|
|
20
|
+
|
|
21
|
+
ln.finish()
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
from lamin_cli.
|
|
1
|
+
from lamin_cli._load import decompose_url
|
|
2
2
|
import subprocess
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def test_decompose_url():
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
urls = [
|
|
7
|
+
"https://lamin.ai/laminlabs/arrayloader-benchmarks/transform/1GCKs8zLtkc85zKv", # noqa
|
|
8
|
+
"https://lamin.company.com/laminlabs/arrayloader-benchmarks/transform/1GCKs8zLtkc85zKv", # noqa
|
|
9
|
+
]
|
|
10
|
+
for url in urls:
|
|
11
|
+
result = decompose_url(url)
|
|
12
|
+
instance_slug, entity, uid = result
|
|
13
|
+
assert instance_slug == "laminlabs/arrayloader-benchmarks"
|
|
14
|
+
assert entity == "transform"
|
|
15
|
+
assert uid == "1GCKs8zLtkc85zKv"
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def test_get_transform():
|
|
15
19
|
result = subprocess.run(
|
|
16
|
-
"lamin
|
|
20
|
+
"lamin load"
|
|
17
21
|
" 'https://lamin.ai/laminlabs/arrayloader-benchmarks/transform/1GCKs8zLtkc85zKv'"
|
|
18
22
|
" --with-env", # noqa
|
|
19
23
|
shell=True,
|
|
@@ -24,7 +28,7 @@ def test_get_transform():
|
|
|
24
28
|
|
|
25
29
|
def test_get_artifact():
|
|
26
30
|
result = subprocess.run(
|
|
27
|
-
"lamin
|
|
31
|
+
"lamin load"
|
|
28
32
|
" 'https://lamin.ai/laminlabs/lamin-site-assets/artifact/e2G7k9EVul4JbfsEYAy5'", # noqa
|
|
29
33
|
shell=True,
|
|
30
34
|
capture_output=True,
|
|
@@ -15,7 +15,9 @@ def test_migrate_deploy():
|
|
|
15
15
|
import lamindb as ln
|
|
16
16
|
|
|
17
17
|
instance_slug = ln.setup.settings.instance.slug
|
|
18
|
-
exit_status = os.system(
|
|
18
|
+
exit_status = os.system(
|
|
19
|
+
"lamin connect testuser1/static-test-instance-private-sqlite"
|
|
20
|
+
)
|
|
19
21
|
assert exit_status == 0
|
|
20
22
|
exit_status = os.system("lamin migrate deploy")
|
|
21
23
|
assert exit_status == 0
|
|
@@ -27,7 +29,7 @@ def test_migrate_deploy():
|
|
|
27
29
|
# )
|
|
28
30
|
# import lamindb
|
|
29
31
|
# assert instance["lamindb_version"] == lamindb.__version__
|
|
30
|
-
exit_status = os.system(f"lamin
|
|
32
|
+
exit_status = os.system(f"lamin connect {instance_slug}")
|
|
31
33
|
assert exit_status == 0
|
|
32
34
|
|
|
33
35
|
|
|
@@ -22,7 +22,7 @@ def test_save_not_initialized():
|
|
|
22
22
|
)
|
|
23
23
|
assert result.returncode == 1
|
|
24
24
|
assert (
|
|
25
|
-
"Call ln.
|
|
25
|
+
"Call `ln.track()` and copy/paste the output into the notebook"
|
|
26
26
|
in result.stderr.decode()
|
|
27
27
|
)
|
|
28
28
|
|
|
@@ -70,7 +70,7 @@ def test_save_consecutive():
|
|
|
70
70
|
transform = ln.Transform.filter(uid="hlsFXswrJjtt0000").one_or_none()
|
|
71
71
|
assert transform is None
|
|
72
72
|
|
|
73
|
-
# let's try to save a notebook for which `ln.
|
|
73
|
+
# let's try to save a notebook for which `ln.track()` was never run
|
|
74
74
|
result = subprocess.run(
|
|
75
75
|
f"lamin save {notebook_path}",
|
|
76
76
|
shell=True,
|
|
@@ -80,7 +80,7 @@ def test_save_consecutive():
|
|
|
80
80
|
assert result.returncode == 1
|
|
81
81
|
assert "Did not find uid 'hlsFXswrJjtt0000'" in result.stdout.decode()
|
|
82
82
|
|
|
83
|
-
# now, let's re-run this notebook so that ln.
|
|
83
|
+
# now, let's re-run this notebook so that `ln.track()` is actually run
|
|
84
84
|
nbproject_test.execute_notebooks(notebook_path, print_outputs=True)
|
|
85
85
|
|
|
86
86
|
# now, there is a transform record, but we're missing all artifacts
|
|
@@ -113,14 +113,13 @@ def test_save_consecutive():
|
|
|
113
113
|
import lamindb as ln
|
|
114
114
|
|
|
115
115
|
# %%
|
|
116
|
-
ln.
|
|
117
|
-
ln.context.track()
|
|
116
|
+
ln.track("hlsFXswrJjtt0000")
|
|
118
117
|
|
|
119
118
|
# %%
|
|
120
119
|
print("my consecutive cell")
|
|
121
120
|
"""
|
|
122
121
|
)
|
|
123
|
-
assert transform.hash == "
|
|
122
|
+
assert transform.hash == "OwVL-0-_gmk8heR3zV7BkA"
|
|
124
123
|
# below is the test that we can use if store the run repot as `.ipynb`
|
|
125
124
|
# and not as html as we do right now
|
|
126
125
|
assert transform.latest_run.report.suffix == ".html"
|
|
@@ -168,13 +167,13 @@ print("my consecutive cell")
|
|
|
168
167
|
transform = ln.Transform.get("hlsFXswrJjtt0000")
|
|
169
168
|
assert transform.latest_run.report.path.exists()
|
|
170
169
|
assert transform.latest_run.report.path == transform.latest_run.report.path
|
|
171
|
-
assert transform.hash == "
|
|
170
|
+
assert transform.hash == "BhQpym0JfeypqhVMPlQ0ng"
|
|
172
171
|
assert transform.latest_run.environment.path.exists()
|
|
173
172
|
assert transform._source_code_artifact is None
|
|
174
173
|
|
|
175
174
|
# get the the source code via command line
|
|
176
175
|
result = subprocess.run(
|
|
177
|
-
"lamin
|
|
176
|
+
"yes | lamin load"
|
|
178
177
|
f" https://lamin.ai/{ln.setup.settings.user.handle}/laminci-unit-tests/transform/hlsFXswrJjtt0000", # noqa
|
|
179
178
|
shell=True,
|
|
180
179
|
capture_output=True,
|
|
@@ -21,7 +21,7 @@ def test_run_save_cache():
|
|
|
21
21
|
)
|
|
22
22
|
# print(result.stdout.decode())
|
|
23
23
|
assert result.returncode == 1
|
|
24
|
-
assert "Did you run ln.
|
|
24
|
+
assert "Did you run `ln.track()`?" in result.stdout.decode()
|
|
25
25
|
|
|
26
26
|
# run the script
|
|
27
27
|
result = subprocess.run(
|
|
@@ -33,11 +33,11 @@ def test_run_save_cache():
|
|
|
33
33
|
# print(result.stderr.decode())
|
|
34
34
|
assert result.returncode == 0
|
|
35
35
|
assert "created Transform" in result.stdout.decode()
|
|
36
|
-
assert "
|
|
37
|
-
assert "
|
|
36
|
+
assert "m5uCHTTp" in result.stdout.decode()
|
|
37
|
+
assert "started new Run" in result.stdout.decode()
|
|
38
38
|
|
|
39
39
|
transform = ln.Transform.get("m5uCHTTpJnjQ")
|
|
40
|
-
assert transform.hash == "
|
|
40
|
+
assert transform.hash == "MoIciBQ0lpVPCKQGofPX6g"
|
|
41
41
|
assert transform.latest_run.environment.path.exists()
|
|
42
42
|
assert transform._source_code_artifact is None
|
|
43
43
|
|
|
@@ -52,8 +52,8 @@ def test_run_save_cache():
|
|
|
52
52
|
# print(result.stderr.decode())
|
|
53
53
|
assert result.returncode == 0
|
|
54
54
|
assert "loaded Transform" in result.stdout.decode()
|
|
55
|
-
assert "
|
|
56
|
-
assert "
|
|
55
|
+
assert "m5uCHTTp" in result.stdout.decode()
|
|
56
|
+
assert "started Run" in result.stdout.decode()
|
|
57
57
|
assert "source code is already saved" in result.stdout.decode()
|
|
58
58
|
|
|
59
59
|
# you can re-save the script
|
|
@@ -85,12 +85,13 @@ def test_run_save_cache():
|
|
|
85
85
|
assert result.returncode == 1
|
|
86
86
|
assert "Did not find blob hash" in result.stderr.decode()
|
|
87
87
|
|
|
88
|
-
# edit the script to remove the git integration
|
|
88
|
+
# edit the script to remove the git integration & ln.finish
|
|
89
89
|
content = filepath.read_text()
|
|
90
90
|
content_lines = content.split("\n")
|
|
91
91
|
content_lines.remove(
|
|
92
92
|
'ln.settings.sync_git_repo = "https://github.com/laminlabs/lamin-cli"'
|
|
93
93
|
)
|
|
94
|
+
content_lines.remove(" ln.finish()")
|
|
94
95
|
content = "\n".join(content_lines)
|
|
95
96
|
filepath.write_text(content)
|
|
96
97
|
|
|
@@ -101,14 +102,51 @@ def test_run_save_cache():
|
|
|
101
102
|
capture_output=True,
|
|
102
103
|
env=env,
|
|
103
104
|
)
|
|
104
|
-
print(result.stdout.decode())
|
|
105
|
-
print(result.stderr.decode())
|
|
105
|
+
# print(result.stdout.decode())
|
|
106
|
+
# print(result.stderr.decode())
|
|
106
107
|
assert result.returncode == 1
|
|
107
108
|
assert "Source code changed, bump revision by setting" in result.stderr.decode()
|
|
108
109
|
|
|
110
|
+
# update the uid
|
|
111
|
+
content = filepath.read_text()
|
|
112
|
+
filepath.write_text(content.replace("m5uCHTTpJnjQ0000", "m5uCHTTpJnjQ0001"))
|
|
113
|
+
|
|
114
|
+
# re-run the script that lacks ln.finish(), hence doesn't yet save source code
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
f"python {filepath}",
|
|
117
|
+
shell=True,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
env=env,
|
|
120
|
+
)
|
|
121
|
+
# print(result.stdout.decode())
|
|
122
|
+
# print(result.stderr.decode())
|
|
123
|
+
assert result.returncode == 0
|
|
124
|
+
assert "created Transform(" in result.stdout.decode()
|
|
125
|
+
assert "started new Run(" in result.stdout.decode()
|
|
126
|
+
|
|
127
|
+
# login a different user
|
|
128
|
+
assert ln.setup.settings.user.handle != "testuser2"
|
|
129
|
+
result = subprocess.run(
|
|
130
|
+
"lamin login testuser2",
|
|
131
|
+
shell=True,
|
|
132
|
+
capture_output=True,
|
|
133
|
+
env=env,
|
|
134
|
+
)
|
|
135
|
+
# re-run the script through the second user
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
f"python {filepath}",
|
|
138
|
+
shell=True,
|
|
139
|
+
capture_output=True,
|
|
140
|
+
env=env,
|
|
141
|
+
)
|
|
142
|
+
# print(result.stdout.decode())
|
|
143
|
+
# print(result.stderr.decode())
|
|
144
|
+
assert result.returncode == 1
|
|
145
|
+
assert "already works on this draft" in result.stderr.decode()
|
|
146
|
+
|
|
109
147
|
# try to get the the source code via command line
|
|
110
148
|
result = subprocess.run(
|
|
111
|
-
"lamin
|
|
149
|
+
"yes | lamin load"
|
|
112
150
|
f" https://lamin.ai/{settings.user.handle}/laminci-unit-tests/transform/m5uCHTTpJnjQ0000", # noqa
|
|
113
151
|
shell=True,
|
|
114
152
|
capture_output=True,
|
|
@@ -117,7 +155,7 @@ def test_run_save_cache():
|
|
|
117
155
|
assert result.returncode == 0
|
|
118
156
|
|
|
119
157
|
result = subprocess.run(
|
|
120
|
-
f"lamin
|
|
158
|
+
f"yes | lamin load transform --key {filepath.name}", # noqa
|
|
121
159
|
shell=True,
|
|
122
160
|
capture_output=True,
|
|
123
161
|
)
|
|
@@ -125,9 +163,48 @@ def test_run_save_cache():
|
|
|
125
163
|
assert result.returncode == 0
|
|
126
164
|
|
|
127
165
|
result = subprocess.run(
|
|
128
|
-
f"lamin
|
|
166
|
+
f"yes | lamin load transform --uid m5uCHTTpJnjQ0000", # noqa
|
|
129
167
|
shell=True,
|
|
130
168
|
capture_output=True,
|
|
131
169
|
)
|
|
132
170
|
print(result.stderr.decode())
|
|
133
171
|
assert result.returncode == 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_run_save_with_params():
|
|
175
|
+
env = os.environ
|
|
176
|
+
env["LAMIN_TESTING"] = "true"
|
|
177
|
+
filepath = scripts_dir / "run-track-with-params.py"
|
|
178
|
+
|
|
179
|
+
# define params
|
|
180
|
+
ln.Param(name="dataset_key", dtype="str").save()
|
|
181
|
+
ln.Param(name="learning_rate", dtype="float").save()
|
|
182
|
+
ln.Param(name="downsample", dtype="bool").save()
|
|
183
|
+
|
|
184
|
+
# run the script
|
|
185
|
+
result = subprocess.run(
|
|
186
|
+
f"python {filepath} --dataset-key mydata --learning-rate 0.01 --downsample",
|
|
187
|
+
shell=True,
|
|
188
|
+
capture_output=True,
|
|
189
|
+
)
|
|
190
|
+
print(result.stdout.decode())
|
|
191
|
+
print(result.stderr.decode())
|
|
192
|
+
assert result.returncode == 0
|
|
193
|
+
assert "created Transform" in result.stdout.decode()
|
|
194
|
+
assert "JjRF4mAC" in result.stdout.decode()
|
|
195
|
+
assert "started new Run" in result.stdout.decode()
|
|
196
|
+
|
|
197
|
+
# you can re-save the script
|
|
198
|
+
result = subprocess.run(
|
|
199
|
+
f"lamin save {filepath}",
|
|
200
|
+
shell=True,
|
|
201
|
+
capture_output=True,
|
|
202
|
+
env=env,
|
|
203
|
+
)
|
|
204
|
+
print(result.stdout.decode())
|
|
205
|
+
print(result.stderr.decode())
|
|
206
|
+
assert result.returncode == 0
|
|
207
|
+
assert "source code is already saved" in result.stdout.decode()
|
|
208
|
+
assert (
|
|
209
|
+
"run-track-with-params.py' on uid 'JjRF4mACd9m00000'" in result.stdout.decode()
|
|
210
|
+
)
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from typing import Tuple
|
|
3
|
-
from lamin_utils import logger
|
|
4
|
-
import lamindb_setup as ln_setup
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def decompose_url(url: str) -> Tuple[str, str, str]:
|
|
9
|
-
assert "transform" in url or "artifact" in url
|
|
10
|
-
for entity in ["transform", "artifact"]:
|
|
11
|
-
if entity in url:
|
|
12
|
-
break
|
|
13
|
-
uid = url.split(f"{entity}/")[1]
|
|
14
|
-
instance_slug = "/".join(url.replace("https://lamin.ai/", "").split("/")[:2])
|
|
15
|
-
return instance_slug, entity, uid
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def get(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
19
|
-
if entity.startswith("https://lamin.ai"):
|
|
20
|
-
url = entity
|
|
21
|
-
instance_slug, entity, uid = decompose_url(url)
|
|
22
|
-
elif entity not in {"artifact", "transform"}:
|
|
23
|
-
raise ValueError(
|
|
24
|
-
"entity has to be a URL starting with https://lamin.ai or 'artifact' or"
|
|
25
|
-
" 'transform'"
|
|
26
|
-
)
|
|
27
|
-
else:
|
|
28
|
-
instance_slug = None
|
|
29
|
-
|
|
30
|
-
if instance_slug is not None:
|
|
31
|
-
auto_connect = ln_setup.settings.auto_connect
|
|
32
|
-
# we don't want to auto-connect when importing lamindb
|
|
33
|
-
ln_setup.settings.auto_connect = False
|
|
34
|
-
|
|
35
|
-
import lamindb as ln
|
|
36
|
-
from lamindb._finish import script_to_notebook
|
|
37
|
-
|
|
38
|
-
ln_setup.settings.auto_connect = auto_connect
|
|
39
|
-
ln.connect(instance_slug)
|
|
40
|
-
else:
|
|
41
|
-
import lamindb as ln
|
|
42
|
-
from lamindb._finish import script_to_notebook
|
|
43
|
-
|
|
44
|
-
# below is to silence warnings about missing run inputs
|
|
45
|
-
ln.settings.track_run_inputs = False
|
|
46
|
-
|
|
47
|
-
if entity == "transform":
|
|
48
|
-
transform = (
|
|
49
|
-
ln.Transform.get(uid) if uid is not None else ln.Transform.get(key=key)
|
|
50
|
-
)
|
|
51
|
-
target_filename = transform.key
|
|
52
|
-
if transform._source_code_artifact_id is not None:
|
|
53
|
-
# backward compat
|
|
54
|
-
filepath_cache = transform._source_code_artifact.cache()
|
|
55
|
-
if not target_filename.endswith(transform._source_code_artifact.suffix):
|
|
56
|
-
target_filename += transform._source_code_artifact.suffix
|
|
57
|
-
filepath_cache.rename(target_filename)
|
|
58
|
-
elif transform.source_code is not None:
|
|
59
|
-
if transform.key.endswith(".ipynb"):
|
|
60
|
-
script_to_notebook(transform, target_filename)
|
|
61
|
-
else:
|
|
62
|
-
Path(target_filename).write_text(transform.source_code)
|
|
63
|
-
else:
|
|
64
|
-
raise ValueError("No source code available for this transform.")
|
|
65
|
-
logger.important(target_filename)
|
|
66
|
-
if with_env:
|
|
67
|
-
if (
|
|
68
|
-
transform.latest_run is not None
|
|
69
|
-
and transform.latest_run.environment is not None
|
|
70
|
-
):
|
|
71
|
-
filepath_env_cache = transform.latest_run.environment.cache()
|
|
72
|
-
target_env_filename = (
|
|
73
|
-
".".join(target_filename.split(".")[:-1]) + "__requirements.txt"
|
|
74
|
-
)
|
|
75
|
-
filepath_env_cache.rename(target_env_filename)
|
|
76
|
-
logger.important(target_env_filename)
|
|
77
|
-
else:
|
|
78
|
-
logger.warning("latest transform run with environment doesn't exist")
|
|
79
|
-
elif entity == "artifact":
|
|
80
|
-
artifact = ln.Artifact.get(uid) if uid is not None else ln.Artifact.get(key=key)
|
|
81
|
-
cache_path = artifact.cache()
|
|
82
|
-
logger.important(cache_path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|