lamin_cli 0.16.3__tar.gz → 0.17.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 (28) hide show
  1. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/PKG-INFO +1 -1
  2. lamin_cli-0.17.1/lamin_cli/__init__.py +3 -0
  3. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/__main__.py +18 -2
  4. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_get.py +18 -5
  5. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_save.py +3 -7
  6. lamin_cli-0.17.1/tests/test_cli.py +42 -0
  7. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_save_notebooks.py +69 -26
  8. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_save_scripts.py +6 -6
  9. lamin_cli-0.16.3/lamin_cli/__init__.py +0 -3
  10. lamin_cli-0.16.3/tests/test_cli.py +0 -16
  11. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.github/workflows/doc-changes.yml +0 -0
  12. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.gitignore +0 -0
  13. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.pre-commit-config.yaml +0 -0
  14. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/README.md +0 -0
  15. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_cache.py +0 -0
  16. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_migration.py +0 -0
  17. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/pyproject.toml +0 -0
  18. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/conftest.py +0 -0
  19. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/not-initialized.ipynb +0 -0
  20. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
  21. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
  22. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/merely-import-lamindb.py +0 -0
  23. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
  24. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/run-track-and-finish.py +0 -0
  25. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_get.py +0 -0
  26. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_migrate.py +0 -0
  27. {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_multi_process.py +0 -0
  28. {lamin_cli-0.16.3 → lamin_cli-0.17.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.16.3
3
+ Version: 0.17.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.17.1"
@@ -96,7 +96,7 @@ def main():
96
96
 
97
97
 
98
98
  @main.command()
99
- @click.argument("user", type=str)
99
+ @click.argument("user", type=str, default=None, required=False)
100
100
  @click.option("--key", type=str, default=None, help="API key")
101
101
  def login(user: str, key: Optional[str]):
102
102
  """Log into LaminHub.
@@ -110,10 +110,26 @@ def login(user: str, key: Optional[str]):
110
110
  You'll find your API key in the top right corner under "Settings".
111
111
 
112
112
  After this, you can either use `lamin login myhandle` or `lamin login myemail@acme.com`
113
+
114
+ You can also use
115
+
116
+ ```
117
+ lamin login
118
+ ```
119
+
120
+ and type your beta API key in the terminal
113
121
  """
114
122
  from lamindb_setup._setup_user import login
115
123
 
116
- return login(user, key=key)
124
+ if user is None:
125
+ if "LAMIN_API_KEY" in os.environ:
126
+ api_key = os.environ["LAMIN_API_KEY"]
127
+ else:
128
+ api_key = input("Your API key: ")
129
+ else:
130
+ api_key = None
131
+
132
+ return login(user, key=key, api_key=api_key)
117
133
 
118
134
 
119
135
  # fmt: off
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  from typing import Tuple
3
3
  from lamin_utils import logger
4
4
  import lamindb_setup as ln_setup
5
+ from pathlib import Path
5
6
 
6
7
 
7
8
  def decompose_url(url: str) -> Tuple[str, str, str]:
@@ -25,6 +26,7 @@ def get(url: str):
25
26
  ln_setup.settings.auto_connect = False
26
27
 
27
28
  import lamindb as ln
29
+ from lamindb._finish import script_to_notebook
28
30
 
29
31
  ln_setup.settings.auto_connect = auto_connect
30
32
  ln.connect(instance_slug)
@@ -32,12 +34,23 @@ def get(url: str):
32
34
 
33
35
  if entity == "transform":
34
36
  transform = ln.Transform.get(uid)
35
- filepath_cache = transform._source_code_artifact.cache()
36
37
  target_filename = transform.key
37
- if not target_filename.endswith(transform._source_code_artifact.suffix):
38
- target_filename += transform._source_code_artifact.suffix
39
- filepath_cache.rename(target_filename)
40
- logger.success(f"cached source code of transform {uid} as {target_filename}")
38
+ if transform._source_code_artifact_id is not None:
39
+ # backward compat
40
+ filepath_cache = transform._source_code_artifact.cache()
41
+ if not target_filename.endswith(transform._source_code_artifact.suffix):
42
+ target_filename += transform._source_code_artifact.suffix
43
+ filepath_cache.rename(target_filename)
44
+ elif transform.source_code is not None:
45
+ if transform.key.endswith(".ipynb"):
46
+ script_to_notebook(transform, target_filename)
47
+ else:
48
+ Path(target_filename).write_text(transform.source_code)
49
+ else:
50
+ raise ValueError("No source code available for this transform.")
51
+ logger.success(
52
+ f"downloaded source code of transform {uid} as {target_filename}"
53
+ )
41
54
  elif entity == "artifact":
42
55
  artifact = ln.Artifact.get(uid)
43
56
  cache_path = artifact.cache()
@@ -90,13 +90,10 @@ def save_from_filepath_cli(
90
90
  " in Transform registry. Did you run ln.context.track()?"
91
91
  )
92
92
  return "not-tracked-in-transform-registry"
93
- # refactor this, save_context_core should not depend on transform_family
94
- transform_family = transform.versions
95
93
  else:
96
- # the corresponding transform family in the transform table
97
- transform_family = ln.Transform.filter(uid__startswith=stem_uid).all()
98
- # the specific version
99
- transform = transform_family.get(version=transform_version)
94
+ transform = ln.Transform.get(
95
+ uid__startswith=stem_uid, version=transform_version
96
+ )
100
97
  # latest run of this transform by user
101
98
  run = ln.Run.filter(transform=transform).order_by("-started_at").first()
102
99
  if run.created_by.id != ln_setup.settings.user.id:
@@ -110,6 +107,5 @@ def save_from_filepath_cli(
110
107
  run=run,
111
108
  transform=transform,
112
109
  filepath=filepath,
113
- transform_family=transform_family,
114
110
  from_cli=True,
115
111
  )
@@ -0,0 +1,42 @@
1
+ import os
2
+ from datetime import datetime, timedelta, timezone
3
+ from lamindb_setup.core._hub_client import connect_hub_with_auth
4
+ from lamindb_setup.core._hub_core import create_api_key
5
+
6
+
7
+ def test_entrypoint():
8
+ exit_status = os.system("lamin --help")
9
+ assert exit_status == 0
10
+
11
+
12
+ def test_cli_login():
13
+ exit_status = os.system("lamin login testuser1")
14
+ assert exit_status == 0
15
+
16
+ exit_status = os.system(
17
+ "lamin login testuser1 --key cEvcwMJFX4OwbsYVaMt2Os6GxxGgDUlBGILs2RyS"
18
+ )
19
+ assert exit_status == 0
20
+
21
+
22
+ def test_cli_login_api_key():
23
+ expires_at = datetime.now(tz=timezone.utc) + timedelta(days=1)
24
+ api_key = create_api_key(
25
+ {
26
+ "expires_at": expires_at.strftime("%Y-%m-%d"),
27
+ "description": "test_cli_login_api_key",
28
+ }
29
+ )
30
+
31
+ exit_status = os.system(f"echo {api_key} | lamin login")
32
+ assert exit_status == 0
33
+
34
+ os.environ["LAMIN_API_KEY"] = api_key
35
+ exit_status = os.system("lamin login")
36
+ assert exit_status == 0
37
+
38
+ hub = connect_hub_with_auth()
39
+ hub.table("api_key").delete().eq("description", "test_cli_login_api_key").execute()
40
+ hub.auth.sign_out({"scope": "local"})
41
+
42
+ os.system("lamin login testuser1@lamin.ai")
@@ -5,6 +5,7 @@ import nbproject_test
5
5
  import pytest
6
6
  from nbproject.dev import read_notebook, write_notebook
7
7
  from nbclient.exceptions import CellExecutionError
8
+ import json
8
9
  import lamindb as ln
9
10
 
10
11
  notebook_dir = "./sub/lamin-cli/tests/notebooks/"
@@ -39,20 +40,22 @@ def test_save_non_consecutive():
39
40
  version="1",
40
41
  name="My test notebook (non-consecutive)",
41
42
  type="notebook",
42
- )
43
- transform.save()
44
- run = ln.Run(transform=transform)
45
- run.save()
46
- result = subprocess.run(
47
- f"lamin save {notebook_path}", # noqa
43
+ ).save()
44
+ ln.Run(transform=transform).save()
45
+
46
+ process = subprocess.Popen(
47
+ f"lamin save {notebook_path}",
48
48
  shell=True,
49
- capture_output=True,
50
49
  env=env,
50
+ stdin=subprocess.PIPE,
51
+ stdout=subprocess.PIPE,
52
+ stderr=subprocess.PIPE,
53
+ text=True,
51
54
  )
52
- print(result.stdout.decode())
53
- print(result.stderr.decode())
54
- assert result.returncode == 1
55
- assert "were not run consecutively" in result.stdout.decode()
55
+ stdout, stderr = process.communicate("n")
56
+ assert "were not run consecutively" in stdout
57
+ assert "Do you still want to proceed with finishing?" in stdout
58
+ assert process.returncode == 1
56
59
 
57
60
 
58
61
  def test_save_consecutive():
@@ -62,6 +65,8 @@ def test_save_consecutive():
62
65
  env = os.environ
63
66
  env["LAMIN_TESTING"] = "true"
64
67
 
68
+ assert not Path("./with-title-and-initialized-consecutive.ipynb").exists()
69
+
65
70
  transform = ln.Transform.filter(uid="hlsFXswrJjtt0000").one_or_none()
66
71
  assert transform is None
67
72
 
@@ -92,47 +97,80 @@ def test_save_consecutive():
92
97
  capture_output=True,
93
98
  env=env,
94
99
  )
100
+ print(result.stdout.decode())
101
+ print(result.stderr.decode())
95
102
  assert result.returncode == 0
96
103
 
97
104
  # now, we have the associated artifacts
98
105
  transform = ln.Transform.filter(uid="hlsFXswrJjtt0000").one_or_none()
99
106
  assert transform is not None
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 == "EQrdZpS-fPaz5MKk_g02AA"
107
+ assert (
108
+ transform.source_code
109
+ == """# %% [markdown]
110
+ # # transform.name
111
+
112
+ # %%
113
+ import lamindb as ln
114
+
115
+ # %%
116
+ ln.context.uid = "hlsFXswrJjtt0000"
117
+ ln.context.track()
118
+
119
+ # %%
120
+ print("my consecutive cell")
121
+ """
122
+ )
123
+ assert transform.hash == "fHpHnC_pScmOl3ZR8x5cTQ"
124
+ # below is the test that we can use if store the run repot as `.ipynb`
125
+ # and not as html as we do right now
126
+ assert transform.latest_run.report.suffix == ".html"
127
+ # with open(transform.latest_run.report.path, "r") as f:
128
+ # json_notebook = json.load(f)
129
+ # # test that title is stripped from notebook
130
+ # assert json_notebook["cells"][0] == {
131
+ # "cell_type": "markdown",
132
+ # "metadata": {},
133
+ # "source": [],
134
+ # }
135
+ # testing for the hash of the report makes no sense because it contains timestamps
103
136
  assert transform.latest_run.environment.path.exists()
104
- assert transform._source_code_artifact.path.exists()
137
+ assert transform._source_code_artifact is None
105
138
 
106
- # now, assume the user modifies the notebook
139
+ # edit the notebook
107
140
  nb = read_notebook(notebook_path)
108
- # simulate editing the notebook (here, duplicate last cell)
109
141
  new_cell = nb.cells[-1].copy()
110
142
  new_cell["execution_count"] += 1
111
- nb.cells.append(new_cell)
143
+ nb.cells.append(new_cell) # duplicate last cell
112
144
  write_notebook(nb, notebook_path)
113
145
 
114
- # try re-running - it fails
146
+ # attempt re-running - it fails
115
147
  with pytest.raises(CellExecutionError) as error:
116
148
  nbproject_test.execute_notebooks(notebook_path, print_outputs=True)
117
149
  # print(error.exconly())
118
150
  assert "UpdateContext" in error.exconly()
119
151
 
120
- # try re-saving - it works but will issue an interactive warning dialogue
121
- # that clarifies that the user is about to re-save the notebook
122
- result = subprocess.run(
152
+ # attempt re-saving - it works but the user needs to confirm overwriting
153
+ # source code and run report
154
+ process = subprocess.Popen(
123
155
  f"lamin save {notebook_path}",
124
156
  shell=True,
125
- capture_output=True,
126
157
  env=env,
158
+ stdin=subprocess.PIPE,
159
+ stdout=subprocess.PIPE,
160
+ stderr=subprocess.PIPE,
161
+ text=True,
127
162
  )
128
- assert result.returncode == 0
163
+ stdout, stderr = process.communicate("y\ny")
164
+ assert "You are about to overwrite existing source code" in stdout
165
+ assert "You are about to overwrite an existing report" in stdout
166
+ assert process.returncode == 0
129
167
  # the source code is overwritten with the edits, reflected in a new hash
130
168
  transform = ln.Transform.get("hlsFXswrJjtt0000")
131
169
  assert transform.latest_run.report.path.exists()
132
170
  assert transform.latest_run.report.path == transform.latest_run.report.path
133
- assert transform._source_code_artifact.hash == "DMVEHVQqmY3ektOg2KtKKA"
171
+ assert transform.hash == "b63gPnGqNWBE0G4G3pOghw"
134
172
  assert transform.latest_run.environment.path.exists()
135
- assert transform._source_code_artifact.path.exists()
173
+ assert transform._source_code_artifact is None
136
174
 
137
175
  # get the the source code via command line
138
176
  result = subprocess.run(
@@ -142,6 +180,11 @@ def test_save_consecutive():
142
180
  capture_output=True,
143
181
  )
144
182
  # print(result.stderr.decode())
183
+ assert Path("./with-title-and-initialized-consecutive.ipynb").exists()
184
+ with open("./with-title-and-initialized-consecutive.ipynb", "r") as f:
185
+ json_notebook = json.load(f)
186
+ print(json_notebook["cells"][0])
187
+ assert json_notebook["cells"][0]["source"] == ["# My test notebook (consecutive)"]
145
188
  assert result.returncode == 0
146
189
 
147
190
  # now, assume the user renames the notebook
@@ -37,9 +37,9 @@ def test_run_save_cache():
37
37
  assert "created Run" in result.stdout.decode()
38
38
 
39
39
  transform = ln.Transform.get("m5uCHTTpJnjQ")
40
- assert transform._source_code_artifact.hash == "Cwk0OPOyUH5nzTiU2ISlDQ"
40
+ assert transform.hash == "Cwk0OPOyUH5nzTiU2ISlDQ"
41
41
  assert transform.latest_run.environment.path.exists()
42
- assert transform._source_code_artifact.path.exists()
42
+ assert transform._source_code_artifact is None
43
43
 
44
44
  # you can rerun the same script
45
45
  result = subprocess.run(
@@ -73,7 +73,7 @@ def test_run_save_cache():
73
73
  content = filepath.read_text() + "\n # edited"
74
74
  filepath.write_text(content)
75
75
 
76
- # re-run the script without commiting
76
+ # re-run the script without committing
77
77
  result = subprocess.run(
78
78
  f"python {filepath}",
79
79
  shell=True,
@@ -101,10 +101,10 @@ def test_run_save_cache():
101
101
  capture_output=True,
102
102
  env=env,
103
103
  )
104
- # print(result.stdout.decode())
105
- # print(result.stderr.decode())
104
+ print(result.stdout.decode())
105
+ print(result.stderr.decode())
106
106
  assert result.returncode == 1
107
- assert "Source code changed, bump version by setting" in result.stderr.decode()
107
+ assert "Source code changed, bump revision by setting" in result.stderr.decode()
108
108
 
109
109
  # try to get the the source code via command line
110
110
  result = subprocess.run(
@@ -1,3 +0,0 @@
1
- """Lamin CLI."""
2
-
3
- __version__ = "0.16.3"
@@ -1,16 +0,0 @@
1
- import os
2
-
3
-
4
- def test_entrypoint():
5
- exit_status = os.system("lamin --help")
6
- assert exit_status == 0
7
-
8
-
9
- def test_login():
10
- exit_status = os.system("lamin login testuser1")
11
- assert exit_status == 0
12
-
13
- exit_status = os.system(
14
- "lamin login testuser1 --key cEvcwMJFX4OwbsYVaMt2Os6GxxGgDUlBGILs2RyS"
15
- )
16
- assert exit_status == 0
File without changes
File without changes
File without changes
File without changes
File without changes