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.
Files changed (76) hide show
  1. {arraylake-1.5.0 → arraylake-1.6.0}/PKG-INFO +2 -2
  2. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/_version.py +2 -2
  3. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/types.py +57 -12
  4. {arraylake-1.5.0 → arraylake-1.6.0}/pyproject.toml +5 -2
  5. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_subscriptions.py +11 -11
  6. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_types.py +12 -0
  7. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_virtual_chunks.py +3 -3
  8. {arraylake-1.5.0 → arraylake-1.6.0}/uv.lock +15 -10
  9. {arraylake-1.5.0 → arraylake-1.6.0}/.gitignore +0 -0
  10. {arraylake-1.5.0 → arraylake-1.6.0}/README.md +0 -0
  11. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/__init__.py +0 -0
  12. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/__main__.py +0 -0
  13. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/_credential_cache.py +0 -0
  14. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/api_utils.py +0 -0
  15. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/asyn.py +0 -0
  16. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/__init__.py +0 -0
  17. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/auth.py +0 -0
  18. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/compute.py +0 -0
  19. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/config.py +0 -0
  20. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/iceberg.py +0 -0
  21. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/main.py +0 -0
  22. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/org.py +0 -0
  23. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/repo.py +0 -0
  24. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/skills.py +0 -0
  25. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/cli/utils.py +0 -0
  26. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/client.py +0 -0
  27. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/http_client.py +0 -0
  28. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/services.py +0 -0
  29. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/compute/types.py +0 -0
  30. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/config.py +0 -0
  31. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/config.yaml +0 -0
  32. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/credentials.py +0 -0
  33. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/diagnostics.py +0 -0
  34. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/__init__.py +0 -0
  35. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/repo.py +0 -0
  36. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/display/repolist.py +0 -0
  37. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/exceptions.py +0 -0
  38. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/log_util.py +0 -0
  39. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/__init__.py +0 -0
  40. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/abc.py +0 -0
  41. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/metastore/http_metastore.py +0 -0
  42. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/obstore.py +0 -0
  43. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/py.typed +0 -0
  44. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/__init__.py +0 -0
  45. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/__init__.py +0 -0
  46. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/auth.py +0 -0
  47. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/iceberg/catalog.py +0 -0
  48. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/__init__.py +0 -0
  49. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/storage.py +0 -0
  50. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/types.py +0 -0
  51. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/repos/icechunk/virtual.py +0 -0
  52. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/token.py +0 -0
  53. {arraylake-1.5.0 → arraylake-1.6.0}/arraylake/tuning.py +0 -0
  54. {arraylake-1.5.0 → arraylake-1.6.0}/tests/__init__.py +0 -0
  55. {arraylake-1.5.0 → arraylake-1.6.0}/tests/compute/test_cli_table.py +0 -0
  56. {arraylake-1.5.0 → arraylake-1.6.0}/tests/compute/test_client.py +0 -0
  57. {arraylake-1.5.0 → arraylake-1.6.0}/tests/config.yaml +0 -0
  58. {arraylake-1.5.0 → arraylake-1.6.0}/tests/conftest.py +0 -0
  59. {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/__init__.py +0 -0
  60. {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/conftest.py +0 -0
  61. {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/test_storage.py +0 -0
  62. {arraylake-1.5.0 → arraylake-1.6.0}/tests/icechunk/test_virtual.py +0 -0
  63. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_api_utils.py +0 -0
  64. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_asyn.py +0 -0
  65. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_cli.py +0 -0
  66. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_client.py +0 -0
  67. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_config.py +0 -0
  68. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_credential_cache.py +0 -0
  69. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_credentials.py +0 -0
  70. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_diagnostics.py +0 -0
  71. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_http_metastore.py +0 -0
  72. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_iceberg_client.py +0 -0
  73. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_obstore.py +0 -0
  74. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_performance.py +0 -0
  75. {arraylake-1.5.0 → arraylake-1.6.0}/tests/test_skills.py +0 -0
  76. {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.5.0
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.5.0'
22
- __version_tuple__ = version_tuple = (1, 5, 0)
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: RepoName
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
- # TODO: rename to exchange
358
- marketplace_listing_id: str = Field(..., description="The ID of the listing to subscribe to")
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
- # TODO: rename to exchange
1360
- marketplace_listing: ExchangeListing | None
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
- "pyiceberg >= 0.11, < 0.13",
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 create_marketplace_listing(token: str, org_name: str, repo_id: str, pricing_model: str = "free"):
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 Marketplace Listing\n\nThis is a test listing for repo {repo_id}",
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}/marketplace/listings", content=json.dumps(body))
27
- assert resp.is_success, f"Failed to create marketplace listing in org {org_name}: {resp.status_code} {resp.content}"
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, marketplace_listing_id: 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
- "marketplace_listing_id": str(marketplace_listing_id),
40
+ "exchange_listing_id": str(listing_id),
41
41
  }
42
- resp = await client._request("POST", f"/orgs/{org_name}/marketplace/subscribe", content=json.dumps(body))
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
- marketplace_listing_id: str,
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
- "marketplace_listing_id": str(marketplace_listing_id),
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}/marketplace/subscribe", content=json.dumps(body))
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 create_marketplace_listing(token, parent_org, parent_repo_obj.id)
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
- create_marketplace_listing,
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 create_marketplace_listing(token, org_a, parent_repo_obj.id, pricing_model="paid")
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 create_marketplace_listing(token, org_a, parent_repo_obj.id, pricing_model=pricing_model)
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 = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
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