lamindb_setup 1.12.1__tar.gz → 1.13.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 (111) hide show
  1. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/PKG-INFO +4 -5
  2. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/__init__.py +1 -1
  3. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_connect_instance.py +1 -1
  4. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_migrate.py +21 -7
  5. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_schema_metadata.py +18 -10
  6. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/__init__.py +1 -0
  7. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_aws_options.py +40 -14
  8. lamindb_setup-1.13.0/lamindb_setup/core/_clone.py +93 -0
  9. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_hub_core.py +50 -0
  10. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings.py +13 -10
  11. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_instance.py +10 -5
  12. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_storage.py +9 -11
  13. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/django.py +73 -25
  14. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/upath.py +15 -30
  15. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/pyproject.toml +10 -4
  16. lamindb_setup-1.13.0/tests/hub-cloud/test_clone_instance.py +87 -0
  17. lamindb_setup-1.13.0/tests/hub-local/README.md +7 -0
  18. lamindb_setup-1.13.0/tests/hub-local/conftest.py +200 -0
  19. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-local/test_all.py +2 -159
  20. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_aws_options_manager.py +1 -1
  21. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_upath.py +7 -0
  22. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_storage_settings.py +22 -0
  23. lamindb_setup-1.12.1/tests/hub-local/conftest.py +0 -28
  24. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/.github/workflows/build.yml +0 -0
  25. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/.github/workflows/doc-changes.yml +0 -0
  26. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/.gitignore +0 -0
  27. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/.pre-commit-config.yaml +0 -0
  28. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/LICENSE +0 -0
  29. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/README.md +0 -0
  30. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/changelog.md +0 -0
  31. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  32. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  33. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  34. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  35. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  36. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/06-connect-hosted-instance.ipynb +0 -0
  37. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  38. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/08-test-multi-session.ipynb +0 -0
  39. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/09-test-migrate.ipynb +0 -0
  40. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-cloud/test_notebooks.py +0 -0
  41. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-cache-management.ipynb +0 -0
  42. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
  43. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  44. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-empty-init.ipynb +0 -0
  45. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-import-schema.ipynb +0 -0
  46. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-init-load-local-anonymously.ipynb +0 -0
  47. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  48. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  49. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  50. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/hub-prod/test_notebooks2.py +0 -0
  51. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/index.md +0 -0
  52. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/notebooks.md +0 -0
  53. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/docs/reference.md +0 -0
  54. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_cache.py +0 -0
  55. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_check.py +0 -0
  56. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_check_setup.py +0 -0
  57. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_delete.py +0 -0
  58. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_disconnect.py +0 -0
  59. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_django.py +0 -0
  60. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_entry_points.py +0 -0
  61. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_exportdb.py +0 -0
  62. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_importdb.py +0 -0
  63. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_init_instance.py +0 -0
  64. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_register_instance.py +0 -0
  65. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_schema.py +0 -0
  66. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_set_managed_storage.py +0 -0
  67. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_setup_user.py +0 -0
  68. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/_silence_loggers.py +0 -0
  69. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_aws_storage.py +0 -0
  70. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_deprecated.py +0 -0
  71. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_docs.py +0 -0
  72. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_hub_client.py +0 -0
  73. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_hub_crud.py +0 -0
  74. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_hub_utils.py +0 -0
  75. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_private_django_api.py +0 -0
  76. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_load.py +0 -0
  77. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_save.py +0 -0
  78. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_store.py +0 -0
  79. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_settings_user.py +0 -0
  80. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/_setup_bionty_sources.py +0 -0
  81. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  82. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/exceptions.py +0 -0
  83. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/hashing.py +0 -0
  84. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/core/types.py +0 -0
  85. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/errors.py +0 -0
  86. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/py.typed +0 -0
  87. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/lamindb_setup/types.py +0 -0
  88. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/noxfile.py +0 -0
  89. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/scripts/script-init-pass-user-no-writes.py +0 -0
  90. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/scripts/script-to-fail-managed-storage.py +0 -0
  91. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_connect_instance.py +0 -0
  92. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_delete_instance.py +0 -0
  93. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_edge_request.py +0 -0
  94. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_fail_managed_storage.py +0 -0
  95. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_init_instance.py +0 -0
  96. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_init_pass_user_no_writes.py +0 -0
  97. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_login.py +0 -0
  98. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-cloud/test_set_storage.py +0 -0
  99. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-local/scripts/script-connect-fine-grained-access.py +0 -0
  100. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-local/test_update_schema_in_hub.py +0 -0
  101. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/conftest.py +0 -0
  102. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_django.py +0 -0
  103. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_global_settings.py +0 -0
  104. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_migrate.py +0 -0
  105. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  106. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_entry_point.py +0 -0
  107. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_hashing.py +0 -0
  108. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_storage_access.py +0 -0
  109. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_storage_basis.py +0 -0
  110. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_storage_stats.py +0 -0
  111. {lamindb_setup-1.12.1 → lamindb_setup-1.13.0}/tests/storage/test_to_url.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lamindb_setup
3
- Version: 1.12.1
3
+ Version: 1.13.0
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <open-source@lamin.ai>
6
6
  Requires-Python: >=3.10
@@ -20,9 +20,8 @@ Requires-Dist: storage3!=0.11.2; python_version < '3.11'
20
20
  Requires-Dist: pyjwt<3.0.0
21
21
  Requires-Dist: psutil
22
22
  Requires-Dist: packaging
23
- Requires-Dist: urllib3<2 ; extra == "aws"
24
- Requires-Dist: aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == "aws"
25
- Requires-Dist: s3fs>=2023.12.2,<=2025.7.0,!=2024.10.0 ; extra == "aws"
23
+ Requires-Dist: aiobotocore[boto3]>=2.12.4,<3.0.0 ; extra == "aws"
24
+ Requires-Dist: s3fs>=2023.12.2,<=2025.9.0,!=2024.10.0 ; extra == "aws"
26
25
  Requires-Dist: line_profiler ; extra == "dev"
27
26
  Requires-Dist: psycopg2-binary ; extra == "dev"
28
27
  Requires-Dist: python-dotenv ; extra == "dev"
@@ -33,7 +32,7 @@ Requires-Dist: pytest-xdist ; extra == "dev"
33
32
  Requires-Dist: nbproject-test>=0.4.3 ; extra == "dev"
34
33
  Requires-Dist: pandas ; extra == "dev"
35
34
  Requires-Dist: django-schema-graph ; extra == "erdiagram"
36
- Requires-Dist: gcsfs>=2023.12.2,<=2025.7.0 ; extra == "gcp"
35
+ Requires-Dist: gcsfs>=2023.12.2,<=2025.9.0 ; extra == "gcp"
37
36
  Project-URL: Home, https://github.com/laminlabs/lamindb-setup
38
37
  Provides-Extra: aws
39
38
  Provides-Extra: dev
@@ -35,7 +35,7 @@ Modules & settings:
35
35
 
36
36
  """
37
37
 
38
- __version__ = "1.12.1" # denote a release candidate for 0.1.0 with 0.1rc1
38
+ __version__ = "1.13.0" # denote a release candidate for 0.1.0 with 0.1rc1
39
39
 
40
40
  import os
41
41
  import warnings
@@ -23,7 +23,7 @@ from .core._settings import settings
23
23
  from .core._settings_instance import InstanceSettings
24
24
  from .core._settings_load import load_instance_settings
25
25
  from .core._settings_storage import StorageSettings
26
- from .core._settings_store import instance_settings_file, settings_dir
26
+ from .core._settings_store import instance_settings_file
27
27
  from .core.cloud_sqlite_locker import unlock_cloud_sqlite_upon_exception
28
28
  from .core.django import reset_django
29
29
  from .errors import CannotSwitchDefaultInstance
@@ -98,6 +98,27 @@ class migrate:
98
98
 
99
99
  @classmethod
100
100
  def deploy(cls, package_name: str | None = None, number: int | None = None) -> None:
101
+ import os
102
+
103
+ # NOTE: this is a temporary solution to avoid breaking tests
104
+ LAMIN_MIGRATE_ON_LAMBDA = (
105
+ os.environ.get("LAMIN_MIGRATE_ON_LAMBDA", "false") == "true"
106
+ )
107
+
108
+ if settings.instance.is_on_hub and LAMIN_MIGRATE_ON_LAMBDA:
109
+ response = httpx.post(
110
+ f"{settings.instance.api_url}/instances/{settings.instance._id}/migrate",
111
+ headers={"Authorization": f"Bearer {settings.user.access_token}"},
112
+ )
113
+ if response.status_code != 200:
114
+ raise Exception(f"Failed to migrate instance: {response.text}")
115
+ else:
116
+ cls._deploy(package_name=package_name, number=number)
117
+
118
+ @classmethod
119
+ def _deploy(
120
+ cls, package_name: str | None = None, number: int | None = None
121
+ ) -> None:
101
122
  """Deploy a migration."""
102
123
  from lamindb_setup._connect_instance import connect
103
124
  from lamindb_setup._schema_metadata import update_schema_in_hub
@@ -143,13 +164,6 @@ class migrate:
143
164
  logger.important(f"updating lamindb version in hub: {lamindb.__version__}")
144
165
  if settings.instance.dialect != "sqlite":
145
166
  update_schema_in_hub()
146
- logger.warning(
147
- "clearing instance cache in hub; if this fails, re-run with latest lamindb version"
148
- )
149
- httpx.delete(
150
- f"{settings.instance.api_url}/cache/instances/{settings.instance._id.hex}",
151
- headers={"Authorization": f"Bearer {settings.user.access_token}"},
152
- )
153
167
  call_with_fallback_auth(
154
168
  update_instance,
155
169
  instance_id=settings.instance._id.hex,
@@ -6,16 +6,6 @@ import json
6
6
  from typing import TYPE_CHECKING, Literal
7
7
  from uuid import UUID
8
8
 
9
- from django.db.models import (
10
- Field,
11
- ForeignKey,
12
- ForeignObjectRel,
13
- ManyToManyField,
14
- ManyToManyRel,
15
- ManyToOneRel,
16
- OneToOneField,
17
- OneToOneRel,
18
- )
19
9
  from lamin_utils import logger
20
10
  from pydantic import BaseModel
21
11
 
@@ -34,6 +24,16 @@ except Exception:
34
24
 
35
25
 
36
26
  if TYPE_CHECKING:
27
+ from django.db.models import (
28
+ Field,
29
+ ForeignKey,
30
+ ForeignObjectRel,
31
+ ManyToManyField,
32
+ ManyToManyRel,
33
+ ManyToOneRel,
34
+ OneToOneField,
35
+ OneToOneRel,
36
+ )
37
37
  from supabase import Client
38
38
 
39
39
 
@@ -285,6 +285,9 @@ class _ModelHandler:
285
285
 
286
286
  column = None
287
287
  if relation_type not in ["many-to-many", "one-to-many"]:
288
+ # have to reload it here in case reset happened
289
+ from django.db.models import ForeignObjectRel
290
+
288
291
  if not isinstance(field, ForeignObjectRel):
289
292
  column = field.column
290
293
 
@@ -316,6 +319,8 @@ class _ModelHandler:
316
319
 
317
320
  @staticmethod
318
321
  def _get_through_many_to_many(field_or_rel: ManyToManyField | ManyToManyRel):
322
+ # have to reload it here in case reset happened
323
+ from django.db.models import ManyToManyField, ManyToManyRel
319
324
  from lamindb.models import Registry
320
325
 
321
326
  if isinstance(field_or_rel, ManyToManyField):
@@ -349,6 +354,9 @@ class _ModelHandler:
349
354
  def _get_through(
350
355
  self, field_or_rel: ForeignKey | OneToOneField | ManyToOneRel | OneToOneRel
351
356
  ):
357
+ # have to reload it here in case reset happened
358
+ from django.db.models import ForeignObjectRel
359
+
352
360
  if isinstance(field_or_rel, ForeignObjectRel):
353
361
  rel_1 = field_or_rel.field.related_fields[0][0]
354
362
  rel_2 = field_or_rel.field.related_fields[0][1]
@@ -13,6 +13,7 @@ Settings:
13
13
  """
14
14
 
15
15
  from . import django, upath
16
+ from ._clone import connect_local_sqlite, init_local_sqlite
16
17
  from ._deprecated import deprecated
17
18
  from ._docs import doc_args
18
19
  from ._settings import SetupSettings
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import os
5
5
  import time
6
+ from typing import Any
6
7
 
7
8
  from lamin_utils import logger
8
9
  from upath import UPath
@@ -55,11 +56,17 @@ class AWSOptionsManager:
55
56
  self._credentials_cache = {}
56
57
  self._parameters_cache = {} # this is not refreshed
57
58
 
59
+ from aiobotocore.session import AioSession
58
60
  from s3fs import S3FileSystem
59
61
 
60
62
  # this is cached so will be resued with the connection initialized
63
+ # these options are set for paths in _path_inject_options
64
+ # here we set the same options to cache the filesystem
61
65
  fs = S3FileSystem(
62
- cache_regions=True, use_listings_cache=True, version_aware=False
66
+ cache_regions=True,
67
+ use_listings_cache=True,
68
+ version_aware=False,
69
+ config_kwargs={"max_pool_connections": 64},
63
70
  )
64
71
 
65
72
  self._suppress_aiobotocore_traceback_logging()
@@ -82,6 +89,12 @@ class AWSOptionsManager:
82
89
  except Exception:
83
90
  self.anon_public = True
84
91
 
92
+ empty_session = AioSession(profile="lamindb_empty_profile")
93
+ empty_session.full_config["profiles"]["lamindb_empty_profile"] = {}
94
+ # this is set downstream to avoid using local configs when we provide credentials
95
+ # or when we set anon=True
96
+ self.empty_session = empty_session
97
+
85
98
  def _find_root(self, path_str: str) -> str | None:
86
99
  roots = self._credentials_cache.keys()
87
100
  if path_str in roots:
@@ -109,35 +122,47 @@ class AWSOptionsManager:
109
122
  def _path_inject_options(
110
123
  self, path: UPath, credentials: dict, extra_parameters: dict | None = None
111
124
  ) -> UPath:
125
+ connection_options: dict[str, Any] = {}
126
+ storage_options = path.storage_options
112
127
  if credentials == {}:
113
- # credentials were specified manually for the path
114
- if "anon" in path.storage_options:
115
- anon = path.storage_options["anon"]
116
- elif path.fs.key is not None and path.fs.secret is not None:
117
- anon = False
118
- else:
128
+ # otherwise credentials were specified manually for the path
129
+ if "anon" not in storage_options and (
130
+ path.fs.key is None or path.fs.secret is None
131
+ ):
119
132
  anon = self.anon
120
133
  if not anon and self.anon_public and path.drive in PUBLIC_BUCKETS:
121
134
  anon = True
122
- connection_options = {"anon": anon}
135
+ if anon:
136
+ connection_options["anon"] = anon
137
+ connection_options["session"] = self.empty_session
123
138
  else:
124
- connection_options = credentials
139
+ connection_options.update(credentials)
140
+ connection_options["session"] = self.empty_session
125
141
 
126
- if "cache_regions" in path.storage_options:
127
- connection_options["cache_regions"] = path.storage_options["cache_regions"]
142
+ if "cache_regions" in storage_options:
143
+ connection_options["cache_regions"] = storage_options["cache_regions"]
128
144
  else:
129
145
  connection_options["cache_regions"] = (
130
- path.storage_options.get("endpoint_url", None) is None
146
+ storage_options.get("endpoint_url", None) is None
131
147
  )
132
148
  # we use cache to avoid some uneeded downloads or credential problems
133
149
  # see in upload_from
134
- connection_options["use_listings_cache"] = path.storage_options.get(
150
+ connection_options["use_listings_cache"] = storage_options.get(
135
151
  "use_listings_cache", True
136
152
  )
137
153
  # normally we want to ignore objects vsrsions in a versioned bucket
138
- connection_options["version_aware"] = path.storage_options.get(
154
+ connection_options["version_aware"] = storage_options.get(
139
155
  "version_aware", False
140
156
  )
157
+ # this is for better concurrency as the default batch_size is 128
158
+ # read https://github.com/laminlabs/lamindb-setup/pull/1146
159
+ if "config_kwargs" not in storage_options:
160
+ connection_options["config_kwargs"] = {"max_pool_connections": 64}
161
+ elif "max_pool_connections" not in (
162
+ config_kwargs := storage_options["config_kwargs"]
163
+ ):
164
+ config_kwargs["max_pool_connections"] = 64
165
+ connection_options["config_kwargs"] = config_kwargs
141
166
 
142
167
  if extra_parameters:
143
168
  connection_options.update(extra_parameters)
@@ -152,6 +177,7 @@ class AWSOptionsManager:
152
177
  if "r2.cloudflarestorage.com" in endpoint_url:
153
178
  # fixed_upload_size should always be True for R2
154
179
  # this option is needed for correct uploads to R2
180
+ # TODO: maybe set max_pool_connections=64 here also
155
181
  path = UPath(path, fixed_upload_size=True)
156
182
  return path
157
183
  # trailing slash is needed to avoid returning incorrect results with .startswith
@@ -0,0 +1,93 @@
1
+ """Utilities to copy, clone and load Postgres instances as local SQLite databases.
2
+
3
+ .. autosummary::
4
+ :toctree:
5
+
6
+ init_local_sqlite
7
+ connect_local_sqlite
8
+ """
9
+
10
+ import os
11
+
12
+ from lamindb_setup.core._settings_instance import InstanceSettings
13
+ from lamindb_setup.core._settings_load import load_instance_settings
14
+ from lamindb_setup.core._settings_store import instance_settings_file
15
+ from lamindb_setup.core.django import reset_django
16
+
17
+
18
+ def init_local_sqlite(
19
+ instance: str | None = None, copy_suffix: str | None = None
20
+ ) -> None:
21
+ """Initialize SQLite copy of an existing Postgres instance.
22
+
23
+ Creates a SQLite database with the same schema as the source Postgres instance.
24
+ The copy shares the same storage location as the original instance.
25
+
26
+ The copy is intended for read-only access to instance data without requiring a Postgres connection.
27
+ Data synchronization to complete the clone happens via a separate Lambda function.
28
+
29
+ Note that essential user, branch and storage tables are missing.
30
+ Therefore, it is not possible to store Artifacts without having replayed these records first.
31
+
32
+ Args:
33
+ instance: Pass a slug (`account/name`) or URL (`https://lamin.ai/account/name`).
34
+ If `None`, looks for an environment variable `LAMIN_CURRENT_INSTANCE` to get the instance identifier.
35
+ If it doesn't find this variable, it connects to the instance that was connected with `lamin connect` through the CLI.
36
+ copy_suffix: Optional suffix to append to the local clone name.
37
+ """
38
+ import lamindb_setup as ln_setup
39
+
40
+ if instance is None: # pragma: no cover
41
+ instance = os.environ.get("LAMIN_CURRENT_INSTANCE")
42
+
43
+ if instance is None:
44
+ raise ValueError(
45
+ "No instance identifier provided and LAMIN_CURRENT_INSTANCE is not set"
46
+ )
47
+
48
+ if ln_setup.settings.instance is None: # pragma: no cover
49
+ ln_setup.connect(instance)
50
+
51
+ name = (
52
+ f"{ln_setup.settings.instance.name}{copy_suffix}"
53
+ if copy_suffix is not None
54
+ else ln_setup.settings.instance.name
55
+ )
56
+ isettings = InstanceSettings(
57
+ id=ln_setup.settings.instance._id,
58
+ owner=ln_setup.settings.instance.owner, # type: ignore
59
+ name=name,
60
+ storage=ln_setup.settings.storage,
61
+ db=None,
62
+ modules=",".join(ln_setup.settings.instance.modules),
63
+ is_on_hub=False,
64
+ )
65
+
66
+ isettings._persist(write_to_disk=True)
67
+
68
+ if not isettings._sqlite_file_local.exists():
69
+ # Reset Django configuration before _init_db() because Django was already configured for the original Postgres instance.
70
+ # Without this reset, the if not settings.configured check in setup_django() would skip reconfiguration,
71
+ # causing migrations to run against the old Postgres database instead of the new SQLite clone database.
72
+ reset_django()
73
+ isettings._init_db()
74
+
75
+
76
+ def connect_local_sqlite(instance: str) -> None:
77
+ """Load a SQLite instance of which a remote hub Postgres instance exists.
78
+
79
+ This function bypasses the hub lookup that `lamin connect` performs, loading the SQLite clone directly from local settings files.
80
+ The clone must first be created via `init_local_sqlite()`.
81
+
82
+ Args:
83
+ instance: Instance slug in the form `account/name` (e.g., `laminlabs/privatedata-local`).
84
+ """
85
+ owner, name = instance.split("/")
86
+ settings_file = instance_settings_file(name=name, owner=owner)
87
+
88
+ if not settings_file.exists():
89
+ raise ValueError("SQLite clone not found. Run init_local_sqlite() first.")
90
+
91
+ isettings = load_instance_settings(settings_file)
92
+ isettings._persist(write_to_disk=False)
93
+ isettings._load_db()
@@ -13,6 +13,8 @@ from postgrest.exceptions import APIError
13
13
 
14
14
  from lamindb_setup._migrate import check_whether_migrations_in_sync
15
15
 
16
+ from ._aws_options import HOSTED_REGIONS
17
+ from ._aws_storage import find_closest_aws_region
16
18
  from ._hub_client import (
17
19
  call_with_fallback,
18
20
  call_with_fallback_auth,
@@ -420,6 +422,54 @@ def _init_instance_hub(
420
422
  logger.important(f"go to: https://lamin.ai/{slug}")
421
423
 
422
424
 
425
+ def _get_default_bucket_for_instance(
426
+ instance_id: UUID | None, region: str | None, client: Client
427
+ ):
428
+ if instance_id is not None:
429
+ bucket_base = (
430
+ client.rpc(
431
+ "get_api_server_default_bucket_by_instance_id",
432
+ {"p_instance_id": instance_id.hex},
433
+ )
434
+ .execute()
435
+ .data
436
+ )
437
+ if bucket_base is not None:
438
+ return f"s3://{bucket_base}"
439
+
440
+ if os.getenv("LAMIN_ENV") in {None, "prod"}:
441
+ if region is None:
442
+ region = find_closest_aws_region()
443
+ elif region not in HOSTED_REGIONS:
444
+ raise ValueError(f"region has to be one of {HOSTED_REGIONS}")
445
+ root = f"s3://lamin-{region}"
446
+ else:
447
+ root = "s3://lamin-hosted-test"
448
+
449
+ return root
450
+
451
+
452
+ # pass None if initializing an instance
453
+ # this can be from the api server attached to the instance or the default bucket
454
+ # that we use for instances with no api servers attached
455
+ def get_default_bucket_for_instance(
456
+ instance_id: UUID | None, region: str | None = None, access_token: str | None = None
457
+ ):
458
+ if settings.user.handle != "anonymous" or access_token is not None:
459
+ return call_with_fallback_auth(
460
+ _get_default_bucket_for_instance,
461
+ instance_id=instance_id,
462
+ region=region,
463
+ access_token=access_token,
464
+ )
465
+ else:
466
+ return call_with_fallback(
467
+ _get_default_bucket_for_instance,
468
+ region=region,
469
+ instance_id=instance_id,
470
+ )
471
+
472
+
423
473
  def _connect_instance_hub(
424
474
  owner: str, # account_handle
425
475
  name: str, # instance_name
@@ -321,25 +321,28 @@ class SetupSettings:
321
321
 
322
322
  def __repr__(self) -> str:
323
323
  """Rich string representation."""
324
+ from lamin_utils import colors
325
+
324
326
  # do not show current setting representation when building docs
325
327
  if "sphinx" in sys.modules:
326
328
  return object.__repr__(self)
329
+
327
330
  repr = ""
328
331
  if self._instance_exists:
329
- repr += "Current branch & space:\n"
332
+ instance_rep = self.instance.__repr__().split("\n")
333
+ repr += f"{colors.cyan('Instance:')} {instance_rep[0].replace('Instance: ', '')}\n"
334
+ repr += f" - work-dir: {self.work_dir}\n"
330
335
  repr += f" - branch: {self._read_branch_idlike_name()[1]}\n"
331
- repr += f" - space: {self._read_space_idlike_name()[1]}\n"
332
- repr += self.instance.__repr__()
336
+ repr += f" - space: {self._read_space_idlike_name()[1]}"
337
+ repr += f"\n{colors.yellow('Details:')}\n"
338
+ repr += "\n".join(instance_rep[1:])
333
339
  else:
334
- repr += "Current instance: None"
335
- repr += "\nConfig:\n"
336
- repr += f" - private Django API: {self.private_django_api}\n"
337
- repr += "Local directories:\n"
338
- repr += f" - working directory: {self.work_dir}\n"
340
+ repr += f"{colors.cyan('Instance:')} None"
341
+ repr += f"\n{colors.blue('Cache & settings:')}\n"
339
342
  repr += f" - cache: {self.cache_dir.as_posix()}\n"
340
343
  repr += f" - user settings: {settings_dir.as_posix()}\n"
341
- repr += f" - system settings: {system_settings_dir.as_posix()}\n"
342
- repr += self.user.__repr__()
344
+ repr += f" - system settings: {system_settings_dir.as_posix()}"
345
+ repr += f"\n{colors.green('User:')} {self.user.handle}"
343
346
  return repr
344
347
 
345
348
 
@@ -112,8 +112,8 @@ class InstanceSettings:
112
112
 
113
113
  def __repr__(self):
114
114
  """Rich string representation."""
115
- representation = "Current instance:"
116
- attrs = ["slug", "storage", "db", "modules", "git_repo"]
115
+ representation = f"Instance: {self.slug}"
116
+ attrs = ["storage", "db", "modules", "git_repo"]
117
117
  for attr in attrs:
118
118
  value = getattr(self, attr)
119
119
  if attr == "storage":
@@ -145,8 +145,8 @@ class InstanceSettings:
145
145
  else:
146
146
  db_print = value
147
147
  representation += f"\n - {attr}: {db_print}"
148
- elif attr == "modules":
149
- representation += f"\n - {attr}: {value if value else '{}'}"
148
+ elif attr == "modules" and value:
149
+ representation += f"\n - {attr}: {', '.join(value)}"
150
150
  else:
151
151
  representation += f"\n - {attr}: {value}"
152
152
  return representation
@@ -342,7 +342,12 @@ class InstanceSettings:
342
342
 
343
343
  Provide the full git repo URL.
344
344
  """
345
- return self._git_repo
345
+ if self._git_repo is not None:
346
+ return self._git_repo
347
+ elif os.environ.get("LAMINDB_SYNC_GIT_REPO") is not None:
348
+ return sanitize_git_repo_url(os.environ["LAMINDB_SYNC_GIT_REPO"])
349
+ else:
350
+ return None
346
351
 
347
352
  @property
348
353
  def api_url(self) -> str | None:
@@ -101,7 +101,11 @@ def init_storage(
101
101
  StorageSettings,
102
102
  Literal["hub-record-not-created", "hub-record-retrieved", "hub-record-created"],
103
103
  ]:
104
- from ._hub_core import delete_storage_record, init_storage_hub
104
+ from ._hub_core import (
105
+ delete_storage_record,
106
+ get_default_bucket_for_instance,
107
+ init_storage_hub,
108
+ )
105
109
 
106
110
  assert root is not None, "`root` argument can't be `None`"
107
111
 
@@ -117,20 +121,14 @@ def init_storage(
117
121
  # this means we constructed a hosted location of shape s3://bucket-name/uid
118
122
  # within LaminHub
119
123
  assert root_str.endswith(uid)
120
- lamin_env = os.getenv("LAMIN_ENV")
121
124
  if root_str.startswith("create-s3"):
122
125
  if root_str != "create-s3":
123
126
  assert "--" in root_str, "example: `create-s3--eu-central-1`"
124
127
  region = root_str.replace("create-s3--", "")
125
- if region is None:
126
- region = find_closest_aws_region()
127
- else:
128
- if region not in HOSTED_REGIONS:
129
- raise ValueError(f"region has to be one of {HOSTED_REGIONS}")
130
- if lamin_env is None or lamin_env == "prod":
131
- root = f"s3://lamin-{region}/{uid}"
132
- else:
133
- root = f"s3://lamin-hosted-test/{uid}"
128
+ bucket = get_default_bucket_for_instance(
129
+ None if init_instance else instance_id, region
130
+ )
131
+ root = f"{bucket}/{uid}"
134
132
  elif (input_protocol := fsspec.utils.get_protocol(root_str)) not in VALID_PROTOCOLS:
135
133
  valid_protocols = ("local",) + VALID_PROTOCOLS[1:] # show local instead of file
136
134
  raise ValueError(