arraylake 0.18.0__tar.gz → 0.19.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.
- {arraylake-0.18.0 → arraylake-0.19.0}/PKG-INFO +3 -3
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/_version.py +2 -2
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/repo.py +15 -1
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/client.py +11 -21
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/compute/doctor.py +34 -18
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/config.yaml +2 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/icechunk/storage.py +8 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/metastore/http_metastore.py +3 -9
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/types.py +35 -13
- {arraylake-0.18.0 → arraylake-0.19.0}/pyproject.toml +2 -2
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/config.yaml +2 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/icechunk/test_client.py +3 -1
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/icechunk/test_storage.py +19 -2
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_client.py +15 -11
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_config.py +1 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/.gitignore +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/README.md +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/__main__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/api_utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/asyn.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/auth.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/compute.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/config.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/export.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/main.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/cli/utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/compute/http_client.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/compute/services.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/compute/types.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/config.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/credentials.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/diagnostics.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/exceptions.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/experimental.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/log_util.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/py.typed +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/icechunk/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/icechunk/types.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/icechunk/utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/icechunk/virtual.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/abc.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/base_chunkstore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/credential_provider.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/fsspec_chunkstore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/fsspec_compat.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/s3chunkstore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/chunkstore/utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/commits.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/metastore/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/metastore/abc.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/repo.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/types.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/virtual.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/zarr2_store.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/zarr3_store.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/repos/v1/zarr_util.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/retrier.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/strategies.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/arraylake/token.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/integration_tests/chunkstores.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/integration_tests/data/small.nc +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/integration_tests/intercompat.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/integration_tests/run-compatibility-test.sh +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/compute/test_client.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/compute/test_doctor.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/conftest.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/icechunk/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/icechunk/conftest.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/icechunk/test_virtual.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_api_utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_asyn.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_diagnostics.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_retrier.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_token_handler.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/test_types.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/conftest.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/helpers/__init__.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/helpers/test_utils.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_chunkstore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_cli.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_client.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_commits.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_credential_provider.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_dask.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_download_optimize_logic.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_expiration.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_export.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_gc.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_memory_leak.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_metastore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_move.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_properties.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_repo.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_repo_experimental.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_sessions.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_stateful_chunkstore.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_stateful_hierarchy.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_stateful_history_rewrite.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_stateful_repo_ops.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_store.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_virtual.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_xarray.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_zarr.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/tests/v1/test_zarr3_shim.py +0 -0
- {arraylake-0.18.0 → arraylake-0.19.0}/validate_export.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraylake
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.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
|
|
@@ -39,7 +39,7 @@ Requires-Dist: cfgrib~=0.9; extra == 'grib'
|
|
|
39
39
|
Requires-Dist: eccodes>=2.37; extra == 'grib'
|
|
40
40
|
Requires-Dist: kerchunk<0.3,>=0.2.8; extra == 'grib'
|
|
41
41
|
Provides-Extra: icechunk
|
|
42
|
-
Requires-Dist: icechunk<0.3.0,>=0.2.
|
|
42
|
+
Requires-Dist: icechunk<0.3.0,>=0.2.15; extra == 'icechunk'
|
|
43
43
|
Provides-Extra: maximal
|
|
44
44
|
Requires-Dist: dask>=2022.10.2; extra == 'maximal'
|
|
45
45
|
Requires-Dist: distributed>=2022.10.2; extra == 'maximal'
|
|
@@ -73,7 +73,7 @@ Requires-Dist: tenacity==8.4.1; extra == 'min-reqs'
|
|
|
73
73
|
Requires-Dist: typer==0.12.0; extra == 'min-reqs'
|
|
74
74
|
Requires-Dist: zarr==2.18.0; extra == 'min-reqs'
|
|
75
75
|
Provides-Extra: minicechunk
|
|
76
|
-
Requires-Dist: icechunk==0.2.
|
|
76
|
+
Requires-Dist: icechunk==0.2.15; extra == 'minicechunk'
|
|
77
77
|
Requires-Dist: numpy==1.25; extra == 'minicechunk'
|
|
78
78
|
Requires-Dist: zarr==3.0.0; extra == 'minicechunk'
|
|
79
79
|
Provides-Extra: virtual
|
|
@@ -84,6 +84,20 @@ def _repos_table(repos, org):
|
|
|
84
84
|
return table
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def _make_json_safe(obj):
|
|
88
|
+
"""Convert an object to a JSON-safe format."""
|
|
89
|
+
if isinstance(obj, dict):
|
|
90
|
+
return {k: _make_json_safe(v) for k, v in obj.items()}
|
|
91
|
+
elif isinstance(obj, list):
|
|
92
|
+
return [_make_json_safe(i) for i in obj]
|
|
93
|
+
elif isinstance(obj, set):
|
|
94
|
+
return _make_json_safe(list(obj))
|
|
95
|
+
elif isinstance(obj, Enum):
|
|
96
|
+
return obj.value
|
|
97
|
+
else:
|
|
98
|
+
return obj
|
|
99
|
+
|
|
100
|
+
|
|
87
101
|
@app.command(name="list")
|
|
88
102
|
@coro # type: ignore
|
|
89
103
|
async def list_repos(
|
|
@@ -111,7 +125,7 @@ async def list_repos(
|
|
|
111
125
|
repos = await AsyncClient().list_repos(org, filter_metadata=metadata_filter_dict)
|
|
112
126
|
|
|
113
127
|
if output == "json":
|
|
114
|
-
repos = [r._asdict() for r in repos]
|
|
128
|
+
repos = [_make_json_safe(r._asdict()) for r in repos]
|
|
115
129
|
print_json(data=repos)
|
|
116
130
|
elif repos:
|
|
117
131
|
rich_console.print(_repos_table(repos, org))
|
|
@@ -54,12 +54,10 @@ from arraylake.types import (
|
|
|
54
54
|
ApiClientResponse,
|
|
55
55
|
Author,
|
|
56
56
|
BucketResponse,
|
|
57
|
-
ExpirationConfig,
|
|
58
|
-
GCConfig,
|
|
59
57
|
GSCredentials,
|
|
60
58
|
HmacAuth,
|
|
61
|
-
MaintenanceWindow,
|
|
62
59
|
NewBucket,
|
|
60
|
+
OptimizationConfig,
|
|
63
61
|
OrgActions,
|
|
64
62
|
)
|
|
65
63
|
from arraylake.types import Repo as RepoModel
|
|
@@ -702,12 +700,16 @@ class AsyncClient:
|
|
|
702
700
|
else:
|
|
703
701
|
credentials = None
|
|
704
702
|
|
|
703
|
+
# If config is not set, set scatter_initial_credentials to True by default
|
|
704
|
+
scatter_initial_credentials = config.get("icechunk.scatter_initial_credentials", True)
|
|
705
|
+
|
|
705
706
|
return _get_icechunk_storage_obj(
|
|
706
707
|
bucket_config=repo_model.bucket,
|
|
707
708
|
prefix=repo_model.prefix,
|
|
708
709
|
credential_type=_get_credential_type(credentials, credential_refresh_func),
|
|
709
710
|
credentials=credentials,
|
|
710
711
|
credential_refresh_func=credential_refresh_func,
|
|
712
|
+
scatter_initial_credentials=scatter_initial_credentials,
|
|
711
713
|
)
|
|
712
714
|
|
|
713
715
|
async def get_icechunk_storage(self, name: str, credentials_override=None): # icechunk.AnyCredential | None # -> icechunk.Storage
|
|
@@ -784,9 +786,7 @@ class AsyncClient:
|
|
|
784
786
|
add_metadata: Optional[RepoMetadataT] = None,
|
|
785
787
|
remove_metadata: Optional[list[str]] = None,
|
|
786
788
|
update_metadata: Optional[RepoMetadataT] = None,
|
|
787
|
-
|
|
788
|
-
gc_config: Optional[GCConfig] = None,
|
|
789
|
-
expiration_config: Optional[ExpirationConfig] = None,
|
|
789
|
+
optimization_config: Optional[OptimizationConfig] = None,
|
|
790
790
|
) -> None:
|
|
791
791
|
"""Modify a repo's metadata or description.
|
|
792
792
|
|
|
@@ -799,9 +799,7 @@ class AsyncClient:
|
|
|
799
799
|
remove_metadata: List of metadata keys to remove from the repo.
|
|
800
800
|
update_metadata: Optional dictionary of metadata to update on the repo.
|
|
801
801
|
Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
|
|
802
|
-
|
|
803
|
-
gc_config: Optional garbage collection configuration for the repo.
|
|
804
|
-
expiration_config: Optional expiration configuration for the repo.
|
|
802
|
+
optimization_config: Optional optimization configuration for the repo.
|
|
805
803
|
"""
|
|
806
804
|
org, repo_name = _parse_org_and_repo(name)
|
|
807
805
|
mstore = self._metastore_for_org(org)
|
|
@@ -811,9 +809,7 @@ class AsyncClient:
|
|
|
811
809
|
add_metadata=add_metadata,
|
|
812
810
|
remove_metadata=remove_metadata,
|
|
813
811
|
update_metadata=update_metadata,
|
|
814
|
-
|
|
815
|
-
gc_config=gc_config,
|
|
816
|
-
expiration_config=expiration_config,
|
|
812
|
+
optimization_config=optimization_config,
|
|
817
813
|
)
|
|
818
814
|
|
|
819
815
|
async def delete_repo(self, name: str, *, imsure: bool = False, imreallysure: bool = False) -> None:
|
|
@@ -1276,9 +1272,7 @@ class Client:
|
|
|
1276
1272
|
add_metadata: Optional[RepoMetadataT] = None,
|
|
1277
1273
|
remove_metadata: Optional[list[str]] = None,
|
|
1278
1274
|
update_metadata: Optional[RepoMetadataT] = None,
|
|
1279
|
-
|
|
1280
|
-
gc_config: Optional[GCConfig] = None,
|
|
1281
|
-
expiration_config: Optional[ExpirationConfig] = None,
|
|
1275
|
+
optimization_config: Optional[OptimizationConfig] = None,
|
|
1282
1276
|
) -> None:
|
|
1283
1277
|
"""Modify a repo's metadata or description.
|
|
1284
1278
|
|
|
@@ -1291,9 +1285,7 @@ class Client:
|
|
|
1291
1285
|
remove_metadata: List of metadata keys to remove from the repo.
|
|
1292
1286
|
update_metadata: Optional dictionary of metadata to update on the repo.
|
|
1293
1287
|
Dictionary values can be a scalar (string, int, float, bool, or None) or a list of scalars.
|
|
1294
|
-
|
|
1295
|
-
gc_config: Optional garbage collection configuration for the repo.
|
|
1296
|
-
expiration_config: Optional expiration configuration for the repo.
|
|
1288
|
+
optimization_config: Optional optimization configurations for the repo.
|
|
1297
1289
|
"""
|
|
1298
1290
|
return sync(
|
|
1299
1291
|
self.aclient.modify_repo,
|
|
@@ -1302,9 +1294,7 @@ class Client:
|
|
|
1302
1294
|
add_metadata=add_metadata,
|
|
1303
1295
|
remove_metadata=remove_metadata,
|
|
1304
1296
|
update_metadata=update_metadata,
|
|
1305
|
-
|
|
1306
|
-
gc_config=gc_config,
|
|
1307
|
-
expiration_config=expiration_config,
|
|
1297
|
+
optimization_config=optimization_config,
|
|
1308
1298
|
)
|
|
1309
1299
|
|
|
1310
1300
|
def delete_repo(self, name: str, *, imsure: bool = False, imreallysure: bool = False) -> None:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
1
|
from typing import TYPE_CHECKING, Literal, TypeAlias
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
5
4
|
import zarr
|
|
5
|
+
from pydantic import BaseModel
|
|
6
6
|
|
|
7
7
|
from arraylake.log_util import get_logger
|
|
8
8
|
|
|
@@ -24,8 +24,7 @@ WMS_REQUIRED_CF_KEYS: list[CFAttributeKey] = ["latitude", "longitude"]
|
|
|
24
24
|
REQUIRED_CF_KEYS: list[CFAttributeKey] = EDR_REQUIRED_CF_KEYS + WMS_REQUIRED_CF_KEYS
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
class CFDiagnosisMissing:
|
|
27
|
+
class CFDiagnosisMissing(BaseModel):
|
|
29
28
|
name: CFAttributeKey
|
|
30
29
|
attr_name: CFAttributeName
|
|
31
30
|
required: bool
|
|
@@ -42,8 +41,7 @@ class CFDiagnosisMissing:
|
|
|
42
41
|
return False
|
|
43
42
|
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
class CFDiagnosisInvalid:
|
|
44
|
+
class CFDiagnosisInvalid(BaseModel):
|
|
47
45
|
name: CFAttributeKey
|
|
48
46
|
attr_name: CFAttributeName
|
|
49
47
|
issue: str
|
|
@@ -52,8 +50,7 @@ class CFDiagnosisInvalid:
|
|
|
52
50
|
return hash((self.name, self.issue))
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
class CFDiagnosisSummary:
|
|
53
|
+
class CFDiagnosisSummary(BaseModel):
|
|
57
54
|
found: set[CFAttributeKey] # All of the correct CF keys found
|
|
58
55
|
missing: set[CFDiagnosisMissing]
|
|
59
56
|
invalid: set[CFDiagnosisInvalid]
|
|
@@ -93,35 +90,53 @@ class CFDiagnosisSummary:
|
|
|
93
90
|
return incompatible
|
|
94
91
|
|
|
95
92
|
|
|
96
|
-
def _guess_cf_axes_and_coords(ds: "Dataset") -> "Dataset":
|
|
93
|
+
def _guess_cf_axes_and_coords(ds: "Dataset", interactive: bool = True) -> "Dataset":
|
|
97
94
|
import cf_xarray # noqa
|
|
98
95
|
|
|
99
96
|
new_ds = ds.cf.guess_coord_axis()
|
|
100
97
|
cf_rep = new_ds.cf
|
|
101
98
|
coords = cf_rep.coordinates
|
|
102
99
|
axes = cf_rep.axes
|
|
100
|
+
|
|
101
|
+
# In non-interactive mode (server context), make reasonable assumptions
|
|
103
102
|
if "longitude" not in coords and "X" in axes:
|
|
104
103
|
if len(axes["X"]) == 1 and any(key not in new_ds[axes["X"][0]].attrs for key in ["standard_name", "units"]):
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if interactive:
|
|
105
|
+
message = f"Does the X axis ({axes['X'][0]!r}) represent longitude? (y/n): "
|
|
106
|
+
modify_x = input(message).lower() == "y"
|
|
107
|
+
else:
|
|
108
|
+
# In server mode, assume X axis represents longitude if it's missing standard_name
|
|
109
|
+
modify_x = "standard_name" not in new_ds[axes["X"][0]].attrs
|
|
107
110
|
if modify_x:
|
|
108
111
|
new_ds[axes["X"][0]].attrs["standard_name"] = "longitude"
|
|
109
112
|
if "latitude" not in coords and "Y" in axes:
|
|
110
113
|
if len(axes["Y"]) == 1 and any(key not in new_ds[axes["Y"][0]].attrs for key in ["standard_name", "units"]):
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
if interactive:
|
|
115
|
+
message = f"Does the Y axis ({axes['Y'][0]!r}) represent latitude? (y/n): "
|
|
116
|
+
modify_y = input(message).lower() == "y"
|
|
117
|
+
else:
|
|
118
|
+
# In server mode, assume Y axis represents latitude if it's missing standard_name
|
|
119
|
+
modify_y = "standard_name" not in new_ds[axes["Y"][0]].attrs
|
|
113
120
|
if modify_y:
|
|
114
121
|
new_ds[axes["Y"][0]].attrs["standard_name"] = "latitude"
|
|
115
122
|
if "vertical" not in coords and "Z" in axes:
|
|
116
123
|
if len(axes["Z"]) == 1 and any(key not in new_ds[axes["Z"][0]].attrs for key in ["standard_name", "units"]):
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
if interactive:
|
|
125
|
+
message = f"Does the Z axis ({axes['Z'][0]!r}) represent vertical? (y/n): "
|
|
126
|
+
modify_z = input(message).lower() == "y"
|
|
127
|
+
else:
|
|
128
|
+
# In server mode, assume Z axis represents vertical if it's missing standard_name
|
|
129
|
+
modify_z = "standard_name" not in new_ds[axes["Z"][0]].attrs
|
|
119
130
|
if modify_z:
|
|
120
131
|
new_ds[axes["Z"][0]].attrs["standard_name"] = "vertical"
|
|
121
132
|
if "time" not in coords and "T" in axes:
|
|
122
133
|
if len(axes["T"]) == 1 and any(key not in new_ds[axes["T"][0]].attrs for key in ["standard_name", "units"]):
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
if interactive:
|
|
135
|
+
message = f"Does the T axis ({axes['T'][0]!r}) represent time? (y/n): "
|
|
136
|
+
modify_t = input(message).lower() == "y"
|
|
137
|
+
else:
|
|
138
|
+
# In server mode, assume T axis represents time if it's missing standard_name
|
|
139
|
+
modify_t = "standard_name" not in new_ds[axes["T"][0]].attrs
|
|
125
140
|
if modify_t:
|
|
126
141
|
new_ds[axes["T"][0]].attrs["standard_name"] = "time"
|
|
127
142
|
|
|
@@ -136,11 +151,12 @@ def _is_wms_compatible(keys: set[CFAttributeKey]) -> bool:
|
|
|
136
151
|
return all(k in keys for k in WMS_REQUIRED_CF_KEYS)
|
|
137
152
|
|
|
138
153
|
|
|
139
|
-
def check_cf_attribute_completeness(ds: "Dataset") -> CFDiagnosisSummary:
|
|
154
|
+
def check_cf_attribute_completeness(ds: "Dataset", interactive: bool = True) -> CFDiagnosisSummary:
|
|
140
155
|
"""Returns a dictionary of the CF compliance errors in the provided dataset.
|
|
141
156
|
|
|
142
157
|
Args:
|
|
143
158
|
ds: The xarray dataset to check.
|
|
159
|
+
interactive: Whether to prompt user for input when guessing CF attributes.
|
|
144
160
|
|
|
145
161
|
Returns:
|
|
146
162
|
dict: A diagnostic dictionary containing the found, missing, and invalid CF compliance errors.
|
|
@@ -151,7 +167,7 @@ def check_cf_attribute_completeness(ds: "Dataset") -> CFDiagnosisSummary:
|
|
|
151
167
|
found, missing, invalid = set(), set(), set()
|
|
152
168
|
|
|
153
169
|
# Guess the axes, to use for fixing missing attributes
|
|
154
|
-
proposed_fix_ds = _guess_cf_axes_and_coords(ds)
|
|
170
|
+
proposed_fix_ds = _guess_cf_axes_and_coords(ds, interactive=interactive)
|
|
155
171
|
|
|
156
172
|
for k in SPATIAL_CF_KEYS:
|
|
157
173
|
if k in cf_keys:
|
|
@@ -48,6 +48,7 @@ def _get_icechunk_storage_obj(
|
|
|
48
48
|
credential_type: CredentialType,
|
|
49
49
|
credentials: TempCredentials | None,
|
|
50
50
|
credential_refresh_func: Callable | None,
|
|
51
|
+
scatter_initial_credentials: bool,
|
|
51
52
|
): # Removed output type so icechunk import is not required
|
|
52
53
|
"""Gets the Icechunk storage object.
|
|
53
54
|
|
|
@@ -64,6 +65,10 @@ def _get_icechunk_storage_obj(
|
|
|
64
65
|
Optional function to refresh credentials. This function must
|
|
65
66
|
be synchronous, cannot take in any args, and return a
|
|
66
67
|
icechunk.S3StaticCredentials or icechunk.GcsBearerCredential object.
|
|
68
|
+
scatter_initial_credentials:
|
|
69
|
+
Whether to immediately call and store the value returned by get_credentials.
|
|
70
|
+
Ensures that the same set of credentials are available for all workers in a
|
|
71
|
+
distributed setup.
|
|
67
72
|
|
|
68
73
|
Returns:
|
|
69
74
|
Icechunk Storage object
|
|
@@ -112,6 +117,7 @@ def _get_icechunk_storage_obj(
|
|
|
112
117
|
anonymous=credential_type == CredentialType.ANONYMOUS,
|
|
113
118
|
from_env=credential_type == CredentialType.ENV,
|
|
114
119
|
get_credentials=credential_refresh_func,
|
|
120
|
+
scatter_initial_credentials=scatter_initial_credentials,
|
|
115
121
|
)
|
|
116
122
|
# Use s3_storage to create the storage object for s3 or s3-compatible buckets
|
|
117
123
|
return icechunk.s3_storage(
|
|
@@ -128,6 +134,7 @@ def _get_icechunk_storage_obj(
|
|
|
128
134
|
from_env=credential_type == CredentialType.ENV,
|
|
129
135
|
get_credentials=credential_refresh_func,
|
|
130
136
|
force_path_style=bool(force_path_style), # mypy thinks force_path_style could be a str
|
|
137
|
+
scatter_initial_credentials=scatter_initial_credentials,
|
|
131
138
|
)
|
|
132
139
|
# Otherwise, check if the bucket is a GCS bucket
|
|
133
140
|
elif bucket_config.platform in ("gs"):
|
|
@@ -145,6 +152,7 @@ def _get_icechunk_storage_obj(
|
|
|
145
152
|
from_env=credential_type == CredentialType.ENV,
|
|
146
153
|
config=None,
|
|
147
154
|
get_credentials=credential_refresh_func,
|
|
155
|
+
scatter_initial_credentials=scatter_initial_credentials,
|
|
148
156
|
)
|
|
149
157
|
else:
|
|
150
158
|
raise ValueError(f"Unsupported bucket platform: {bucket_config.platform}")
|
|
@@ -39,12 +39,10 @@ from arraylake.types import (
|
|
|
39
39
|
ApiClientResponse,
|
|
40
40
|
BucketModifyRequest,
|
|
41
41
|
BucketResponse,
|
|
42
|
-
ExpirationConfig,
|
|
43
|
-
GCConfig,
|
|
44
42
|
GSCredentials,
|
|
45
|
-
MaintenanceWindow,
|
|
46
43
|
NewBucket,
|
|
47
44
|
NewRepoOperationStatus,
|
|
45
|
+
OptimizationConfig,
|
|
48
46
|
OrgActions,
|
|
49
47
|
PermissionBody,
|
|
50
48
|
PermissionCheckResponse,
|
|
@@ -204,18 +202,14 @@ class HttpMetastore(ArraylakeHttpClient, Metastore):
|
|
|
204
202
|
add_metadata: RepoMetadataT | None = None,
|
|
205
203
|
remove_metadata: list[str] | None = None,
|
|
206
204
|
update_metadata: RepoMetadataT | None = None,
|
|
207
|
-
|
|
208
|
-
gc_config: GCConfig | None = None,
|
|
209
|
-
expiration_config: ExpirationConfig | None = None,
|
|
205
|
+
optimization_config: OptimizationConfig | None = None,
|
|
210
206
|
) -> None:
|
|
211
207
|
repo_modify_request = RepoModifyRequest(
|
|
212
208
|
description=description,
|
|
213
209
|
add_metadata=add_metadata,
|
|
214
210
|
remove_metadata=remove_metadata,
|
|
215
211
|
update_metadata=update_metadata,
|
|
216
|
-
|
|
217
|
-
gc_config=gc_config,
|
|
218
|
-
expiration_config=expiration_config,
|
|
212
|
+
optimization_config=optimization_config,
|
|
219
213
|
)
|
|
220
214
|
response = await self._request("PATCH", f"/orgs/{self._config.org}/{name}", content=repo_modify_request.model_dump_json())
|
|
221
215
|
handle_response(response)
|
|
@@ -100,17 +100,13 @@ class RepoModifyRequest(BaseModel):
|
|
|
100
100
|
add_metadata: RepoMetadataT | None = None
|
|
101
101
|
remove_metadata: list[str] | None = None
|
|
102
102
|
update_metadata: RepoMetadataT | None = None
|
|
103
|
-
|
|
104
|
-
expiration_config: ExpirationConfig | None = None
|
|
105
|
-
maintenance_window: MaintenanceWindow | None = None
|
|
103
|
+
optimization_config: OptimizationConfig | None = None
|
|
106
104
|
|
|
107
105
|
|
|
108
106
|
class RepoModifyBody(BaseModel):
|
|
109
107
|
description: str | None = None
|
|
110
108
|
metadata: RepoMetadataT | None = None
|
|
111
|
-
|
|
112
|
-
expiration_config: ExpirationConfig | None = None
|
|
113
|
-
maintenance_window: MaintenanceWindow | None = None
|
|
109
|
+
optimization_config: Optional[OptimizationConfig] = None
|
|
114
110
|
|
|
115
111
|
|
|
116
112
|
# Used for `/orgs/{org}/{repo}/visibility` endpoint
|
|
@@ -392,13 +388,27 @@ class GCConfig(BaseModel):
|
|
|
392
388
|
|
|
393
389
|
@field_serializer("dangling_chunks", "dangling_manifests", "dangling_attributes", "dangling_transaction_logs", "dangling_snapshots")
|
|
394
390
|
@classmethod
|
|
395
|
-
def
|
|
391
|
+
def serialize_dangling(self, value: GCAction) -> float | None:
|
|
396
392
|
if isinstance(value, GCKeep):
|
|
397
393
|
return None
|
|
398
394
|
else:
|
|
399
395
|
assert isinstance(value, GCDeleteOlderThan)
|
|
400
396
|
return value.date.total_seconds()
|
|
401
397
|
|
|
398
|
+
@field_validator("gc_every", mode="before")
|
|
399
|
+
@classmethod
|
|
400
|
+
def parse_gc_every(cls, v: Any) -> timedelta | None:
|
|
401
|
+
if v is not None:
|
|
402
|
+
return timedelta_from_seconds(v)
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
@field_serializer("gc_every")
|
|
406
|
+
@classmethod
|
|
407
|
+
def serialize_gc_every(self, value: timedelta | None) -> float | None:
|
|
408
|
+
if value is not None:
|
|
409
|
+
return value.total_seconds()
|
|
410
|
+
return None
|
|
411
|
+
|
|
402
412
|
|
|
403
413
|
class ExpirationConfig(BaseModel):
|
|
404
414
|
"""Configuration for expiration"""
|
|
@@ -416,14 +426,14 @@ class ExpirationConfig(BaseModel):
|
|
|
416
426
|
|
|
417
427
|
@field_serializer("expire_every", "expire_versions_older_than")
|
|
418
428
|
@classmethod
|
|
419
|
-
def
|
|
429
|
+
def serialize_expire_every(self, value: timedelta | None) -> float | None:
|
|
420
430
|
if value is not None:
|
|
421
431
|
return value.total_seconds()
|
|
422
432
|
return None
|
|
423
433
|
|
|
424
434
|
|
|
425
|
-
class
|
|
426
|
-
"""The preferred time to run
|
|
435
|
+
class OptimizationWindow(BaseModel):
|
|
436
|
+
"""The preferred time to run optimization tasks on a repo"""
|
|
427
437
|
|
|
428
438
|
duration: timedelta
|
|
429
439
|
start_time: time # Time of day in UTC
|
|
@@ -440,6 +450,14 @@ class MaintenanceWindow(BaseModel):
|
|
|
440
450
|
return duration.total_seconds()
|
|
441
451
|
|
|
442
452
|
|
|
453
|
+
class OptimizationConfig(BaseModel):
|
|
454
|
+
"""High-level config for repo optimizations"""
|
|
455
|
+
|
|
456
|
+
window: Optional[OptimizationWindow] = None # If None, uses the org default
|
|
457
|
+
gc_config: Optional[GCConfig] = None
|
|
458
|
+
expiration_config: Optional[ExpirationConfig] = None
|
|
459
|
+
|
|
460
|
+
|
|
443
461
|
class Repo(ModelWithID):
|
|
444
462
|
org: str
|
|
445
463
|
name: str
|
|
@@ -453,9 +471,13 @@ class Repo(ModelWithID):
|
|
|
453
471
|
status: RepoOperationStatusResponse
|
|
454
472
|
kind: RepoKind = RepoKind.V1
|
|
455
473
|
prefix: Optional[str] = None
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
474
|
+
optimization_config: OptimizationConfig = Field(
|
|
475
|
+
default_factory=lambda: OptimizationConfig(
|
|
476
|
+
window=None,
|
|
477
|
+
gc_config=None,
|
|
478
|
+
expiration_config=None,
|
|
479
|
+
)
|
|
480
|
+
)
|
|
459
481
|
permissions: list[Permission[RepoActions]] = Field(default_factory=list)
|
|
460
482
|
|
|
461
483
|
def _asdict(self):
|
|
@@ -87,11 +87,11 @@ maximal = [
|
|
|
87
87
|
"xarray >= 2024.10.0",
|
|
88
88
|
]
|
|
89
89
|
icechunk = [
|
|
90
|
-
"icechunk >= 0.2.
|
|
90
|
+
"icechunk >= 0.2.15, < 0.3.0"
|
|
91
91
|
]
|
|
92
92
|
# TODO: move to dependency group once pip supports it
|
|
93
93
|
minicechunk = [
|
|
94
|
-
"icechunk==0.2.
|
|
94
|
+
"icechunk==0.2.15",
|
|
95
95
|
"numpy==1.25", # min zarr 3 numpy
|
|
96
96
|
"zarr==3.0.0",
|
|
97
97
|
]
|
|
@@ -7,6 +7,7 @@ import zarr
|
|
|
7
7
|
from icechunk import IcechunkError, Repository, S3Credentials, Storage
|
|
8
8
|
|
|
9
9
|
from arraylake import AsyncClient, Client
|
|
10
|
+
from arraylake.config import config
|
|
10
11
|
from arraylake.types import DBID
|
|
11
12
|
from arraylake.types import Repo as RepoModel
|
|
12
13
|
from arraylake.types import RepoKind, RepoOperationMode, RepoOperationStatusResponse
|
|
@@ -197,7 +198,8 @@ async def test_get_icechunk_storage_from_repo_model(isolated_org_with_bucket, to
|
|
|
197
198
|
status=RepoOperationStatusResponse(mode=RepoOperationMode.ONLINE, initiated_by={}),
|
|
198
199
|
bucket=bucket_config,
|
|
199
200
|
)
|
|
200
|
-
|
|
201
|
+
with config.set({"icechunk.scatter_initial_credentials": False}):
|
|
202
|
+
storage = await aclient._get_icechunk_storage_from_repo_model(repo_model=repo_model)
|
|
201
203
|
assert isinstance(storage, Storage)
|
|
202
204
|
|
|
203
205
|
|
|
@@ -31,6 +31,7 @@ def test_get_icechunk_storage_obj_s3_credentials(
|
|
|
31
31
|
credentials=s3_credentials,
|
|
32
32
|
credential_type=CredentialType.PROVIDED,
|
|
33
33
|
credential_refresh_func=None,
|
|
34
|
+
scatter_initial_credentials=False,
|
|
34
35
|
)
|
|
35
36
|
assert isinstance(storage, icechunk.Storage)
|
|
36
37
|
|
|
@@ -45,6 +46,7 @@ def test_get_icechunk_storage_obj_gcs_credentials(
|
|
|
45
46
|
credentials=gs_credentials,
|
|
46
47
|
credential_type=CredentialType.PROVIDED,
|
|
47
48
|
credential_refresh_func=None,
|
|
49
|
+
scatter_initial_credentials=False,
|
|
48
50
|
)
|
|
49
51
|
assert isinstance(storage, icechunk.Storage)
|
|
50
52
|
|
|
@@ -54,7 +56,12 @@ def test_get_icechunk_storage_obj_from_env(s3_bucket_config: BucketResponse, mon
|
|
|
54
56
|
monkeypatch.setenv(var, "test")
|
|
55
57
|
|
|
56
58
|
storage = _get_icechunk_storage_obj(
|
|
57
|
-
bucket_config=s3_bucket_config,
|
|
59
|
+
bucket_config=s3_bucket_config,
|
|
60
|
+
prefix=None,
|
|
61
|
+
credentials=None,
|
|
62
|
+
credential_type=CredentialType.ENV,
|
|
63
|
+
credential_refresh_func=None,
|
|
64
|
+
scatter_initial_credentials=False,
|
|
58
65
|
)
|
|
59
66
|
assert isinstance(storage, icechunk.Storage)
|
|
60
67
|
|
|
@@ -70,6 +77,7 @@ def test_get_icechunk_storage_obj_s3_no_credentials(s3_bucket_config: BucketResp
|
|
|
70
77
|
credentials=None,
|
|
71
78
|
credential_type=CredentialType.ANONYMOUS,
|
|
72
79
|
credential_refresh_func=None,
|
|
80
|
+
scatter_initial_credentials=False,
|
|
73
81
|
)
|
|
74
82
|
assert isinstance(storage, icechunk.Storage)
|
|
75
83
|
|
|
@@ -81,6 +89,7 @@ def test_get_icechunk_storage_obj_tigris_no_credentials(tigris_bucket_config: Bu
|
|
|
81
89
|
credentials=None,
|
|
82
90
|
credential_type=CredentialType.ANONYMOUS,
|
|
83
91
|
credential_refresh_func=None,
|
|
92
|
+
scatter_initial_credentials=False,
|
|
84
93
|
)
|
|
85
94
|
assert isinstance(storage, icechunk.Storage)
|
|
86
95
|
|
|
@@ -95,6 +104,7 @@ def test_get_icechunk_storage_obj_tigris_no_region(tigris_bucket_config: BucketR
|
|
|
95
104
|
credentials=None,
|
|
96
105
|
credential_type=CredentialType.ENV,
|
|
97
106
|
credential_refresh_func=None,
|
|
107
|
+
scatter_initial_credentials=False,
|
|
98
108
|
)
|
|
99
109
|
assert "Region cannot be None for Tigris buckets" in str(e.value)
|
|
100
110
|
|
|
@@ -109,13 +119,19 @@ def test_get_icechunk_storage_obj_tigris_auto_region(tigris_bucket_config: Bucke
|
|
|
109
119
|
credentials=None,
|
|
110
120
|
credential_type=CredentialType.ENV,
|
|
111
121
|
credential_refresh_func=None,
|
|
122
|
+
scatter_initial_credentials=False,
|
|
112
123
|
)
|
|
113
124
|
assert "Region cannot be auto for Tigris buckets" in str(e.value)
|
|
114
125
|
|
|
115
126
|
|
|
116
127
|
def test_get_icechunk_storage_obj_gcs_no_credentials(gcs_bucket_config: BucketResponse):
|
|
117
128
|
storage = _get_icechunk_storage_obj(
|
|
118
|
-
bucket_config=gcs_bucket_config,
|
|
129
|
+
bucket_config=gcs_bucket_config,
|
|
130
|
+
prefix=None,
|
|
131
|
+
credentials=None,
|
|
132
|
+
credential_type=CredentialType.ENV,
|
|
133
|
+
credential_refresh_func=None,
|
|
134
|
+
scatter_initial_credentials=False,
|
|
119
135
|
)
|
|
120
136
|
assert isinstance(storage, icechunk.Storage)
|
|
121
137
|
|
|
@@ -130,5 +146,6 @@ def test_get_icechunk_storage_obj_invalid_credential_input(
|
|
|
130
146
|
credentials=s3_credentials,
|
|
131
147
|
credential_type=CredentialType.PROVIDED,
|
|
132
148
|
credential_refresh_func=credential_refresh_func,
|
|
149
|
+
scatter_initial_credentials=True,
|
|
133
150
|
)
|
|
134
151
|
assert "Cannot provide both static credentials and a credential refresh function" in str(e.value)
|
|
@@ -25,7 +25,8 @@ from arraylake.types import (
|
|
|
25
25
|
GCConfig,
|
|
26
26
|
GCDeleteOlderThan,
|
|
27
27
|
GCKeep,
|
|
28
|
-
|
|
28
|
+
OptimizationConfig,
|
|
29
|
+
OptimizationWindow,
|
|
29
30
|
RepoKind,
|
|
30
31
|
UserInfo,
|
|
31
32
|
)
|
|
@@ -187,7 +188,7 @@ async def test_modify_repo(token, isolated_org_with_bucket, repo_kind_to_test: R
|
|
|
187
188
|
|
|
188
189
|
# Modify metadata
|
|
189
190
|
new_description = "This is a modified test repo"
|
|
190
|
-
|
|
191
|
+
optimization_window = OptimizationWindow(
|
|
191
192
|
duration=3600, # 1 hour
|
|
192
193
|
start_time=dt_time(2, 0), # 2:00 AM UTC
|
|
193
194
|
day_of_week=1, # Monday
|
|
@@ -207,6 +208,11 @@ async def test_modify_repo(token, isolated_org_with_bucket, repo_kind_to_test: R
|
|
|
207
208
|
expire_every=None,
|
|
208
209
|
enabled=True,
|
|
209
210
|
)
|
|
211
|
+
optimization_config = OptimizationConfig(
|
|
212
|
+
expiration_config=expiration_config,
|
|
213
|
+
gc_config=gc_config,
|
|
214
|
+
window=optimization_window,
|
|
215
|
+
)
|
|
210
216
|
add_metadata = {"legume": "soybean", "fats": "butter"}
|
|
211
217
|
remove_metadata = ["dairy"]
|
|
212
218
|
update_metadata = {"fruits": ["pear", "kiwi"], "vegetable": ["broccoli", "carrot"], "healthy": False}
|
|
@@ -225,9 +231,7 @@ async def test_modify_repo(token, isolated_org_with_bucket, repo_kind_to_test: R
|
|
|
225
231
|
add_metadata=add_metadata,
|
|
226
232
|
remove_metadata=remove_metadata,
|
|
227
233
|
update_metadata=update_metadata,
|
|
228
|
-
|
|
229
|
-
gc_config=gc_config,
|
|
230
|
-
expiration_config=expiration_config,
|
|
234
|
+
optimization_config=optimization_config,
|
|
231
235
|
)
|
|
232
236
|
updated_repo_obj = await aclient.get_repo_object(repo_name)
|
|
233
237
|
assert updated_repo_obj.metadata.keys() == expected_metadata.keys()
|
|
@@ -238,9 +242,9 @@ async def test_modify_repo(token, isolated_org_with_bucket, repo_kind_to_test: R
|
|
|
238
242
|
assert updated_repo_obj.metadata[key] == value
|
|
239
243
|
assert updated_repo_obj.description == new_description
|
|
240
244
|
assert updated_repo_obj.updated > current_repo_obj.updated
|
|
241
|
-
assert updated_repo_obj.
|
|
242
|
-
assert updated_repo_obj.gc_config == gc_config
|
|
243
|
-
assert updated_repo_obj.expiration_config == expiration_config
|
|
245
|
+
assert updated_repo_obj.optimization_config.window == optimization_window
|
|
246
|
+
assert updated_repo_obj.optimization_config.gc_config == gc_config
|
|
247
|
+
assert updated_repo_obj.optimization_config.expiration_config == expiration_config
|
|
244
248
|
|
|
245
249
|
|
|
246
250
|
def test_modify_repo_raises(token, isolated_org_with_bucket, repo_kind_to_test: RepoKind) -> None:
|
|
@@ -284,9 +288,9 @@ async def test_modify_repo_no_change(token, isolated_org_with_bucket, repo_kind_
|
|
|
284
288
|
assert updated_repo_obj.metadata == metadata
|
|
285
289
|
assert updated_repo_obj.description is None
|
|
286
290
|
assert updated_repo_obj.updated == current_repo_obj.updated
|
|
287
|
-
assert updated_repo_obj.
|
|
288
|
-
assert updated_repo_obj.gc_config is None
|
|
289
|
-
assert updated_repo_obj.expiration_config is None
|
|
291
|
+
assert updated_repo_obj.optimization_config.window is None
|
|
292
|
+
assert updated_repo_obj.optimization_config.gc_config is None
|
|
293
|
+
assert updated_repo_obj.optimization_config.expiration_config is None
|
|
290
294
|
|
|
291
295
|
|
|
292
296
|
def test_list_repos_filter_metadata(token, isolated_org_with_bucket, repo_kind_to_test: RepoKind):
|
|
@@ -19,3 +19,4 @@ def test_config_defaults_set():
|
|
|
19
19
|
assert "verify" in arraylake.config.defaults[0]["service"]["ssl"]
|
|
20
20
|
assert "hash_method" in arraylake.config.defaults[0]["chunkstore"]
|
|
21
21
|
assert "inline_threshold_bytes" in arraylake.config.defaults[0]["chunkstore"]
|
|
22
|
+
assert "scatter_initial_credentials" in arraylake.config.defaults[0]["icechunk"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|