arraylake 1.5.0__tar.gz → 1.6.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-1.5.0 → arraylake-1.6.0}/PKG-INFO +2 -2
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/_version.py +2 -2
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/types.py +57 -12
- {arraylake-1.5.0 → arraylake-1.6.0}/pyproject.toml +5 -2
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_subscriptions.py +11 -11
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_types.py +12 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_virtual_chunks.py +3 -3
- {arraylake-1.5.0 → arraylake-1.6.0}/uv.lock +15 -10
- {arraylake-1.5.0 → arraylake-1.6.0}/.gitignore +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/README.md +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/__main__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/_credential_cache.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/api_utils.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/asyn.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/auth.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/compute.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/config.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/iceberg.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/main.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/org.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/repo.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/skills.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/utils.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/client.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/http_client.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/services.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/types.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/config.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/config.yaml +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/credentials.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/diagnostics.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/repo.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/repolist.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/exceptions.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/log_util.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/abc.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/http_metastore.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/obstore.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/py.typed +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/auth.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/catalog.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/storage.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/types.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/virtual.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/token.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/tuning.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/compute/test_cli_table.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/compute/test_client.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/config.yaml +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/conftest.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/__init__.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/conftest.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/test_storage.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/test_virtual.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_api_utils.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_asyn.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_cli.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_client.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_config.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_credential_cache.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_credentials.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_diagnostics.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_http_metastore.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_iceberg_client.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_obstore.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_performance.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_skills.py +0 -0
- {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_token_handler.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: arraylake
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -25,7 +25,7 @@ Requires-Dist: structlog<27.0,>=24.1
|
|
|
25
25
|
Requires-Dist: typer<1.0,>=0.12.0
|
|
26
26
|
Requires-Dist: zarr<4.0,>=3.1.0
|
|
27
27
|
Provides-Extra: iceberg
|
|
28
|
-
Requires-Dist: pyiceberg<0.13,>=0.11; extra == 'iceberg'
|
|
28
|
+
Requires-Dist: pyiceberg[pyarrow]<0.13,>=0.11; extra == 'iceberg'
|
|
29
29
|
Provides-Extra: maximal
|
|
30
30
|
Requires-Dist: xarray[accel,etc,io,parallel]>=2024.10.0; extra == 'maximal'
|
|
31
31
|
Provides-Extra: obstore
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 6, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
|
|
|
17
17
|
from obstore.store import AzureConfig, ClientConfig, GCSCredential, S3Config
|
|
18
18
|
from pydantic import (
|
|
19
19
|
AfterValidator,
|
|
20
|
+
AliasChoices,
|
|
20
21
|
BaseModel,
|
|
21
22
|
ConfigDict,
|
|
22
23
|
EmailStr,
|
|
@@ -39,6 +40,30 @@ VALID_NAME = r"(\w[\w\.\-_]+)"
|
|
|
39
40
|
# AWS STS session tag values are limited to 256 characters. Repo names are passed as the
|
|
40
41
|
# AlRepoName tag value in STS AssumeRole calls, so we cap well below that.
|
|
41
42
|
MAX_REPO_NAME_LENGTH = 128
|
|
43
|
+
# /{org}/{repo} URLs lose to these app routes: static children of app/src/app/(pages)/[org]/
|
|
44
|
+
# (route groups flattened) plus the /:org/<x> redirect sources in app/next.config.js.
|
|
45
|
+
RESERVED_REPO_NAMES = frozenset(
|
|
46
|
+
{
|
|
47
|
+
"access",
|
|
48
|
+
"analytics",
|
|
49
|
+
"billing",
|
|
50
|
+
"buckets",
|
|
51
|
+
"dap2",
|
|
52
|
+
"edr",
|
|
53
|
+
"exchange",
|
|
54
|
+
"metrics",
|
|
55
|
+
"optimization",
|
|
56
|
+
"publish",
|
|
57
|
+
"repos",
|
|
58
|
+
"repositories",
|
|
59
|
+
"services",
|
|
60
|
+
"settings",
|
|
61
|
+
"sql",
|
|
62
|
+
"tables",
|
|
63
|
+
"tiles",
|
|
64
|
+
"virtual-chunks",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
42
67
|
# Bucket prefixes are embedded (×3) in AWS session policy JSON (2048-char limit) and GCP
|
|
43
68
|
# CAB CEL expressions. 256 keeps worst-case total prefix comfortably within budget.
|
|
44
69
|
MAX_BUCKET_PREFIX_LENGTH = 256
|
|
@@ -99,6 +124,18 @@ RepoName: TypeAlias = Annotated[str, BeforeValidator(NameValidator(entity="repo"
|
|
|
99
124
|
TeamName: TypeAlias = Annotated[str, BeforeValidator(NameValidator(entity="team"))]
|
|
100
125
|
|
|
101
126
|
|
|
127
|
+
def validate_new_repo_name(v: str) -> str:
|
|
128
|
+
if len(v) > MAX_REPO_NAME_LENGTH:
|
|
129
|
+
raise ValueError(f"Repo name must be at most {MAX_REPO_NAME_LENGTH} characters long. Got {len(v)}.")
|
|
130
|
+
if v.lower() in RESERVED_REPO_NAMES:
|
|
131
|
+
raise ValueError(f"Repo name {v!r} is reserved by the web app.")
|
|
132
|
+
return v
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Create-only rules; RepoName stays lenient so existing repos remain readable.
|
|
136
|
+
NewRepoName: TypeAlias = Annotated[RepoName, AfterValidator(validate_new_repo_name)]
|
|
137
|
+
|
|
138
|
+
|
|
102
139
|
def validate_org_and_repo_name(org_and_repo_name: str) -> str:
|
|
103
140
|
expr = f"{VALID_NAME}/{VALID_NAME}"
|
|
104
141
|
res = re.fullmatch(expr, org_and_repo_name)
|
|
@@ -300,19 +337,12 @@ class SubscriptionPricingType(StrEnum):
|
|
|
300
337
|
class RepoCreateBodyBase(BaseModel):
|
|
301
338
|
"""Base fields shared across all repo creation modes."""
|
|
302
339
|
|
|
303
|
-
name:
|
|
340
|
+
name: NewRepoName
|
|
304
341
|
description: RepoDescription | None = None
|
|
305
342
|
bucket_nickname: BucketNickname | None = None
|
|
306
343
|
kind: RepoKind = RepoKind.Icechunk
|
|
307
344
|
metadata: BoundedRepoMetadata | None = None
|
|
308
345
|
|
|
309
|
-
@field_validator("name")
|
|
310
|
-
@classmethod
|
|
311
|
-
def validate_name_length(cls, v: str) -> str:
|
|
312
|
-
if len(v) > MAX_REPO_NAME_LENGTH:
|
|
313
|
-
raise ValueError(f"Repo name must be at most {MAX_REPO_NAME_LENGTH} characters long. Got {len(v)}.")
|
|
314
|
-
return v
|
|
315
|
-
|
|
316
346
|
|
|
317
347
|
class RepoCreateBody(RepoCreateBodyBase):
|
|
318
348
|
"""Body for creating or registering a new repo.
|
|
@@ -354,8 +384,14 @@ class RepoSubscribeBody(RepoCreateBodyBase):
|
|
|
354
384
|
Note: spec_version is inherited from the parent repo, not specified by the user.
|
|
355
385
|
"""
|
|
356
386
|
|
|
357
|
-
#
|
|
358
|
-
|
|
387
|
+
# The legacy ``marketplace_listing_id`` spelling stays accepted: released clients never
|
|
388
|
+
# send this key, but the app, tests and direct HTTP callers do, and deployed environments
|
|
389
|
+
# lag the code. Drop the alias only once every caller sends ``exchange_listing_id``.
|
|
390
|
+
exchange_listing_id: str = Field(
|
|
391
|
+
...,
|
|
392
|
+
validation_alias=AliasChoices("exchange_listing_id", "marketplace_listing_id"),
|
|
393
|
+
description="The ID of the listing to subscribe to",
|
|
394
|
+
)
|
|
359
395
|
target_filter: DatasetFilter | None = Field(
|
|
360
396
|
None, description="Subscriber-defined filter applied on top of the provider's source filter. None means no additional filtering."
|
|
361
397
|
)
|
|
@@ -1340,6 +1376,8 @@ class ExchangeListing(BaseModel):
|
|
|
1340
1376
|
pricing_model: str = ExchangeListingPricingModel.FREE
|
|
1341
1377
|
featured_rank: float | None = None
|
|
1342
1378
|
source_filter: DatasetFilter | None = None
|
|
1379
|
+
exchange_owner: str | None = None
|
|
1380
|
+
exchange_slug: str | None = None
|
|
1343
1381
|
|
|
1344
1382
|
class Config:
|
|
1345
1383
|
populate_by_name = True
|
|
@@ -1353,11 +1391,18 @@ MarketplaceListingPricingModel = ExchangeListingPricingModel
|
|
|
1353
1391
|
class RepoSubscriptionInfo(BaseModel):
|
|
1354
1392
|
"""Information about a repo's subscription to a parent repo"""
|
|
1355
1393
|
|
|
1394
|
+
# populate_by_name lets the server keep constructing by field name now that
|
|
1395
|
+
# exchange_listing carries a validation alias.
|
|
1396
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
1397
|
+
|
|
1356
1398
|
parent_repo: Repo | None
|
|
1357
1399
|
status: SubscriptionStatus = SubscriptionStatus.ACTIVE
|
|
1358
1400
|
subscription_type: SubscriptionType = SubscriptionType.DIRECT
|
|
1359
|
-
#
|
|
1360
|
-
|
|
1401
|
+
# The alias lets this client parse servers that predate the rename and still emit marketplace_listing.
|
|
1402
|
+
exchange_listing: ExchangeListing | None = Field(validation_alias=AliasChoices("exchange_listing", "marketplace_listing"))
|
|
1403
|
+
# Deprecated mirror of exchange_listing: the server fills it for clients that predate the
|
|
1404
|
+
# rename, which require the key. Remove behind a client-version floor.
|
|
1405
|
+
marketplace_listing: ExchangeListing | None = None
|
|
1361
1406
|
source_filter: DatasetFilter | None = None
|
|
1362
1407
|
target_filter: DatasetFilter | None = None
|
|
1363
1408
|
pricing_type: SubscriptionPricingType | None = None
|
|
@@ -58,8 +58,11 @@ obstore = [
|
|
|
58
58
|
"obstore >= 0.9.2",
|
|
59
59
|
]
|
|
60
60
|
iceberg = [
|
|
61
|
-
# get_iceberg returns a pyiceberg RestCatalog for an org's iceberg catalog
|
|
62
|
-
|
|
61
|
+
# get_iceberg returns a pyiceberg RestCatalog for an org's iceberg catalog.
|
|
62
|
+
# The pyarrow extra is not optional in practice: pyiceberg raises
|
|
63
|
+
# "For writes PyArrow needs to be installed" on any table append/overwrite,
|
|
64
|
+
# and scan.to_arrow()/to_pandas() need it too.
|
|
65
|
+
"pyiceberg[pyarrow] >= 0.11, < 0.13",
|
|
63
66
|
]
|
|
64
67
|
openeo = [
|
|
65
68
|
# openeo >=0.40 pins xarray<2025.01.2, which forces xarray below the
|
|
@@ -7,7 +7,7 @@ from arraylake import AsyncClient
|
|
|
7
7
|
from arraylake.api_utils import ArraylakeHttpClient
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
async def
|
|
10
|
+
async def create_exchange_listing(token: str, org_name: str, repo_id: str, pricing_model: str = "free"):
|
|
11
11
|
"""Create an exchange listing for a given repo.
|
|
12
12
|
|
|
13
13
|
Client does not yet support exchange listing creation, so we use the lower-level HTTP client here.
|
|
@@ -19,16 +19,16 @@ async def create_marketplace_listing(token: str, org_name: str, repo_id: str, pr
|
|
|
19
19
|
body = {
|
|
20
20
|
"repo_id": str(repo_id),
|
|
21
21
|
"status": "published",
|
|
22
|
-
"repo_readme": f"# Test
|
|
22
|
+
"repo_readme": f"# Test Exchange Listing\n\nThis is a test listing for repo {repo_id}",
|
|
23
23
|
"listing_name": f"Test Listing for {repo_id}",
|
|
24
24
|
"pricing_model": pricing_model,
|
|
25
25
|
}
|
|
26
|
-
resp = await client._request("POST", f"/orgs/{org_name}/
|
|
27
|
-
assert resp.is_success, f"Failed to create
|
|
26
|
+
resp = await client._request("POST", f"/orgs/{org_name}/exchange-listings", content=json.dumps(body))
|
|
27
|
+
assert resp.is_success, f"Failed to create exchange listing in org {org_name}: {resp.status_code} {resp.content}"
|
|
28
28
|
return resp.json()
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
async def create_subscription_repo(token: str, org_name: str, repo_name: str,
|
|
31
|
+
async def create_subscription_repo(token: str, org_name: str, repo_name: str, listing_id: str):
|
|
32
32
|
"""Create a subscription repo in the specified org that subscribes to the given exchange listing.
|
|
33
33
|
|
|
34
34
|
Client does not yet support subscription repo creation, so we use the lower-level HTTP client here.
|
|
@@ -37,9 +37,9 @@ async def create_subscription_repo(token: str, org_name: str, repo_name: str, ma
|
|
|
37
37
|
body = {
|
|
38
38
|
"name": repo_name,
|
|
39
39
|
"description": "Subscriber repo for subscription tests",
|
|
40
|
-
"
|
|
40
|
+
"exchange_listing_id": str(listing_id),
|
|
41
41
|
}
|
|
42
|
-
resp = await client._request("POST", f"/orgs/{org_name}/
|
|
42
|
+
resp = await client._request("POST", f"/orgs/{org_name}/exchange-subscriptions", content=json.dumps(body))
|
|
43
43
|
assert resp.is_success, f"Failed to create repo in isolated org {org_name}: {resp.status_code} {resp.content}"
|
|
44
44
|
return resp.json()
|
|
45
45
|
|
|
@@ -48,7 +48,7 @@ async def create_filtered_subscription_repo(
|
|
|
48
48
|
token: str,
|
|
49
49
|
org_name: str,
|
|
50
50
|
repo_name: str,
|
|
51
|
-
|
|
51
|
+
listing_id: str,
|
|
52
52
|
bucket_nickname: str,
|
|
53
53
|
target_filter: dict,
|
|
54
54
|
):
|
|
@@ -64,11 +64,11 @@ async def create_filtered_subscription_repo(
|
|
|
64
64
|
"name": repo_name,
|
|
65
65
|
"description": "Filtered subscriber repo for subscription tests",
|
|
66
66
|
"bucket_nickname": bucket_nickname,
|
|
67
|
-
"
|
|
67
|
+
"exchange_listing_id": str(listing_id),
|
|
68
68
|
"subscription_type": "filtered",
|
|
69
69
|
"target_filter": target_filter,
|
|
70
70
|
}
|
|
71
|
-
resp = await client._request("POST", f"/orgs/{org_name}/
|
|
71
|
+
resp = await client._request("POST", f"/orgs/{org_name}/exchange-subscriptions", content=json.dumps(body))
|
|
72
72
|
assert resp.is_success, f"Failed to create filtered subscription repo in org {org_name}: {resp.status_code} {resp.content}"
|
|
73
73
|
return resp.json()
|
|
74
74
|
|
|
@@ -95,7 +95,7 @@ async def run_subscription_workflow_test(token: str, parent_org: str, subscriber
|
|
|
95
95
|
parent_repo_obj = await aclient.get_repo_object(parent_repo_name)
|
|
96
96
|
|
|
97
97
|
# Create an exchange listing for the parent repo
|
|
98
|
-
listing = await
|
|
98
|
+
listing = await create_exchange_listing(token, parent_org, parent_repo_obj.id)
|
|
99
99
|
|
|
100
100
|
# Create a subscription repo that subscribes to the parent repo
|
|
101
101
|
repo_name = "subscription-repo"
|
|
@@ -19,11 +19,13 @@ from arraylake.types import (
|
|
|
19
19
|
MAX_REPO_NAME_LENGTH,
|
|
20
20
|
NewBucket,
|
|
21
21
|
NodeFilter,
|
|
22
|
+
RESERVED_REPO_NAMES,
|
|
22
23
|
R2AuthPatch,
|
|
23
24
|
R2CustomerManagedRoleAuth,
|
|
24
25
|
Repo,
|
|
25
26
|
RepoCreateBody,
|
|
26
27
|
RepoKind,
|
|
28
|
+
RepoSubscribeBody,
|
|
27
29
|
S3Credentials,
|
|
28
30
|
TokenAuthenticateBody,
|
|
29
31
|
utc_now,
|
|
@@ -131,6 +133,16 @@ def test_name_length_limits():
|
|
|
131
133
|
assert RepoCreateBody(name="a" * MAX_REPO_NAME_LENGTH, bucket_nickname="my-bucket").name == "a" * MAX_REPO_NAME_LENGTH
|
|
132
134
|
|
|
133
135
|
|
|
136
|
+
@pytest.mark.parametrize("name", [*sorted(RESERVED_REPO_NAMES), "Exchange"])
|
|
137
|
+
def test_reserved_repo_names(name: str):
|
|
138
|
+
with pytest.raises(ValidationError, match="reserved"):
|
|
139
|
+
RepoCreateBody(name=name, bucket_nickname="my-bucket")
|
|
140
|
+
with pytest.raises(ValidationError, match="reserved"):
|
|
141
|
+
RepoSubscribeBody(name=name, exchange_listing_id="some-listing")
|
|
142
|
+
# Existing repos may already hold a reserved name and must stay readable.
|
|
143
|
+
assert Repo.model_validate({**_repo_payload(RepoKind.Icechunk.value), "name": name}).name == name
|
|
144
|
+
|
|
145
|
+
|
|
134
146
|
def test_anonymous_azure_bucket_requires_storage_account():
|
|
135
147
|
# Anonymous Azure buckets must carry the storage account name (s3/gcs don't need it).
|
|
136
148
|
with pytest.raises(ValidationError, match="Anonymous Azure buckets require a storage_account"):
|
|
@@ -26,7 +26,7 @@ from arraylake.types import (
|
|
|
26
26
|
)
|
|
27
27
|
from tests.test_subscriptions import (
|
|
28
28
|
create_filtered_subscription_repo,
|
|
29
|
-
|
|
29
|
+
create_exchange_listing,
|
|
30
30
|
create_subscription_repo,
|
|
31
31
|
)
|
|
32
32
|
|
|
@@ -1057,7 +1057,7 @@ async def test_filtered_subscription_implementation(two_isolated_orgs, default_b
|
|
|
1057
1057
|
|
|
1058
1058
|
# Creating the exchange listing auto-creates a SubscriptionVCAP for the repo
|
|
1059
1059
|
# Use paid pricing model to support filtered subscriptions
|
|
1060
|
-
listing = await
|
|
1060
|
+
listing = await create_exchange_listing(token, org_a, parent_repo_obj.id, pricing_model="paid")
|
|
1061
1061
|
|
|
1062
1062
|
await create_filtered_subscription_repo(
|
|
1063
1063
|
token,
|
|
@@ -1166,7 +1166,7 @@ async def provider_with_virtual_chunks(two_isolated_orgs, default_bucket, token,
|
|
|
1166
1166
|
|
|
1167
1167
|
# The subscription VCAP is auto-created when the exchange listing is created
|
|
1168
1168
|
async with vcap_for_bucket(async_client, org_a, virtual_chunk_bucket, public=public):
|
|
1169
|
-
listing = await
|
|
1169
|
+
listing = await create_exchange_listing(token, org_a, parent_repo_obj.id, pricing_model=pricing_model)
|
|
1170
1170
|
|
|
1171
1171
|
yield (
|
|
1172
1172
|
client,
|
|
@@ -17,7 +17,7 @@ resolution-markers = [
|
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[options]
|
|
20
|
-
exclude-newer = "
|
|
20
|
+
exclude-newer = "2026-09-10T18:46:40.594392Z"
|
|
21
21
|
exclude-newer-span = "P2W"
|
|
22
22
|
|
|
23
23
|
[options.exclude-newer-package]
|
|
@@ -75,7 +75,7 @@ dependencies = [
|
|
|
75
75
|
|
|
76
76
|
[package.optional-dependencies]
|
|
77
77
|
iceberg = [
|
|
78
|
-
{ name = "pyiceberg" },
|
|
78
|
+
{ name = "pyiceberg", extra = ["pyarrow"] },
|
|
79
79
|
]
|
|
80
80
|
maximal = [
|
|
81
81
|
{ name = "xarray", extra = ["accel", "etc", "io", "parallel"] },
|
|
@@ -125,7 +125,7 @@ requires-dist = [
|
|
|
125
125
|
{ name = "openeo", marker = "extra == 'openeo'", specifier = ">=0.35.0,<0.52" },
|
|
126
126
|
{ name = "packaging", specifier = ">=23.0,<27.0" },
|
|
127
127
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.9,<3.0" },
|
|
128
|
-
{ name = "pyiceberg", marker = "extra == 'iceberg'", specifier = ">=0.11,<0.13" },
|
|
128
|
+
{ name = "pyiceberg", extras = ["pyarrow"], marker = "extra == 'iceberg'", specifier = ">=0.11,<0.13" },
|
|
129
129
|
{ name = "python-dateutil", specifier = ">=2.8,<3.0" },
|
|
130
130
|
{ name = "rich", specifier = ">=12.6,<16.0" },
|
|
131
131
|
{ name = "ruamel-yaml", specifier = ">=0.17,<1.0" },
|
|
@@ -338,7 +338,7 @@ name = "cffi"
|
|
|
338
338
|
version = "2.0.0"
|
|
339
339
|
source = { registry = "https://pypi.org/simple" }
|
|
340
340
|
dependencies = [
|
|
341
|
-
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
|
341
|
+
{ name = "pycparser", marker = "implementation_name != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
|
342
342
|
]
|
|
343
343
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
|
344
344
|
wheels = [
|
|
@@ -663,7 +663,7 @@ name = "cryptography"
|
|
|
663
663
|
version = "50.0.0"
|
|
664
664
|
source = { registry = "https://pypi.org/simple" }
|
|
665
665
|
dependencies = [
|
|
666
|
-
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
|
666
|
+
{ name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
|
667
667
|
]
|
|
668
668
|
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
|
669
669
|
wheels = [
|
|
@@ -999,8 +999,8 @@ name = "httpcore2"
|
|
|
999
999
|
version = "2.12.0"
|
|
1000
1000
|
source = { registry = "https://pypi.org/simple" }
|
|
1001
1001
|
dependencies = [
|
|
1002
|
-
{ name = "h11" },
|
|
1003
|
-
{ name = "truststore" },
|
|
1002
|
+
{ name = "h11", marker = "sys_platform != 'emscripten'" },
|
|
1003
|
+
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
|
|
1004
1004
|
]
|
|
1005
1005
|
sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" }
|
|
1006
1006
|
wheels = [
|
|
@@ -1892,7 +1892,7 @@ name = "oschmod"
|
|
|
1892
1892
|
version = "0.3.12"
|
|
1893
1893
|
source = { registry = "https://pypi.org/simple" }
|
|
1894
1894
|
dependencies = [
|
|
1895
|
-
{ name = "pywin32" },
|
|
1895
|
+
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
|
1896
1896
|
]
|
|
1897
1897
|
sdist = { url = "https://files.pythonhosted.org/packages/9d/64/6e522f0a1b500470ae14dcd1a4bb8f8751f5cf441e85e9fee7dfc712cb7b/oschmod-0.3.12.tar.gz", hash = "sha256:bec99216f31615ee653b2a5c87cacfb4e4b6184c0e9f71da186300dacae974f3", size = 20064, upload-time = "2020-10-30T00:38:42.409Z" }
|
|
1898
1898
|
wheels = [
|
|
@@ -2307,6 +2307,11 @@ wheels = [
|
|
|
2307
2307
|
{ url = "https://files.pythonhosted.org/packages/a4/57/2ae640b6220a321958484162cfbdd1a2772198bac0c4588a990b8afd22d5/pyiceberg-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:ee7b572d209a39224a093661f462f95551b2bd3982a07aecd80c7dd560be15ea", size = 561552, upload-time = "2026-09-01T17:28:41.156Z" },
|
|
2308
2308
|
]
|
|
2309
2309
|
|
|
2310
|
+
[package.optional-dependencies]
|
|
2311
|
+
pyarrow = [
|
|
2312
|
+
{ name = "pyarrow" },
|
|
2313
|
+
]
|
|
2314
|
+
|
|
2310
2315
|
[[package]]
|
|
2311
2316
|
name = "pyogrio"
|
|
2312
2317
|
version = "0.13.0"
|
|
@@ -2801,8 +2806,8 @@ name = "secretstorage"
|
|
|
2801
2806
|
version = "3.5.0"
|
|
2802
2807
|
source = { registry = "https://pypi.org/simple" }
|
|
2803
2808
|
dependencies = [
|
|
2804
|
-
{ name = "cryptography" },
|
|
2805
|
-
{ name = "jeepney" },
|
|
2809
|
+
{ name = "cryptography", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
|
2810
|
+
{ name = "jeepney", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
|
2806
2811
|
]
|
|
2807
2812
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
|
2808
2813
|
wheels = [
|
|
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
|