arraylake 0.32.0__tar.gz → 0.33.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-0.33.0}/.gitignore +1 -0
  2. {arraylake-0.32.0 → arraylake-0.33.0}/PKG-INFO +2 -2
  3. arraylake-0.33.0/arraylake/_version.py +24 -0
  4. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/compute.py +2 -2
  5. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/config.py +2 -2
  6. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/repo.py +6 -8
  7. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/client.py +70 -60
  8. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/compute/types.py +4 -4
  9. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/metastore/http_metastore.py +47 -7
  10. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/repos/icechunk/types.py +23 -3
  11. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/repos/icechunk/virtual.py +22 -0
  12. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/types.py +60 -37
  13. {arraylake-0.32.0 → arraylake-0.33.0}/pyproject.toml +2 -2
  14. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_cli.py +1 -1
  15. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_client.py +6 -11
  16. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_subscriptions.py +11 -11
  17. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_virtual_chunks.py +100 -99
  18. {arraylake-0.32.0 → arraylake-0.33.0}/uv.lock +48 -365
  19. arraylake-0.32.0/arraylake/_version.py +0 -34
  20. {arraylake-0.32.0 → arraylake-0.33.0}/README.md +0 -0
  21. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/__init__.py +0 -0
  22. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/__main__.py +0 -0
  23. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/api_utils.py +0 -0
  24. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/asyn.py +0 -0
  25. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/__init__.py +0 -0
  26. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/auth.py +0 -0
  27. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/main.py +0 -0
  28. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/cli/utils.py +0 -0
  29. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/compute/doctor.py +0 -0
  30. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/compute/http_client.py +0 -0
  31. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/compute/services.py +0 -0
  32. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/config.py +0 -0
  33. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/config.yaml +0 -0
  34. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/credentials.py +0 -0
  35. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/diagnostics.py +0 -0
  36. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/display/__init__.py +0 -0
  37. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/display/repo.py +0 -0
  38. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/display/repolist.py +0 -0
  39. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/exceptions.py +0 -0
  40. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/log_util.py +0 -0
  41. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/metastore/__init__.py +0 -0
  42. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/metastore/abc.py +0 -0
  43. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/py.typed +0 -0
  44. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/repos/__init__.py +0 -0
  45. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/repos/icechunk/__init__.py +0 -0
  46. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/repos/icechunk/storage.py +0 -0
  47. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/token.py +0 -0
  48. {arraylake-0.32.0 → arraylake-0.33.0}/arraylake/tuning.py +0 -0
  49. {arraylake-0.32.0 → arraylake-0.33.0}/tests/__init__.py +0 -0
  50. {arraylake-0.32.0 → arraylake-0.33.0}/tests/compute/test_client.py +0 -0
  51. {arraylake-0.32.0 → arraylake-0.33.0}/tests/compute/test_doctor.py +0 -0
  52. {arraylake-0.32.0 → arraylake-0.33.0}/tests/config.yaml +0 -0
  53. {arraylake-0.32.0 → arraylake-0.33.0}/tests/conftest.py +0 -0
  54. {arraylake-0.32.0 → arraylake-0.33.0}/tests/icechunk/__init__.py +0 -0
  55. {arraylake-0.32.0 → arraylake-0.33.0}/tests/icechunk/conftest.py +0 -0
  56. {arraylake-0.32.0 → arraylake-0.33.0}/tests/icechunk/test_storage.py +0 -0
  57. {arraylake-0.32.0 → arraylake-0.33.0}/tests/icechunk/test_virtual.py +0 -0
  58. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_api_utils.py +0 -0
  59. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_asyn.py +0 -0
  60. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_config.py +0 -0
  61. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_credentials.py +0 -0
  62. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_diagnostics.py +0 -0
  63. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_performance.py +0 -0
  64. {arraylake-0.32.0 → arraylake-0.33.0}/tests/test_token_handler.py +0 -0
  65. {arraylake-0.32.0 → arraylake-0.33.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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraylake
3
- Version: 0.32.0
3
+ Version: 0.33.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
@@ -28,7 +28,7 @@ Provides-Extra: maximal
28
28
  Requires-Dist: cf-xarray>=0.10.4; extra == 'maximal'
29
29
  Requires-Dist: numpy<2.3; extra == 'maximal'
30
30
  Requires-Dist: pyarrow<22; extra == 'maximal'
31
- Requires-Dist: xarray[complete]>=2024.10.0; extra == 'maximal'
31
+ Requires-Dist: xarray[accel,etc,io,parallel]>=2024.10.0; extra == 'maximal'
32
32
  Provides-Extra: xarray
33
33
  Requires-Dist: cf-xarray>=0.10.4; extra == 'xarray'
34
34
  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 = '0.33.0'
22
+ __version_tuple__ = version_tuple = (0, 33, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -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
  )
@@ -46,6 +46,7 @@ from arraylake.repos.icechunk.storage import (
46
46
  _get_icechunk_storage_obj,
47
47
  )
48
48
  from arraylake.repos.icechunk.virtual import (
49
+ create_vcc_store_config_for_platform,
49
50
  create_virtual_chunk_container,
50
51
  forbid_unsafe_virtual_bucket_configs,
51
52
  forbid_unsafe_virtual_chunk_containers,
@@ -558,6 +559,16 @@ class AsyncClient:
558
559
  if response.virtual_chunk_credentials:
559
560
  credentials_map: dict[str, icechunk.AnyS3Credential | icechunk.AnyGcsCredential | icechunk.AnyAzureCredential | None] = {}
560
561
  for vcc_prefix, vcc_creds in response.virtual_chunk_credentials.items():
562
+ # Handle runtime VCCs (e.g., filtered subscriptions) - create VCC on the fly
563
+ if vcc_creds.runtime_vcc_name is not None:
564
+ store_config = create_vcc_store_config_for_platform(vcc_creds.platform)
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,7 +729,6 @@ 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,
@@ -734,7 +741,6 @@ class AsyncClient:
734
741
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
735
742
  bucket_config_nickname: Bucket in which the underlying Icechunk repo will be created.
736
743
  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
744
  description: Optional description for the repo.
739
745
  metadata: Optional dictionary of metadata to tag the repo with.
740
746
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -761,13 +767,12 @@ class AsyncClient:
761
767
  bucket_config_nickname,
762
768
  kind=RepoKind.Icechunk,
763
769
  prefix=prefix,
764
- import_existing=import_existing,
765
770
  description=description,
766
771
  metadata=metadata,
767
772
  )
768
773
 
769
774
  # 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":
775
+ if repo_model.bucket and repo_model.bucket.auth_config and repo_model.bucket.auth_config.method == "azure_credential_delegation":
771
776
  warnings.warn(
772
777
  "The bucket is configured to use Azure Customer Managed Role authentication. "
773
778
  "Credentials will be scoped to the container level only for this repo. "
@@ -776,6 +781,13 @@ class AsyncClient:
776
781
  )
777
782
 
778
783
  try:
784
+ # Throw error if the bucket is configured for anonymous access, this is only supported for imported repo
785
+ if _use_anonymous_credentials(repo_model.bucket):
786
+ bucket_name = f"{repo_model.bucket.nickname}" if repo_model.bucket else ""
787
+ raise ValueError(
788
+ 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."
789
+ )
790
+
779
791
  if not isinstance(config, RepositoryConfig) and config is not None:
780
792
  raise ValueError(f"config must be an icechunk.RepositoryConfig object or None: {config}.")
781
793
 
@@ -796,41 +808,11 @@ class AsyncClient:
796
808
  # TODO: make icechunk accept an empty dict instead of None
797
809
  prefixes_credential_mapping = None
798
810
 
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
- )
817
-
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
- )
811
+ ic_repo = IcechunkRepository.create(
812
+ icechunk_storage,
813
+ config=config,
814
+ authorize_virtual_chunk_access=prefixes_credential_mapping,
815
+ )
834
816
 
835
817
  ic_repo.set_default_commit_metadata({"author_name": author.name, "author_email": author.email})
836
818
 
@@ -845,10 +827,11 @@ class AsyncClient:
845
827
  self,
846
828
  name: OrgAndRepoName,
847
829
  bucket_config_nickname: BucketNickname,
830
+ prefix: str,
848
831
  *,
849
- prefix: str | None = None,
850
832
  description: str | None = None,
851
833
  metadata: RepoMetadataT | None = None,
834
+ authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
852
835
  storage_options: StorageOptions | None = None,
853
836
  ) -> IcechunkRepository:
854
837
  """Create a new Arraylake Repo by importing an existing Icechunk Repository.
@@ -856,35 +839,36 @@ class AsyncClient:
856
839
  Args:
857
840
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
858
841
  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.
842
+ prefix: Prefix in which the Icechunk repo exists in the bucket.
860
843
  description: Optional description for the repo.
861
844
  metadata: Optional dictionary of metadata to tag the repo with.
862
845
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
846
+ authorize_virtual_chunk_access: A mapping from virtual chunk container prefixes to bucket nicknames.
847
+ Credentials will be fetched for these buckets based on the auth configuration
848
+ and used for the containers. Must be supplied as complete urls.
863
849
  storage_options: Optional storage options for the underlying Icechunk storage.
864
850
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
865
851
 
866
852
  Returns:
867
853
  icechunk.Repository object for the repo.
868
854
  """
869
-
870
855
  org, repo_name = _parse_org_and_repo(name)
871
856
  mstore = self._metastore_for_org(org)
872
857
 
873
858
  user = await mstore.get_user()
874
859
  author: Author = user.as_author()
875
860
 
876
- repo_model = await mstore.create_database(
861
+ repo_model = await mstore.import_database(
877
862
  repo_name,
878
863
  bucket_config_nickname,
879
- kind=RepoKind.Icechunk,
880
864
  prefix=prefix,
881
- import_existing=True,
865
+ kind=RepoKind.Icechunk,
882
866
  description=description,
883
867
  metadata=metadata,
884
868
  )
885
869
 
886
870
  # 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":
871
+ if repo_model.bucket and repo_model.bucket.auth_config and repo_model.bucket.auth_config.method == "azure_credential_delegation":
888
872
  warnings.warn(
889
873
  "The bucket is configured to use Azure Customer Managed Role authentication. "
890
874
  "Credentials will be scoped to the container level only for this repo. "
@@ -897,7 +881,34 @@ class AsyncClient:
897
881
  repo_model, user_id=user.id, storage_options=storage_options
898
882
  )
899
883
 
900
- ic_repo = IcechunkRepository.open(icechunk_storage)
884
+ if authorize_virtual_chunk_access is not None:
885
+ config = await self._add_virtual_chunk_containers(
886
+ org=org, user=user, config=None, authorize_virtual_chunk_access=authorize_virtual_chunk_access
887
+ )
888
+
889
+ # fetch credentials to access buckets containing virtual chunks
890
+ prefixes_credential_mapping = await self.containers_credentials_for_buckets(
891
+ org, containers_to_buckets_map=dict(authorize_virtual_chunk_access)
892
+ )
893
+
894
+ # Warn that VCCs won't be persisted when importing
895
+ warnings.warn(
896
+ "New virtual chunk containers will not be persisted to the icechunk repo config. "
897
+ "If you want future `client.get_repo` calls to be authorized to access the same virtual chunks, please now modify the config explicitly. "
898
+ "You can do this using `client.set_virtual_chunk_containers()` with the same `authorize_virtual_chunk_access`, i.e: "
899
+ f"`client.set_virtual_chunk_containers({name}, authorize_virtual_chunk_access={authorize_virtual_chunk_access})`. "
900
+ "Note that this requires write access to the location of the icechunk repository, so will not work for anonymous access buckets, for example.",
901
+ UserWarning,
902
+ )
903
+ else:
904
+ config = None
905
+ prefixes_credential_mapping = None
906
+
907
+ ic_repo = IcechunkRepository.open(
908
+ icechunk_storage,
909
+ config=config,
910
+ authorize_virtual_chunk_access=prefixes_credential_mapping,
911
+ )
901
912
 
902
913
  ic_repo.set_default_commit_metadata({"author_name": author.name, "author_email": author.email})
903
914
 
@@ -1600,7 +1611,6 @@ class Client:
1600
1611
  *,
1601
1612
  bucket_config_nickname: BucketNickname | None = None,
1602
1613
  prefix: str | None = None,
1603
- import_existing: bool = False,
1604
1614
  description: str | None = None,
1605
1615
  metadata: RepoMetadataT | None = None,
1606
1616
  config: icechunk.RepositoryConfig | None = None,
@@ -1614,7 +1624,6 @@ class Client:
1614
1624
  bucket_config_nickname: The created repo will use this bucket for its chunks.
1615
1625
  If the repo exists, bucket_config_nickname is ignored.
1616
1626
  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
1627
  description: Optional description for the repo.
1619
1628
  metadata: Optional dictionary of metadata to tag the repo with.
1620
1629
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -1640,7 +1649,6 @@ class Client:
1640
1649
  name,
1641
1650
  bucket_config_nickname=bucket_config_nickname,
1642
1651
  prefix=prefix,
1643
- import_existing=import_existing,
1644
1652
  description=description,
1645
1653
  metadata=metadata,
1646
1654
  config=config,
@@ -1654,7 +1662,6 @@ class Client:
1654
1662
  *,
1655
1663
  bucket_config_nickname: BucketNickname | None = None,
1656
1664
  prefix: str | None = None,
1657
- import_existing: bool = False,
1658
1665
  description: str | None = None,
1659
1666
  metadata: RepoMetadataT | None = None,
1660
1667
  config: icechunk.RepositoryConfig | None = None,
@@ -1667,7 +1674,6 @@ class Client:
1667
1674
  name: Full name of the repo to create (of the form [ORG]/[REPO])
1668
1675
  bucket_config_nickname: An optional bucket to use for the chunkstore
1669
1676
  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
1677
  description: Optional description for the repo.
1672
1678
  metadata: Optional dictionary of metadata to tag the repo with.
1673
1679
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
@@ -1688,7 +1694,6 @@ class Client:
1688
1694
  name,
1689
1695
  bucket_config_nickname=bucket_config_nickname,
1690
1696
  prefix=prefix,
1691
- import_existing=import_existing,
1692
1697
  description=description,
1693
1698
  metadata=metadata,
1694
1699
  config=config,
@@ -1700,10 +1705,11 @@ class Client:
1700
1705
  self,
1701
1706
  name: OrgAndRepoName,
1702
1707
  bucket_config_nickname: BucketNickname,
1708
+ prefix: str,
1703
1709
  *,
1704
- prefix: str | None = None,
1705
1710
  description: str | None = None,
1706
1711
  metadata: RepoMetadataT | None = None,
1712
+ authorize_virtual_chunk_access: Mapping[BucketPrefix, BucketNickname] | None = None,
1707
1713
  storage_options: StorageOptions | None = None,
1708
1714
  ) -> IcechunkRepository:
1709
1715
  """Create a new Arraylake Repo by importing an existing Icechunk Repository.
@@ -1711,10 +1717,13 @@ class Client:
1711
1717
  Args:
1712
1718
  name: Full name of the repo to create (of the form [ORG]/[REPO]).
1713
1719
  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.
1720
+ prefix: Prefix in which the Icechunk repo exists in the bucket.
1715
1721
  description: Optional description for the repo.
1716
1722
  metadata: Optional dictionary of metadata to tag the repo with.
1717
1723
  Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
1724
+ authorize_virtual_chunk_access: A mapping from virtual chunk container prefixes to bucket nicknames.
1725
+ Credentials will be fetched for these buckets based on the auth configuration
1726
+ and used for the containers. Must be supplied as complete urls.
1718
1727
  storage_options: Optional storage options for the underlying Icechunk storage.
1719
1728
  Currently supports `network_stream_timeout_seconds` for S3, Tigris, and R2 storage.
1720
1729
 
@@ -1724,10 +1733,11 @@ class Client:
1724
1733
  return sync(
1725
1734
  self.aclient.import_repo,
1726
1735
  name,
1727
- bucket_config_nickname=bucket_config_nickname,
1728
- prefix=prefix,
1736
+ bucket_config_nickname,
1737
+ prefix,
1729
1738
  description=description,
1730
1739
  metadata=metadata,
1740
+ authorize_virtual_chunk_access=authorize_virtual_chunk_access,
1731
1741
  storage_options=storage_options,
1732
1742
  )
1733
1743
 
@@ -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}")
@@ -1,10 +1,10 @@
1
1
  import json
2
2
  from datetime import datetime
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
  from typing import Any, NewType
5
5
 
6
6
  from icechunk import Diff, SnapshotInfo
7
- from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_serializer
7
+ from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_serializer, field_validator
8
8
  from zarr import AsyncArray, AsyncGroup
9
9
  from zarr.abc.metadata import Metadata
10
10
 
@@ -153,7 +153,7 @@ class ExpirationResult(BaseModel):
153
153
  # Dataset Tree types for xarray-style representation
154
154
 
155
155
 
156
- class ArrayClassification(str, Enum):
156
+ class ArrayClassification(StrEnum):
157
157
  """Classification of an array as coordinate or data variable (xarray convention)."""
158
158
 
159
159
  COORDINATE = "coordinate"
@@ -198,4 +198,24 @@ class DatasetNode(BaseModel):
198
198
  child_groups: list[str] # Names of direct child groups (for navigation)
199
199
  is_xarray_compatible: bool = True # False if dimensions conflict or arrays lack dimension_names
200
200
  nbytes: int = 0 # Total bytes consumed by all arrays (uncompressed)
201
+ # Returning spec_version in this response to avoid another roundtrip in the web app for IC spec version
202
+ spec_version: int | None = None # Icechunk spec version (1 or 2)
201
203
  model_config = {"arbitrary_types_allowed": True}
204
+
205
+ @field_validator("spec_version", mode="before")
206
+ @classmethod
207
+ def validate_spec_version(cls, value: Any) -> int | None:
208
+ """Convert SpecVersion enum to int during validation."""
209
+ if value is None:
210
+ return None
211
+ # Handle icechunk.SpecVersion which has __int__ method
212
+ return int(value)
213
+
214
+ @field_serializer("spec_version")
215
+ @classmethod
216
+ def serialize_spec_version(cls, value: Any) -> int | None:
217
+ """Serialize SpecVersion to int for API responses."""
218
+ if value is None:
219
+ return None
220
+ # Handle icechunk.SpecVersion which has __int__ method
221
+ return int(value)
@@ -19,6 +19,28 @@ from arraylake.types import (
19
19
  FORBIDDEN_VIRTUAL_CHUNK_PREFIXES = {"file://", "memory://", "http://"}
20
20
 
21
21
 
22
+ def create_vcc_store_config_for_platform(platform: str) -> icechunk.AnyObjectStoreConfig:
23
+ """Create an Icechunk ObjectStoreConfig for a virtual chunk container based on platform.
24
+
25
+ This is used for runtime VCCs (e.g., filtered subscriptions) where we need to create
26
+ a VCC dynamically based on the server-provided platform type.
27
+
28
+ Args:
29
+ platform: The storage platform (s3, s3-compatible, minio, gs, azure)
30
+
31
+ Returns:
32
+ An icechunk ObjectStoreConfig for the given platform
33
+ """
34
+ if platform in ("s3", "s3-compatible", "minio"):
35
+ return icechunk.ObjectStoreConfig.S3(icechunk.S3Options())
36
+ elif platform == "gs":
37
+ return icechunk.ObjectStoreConfig.Gcs({})
38
+ elif platform == "azure":
39
+ return icechunk.ObjectStoreConfig.Azure({})
40
+ else:
41
+ raise ValueError(f"Unsupported platform for VCC store config: {platform}")
42
+
43
+
22
44
  def get_icechunk_container_credentials(
23
45
  bucket_platform: str,
24
46
  credentials: S3Credentials | GSCredentials | None,