lamin_cli 0.15.0__tar.gz → 0.16.0__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.0}/PKG-INFO +1 -1
- lamin_cli-0.16.0/lamin_cli/__init__.py +3 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/lamin_cli/__main__.py +129 -71
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/lamin_cli/_get.py +3 -3
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_save_notebooks.py +10 -10
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/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.0}/.github/workflows/doc-changes.yml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/.gitignore +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/.pre-commit-config.yaml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/README.md +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/lamin_cli/_cache.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/lamin_cli/_migration.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/lamin_cli/_save.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/pyproject.toml +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/conftest.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/notebooks/not-initialized.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/scripts/merely-import-lamindb.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/scripts/run-track-and-finish.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_cli.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_get.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_migrate.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_multi_process.py +0 -0
- {lamin_cli-0.15.0 → lamin_cli-0.16.0}/tests/test_save_files.py +0 -0
|
@@ -1,17 +1,73 @@
|
|
|
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
|
|
7
8
|
|
|
8
9
|
# https://github.com/ewels/rich-click/issues/19
|
|
9
10
|
# Otherwise rich-click takes over the formatting.
|
|
10
11
|
if os.environ.get("NO_RICH"):
|
|
11
12
|
import click as click
|
|
13
|
+
|
|
14
|
+
class OrderedGroup(click.Group):
|
|
15
|
+
"""Overwrites list_commands to return commands in order of definition."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
name: Optional[str] = None,
|
|
20
|
+
commands: Optional[Mapping[str, click.Command]] = None,
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
super(OrderedGroup, self).__init__(name, commands, **kwargs)
|
|
24
|
+
self.commands = commands or OrderedDict()
|
|
25
|
+
|
|
26
|
+
def list_commands(self, ctx: click.Context) -> Mapping[str, click.Command]:
|
|
27
|
+
return self.commands
|
|
28
|
+
|
|
29
|
+
lamin_group_decorator = click.group(cls=OrderedGroup)
|
|
30
|
+
|
|
12
31
|
else:
|
|
13
32
|
import rich_click as click
|
|
14
33
|
|
|
34
|
+
COMMAND_GROUPS = {
|
|
35
|
+
"lamin": [
|
|
36
|
+
{
|
|
37
|
+
"name": "Main commands",
|
|
38
|
+
"commands": [
|
|
39
|
+
"login",
|
|
40
|
+
"init",
|
|
41
|
+
"load",
|
|
42
|
+
"info",
|
|
43
|
+
"close",
|
|
44
|
+
"delete",
|
|
45
|
+
"logout",
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "Data commands",
|
|
50
|
+
"commands": ["get", "save"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "Configuration commands",
|
|
54
|
+
"commands": ["register", "cache", "set"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "Schema commands",
|
|
58
|
+
"commands": ["migrate", "schema"],
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
lamin_group_decorator = click.rich_config(
|
|
64
|
+
help_config=click.RichHelpConfiguration(
|
|
65
|
+
command_groups=COMMAND_GROUPS,
|
|
66
|
+
style_commands_table_column_width_ratio=(1, 13),
|
|
67
|
+
)
|
|
68
|
+
)(click.group())
|
|
69
|
+
|
|
70
|
+
|
|
15
71
|
from click import Command, Context
|
|
16
72
|
from lamindb_setup._silence_loggers import silence_loggers
|
|
17
73
|
|
|
@@ -24,35 +80,13 @@ except PackageNotFoundError:
|
|
|
24
80
|
lamindb_version = "lamindb installation not found"
|
|
25
81
|
|
|
26
82
|
|
|
27
|
-
@
|
|
83
|
+
@lamin_group_decorator
|
|
28
84
|
@click.version_option(version=lamindb_version, prog_name="lamindb")
|
|
29
85
|
def main():
|
|
30
86
|
"""Configure LaminDB and perform simple actions."""
|
|
31
87
|
silence_loggers()
|
|
32
88
|
|
|
33
89
|
|
|
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
90
|
@main.command()
|
|
57
91
|
@click.argument("user", type=str)
|
|
58
92
|
@click.option("--key", type=str, default=None, help="API key")
|
|
@@ -67,12 +101,18 @@ def login(user: str, key: Optional[str], password: Optional[str]):
|
|
|
67
101
|
return login(user, key=key, password=password)
|
|
68
102
|
|
|
69
103
|
|
|
104
|
+
# fmt: off
|
|
70
105
|
@main.command()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
@click.option("--storage", type=str, help="local dir, s3://bucket_name, gs://bucket_name") # noqa: E501
|
|
107
|
+
@click.option("--db", type=str, default=None, help="postgres database connection URL, do not pass for SQLite") # noqa: E501
|
|
108
|
+
@click.option("--schema", type=str, default=None, help="comma-separated string of schema modules") # noqa: E501
|
|
109
|
+
@click.option("--name", type=str, default=None, help="instance name")
|
|
110
|
+
# fmt: on
|
|
111
|
+
def init(storage: str, db: Optional[str], schema: Optional[str], name: Optional[str]):
|
|
112
|
+
"""Init a lamindb instance."""
|
|
113
|
+
from lamindb_setup._init_instance import init as init_
|
|
74
114
|
|
|
75
|
-
return
|
|
115
|
+
return init_(storage=storage, db=db, schema=schema, name=name)
|
|
76
116
|
|
|
77
117
|
|
|
78
118
|
# fmt: off
|
|
@@ -93,36 +133,12 @@ def load(identifier: str, db: Optional[str], storage: Optional[str]):
|
|
|
93
133
|
return connect(slug=identifier, db=db, storage=storage)
|
|
94
134
|
|
|
95
135
|
|
|
96
|
-
# fmt: off
|
|
97
136
|
@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
|
|
137
|
+
def info():
|
|
138
|
+
"""Show user, settings & instance info."""
|
|
139
|
+
import lamindb_setup
|
|
121
140
|
|
|
122
|
-
|
|
123
|
-
settings.auto_connect = value
|
|
124
|
-
if setting == "private-django-api":
|
|
125
|
-
settings.private_django_api = value
|
|
141
|
+
print(lamindb_setup.settings)
|
|
126
142
|
|
|
127
143
|
|
|
128
144
|
@main.command()
|
|
@@ -136,22 +152,33 @@ def close():
|
|
|
136
152
|
return close_()
|
|
137
153
|
|
|
138
154
|
|
|
155
|
+
# fmt: off
|
|
139
156
|
@main.command()
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
@click.argument("instance", type=str, default=None)
|
|
158
|
+
@click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation.") # noqa: E501
|
|
159
|
+
# fmt: on
|
|
160
|
+
def delete(instance: str, force: bool = False):
|
|
161
|
+
"""Delete an instance."""
|
|
162
|
+
from lamindb_setup._delete import delete
|
|
143
163
|
|
|
144
|
-
return
|
|
164
|
+
return delete(instance, force=force)
|
|
145
165
|
|
|
146
166
|
|
|
147
167
|
@main.command()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
from lamindb_setup._schema import view
|
|
168
|
+
def logout():
|
|
169
|
+
"""Logout."""
|
|
170
|
+
from lamindb_setup._setup_user import logout
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
return logout()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@main.command()
|
|
176
|
+
@click.argument("url", type=str)
|
|
177
|
+
def get(url: str):
|
|
178
|
+
"""Get an object from a lamin.ai URL."""
|
|
179
|
+
from lamin_cli._get import get
|
|
180
|
+
|
|
181
|
+
return get(url)
|
|
155
182
|
|
|
156
183
|
|
|
157
184
|
@main.command()
|
|
@@ -169,18 +196,49 @@ def save(filepath: str, key: str, description: str):
|
|
|
169
196
|
|
|
170
197
|
|
|
171
198
|
@main.command()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
from lamin_cli._get import get
|
|
199
|
+
def register():
|
|
200
|
+
"""Register an instance on the hub."""
|
|
201
|
+
from lamindb_setup._register_instance import register as register_
|
|
176
202
|
|
|
177
|
-
return
|
|
203
|
+
return register_()
|
|
178
204
|
|
|
179
205
|
|
|
180
206
|
main.add_command(cache)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@main.command(name="set")
|
|
210
|
+
@click.argument(
|
|
211
|
+
"setting",
|
|
212
|
+
type=click.Choice(["auto-connect", "private-django-api"], case_sensitive=False),
|
|
213
|
+
)
|
|
214
|
+
@click.argument("value", type=click.BOOL)
|
|
215
|
+
def set_(setting: str, value: bool):
|
|
216
|
+
"""Update settings.
|
|
217
|
+
|
|
218
|
+
- `auto-connect` → {attr}`~lamindb.setup.core.SetupSettings.auto_connect`
|
|
219
|
+
- `private-django-api` → {attr}`~lamindb.setup.core.SetupSettings.private_django_api`
|
|
220
|
+
"""
|
|
221
|
+
from lamindb_setup import settings
|
|
222
|
+
|
|
223
|
+
if setting == "auto-connect":
|
|
224
|
+
settings.auto_connect = value
|
|
225
|
+
if setting == "private-django-api":
|
|
226
|
+
settings.private_django_api = value
|
|
227
|
+
|
|
228
|
+
|
|
181
229
|
main.add_command(migrate)
|
|
182
230
|
|
|
183
231
|
|
|
232
|
+
@main.command()
|
|
233
|
+
@click.argument("action", type=click.Choice(["view"]))
|
|
234
|
+
def schema(action: str):
|
|
235
|
+
"""View schema."""
|
|
236
|
+
from lamindb_setup._schema import view
|
|
237
|
+
|
|
238
|
+
if action == "view":
|
|
239
|
+
return view()
|
|
240
|
+
|
|
241
|
+
|
|
184
242
|
# https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
|
|
185
243
|
# https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
|
|
186
244
|
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.0}/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
|