lamin_cli 1.2.0__tar.gz → 1.3.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.
Files changed (39) hide show
  1. lamin_cli-1.3.0/.github/workflows/build.yml +82 -0
  2. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/.gitignore +1 -0
  3. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/PKG-INFO +1 -1
  4. lamin_cli-1.3.0/lamin_cli/__init__.py +3 -0
  5. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/__main__.py +69 -24
  6. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/_save.py +29 -2
  7. lamin_cli-1.3.0/lamin_cli/compute/__init__.py +0 -0
  8. lamin_cli-1.3.0/lamin_cli/compute/modal.py +175 -0
  9. lamin_cli-1.3.0/noxfile.py +13 -0
  10. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_multi_process.py +1 -1
  11. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_save_files.py +1 -1
  12. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_save_notebooks.py +4 -6
  13. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_save_r_code.py +1 -1
  14. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_save_scripts.py +1 -1
  15. lamin_cli-1.3.0/tests/modal/test_modal.py +19 -0
  16. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/run-track-and-finish.py +0 -4
  17. lamin_cli-1.2.0/lamin_cli/__init__.py +0 -3
  18. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/.github/workflows/doc-changes.yml +0 -0
  19. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/.pre-commit-config.yaml +0 -0
  20. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/LICENSE +0 -0
  21. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/README.md +0 -0
  22. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/_cache.py +0 -0
  23. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/_load.py +0 -0
  24. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/_migration.py +0 -0
  25. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/lamin_cli/_settings.py +0 -0
  26. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/pyproject.toml +0 -0
  27. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/conftest.py +0 -0
  28. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_cli.py +0 -0
  29. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_load.py +0 -0
  30. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_migrate.py +0 -0
  31. {lamin_cli-1.2.0/tests → lamin_cli-1.3.0/tests/core}/test_parse_uid_from_code.py +0 -0
  32. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/notebooks/not-initialized.ipynb +0 -0
  33. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/notebooks/with-title-and-initialized-consecutive.ipynb +0 -0
  34. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/notebooks/with-title-and-initialized-non-consecutive.ipynb +0 -0
  35. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/merely-import-lamindb.py +0 -0
  36. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/run-track-and-finish-sync-git.py +0 -0
  37. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/run-track-with-params.py +0 -0
  38. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/run-track.R +0 -0
  39. {lamin_cli-1.2.0 → lamin_cli-1.3.0}/tests/scripts/run-track.qmd +0 -0
@@ -0,0 +1,82 @@
1
+ name: build
2
+
3
+ on:
4
+ push:
5
+ branches: [release]
6
+ pull_request:
7
+
8
+ jobs:
9
+ pre-filter:
10
+ runs-on: ubuntu-latest
11
+ outputs:
12
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - uses: dorny/paths-filter@v3
19
+ id: changes
20
+ if: github.event_name != 'push'
21
+ with:
22
+ filters: |
23
+ modal:
24
+ - 'lamin_cli/compute/modal.py'
25
+ - 'tests/modal/**'
26
+
27
+ - id: set-matrix
28
+ shell: bash
29
+ run: |
30
+ BASE_GROUPS=$(jq -n -c '[]')
31
+
32
+ if [[ "${{ github.event_name }}" == "push" || "${{ steps.changes.outputs.modal }}" == "true" ]]; then
33
+ # Run everything on push or when modal paths change
34
+ MATRIX=$(jq -n -c --argjson groups "$BASE_GROUPS" '{group: ($groups + ["modal"])}')
35
+ else
36
+ # Otherwise only run base groups
37
+ MATRIX=$(jq -n -c --argjson groups "$BASE_GROUPS" '{group: $groups}')
38
+ fi
39
+
40
+ # Output as single line for GitHub Actions
41
+ echo "matrix=$(echo "$MATRIX" | jq -c .)" >> $GITHUB_OUTPUT
42
+
43
+ # Pretty print for debugging
44
+ echo "Generated matrix:"
45
+ echo "$MATRIX" | jq .
46
+
47
+ test:
48
+ needs: pre-filter
49
+ runs-on: ubuntu-latest
50
+ env:
51
+ LAMIN_API_KEY: ${{ secrets.LAMIN_API_KEY_TESTUSER1 }}
52
+ strategy:
53
+ fail-fast: false
54
+ matrix: ${{fromJson(needs.pre-filter.outputs.matrix)}}
55
+ timeout-minutes: 20
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ with:
59
+ submodules: recursive
60
+ fetch-depth: 0
61
+
62
+ - uses: actions/setup-python@v5
63
+ with:
64
+ python-version: 3.12
65
+
66
+ - name: cache pre-commit
67
+ uses: actions/cache@v4
68
+ with:
69
+ path: ~/.cache/pre-commit
70
+ key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
71
+
72
+ - run: pip install git+https://github.com/laminlabs/laminci
73
+
74
+ - run: uv pip install --system modal pytest
75
+
76
+ - run: modal token set --token-id ${{ secrets.MODAL_DEV_TOKEN_ID }} --token-secret ${{ secrets.MODAL_DEV_TOKEN_SECRET }}
77
+
78
+ - run: nox -s setup
79
+
80
+ - run: lamin login
81
+
82
+ - run: pytest tests/modal
@@ -1,4 +1,5 @@
1
1
  # LaminDB
2
+ modal_mount_dir/
2
3
  lamindb_docs/
3
4
  _build
4
5
  mydata/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamin_cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
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__ = "1.3.0"
@@ -9,6 +9,13 @@ from functools import wraps
9
9
  from importlib.metadata import PackageNotFoundError, version
10
10
  from typing import TYPE_CHECKING
11
11
 
12
+ from lamindb_setup._init_instance import (
13
+ DOC_DB,
14
+ DOC_INSTANCE_NAME,
15
+ DOC_MODULES,
16
+ DOC_STORAGE_ARG,
17
+ )
18
+
12
19
  if TYPE_CHECKING:
13
20
  from collections.abc import Mapping
14
21
 
@@ -41,12 +48,7 @@ else:
41
48
  "lamin": [
42
49
  {
43
50
  "name": "Connect to an instance",
44
- "commands": [
45
- "connect",
46
- "disconnect",
47
- "info",
48
- "init",
49
- ],
51
+ "commands": ["connect", "disconnect", "info", "init", "run"],
50
52
  },
51
53
  {
52
54
  "name": "Read & write data",
@@ -155,24 +157,20 @@ def schema_to_modules_callback(ctx, param, value):
155
157
 
156
158
  # fmt: off
157
159
  @main.command()
158
- @click.option("--storage", type=str, help="Local directory, s3://bucket_name, gs://bucket_name.")
159
- @click.option("--db", type=str, default=None, help="Postgres database connection URL, do not pass for SQLite.")
160
- @click.option("--modules", type=str, default=None, help="Comma-separated string of schema modules.")
161
- @click.option("--name", type=str, default=None, help="The instance name.")
162
- @click.option("--schema", type=str, default=None, help="[DEPRECATED] Use --modules instead.", callback=schema_to_modules_callback)
160
+ @click.option("--storage", type=str, default = ".", help=DOC_STORAGE_ARG)
161
+ @click.option("--name", type=str, default=None, help=DOC_INSTANCE_NAME)
162
+ @click.option("--db", type=str, default=None, help=DOC_DB)
163
+ @click.option("--modules", type=str, default=None, help=DOC_MODULES)
163
164
  # fmt: on
164
165
  def init(
165
166
  storage: str,
167
+ name: str | None,
166
168
  db: str | None,
167
169
  modules: str | None,
168
- name: str | None,
169
- schema: str | None,
170
170
  ):
171
171
  """Init an instance."""
172
172
  from lamindb_setup._init_instance import init as init_
173
173
 
174
- modules = modules if modules is not None else schema
175
-
176
174
  return init_(storage=storage, db=db, modules=modules, name=name)
177
175
 
178
176
 
@@ -188,6 +186,8 @@ def connect(instance: str):
188
186
  `lamin connect` switches
189
187
  {attr}`~lamindb.setup.core.SetupSettings.auto_connect` to `True` so that you
190
188
  auto-connect in a Python session upon importing `lamindb`.
189
+
190
+ For manually connecting in a Python session, use {func}`~lamindb.connect`.
191
191
  """
192
192
  from lamindb_setup import connect as connect_
193
193
  from lamindb_setup import settings as settings_
@@ -307,30 +307,75 @@ def get(entity: str, uid: str | None = None, key: str | None = None):
307
307
 
308
308
 
309
309
  @main.command()
310
- @click.argument("filepath", type=click.Path(exists=True, dir_okay=True, file_okay=True))
310
+ @click.argument("path", type=click.Path(exists=True, dir_okay=True, file_okay=True))
311
311
  @click.option("--key", type=str, default=None)
312
312
  @click.option("--description", type=str, default=None)
313
+ @click.option("--stem-uid", type=str, default=None)
313
314
  @click.option("--registry", type=str, default=None)
314
- def save(filepath: str, key: str, description: str, registry: str):
315
+ def save(path: str, key: str, description: str, stem_uid: str, registry: str):
315
316
  """Save a file or folder.
316
317
 
317
- Defaults to saving `.py` and `.ipynb` as {class}`~lamindb.Transform` and
318
- other file types and folders as {class}`~lamindb.Artifact`.
319
-
320
- You can save a `.py` or `.ipynb` file as an {class}`~lamindb.Artifact` by
321
- passing `--registry artifact`.
318
+ Defaults to saving `.py`, `.ipynb`, `.R`, `.Rmd`, and `.qmd` as {class}`~lamindb.Transform` and
319
+ other file types and folders as {class}`~lamindb.Artifact`. You can save a `.py` or `.ipynb` file as
320
+ an {class}`~lamindb.Artifact` by passing `--registry artifact`.
322
321
  """
323
322
  from lamin_cli._save import save_from_filepath_cli
324
323
 
325
- if save_from_filepath_cli(filepath, key, description, registry) is not None:
324
+ if save_from_filepath_cli(path, key, description, stem_uid, registry) is not None:
326
325
  sys.exit(1)
327
326
 
328
327
 
328
+ @main.command()
329
+ @click.argument("filepath", type=str)
330
+ @click.option("--project", type=str, default=None, help="A valid project name or uid. When running on Modal, creates an app with the same name.", required=True)
331
+ @click.option("--image-url", type=str, default=None, help="A URL to the base docker image to use.")
332
+ @click.option("--packages", type=str, default="lamindb", help="A comma-separated list of additional packages to install.")
333
+ @click.option("--cpu", type=float, default=None, help="Configuration for the CPU.")
334
+ @click.option("--gpu", type=str, default=None, help="The type of GPU to use (only compatible with cuda images).")
335
+ def run(filepath: str, project: str, image_url: str, packages: str, cpu: int, gpu: str | None):
336
+ """Run a compute job in the cloud.
337
+
338
+ This is an EXPERIMENTAL feature that enables to run a script on Modal.
339
+
340
+ Example: Given a valid project name "my_project".
341
+
342
+ ```
343
+ lamin run my_script.py --project my_project
344
+ ```
345
+ """
346
+ import shutil
347
+ from pathlib import Path
348
+
349
+ from lamin_cli.compute.modal import Runner
350
+
351
+ default_mount_dir = Path('./modal_mount_dir')
352
+ if not default_mount_dir.is_dir():
353
+ default_mount_dir.mkdir(parents=True, exist_ok=True)
354
+
355
+ shutil.copy(filepath, default_mount_dir)
356
+
357
+ filepath_in_mount_dir = Path(default_mount_dir) / Path(filepath).name
358
+
359
+ package_list = []
360
+ if packages:
361
+ package_list = [package.strip() for package in packages.split(',')]
362
+
363
+ runner = Runner(
364
+ local_mount_dir=default_mount_dir,
365
+ app_name=project,
366
+ packages=package_list,
367
+ image_url=image_url,
368
+ cpu=cpu,
369
+ gpu=gpu
370
+ )
371
+
372
+ runner.run(filepath_in_mount_dir)
373
+
374
+
329
375
  main.add_command(settings)
330
376
  main.add_command(cache)
331
377
  main.add_command(migrate)
332
378
 
333
-
334
379
  # https://stackoverflow.com/questions/57810659/automatically-generate-all-help-documentation-for-click-commands
335
380
  # https://claude.ai/chat/73c28487-bec3-4073-8110-50d1a2dd6b84
336
381
  def _generate_help():
@@ -46,6 +46,7 @@ def save_from_filepath_cli(
46
46
  filepath: str | Path,
47
47
  key: str | None,
48
48
  description: str | None,
49
+ stem_uid: str | None,
49
50
  registry: str | None,
50
51
  ) -> str | None:
51
52
  import lamindb_setup as ln_setup
@@ -99,10 +100,21 @@ def save_from_filepath_cli(
99
100
 
100
101
  if registry == "artifact":
101
102
  ln.settings.creation.artifact_silence_missing_run_warning = True
102
- if key is None and description is None:
103
+ revises = None
104
+ if stem_uid is not None:
105
+ revises = (
106
+ ln.Artifact.filter(uid__startswith=stem_uid)
107
+ .order_by("-created_at")
108
+ .first()
109
+ )
110
+ if revises is None:
111
+ raise ln.errors.InvalidArgument("The stem uid is not found.")
112
+ elif key is None and description is None:
103
113
  logger.error("Please pass a key or description via --key or --description")
104
114
  return "missing-key-or-description"
105
- artifact = ln.Artifact(filepath, key=key, description=description).save()
115
+ artifact = ln.Artifact(
116
+ filepath, key=key, description=description, revises=revises
117
+ ).save()
106
118
  logger.important(f"saved: {artifact}")
107
119
  logger.important(f"storage path: {artifact.path}")
108
120
  if ln_setup.settings.storage.type == "s3":
@@ -125,6 +137,15 @@ def save_from_filepath_cli(
125
137
  )
126
138
  return "not-tracked-in-transform-registry"
127
139
  else:
140
+ revises = None
141
+ if stem_uid is not None:
142
+ revises = (
143
+ ln.Transform.filter(uid__startswith=stem_uid)
144
+ .order_by("-created_at")
145
+ .first()
146
+ )
147
+ if revises is None:
148
+ raise ln.errors.InvalidArgument("The stem uid is not found.")
128
149
  # TODO: build in the logic that queries for relative file paths
129
150
  # we have in Context; add tests for multiple versions
130
151
  transform = ln.Transform.filter(
@@ -135,6 +156,7 @@ def save_from_filepath_cli(
135
156
  description=filepath.name,
136
157
  key=filepath.name,
137
158
  type="script" if filepath.suffix in {".R", ".py"} else "notebook",
159
+ revises=revises,
138
160
  ).save()
139
161
  logger.important(f"created Transform('{transform.uid}')")
140
162
  # latest run of this transform by user
@@ -147,6 +169,11 @@ def save_from_filepath_cli(
147
169
  )
148
170
  if response != "y":
149
171
  return "aborted-save-notebook-created-by-different-user"
172
+ if run is None and transform.key.endswith(".ipynb"):
173
+ run = ln.Run(transform=transform).save()
174
+ logger.important(
175
+ f"found no run, creating Run('{run.uid}') to display the html"
176
+ )
150
177
  return_code = save_context_core(
151
178
  run=run,
152
179
  transform=transform,
File without changes
@@ -0,0 +1,175 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import threading
5
+ from pathlib import Path
6
+
7
+ import lamindb_setup as ln_setup
8
+ import modal
9
+
10
+
11
+ def run_script(path: Path) -> dict:
12
+ """Takes a path to a script for running it as a function through Modal."""
13
+ result = {"success": False, "output": "", "error": ""}
14
+
15
+ def stream_output(stream, capture_list):
16
+ """Read from stream line by line and print in real-time while also capturing to a list."""
17
+ for line in iter(stream.readline, ""):
18
+ print(line, end="") # Print in real-time
19
+ capture_list.append(line)
20
+ stream.close()
21
+
22
+ if not path.exists():
23
+ raise FileNotFoundError(f"Script file not found: {path}")
24
+
25
+ try:
26
+ # Run the script using subprocess
27
+ process = subprocess.Popen(
28
+ [sys.executable, path.as_posix()],
29
+ stdout=subprocess.PIPE,
30
+ stderr=subprocess.PIPE,
31
+ text=True,
32
+ bufsize=1, # Line buffered
33
+ )
34
+
35
+ # Capture output and error while streaming stdout in real-time
36
+ stdout_lines: list[str] = []
37
+ stderr_lines: list[str] = []
38
+
39
+ # Create threads to handle stdout and stderr streams
40
+ stdout_thread = threading.Thread(
41
+ target=stream_output, args=(process.stdout, stdout_lines)
42
+ )
43
+ stderr_thread = threading.Thread(
44
+ target=stream_output, args=(process.stderr, stderr_lines)
45
+ )
46
+
47
+ # Set as daemon threads so they exit when the main program exits
48
+ stdout_thread.daemon = True
49
+ stderr_thread.daemon = True
50
+
51
+ # Start the threads
52
+ stdout_thread.start()
53
+ stderr_thread.start()
54
+
55
+ # Wait for the process to complete
56
+ return_code = process.wait()
57
+
58
+ # Wait for the threads to finish
59
+ stdout_thread.join()
60
+ stderr_thread.join()
61
+
62
+ # Join the captured output
63
+ stdout_output = "".join(stdout_lines)
64
+ stderr_output = "".join(stderr_lines)
65
+
66
+ # Check return code
67
+ if return_code == 0:
68
+ result["success"] = True
69
+ result["output"] = stdout_output
70
+ else:
71
+ result["error"] = stderr_output
72
+
73
+ except Exception as e:
74
+ import traceback
75
+
76
+ result["error"] = str(e) + "\n" + traceback.format_exc()
77
+ return result
78
+
79
+
80
+ class Runner:
81
+ def __init__(
82
+ self,
83
+ app_name: str,
84
+ local_mount_dir: str | Path = "./scripts",
85
+ remote_mount_dir: str | Path = "/scripts",
86
+ image_url: str | None = None,
87
+ packages: list[str] | None = None,
88
+ cpu: float | None = None,
89
+ gpu: str | None = None,
90
+ ):
91
+ self.app_name = app_name # we use the LaminDB project name as the app name
92
+ self.app = self.create_modal_app(app_name)
93
+
94
+ self.local_mount_dir = local_mount_dir
95
+ self.remote_mount_dir = remote_mount_dir
96
+
97
+ self.image = self.create_modal_image(
98
+ local_dir=local_mount_dir, packages=packages, image_url=image_url
99
+ )
100
+
101
+ local_secrets = self._configure_local_secrets()
102
+
103
+ self.modal_function = self.app.function(
104
+ image=self.image, cpu=cpu, gpu=gpu, secrets=[local_secrets]
105
+ )(run_script)
106
+
107
+ def run(self, script_local_path: Path) -> None:
108
+ script_remote_path = self.local_to_remote_path(str(script_local_path))
109
+ with modal.enable_output(show_progress=True): # Prints out modal logs
110
+ with self.app.run():
111
+ self.modal_function.remote(Path(script_remote_path))
112
+
113
+ def create_modal_app(self, app_name: str) -> modal.App:
114
+ app = modal.App(app_name)
115
+ return app
116
+
117
+ def local_to_remote_path(self, local_path: str | Path) -> str:
118
+ local_path = Path(local_path).absolute()
119
+ local_mount_dir = Path(self.local_mount_dir).absolute()
120
+ remote_mount_dir = Path(self.remote_mount_dir)
121
+
122
+ # Check if local_path is inside local_mount_dir
123
+ try:
124
+ # This will raise ValueError if local_path is not relative to local_mount_dir
125
+ relative_path = local_path.relative_to(local_mount_dir)
126
+ except ValueError as err:
127
+ raise ValueError(
128
+ f"Local path '{local_path}' is not inside the mount directory '{local_mount_dir}'"
129
+ ) from err
130
+
131
+ # Join remote_mount_dir with the relative path
132
+ remote_path = remote_mount_dir / relative_path
133
+
134
+ # Return as string with normalized separators
135
+ return remote_path.as_posix()
136
+
137
+ def _configure_local_secrets(self) -> dict:
138
+ if ln_setup.settings.user.api_key is None:
139
+ raise ValueError("Please authenticate via: lamin login")
140
+
141
+ all_env_variables = {
142
+ "LAMIN_API_KEY": ln_setup.settings.user.api_key,
143
+ "LAMIN_CURRENT_PROJECT": self.app_name,
144
+ "LAMIN_CURRENT_INSTANCE": ln_setup.settings.instance.slug,
145
+ }
146
+ local_secrets = modal.Secret.from_dict(all_env_variables)
147
+ return local_secrets
148
+
149
+ def create_modal_image(
150
+ self,
151
+ python_version: str = "3.12",
152
+ packages: list[str] | None = None,
153
+ local_dir: str | Path = "./scripts",
154
+ remote_dir: str = "/scripts/",
155
+ image_url: str | None = None,
156
+ env_variables: dict | None = None,
157
+ ) -> modal.Image:
158
+ if env_variables is None:
159
+ env_variables = {}
160
+ if packages is None:
161
+ packages = ["lamindb"]
162
+ else:
163
+ packages.append("lamindb") # Append lamindb to the list of packages
164
+
165
+ if image_url is None:
166
+ image = modal.Image.debian_slim(python_version=python_version)
167
+ else:
168
+ image = modal.Image.from_registry(image_url, add_python=python_version)
169
+ return (
170
+ image.pip_install(packages)
171
+ .env(env_variables)
172
+ .add_local_python_source("lamindb", "lamindb_setup", copy=True)
173
+ .run_commands("lamin settings set auto-connect true")
174
+ .add_local_dir(local_dir, remote_dir)
175
+ )
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ import nox
4
+ from laminci.nox import install_lamindb
5
+
6
+ IS_PR = os.getenv("GITHUB_EVENT_NAME") != "push"
7
+ nox.options.default_venv_backend = "none"
8
+
9
+
10
+ @nox.session
11
+ def setup(session):
12
+ branch = "main" if IS_PR else "release"
13
+ install_lamindb(session, branch=branch)
@@ -2,7 +2,7 @@ import subprocess
2
2
  from multiprocessing import Process
3
3
  from pathlib import Path
4
4
 
5
- scripts_dir = Path(__file__).parent.resolve() / "scripts"
5
+ scripts_dir = Path(__file__).parent.parent.resolve() / "scripts"
6
6
 
7
7
 
8
8
  def run_script():
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
 
4
4
  import lamindb_setup as ln_setup
5
5
 
6
- test_file = Path(__file__).parent.parent.resolve() / ".gitignore"
6
+ test_file = Path(__file__).parent.parent.parent.resolve() / ".gitignore"
7
7
 
8
8
 
9
9
  def test_save_file():
@@ -184,9 +184,7 @@ print("my consecutive cell")
184
184
  new_path = notebook_path.with_name("new_name.ipynb")
185
185
  os.system(f"cp {notebook_path} {new_path}")
186
186
 
187
- # upon re-running it, the user is asked to create a new stem uid
188
- with pytest.raises(CellExecutionError) as error:
189
- nbproject_test.execute_notebooks(new_path, print_outputs=True)
190
-
191
- print(error.exconly())
192
- assert "clashes with the existing key" in error.exconly()
187
+ # upon re-running it, the notebook name is updated
188
+ nbproject_test.execute_notebooks(new_path, print_outputs=True)
189
+ transform = ln.Transform.get("hlsFXswrJjtt0001")
190
+ assert "new_name.ipynb" in transform.key
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
 
5
5
  import lamindb as ln
6
6
 
7
- scripts_dir = Path(__file__).parent.resolve() / "scripts"
7
+ scripts_dir = Path(__file__).parent.parent.resolve() / "scripts"
8
8
 
9
9
 
10
10
  def test_run_save_cache():
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  import lamindb as ln
6
6
  from lamindb_setup import settings
7
7
 
8
- scripts_dir = Path(__file__).parent.resolve() / "scripts"
8
+ scripts_dir = Path(__file__).parent.parent.resolve() / "scripts"
9
9
 
10
10
 
11
11
  def test_save_without_uid():
@@ -0,0 +1,19 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+ scripts_dir = Path(__file__).parent.parent.resolve() / "scripts"
5
+
6
+
7
+ def test_run_on_modal():
8
+ filepath = scripts_dir / "run-track-and-finish.py"
9
+
10
+ subprocess.run("lamin connect laminlabs/lamindata", shell=True, check=True)
11
+ result = subprocess.run(
12
+ f"lamin run {filepath} --project 1QLbS6N7wwiL",
13
+ shell=True,
14
+ capture_output=True,
15
+ )
16
+ print(result.stdout.decode())
17
+ assert result.returncode == 0
18
+ assert "hello!" in result.stdout.decode()
19
+ assert "finished Run" in result.stdout.decode()
@@ -1,9 +1,5 @@
1
1
  import lamindb as ln
2
2
 
3
- # purposefully do not pass uid
4
- ln.track()
5
-
6
-
7
3
  if __name__ == "__main__":
8
4
  # we're using new_run here to mock the notebook situation
9
5
  # and cover the look up of an existing run in the tests
@@ -1,3 +0,0 @@
1
- """Lamin CLI."""
2
-
3
- __version__ = "1.2.0"
File without changes
File without changes
File without changes
File without changes
File without changes