lamindb_setup 0.71.2__tar.gz → 0.71.4__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 (89) hide show
  1. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/.github/workflows/build.yml +2 -2
  2. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/PKG-INFO +1 -1
  3. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/changelog.md +7 -0
  4. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/03-add-managed-storage.ipynb +26 -7
  5. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/__init__.py +1 -1
  6. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_connect_instance.py +78 -61
  7. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_delete.py +6 -46
  8. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_init_instance.py +18 -17
  9. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_set_managed_storage.py +4 -0
  10. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_hub_core.py +5 -3
  11. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_instance.py +3 -12
  12. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_storage.py +10 -1
  13. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_user.py +2 -6
  14. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/django.py +4 -1
  15. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/hashing.py +23 -20
  16. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_connect_instance.py +8 -11
  17. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-local/conftest.py +1 -1
  18. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-local/test_all.py +3 -3
  19. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-prod/conftest.py +2 -2
  20. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/.github/workflows/latest-changes.jinja2 +0 -0
  21. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/.github/workflows/latest-changes.yml +0 -0
  22. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/.gitignore +0 -0
  23. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/.pre-commit-config.yaml +0 -0
  24. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/LICENSE +0 -0
  25. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/README.md +0 -0
  26. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  27. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  28. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  29. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  30. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
  31. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  32. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/test-multi-session.ipynb +0 -0
  33. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-cloud/test_notebooks.py +0 -0
  34. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-cache-management.ipynb +0 -0
  35. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
  36. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  37. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-empty-init.ipynb +0 -0
  38. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-import-schema.ipynb +0 -0
  39. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  40. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  41. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  42. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/hub-prod/test_notebooks2.py +0 -0
  43. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/index.md +0 -0
  44. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/notebooks.md +0 -0
  45. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/docs/reference.md +0 -0
  46. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_cache.py +0 -0
  47. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_check.py +0 -0
  48. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_check_setup.py +0 -0
  49. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_close.py +0 -0
  50. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_django.py +0 -0
  51. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_exportdb.py +0 -0
  52. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_importdb.py +0 -0
  53. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_migrate.py +0 -0
  54. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_register_instance.py +0 -0
  55. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_schema.py +0 -0
  56. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_setup_user.py +0 -0
  57. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/_silence_loggers.py +0 -0
  58. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/__init__.py +0 -0
  59. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_aws_storage.py +0 -0
  60. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_deprecated.py +0 -0
  61. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_docs.py +0 -0
  62. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_hub_client.py +0 -0
  63. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_hub_crud.py +0 -0
  64. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_hub_utils.py +0 -0
  65. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings.py +0 -0
  66. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_load.py +0 -0
  67. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_save.py +0 -0
  68. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_settings_store.py +0 -0
  69. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  70. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  71. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/exceptions.py +0 -0
  72. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/types.py +0 -0
  73. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/lamindb_setup/core/upath.py +0 -0
  74. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/noxfile.py +0 -0
  75. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/pyproject.toml +0 -0
  76. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_delete_instance.py +0 -0
  77. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_init_instance.py +0 -0
  78. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_login.py +0 -0
  79. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_migrate.py +0 -0
  80. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-cloud/test_set_storage.py +0 -0
  81. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-prod/test_auto_connect.py +0 -0
  82. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-prod/test_django.py +0 -0
  83. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  84. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/hub-prod/test_upath.py +0 -0
  85. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/storage/test_hashing.py +0 -0
  86. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/storage/test_storage_access.py +0 -0
  87. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/storage/test_storage_basis.py +0 -0
  88. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/storage/test_storage_stats.py +0 -0
  89. {lamindb_setup-0.71.2 → lamindb_setup-0.71.4}/tests/storage/test_to_url.py +0 -0
@@ -61,7 +61,7 @@ jobs:
61
61
  repository: laminlabs/laminapp-ui
62
62
  token: ${{ secrets.GH_TOKEN_DEPLOY_LAMINAPP }}
63
63
  path: laminhub
64
- ref: 11b0e5065d6f1a05484e1ef8ab8413f3c65bfa5a
64
+ ref: main
65
65
  - uses: actions/cache@v3
66
66
  with:
67
67
  path: ~/.cache/pre-commit
@@ -130,7 +130,7 @@ jobs:
130
130
  repository: laminlabs/laminapp-ui
131
131
  token: ${{ secrets.GH_TOKEN_DEPLOY_LAMINAPP }}
132
132
  path: laminhub
133
- ref: 11b0e5065d6f1a05484e1ef8ab8413f3c65bfa5a
133
+ ref: main
134
134
  - name: Set env file for local test of edge functions
135
135
  run: |
136
136
  touch .env.local
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb_setup
3
- Version: 0.71.2
3
+ Version: 0.71.4
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <laminlabs@gmail.com>
6
6
  Description-Content-Type: text/markdown
@@ -3,6 +3,13 @@
3
3
  <!-- prettier-ignore -->
4
4
  Name | PR | Developer | Date | Version
5
5
  --- | --- | --- | --- | ---
6
+ ⚡️ Speed-up file hash | [761](https://github.com/laminlabs/lamindb-setup/pull/761) | [Koncopd](https://github.com/Koncopd) | 2024-05-11 | 0.71.4
7
+ ♻️ Account for public storage locations | [758](https://github.com/laminlabs/lamindb-setup/pull/758) | [falexwolf](https://github.com/falexwolf) | 2024-05-10 |
8
+ ♻️ Make init_instance_hub idempotent | [757](https://github.com/laminlabs/lamindb-setup/pull/757) | [falexwolf](https://github.com/falexwolf) | 2024-05-09 |
9
+ ♻️ Refactor delete & connect | [756](https://github.com/laminlabs/lamindb-setup/pull/756) | [falexwolf](https://github.com/falexwolf) | 2024-05-09 |
10
+ 🔥 Remove laminapp-admin logic | [755](https://github.com/laminlabs/lamindb-setup/pull/755) | [falexwolf](https://github.com/falexwolf) | 2024-05-08 |
11
+ ♻️ Better treatment of current_user_id | [754](https://github.com/laminlabs/lamindb-setup/pull/754) | [falexwolf](https://github.com/falexwolf) | 2024-05-06 | 0.71.2
12
+ 💚 Fix tests | [753](https://github.com/laminlabs/lamindb-setup/pull/753) | [fredericenard](https://github.com/fredericenard) | 2024-05-06 |
6
13
  ♻️ Actually use local db | [751](https://github.com/laminlabs/lamindb-setup/pull/751) | [falexwolf](https://github.com/falexwolf) | 2024-05-06 | 0.71.2
7
14
  ✏️ Restore view_tree py3.9 compatibility | [750](https://github.com/laminlabs/lamindb-setup/pull/750) | [sunnyosun](https://github.com/sunnyosun) | 2024-05-06 |
8
15
  💚 Pin laminapp-ui | [749](https://github.com/laminlabs/lamindb-setup/pull/749) | [falexwolf](https://github.com/falexwolf) | 2024-05-06 |
@@ -255,7 +255,8 @@
255
255
  "# root.fs contains the underlying fsspec filesystem\n",
256
256
  "assert (\n",
257
257
  " ln_setup.settings.storage.root.fs.cache_regions # set by lamindb to True for s3 by default\n",
258
- ")"
258
+ ")\n",
259
+ "assert ln_setup.settings.storage._instance_id is not None"
259
260
  ]
260
261
  },
261
262
  {
@@ -287,6 +288,26 @@
287
288
  "assert not ln_setup.settings.storage.root.fs.cache_regions"
288
289
  ]
289
290
  },
291
+ {
292
+ "cell_type": "markdown",
293
+ "id": "015b8f36",
294
+ "metadata": {},
295
+ "source": [
296
+ "Cloud storage with mere read access:"
297
+ ]
298
+ },
299
+ {
300
+ "cell_type": "code",
301
+ "execution_count": null,
302
+ "id": "4ef50cab",
303
+ "metadata": {},
304
+ "outputs": [],
305
+ "source": [
306
+ "with pytest.raises(ValueError) as error:\n",
307
+ " set_managed_storage(\"gs://rxrx1-europe-west4/images/test/HEPG2-08\")\n",
308
+ "assert error.exconly().startswith(\"ValueError: Cannot manage storage without write access\")"
309
+ ]
310
+ },
290
311
  {
291
312
  "cell_type": "markdown",
292
313
  "id": "83a2ee6b",
@@ -302,16 +323,14 @@
302
323
  "metadata": {},
303
324
  "outputs": [],
304
325
  "source": [
305
- "from laminhub_rest.core.collaborator._add_collaborator import add_collaborator\n",
326
+ "from laminhub_rest.core.instance.collaborator import InstanceCollaboratorHandler\n",
306
327
  "from lamindb_setup.core._hub_client import connect_hub_with_auth\n",
307
328
  "\n",
308
329
  "admin_hub = connect_hub_with_auth()\n",
309
- "add_collaborator(\n",
330
+ "InstanceCollaboratorHandler(admin_hub).add_by_slug(\n",
331
+ " \"testuser1/test-add-managed-storage\",\n",
310
332
  " \"testuser2\",\n",
311
- " \"testuser1\",\n",
312
- " \"test-add-managed-storage\",\n",
313
- " \"write\",\n",
314
- " admin_hub,\n",
333
+ " \"write\"\n",
315
334
  ")\n",
316
335
  "admin_hub.auth.close()"
317
336
  ]
@@ -34,7 +34,7 @@ Modules & settings:
34
34
 
35
35
  """
36
36
 
37
- __version__ = "0.71.2" # denote a release candidate for 0.1.0 with 0.1rc1
37
+ __version__ = "0.71.4" # denote a release candidate for 0.1.0 with 0.1rc1
38
38
 
39
39
  import sys
40
40
  from os import name as _os_name
@@ -55,7 +55,10 @@ def check_db_dsn_equal_up_to_credentials(db_dsn_hub, db_dsn_local):
55
55
 
56
56
 
57
57
  def update_db_using_local(
58
- hub_instance_result: dict[str, str], settings_file: Path, db: str | None = None
58
+ hub_instance_result: dict[str, str],
59
+ settings_file: Path,
60
+ db: str | None = None,
61
+ raise_permission_error=True,
59
62
  ) -> str | None:
60
63
  db_updated = None
61
64
  # check if postgres
@@ -77,7 +80,11 @@ def update_db_using_local(
77
80
  db_dsn_local = LaminDsnModel(db=isettings.db)
78
81
  else:
79
82
  # just take the default hub result and ensure there is actually a user
80
- if db_dsn_hub.db.user == "none" and db_dsn_hub.db.password == "none":
83
+ if (
84
+ db_dsn_hub.db.user == "none"
85
+ and db_dsn_hub.db.password == "none"
86
+ and raise_permission_error
87
+ ):
81
88
  raise PermissionError(
82
89
  "No database access, please ask your admin to provide you with"
83
90
  " a DB URL and pass it via --db <db_url>"
@@ -101,6 +108,65 @@ def update_db_using_local(
101
108
  return db_updated
102
109
 
103
110
 
111
+ def _connect_instance(
112
+ owner: str,
113
+ name: str,
114
+ *,
115
+ db: str | None = None,
116
+ raise_permission_error: bool = True,
117
+ ) -> InstanceSettings:
118
+ settings_file = instance_settings_file(name, owner)
119
+ make_hub_request = True
120
+ if settings_file.exists():
121
+ isettings = load_instance_settings(settings_file)
122
+ # skip hub request for a purely local instance
123
+ make_hub_request = isettings.is_remote
124
+ if make_hub_request:
125
+ # the following will return a string if the instance does not exist
126
+ # on the hub
127
+ hub_result = load_instance_from_hub(owner=owner, name=name)
128
+ # if hub_result is not a string, it means it made a request
129
+ # that successfully returned metadata
130
+ if not isinstance(hub_result, str):
131
+ instance_result, storage_result = hub_result
132
+ db_updated = update_db_using_local(
133
+ instance_result,
134
+ settings_file,
135
+ db=db,
136
+ raise_permission_error=raise_permission_error,
137
+ )
138
+ ssettings = StorageSettings(
139
+ root=storage_result["root"],
140
+ region=storage_result["region"],
141
+ uid=storage_result["lnid"],
142
+ uuid=UUID(storage_result["id"]),
143
+ instance_id=UUID(instance_result["id"]),
144
+ )
145
+ isettings = InstanceSettings(
146
+ id=UUID(instance_result["id"]),
147
+ owner=owner,
148
+ name=name,
149
+ storage=ssettings,
150
+ db=db_updated,
151
+ schema=instance_result["schema_str"],
152
+ git_repo=instance_result["git_repo"],
153
+ keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
154
+ is_on_hub=True,
155
+ )
156
+ check_whether_migrations_in_sync(instance_result["lamindb_version"])
157
+ else:
158
+ message = INSTANCE_NOT_FOUND_MESSAGE.format(
159
+ owner=owner, name=name, hub_result=hub_result
160
+ )
161
+ if settings_file.exists():
162
+ isettings = load_instance_settings(settings_file)
163
+ if isettings.is_remote:
164
+ raise InstanceNotFoundError(message)
165
+ else:
166
+ raise InstanceNotFoundError(message)
167
+ return isettings
168
+
169
+
104
170
  @unlock_cloud_sqlite_upon_exception(ignore_prev_locker=True)
105
171
  def connect(
106
172
  slug: str,
@@ -134,71 +200,22 @@ def connect(
134
200
  elif settings._instance_exists and f"{owner}/{name}" != settings.instance.slug:
135
201
  close_instance(mute=True)
136
202
 
137
- settings_file = instance_settings_file(name, owner)
138
-
139
- make_hub_request = True
140
- if settings_file.exists():
141
- isettings = load_instance_settings(settings_file)
142
- # mimic instance_result from hub
143
- instance_result = {"id": isettings._id.hex}
144
- # skip hub request for a purely local instance
145
- make_hub_request = isettings.is_remote
146
-
147
- if make_hub_request:
148
- # the following will return a string if the instance does not exist
149
- # on the hub
150
- hub_result = load_instance_from_hub(owner=owner, name=name)
151
- # if hub_result is not a string, it means it made a request
152
- # that successfully returned metadata
153
- if not isinstance(hub_result, str):
154
- instance_result, storage_result = hub_result
155
- db_updated = update_db_using_local(
156
- instance_result, settings_file, db=db
157
- )
158
- ssettings = StorageSettings(
159
- root=storage_result["root"],
160
- region=storage_result["region"],
161
- uid=storage_result["lnid"],
162
- uuid=UUID(storage_result["id"]),
163
- instance_id=UUID(instance_result["id"]),
164
- )
165
- isettings = InstanceSettings(
166
- id=UUID(instance_result["id"]),
167
- owner=owner,
168
- name=name,
169
- storage=ssettings,
170
- db=db_updated,
171
- schema=instance_result["schema_str"],
172
- git_repo=instance_result["git_repo"],
173
- keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
174
- is_on_hub=True,
175
- )
176
- check_whether_migrations_in_sync(instance_result["lamindb_version"])
203
+ try:
204
+ isettings = _connect_instance(owner, name, db=db)
205
+ except InstanceNotFoundError as e:
206
+ if _raise_not_found_error:
207
+ raise e
177
208
  else:
178
- message = INSTANCE_NOT_FOUND_MESSAGE.format(
179
- owner=owner, name=name, hub_result=hub_result
180
- )
181
- if settings_file.exists():
182
- isettings = load_instance_settings(settings_file)
183
- if isettings.is_remote:
184
- if _raise_not_found_error:
185
- raise InstanceNotFoundError(message)
186
- return "instance-not-found"
187
-
188
- else:
189
- if _raise_not_found_error:
190
- raise InstanceNotFoundError(message)
191
- return "instance-not-found"
192
-
209
+ return "instance-not-found"
210
+ if isinstance(isettings, str):
211
+ return isettings
193
212
  if storage is not None:
194
213
  update_isettings_with_storage(isettings, storage)
195
214
  isettings._persist()
196
215
  if _test:
197
216
  return None
198
217
  silence_loggers()
199
- check, msg = isettings._load_db(
200
- do_not_lock_for_laminapp_admin=True
201
- ) # this also updates local SQLite
218
+ check, msg = isettings._load_db()
202
219
  if not check:
203
220
  local_db = (
204
221
  isettings._is_cloud_sqlite and isettings._sqlite_file_local.exists()
@@ -216,7 +233,7 @@ def connect(
216
233
  f"instance exists with id {isettings._id.hex}, but database is not"
217
234
  " loadable: re-initializing"
218
235
  )
219
- return "instance-corrupted-or-deleted", instance_result
236
+ return "instance-corrupted-or-deleted"
220
237
  # this is for testing purposes only
221
238
  if _TEST_FAILED_LOAD:
222
239
  raise RuntimeError("Technical testing error.")
@@ -1,29 +1,23 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import shutil
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
  from uuid import UUID
6
6
 
7
7
  from lamin_utils import logger
8
8
 
9
- from ._connect_instance import (
10
- INSTANCE_NOT_FOUND_MESSAGE,
11
- InstanceNotFoundError,
12
- get_owner_name_from_identifier,
13
- )
14
- from .core._hub_core import connect_instance as load_instance_from_hub
9
+ from ._connect_instance import _connect_instance, get_owner_name_from_identifier
15
10
  from .core._hub_core import delete_instance as delete_instance_on_hub
16
11
  from .core._hub_core import get_storage_records_for_instance
17
12
  from .core._settings import settings
18
- from .core._settings_instance import InstanceSettings
19
- from .core._settings_load import load_instance_settings
20
13
  from .core._settings_storage import StorageSettings
21
- from .core._settings_store import instance_settings_file
22
14
  from .core.upath import HOSTED_BUCKETS, check_storage_is_empty
23
15
 
24
16
  if TYPE_CHECKING:
25
17
  from pathlib import Path
26
18
 
19
+ from .core._settings_instance import InstanceSettings
20
+
27
21
 
28
22
  def delete_cache(cache_dir: Path):
29
23
  if cache_dir is not None and cache_dir.exists():
@@ -67,42 +61,8 @@ def delete(slug: str, force: bool = False, require_empty: bool = True) -> int |
67
61
  force: Whether to skip the confirmation prompt.
68
62
  require_empty: Whether to check if the instance is empty before deleting.
69
63
  """
70
- instance_owner, instance_name = get_owner_name_from_identifier(slug)
71
- if settings._instance_exists and settings.instance.name == instance_name:
72
- isettings = settings.instance
73
- else:
74
- settings_file = instance_settings_file(instance_name, instance_owner)
75
- if not settings_file.exists():
76
- hub_result = load_instance_from_hub(
77
- owner=instance_owner, name=instance_name
78
- )
79
- if isinstance(hub_result, str):
80
- message = INSTANCE_NOT_FOUND_MESSAGE.format(
81
- owner=instance_owner,
82
- name=instance_name,
83
- hub_result=hub_result,
84
- )
85
- raise InstanceNotFoundError(message)
86
- instance_result, storage_result = hub_result
87
- ssettings = StorageSettings(
88
- root=storage_result["root"],
89
- region=storage_result["region"],
90
- uid=storage_result["lnid"],
91
- uuid=UUID(storage_result["id"]),
92
- )
93
- isettings = InstanceSettings(
94
- id=UUID(instance_result["id"]),
95
- owner=instance_owner,
96
- name=instance_name,
97
- storage=ssettings,
98
- keep_artifacts_local=bool(instance_result["keep_artifacts_local"]),
99
- db=instance_result["db"] if "db" in instance_result else None,
100
- schema=instance_result["schema_str"],
101
- git_repo=instance_result["git_repo"],
102
- is_on_hub=True,
103
- )
104
- else:
105
- isettings = load_instance_settings(settings_file)
64
+ owner, name = get_owner_name_from_identifier(slug)
65
+ isettings = _connect_instance(owner, name, raise_permission_error=False)
106
66
  if isettings.dialect != "sqlite":
107
67
  logger.warning(
108
68
  f"delete() does not yet affect your Postgres database at {isettings.db}"
@@ -44,15 +44,17 @@ def register_storage_in_instance(ssettings: StorageSettings):
44
44
 
45
45
  from .core.hashing import hash_and_encode_as_b62
46
46
 
47
- assert ssettings._instance_id is not None
48
-
47
+ if ssettings._instance_id is not None:
48
+ instance_uid = hash_and_encode_as_b62(ssettings._instance_id.hex)[:12]
49
+ else:
50
+ instance_uid = None
49
51
  # how do we ensure that this function is only called passing
50
52
  # the managing instance?
51
53
  defaults = {
52
54
  "root": ssettings.root_as_str,
53
55
  "type": ssettings.type,
54
56
  "region": ssettings.region,
55
- "instance_uid": hash_and_encode_as_b62(ssettings._instance_id.hex)[:12],
57
+ "instance_uid": instance_uid,
56
58
  "created_by_id": current_user_id(),
57
59
  }
58
60
  if ssettings._uid is not None:
@@ -67,20 +69,19 @@ def register_storage_in_instance(ssettings: StorageSettings):
67
69
  def register_user(usettings):
68
70
  from lnschema_core.models import User
69
71
 
70
- if usettings.handle != "laminapp-admin":
71
- try:
72
- # need to have try except because of integer primary key migration
73
- user, created = User.objects.update_or_create(
74
- uid=usettings.uid,
75
- defaults={
76
- "handle": usettings.handle,
77
- "name": usettings.name,
78
- },
79
- )
80
- # for users with only read access, except via ProgrammingError
81
- # ProgrammingError: permission denied for table lnschema_core_user
82
- except (OperationalError, FieldError, ProgrammingError):
83
- pass
72
+ try:
73
+ # need to have try except because of integer primary key migration
74
+ user, created = User.objects.update_or_create(
75
+ uid=usettings.uid,
76
+ defaults={
77
+ "handle": usettings.handle,
78
+ "name": usettings.name,
79
+ },
80
+ )
81
+ # for users with only read access, except via ProgrammingError
82
+ # ProgrammingError: permission denied for table lnschema_core_user
83
+ except (OperationalError, FieldError, ProgrammingError):
84
+ pass
84
85
 
85
86
 
86
87
  def register_user_and_storage_in_instance(isettings: InstanceSettings, usettings):
@@ -31,6 +31,10 @@ def set_managed_storage(root: UPathStr, **fs_kwargs):
31
31
  ssettings = init_storage(
32
32
  root=root, instance_id=settings.instance._id, register_hub=True
33
33
  )
34
+ if ssettings._instance_id is None:
35
+ raise ValueError(
36
+ f"Cannot manage storage without write access: {ssettings.root}"
37
+ )
34
38
  settings.instance._storage = ssettings
35
39
  settings.instance._persist() # this also updates the settings object
36
40
  register_storage_in_instance(ssettings)
@@ -284,9 +284,11 @@ def _init_instance(isettings: InstanceSettings, client: Client) -> None:
284
284
  # as then init_instance is no longer idempotent
285
285
  try:
286
286
  client.table("instance").insert(fields, returning="minimal").execute()
287
- except APIError as e:
288
- logger.warning("instance likely already exists")
289
- raise e
287
+ except APIError:
288
+ logger.warning(
289
+ f"instance already existed at: https://lamin.ai/{isettings.owner}/{isettings.name}"
290
+ )
291
+ return None
290
292
  client.table("storage").update(
291
293
  {"instance_id": isettings._id.hex, "is_default": True}
292
294
  ).eq("id", isettings.storage._uuid.hex).execute() # type: ignore
@@ -42,7 +42,7 @@ class InstanceSettings:
42
42
  db: str | None = None, # DB URI
43
43
  schema: str | None = None, # comma-separated string of schema names
44
44
  git_repo: str | None = None, # a git repo URL
45
- is_on_hub: bool = False, # initialized from hub
45
+ is_on_hub: bool | None = None, # initialized from hub
46
46
  ):
47
47
  from ._hub_utils import validate_db_arg
48
48
 
@@ -406,9 +406,7 @@ class InstanceSettings:
406
406
 
407
407
  setup_django(self, init=True)
408
408
 
409
- def _load_db(
410
- self, do_not_lock_for_laminapp_admin: bool = False
411
- ) -> tuple[bool, str]:
409
+ def _load_db(self) -> tuple[bool, str]:
412
410
  # Is the database available and initialized as LaminDB?
413
411
  # returns a tuple of status code and message
414
412
  if self.dialect == "sqlite" and not self._sqlite_file.exists():
@@ -423,15 +421,8 @@ class InstanceSettings:
423
421
 
424
422
  from .django import setup_django
425
423
 
426
- # lock in all cases except if do_not_lock_for_laminapp_admin is True and
427
- # user is `laminapp-admin`
428
- # value doesn't matter if not a cloud sqlite instance
429
- lock_cloud_sqlite = self._is_cloud_sqlite and (
430
- not do_not_lock_for_laminapp_admin
431
- or settings.user.handle != "laminapp-admin"
432
- )
433
424
  # we need the local sqlite to setup django
434
- self._update_local_sqlite_file(lock_cloud_sqlite=lock_cloud_sqlite)
425
+ self._update_local_sqlite_file(lock_cloud_sqlite=self._is_cloud_sqlite)
435
426
  # setting up django also performs a check for migrations & prints them
436
427
  # as warnings
437
428
  # this should fail, e.g., if the db is not reachable
@@ -113,11 +113,20 @@ def init_storage(
113
113
  )
114
114
  # the below might update the uid with one that's already taken on the hub
115
115
  if ssettings.type_is_cloud or register_hub:
116
+ from ._hub_core import delete_storage_record
116
117
  from ._hub_core import init_storage as init_storage_hub
117
118
 
118
119
  init_storage_hub(ssettings)
119
120
  # below comes last only if everything else was successful
120
- mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
121
+ try:
122
+ mark_storage_root(ssettings.root, ssettings.uid) # type: ignore
123
+ except Exception:
124
+ logger.important(
125
+ f"due to lack of write access, LaminDB won't manage storage location: {ssettings.root}"
126
+ )
127
+ if ssettings._uuid is not None:
128
+ delete_storage_record(ssettings._uuid) # type: ignore
129
+ ssettings._instance_id = None
121
130
  return ssettings
122
131
 
123
132
 
@@ -46,10 +46,6 @@ class UserSettings:
46
46
  @property
47
47
  def id(self):
48
48
  """Integer id valid in current intance."""
49
- # do NOT use the function below because this
50
- # doesn't error - it needs to be this way to have
51
- # a user id available in migrations
52
- # from lnschema_core.users import current_user_id
53
- from lnschema_core.models import User
49
+ from lnschema_core.users import current_user_id
54
50
 
55
- return User.objects.get(uid=self.uid).id
51
+ return current_user_id()
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
  import builtins
5
5
  import os
6
6
  from pathlib import Path
7
- import shutil
8
7
  import time
9
8
  from lamin_utils import logger
10
9
  from ._settings_store import current_instance_settings_file
@@ -12,6 +11,7 @@ from ._settings_instance import InstanceSettings
12
11
 
13
12
  IS_RUN_FROM_IPYTHON = getattr(builtins, "__IPYTHON__", False)
14
13
  IS_SETUP = False
14
+ IS_MIGRATING = False
15
15
  CONN_MAX_AGE = 299
16
16
 
17
17
 
@@ -101,7 +101,10 @@ def setup_django(
101
101
  call_command("migrate", verbosity=2)
102
102
  isettings._update_cloud_sqlite_file(unlock_cloud_sqlite=False)
103
103
  elif init:
104
+ global IS_MIGRATING
105
+ IS_MIGRATING = True
104
106
  call_command("migrate", verbosity=0)
107
+ IS_MIGRATING = False
105
108
 
106
109
  global IS_SETUP
107
110
  IS_SETUP = True
@@ -40,11 +40,11 @@ def hash_set(s: set[str]) -> str:
40
40
  return to_b64_str(hashlib.md5(bstr).digest())[:20]
41
41
 
42
42
 
43
- def hash_md5s_from_dir(etags: list[str]) -> tuple[str, str]:
43
+ def hash_md5s_from_dir(hashes: list[str]) -> tuple[str, str]:
44
44
  # need to sort below because we don't want the order of parsing the dir to
45
45
  # affect the hash
46
46
  digests = b"".join(
47
- hashlib.md5(etag.encode("utf-8")).digest() for etag in sorted(etags)
47
+ hashlib.md5(hash.encode("utf-8")).digest() for hash in sorted(hashes)
48
48
  )
49
49
  digest = hashlib.md5(digests).digest()
50
50
  return to_b64_str(digest)[:22], "md5-d"
@@ -59,24 +59,27 @@ def hash_code(file_path: UPathStr):
59
59
  return hashlib.sha1(blob)
60
60
 
61
61
 
62
- def hash_file(file_path: Path, chunk_size=50 * 1024 * 1024) -> tuple[str, str]:
63
- chunks = []
62
+ def hash_file(
63
+ file_path: Path,
64
+ file_size: int | None = None,
65
+ chunk_size: int | None = 50 * 1024 * 1024,
66
+ ) -> tuple[str, str]:
64
67
  with open(file_path, "rb") as fp:
65
- # read first chunk
66
- chunks = [fp.read(chunk_size)]
67
- # try reading the 2nd chunk
68
- data = fp.read(chunk_size)
69
- if data:
70
- # go to end of file
68
+ if file_size is None:
69
+ fp.seek(0, 2)
70
+ file_size = fp.tell()
71
+ fp.seek(0, 0)
72
+ if chunk_size is None:
73
+ chunk_size = file_size
74
+ first_chunk = fp.read(chunk_size)
75
+ if file_size <= chunk_size:
76
+ digest = hashlib.md5(first_chunk).digest()
77
+ hash_type = "md5"
78
+ else:
71
79
  fp.seek(-chunk_size, 2)
72
- # read last chunk
73
- data = fp.read(chunk_size)
74
- chunks.append(data)
75
- if len(chunks) == 1:
76
- digest = hashlib.md5(chunks[0]).digest()
77
- hash_type = "md5"
78
- else:
79
- digests = b"".join(hashlib.sha1(chunk).digest() for chunk in chunks)
80
- digest = hashlib.sha1(digests).digest()
81
- hash_type = "sha1-fl" # sha1 first last chunk
80
+ last_chunk = fp.read(chunk_size)
81
+ digest = hashlib.sha1(
82
+ hashlib.sha1(first_chunk).digest() + hashlib.sha1(last_chunk).digest()
83
+ ).digest()
84
+ hash_type = "sha1-fl"
82
85
  return to_b64_str(digest)[:22], hash_type
@@ -7,8 +7,7 @@ import pytest
7
7
  from lamindb_setup._connect_instance import InstanceNotFoundError
8
8
  from lamindb_setup.core._hub_client import connect_hub_with_auth
9
9
  from lamindb_setup.core._hub_crud import update_instance
10
- from laminhub_rest.core.collaborator._add_collaborator import add_collaborator
11
- from laminhub_rest.core.collaborator._delete_collaborator import delete_collaborator
10
+ from laminhub_rest.core.instance.collaborator import InstanceCollaboratorHandler
12
11
  from postgrest.exceptions import APIError
13
12
 
14
13
  # @pytest.fixture
@@ -24,15 +23,16 @@ def test_connect_after_revoked_access():
24
23
  if os.getenv("LAMIN_ENV") == "prod":
25
24
  ln_setup.login("testuser1@lamin.ai")
26
25
  admin_hub = connect_hub_with_auth()
26
+ collaborator_handler = InstanceCollaboratorHandler(admin_hub)
27
27
  try:
28
28
  # if a previous test run failed, this will
29
29
  # error with a violation of a unique constraint
30
- add_collaborator(
30
+ collaborator_handler.add_by_slug(
31
+ "laminlabs/static-test-instance-private-sqlite",
31
32
  "testuser2",
32
- "laminlabs",
33
- "static-test-instance-private-sqlite",
34
33
  "write",
35
- admin_hub,
34
+ "default",
35
+ skip_insert_user_table=True,
36
36
  )
37
37
  except APIError:
38
38
  pass
@@ -44,11 +44,8 @@ def test_connect_after_revoked_access():
44
44
  ln_setup.settings.instance.storage.root_as_str
45
45
  == "s3://lamindb-setup-private-bucket"
46
46
  )
47
- delete_collaborator(
48
- "laminlabs",
49
- "static-test-instance-private-sqlite",
50
- ln_setup.settings.user._uuid,
51
- admin_hub,
47
+ collaborator_handler.delete_by_slug(
48
+ "laminlabs/static-test-instance-private-sqlite", "testuser2"
52
49
  )
53
50
  # make the instance private
54
51
  with pytest.raises(InstanceNotFoundError):
@@ -6,7 +6,7 @@ from lamin_utils import logger
6
6
  from laminhub_rest.dev._setup_local_hub import setup_local_hub
7
7
 
8
8
  pytest_plugins = [
9
- "laminhub_rest.test.fixtures.user",
9
+ "laminhub_rest.core.account.user.test.fixtures",
10
10
  "laminhub_rest.test.fixtures.run_id",
11
11
  ]
12
12
 
@@ -34,7 +34,7 @@ from lamindb_setup.core._settings_save import save_user_settings
34
34
  from lamindb_setup.core._settings_storage import base62
35
35
  from lamindb_setup.core._settings_storage import init_storage as init_storage_base
36
36
  from lamindb_setup.core._settings_user import UserSettings
37
- from laminhub_rest.core.collaborator._add_collaborator import add_collaborator_by_ids
37
+ from laminhub_rest.core.instance.collaborator import InstanceCollaboratorHandler
38
38
  from postgrest.exceptions import APIError
39
39
 
40
40
 
@@ -196,11 +196,11 @@ def test_db_user(
196
196
  )
197
197
  assert db_collaborator is None
198
198
  # now add testreader1 as a collaborator
199
- add_collaborator_by_ids(
199
+ InstanceCollaboratorHandler(admin_client).add(
200
200
  account_id=reader_settings._uuid,
201
201
  instance_id=instance_id,
202
202
  role="read",
203
- supabase_client=admin_client,
203
+ skip_insert_user_table=True,
204
204
  )
205
205
  # check that this was successful and can be read by the reader
206
206
  db_collaborator = select_collaborator(
@@ -1,5 +1,4 @@
1
1
  import os
2
- from typing import TYPE_CHECKING
3
2
  from uuid import UUID
4
3
 
5
4
  import lamindb_setup
@@ -10,6 +9,7 @@ from lamin_utils import logger
10
9
  def pytest_sessionstart(session: pytest.Session):
11
10
  lamindb_instance_id = UUID("e1a2d3ab762e4592af5a1e53f288284e")
12
11
  os.environ["LAMINDB_INSTANCE_ID_INIT"] = lamindb_instance_id.hex
12
+ assert lamindb_setup.settings.user.handle == "testuser2"
13
13
  lamindb_setup.init(
14
14
  storage="./default_storage",
15
15
  schema="bionty",
@@ -21,4 +21,4 @@ def pytest_sessionstart(session: pytest.Session):
21
21
 
22
22
  def pytest_sessionfinish(session: pytest.Session):
23
23
  logger.set_verbosity(1)
24
- lamindb_setup.delete("lamindb-setup-unit-tests", force=True)
24
+ lamindb_setup.delete("testuser2/lamindb-setup-unit-tests", force=True)
File without changes
File without changes