arraylake 0.32.0__tar.gz → 1.0.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 (65) hide show
  1. {arraylake-0.32.0 → arraylake-1.0.0}/.gitignore +1 -0
  2. {arraylake-0.32.0 → arraylake-1.0.0}/PKG-INFO +6 -8
  3. arraylake-1.0.0/arraylake/_version.py +24 -0
  4. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/api_utils.py +8 -0
  5. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/compute.py +2 -2
  6. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/config.py +2 -2
  7. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/repo.py +6 -8
  8. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/client.py +79 -59
  9. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/compute/types.py +4 -4
  10. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/metastore/http_metastore.py +47 -7
  11. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/repos/icechunk/types.py +23 -3
  12. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/types.py +135 -37
  13. {arraylake-0.32.0 → arraylake-1.0.0}/pyproject.toml +7 -9
  14. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_api_utils.py +30 -3
  15. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_cli.py +1 -1
  16. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_client.py +47 -11
  17. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_subscriptions.py +11 -11
  18. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_virtual_chunks.py +119 -103
  19. {arraylake-0.32.0 → arraylake-1.0.0}/uv.lock +456 -767
  20. arraylake-0.32.0/arraylake/_version.py +0 -34
  21. {arraylake-0.32.0 → arraylake-1.0.0}/README.md +0 -0
  22. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/__init__.py +0 -0
  23. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/__main__.py +0 -0
  24. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/asyn.py +0 -0
  25. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/__init__.py +0 -0
  26. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/auth.py +0 -0
  27. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/main.py +0 -0
  28. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/cli/utils.py +0 -0
  29. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/compute/doctor.py +0 -0
  30. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/compute/http_client.py +0 -0
  31. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/compute/services.py +0 -0
  32. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/config.py +0 -0
  33. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/config.yaml +0 -0
  34. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/credentials.py +0 -0
  35. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/diagnostics.py +0 -0
  36. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/display/__init__.py +0 -0
  37. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/display/repo.py +0 -0
  38. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/display/repolist.py +0 -0
  39. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/exceptions.py +0 -0
  40. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/log_util.py +0 -0
  41. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/metastore/__init__.py +0 -0
  42. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/metastore/abc.py +0 -0
  43. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/py.typed +0 -0
  44. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/repos/__init__.py +0 -0
  45. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/repos/icechunk/__init__.py +0 -0
  46. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/repos/icechunk/storage.py +0 -0
  47. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/repos/icechunk/virtual.py +0 -0
  48. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/token.py +0 -0
  49. {arraylake-0.32.0 → arraylake-1.0.0}/arraylake/tuning.py +0 -0
  50. {arraylake-0.32.0 → arraylake-1.0.0}/tests/__init__.py +0 -0
  51. {arraylake-0.32.0 → arraylake-1.0.0}/tests/compute/test_client.py +0 -0
  52. {arraylake-0.32.0 → arraylake-1.0.0}/tests/compute/test_doctor.py +0 -0
  53. {arraylake-0.32.0 → arraylake-1.0.0}/tests/config.yaml +0 -0
  54. {arraylake-0.32.0 → arraylake-1.0.0}/tests/conftest.py +0 -0
  55. {arraylake-0.32.0 → arraylake-1.0.0}/tests/icechunk/__init__.py +0 -0
  56. {arraylake-0.32.0 → arraylake-1.0.0}/tests/icechunk/conftest.py +0 -0
  57. {arraylake-0.32.0 → arraylake-1.0.0}/tests/icechunk/test_storage.py +0 -0
  58. {arraylake-0.32.0 → arraylake-1.0.0}/tests/icechunk/test_virtual.py +0 -0
  59. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_asyn.py +0 -0
  60. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_config.py +0 -0
  61. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_credentials.py +0 -0
  62. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_diagnostics.py +0 -0
  63. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_performance.py +0 -0
  64. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_token_handler.py +0 -0
  65. {arraylake-0.32.0 → arraylake-1.0.0}/tests/test_types.py +0 -0
@@ -104,6 +104,7 @@ celerybeat.pid
104
104
 
105
105
  # Environments
106
106
  .env
107
+ !jobs/subsync/.env
107
108
  .venv
108
109
  env/
109
110
  venv/
@@ -1,20 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraylake
3
- Version: 0.32.0
3
+ Version: 1.0.0
4
4
  Summary: Python client for ArrayLake
5
5
  Project-URL: Documentation, https://docs.earthmover.io/
6
6
  Project-URL: Changelog, https://docs.earthmover.io/changelog
7
7
  Author-email: Joseph Hamman <joe@earthmover.io>
8
8
  License-Expression: MIT
9
9
  Classifier: Programming Language :: Python
10
- Classifier: Programming Language :: Python :: 3.11
11
10
  Classifier: Programming Language :: Python :: 3.12
12
11
  Classifier: Programming Language :: Python :: 3.13
13
- Requires-Python: <3.14,>=3.11
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: <3.15,>=3.12
14
14
  Requires-Dist: click<9.0,>=8.1.3
15
15
  Requires-Dist: donfig<1.0,>=0.7
16
- Requires-Dist: httpx<0.28,>=0.23
17
- Requires-Dist: icechunk<2.0.0,>=1.1.0
16
+ Requires-Dist: httpx<0.29,>=0.23
17
+ Requires-Dist: icechunk<3.0.0,>=2.0.1
18
18
  Requires-Dist: numpy<3.0,>=1.23
19
19
  Requires-Dist: packaging<26.0,>=23.0
20
20
  Requires-Dist: pydantic[email]<3.0,>=2.9
@@ -26,9 +26,7 @@ Requires-Dist: typer<1.0,>=0.12.0
26
26
  Requires-Dist: zarr<4.0,>=3.1.0
27
27
  Provides-Extra: maximal
28
28
  Requires-Dist: cf-xarray>=0.10.4; extra == 'maximal'
29
- Requires-Dist: numpy<2.3; extra == 'maximal'
30
- Requires-Dist: pyarrow<22; extra == 'maximal'
31
- Requires-Dist: xarray[complete]>=2024.10.0; extra == 'maximal'
29
+ Requires-Dist: xarray[accel,etc,io,parallel]>=2024.10.0; extra == 'maximal'
32
30
  Provides-Extra: xarray
33
31
  Requires-Dist: cf-xarray>=0.10.4; extra == 'xarray'
34
32
  Requires-Dist: xarray>=2024.10.0; extra == 'xarray'
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '1.0.0'
22
+ __version_tuple__ = version_tuple = (1, 0, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -4,6 +4,7 @@ import asyncio
4
4
  import json
5
5
  import os
6
6
  import random
7
+ import warnings
7
8
  import weakref
8
9
  from collections.abc import AsyncGenerator, Callable, Generator, Mapping
9
10
  from contextlib import _AsyncGeneratorContextManager
@@ -430,8 +431,15 @@ def _exception_log_debug(request: httpx.Request, response: httpx.Response):
430
431
  )
431
432
 
432
433
 
434
+ _emitted_deprecation_warnings: set[str] = set()
435
+
436
+
433
437
  def handle_response(response: httpx.Response):
434
438
  """Convenience function to handle response status codes"""
439
+ deprecation_msg = response.headers.get("X-Deprecation-Warning")
440
+ if deprecation_msg and deprecation_msg not in _emitted_deprecation_warnings:
441
+ _emitted_deprecation_warnings.add(deprecation_msg)
442
+ warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
435
443
  try:
436
444
  response.raise_for_status()
437
445
  except httpx.RequestError as exc:
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from enum import Enum
2
+ from enum import StrEnum
3
3
 
4
4
  import typer
5
5
  from rich import print_json
@@ -14,7 +14,7 @@ app = typer.Typer(help="Manage Arraylake compute services", no_args_is_help=True
14
14
  logger = get_logger(__name__)
15
15
 
16
16
 
17
- class ListOutputType(str, Enum):
17
+ class ListOutputType(StrEnum):
18
18
  rich = "rich"
19
19
  json = "json"
20
20
 
@@ -1,5 +1,5 @@
1
1
  import io
2
- from enum import Enum
2
+ from enum import StrEnum
3
3
  from pathlib import Path
4
4
 
5
5
  import typer
@@ -17,7 +17,7 @@ yaml = YAML(typ="safe", pure=True)
17
17
  yaml.default_flow_style = False
18
18
 
19
19
 
20
- class ListOutputType(str, Enum):
20
+ class ListOutputType(StrEnum):
21
21
  json = "json"
22
22
  yaml = "yaml"
23
23
 
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import warnings
3
- from enum import Enum
3
+ from enum import Enum, StrEnum
4
4
  from typing import TYPE_CHECKING, Optional
5
5
  from uuid import UUID
6
6
 
@@ -30,7 +30,7 @@ app = typer.Typer(help="Manage Arraylake repositories", no_args_is_help=True)
30
30
  logger = get_logger(__name__)
31
31
 
32
32
 
33
- class ListOutputType(str, Enum):
33
+ class ListOutputType(StrEnum):
34
34
  rich = "rich"
35
35
  json = "json"
36
36
 
@@ -142,7 +142,6 @@ async def create(
142
142
  repo_name: str = typer.Argument(..., help="Name of repository {ORG}/{REPO_NAME}"),
143
143
  bucket_config_nickname: str | None = typer.Option(None, help="Bucket config nickname"),
144
144
  prefix: str = typer.Option(None, help="Optional prefix for Icechunk repo. If not provided, a random ID + the repo name will be used."),
145
- import_existing: bool = typer.Option(False, help="Import existing Icechunk data into the new repo"),
146
145
  description: str | None = typer.Option(None, help="Description of the repo"),
147
146
  metadata: str | None = typer.Option(None, help="Optional metadata for the repo"),
148
147
  ):
@@ -162,7 +161,6 @@ async def create(
162
161
  repo_name,
163
162
  bucket_config_nickname=bucket_config_nickname,
164
163
  prefix=prefix,
165
- import_existing=import_existing,
166
164
  description=description,
167
165
  metadata=metadata_dict,
168
166
  )
@@ -173,7 +171,7 @@ async def create(
173
171
  async def import_repo(
174
172
  repo_name: str = typer.Argument(..., help="Name of repository {ORG}/{REPO_NAME}"),
175
173
  bucket_config_nickname: str = typer.Argument(..., help="Bucket config nickname"),
176
- prefix: str = typer.Option(None, help="Sub-prefix in which the Icechunk repo exists"),
174
+ prefix: str = typer.Argument(..., help="Prefix in which the Icechunk repo exists in the bucket"),
177
175
  description: str | None = typer.Option(None, help="Description of the repo"),
178
176
  metadata: str | None = typer.Option(None, help="Optional metadata for the repo"),
179
177
  ):
@@ -184,15 +182,15 @@ async def import_repo(
184
182
  - Import existing Icechunk repository
185
183
 
186
184
  ```
187
- $ arraylake repo import my-org/example-repo --bucket-config-nickname icechunk-bucket --prefix my-icechunk-prefix --metadata '{"key": "value"}'
185
+ $ arraylake repo import my-org/example-repo icechunk-bucket my-icechunk-prefix --metadata '{"key": "value"}'
188
186
  ```
189
187
  """
190
188
  metadata_dict = json.loads(metadata) if metadata else None
191
189
  with simple_progress(f"Importing repo [bold]{repo_name}[/bold]..."):
192
190
  await AsyncClient().import_repo(
193
191
  repo_name,
194
- bucket_config_nickname=bucket_config_nickname,
195
- prefix=prefix,
192
+ bucket_config_nickname,
193
+ prefix,
196
194
  description=description,
197
195
  metadata=metadata_dict,
198
196
  )
@@ -558,6 +558,17 @@ class AsyncClient:
558
558
  if response.virtual_chunk_credentials:
559
559
  credentials_map: dict[str, icechunk.AnyS3Credential | icechunk.AnyGcsCredential | icechunk.AnyAzureCredential | None] = {}
560
560
  for vcc_prefix, vcc_creds in response.virtual_chunk_credentials.items():
561
+ # Handle runtime VCCs (e.g., filtered subscriptions) - create VCC on the fly
562
+ if vcc_creds.runtime_vcc_name is not None:
563
+ bucket_config = await self.get_bucket_config(org=vcc_creds.org, nickname=vcc_creds.bucket_nickname)
564
+ store_config = bucket_config.to_vcc_store_config(user_id=UUID(response.principal_id))
565
+ container = icechunk.VirtualChunkContainer(
566
+ url_prefix=vcc_prefix,
567
+ store=store_config,
568
+ )
569
+ container.name = vcc_creds.runtime_vcc_name
570
+ config.set_virtual_chunk_container(container)
571
+
561
572
  # TODO: build refresh closures here using vcc_creds.org, vcc_creds.bucket_nickname,
562
573
  # and vcc_creds.platform so that credentials can be refreshed when they expire. (See https://github.com/earth-mover/arraylake/issues/5122)
563
574
  # TODO: support Azure virtual chunk credentials
@@ -644,7 +655,6 @@ class AsyncClient:
644
655
  *,
645
656
  bucket_config_nickname: BucketNickname | None = None,
646
657
  prefix: str | None = None,
647
- import_existing: bool = False,
648
658
  description: str | None = None,
649
659
  metadata: RepoMetadataT | None = None,
650
660
  config: icechunk.RepositoryConfig | None = None,
@@ -658,7 +668,6 @@ class AsyncClient:
658
668
  bucket_config_nickname: Bucket in which the underlying Icechunk repo will be created.
659
669
  If the repo exists, bucket_config_nickname is ignored.
660
670
  prefix: Optional prefix for Icechunk store. If not provided, a random ID + the repo name will be used.
661
- import_existing: If True, the Icechunk repo will be imported if it already exists.
662
671
  description: Optional description for the repo.
663
672
  metadata: Optional dictionary of metadata to tag the repo with.
664
673
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -707,7 +716,6 @@ class AsyncClient:
707
716
  name,
708
717
  bucket_config_nickname=bucket_config_nickname,
709
718
  prefix=prefix,
710
- import_existing=import_existing,
711
719
  description=description,
712
720
  metadata=metadata,
713
721
  config=config,
@@ -721,12 +729,12 @@ class AsyncClient:
721
729
  *,
722
730
  bucket_config_nickname: BucketNickname | None = None,
723
731
  prefix: str | None = None,
724
- import_existing: bool = False,
725
732
  description: str | None = None,
726
733
  metadata: RepoMetadataT | None = None,
727
734
  config: icechunk.RepositoryConfig | None = None,
728
735
  authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
729
736
  storage_options: StorageOptions | None = None,
737
+ spec_version: int = 2,
730
738
  ) -> IcechunkRepository:
731
739
  """Create a new repo.
732
740
 
@@ -734,7 +742,6 @@ class AsyncClient:
734
742
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
735
743
  bucket_config_nickname: Bucket in which the underlying Icechunk repo will be created.
736
744
  prefix: Optional prefix for Icechunk store. If not provided, a random ID + the repo name will be used.
737
- import_existing: If True, the Icechunk repo will be imported if it already exists.
738
745
  description: Optional description for the repo.
739
746
  metadata: Optional dictionary of metadata to tag the repo with.
740
747
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -745,6 +752,7 @@ class AsyncClient:
745
752
  and used for the containers. Must be supplied as complete urls.
746
753
  storage_options: Optional storage options for the underlying Icechunk storage.
747
754
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
755
+ spec_version: The Icechunk spec version to use for the repo. Defaults to 2.
748
756
 
749
757
  Returns:
750
758
  icechunk.Repository object for the repo.
@@ -761,13 +769,12 @@ class AsyncClient:
761
769
  bucket_config_nickname,
762
770
  kind=RepoKind.Icechunk,
763
771
  prefix=prefix,
764
- import_existing=import_existing,
765
772
  description=description,
766
773
  metadata=metadata,
767
774
  )
768
775
 
769
776
  # Raise a warning about token scoping for Azure delegated credentials
770
- if repo_model.bucket and repo_model.bucket.auth_config.method == "azure_credential_delegation":
777
+ if repo_model.bucket and repo_model.bucket.auth_config and repo_model.bucket.auth_config.method == "azure_credential_delegation":
771
778
  warnings.warn(
772
779
  "The bucket is configured to use Azure Customer Managed Role authentication. "
773
780
  "Credentials will be scoped to the container level only for this repo. "
@@ -776,6 +783,13 @@ class AsyncClient:
776
783
  )
777
784
 
778
785
  try:
786
+ # Throw error if the bucket is configured for anonymous access, this is only supported for imported repo
787
+ if _use_anonymous_credentials(repo_model.bucket):
788
+ bucket_name = f"{repo_model.bucket.nickname}" if repo_model.bucket else ""
789
+ raise ValueError(
790
+ f"The bucket {bucket_name} is configured for anonymous access and cannot be written to. Use `import_repo` to import existing repositories from this bucket."
791
+ )
792
+
779
793
  if not isinstance(config, RepositoryConfig) and config is not None:
780
794
  raise ValueError(f"config must be an icechunk.RepositoryConfig object or None: {config}.")
781
795
 
@@ -796,41 +810,16 @@ class AsyncClient:
796
810
  # TODO: make icechunk accept an empty dict instead of None
797
811
  prefixes_credential_mapping = None
798
812
 
799
- # Throws a specific error if the bucket is configured for anonymous access and the repo is not being imported
800
- if _use_anonymous_credentials(repo_model.bucket) and not import_existing:
801
- bucket_name = f"{repo_model.bucket.nickname}" if repo_model.bucket else ""
802
- raise ValueError(
803
- f"The bucket {bucket_name} is configured for anonymous access and cannot be written to. Import existing repositories from this bucket with the `import_existing` parameter."
804
- )
805
-
806
- if import_existing:
807
- warnings.warn(
808
- "The `import_existing` keyword argument to `create_repo` is deprecated. Please use the dedicated `import_repo` method instead of using `import_existing=True.",
809
- DeprecationWarning,
810
- )
811
-
812
- ic_repo = IcechunkRepository.open(
813
- icechunk_storage,
814
- config=config,
815
- authorize_virtual_chunk_access=prefixes_credential_mapping,
816
- )
813
+ create_kwargs: dict[str, object] = {
814
+ "config": config,
815
+ "authorize_virtual_chunk_access": prefixes_credential_mapping,
816
+ "spec_version": spec_version,
817
+ }
817
818
 
818
- # Note: We decided this was better than silently attempting to modify the IC repo config upon import by saving the config
819
- if authorize_virtual_chunk_access:
820
- warnings.warn(
821
- "New virtual chunk containers will not be persisted to the icechunk repo config. "
822
- "If you want future `client.get_repo` calls to be authorized to access the same virtual chunks, please now modify the config explicitly. "
823
- "You can do this using `client.set_virtual_chunk_containers()` with the same `authorize_virtual_chunk_access`, i.e: "
824
- f"`client.set_virtual_chunk_containers({name}, authorize_virtual_chunk_access={authorize_virtual_chunk_access})`. "
825
- "Note that this requires write access to the location of the icechunk repository, so will not work for anonymous access buckets, for example.",
826
- UserWarning,
827
- )
828
- else:
829
- ic_repo = IcechunkRepository.create(
830
- icechunk_storage,
831
- config=config,
832
- authorize_virtual_chunk_access=prefixes_credential_mapping,
833
- )
819
+ ic_repo = IcechunkRepository.create(
820
+ icechunk_storage,
821
+ **create_kwargs, # type: ignore[arg-type]
822
+ )
834
823
 
835
824
  ic_repo.set_default_commit_metadata({"author_name": author.name, "author_email": author.email})
836
825
 
@@ -845,10 +834,11 @@ class AsyncClient:
845
834
  self,
846
835
  name: OrgAndRepoName,
847
836
  bucket_config_nickname: BucketNickname,
837
+ prefix: str,
848
838
  *,
849
- prefix: str | None = None,
850
839
  description: str | None = None,
851
840
  metadata: RepoMetadataT | None = None,
841
+ authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
852
842
  storage_options: StorageOptions | None = None,
853
843
  ) -> IcechunkRepository:
854
844
  """Create a new Arraylake Repo by importing an existing Icechunk Repository.
@@ -856,35 +846,36 @@ class AsyncClient:
856
846
  Args:
857
847
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
858
848
  bucket_config_nickname: Bucket in which the underlying Icechunk repo exists.
859
- prefix: Sub-prefix in which the Icechunk repo exists. If not provided, repo is assumed to be located at the root of the bucket.
849
+ prefix: Prefix in which the Icechunk repo exists in the bucket.
860
850
  description: Optional description for the repo.
861
851
  metadata: Optional dictionary of metadata to tag the repo with.
862
852
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
853
+ authorize_virtual_chunk_access: A mapping from virtual chunk container prefixes to bucket nicknames.
854
+ Credentials will be fetched for these buckets based on the auth configuration
855
+ and used for the containers. Must be supplied as complete urls.
863
856
  storage_options: Optional storage options for the underlying Icechunk storage.
864
857
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
865
858
 
866
859
  Returns:
867
860
  icechunk.Repository object for the repo.
868
861
  """
869
-
870
862
  org, repo_name = _parse_org_and_repo(name)
871
863
  mstore = self._metastore_for_org(org)
872
864
 
873
865
  user = await mstore.get_user()
874
866
  author: Author = user.as_author()
875
867
 
876
- repo_model = await mstore.create_database(
868
+ repo_model = await mstore.import_database(
877
869
  repo_name,
878
870
  bucket_config_nickname,
879
- kind=RepoKind.Icechunk,
880
871
  prefix=prefix,
881
- import_existing=True,
872
+ kind=RepoKind.Icechunk,
882
873
  description=description,
883
874
  metadata=metadata,
884
875
  )
885
876
 
886
877
  # Raise a warning about token scoping for Azure delegated credentials
887
- if repo_model.bucket and repo_model.bucket.auth_config.method == "azure_credential_delegation":
878
+ if repo_model.bucket and repo_model.bucket.auth_config and repo_model.bucket.auth_config.method == "azure_credential_delegation":
888
879
  warnings.warn(
889
880
  "The bucket is configured to use Azure Customer Managed Role authentication. "
890
881
  "Credentials will be scoped to the container level only for this repo. "
@@ -897,7 +888,34 @@ class AsyncClient:
897
888
  repo_model, user_id=user.id, storage_options=storage_options
898
889
  )
899
890
 
900
- ic_repo = IcechunkRepository.open(icechunk_storage)
891
+ if authorize_virtual_chunk_access is not None:
892
+ config = await self._add_virtual_chunk_containers(
893
+ org=org, user=user, config=None, authorize_virtual_chunk_access=authorize_virtual_chunk_access
894
+ )
895
+
896
+ # fetch credentials to access buckets containing virtual chunks
897
+ prefixes_credential_mapping = await self.containers_credentials_for_buckets(
898
+ org, containers_to_buckets_map=dict(authorize_virtual_chunk_access)
899
+ )
900
+
901
+ # Warn that VCCs won't be persisted when importing
902
+ warnings.warn(
903
+ "New virtual chunk containers will not be persisted to the icechunk repo config. "
904
+ "If you want future `client.get_repo` calls to be authorized to access the same virtual chunks, please now modify the config explicitly. "
905
+ "You can do this using `client.set_virtual_chunk_containers()` with the same `authorize_virtual_chunk_access`, i.e: "
906
+ f"`client.set_virtual_chunk_containers({name}, authorize_virtual_chunk_access={authorize_virtual_chunk_access})`. "
907
+ "Note that this requires write access to the location of the icechunk repository, so will not work for anonymous access buckets, for example.",
908
+ UserWarning,
909
+ )
910
+ else:
911
+ config = None
912
+ prefixes_credential_mapping = None
913
+
914
+ ic_repo = IcechunkRepository.open(
915
+ icechunk_storage,
916
+ config=config,
917
+ authorize_virtual_chunk_access=prefixes_credential_mapping,
918
+ )
901
919
 
902
920
  ic_repo.set_default_commit_metadata({"author_name": author.name, "author_email": author.email})
903
921
 
@@ -1600,7 +1618,6 @@ class Client:
1600
1618
  *,
1601
1619
  bucket_config_nickname: BucketNickname | None = None,
1602
1620
  prefix: str | None = None,
1603
- import_existing: bool = False,
1604
1621
  description: str | None = None,
1605
1622
  metadata: RepoMetadataT | None = None,
1606
1623
  config: icechunk.RepositoryConfig | None = None,
@@ -1614,7 +1631,6 @@ class Client:
1614
1631
  bucket_config_nickname: The created repo will use this bucket for its chunks.
1615
1632
  If the repo exists, bucket_config_nickname is ignored.
1616
1633
  prefix: Optional prefix for Icechunk store. If not provided, a random ID + the repo name will be used.
1617
- import_existing: If True, the Icechunk repo will be imported if it already exists.
1618
1634
  description: Optional description for the repo.
1619
1635
  metadata: Optional dictionary of metadata to tag the repo with.
1620
1636
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -1640,7 +1656,6 @@ class Client:
1640
1656
  name,
1641
1657
  bucket_config_nickname=bucket_config_nickname,
1642
1658
  prefix=prefix,
1643
- import_existing=import_existing,
1644
1659
  description=description,
1645
1660
  metadata=metadata,
1646
1661
  config=config,
@@ -1654,12 +1669,12 @@ class Client:
1654
1669
  *,
1655
1670
  bucket_config_nickname: BucketNickname | None = None,
1656
1671
  prefix: str | None = None,
1657
- import_existing: bool = False,
1658
1672
  description: str | None = None,
1659
1673
  metadata: RepoMetadataT | None = None,
1660
1674
  config: icechunk.RepositoryConfig | None = None,
1661
1675
  authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
1662
1676
  storage_options: StorageOptions | None = None,
1677
+ spec_version: int = 2,
1663
1678
  ) -> IcechunkRepository:
1664
1679
  """Create a new repo
1665
1680
 
@@ -1667,7 +1682,6 @@ class Client:
1667
1682
  name: Full name of the repo to create (of the form [ORG]/[REPO])
1668
1683
  bucket_config_nickname: An optional bucket to use for the chunkstore
1669
1684
  prefix: Optional prefix for Icechunk store. If not provided, a random ID + the repo name will be used.
1670
- import_existing: If True, the Icechunk repo will be imported if it already exists.
1671
1685
  description: Optional description for the repo.
1672
1686
  metadata: Optional dictionary of metadata to tag the repo with.
1673
1687
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -1679,6 +1693,7 @@ class Client:
1679
1693
  and used for the containers. Used for Icechunk repos only.
1680
1694
  storage_options: Optional storage options for the underlying Icechunk storage.
1681
1695
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
1696
+ spec_version: The Icechunk spec version to use for the repo. Defaults to 2.
1682
1697
 
1683
1698
  Returns:
1684
1699
  icechunk.Repository: The created Icechunk repository.
@@ -1688,22 +1703,23 @@ class Client:
1688
1703
  name,
1689
1704
  bucket_config_nickname=bucket_config_nickname,
1690
1705
  prefix=prefix,
1691
- import_existing=import_existing,
1692
1706
  description=description,
1693
1707
  metadata=metadata,
1694
1708
  config=config,
1695
1709
  authorize_virtual_chunk_access=authorize_virtual_chunk_access,
1696
1710
  storage_options=storage_options,
1711
+ spec_version=spec_version,
1697
1712
  )
1698
1713
 
1699
1714
  def import_repo(
1700
1715
  self,
1701
1716
  name: OrgAndRepoName,
1702
1717
  bucket_config_nickname: BucketNickname,
1718
+ prefix: str,
1703
1719
  *,
1704
- prefix: str | None = None,
1705
1720
  description: str | None = None,
1706
1721
  metadata: RepoMetadataT | None = None,
1722
+ authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
1707
1723
  storage_options: StorageOptions | None = None,
1708
1724
  ) -> IcechunkRepository:
1709
1725
  """Create a new Arraylake Repo by importing an existing Icechunk Repository.
@@ -1711,10 +1727,13 @@ class Client:
1711
1727
  Args:
1712
1728
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
1713
1729
  bucket_config_nickname: Bucket in which the underlying Icechunk repo exists.
1714
- prefix: Sub-prefix in which the Icechunk repo exists. If not provided, repo is assumed to be located at the root of the bucket.
1730
+ prefix: Prefix in which the Icechunk repo exists in the bucket.
1715
1731
  description: Optional description for the repo.
1716
1732
  metadata: Optional dictionary of metadata to tag the repo with.
1717
1733
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
1734
+ authorize_virtual_chunk_access: A mapping from virtual chunk container prefixes to bucket nicknames.
1735
+ Credentials will be fetched for these buckets based on the auth configuration
1736
+ and used for the containers. Must be supplied as complete urls.
1718
1737
  storage_options: Optional storage options for the underlying Icechunk storage.
1719
1738
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
1720
1739
 
@@ -1724,10 +1743,11 @@ class Client:
1724
1743
  return sync(
1725
1744
  self.aclient.import_repo,
1726
1745
  name,
1727
- bucket_config_nickname=bucket_config_nickname,
1728
- prefix=prefix,
1746
+ bucket_config_nickname,
1747
+ prefix,
1729
1748
  description=description,
1730
1749
  metadata=metadata,
1750
+ authorize_virtual_chunk_access=authorize_virtual_chunk_access,
1731
1751
  storage_options=storage_options,
1732
1752
  )
1733
1753
 
@@ -1,11 +1,11 @@
1
1
  import re
2
2
  from datetime import datetime
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
 
5
5
  from pydantic import BaseModel, field_validator
6
6
 
7
7
 
8
- class ServiceType(str, Enum):
8
+ class ServiceType(StrEnum):
9
9
  dap = "dap2"
10
10
  edr = "edr"
11
11
  wms = "wms"
@@ -13,14 +13,14 @@ class ServiceType(str, Enum):
13
13
  zarr = "zarr"
14
14
 
15
15
 
16
- class ServiceStatus(str, Enum):
16
+ class ServiceStatus(StrEnum):
17
17
  available = "available"
18
18
  progressing = "progressing"
19
19
  unknown = "unknown"
20
20
  error = "error"
21
21
 
22
22
 
23
- class LogLevel(str, Enum):
23
+ class LogLevel(StrEnum):
24
24
  debug = "DEBUG"
25
25
  info = "INFO"
26
26
  warning = "WARNING"
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  from dataclasses import dataclass, field
5
- from typing import Any, Literal
5
+ from typing import Any
6
6
  from uuid import UUID
7
7
 
8
8
  from pydantic import TypeAdapter
@@ -26,6 +26,7 @@ from arraylake.types import (
26
26
  Repo,
27
27
  RepoActions,
28
28
  RepoCreateBody,
29
+ RepoImportBody,
29
30
  RepoKind,
30
31
  RepoMetadataT,
31
32
  RepoModifyRequest,
@@ -132,10 +133,9 @@ class HttpMetastore(ArraylakeHttpClient, Metastore):
132
133
  bucket_nickname: str | None = None,
133
134
  kind: RepoKind = RepoKind.Icechunk,
134
135
  prefix: str | None = None,
135
- import_existing: bool = False,
136
136
  description: str | None = None,
137
137
  metadata: RepoMetadataT | None = None,
138
- ):
138
+ ) -> Repo:
139
139
  """
140
140
  Creates a repo database entry in the metastore.
141
141
 
@@ -144,17 +144,18 @@ class HttpMetastore(ArraylakeHttpClient, Metastore):
144
144
  bucket_nickname: Optional nickname of a bucket already existing in the org.
145
145
  kind: Kind of repo to create
146
146
  prefix: Optional prefix for the icechunk repo
147
- import_existing: Whether to import an existing icechunk repo
148
147
  description: Optional description for the repo
149
148
  metadata: Optional metadata for the repo
149
+
150
+ Returns:
151
+ The created Repo object
150
152
  """
151
- create_mode: Literal["create", "register", "import"] = "import" if import_existing else "register"
152
153
  body = RepoCreateBody(
153
154
  name=name,
154
155
  bucket_nickname=bucket_nickname,
155
156
  kind=kind,
156
157
  prefix=prefix,
157
- create_mode=create_mode,
158
+ create_mode="register",
158
159
  description=description,
159
160
  metadata=metadata,
160
161
  )
@@ -163,7 +164,46 @@ class HttpMetastore(ArraylakeHttpClient, Metastore):
163
164
  repo = Repo.model_validate_json(response.content)
164
165
 
165
166
  if repo.kind == RepoKind.Icechunk:
166
- # TODO: should I wrap this response in an IcechunkV2Database object?
167
+ return repo
168
+
169
+ raise ValueError(f"Unknown repo kind: {repo.kind}")
170
+
171
+ async def import_database(
172
+ self,
173
+ name: str,
174
+ bucket_nickname: str,
175
+ prefix: str,
176
+ kind: RepoKind = RepoKind.Icechunk,
177
+ description: str | None = None,
178
+ metadata: RepoMetadataT | None = None,
179
+ ) -> Repo:
180
+ """
181
+ Imports an existing icechunk repo from storage.
182
+
183
+ Args:
184
+ name: Name of the repo to create
185
+ bucket_nickname: Nickname of the bucket containing the existing icechunk repo.
186
+ prefix: Prefix where the icechunk repo exists in the bucket.
187
+ kind: Kind of repo to create
188
+ description: Optional description for the repo
189
+ metadata: Optional metadata for the repo
190
+
191
+ Returns:
192
+ The imported Repo object
193
+ """
194
+ body = RepoImportBody(
195
+ name=name,
196
+ bucket_nickname=bucket_nickname,
197
+ kind=kind,
198
+ prefix=prefix,
199
+ description=description,
200
+ metadata=metadata,
201
+ )
202
+ response = await self._request("POST", f"/orgs/{self._config.org}/repos/import", content=body.model_dump_json())
203
+ handle_response(response)
204
+ repo = Repo.model_validate_json(response.content)
205
+
206
+ if repo.kind == RepoKind.Icechunk:
167
207
  return repo
168
208
 
169
209
  raise ValueError(f"Unknown repo kind: {repo.kind}")