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.
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/PKG-INFO +1 -1
- lamin_cli-0.17.1/lamin_cli/__init__.py +3 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/__main__.py +18 -2
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_get.py +18 -5
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_save.py +3 -7
- lamin_cli-0.17.1/tests/test_cli.py +42 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_save_notebooks.py +69 -26
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_save_scripts.py +6 -6
- lamin_cli-0.16.3/lamin_cli/__init__.py +0 -3
- lamin_cli-0.16.3/tests/test_cli.py +0 -16
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.github/workflows/doc-changes.yml +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.gitignore +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/.pre-commit-config.yaml +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/README.md +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_cache.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/lamin_cli/_migration.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/pyproject.toml +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/conftest.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/not-initialized.ipynb +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/merely-import-lamindb.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/scripts/run-track-and-finish.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_get.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_migrate.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_multi_process.py +0 -0
- {lamin_cli-0.16.3 → lamin_cli-0.17.1}/tests/test_save_files.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
assert
|
|
55
|
-
assert
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
137
|
+
assert transform._source_code_artifact is None
|
|
105
138
|
|
|
106
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
121
|
-
#
|
|
122
|
-
|
|
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
|
-
|
|
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.
|
|
171
|
+
assert transform.hash == "b63gPnGqNWBE0G4G3pOghw"
|
|
134
172
|
assert transform.latest_run.environment.path.exists()
|
|
135
|
-
assert transform._source_code_artifact
|
|
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.
|
|
40
|
+
assert transform.hash == "Cwk0OPOyUH5nzTiU2ISlDQ"
|
|
41
41
|
assert transform.latest_run.environment.path.exists()
|
|
42
|
-
assert transform._source_code_artifact
|
|
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
|
|
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
|
-
|
|
105
|
-
|
|
104
|
+
print(result.stdout.decode())
|
|
105
|
+
print(result.stderr.decode())
|
|
106
106
|
assert result.returncode == 1
|
|
107
|
-
assert "Source code changed, bump
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lamin_cli-0.16.3 → lamin_cli-0.17.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
|