lamindb_setup 0.72.1__tar.gz → 0.73.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 (90) hide show
  1. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/.github/workflows/build.yml +14 -14
  2. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/.pre-commit-config.yaml +1 -1
  3. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/PKG-INFO +1 -1
  4. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/changelog.md +7 -0
  5. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/06-connect-hosted-instance.ipynb +29 -4
  6. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/__init__.py +1 -1
  7. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_init_instance.py +1 -0
  8. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_aws_credentials.py +9 -2
  9. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_instance.py +9 -1
  10. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_storage.py +24 -21
  11. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_setup_bionty_sources.py +1 -0
  12. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/upath.py +74 -36
  13. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/noxfile.py +1 -1
  14. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/storage/test_storage_stats.py +11 -3
  15. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/.github/workflows/latest-changes.jinja2 +0 -0
  16. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/.github/workflows/latest-changes.yml +0 -0
  17. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/.gitignore +0 -0
  18. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/LICENSE +0 -0
  19. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/README.md +0 -0
  20. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/01-init-local-instance.ipynb +0 -0
  21. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/02-connect-local-instance.ipynb +0 -0
  22. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/03-add-managed-storage.ipynb +0 -0
  23. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/04-test-bionty.ipynb +0 -0
  24. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/05-init-hosted-instance.ipynb +0 -0
  25. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/07-keep-artifacts-local.ipynb +0 -0
  26. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/test-multi-session.ipynb +0 -0
  27. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-cloud/test_notebooks.py +0 -0
  28. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-cache-management.ipynb +0 -0
  29. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-cloud-sync.ipynb +0 -0
  30. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-connect-anonymously.ipynb +0 -0
  31. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-empty-init.ipynb +0 -0
  32. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-import-schema.ipynb +0 -0
  33. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-insufficient-user-info.ipynb +0 -0
  34. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-invalid-schema.ipynb +0 -0
  35. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test-sqlite-lock.ipynb +0 -0
  36. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/hub-prod/test_notebooks2.py +0 -0
  37. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/index.md +0 -0
  38. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/notebooks.md +0 -0
  39. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/docs/reference.md +0 -0
  40. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_cache.py +0 -0
  41. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_check.py +0 -0
  42. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_check_setup.py +0 -0
  43. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_close.py +0 -0
  44. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_connect_instance.py +0 -0
  45. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_delete.py +0 -0
  46. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_django.py +0 -0
  47. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_exportdb.py +0 -0
  48. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_importdb.py +0 -0
  49. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_migrate.py +0 -0
  50. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_register_instance.py +0 -0
  51. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_schema.py +0 -0
  52. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_set_managed_storage.py +0 -0
  53. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_setup_user.py +0 -0
  54. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/_silence_loggers.py +0 -0
  55. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/__init__.py +0 -0
  56. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_aws_storage.py +0 -0
  57. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_deprecated.py +0 -0
  58. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_docs.py +0 -0
  59. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_hub_client.py +0 -0
  60. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_hub_core.py +0 -0
  61. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_hub_crud.py +0 -0
  62. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_hub_utils.py +0 -0
  63. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings.py +0 -0
  64. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_load.py +0 -0
  65. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_save.py +0 -0
  66. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_store.py +0 -0
  67. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/_settings_user.py +0 -0
  68. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/cloud_sqlite_locker.py +0 -0
  69. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/django.py +0 -0
  70. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/exceptions.py +0 -0
  71. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/hashing.py +0 -0
  72. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/lamindb_setup/core/types.py +0 -0
  73. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/pyproject.toml +0 -0
  74. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_connect_instance.py +0 -0
  75. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_delete_instance.py +0 -0
  76. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_init_instance.py +0 -0
  77. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_login.py +0 -0
  78. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_migrate.py +0 -0
  79. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-cloud/test_set_storage.py +0 -0
  80. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-local/conftest.py +0 -0
  81. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-local/test_all.py +0 -0
  82. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-prod/conftest.py +0 -0
  83. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-prod/test_auto_connect.py +0 -0
  84. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-prod/test_django.py +0 -0
  85. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-prod/test_switch_and_fallback_env.py +0 -0
  86. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/hub-prod/test_upath.py +0 -0
  87. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/storage/test_hashing.py +0 -0
  88. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/storage/test_storage_access.py +0 -0
  89. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/storage/test_storage_basis.py +0 -0
  90. {lamindb_setup-0.72.1 → lamindb_setup-0.73.0}/tests/storage/test_to_url.py +0 -0
@@ -12,14 +12,13 @@ jobs:
12
12
  # tests only on production hub
13
13
  hub-prod:
14
14
  runs-on: ubuntu-latest
15
- timeout-minutes: 10
15
+ timeout-minutes: 6
16
16
  steps:
17
17
  - uses: actions/checkout@v3
18
18
  - uses: actions/setup-python@v4
19
19
  with:
20
- python-version: "3.9" # consciously run one job on Python 3.9
20
+ python-version: "3.9" # run one job on 3.9
21
21
  cache: "pip"
22
- cache-dependency-path: ".github/workflows/build.yml"
23
22
  - uses: aws-actions/configure-aws-credentials@v2
24
23
  with:
25
24
  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -39,10 +38,12 @@ jobs:
39
38
  strategy:
40
39
  fail-fast: false
41
40
  matrix:
42
- lamin_env:
43
- - "staging"
44
- - "prod"
45
- timeout-minutes: 15
41
+ include:
42
+ - lamin_env: "prod"
43
+ python-version: "3.11"
44
+ - lamin_env: "staging"
45
+ python-version: "3.10" # test on 3.10
46
+ timeout-minutes: 6
46
47
  steps:
47
48
  - uses: aws-actions/configure-aws-credentials@v2
48
49
  with:
@@ -52,7 +53,7 @@ jobs:
52
53
  - uses: actions/checkout@v4
53
54
  - uses: actions/setup-python@v4
54
55
  with:
55
- python-version: "3.11" # consciously run one job on Python 3.11
56
+ python-version: ${{ matrix.python-version }}
56
57
  cache: "pip"
57
58
  cache-dependency-path: ".github/workflows/build.yml"
58
59
  - name: checkout laminhub
@@ -92,12 +93,12 @@ jobs:
92
93
  # test user access to storage
93
94
  storage:
94
95
  runs-on: ubuntu-latest
95
- timeout-minutes: 10
96
+ timeout-minutes: 6
96
97
  steps:
97
98
  - uses: actions/checkout@v4
98
99
  - uses: actions/setup-python@v4
99
100
  with:
100
- python-version: "3.10" # consciously run one job on Python 3.10
101
+ python-version: "3.11" # we need to run everything for coverage on 3.11
101
102
  cache: "pip"
102
103
  cache-dependency-path: ".github/workflows/build.yml"
103
104
  - run: pip install -U laminci
@@ -116,7 +117,7 @@ jobs:
116
117
  # test low-level hub functionality
117
118
  hub-local:
118
119
  runs-on: ubuntu-latest
119
- timeout-minutes: 10
120
+ timeout-minutes: 6
120
121
  steps:
121
122
  - uses: aws-actions/configure-aws-credentials@v2
122
123
  with:
@@ -139,7 +140,7 @@ jobs:
139
140
  working-directory: laminhub/rest-hub/supabase
140
141
  - uses: actions/setup-python@v4
141
142
  with:
142
- python-version: "3.11" # consciously run one job on Python 3.11
143
+ python-version: "3.11" # we need to run everything for coverage on 3.11
143
144
  cache: "pip"
144
145
  cache-dependency-path: ".github/workflows/build.yml"
145
146
  - run: pip install -U laminci
@@ -165,9 +166,8 @@ jobs:
165
166
  - uses: actions/checkout@v4
166
167
  - uses: actions/setup-python@v4
167
168
  with:
168
- python-version: "3.10"
169
+ python-version: "3.11"
169
170
  cache: "pip"
170
- cache-dependency-path: ".github/workflows/build.yml"
171
171
  - run: |
172
172
  pip install coverage[toml]
173
173
  pip install --no-deps .
@@ -49,4 +49,4 @@ repos:
49
49
  hooks:
50
50
  - id: pydocstyle
51
51
  args: # google style + __init__, see http://www.pydocstyle.org/en/stable/error_codes.html
52
- - --ignore=D100,D101,D102,D103,D104,D106,D107,D203,D204,D213,D215,D400,D401,D403,D404,D406,D407,D408,D409,D412,D413
52
+ - --ignore=D100,D101,D102,D103,D104,D106,D107,D203,D204,D213,D215,D400,D401,D403,D404,D406,D407,D408,D409,D412,D413,D417
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lamindb_setup
3
- Version: 0.72.1
3
+ Version: 0.73.0
4
4
  Summary: Setup & configure LaminDB.
5
5
  Author-email: Lamin Labs <laminlabs@gmail.com>
6
6
  Description-Content-Type: text/markdown
@@ -3,7 +3,14 @@
3
3
  <!-- prettier-ignore -->
4
4
  Name | PR | Developer | Date | Version
5
5
  --- | --- | --- | --- | ---
6
+ 🐛 Fix trailing slash in upload_from source | [773](https://github.com/laminlabs/lamindb-setup/pull/773) | [Koncopd](https://github.com/Koncopd) | 2024-05-23 | 0.73.0
7
+ 🚸 Make `upload_from()`, `download_to()`, and `view_tree()` more user friendly | [772](https://github.com/laminlabs/lamindb-setup/pull/772) | [falexwolf](https://github.com/falexwolf) | 2024-05-23 |
8
+ ✨ Resolve s3 bucket region even without access rights | [771](https://github.com/laminlabs/lamindb-setup/pull/771) | [Koncopd](https://github.com/Koncopd) | 2024-05-22 |
9
+ 💚 Coverage fix | [770](https://github.com/laminlabs/lamindb-setup/pull/770) | [falexwolf](https://github.com/falexwolf) | 2024-05-22 |
10
+ 👷 Fix coverage compute | [769](https://github.com/laminlabs/lamindb-setup/pull/769) | [falexwolf](https://github.com/falexwolf) | 2024-05-22 |
11
+ 🐛 Deal with migration errors when keep-artifacts-local is true | [767](https://github.com/laminlabs/lamindb-setup/pull/767) | [falexwolf](https://github.com/falexwolf) | 2024-05-20 | 0.72.2
6
12
  ♻️ Do not error if empty dict in `access_aws` | [764](https://github.com/laminlabs/lamindb-setup/pull/764) | [falexwolf](https://github.com/falexwolf) | 2024-05-19 | 0.72.1
13
+ 🐛 Keep training slash in aws cache keys | [766](https://github.com/laminlabs/lamindb-setup/pull/766) | [Koncopd](https://github.com/Koncopd) | 2024-05-19 |
7
14
  🐛 Check empty after storage record root init in delete | [763](https://github.com/laminlabs/lamindb-setup/pull/763) | [Koncopd](https://github.com/Koncopd) | 2024-05-18 |
8
15
  ✨ Call `access_aws` for all paths and cache | [762](https://github.com/laminlabs/lamindb-setup/pull/762) | [Koncopd](https://github.com/Koncopd) | 2024-05-18 | 0.72.0
9
16
  ⚡️ Speed-up file hash | [761](https://github.com/laminlabs/lamindb-setup/pull/761) | [Koncopd](https://github.com/Koncopd) | 2024-05-11 | 0.71.4
@@ -86,10 +86,11 @@
86
86
  "outputs": [],
87
87
  "source": [
88
88
  "target_dir = root / \"test-dir-upload\"\n",
89
- "target_dir.upload_from(test_dir, recursive=True)\n",
89
+ "target_dir.upload_from(test_dir)\n",
90
90
  "\n",
91
91
  "assert target_dir.is_dir()\n",
92
92
  "assert (target_dir / \"test-dir-upload\").exists()\n",
93
+ "assert (target_dir / \"test-dir-upload/file1\").exists()\n",
93
94
  "\n",
94
95
  "target_dir.rmdir()\n",
95
96
  "assert not target_dir.exists()"
@@ -101,10 +102,34 @@
101
102
  "metadata": {},
102
103
  "outputs": [],
103
104
  "source": [
104
- "target_dir.upload_from(test_dir, dir_inplace=True)\n",
105
+ "# test trailing slash in target_dir\n",
106
+ "target_dir = root / \"test-dir-upload/\"\n",
107
+ "assert target_dir.path.endswith(\"/\")\n",
105
108
  "\n",
106
- "assert target_dir.is_dir()\n",
107
- "assert not (target_dir / \"test-dir-upload\").exists()"
109
+ "dest_dir = target_dir.upload_from(test_dir, create_folder=True)\n",
110
+ "\n",
111
+ "assert \"//\" not in dest_dir.path\n",
112
+ "assert dest_dir.as_posix() == (root / \"test-dir-upload/test-dir-upload\").as_posix()\n",
113
+ "assert dest_dir.is_dir()\n",
114
+ "\n",
115
+ "dest_dir.rmdir()\n",
116
+ "assert not dest_dir.exists()"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "code",
121
+ "execution_count": null,
122
+ "metadata": {},
123
+ "outputs": [],
124
+ "source": [
125
+ "target_dir_check = root / \"test-dir-upload\"\n",
126
+ "assert not target_dir_check.exists()\n",
127
+ "\n",
128
+ "target_dir.upload_from(test_dir, create_folder=False)\n",
129
+ "\n",
130
+ "assert target_dir_check.is_dir()\n",
131
+ "assert (target_dir_check / \"file1\").exists()\n",
132
+ "assert not (target_dir_check / \"test-dir-upload\").exists()"
108
133
  ]
109
134
  },
110
135
  {
@@ -34,7 +34,7 @@ Modules & settings:
34
34
 
35
35
  """
36
36
 
37
- __version__ = "0.72.1" # denote a release candidate for 0.1.0 with 0.1rc1
37
+ __version__ = "0.73.0" # 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
@@ -56,6 +56,7 @@ def register_storage_in_instance(ssettings: StorageSettings):
56
56
  "region": ssettings.region,
57
57
  "instance_uid": instance_uid,
58
58
  "created_by_id": current_user_id(),
59
+ "run": None,
59
60
  }
60
61
  if ssettings._uid is not None:
61
62
  defaults["uid"] = ssettings._uid
@@ -22,6 +22,10 @@ else:
22
22
  HOSTED_BUCKETS = ("s3://lamin-hosted-test",) # type: ignore
23
23
 
24
24
 
25
+ def _keep_trailing_slash(path_str: str):
26
+ return path_str if path_str[-1] == "/" else path_str + "/"
27
+
28
+
25
29
  AWS_CREDENTIALS_EXPIRATION = 11 * 60 * 60 # refresh credentials after 11 hours
26
30
 
27
31
 
@@ -81,7 +85,10 @@ class AWSCredentialsManager:
81
85
  return S3Path(path, cache_regions=cache_regions, **connection_options)
82
86
 
83
87
  def enrich_path(self, path: S3Path, access_token: str | None = None) -> S3Path:
84
- path_str = path.as_posix().rstrip("/")
88
+ # trailing slash is needed to avoid returning incorrect results
89
+ # with .startswith
90
+ # for example s3://lamindata-eu should not receive cache for s3://lamindata
91
+ path_str = _keep_trailing_slash(path.as_posix())
85
92
  root = self._find_root(path_str)
86
93
 
87
94
  if root is not None:
@@ -127,7 +134,7 @@ class AWSCredentialsManager:
127
134
  # write the bucket for everything else
128
135
  root = path._url.netloc
129
136
  root = "s3://" + root
130
- self._set_cached_credentials(root, credentials)
137
+ self._set_cached_credentials(_keep_trailing_slash(root), credentials)
131
138
 
132
139
  return self._path_inject_options(path, credentials)
133
140
 
@@ -5,6 +5,7 @@ import shutil
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING, Literal
7
7
 
8
+ from django.db.utils import ProgrammingError
8
9
  from lamin_utils import logger
9
10
 
10
11
  from ._hub_client import call_with_fallback
@@ -106,8 +107,15 @@ class InstanceSettings:
106
107
  local_records = Storage.objects.filter(root=local_root)
107
108
  else:
108
109
  local_records = Storage.objects.filter(type="local")
110
+ all_local_records = local_records.all()
111
+ try:
112
+ # trigger an error in case of a migration issue
113
+ all_local_records.first()
114
+ except ProgrammingError:
115
+ logger.error("not able to load Storage registry: please migrate")
116
+ return None
109
117
  found = False
110
- for record in local_records.all():
118
+ for record in all_local_records:
111
119
  root_path = Path(record.root)
112
120
  if root_path.exists():
113
121
  marker_path = root_path / ".lamindb/_is_initialized"
@@ -37,33 +37,36 @@ def base62(n_char: int) -> str:
37
37
  return id
38
38
 
39
39
 
40
- def get_storage_region(storage_root: UPathStr) -> str | None:
41
- storage_root_str = str(storage_root)
42
- if storage_root_str.startswith("s3://"):
43
- import botocore.session as session
40
+ def get_storage_region(path: UPathStr) -> str | None:
41
+ path_str = str(path)
42
+ if path_str.startswith("s3://"):
43
+ import botocore.session
44
44
  from botocore.config import Config
45
- from botocore.exceptions import NoCredentialsError
45
+ from botocore.exceptions import ClientError
46
46
 
47
47
  # strip the prefix and any suffixes of the bucket name
48
- bucket = storage_root_str.replace("s3://", "").split("/")[0]
49
- s3_session = session.get_session()
50
- s3_client = s3_session.create_client("s3")
48
+ bucket = path_str.replace("s3://", "").split("/")[0]
49
+ session = botocore.session.get_session()
50
+ credentials = session.get_credentials()
51
+ if credentials is None or credentials.access_key is None:
52
+ config = Config(signature_version=botocore.session.UNSIGNED)
53
+ else:
54
+ config = None
55
+ s3_client = session.create_client("s3", config=config)
51
56
  try:
52
57
  response = s3_client.head_bucket(Bucket=bucket)
53
- except NoCredentialsError: # deal with anonymous access
54
- s3_client = s3_session.create_client(
55
- "s3", config=Config(signature_version=session.UNSIGNED)
56
- )
57
- response = s3_client.head_bucket(Bucket=bucket)
58
- storage_region = response["ResponseMetadata"].get("HTTPHeaders", {})[
59
- "x-amz-bucket-region"
60
- ]
61
- # if we want to except botcore.exceptions.ClientError to reformat an
62
- # error message, this is how to do test for the "NoSuchBucket" error:
63
- # exc.response["Error"]["Code"] == "NoSuchBucket"
58
+ except ClientError as exc:
59
+ response = getattr(exc, "response", {})
60
+ if response.get("Error", {}).get("Code") == "404":
61
+ raise exc
62
+ region = (
63
+ response.get("ResponseMetadata", {})
64
+ .get("HTTPHeaders", {})
65
+ .get("x-amz-bucket-region")
66
+ )
64
67
  else:
65
- storage_region = None
66
- return storage_region
68
+ region = None
69
+ return region
67
70
 
68
71
 
69
72
  def mark_storage_root(root: UPathStr, uid: str):
@@ -53,6 +53,7 @@ def write_bionty_sources(isettings: InstanceSettings) -> None:
53
53
  kwargs["species"] = kwargs.pop("organism")
54
54
  elif hasattr(PublicSource, "organism") and "species" in kwargs:
55
55
  kwargs["organism"] = kwargs.pop("species")
56
+ kwargs["run"] = None # can't yet access tracking information
56
57
  record = PublicSource(**kwargs)
57
58
  all_records.append(record)
58
59
 
@@ -155,7 +155,10 @@ def create_mapper(
155
155
 
156
156
 
157
157
  def print_hook(size: int, value: int, objectname: str, action: str):
158
- progress_in_percent = (value / size) * 100
158
+ if size == 0:
159
+ progress_in_percent = 100.0
160
+ else:
161
+ progress_in_percent = (value / size) * 100
159
162
  out = f"... {action} {objectname}:" f" {min(progress_in_percent, 100):4.1f}%"
160
163
  if "NBPRJ_TEST_NBPATH" not in os.environ:
161
164
  end = "\n" if progress_in_percent >= 100 else "\r"
@@ -238,50 +241,75 @@ class ChildProgressCallback(fsspec.callbacks.Callback):
238
241
  self.parent.update_relative_value(inc)
239
242
 
240
243
  def relative_update(self, inc=1):
241
- self.parent_update(inc / self.size)
244
+ if self.size != 0:
245
+ self.parent_update(inc / self.size)
246
+ else:
247
+ self.parent_update(1)
242
248
 
243
249
 
244
- def download_to(self, path: UPathStr, print_progress: bool = False, **kwargs):
245
- """Download to a path."""
250
+ def download_to(self, local_path: UPathStr, print_progress: bool = True, **kwargs):
251
+ """Download from self (a destination in the cloud) to the local path."""
252
+ if "recursive" not in kwargs:
253
+ kwargs["recursive"] = True
246
254
  if print_progress and "callback" not in kwargs:
247
255
  callback = ProgressCallback(
248
- PurePosixPath(path).name, "downloading", adjust_size=True
256
+ PurePosixPath(local_path).name, "downloading", adjust_size=True
249
257
  )
250
258
  kwargs["callback"] = callback
251
259
 
252
- self.fs.download(str(self), str(path), **kwargs)
260
+ self.fs.download(str(self), str(local_path), **kwargs)
253
261
 
254
262
 
255
263
  def upload_from(
256
264
  self,
257
- path: UPathStr,
258
- dir_inplace: bool = False,
259
- print_progress: bool = False,
265
+ local_path: UPathStr,
266
+ create_folder: bool | None = None,
267
+ print_progress: bool = True,
260
268
  **kwargs,
261
- ):
262
- """Upload from a local path."""
263
- path = Path(path)
264
- path_is_dir = path.is_dir()
265
- if not path_is_dir:
266
- dir_inplace = False
269
+ ) -> UPath:
270
+ """Upload from the local path to `self` (a destination in the cloud).
271
+
272
+ If the local path is a directory, recursively upload its contents.
273
+
274
+ Args:
275
+ local_path: A local path of a file or directory.
276
+ create_folder: Only applies if `local_path` is a directory and then
277
+ defaults to `True`. If `True`, make a new folder in the destination
278
+ using the directory name of `local_path`. If `False`, upload the
279
+ contents of the directory to to the root-level of the destination.
280
+ print_progress: Print progress.
281
+
282
+ Returns:
283
+ The destination path.
284
+ """
285
+ local_path = Path(local_path)
286
+ local_path_is_dir = local_path.is_dir()
287
+ if create_folder is None:
288
+ create_folder = local_path_is_dir
289
+ if create_folder and not local_path_is_dir:
290
+ raise ValueError("create_folder can only be True if local_path is a directory")
267
291
 
268
292
  if print_progress and "callback" not in kwargs:
269
- callback = ProgressCallback(path.name, "uploading")
293
+ callback = ProgressCallback(local_path.name, "uploading")
270
294
  kwargs["callback"] = callback
271
295
 
272
- if dir_inplace:
273
- source = [f for f in path.rglob("*") if f.is_file()]
274
- destination = [str(self / f.relative_to(path)) for f in source]
296
+ if local_path_is_dir and not create_folder:
297
+ source = [f for f in local_path.rglob("*") if f.is_file()]
298
+ # convert_pathlike is needed to remove the trailing slash because
299
+ # UPath("s3://some-bucket/some-folder/") / "some-key"
300
+ # results in UPath("s3://some-bucket/some-folder//some-key")
301
+ # for upath 0.1.4
302
+ dest_root = convert_pathlike(self) if self._parts[-1] == "" else self
303
+ destination = [str(dest_root / f.relative_to(local_path)) for f in source]
275
304
  source = [str(f) for f in source] # type: ignore
276
305
  else:
277
- source = str(path) # type: ignore
306
+ source = str(local_path) # type: ignore
278
307
  destination = str(self) # type: ignore
279
- # this weird thing is to avoid s3fs triggering create_bucket in upload
280
- # if dirs are present
281
- # it allows to avoid permission error
282
- if self.protocol != "s3" or not path_is_dir or dir_inplace:
283
- cleanup_cache = False
284
- else:
308
+
309
+ # the below lines are to avoid s3fs triggering create_bucket in upload if
310
+ # dirs are present it allows to avoid permission error
311
+ # would be easier to just
312
+ if self.protocol == "s3" and local_path_is_dir and create_folder:
285
313
  bucket = self._url.netloc
286
314
  if bucket not in self.fs.dircache:
287
315
  self.fs.dircache[bucket] = [{}]
@@ -290,14 +318,23 @@ def upload_from(
290
318
  cleanup_cache = True
291
319
  else:
292
320
  cleanup_cache = False
321
+ else:
322
+ cleanup_cache = False
293
323
 
294
- self.fs.upload(source, destination, **kwargs)
324
+ self.fs.upload(source, destination, recursive=create_folder, **kwargs)
295
325
 
296
326
  if cleanup_cache:
297
327
  # normally this is invalidated after the upload but still better to check
298
328
  if bucket in self.fs.dircache:
299
329
  del self.fs.dircache[bucket]
300
330
 
331
+ if local_path_is_dir and create_folder:
332
+ # convert_pathlike is needed to remove the trailing slash
333
+ dest_root = convert_pathlike(self) if self._parts[-1] == "" else self
334
+ return dest_root / local_path.name
335
+ else:
336
+ return self
337
+
301
338
 
302
339
  def synchronize(
303
340
  self,
@@ -305,14 +342,14 @@ def synchronize(
305
342
  error_no_origin: bool = True,
306
343
  print_progress: bool = False,
307
344
  callback: fsspec.callbacks.Callback | None = None,
308
- **kwargs,
345
+ timestamp: float | None = None,
309
346
  ):
310
347
  """Sync to a local destination path."""
311
348
  # optimize the number of network requests
312
- if "timestamp" in kwargs:
349
+ if timestamp is not None:
313
350
  is_dir = False
314
351
  exists = True
315
- cloud_mts = kwargs.pop("timestamp")
352
+ cloud_mts = timestamp
316
353
  else:
317
354
  # perform only one network request to check existence, type and timestamp
318
355
  try:
@@ -379,7 +416,7 @@ def synchronize(
379
416
  destination = objectpath / file_key
380
417
  child = callback.branched(origin, destination.as_posix())
381
418
  UPath(origin, **self._kwargs).synchronize(
382
- destination, timestamp=timestamp, callback=child, **kwargs
419
+ destination, callback=child, timestamp=timestamp
383
420
  )
384
421
  child.close()
385
422
  if destination_exists:
@@ -400,15 +437,16 @@ def synchronize(
400
437
  callback = ProgressCallback.requires_progress(
401
438
  callback, print_progress, objectpath.name, "synchronizing"
402
439
  )
403
- kwargs["callback"] = callback
404
440
  if objectpath.exists():
405
- local_mts = objectpath.stat().st_mtime # type: ignore
406
- need_synchronize = cloud_mts > local_mts
441
+ local_mts_obj = objectpath.stat().st_mtime # type: ignore
442
+ need_synchronize = cloud_mts > local_mts_obj
407
443
  else:
408
444
  objectpath.parent.mkdir(parents=True, exist_ok=True)
409
445
  need_synchronize = True
410
446
  if need_synchronize:
411
- self.download_to(objectpath, **kwargs)
447
+ self.download_to(
448
+ objectpath, recursive=False, print_progress=False, callback=callback
449
+ )
412
450
  os.utime(objectpath, times=(cloud_mts, cloud_mts))
413
451
  else:
414
452
  # nothing happens if parent_update is not defined
@@ -477,7 +515,7 @@ def compute_file_tree(
477
515
  if child_path.is_dir():
478
516
  if include_dirs and child_path not in include_dirs:
479
517
  continue
480
- yield prefix + pointer + child_path.name
518
+ yield prefix + pointer + child_path.name + "/"
481
519
  n_directories += 1
482
520
  n_files_per_dir_and_type = defaultdict(lambda: 0)
483
521
  extension = branch if pointer == tee else space
@@ -47,7 +47,7 @@ uv pip install --system --no-deps {no_deps_packages}
47
47
  elif group == "hub-local":
48
48
  cmds = """uv pip install --system -e ./laminhub/rest-hub"""
49
49
  # current package
50
- cmds += """\nuv pip install --system .[aws,dev] lamin-cli"""
50
+ cmds += """\nuv pip install --system -e '.[aws,dev]' lamin-cli"""
51
51
 
52
52
  [run(session, line) for line in cmds.splitlines()]
53
53
 
@@ -1,17 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from lamindb_setup.core._aws_credentials import HOSTED_REGIONS
4
+ from lamindb_setup.core._settings_storage import get_storage_region
3
5
  from lamindb_setup.core.upath import UPath, compute_file_tree, get_stat_dir_cloud
4
6
 
5
7
 
6
8
  def test_get_stat_dir_cloud_aws():
7
- string_path = "s3://lamindb-dev-datasets/iris_studies/study0_raw_images"
9
+ string_path = "s3://lamindata/iris_studies/study0_raw_images"
8
10
  path = UPath(string_path, anon=True)
9
11
  _, n_objects_file_tree = compute_file_tree(path)
10
12
  size, hash, hash_type, n_objects = get_stat_dir_cloud(path)
11
13
  assert n_objects == n_objects_file_tree
12
- assert hash == "wVYKPpEsmmrqSpAZIRXCFg"
14
+ assert hash == "IVKGMfNwi8zKvnpaD_gG7w"
13
15
  assert hash_type == "md5-d"
14
- assert size == 656692
16
+ assert size == 658465
17
+ assert n_objects == 51
15
18
 
16
19
 
17
20
  def test_get_stat_dir_cloud_gcp():
@@ -22,3 +25,8 @@ def test_get_stat_dir_cloud_gcp():
22
25
  assert hash == "6r5Hkce0UTy7X6gLeaqzBA"
23
26
  assert hash_type == "md5-d"
24
27
  assert size == 994441606
28
+
29
+
30
+ def test_get_storage_region():
31
+ for region in HOSTED_REGIONS:
32
+ assert get_storage_region(f"s3://lamin-{region}") == region
File without changes
File without changes