diff --git a/CHANGELOG.md b/CHANGELOG.md index deb8068..bb484ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## Unreleased +## 0.6.0 — 2026-08-23 + +- Added exact immutable Event configuration binding reads and compare-and-set + attach/detach through `Events.retrieve_configuration_binding` and + `Events.update_configuration_binding`. Updates remain deliberately + single-attempt. ## 0.5.0 — 2026-08-21 diff --git a/README.md b/README.md index 6f1695b..bb73dbe 100644 --- a/README.md +++ b/README.md @@ -283,7 +283,7 @@ seatlayer.request("POST", "/v1/events/ev_1/some-new-route", body={...}) | --- | --- | | `charts` | `list` `list_all` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` | | `templates` | `instantiate_template` | -| `events` | `list` `list_all` `create` `retrieve` `update` `delete` `update_poster` `delete_poster` `update_chart` `close` `reopen` `archive` `list_ticket_releases` `update_ticket_releases` `close_ticket_release` `retrieve_hold_ttl` `update_hold_ttl` `retrieve_report` `retrieve_log` | +| `events` | `list` `list_all` `create` `retrieve` `retrieve_configuration_binding` `update_configuration_binding` `update` `delete` `update_poster` `delete_poster` `update_chart` `close` `reopen` `archive` `list_ticket_releases` `update_ticket_releases` `close_ticket_release` `retrieve_hold_ttl` `update_hold_ttl` `retrieve_report` `retrieve_log` | | `inventory` | `hold` `hold_best_available` `book_best_available` `extend_hold` `retrieve_hold` `release` `book` `box_office_book` `unbook` `list_bookings` `retrieve_booking` `block` `unblock` `unblock_all` `retrieve_availability` `update_availability` | | `channels` | `list_channels` `create_channel` `update_channel` `update_assignments` `list_allocation` `retrieve_access_preview` `retrieve_report` `pause` `unpause` `archive` `create_buyer_access_session` `list_buyer_access_sessions` `revoke_buyer_access_session` `create_access_link` `list_access_links` `rotate_access_link` `revoke_access_link` | | `sessions` | `create_manage_session` `revoke_manage_session` `create_designer_session` `revoke_designer_session` | diff --git a/pyproject.toml b/pyproject.toml index 9d33788..f5af633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" [project] name = "seatlayer" -version = "0.5.0" +version = "0.6.0" description = "Official Python server SDK for the SeatLayer reserved-seating API." readme = "README.md" license = "MIT" diff --git a/src/seatlayer/__init__.py b/src/seatlayer/__init__.py index d762d6d..abfe578 100644 --- a/src/seatlayer/__init__.py +++ b/src/seatlayer/__init__.py @@ -25,6 +25,9 @@ DesignerSafeModeOptionsInput, DesignerSession, DesignerSessionEnvelope, + EventConfigurationBinding, + EventConfigurationBindingAudit, + EventConfigurationRef, EventLogEntry, EventLogPage, HoldInspection, @@ -46,7 +49,7 @@ ) from .webhooks import WebhookVerificationError, verify_webhook -__version__ = "0.5.0" +__version__ = "0.6.0" __all__ = [ "AccessLink", @@ -60,6 +63,9 @@ "DesignerSafeModeOptionsInput", "DesignerSession", "DesignerSessionEnvelope", + "EventConfigurationBinding", + "EventConfigurationBindingAudit", + "EventConfigurationRef", "EventLogEntry", "EventLogPage", "HoldInspection", diff --git a/src/seatlayer/resources.py b/src/seatlayer/resources.py index 0636201..9497321 100644 --- a/src/seatlayer/resources.py +++ b/src/seatlayer/resources.py @@ -16,6 +16,8 @@ AccessLinkRevokeResult, DesignerSafeModeOptionsInput, DesignerSessionEnvelope, + EventConfigurationBinding, + EventConfigurationRef, EventLogPage, HoldInspection, ManageCapability, @@ -317,6 +319,37 @@ def create( def retrieve(self, event_key: str) -> Any: return self._http.get(f"/v1/events/{quote(event_key)}") + def retrieve_configuration_binding(self, event_key: str) -> EventConfigurationBinding: + """Read the Event's exact immutable configuration binding and audit history.""" + return cast( + EventConfigurationBinding, + self._http.get( + f"/v1/events/{quote(event_key)}/event-configuration" + ), + ) + + def update_configuration_binding( + self, + event_key: str, + expected_revision: int, + configuration: EventConfigurationRef | None, + ) -> EventConfigurationBinding: + """Bind an exact published version, or pass ``None`` to detach. + + This compare-and-set mutation stays single-attempt because the public + operation does not promise exact response replay. + """ + return cast( + EventConfigurationBinding, + self._http.put( + f"/v1/events/{quote(event_key)}/event-configuration", + body={ + "expectedRevision": expected_revision, + "configuration": configuration, + }, + ), + ) + def update(self, event_key: str, **fields: Any) -> Any: return self._http.patch(f"/v1/events/{quote(event_key)}", body=fields) diff --git a/src/seatlayer/types.py b/src/seatlayer/types.py index 03be9c5..c88a4e4 100644 --- a/src/seatlayer/types.py +++ b/src/seatlayer/types.py @@ -81,6 +81,36 @@ class TicketReleaseList(TypedDict): releases: list[TicketRelease] +class EventConfigurationRef(TypedDict): + """Exact immutable Event configuration version.""" + + id: str + version: int + + +EventConfigurationBindingAudit = TypedDict( + "EventConfigurationBindingAudit", + { + "id": str, + "from": EventConfigurationRef | None, + "to": EventConfigurationRef | None, + "revision": int, + "actor": str, + "createdAt": int, + }, +) + + +class EventConfigurationBinding(TypedDict): + """Current exact binding and its complete audit history.""" + + configuration: EventConfigurationRef | None + revision: int + changedBy: str | None + changedAt: int | None + audit: list[EventConfigurationBindingAudit] + + class _InventoryItemOptional(TypedDict, total=False): quantity: int bookingMode: Literal["individual", "whole", "variable"] diff --git a/tests/test_client.py b/tests/test_client.py index 47f0148..c80f173 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -106,6 +106,47 @@ def test_percent_encodes_path_parameters(self) -> None: sdk.events.retrieve("ev/../admin") assert calls[0].full_url == "https://api.seatlayer.io/v1/events/ev%2F..%2Fadmin" + def test_event_configuration_binding_wire_contract(self) -> None: + binding = { + "configuration": {"id": "ec_touring", "version": 3}, + "revision": 7, + "changedBy": "api-key:key_1", + "changedAt": 123, + "audit": [{ + "id": "eca_1", "from": None, + "to": {"id": "ec_touring", "version": 3}, + "revision": 7, "actor": "api-key:key_1", "createdAt": 123, + }], + } + sdk, calls = make_client([ + {"status": 200, "body": binding}, + {"status": 200, "body": binding}, + {"status": 200, "body": {**binding, "configuration": None, "revision": 8}}, + ]) + + retrieved = sdk.events.retrieve_configuration_binding("ev / main") + assert retrieved["audit"][0]["to"] == {"id": "ec_touring", "version": 3} + sdk.events.update_configuration_binding( + "ev / main", 6, {"id": "ec_touring", "version": 3} + ) + sdk.events.update_configuration_binding("ev / main", 7, None) + + expected_url = ( + "https://api.seatlayer.io/v1/events/ev%20%2F%20main/event-configuration" + ) + assert [call.full_url for call in calls] == [expected_url, expected_url, expected_url] + assert calls[0].method == "GET" + assert json.loads(calls[1].data) == { + "expectedRevision": 6, + "configuration": {"id": "ec_touring", "version": 3}, + } + assert json.loads(calls[2].data) == { + "expectedRevision": 7, + "configuration": None, + } + assert calls[1].get_header("Idempotency-key") is None + assert calls[2].get_header("Idempotency-key") is None + def test_generated_idempotency_key_only_on_header_replay_mutations(self) -> None: sdk, calls = make_client([ {"status": 200, "body": {}},