benvo 1.0.0__py3-none-any.whl

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.
benvo/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ from . import webhooks
2
+ from ._client import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, Benvo, response_of
3
+ from ._endpoints import ENDPOINTS, Endpoint
4
+ from ._pagination import ListIterator
5
+ from ._transport import BenvoObject
6
+ from ._version import __version__
7
+ from .errors import (
8
+ BenvoApiError,
9
+ BenvoAuthenticationError,
10
+ BenvoConnectionError,
11
+ BenvoError,
12
+ BenvoInvalidRequestError,
13
+ BenvoNotFoundError,
14
+ BenvoPermissionError,
15
+ BenvoRateLimitError,
16
+ BenvoWebhookVerificationError,
17
+ )
18
+ from .signing import SIGNATURE_HEADER, sign_request
19
+
20
+ __all__ = [
21
+ "DEFAULT_BASE_URL",
22
+ "DEFAULT_MAX_RETRIES",
23
+ "DEFAULT_TIMEOUT",
24
+ "ENDPOINTS",
25
+ "SIGNATURE_HEADER",
26
+ "Benvo",
27
+ "BenvoApiError",
28
+ "BenvoAuthenticationError",
29
+ "BenvoConnectionError",
30
+ "BenvoError",
31
+ "BenvoInvalidRequestError",
32
+ "BenvoNotFoundError",
33
+ "BenvoObject",
34
+ "BenvoPermissionError",
35
+ "BenvoRateLimitError",
36
+ "BenvoWebhookVerificationError",
37
+ "Endpoint",
38
+ "ListIterator",
39
+ "__version__",
40
+ "response_of",
41
+ "sign_request",
42
+ "webhooks",
43
+ ]
benvo/_client.py ADDED
@@ -0,0 +1,80 @@
1
+ import re
2
+ from typing import Any, Dict, Mapping, Optional
3
+
4
+ from ._resources import (
5
+ AvailabilityResource,
6
+ Events,
7
+ Invoices,
8
+ Leads,
9
+ Members,
10
+ MembershipTiers,
11
+ Menus,
12
+ Meta,
13
+ Orders,
14
+ Payments,
15
+ People,
16
+ Products,
17
+ Reservations,
18
+ Rsvps,
19
+ )
20
+ from ._transport import Transport, default_user_agent
21
+ from ._version import __version__
22
+ from .types import Ping
23
+
24
+ DEFAULT_BASE_URL = "https://api.benvo.io"
25
+ DEFAULT_TIMEOUT = 30.0
26
+ DEFAULT_MAX_RETRIES = 2
27
+
28
+
29
+ class Benvo:
30
+ VERSION = __version__
31
+
32
+ def __init__(
33
+ self,
34
+ api_key: str,
35
+ *,
36
+ base_url: str = DEFAULT_BASE_URL,
37
+ timeout: float = DEFAULT_TIMEOUT,
38
+ max_retries: int = DEFAULT_MAX_RETRIES,
39
+ signing_secret: Optional[str] = None,
40
+ user_agent: Optional[str] = None,
41
+ ) -> None:
42
+ if not isinstance(api_key, str) or not api_key.strip():
43
+ raise ValueError("A Benvo API key is required")
44
+ base = base_url.rstrip("/")
45
+ if not re.match(r"^https?://", base):
46
+ raise ValueError("base_url must start with https:// or http://")
47
+ self.base_url = base
48
+ self.livemode = not api_key.startswith("bvk_test_")
49
+ self._transport = Transport(
50
+ api_key=api_key,
51
+ base_url=base,
52
+ timeout=timeout,
53
+ max_retries=max_retries,
54
+ signing_secret=signing_secret,
55
+ user_agent=user_agent or default_user_agent(),
56
+ )
57
+ self.people = People(self._transport)
58
+ self.members = Members(self._transport)
59
+ self.membership_tiers = MembershipTiers(self._transport)
60
+ self.leads = Leads(self._transport)
61
+ self.reservations = Reservations(self._transport)
62
+ self.availability = AvailabilityResource(self._transport)
63
+ self.events = Events(self._transport)
64
+ self.rsvps = Rsvps(self._transport)
65
+ self.orders = Orders(self._transport)
66
+ self.invoices = Invoices(self._transport)
67
+ self.payments = Payments(self._transport)
68
+ self.menus = Menus(self._transport)
69
+ self.products = Products(self._transport)
70
+ self.meta = Meta(self._transport)
71
+
72
+ def ping(self, *, request_options: Optional[Mapping[str, Any]] = None) -> Ping:
73
+ return self._transport.call("ping", **dict(request_options or {}))
74
+
75
+ def __repr__(self) -> str:
76
+ return f"Benvo(base_url={self.base_url!r}, livemode={self.livemode!r})"
77
+
78
+
79
+ def response_of(obj: Any) -> Optional[Dict[str, Any]]:
80
+ return getattr(obj, "last_response", None)
benvo/_endpoints.py ADDED
@@ -0,0 +1,59 @@
1
+ import re
2
+ from typing import Dict, NamedTuple, Optional
3
+ from urllib.parse import quote
4
+
5
+
6
+ class Endpoint(NamedTuple):
7
+ method: str
8
+ path: str
9
+ sdk: str
10
+ auth: bool = True
11
+
12
+
13
+ ENDPOINTS: Dict[str, Endpoint] = {
14
+ "ping": Endpoint("GET", "/v1/ping", "ping"),
15
+ "ipRanges": Endpoint("GET", "/v1/meta/ip-ranges", "meta.ip_ranges", auth=False),
16
+ "listPeople": Endpoint("GET", "/v1/people", "people.list"),
17
+ "getPerson": Endpoint("GET", "/v1/people/{id}", "people.retrieve"),
18
+ "createPerson": Endpoint("POST", "/v1/people", "people.create"),
19
+ "updatePerson": Endpoint("PATCH", "/v1/people/{id}", "people.update"),
20
+ "listMembers": Endpoint("GET", "/v1/members", "members.list"),
21
+ "getMember": Endpoint("GET", "/v1/members/{id}", "members.retrieve"),
22
+ "listMembershipTiers": Endpoint("GET", "/v1/membership-tiers", "membership_tiers.list"),
23
+ "listLeads": Endpoint("GET", "/v1/leads", "leads.list"),
24
+ "createLead": Endpoint("POST", "/v1/leads", "leads.create"),
25
+ "listReservations": Endpoint("GET", "/v1/reservations", "reservations.list"),
26
+ "getReservation": Endpoint("GET", "/v1/reservations/{id}", "reservations.retrieve"),
27
+ "createReservation": Endpoint("POST", "/v1/reservations", "reservations.create"),
28
+ "updateReservation": Endpoint("PATCH", "/v1/reservations/{id}", "reservations.update"),
29
+ "cancelReservation": Endpoint("POST", "/v1/reservations/{id}/cancel", "reservations.cancel"),
30
+ "getAvailability": Endpoint("GET", "/v1/availability", "availability.check"),
31
+ "listEvents": Endpoint("GET", "/v1/events", "events.list"),
32
+ "getEvent": Endpoint("GET", "/v1/events/{id}", "events.retrieve"),
33
+ "listEventRsvps": Endpoint("GET", "/v1/events/{id}/rsvps", "rsvps.list"),
34
+ "createEventRsvp": Endpoint("POST", "/v1/events/{id}/rsvps", "rsvps.create"),
35
+ "updateEventRsvp": Endpoint("PATCH", "/v1/events/{id}/rsvps/{rsvp_id}", "rsvps.update"),
36
+ "listOrders": Endpoint("GET", "/v1/orders", "orders.list"),
37
+ "getOrder": Endpoint("GET", "/v1/orders/{id}", "orders.retrieve"),
38
+ "listInvoices": Endpoint("GET", "/v1/invoices", "invoices.list"),
39
+ "getInvoice": Endpoint("GET", "/v1/invoices/{id}", "invoices.retrieve"),
40
+ "listPayments": Endpoint("GET", "/v1/payments", "payments.list"),
41
+ "getPayment": Endpoint("GET", "/v1/payments/{id}", "payments.retrieve"),
42
+ "listMenus": Endpoint("GET", "/v1/menus", "menus.list"),
43
+ "getMenu": Endpoint("GET", "/v1/menus/{id}", "menus.retrieve"),
44
+ "listProducts": Endpoint("GET", "/v1/products", "products.list"),
45
+ }
46
+
47
+ _PARAM = re.compile(r"\{([a-z_]+)\}")
48
+
49
+
50
+ def expand_path(template: str, params: Optional[Dict[str, str]] = None) -> str:
51
+ values = params or {}
52
+
53
+ def replace(match: "re.Match[str]") -> str:
54
+ value = values.get(match.group(1))
55
+ if not isinstance(value, str) or not value:
56
+ raise ValueError(f"Missing path parameter {match.group(1)}")
57
+ return quote(value, safe="")
58
+
59
+ return _PARAM.sub(replace, template)
benvo/_pagination.py ADDED
@@ -0,0 +1,39 @@
1
+ from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, TypeVar
2
+
3
+ T = TypeVar("T")
4
+
5
+ PageFetcher = Callable[[Optional[str]], Dict[str, Any]]
6
+
7
+
8
+ class ListIterator(Generic[T]):
9
+ def __init__(self, fetch_page: PageFetcher, starting_after: Optional[str] = None) -> None:
10
+ self._fetch = fetch_page
11
+ self._start = starting_after
12
+
13
+ def page(self) -> Dict[str, Any]:
14
+ return self._fetch(self._start)
15
+
16
+ def pages(self) -> Iterator[Dict[str, Any]]:
17
+ cursor = self._start
18
+ while True:
19
+ page = self._fetch(cursor)
20
+ yield page
21
+ next_cursor = page.get("next_cursor")
22
+ if not page.get("has_more") or not next_cursor or next_cursor == cursor:
23
+ return
24
+ cursor = next_cursor
25
+
26
+ def __iter__(self) -> Iterator[T]:
27
+ for page in self.pages():
28
+ for item in page.get("data", []):
29
+ yield item
30
+
31
+ def to_list(self, max_items: Optional[int] = None) -> List[T]:
32
+ out: List[T] = []
33
+ if max_items is not None and max_items <= 0:
34
+ return out
35
+ for item in self:
36
+ out.append(item)
37
+ if max_items is not None and len(out) >= max_items:
38
+ break
39
+ return out
benvo/_resources.py ADDED
@@ -0,0 +1,297 @@
1
+ from typing import Any, Dict, Mapping, Optional, Union
2
+
3
+ import datetime as _dt
4
+
5
+ from ._pagination import ListIterator
6
+ from ._transport import Transport
7
+ from .types import (
8
+ Availability,
9
+ Event,
10
+ EventRsvp,
11
+ Invoice,
12
+ IpRanges,
13
+ Lead,
14
+ LeadCreateParams,
15
+ Member,
16
+ MembershipTier,
17
+ Menu,
18
+ Order,
19
+ Payment,
20
+ Person,
21
+ PersonCreateParams,
22
+ PersonUpdateParams,
23
+ Product,
24
+ Reservation,
25
+ ReservationCancelParams,
26
+ ReservationCreateParams,
27
+ ReservationUpdateParams,
28
+ RsvpCreateParams,
29
+ RsvpUpdateParams,
30
+ )
31
+
32
+ TimestampInput = Union[str, _dt.datetime]
33
+ DateInput = Union[str, _dt.date]
34
+
35
+
36
+ def _merge(params: Optional[Mapping[str, Any]], fields: Mapping[str, Any]) -> Dict[str, Any]:
37
+ body: Dict[str, Any] = dict(params or {})
38
+ body.update(fields)
39
+ return body
40
+
41
+
42
+ class _Resource:
43
+ def __init__(self, transport: Transport) -> None:
44
+ self._t = transport
45
+
46
+
47
+ class People(_Resource):
48
+ def list(
49
+ self,
50
+ *,
51
+ limit: Optional[int] = None,
52
+ starting_after: Optional[str] = None,
53
+ created_since: Optional[TimestampInput] = None,
54
+ updated_since: Optional[TimestampInput] = None,
55
+ email: Optional[str] = None,
56
+ phone: Optional[str] = None,
57
+ request_options: Optional[Mapping[str, Any]] = None,
58
+ **extra: Any,
59
+ ) -> ListIterator[Person]:
60
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, email=email, phone=phone, **extra)
61
+ return self._t.list("listPeople", query, **dict(request_options or {}))
62
+
63
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Person:
64
+ return self._t.call("getPerson", params={"id": id}, **dict(request_options or {}))
65
+
66
+ def create(self, params: Optional[PersonCreateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Person:
67
+ return self._t.call("createPerson", body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
68
+
69
+ def update(self, id: str, params: Optional[PersonUpdateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Person:
70
+ return self._t.call("updatePerson", params={"id": id}, body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
71
+
72
+
73
+ class Members(_Resource):
74
+ def list(
75
+ self,
76
+ *,
77
+ limit: Optional[int] = None,
78
+ starting_after: Optional[str] = None,
79
+ created_since: Optional[TimestampInput] = None,
80
+ updated_since: Optional[TimestampInput] = None,
81
+ status: Optional[str] = None,
82
+ tier_id: Optional[str] = None,
83
+ email: Optional[str] = None,
84
+ request_options: Optional[Mapping[str, Any]] = None,
85
+ **extra: Any,
86
+ ) -> ListIterator[Member]:
87
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, status=status, tier_id=tier_id, email=email, **extra)
88
+ return self._t.list("listMembers", query, **dict(request_options or {}))
89
+
90
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Member:
91
+ return self._t.call("getMember", params={"id": id}, **dict(request_options or {}))
92
+
93
+
94
+ class MembershipTiers(_Resource):
95
+ def list(self, *, request_options: Optional[Mapping[str, Any]] = None) -> ListIterator[MembershipTier]:
96
+ return self._t.list("listMembershipTiers", {}, **dict(request_options or {}))
97
+
98
+
99
+ class Leads(_Resource):
100
+ def list(
101
+ self,
102
+ *,
103
+ limit: Optional[int] = None,
104
+ starting_after: Optional[str] = None,
105
+ created_since: Optional[TimestampInput] = None,
106
+ updated_since: Optional[TimestampInput] = None,
107
+ status: Optional[str] = None,
108
+ stage: Optional[str] = None,
109
+ source: Optional[str] = None,
110
+ person_id: Optional[str] = None,
111
+ request_options: Optional[Mapping[str, Any]] = None,
112
+ **extra: Any,
113
+ ) -> ListIterator[Lead]:
114
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, status=status, stage=stage, source=source, person_id=person_id, **extra)
115
+ return self._t.list("listLeads", query, **dict(request_options or {}))
116
+
117
+ def create(self, params: Optional[LeadCreateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Lead:
118
+ return self._t.call("createLead", body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
119
+
120
+
121
+ class Reservations(_Resource):
122
+ def list(
123
+ self,
124
+ *,
125
+ limit: Optional[int] = None,
126
+ starting_after: Optional[str] = None,
127
+ created_since: Optional[TimestampInput] = None,
128
+ updated_since: Optional[TimestampInput] = None,
129
+ date_from: Optional[DateInput] = None,
130
+ date_to: Optional[DateInput] = None,
131
+ status: Optional[str] = None,
132
+ member_id: Optional[str] = None,
133
+ person_id: Optional[str] = None,
134
+ venue_id: Optional[str] = None,
135
+ request_options: Optional[Mapping[str, Any]] = None,
136
+ **extra: Any,
137
+ ) -> ListIterator[Reservation]:
138
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, date_from=date_from, date_to=date_to, status=status, member_id=member_id, person_id=person_id, venue_id=venue_id, **extra)
139
+ return self._t.list("listReservations", query, **dict(request_options or {}))
140
+
141
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Reservation:
142
+ return self._t.call("getReservation", params={"id": id}, **dict(request_options or {}))
143
+
144
+ def create(self, params: Optional[ReservationCreateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Reservation:
145
+ return self._t.call("createReservation", body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
146
+
147
+ def update(self, id: str, params: Optional[ReservationUpdateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Reservation:
148
+ return self._t.call("updateReservation", params={"id": id}, body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
149
+
150
+ def cancel(self, id: str, params: Optional[ReservationCancelParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> Reservation:
151
+ return self._t.call("cancelReservation", params={"id": id}, body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
152
+
153
+
154
+ class AvailabilityResource(_Resource):
155
+ def check(
156
+ self,
157
+ *,
158
+ date: DateInput,
159
+ party_size: int,
160
+ venue_id: Optional[str] = None,
161
+ area: Optional[str] = None,
162
+ request_options: Optional[Mapping[str, Any]] = None,
163
+ **extra: Any,
164
+ ) -> Availability:
165
+ query = dict(date=date, party_size=party_size, venue_id=venue_id, area=area, **extra)
166
+ return self._t.call("getAvailability", query=query, **dict(request_options or {}))
167
+
168
+
169
+ class Events(_Resource):
170
+ def list(
171
+ self,
172
+ *,
173
+ limit: Optional[int] = None,
174
+ starting_after: Optional[str] = None,
175
+ created_since: Optional[TimestampInput] = None,
176
+ updated_since: Optional[TimestampInput] = None,
177
+ starts_after: Optional[TimestampInput] = None,
178
+ starts_before: Optional[TimestampInput] = None,
179
+ request_options: Optional[Mapping[str, Any]] = None,
180
+ **extra: Any,
181
+ ) -> ListIterator[Event]:
182
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, starts_after=starts_after, starts_before=starts_before, **extra)
183
+ return self._t.list("listEvents", query, **dict(request_options or {}))
184
+
185
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Event:
186
+ return self._t.call("getEvent", params={"id": id}, **dict(request_options or {}))
187
+
188
+
189
+ class Rsvps(_Resource):
190
+ def list(self, event_id: str, *, status: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **extra: Any) -> ListIterator[EventRsvp]:
191
+ return self._t.list("listEventRsvps", dict(status=status, **extra), {"id": event_id}, **dict(request_options or {}))
192
+
193
+ def create(self, event_id: str, params: Optional[RsvpCreateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> EventRsvp:
194
+ return self._t.call("createEventRsvp", params={"id": event_id}, body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
195
+
196
+ def update(self, event_id: str, rsvp_id: str, params: Optional[RsvpUpdateParams] = None, *, idempotency_key: Optional[str] = None, request_options: Optional[Mapping[str, Any]] = None, **fields: Any) -> EventRsvp:
197
+ return self._t.call("updateEventRsvp", params={"id": event_id, "rsvp_id": rsvp_id}, body=_merge(params, fields), idempotency_key=idempotency_key, **dict(request_options or {}))
198
+
199
+
200
+ class Orders(_Resource):
201
+ def list(
202
+ self,
203
+ *,
204
+ limit: Optional[int] = None,
205
+ starting_after: Optional[str] = None,
206
+ created_since: Optional[TimestampInput] = None,
207
+ updated_since: Optional[TimestampInput] = None,
208
+ status: Optional[str] = None,
209
+ member_id: Optional[str] = None,
210
+ person_id: Optional[str] = None,
211
+ request_options: Optional[Mapping[str, Any]] = None,
212
+ **extra: Any,
213
+ ) -> ListIterator[Order]:
214
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, status=status, member_id=member_id, person_id=person_id, **extra)
215
+ return self._t.list("listOrders", query, **dict(request_options or {}))
216
+
217
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Order:
218
+ return self._t.call("getOrder", params={"id": id}, **dict(request_options or {}))
219
+
220
+
221
+ class Invoices(_Resource):
222
+ def list(
223
+ self,
224
+ *,
225
+ limit: Optional[int] = None,
226
+ starting_after: Optional[str] = None,
227
+ created_since: Optional[TimestampInput] = None,
228
+ updated_since: Optional[TimestampInput] = None,
229
+ status: Optional[str] = None,
230
+ member_id: Optional[str] = None,
231
+ request_options: Optional[Mapping[str, Any]] = None,
232
+ **extra: Any,
233
+ ) -> ListIterator[Invoice]:
234
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, status=status, member_id=member_id, **extra)
235
+ return self._t.list("listInvoices", query, **dict(request_options or {}))
236
+
237
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Invoice:
238
+ return self._t.call("getInvoice", params={"id": id}, **dict(request_options or {}))
239
+
240
+
241
+ class Payments(_Resource):
242
+ def list(
243
+ self,
244
+ *,
245
+ limit: Optional[int] = None,
246
+ starting_after: Optional[str] = None,
247
+ created_since: Optional[TimestampInput] = None,
248
+ updated_since: Optional[TimestampInput] = None,
249
+ invoice_id: Optional[str] = None,
250
+ member_id: Optional[str] = None,
251
+ status: Optional[str] = None,
252
+ request_options: Optional[Mapping[str, Any]] = None,
253
+ **extra: Any,
254
+ ) -> ListIterator[Payment]:
255
+ query = dict(limit=limit, starting_after=starting_after, created_since=created_since, updated_since=updated_since, invoice_id=invoice_id, member_id=member_id, status=status, **extra)
256
+ return self._t.list("listPayments", query, **dict(request_options or {}))
257
+
258
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Payment:
259
+ return self._t.call("getPayment", params={"id": id}, **dict(request_options or {}))
260
+
261
+
262
+ class Menus(_Resource):
263
+ def list(
264
+ self,
265
+ *,
266
+ limit: Optional[int] = None,
267
+ starting_after: Optional[str] = None,
268
+ updated_since: Optional[TimestampInput] = None,
269
+ venue_id: Optional[str] = None,
270
+ request_options: Optional[Mapping[str, Any]] = None,
271
+ **extra: Any,
272
+ ) -> ListIterator[Menu]:
273
+ query = dict(limit=limit, starting_after=starting_after, updated_since=updated_since, venue_id=venue_id, **extra)
274
+ return self._t.list("listMenus", query, **dict(request_options or {}))
275
+
276
+ def retrieve(self, id: str, *, request_options: Optional[Mapping[str, Any]] = None) -> Menu:
277
+ return self._t.call("getMenu", params={"id": id}, **dict(request_options or {}))
278
+
279
+
280
+ class Products(_Resource):
281
+ def list(
282
+ self,
283
+ *,
284
+ limit: Optional[int] = None,
285
+ starting_after: Optional[str] = None,
286
+ updated_since: Optional[TimestampInput] = None,
287
+ menu_id: Optional[str] = None,
288
+ request_options: Optional[Mapping[str, Any]] = None,
289
+ **extra: Any,
290
+ ) -> ListIterator[Product]:
291
+ query = dict(limit=limit, starting_after=starting_after, updated_since=updated_since, menu_id=menu_id, **extra)
292
+ return self._t.list("listProducts", query, **dict(request_options or {}))
293
+
294
+
295
+ class Meta(_Resource):
296
+ def ip_ranges(self, *, request_options: Optional[Mapping[str, Any]] = None) -> IpRanges:
297
+ return self._t.call("ipRanges", **dict(request_options or {}))