lamin_cli 0.15.0__tar.gz → 0.16.1__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.15.0 → lamin_cli-0.16.1}/PKG-INFO +1 -1
- lamin_cli-0.16.1/lamin_cli/__init__.py +3 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/__main__.py +136 -71
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_get.py +3 -3
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_notebooks.py +10 -10
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_scripts.py +2 -2
- lamin_cli-0.15.0/lamin_cli/__init__.py +0 -3
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.github/workflows/doc-changes.yml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.gitignore +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.pre-commit-config.yaml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/README.md +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_cache.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_migration.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_save.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/pyproject.toml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/conftest.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/not-initialized.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/merely-import-lamindb.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/run-track-and-finish.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_cli.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_get.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_migrate.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_multi_process.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_files.py +0 -0
|
@@ -1,17 +1,80 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
+
from collections import OrderedDict
|
|
4
5
|
import inspect
|
|
5
6
|
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
-
from typing import Optional
|
|
7
|
+
from typing import Optional, Mapping
|
|
8
|
+
from functools import wraps
|
|
7
9
|
|
|
8
10
|
# https://github.com/ewels/rich-click/issues/19
|
|
9
11
|
# Otherwise rich-click takes over the formatting.
|
|
10
12
|
if os.environ.get("NO_RICH"):
|
|
11
13
|
import click as click
|
|
14
|
+
|
|
15
|
+
class OrderedGroup(click.Group):
|
|
16
|
+
"""Overwrites list_commands to return commands in order of definition."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: Optional[str] = None,
|
|
21
|
+
commands: Optional[Mapping[str, click.Command]] = None,
|
|
22
|
+
**kwargs,
|
|
23
|
+
):
|
|
24
|
+
super(OrderedGroup, self).__init__(name, commands, **kwargs)
|
|
25
|
+
self.commands = commands or OrderedDict()
|
|
26
|
+
|
|
27
|
+
def list_commands(self, ctx: click.Context) -> Mapping[str, click.Command]:
|
|
28
|
+
return self.commands
|
|
29
|
+
|
|
30
|
+
lamin_group_decorator = click.group(cls=OrderedGroup)
|
|
31
|
+
|
|
12
32
|
else:
|
|
13
33
|
import rich_click as click
|
|
14
34
|
|
|
35
|
+
COMMAND_GROUPS = {
|
|
36
|
+
"lamin": [
|
|
37
|
+
{
|
|
38
|
+
"name": "Main commands",
|
|
39
|
+
"commands": [
|
|
40
|
+
"login",
|
|
41
|
+
"init",
|
|
42
|
+
"load",
|
|
43
|
+
"info",
|
|
44
|
+
"close",
|
|
45
|
+
"delete",
|
|
46
|
+
"logout",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Data commands",
|
|
51
|
+
"commands": ["get", "save"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "Configuration commands",
|
|
55
|
+
"commands": ["register", "cache", "set"],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Schema commands",
|
|
59
|
+
"commands": ["migrate", "schema"],
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def lamin_group_decorator(f):
|
|
65
|
+
@click.rich_config(
|
|
66
|
+
help_config=click.RichHelpConfiguration(
|
|
67
|
+
command_groups=COMMAND_GROUPS,
|
|
68
|
+
style_commands_table_column_width_ratio=(1, 13),
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
@click.group()
|
|
72
|
+
@wraps(f)
|
|
73
|
+
def wrapper(*args, **kwargs):
|
|
74
|
+
return f(*args, **kwargs)
|
|
75
|
+
return wrapper
|
|
76
|
+
|
|
77
|
+
|
|
15
78
|
from click import Command, Context
|
|
16
79
|
from lamindb_setup._silence_loggers import silence_loggers
|
|
17
80
|
|
|
@@ -24,35 +87,13 @@ except PackageNotFoundError:
|
|
|
24
87
|
lamindb_version = "lamindb installation not found"
|
|
25
88
|
|
|
26
89
|
|
|
27
|
-
@
|
|
90
|
+
@lamin_group_decorator
|
|
28
91
|
@click.version_option(version=lamindb_version, prog_name="lamindb")
|
|
29
92
|
def main():
|
|
30
93
|
"""Configure LaminDB and perform simple actions."""
|
|
31
94
|
silence_loggers()
|
|
32
95
|
|
|
33
96
|
|
|
34
|
-
@main.command()
|
|
35
|
-
def info():
|
|
36
|
-
"""Show user, settings & instance info."""
|
|
37
|
-
import lamindb_setup
|
|
38
|
-
|
|
39
|
-
print(lamindb_setup.settings)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# fmt: off
|
|
43
|
-
@main.command()
|
|
44
|
-
@click.option("--storage", type=str, help="local dir, s3://bucket_name, gs://bucket_name") # noqa: E501
|
|
45
|
-
@click.option("--db", type=str, default=None, help="postgres database connection URL, do not pass for SQLite") # noqa: E501
|
|
46
|
-
@click.option("--schema", type=str, default=None, help="comma-separated string of schema modules") # noqa: E501
|
|
47
|
-
@click.option("--name", type=str, default=None, help="instance name")
|
|
48
|
-
# fmt: on
|
|
49
|
-
def init(storage: str, db: Optional[str], schema: Optional[str], name: Optional[str]):
|
|
50
|
-
"""Init a lamindb instance."""
|
|
51
|
-
from lamindb_setup._init_instance import init as init_
|
|
52
|
-
|
|
53
|
-
return init_(storage=storage, db=db, schema=schema, name=name)
|
|
54
|
-
|
|
55
|
-
|
|
56
97
|
@main.command()
|
|
57
98
|
@click.argument("user", type=str)
|
|
58
99
|
@click.option("--key", type=str, default=None, help="API key")
|
|
@@ -67,12 +108,18 @@ def login(user: str, key: Optional[str], password: Optional[str]):
|
|
|
67
108
|
return login(user, key=key, password=password)
|
|
68
109
|
|
|
69
110
|
|
|
111
|
+
# fmt: off
|
|
70
112
|
@main.command()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
113
|
+
@click.option("--storage", type=str, help="local dir, s3://bucket_name, gs://bucket_name") # noqa: E501
|
|
114
|
+
@click.option("--db", type=str, default=None, help="postgres database connection URL, do not pass for SQLite") # noqa: E501
|
|
115
|
+
@click.option("--schema", type=str, default=None, help="comma-separated string of schema modules") # noqa: E501
|
|
116
|
+
@click.option("--name", type=str, default=None, help="instance name")
|
|
117
|
+
# fmt: on
|
|
118
|
+
def init(storage: str, db: Optional[str], schema: Optional[str], name: Optional[str]):
|
|
119
|
+
"""Init a lamindb instance."""
|
|
120
|
+
from lamindb_setup._init_instance import init as init_
|
|
74
121
|
|
|
75
|
-
return
|
|
122
|
+
return init_(storage=storage, db=db, schema=schema, name=name)
|
|
76
123
|
|
|
77
124
|
|
|
78
125
|
# fmt: off
|
|
@@ -93,36 +140,12 @@ def load(identifier: str, db: Optional[str], storage: Optional[str]):
|
|
|
93
140
|
return connect(slug=identifier, db=db, storage=storage)
|
|
94
141
|
|
|
95
142
|
|
|
96
|
-
# fmt: off
|
|
97
143
|
@main.command()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def delete(instance: str, force: bool = False):
|
|
102
|
-
"""Delete an instance."""
|
|
103
|
-
from lamindb_setup._delete import delete
|
|
104
|
-
|
|
105
|
-
return delete(instance, force=force)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@main.command(name="set")
|
|
109
|
-
@click.argument(
|
|
110
|
-
"setting",
|
|
111
|
-
type=click.Choice(["auto-connect", "private-django-api"], case_sensitive=False),
|
|
112
|
-
)
|
|
113
|
-
@click.argument("value", type=click.BOOL)
|
|
114
|
-
def set_(setting: str, value: bool):
|
|
115
|
-
"""Update settings.
|
|
116
|
-
|
|
117
|
-
- `auto-connect` → {attr}`~lamindb.setup.core.SetupSettings.auto_connect`
|
|
118
|
-
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
119
|
-
"""
|
|
120
|
-
from lamindb_setup import settings
|
|
144
|
+
def info():
|
|
145
|
+
"""Show user, settings & instance info."""
|
|
146
|
+
import lamindb_setup
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
settings.auto_connect = value
|
|
124
|
-
if setting == "private-django-api":
|
|
125
|
-
settings.private_django_api = value
|
|
148
|
+
print(lamindb_setup.settings)
|
|
126
149
|
|
|
127
150
|
|
|
128
151
|
@main.command()
|
|
@@ -136,22 +159,33 @@ def close():
|
|
|
136
159
|
return close_()
|
|
137
160
|
|
|
138
161
|
|
|
162
|
+
# fmt: off
|
|
139
163
|
@main.command()
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
@click.argument("instance", type=str, default=None)
|
|
165
|
+
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation.") # noqa: E501
|
|
166
|
+
# fmt: on
|
|
167
|
+
def delete(instance: str, force: bool = False):
|
|
168
|
+
"""Delete an instance."""
|
|
169
|
+
from lamindb_setup._delete import delete
|
|
143
170
|
|
|
144
|
-
return
|
|
171
|
+
return delete(instance, force=force)
|
|
145
172
|
|
|
146
173
|
|
|
147
174
|
@main.command()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
from lamindb_setup._schema import view
|
|
175
|
+
def logout():
|
|
176
|
+
"""Logout."""
|
|
177
|
+
from lamindb_setup._setup_user import logout
|
|
152
178
|
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
return logout()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@main.command()
|
|
183
|
+
@click.argument("url", type=str)
|
|
184
|
+
def get(url: str):
|
|
185
|
+
"""Get an object from a lamin.ai URL."""
|
|
186
|
+
from lamin_cli._get import get
|
|
187
|
+
|
|
188
|
+
return get(url)
|
|
155
189
|
|
|
156
190
|
|
|
157
191
|
@main.command()
|
|
@@ -169,18 +203,49 @@ def save(filepath: str, key: str, description: str):
|
|
|
169
203
|
|
|
170
204
|
|
|
171
205
|
@main.command()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
from lamin_cli._get import get
|
|
206
|
+
def register():
|
|
207
|
+
"""Register an instance on the hub."""
|
|
208
|
+
from lamindb_setup._register_instance import register as register_
|
|
176
209
|
|
|
177
|
-
return
|
|
210
|
+
return register_()
|
|
178
211
|
|
|
179
212
|
|
|
180
213
|
main.add_command(cache)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@main.command(name="set")
|
|
217
|
+
@click.argument(
|
|
218
|
+
"setting",
|
|
219
|
+
type=click.Choice(["auto-connect", "private-django-api"], case_sensitive=False),
|
|
220
|
+
)
|
|
221
|
+
@click.argument("value", type=click.BOOL)
|
|
222
|
+
def set_(setting: str, value: bool):
|
|
223
|
+
"""Update settings.
|
|
224
|
+
|
|
225
|
+
- `auto-connect` → {attr}`~lamindb.setup.core.SetupSettings.auto_connect`
|
|
226
|
+
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
227
|
+
"""
|
|
228
|
+
from lamindb_setup import settings
|
|
229
|
+
|
|
230
|
+
if setting == "auto-connect":
|
|
231
|
+
settings.auto_connect = value
|
|
232
|
+
if setting == "private-django-api":
|
|
233
|
+
settings.private_django_api = value
|
|
234
|
+
|
|
235
|
+
|
|
181
236
|
main.add_command(migrate)
|
|
182
237
|
|
|
183
238
|
|
|
239
|
+
@main.command()
|
|
240
|
+
@click.argument("action", type=click.Choice(["view"]))
|
|
241
|
+
def schema(action: str):
|
|
242
|
+
"""View schema."""
|
|
243
|
+
from lamindb_setup._schema import view
|
|
244
|
+
|
|
245
|
+
if action == "view":
|
|
246
|
+
return view()
|
|
247
|
+
|
|
248
|
+
|
|
184
249
|
# https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
|
|
185
250
|
# https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
|
|
186
251
|
def _generate_help():
|
|
@@ -32,10 +32,10 @@ def get(url: str):
|
|
|
32
32
|
|
|
33
33
|
if entity == "transform":
|
|
34
34
|
transform = ln.Transform.get(uid)
|
|
35
|
-
filepath_cache = transform.
|
|
35
|
+
filepath_cache = transform._source_code_artifact.cache()
|
|
36
36
|
target_filename = transform.key
|
|
37
|
-
if not target_filename.endswith(transform.
|
|
38
|
-
target_filename += transform.
|
|
37
|
+
if not target_filename.endswith(transform._source_code_artifact.suffix):
|
|
38
|
+
target_filename += transform._source_code_artifact.suffix
|
|
39
39
|
filepath_cache.rename(target_filename)
|
|
40
40
|
logger.success(f"cached source code of transform {uid} as {target_filename}")
|
|
41
41
|
elif entity == "artifact":
|
|
@@ -81,8 +81,8 @@ def test_save_consecutive():
|
|
|
81
81
|
# now, there is a transform record, but we're missing all artifacts
|
|
82
82
|
transform = ln.Transform.filter(uid="hlsFXswrJjtt5zKv").one_or_none()
|
|
83
83
|
assert transform is not None
|
|
84
|
-
assert transform.
|
|
85
|
-
assert transform.
|
|
84
|
+
assert transform.latest_run.report is None
|
|
85
|
+
assert transform._source_code_artifact is None
|
|
86
86
|
assert transform.latest_run.environment is None
|
|
87
87
|
|
|
88
88
|
# and save again
|
|
@@ -97,11 +97,11 @@ def test_save_consecutive():
|
|
|
97
97
|
# now, we have the associated artifacts
|
|
98
98
|
transform = ln.Transform.filter(uid="hlsFXswrJjtt5zKv").one_or_none()
|
|
99
99
|
assert transform is not None
|
|
100
|
-
assert transform.
|
|
101
|
-
assert transform.latest_run.report.path == transform.
|
|
102
|
-
assert transform.
|
|
100
|
+
assert transform.latest_run.report.path.exists()
|
|
101
|
+
assert transform.latest_run.report.path == transform.latest_run.report.path
|
|
102
|
+
assert transform._source_code_artifact.hash == "5nc_HMjPvT9n26OWrjq6uQ"
|
|
103
103
|
assert transform.latest_run.environment.path.exists()
|
|
104
|
-
assert transform.
|
|
104
|
+
assert transform._source_code_artifact.path.exists()
|
|
105
105
|
|
|
106
106
|
# now, assume the user modifies the notebook
|
|
107
107
|
nb = read_notebook(notebook_path)
|
|
@@ -128,11 +128,11 @@ def test_save_consecutive():
|
|
|
128
128
|
assert result.returncode == 0
|
|
129
129
|
# the source code is overwritten with the edits, reflected in a new hash
|
|
130
130
|
transform = ln.Transform.get("hlsFXswrJjtt5zKv")
|
|
131
|
-
assert transform.
|
|
132
|
-
assert transform.latest_run.report.path == transform.
|
|
133
|
-
assert transform.
|
|
131
|
+
assert transform.latest_run.report.path.exists()
|
|
132
|
+
assert transform.latest_run.report.path == transform.latest_run.report.path
|
|
133
|
+
assert transform._source_code_artifact.hash == "ocLybD0Hv_L3NhhXgTyQcw"
|
|
134
134
|
assert transform.latest_run.environment.path.exists()
|
|
135
|
-
assert transform.
|
|
135
|
+
assert transform._source_code_artifact.path.exists()
|
|
136
136
|
|
|
137
137
|
# get the the source code via command line
|
|
138
138
|
result = subprocess.run(
|
|
@@ -37,9 +37,9 @@ def test_run_save_cache():
|
|
|
37
37
|
assert "saved: Run" in result.stdout.decode()
|
|
38
38
|
|
|
39
39
|
transform = ln.Transform.get("m5uCHTTpJnjQ")
|
|
40
|
-
assert transform.
|
|
40
|
+
assert transform._source_code_artifact.hash == "T1zmmTJyeEpBxjaHcHcZdg"
|
|
41
41
|
assert transform.latest_run.environment.path.exists()
|
|
42
|
-
assert transform.
|
|
42
|
+
assert transform._source_code_artifact.path.exists()
|
|
43
43
|
|
|
44
44
|
# you can rerun the same script
|
|
45
45
|
result = subprocess.run(
|
|
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
|
{lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/with-title-and-initialized-consecutive.ipynb
RENAMED
|
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
|