lumera 0.7.3__tar.gz → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumera
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: SDK for building on Lumera platform
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: requests
@@ -5,10 +5,10 @@ This SDK provides helpers for agents running within the Lumera Notebook environm
5
5
  to interact with the Lumera API and define dynamic user interfaces.
6
6
  """
7
7
 
8
- __version__ = "0.7.0"
8
+ __version__ = "0.8.0"
9
9
 
10
10
  # Import new modules (as modules, not individual functions)
11
- from . import automations, exceptions, llm, locks, pb, storage
11
+ from . import automations, exceptions, llm, locks, pb, storage, webhooks
12
12
  from ._utils import (
13
13
  LumeraAPIError,
14
14
  RecordNotUniqueError,
@@ -97,4 +97,5 @@ __all__ = [
97
97
  "llm",
98
98
  "locks",
99
99
  "exceptions",
100
+ "webhooks",
100
101
  ]
@@ -0,0 +1,295 @@
1
+ """
2
+ Webhook endpoint management for Lumera.
3
+
4
+ This module provides functions for managing webhook endpoints that receive
5
+ external events from third-party services (Stripe, GitHub, etc.).
6
+
7
+ Functions:
8
+ create() - Create a new webhook endpoint
9
+ list() - List all webhook endpoints
10
+ get() - Get endpoint by external_id
11
+ update() - Update an existing endpoint
12
+ delete() - Delete an endpoint
13
+ url() - Get the public webhook URL for an endpoint
14
+
15
+ Example:
16
+ >>> from lumera import webhooks
17
+ >>>
18
+ >>> # Create a webhook endpoint
19
+ >>> endpoint = webhooks.create(
20
+ ... name="Stripe Events",
21
+ ... external_id="stripe-events",
22
+ ... description="Receives Stripe payment webhooks"
23
+ ... )
24
+ >>>
25
+ >>> # Get the public URL to configure in Stripe
26
+ >>> webhook_url = webhooks.url("stripe-events")
27
+ >>> print(webhook_url)
28
+ https://app.lumerahq.com/webhooks/acme/stripe-events
29
+ >>>
30
+ >>> # List all endpoints
31
+ >>> for ep in webhooks.list():
32
+ ... print(ep["name"], ep["external_id"])
33
+
34
+ External ID Format:
35
+ The external_id is used as the URL slug and must follow these rules:
36
+ - 3-50 characters
37
+ - Lowercase alphanumeric and hyphens only
38
+ - Must start with a letter
39
+ - Cannot end with a hyphen
40
+ - No consecutive hyphens
41
+
42
+ Valid examples: "stripe-events", "github-webhooks", "acme-orders"
43
+ Invalid examples: "1-start", "end-", "double--hyphen"
44
+ """
45
+
46
+ import os
47
+ from typing import Any
48
+
49
+ __all__ = [
50
+ "create",
51
+ "list",
52
+ "get",
53
+ "update",
54
+ "delete",
55
+ "url",
56
+ ]
57
+
58
+ from ._utils import API_BASE, LumeraAPIError, _api_request
59
+
60
+ # Collection name for webhook endpoints
61
+ _COLLECTION = "lm_webhook_endpoints"
62
+
63
+
64
+ def create(
65
+ name: str,
66
+ external_id: str,
67
+ *,
68
+ description: str | None = None,
69
+ ) -> dict[str, Any]:
70
+ """Create a new webhook endpoint.
71
+
72
+ Args:
73
+ name: Human-readable name for the endpoint (e.g., "Stripe Events")
74
+ external_id: URL-safe identifier used in the webhook URL.
75
+ Must be 3-50 chars, lowercase alphanumeric with hyphens,
76
+ start with a letter. Example: "stripe-events"
77
+ description: Optional description of what this webhook receives
78
+
79
+ Returns:
80
+ Created endpoint record with id, external_id, name, etc.
81
+
82
+ Raises:
83
+ ValueError: If name or external_id is empty
84
+ LumeraAPIError: If external_id format is invalid or already exists
85
+
86
+ Example:
87
+ >>> endpoint = webhooks.create(
88
+ ... name="Stripe Events",
89
+ ... external_id="stripe-events",
90
+ ... description="Payment and subscription events from Stripe"
91
+ ... )
92
+ >>> print(endpoint["id"])
93
+ """
94
+ name = (name or "").strip()
95
+ external_id = (external_id or "").strip()
96
+
97
+ if not name:
98
+ raise ValueError("name is required")
99
+ if not external_id:
100
+ raise ValueError("external_id is required")
101
+
102
+ payload: dict[str, Any] = {
103
+ "name": name,
104
+ "external_id": external_id,
105
+ }
106
+ if description is not None:
107
+ payload["description"] = description.strip()
108
+
109
+ result = _api_request("POST", f"collections/{_COLLECTION}/records", json_body=payload)
110
+ if not isinstance(result, dict):
111
+ raise RuntimeError("unexpected response payload")
112
+ return result
113
+
114
+
115
+ def list(
116
+ *,
117
+ per_page: int = 100,
118
+ page: int = 1,
119
+ ) -> list[dict[str, Any]]:
120
+ """List all webhook endpoints.
121
+
122
+ Args:
123
+ per_page: Number of results per page (default 100, max 500)
124
+ page: Page number, 1-indexed (default 1)
125
+
126
+ Returns:
127
+ List of endpoint records
128
+
129
+ Example:
130
+ >>> endpoints = webhooks.list()
131
+ >>> for ep in endpoints:
132
+ ... print(f"{ep['name']}: {ep['external_id']}")
133
+ """
134
+ params: dict[str, Any] = {
135
+ "perPage": per_page,
136
+ "page": page,
137
+ "sort": "-created",
138
+ }
139
+
140
+ result = _api_request("GET", f"collections/{_COLLECTION}/records", params=params)
141
+ if not isinstance(result, dict):
142
+ return []
143
+ return result.get("items", [])
144
+
145
+
146
+ def get(external_id: str) -> dict[str, Any]:
147
+ """Get a webhook endpoint by its external_id.
148
+
149
+ Args:
150
+ external_id: The external_id of the endpoint (e.g., "stripe-events")
151
+
152
+ Returns:
153
+ Endpoint record
154
+
155
+ Raises:
156
+ ValueError: If external_id is empty
157
+ LumeraAPIError: If endpoint not found (404)
158
+
159
+ Example:
160
+ >>> endpoint = webhooks.get("stripe-events")
161
+ >>> print(endpoint["name"])
162
+ Stripe Events
163
+ """
164
+ external_id = (external_id or "").strip()
165
+ if not external_id:
166
+ raise ValueError("external_id is required")
167
+
168
+ import json
169
+
170
+ params = {"filter": json.dumps({"external_id": external_id}), "perPage": 1}
171
+ result = _api_request("GET", f"collections/{_COLLECTION}/records", params=params)
172
+
173
+ if not isinstance(result, dict):
174
+ raise LumeraAPIError(404, "endpoint not found", url=f"collections/{_COLLECTION}/records", payload=None)
175
+
176
+ items = result.get("items", [])
177
+ if not items:
178
+ raise LumeraAPIError(404, f"webhook endpoint '{external_id}' not found", url=f"collections/{_COLLECTION}/records", payload=None)
179
+
180
+ return items[0]
181
+
182
+
183
+ def update(
184
+ external_id: str,
185
+ *,
186
+ name: str | None = None,
187
+ description: str | None = None,
188
+ ) -> dict[str, Any]:
189
+ """Update a webhook endpoint.
190
+
191
+ Args:
192
+ external_id: The external_id of the endpoint to update
193
+ name: New name (optional)
194
+ description: New description (optional)
195
+
196
+ Returns:
197
+ Updated endpoint record
198
+
199
+ Raises:
200
+ ValueError: If external_id is empty or no fields to update
201
+ LumeraAPIError: If endpoint not found
202
+
203
+ Example:
204
+ >>> endpoint = webhooks.update(
205
+ ... "stripe-events",
206
+ ... description="Updated: Now includes refund events"
207
+ ... )
208
+ """
209
+ external_id = (external_id or "").strip()
210
+ if not external_id:
211
+ raise ValueError("external_id is required")
212
+
213
+ # First, find the endpoint to get its record ID
214
+ endpoint = get(external_id)
215
+ record_id = endpoint["id"]
216
+
217
+ payload: dict[str, Any] = {}
218
+ if name is not None:
219
+ payload["name"] = name.strip()
220
+ if description is not None:
221
+ payload["description"] = description.strip()
222
+
223
+ if not payload:
224
+ raise ValueError("at least one field (name or description) must be provided")
225
+
226
+ result = _api_request("PATCH", f"collections/{_COLLECTION}/records/{record_id}", json_body=payload)
227
+ if not isinstance(result, dict):
228
+ raise RuntimeError("unexpected response payload")
229
+ return result
230
+
231
+
232
+ def delete(external_id: str) -> None:
233
+ """Delete a webhook endpoint.
234
+
235
+ Args:
236
+ external_id: The external_id of the endpoint to delete
237
+
238
+ Raises:
239
+ ValueError: If external_id is empty
240
+ LumeraAPIError: If endpoint not found
241
+
242
+ Example:
243
+ >>> webhooks.delete("old-stripe-endpoint")
244
+ """
245
+ external_id = (external_id or "").strip()
246
+ if not external_id:
247
+ raise ValueError("external_id is required")
248
+
249
+ # First, find the endpoint to get its record ID
250
+ endpoint = get(external_id)
251
+ record_id = endpoint["id"]
252
+
253
+ _api_request("DELETE", f"collections/{_COLLECTION}/records/{record_id}")
254
+
255
+
256
+ def url(external_id: str) -> str:
257
+ """Get the public webhook URL for an endpoint.
258
+
259
+ This returns the full URL that external services should send webhooks to.
260
+ The URL format is: https://{base}/webhooks/{company_api}/{external_id}
261
+
262
+ Args:
263
+ external_id: The external_id of the endpoint
264
+
265
+ Returns:
266
+ Full public webhook URL
267
+
268
+ Raises:
269
+ ValueError: If external_id is empty
270
+ RuntimeError: If COMPANY_API_NAME environment variable is not set
271
+
272
+ Example:
273
+ >>> url = webhooks.url("stripe-events")
274
+ >>> print(url)
275
+ https://app.lumerahq.com/webhooks/acme/stripe-events
276
+
277
+ # Use this URL when configuring webhooks in Stripe, GitHub, etc.
278
+ """
279
+ external_id = (external_id or "").strip()
280
+ if not external_id:
281
+ raise ValueError("external_id is required")
282
+
283
+ company_api = os.getenv("COMPANY_API_NAME", "").strip()
284
+ if not company_api:
285
+ raise RuntimeError(
286
+ "COMPANY_API_NAME environment variable not set. "
287
+ "This is required to construct the webhook URL."
288
+ )
289
+
290
+ # API_BASE is like "https://app.lumerahq.com/api" - we need the base without /api
291
+ base_url = API_BASE.rstrip("/")
292
+ if base_url.endswith("/api"):
293
+ base_url = base_url[:-4]
294
+
295
+ return f"{base_url}/webhooks/{company_api}/{external_id}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumera
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: SDK for building on Lumera platform
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: requests
@@ -9,6 +9,7 @@ lumera/locks.py
9
9
  lumera/pb.py
10
10
  lumera/sdk.py
11
11
  lumera/storage.py
12
+ lumera/webhooks.py
12
13
  lumera.egg-info/PKG-INFO
13
14
  lumera.egg-info/SOURCES.txt
14
15
  lumera.egg-info/dependency_links.txt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lumera"
3
- version = "0.7.3"
3
+ version = "0.8.0"
4
4
  description = "SDK for building on Lumera platform"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
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