diff --git a/.hark/changes/2026-09-11_xavdid_singleton-related-object.change.md b/.hark/changes/2026-09-11_xavdid_singleton-related-object.change.md new file mode 100644 index 000000000..878dbb765 --- /dev/null +++ b/.hark/changes/2026-09-11_xavdid_singleton-related-object.change.md @@ -0,0 +1,6 @@ +--- +title: Support `EventNotification`s with singleton related objects +pr_url: https://github.com/stripe/stripe-python/pull/1904 +jira_tickets_closed: +- RUN_DEVSDK-2825 +--- diff --git a/stripe/v2/core/__init__.py b/stripe/v2/core/__init__.py index 8ff2aeb1e..4a5b9ff11 100644 --- a/stripe/v2/core/__init__.py +++ b/stripe/v2/core/__init__.py @@ -4,6 +4,7 @@ from stripe.v2.core._event import ( EventNotification as EventNotification, RelatedObject as RelatedObject, + RelatedSingletonObject as RelatedSingletonObject, Reason as Reason, ReasonRequest as ReasonRequest, ) diff --git a/stripe/v2/core/_event.py b/stripe/v2/core/_event.py index fe014db67..fb6ab6e09 100644 --- a/stripe/v2/core/_event.py +++ b/stripe/v2/core/_event.py @@ -123,6 +123,18 @@ def __repr__(self) -> str: return f"" +class RelatedSingletonObject: + type: str + url: str + + def __init__(self, d) -> None: + self.type = d["type"] + self.url = d["url"] + + def __repr__(self) -> str: + return f"" + + class EventNotification: """ EventNotification represents the json that's delivered from an Event Destination. It's a basic struct-like object with a few convenience methods. Use `fetch_event()` to get the full event object. diff --git a/tests/test_exports.py b/tests/test_exports.py index 3aff0a530..e9c3a04cd 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -45,6 +45,16 @@ def test_can_import_webhook_members() -> None: ) +def test_can_import_event_notification_members() -> None: + from stripe.v2.core import ( + EventNotification, # pyright: ignore[reportUnusedImport] + Reason, # pyright: ignore[reportUnusedImport] + ReasonRequest, # pyright: ignore[reportUnusedImport] + RelatedObject, # pyright: ignore[reportUnusedImport] + RelatedSingletonObject, # pyright: ignore[reportUnusedImport] + ) + + def test_can_import_abstract() -> None: from stripe import ( APIResource, # pyright: ignore[reportUnusedImport] diff --git a/tests/test_v2_event.py b/tests/test_v2_event.py index 694b591af..54a1f2e2d 100644 --- a/tests/test_v2_event.py +++ b/tests/test_v2_event.py @@ -14,7 +14,11 @@ V1BillingMeterErrorReportTriggeredEventNotification, V1BillingMeterErrorReportTriggeredEvent, ) -from stripe.v2.core._event import EventNotification, UnknownEventNotification +from stripe.v2.core._event import ( + EventNotification, + RelatedSingletonObject, + UnknownEventNotification, +) from stripe.events._event_classes import ALL_EVENT_NOTIFICATIONS from stripe._webhook import WebhookPayload, WebhookSignature from tests.test_webhook import DUMMY_WEBHOOK_SECRET @@ -198,6 +202,19 @@ def test_parses_unknown_event_notif(self, parse_event_notif: EventParser): assert type(event) is UnknownEventNotification assert event.related_object + def test_related_singleton_object_omits_id(self): + """ + events whose related object is a singleton get a `RelatedSingletonObject`, + which has no `id` at all (instead of a perpetually-`None` one) + """ + related_object = RelatedSingletonObject( + {"type": "balance", "url": "/v1/balance"} + ) + + assert related_object.type == "balance" + assert related_object.url == "/v1/balance" + assert not hasattr(related_object, "id") + def test_raise_on_v1_payload(self, parse_event_notif: EventParser): v1_payload = json.dumps( {