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.
Files changed (27) hide show
  1. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/PKG-INFO +1 -1
  2. lamin_cli-0.16.1/lamin_cli/__init__.py +3 -0
  3. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/__main__.py +136 -71
  4. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_get.py +3 -3
  5. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_notebooks.py +10 -10
  6. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_scripts.py +2 -2
  7. lamin_cli-0.15.0/lamin_cli/__init__.py +0 -3
  8. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.github/workflows/doc-changes.yml +0 -0
  9. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.gitignore +0 -0
  10. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/.pre-commit-config.yaml +0 -0
  11. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/README.md +0 -0
  12. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_cache.py +0 -0
  13. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_migration.py +0 -0
  14. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/lamin_cli/_save.py +0 -0
  15. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/pyproject.toml +0 -0
  16. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/conftest.py +0 -0
  17. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/not-initialized.ipynb +0 -0
  18. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
  19. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
  20. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/merely-import-lamindb.py +0 -0
  21. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
  22. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/scripts/run-track-and-finish.py +0 -0
  23. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_cli.py +0 -0
  24. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_get.py +0 -0
  25. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_migrate.py +0 -0
  26. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_multi_process.py +0 -0
  27. {lamin_cli-0.15.0 → lamin_cli-0.16.1}/tests/test_save_files.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamin_cli
3
- Version: 0.15.0
3
+ Version: 0.16.1
4
4
  Summary: Lamin CLI.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1,3 @@
1
+ """Lamin CLI."""
2
+
3
+ __version__ = "0.16.1"
@@ -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
- @click.group()
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
- def logout():
72
- """Logout."""
73
- from lamindb_setup._setup_user import logout
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 logout()
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
- @click.argument("instance", type=str, default=None)
99
- @click.option("--force", is_flag=True, default=False, help="Do not ask for confirmation.") # noqa: E501
100
- # fmt: on
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
- if setting == "auto-connect":
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
- def register():
141
- """Register an instance on the hub."""
142
- from lamindb_setup._register_instance import register as register_
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 register_()
171
+ return delete(instance, force=force)
145
172
 
146
173
 
147
174
  @main.command()
148
- @click.argument("action", type=click.Choice(["view"]))
149
- def schema(action: str):
150
- """View schema."""
151
- from lamindb_setup._schema import view
175
+ def logout():
176
+ """Logout."""
177
+ from lamindb_setup._setup_user import logout
152
178
 
153
- if action == "view":
154
- return view()
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
- @click.argument("url", type=str)
173
- def get(url: str):
174
- """Get an object from a lamin.ai URL."""
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 get(url)
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.source_code.cache()
35
+ filepath_cache = transform._source_code_artifact.cache()
36
36
  target_filename = transform.key
37
- if not target_filename.endswith(transform.source_code.suffix):
38
- target_filename += transform.source_code.suffix
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.latest_report is None
85
- assert transform.source_code is None
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.latest_report.path.exists()
101
- assert transform.latest_run.report.path == transform.latest_report.path
102
- assert transform.source_code.hash == "5nc_HMjPvT9n26OWrjq6uQ"
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.source_code.path.exists()
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.latest_report.path.exists()
132
- assert transform.latest_run.report.path == transform.latest_report.path
133
- assert transform.source_code.hash == "ocLybD0Hv_L3NhhXgTyQcw"
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.source_code.path.exists()
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.source_code.hash == "T1zmmTJyeEpBxjaHcHcZdg"
40
+ assert transform._source_code_artifact.hash == "T1zmmTJyeEpBxjaHcHcZdg"
41
41
  assert transform.latest_run.environment.path.exists()
42
- assert transform.source_code.path.exists()
42
+ assert transform._source_code_artifact.path.exists()
43
43
 
44
44
  # you can rerun the same script
45
45
  result = subprocess.run(
@@ -1,3 +0,0 @@
1
- """Lamin CLI."""
2
-
3
- __version__ = "0.15.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes