arraylake 1.0.1__tar.gz → 1.0.3a1__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-1.0.1 → arraylake-1.0.3a1}/.gitignore +1 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/PKG-INFO +3 -1
- arraylake-1.0.3a1/arraylake/_credential_cache.py +86 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/_version.py +2 -2
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/client.py +330 -59
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/metastore/http_metastore.py +21 -10
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/types.py +109 -1
- {arraylake-1.0.1 → arraylake-1.0.3a1}/pyproject.toml +7 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_client.py +123 -22
- arraylake-1.0.3a1/tests/test_credential_cache.py +237 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/uv.lock +256 -348
- {arraylake-1.0.1 → arraylake-1.0.3a1}/README.md +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/__main__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/api_utils.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/asyn.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/auth.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/compute.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/config.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/main.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/repo.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/cli/utils.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/compute/doctor.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/compute/http_client.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/compute/services.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/compute/types.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/config.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/config.yaml +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/credentials.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/diagnostics.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/display/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/display/repo.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/display/repolist.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/exceptions.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/log_util.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/metastore/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/metastore/abc.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/py.typed +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/repos/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/repos/icechunk/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/repos/icechunk/storage.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/repos/icechunk/types.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/repos/icechunk/virtual.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/token.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/arraylake/tuning.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/compute/test_client.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/compute/test_doctor.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/config.yaml +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/conftest.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/icechunk/__init__.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/icechunk/conftest.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/icechunk/test_storage.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/icechunk/test_virtual.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_api_utils.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_asyn.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_cli.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_config.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_credentials.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_diagnostics.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_performance.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_subscriptions.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_token_handler.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_types.py +0 -0
- {arraylake-1.0.1 → arraylake-1.0.3a1}/tests/test_virtual_chunks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraylake
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3a1
|
|
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
|
|
@@ -27,6 +27,8 @@ 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
29
|
Requires-Dist: xarray[accel,etc,io,parallel]>=2024.10.0; extra == 'maximal'
|
|
30
|
+
Provides-Extra: obstore
|
|
31
|
+
Requires-Dist: obstore>=0.9.2; extra == 'obstore'
|
|
30
32
|
Provides-Extra: xarray
|
|
31
33
|
Requires-Dist: cf-xarray>=0.10.4; extra == 'xarray'
|
|
32
34
|
Requires-Dist: xarray>=2024.10.0; extra == 'xarray'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Process-wide cache for delegated bucket credentials.
|
|
2
|
+
|
|
3
|
+
When users pickle an icechunk Session and ship it to many workers, every
|
|
4
|
+
unpickled task can independently call the arraylake server for fresh
|
|
5
|
+
credentials when the embedded ones expire. This module gives the client a
|
|
6
|
+
single per-process cache so all consumers in a worker process share one
|
|
7
|
+
in-flight refresh.
|
|
8
|
+
|
|
9
|
+
Validity follows ``Cache-Control: max-age`` semantics: cached creds are
|
|
10
|
+
served while ``expires_at - CLOCK_SKEW_SAFETY > now``. The 30s safety only
|
|
11
|
+
protects against worker/server NTP-level clock drift; it is not a
|
|
12
|
+
pre-refresh window.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
from collections.abc import Awaitable, Callable
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import UTC, datetime, timedelta
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
from arraylake.asyn import get_background_loop
|
|
24
|
+
from arraylake.log_util import get_logger
|
|
25
|
+
from arraylake.types import OrgName, Platform, TempCredentials
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
CLOCK_SKEW_SAFETY = timedelta(seconds=30)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(eq=True, frozen=True)
|
|
34
|
+
class CredentialCacheKey:
|
|
35
|
+
api_url: str
|
|
36
|
+
auth_key: int
|
|
37
|
+
scope: Literal["repo", "bucket"]
|
|
38
|
+
org: OrgName
|
|
39
|
+
identifier: str
|
|
40
|
+
platform: Platform
|
|
41
|
+
access: Literal["read", "write"] = "read"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class _Entry:
|
|
46
|
+
lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
|
47
|
+
creds: TempCredentials | None = None
|
|
48
|
+
expires_at: datetime | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_CACHE: dict[CredentialCacheKey, _Entry] = {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def get_or_refresh(
|
|
55
|
+
key: CredentialCacheKey,
|
|
56
|
+
fetcher: Callable[[], Awaitable[TempCredentials]],
|
|
57
|
+
*,
|
|
58
|
+
use_cache: bool = True,
|
|
59
|
+
) -> TempCredentials:
|
|
60
|
+
assert asyncio.get_running_loop() is get_background_loop(), "credential cache must be used from the arraylake background loop"
|
|
61
|
+
if not use_cache:
|
|
62
|
+
return await fetcher()
|
|
63
|
+
entry = _CACHE.setdefault(key, _Entry())
|
|
64
|
+
async with entry.lock:
|
|
65
|
+
now = datetime.now(UTC)
|
|
66
|
+
if entry.creds is not None and entry.expires_at is not None and entry.expires_at - CLOCK_SKEW_SAFETY > now:
|
|
67
|
+
return entry.creds
|
|
68
|
+
creds = await fetcher()
|
|
69
|
+
exp = creds.expiration
|
|
70
|
+
if exp is not None and exp.tzinfo is None:
|
|
71
|
+
exp = exp.replace(tzinfo=UTC)
|
|
72
|
+
if exp is not None and exp - CLOCK_SKEW_SAFETY <= now:
|
|
73
|
+
logger.warning(
|
|
74
|
+
"delegated credentials returned with lifetime <= clock-skew safety; server contract violation",
|
|
75
|
+
remaining_seconds=(exp - now).total_seconds(),
|
|
76
|
+
api_url=key.api_url,
|
|
77
|
+
org=key.org,
|
|
78
|
+
identifier=key.identifier,
|
|
79
|
+
)
|
|
80
|
+
entry.creds = creds
|
|
81
|
+
entry.expires_at = exp
|
|
82
|
+
return creds
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def clear() -> None:
|
|
86
|
+
_CACHE.clear()
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.3a1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 3, 'a1')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|