lamin_cli 0.17.6__py2.py3-none-any.whl → 0.17.7__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 +1 -1
- lamin_cli/__main__.py +111 -94
- lamin_cli/_load.py +110 -0
- lamin_cli/_migration.py +1 -1
- lamin_cli/_save.py +20 -17
- lamin_cli/_settings.py +41 -0
- {lamin_cli-0.17.6.dist-info → lamin_cli-0.17.7.dist-info}/METADATA +1 -1
- lamin_cli-0.17.7.dist-info/RECORD +12 -0
- lamin_cli/_get.py +0 -82
- lamin_cli-0.17.6.dist-info/RECORD +0 -11
- {lamin_cli-0.17.6.dist-info → lamin_cli-0.17.7.dist-info}/LICENSE +0 -0
- {lamin_cli-0.17.6.dist-info → lamin_cli-0.17.7.dist-info}/WHEEL +0 -0
- {lamin_cli-0.17.6.dist-info → lamin_cli-0.17.7.dist-info}/entry_points.txt +0 -0
lamin_cli/__init__.py
CHANGED
lamin_cli/__main__.py
CHANGED
|
@@ -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
|
|
lamin_cli/_load.py
ADDED
|
@@ -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
|
lamin_cli/_migration.py
CHANGED
lamin_cli/_save.py
CHANGED
|
@@ -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:
|
lamin_cli/_settings.py
ADDED
|
@@ -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
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
lamin_cli/__init__.py,sha256=7lQuTta1nc-cmxhBP-lwuNItIDyamNoyfWWvnzL1HWg,41
|
|
2
|
+
lamin_cli/__main__.py,sha256=_7d8PPnNKWdBHF-5jVCHKIqF6HkeXpvTJz3yNcvvnsc,9706
|
|
3
|
+
lamin_cli/_cache.py,sha256=6LrrWNrdZzH_BrohQ2n24xLFhUQ5EFan1bnzqMX_mIg,812
|
|
4
|
+
lamin_cli/_load.py,sha256=Ow34O5wK8i3Zg1q6K-bQXrCaanWf8GoeeXZTCWWZXJQ,4464
|
|
5
|
+
lamin_cli/_migration.py,sha256=xTbad6aDUwuK0QvWCmDW3f8-xaqwisS-Cqf-LbD1WRI,1071
|
|
6
|
+
lamin_cli/_save.py,sha256=KpGB1ZeKyE0Of0PkCX8j2UPtj4FQPS5wwKIYUFkTeio,4962
|
|
7
|
+
lamin_cli/_settings.py,sha256=iS37mcQUHKRWxi2sHnAojEI6sWk3w232qwG-GeY2_Qc,1141
|
|
8
|
+
lamin_cli-0.17.7.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
9
|
+
lamin_cli-0.17.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
10
|
+
lamin_cli-0.17.7.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
11
|
+
lamin_cli-0.17.7.dist-info/METADATA,sha256=YjKsY2CBMUlg7EArBS9zzWB6h44Fyul1Vl68ma2bZ5I,336
|
|
12
|
+
lamin_cli-0.17.7.dist-info/RECORD,,
|
lamin_cli/_get.py
DELETED
|
@@ -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)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
lamin_cli/__init__.py,sha256=NsLSTY60JMWUwHA302KuRXtvwrdZXYfftnkxK5L7Ntw,41
|
|
2
|
-
lamin_cli/__main__.py,sha256=sJXjBotegrk_xl5gY0-z_2dZhi8TKbC4RabVvD9LS8A,9121
|
|
3
|
-
lamin_cli/_cache.py,sha256=6LrrWNrdZzH_BrohQ2n24xLFhUQ5EFan1bnzqMX_mIg,812
|
|
4
|
-
lamin_cli/_get.py,sha256=2LVNPxpjE0Gz-xIm6Sf9uwHLND66XpE5dg0Phu3Kgmc,3254
|
|
5
|
-
lamin_cli/_migration.py,sha256=dULOvbpJ4VBiXuxPAM8jFGnBkh7pQGqE5eP-UC6uxWc,1055
|
|
6
|
-
lamin_cli/_save.py,sha256=Ze_ztLSzib12-xvwM6QL2Z6UAN33n-DFrJDZg-t7nDU,4795
|
|
7
|
-
lamin_cli-0.17.6.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
8
|
-
lamin_cli-0.17.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
-
lamin_cli-0.17.6.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
10
|
-
lamin_cli-0.17.6.dist-info/METADATA,sha256=8mmKSv7wuqXP4MSOf3DvnPqGt-IAUpHRmWLNJAGzPvM,336
|
|
11
|
-
lamin_cli-0.17.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|