lumera 0.4.6__py3-none-any.whl → 0.9.4__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.
- lumera/__init__.py +94 -4
- lumera/_utils.py +782 -0
- lumera/automations.py +904 -0
- lumera/exceptions.py +72 -0
- lumera/google.py +47 -270
- lumera/integrations/__init__.py +34 -0
- lumera/integrations/google.py +338 -0
- lumera/llm.py +481 -0
- lumera/locks.py +216 -0
- lumera/pb.py +670 -0
- lumera/sdk.py +921 -381
- lumera/storage.py +270 -0
- lumera/webhooks.py +304 -0
- lumera-0.9.4.dist-info/METADATA +37 -0
- lumera-0.9.4.dist-info/RECORD +17 -0
- {lumera-0.4.6.dist-info → lumera-0.9.4.dist-info}/WHEEL +1 -1
- lumera-0.4.6.dist-info/METADATA +0 -11
- lumera-0.4.6.dist-info/RECORD +0 -7
- {lumera-0.4.6.dist-info → lumera-0.9.4.dist-info}/top_level.txt +0 -0
lumera/pb.py
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Record and collection operations for Lumera.
|
|
3
|
+
|
|
4
|
+
This module provides a clean interface for working with Lumera collections,
|
|
5
|
+
using the `pb.` namespace convention familiar from automation contexts.
|
|
6
|
+
|
|
7
|
+
Record functions:
|
|
8
|
+
search() - Query records with filters, pagination, sorting
|
|
9
|
+
get() - Get single record by ID
|
|
10
|
+
get_by_external_id() - Get record by external_id field
|
|
11
|
+
create() - Create new record
|
|
12
|
+
update() - Update existing record
|
|
13
|
+
upsert() - Create or update by external_id
|
|
14
|
+
delete() - Delete record
|
|
15
|
+
iter_all() - Iterate all matching records (auto-pagination)
|
|
16
|
+
|
|
17
|
+
Collection functions:
|
|
18
|
+
list_collections() - List all collections
|
|
19
|
+
get_collection() - Get collection by name
|
|
20
|
+
ensure_collection() - Create or update collection (idempotent)
|
|
21
|
+
create_collection() - Create new collection
|
|
22
|
+
update_collection() - Update collection schema
|
|
23
|
+
delete_collection() - Delete collection
|
|
24
|
+
|
|
25
|
+
Filter Syntax:
|
|
26
|
+
Filters can be passed as dict or JSON string to search() and iter_all().
|
|
27
|
+
|
|
28
|
+
Simple equality:
|
|
29
|
+
{"status": "pending"}
|
|
30
|
+
{"external_id": "dep-001"}
|
|
31
|
+
|
|
32
|
+
Comparison operators (eq, gt, gte, lt, lte):
|
|
33
|
+
{"amount": {"gt": 1000}}
|
|
34
|
+
{"amount": {"gte": 100, "lte": 500}}
|
|
35
|
+
|
|
36
|
+
OR logic:
|
|
37
|
+
{"or": [{"status": "pending"}, {"status": "review"}]}
|
|
38
|
+
|
|
39
|
+
AND logic (implicit - multiple fields at same level):
|
|
40
|
+
{"status": "pending", "amount": {"gt": 1000}}
|
|
41
|
+
|
|
42
|
+
Combined:
|
|
43
|
+
{"status": "active", "or": [{"priority": "high"}, {"amount": {"gt": 5000}}]}
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> from lumera import pb
|
|
47
|
+
>>> results = pb.search("deposits", filter={"status": "pending"})
|
|
48
|
+
>>> deposit = pb.get("deposits", "rec_abc123")
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
import warnings
|
|
52
|
+
from typing import Any, Iterator, Mapping, Sequence
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Record operations
|
|
56
|
+
"search",
|
|
57
|
+
"get",
|
|
58
|
+
"get_by_external_id",
|
|
59
|
+
"create",
|
|
60
|
+
"update",
|
|
61
|
+
"upsert",
|
|
62
|
+
"delete",
|
|
63
|
+
"iter_all",
|
|
64
|
+
# Bulk operations
|
|
65
|
+
"bulk_delete",
|
|
66
|
+
"bulk_update",
|
|
67
|
+
"bulk_upsert",
|
|
68
|
+
"bulk_insert",
|
|
69
|
+
# Collection operations
|
|
70
|
+
"list_collections",
|
|
71
|
+
"get_collection",
|
|
72
|
+
"ensure_collection",
|
|
73
|
+
"create_collection",
|
|
74
|
+
"update_collection",
|
|
75
|
+
"delete_collection",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Import underlying SDK functions (prefixed with _ to indicate internal use)
|
|
79
|
+
from ._utils import LumeraAPIError
|
|
80
|
+
from .sdk import (
|
|
81
|
+
bulk_delete_records as _bulk_delete_records,
|
|
82
|
+
)
|
|
83
|
+
from .sdk import (
|
|
84
|
+
bulk_insert_records as _bulk_insert_records,
|
|
85
|
+
)
|
|
86
|
+
from .sdk import (
|
|
87
|
+
bulk_update_records as _bulk_update_records,
|
|
88
|
+
)
|
|
89
|
+
from .sdk import (
|
|
90
|
+
bulk_upsert_records as _bulk_upsert_records,
|
|
91
|
+
)
|
|
92
|
+
from .sdk import (
|
|
93
|
+
create_record as _create_record,
|
|
94
|
+
)
|
|
95
|
+
from .sdk import (
|
|
96
|
+
delete_collection as _delete_collection,
|
|
97
|
+
)
|
|
98
|
+
from .sdk import (
|
|
99
|
+
delete_record as _delete_record,
|
|
100
|
+
)
|
|
101
|
+
from .sdk import (
|
|
102
|
+
ensure_collection as _ensure_collection,
|
|
103
|
+
)
|
|
104
|
+
from .sdk import (
|
|
105
|
+
get_collection as _get_collection,
|
|
106
|
+
)
|
|
107
|
+
from .sdk import (
|
|
108
|
+
get_record as _get_record,
|
|
109
|
+
)
|
|
110
|
+
from .sdk import (
|
|
111
|
+
get_record_by_external_id as _get_record_by_external_id,
|
|
112
|
+
)
|
|
113
|
+
from .sdk import (
|
|
114
|
+
list_collections as _list_collections,
|
|
115
|
+
)
|
|
116
|
+
from .sdk import (
|
|
117
|
+
list_records as _list_records,
|
|
118
|
+
)
|
|
119
|
+
from .sdk import (
|
|
120
|
+
update_record as _update_record,
|
|
121
|
+
)
|
|
122
|
+
from .sdk import (
|
|
123
|
+
upsert_record as _upsert_record,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def search(
|
|
128
|
+
collection: str,
|
|
129
|
+
*,
|
|
130
|
+
filter: Mapping[str, Any] | str | None = None,
|
|
131
|
+
per_page: int = 50,
|
|
132
|
+
page: int = 1,
|
|
133
|
+
sort: str | None = None,
|
|
134
|
+
expand: str | None = None,
|
|
135
|
+
) -> dict[str, Any]:
|
|
136
|
+
"""Search records in a collection.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
collection: Collection name or ID
|
|
140
|
+
filter: Filter as dict or JSON string. See module docstring for syntax.
|
|
141
|
+
Examples:
|
|
142
|
+
- {"status": "pending"} - equality
|
|
143
|
+
- {"amount": {"gt": 1000}} - comparison
|
|
144
|
+
- {"or": [{"status": "a"}, {"status": "b"}]} - OR logic
|
|
145
|
+
per_page: Results per page (max 500, default 50)
|
|
146
|
+
page: Page number, 1-indexed (default 1)
|
|
147
|
+
sort: Sort expression (e.g., "-created,name" for created DESC, name ASC)
|
|
148
|
+
expand: Comma-separated relation fields to expand (e.g., "user_id,company_id")
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Paginated results with structure:
|
|
152
|
+
{
|
|
153
|
+
"items": [...], # List of records
|
|
154
|
+
"page": 1,
|
|
155
|
+
"perPage": 50,
|
|
156
|
+
"totalItems": 100,
|
|
157
|
+
"totalPages": 2
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> results = pb.search("deposits",
|
|
162
|
+
... filter={"status": "pending", "amount": {"gt": 1000}},
|
|
163
|
+
... per_page=100,
|
|
164
|
+
... sort="-created"
|
|
165
|
+
... )
|
|
166
|
+
>>> for deposit in results["items"]:
|
|
167
|
+
... print(deposit["id"], deposit["amount"])
|
|
168
|
+
"""
|
|
169
|
+
# Note: _list_records handles both dict and str filters via JSON encoding
|
|
170
|
+
return _list_records(
|
|
171
|
+
collection,
|
|
172
|
+
filter=filter,
|
|
173
|
+
per_page=per_page,
|
|
174
|
+
page=page,
|
|
175
|
+
sort=sort,
|
|
176
|
+
expand=expand,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get(collection: str, record_id: str) -> dict[str, Any]:
|
|
181
|
+
"""Get a single record by ID.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
collection: Collection name or ID
|
|
185
|
+
record_id: Record ID (15-character alphanumeric ID)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Record data with all fields
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
LumeraAPIError: If record doesn't exist (404)
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
>>> deposit = pb.get("deposits", "dep_abc123")
|
|
195
|
+
>>> print(deposit["amount"])
|
|
196
|
+
"""
|
|
197
|
+
return _get_record(collection, record_id)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_by_external_id(collection: str, external_id: str) -> dict[str, Any]:
|
|
201
|
+
"""Get a single record by external_id (unique field).
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
collection: Collection name or ID
|
|
205
|
+
external_id: Value of the external_id field (your business identifier)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Record data
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
LumeraAPIError: If no record with that external_id (404)
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
>>> deposit = pb.get_by_external_id("deposits", "dep-2024-001")
|
|
215
|
+
"""
|
|
216
|
+
return _get_record_by_external_id(collection, external_id)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def create(collection: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
220
|
+
"""Create a new record.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
collection: Collection name or ID
|
|
224
|
+
data: Record data as dict mapping field names to values
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Created record with id, created, and updated timestamps
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
LumeraAPIError: If validation fails or unique constraint violated
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
>>> deposit = pb.create("deposits", {
|
|
234
|
+
... "external_id": "dep-001",
|
|
235
|
+
... "amount": 1000,
|
|
236
|
+
... "status": "pending"
|
|
237
|
+
... })
|
|
238
|
+
>>> print(deposit["id"])
|
|
239
|
+
"""
|
|
240
|
+
return _create_record(collection, data)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def update(collection: str, record_id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
244
|
+
"""Update an existing record (partial update).
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
collection: Collection name or ID
|
|
248
|
+
record_id: Record ID to update
|
|
249
|
+
data: Fields to update (only include fields you want to change)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Updated record
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
LumeraAPIError: If record doesn't exist or validation fails
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
>>> deposit = pb.update("deposits", "dep_abc123", {
|
|
259
|
+
... "status": "processed",
|
|
260
|
+
... "processed_at": datetime.utcnow().isoformat()
|
|
261
|
+
... })
|
|
262
|
+
"""
|
|
263
|
+
return _update_record(collection, record_id, data)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def upsert(collection: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
267
|
+
"""Create or update a record by external_id.
|
|
268
|
+
|
|
269
|
+
If a record with the given external_id exists, updates it.
|
|
270
|
+
Otherwise, creates a new record. This is useful for idempotent imports.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
collection: Collection name or ID
|
|
274
|
+
data: Record data (MUST include "external_id" field)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Created or updated record
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
ValueError: If data doesn't contain "external_id"
|
|
281
|
+
LumeraAPIError: If validation fails
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
>>> deposit = pb.upsert("deposits", {
|
|
285
|
+
... "external_id": "dep-001",
|
|
286
|
+
... "amount": 1000,
|
|
287
|
+
... "status": "pending"
|
|
288
|
+
... })
|
|
289
|
+
>>> # Second call updates the existing record
|
|
290
|
+
>>> deposit = pb.upsert("deposits", {
|
|
291
|
+
... "external_id": "dep-001",
|
|
292
|
+
... "amount": 2000
|
|
293
|
+
... })
|
|
294
|
+
"""
|
|
295
|
+
return _upsert_record(collection, data)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def delete(collection: str, record_id: str) -> None:
|
|
299
|
+
"""Delete a record.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
collection: Collection name or ID
|
|
303
|
+
record_id: Record ID to delete
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
LumeraAPIError: If record doesn't exist
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
>>> pb.delete("deposits", "dep_abc123")
|
|
310
|
+
"""
|
|
311
|
+
_delete_record(collection, record_id)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# =============================================================================
|
|
315
|
+
# Bulk Operations
|
|
316
|
+
# =============================================================================
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def bulk_delete(
|
|
320
|
+
collection: str,
|
|
321
|
+
record_ids: Sequence[str],
|
|
322
|
+
*,
|
|
323
|
+
transaction: bool = False,
|
|
324
|
+
) -> dict[str, Any]:
|
|
325
|
+
"""Delete multiple records by ID.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
collection: Collection name or ID
|
|
329
|
+
record_ids: List of record IDs to delete (max 1000)
|
|
330
|
+
transaction: If True, use all-or-nothing semantics (rollback on any failure).
|
|
331
|
+
If False (default), partial success is allowed.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Result with succeeded/failed counts and any errors:
|
|
335
|
+
{
|
|
336
|
+
"succeeded": 10,
|
|
337
|
+
"failed": 0,
|
|
338
|
+
"errors": [],
|
|
339
|
+
"rolled_back": false # only present if transaction=True and failed
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
>>> result = pb.bulk_delete("deposits", ["id1", "id2", "id3"])
|
|
344
|
+
>>> print(f"Deleted {result['succeeded']} records")
|
|
345
|
+
|
|
346
|
+
>>> # Use transaction mode for all-or-nothing
|
|
347
|
+
>>> result = pb.bulk_delete("deposits", ids, transaction=True)
|
|
348
|
+
"""
|
|
349
|
+
return _bulk_delete_records(collection, record_ids, transaction=transaction)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def bulk_update(
|
|
353
|
+
collection: str,
|
|
354
|
+
records: Sequence[dict[str, Any]],
|
|
355
|
+
*,
|
|
356
|
+
transaction: bool = False,
|
|
357
|
+
) -> dict[str, Any]:
|
|
358
|
+
"""Update multiple records with individual data per record.
|
|
359
|
+
|
|
360
|
+
Each record must have an 'id' field plus fields to update.
|
|
361
|
+
Records must exist (returns error if not found, does not create).
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
collection: Collection name or ID
|
|
365
|
+
records: List of records to update (max 1000). Each must have 'id' field.
|
|
366
|
+
transaction: If True, use all-or-nothing semantics (rollback on any failure).
|
|
367
|
+
If False (default), partial success is allowed.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Result with succeeded/failed counts:
|
|
371
|
+
{
|
|
372
|
+
"succeeded": 2,
|
|
373
|
+
"failed": 0,
|
|
374
|
+
"errors": [],
|
|
375
|
+
"rolled_back": false # only present if transaction=True and failed
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
Example:
|
|
379
|
+
>>> result = pb.bulk_update("deposits", [
|
|
380
|
+
... {"id": "rec1", "status": "approved", "amount": 100},
|
|
381
|
+
... {"id": "rec2", "status": "rejected", "amount": 200},
|
|
382
|
+
... ])
|
|
383
|
+
>>> print(f"Updated {result['succeeded']} records")
|
|
384
|
+
|
|
385
|
+
>>> # Use transaction mode for all-or-nothing
|
|
386
|
+
>>> result = pb.bulk_update("deposits", records, transaction=True)
|
|
387
|
+
"""
|
|
388
|
+
return _bulk_update_records(collection, records, transaction=transaction)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def bulk_upsert(
|
|
392
|
+
collection: str,
|
|
393
|
+
records: Sequence[dict[str, Any]],
|
|
394
|
+
*,
|
|
395
|
+
transaction: bool = False,
|
|
396
|
+
) -> dict[str, Any]:
|
|
397
|
+
"""Create or update multiple records by ID.
|
|
398
|
+
|
|
399
|
+
Each record can include an "id" field. Records with matching IDs will be
|
|
400
|
+
updated; records without IDs or with non-existent IDs create new records.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
collection: Collection name or ID
|
|
404
|
+
records: List of records (max 1000)
|
|
405
|
+
transaction: If True, use all-or-nothing semantics (rollback on any failure).
|
|
406
|
+
If False (default), partial success is allowed.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Result with succeeded/failed counts and created record IDs:
|
|
410
|
+
{
|
|
411
|
+
"succeeded": 2,
|
|
412
|
+
"failed": 0,
|
|
413
|
+
"errors": [],
|
|
414
|
+
"records": [{"id": "..."}, ...],
|
|
415
|
+
"rolled_back": false # only present if transaction=True and failed
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
Example:
|
|
419
|
+
>>> result = pb.bulk_upsert("deposits", [
|
|
420
|
+
... {"id": "existing_id", "amount": 100},
|
|
421
|
+
... {"amount": 200}, # creates new record
|
|
422
|
+
... ])
|
|
423
|
+
|
|
424
|
+
>>> # Use transaction mode for all-or-nothing
|
|
425
|
+
>>> result = pb.bulk_upsert("deposits", records, transaction=True)
|
|
426
|
+
"""
|
|
427
|
+
return _bulk_upsert_records(collection, records, transaction=transaction)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def bulk_insert(
|
|
431
|
+
collection: str,
|
|
432
|
+
records: Sequence[dict[str, Any]],
|
|
433
|
+
*,
|
|
434
|
+
transaction: bool = False,
|
|
435
|
+
) -> dict[str, Any]:
|
|
436
|
+
"""Insert multiple new records.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
collection: Collection name or ID
|
|
440
|
+
records: List of records to create (max 1000)
|
|
441
|
+
transaction: If True, use all-or-nothing semantics (rollback on any failure).
|
|
442
|
+
If False (default), partial success is allowed.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Result with succeeded/failed counts and created record IDs:
|
|
446
|
+
{
|
|
447
|
+
"succeeded": 2,
|
|
448
|
+
"failed": 0,
|
|
449
|
+
"errors": [],
|
|
450
|
+
"records": [{"id": "..."}, {"id": "..."}],
|
|
451
|
+
"rolled_back": false # only present if transaction=True and failed
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
Example:
|
|
455
|
+
>>> result = pb.bulk_insert("deposits", [
|
|
456
|
+
... {"external_id": "dep-001", "amount": 100},
|
|
457
|
+
... {"external_id": "dep-002", "amount": 200},
|
|
458
|
+
... ])
|
|
459
|
+
>>> for rec in result["records"]:
|
|
460
|
+
... print(f"Created: {rec['id']}")
|
|
461
|
+
|
|
462
|
+
>>> # Use transaction mode for all-or-nothing
|
|
463
|
+
>>> result = pb.bulk_insert("deposits", records, transaction=True)
|
|
464
|
+
"""
|
|
465
|
+
return _bulk_insert_records(collection, records, transaction=transaction)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def iter_all(
|
|
469
|
+
collection: str,
|
|
470
|
+
*,
|
|
471
|
+
filter: Mapping[str, Any] | str | None = None,
|
|
472
|
+
sort: str | None = None,
|
|
473
|
+
expand: str | None = None,
|
|
474
|
+
batch_size: int = 500,
|
|
475
|
+
) -> Iterator[dict[str, Any]]:
|
|
476
|
+
"""Iterate over all matching records, handling pagination automatically.
|
|
477
|
+
|
|
478
|
+
This is a convenience function for processing large result sets without
|
|
479
|
+
manually handling pagination. Use this instead of manual page loops.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
collection: Collection name or ID
|
|
483
|
+
filter: Optional filter as dict or JSON string (e.g., {"status": "pending"})
|
|
484
|
+
sort: Optional sort expression (e.g., "-created" for newest first)
|
|
485
|
+
expand: Optional comma-separated relation fields to expand
|
|
486
|
+
batch_size: Records per API request (max 500, default 500)
|
|
487
|
+
|
|
488
|
+
Yields:
|
|
489
|
+
Individual records
|
|
490
|
+
|
|
491
|
+
Example:
|
|
492
|
+
>>> for deposit in pb.iter_all("deposits", filter={"status": "pending"}):
|
|
493
|
+
... process(deposit)
|
|
494
|
+
"""
|
|
495
|
+
page = 1
|
|
496
|
+
while True:
|
|
497
|
+
result = search(
|
|
498
|
+
collection,
|
|
499
|
+
filter=filter,
|
|
500
|
+
per_page=batch_size,
|
|
501
|
+
page=page,
|
|
502
|
+
sort=sort,
|
|
503
|
+
expand=expand,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
items = result.get("items", [])
|
|
507
|
+
if not items:
|
|
508
|
+
break
|
|
509
|
+
|
|
510
|
+
yield from items
|
|
511
|
+
|
|
512
|
+
# Check if there are more pages
|
|
513
|
+
total_pages = result.get("totalPages", 0)
|
|
514
|
+
if page >= total_pages:
|
|
515
|
+
break
|
|
516
|
+
|
|
517
|
+
page += 1
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# =============================================================================
|
|
521
|
+
# Collection Management Functions
|
|
522
|
+
# =============================================================================
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def list_collections() -> list[dict[str, Any]]:
|
|
526
|
+
"""List all collections.
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
List of collection objects with id, name, schema, etc.
|
|
530
|
+
|
|
531
|
+
Example:
|
|
532
|
+
>>> collections = pb.list_collections()
|
|
533
|
+
>>> for col in collections:
|
|
534
|
+
... print(col["name"])
|
|
535
|
+
"""
|
|
536
|
+
result = _list_collections()
|
|
537
|
+
return result.get("items", [])
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def get_collection(name: str) -> dict[str, Any] | None:
|
|
541
|
+
"""Get a collection by name.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
name: Collection name
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Collection object if found, None if not found
|
|
548
|
+
|
|
549
|
+
Example:
|
|
550
|
+
>>> col = pb.get_collection("deposits")
|
|
551
|
+
>>> if col:
|
|
552
|
+
... print(col["schema"])
|
|
553
|
+
"""
|
|
554
|
+
try:
|
|
555
|
+
return _get_collection(name)
|
|
556
|
+
except LumeraAPIError as e:
|
|
557
|
+
if e.status_code == 404:
|
|
558
|
+
return None
|
|
559
|
+
raise
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def ensure_collection(
|
|
563
|
+
name: str,
|
|
564
|
+
schema: Sequence[dict[str, Any]] | None = None,
|
|
565
|
+
*,
|
|
566
|
+
indexes: Sequence[str] | None = None,
|
|
567
|
+
) -> dict[str, Any]:
|
|
568
|
+
"""Ensure a collection exists with the given schema (idempotent).
|
|
569
|
+
|
|
570
|
+
This is the recommended way to manage collections. It creates the collection
|
|
571
|
+
if it doesn't exist, or updates it if it does. Safe to call multiple times.
|
|
572
|
+
|
|
573
|
+
IMPORTANT: The schema and indexes are declarative:
|
|
574
|
+
- schema: The COMPLETE list of user fields you want (replaces all existing user fields)
|
|
575
|
+
- indexes: The COMPLETE list of user indexes you want (replaces all existing user indexes)
|
|
576
|
+
- System fields (id, created, updated, etc.) are automatically managed
|
|
577
|
+
- System indexes (external_id, updated) are automatically managed
|
|
578
|
+
|
|
579
|
+
If you omit schema or indexes, existing values are preserved.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
name: Collection name (must not start with '_')
|
|
583
|
+
schema: List of field definitions. If provided, replaces all user fields.
|
|
584
|
+
Each field is a dict with:
|
|
585
|
+
- name: Field name (required)
|
|
586
|
+
- type: Field type (required): text, number, bool, date, json,
|
|
587
|
+
relation, select, editor, lumera_file
|
|
588
|
+
- required: Whether field is required (default False)
|
|
589
|
+
- options: Type-specific options (e.g., collectionId for relations)
|
|
590
|
+
indexes: Optional list of user index DDL statements. If provided,
|
|
591
|
+
replaces all user indexes.
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
Collection object with:
|
|
595
|
+
- schema: User-defined fields only (what you can modify)
|
|
596
|
+
- indexes: User-defined indexes only (what you can modify)
|
|
597
|
+
- systemInfo: Read-only system fields and indexes (automatically managed)
|
|
598
|
+
|
|
599
|
+
Example:
|
|
600
|
+
>>> # Create or update a collection
|
|
601
|
+
>>> col = pb.ensure_collection("deposits", [
|
|
602
|
+
... {"name": "amount", "type": "number", "required": True},
|
|
603
|
+
... {"name": "status", "type": "text"},
|
|
604
|
+
... ])
|
|
605
|
+
>>>
|
|
606
|
+
>>> # Add a field using copy-modify-send pattern
|
|
607
|
+
>>> col = pb.get_collection("deposits")
|
|
608
|
+
>>> col["schema"].append({"name": "notes", "type": "text"})
|
|
609
|
+
>>> pb.ensure_collection("deposits", col["schema"])
|
|
610
|
+
>>>
|
|
611
|
+
>>> # Add an index
|
|
612
|
+
>>> pb.ensure_collection("deposits", indexes=[
|
|
613
|
+
... "CREATE INDEX idx_status ON deposits (status)"
|
|
614
|
+
... ])
|
|
615
|
+
"""
|
|
616
|
+
return _ensure_collection(name, schema=schema, indexes=indexes)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def delete_collection(name: str) -> None:
|
|
620
|
+
"""Delete a collection.
|
|
621
|
+
|
|
622
|
+
Args:
|
|
623
|
+
name: Collection name to delete
|
|
624
|
+
|
|
625
|
+
Raises:
|
|
626
|
+
LumeraAPIError: If collection doesn't exist or is protected
|
|
627
|
+
|
|
628
|
+
Example:
|
|
629
|
+
>>> pb.delete_collection("old_deposits")
|
|
630
|
+
"""
|
|
631
|
+
_delete_collection(name)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
# Backwards compatibility aliases
|
|
635
|
+
def create_collection(
|
|
636
|
+
name: str,
|
|
637
|
+
schema: Sequence[dict[str, Any]],
|
|
638
|
+
*,
|
|
639
|
+
indexes: Sequence[str] | None = None,
|
|
640
|
+
) -> dict[str, Any]:
|
|
641
|
+
"""Create a new collection.
|
|
642
|
+
|
|
643
|
+
.. deprecated::
|
|
644
|
+
Use :func:`ensure_collection` instead, which handles both create and update.
|
|
645
|
+
"""
|
|
646
|
+
warnings.warn(
|
|
647
|
+
"create_collection() is deprecated, use ensure_collection() instead",
|
|
648
|
+
DeprecationWarning,
|
|
649
|
+
stacklevel=2,
|
|
650
|
+
)
|
|
651
|
+
return ensure_collection(name, schema, indexes=indexes)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def update_collection(
|
|
655
|
+
name: str,
|
|
656
|
+
schema: Sequence[dict[str, Any]],
|
|
657
|
+
*,
|
|
658
|
+
indexes: Sequence[str] | None = None,
|
|
659
|
+
) -> dict[str, Any]:
|
|
660
|
+
"""Update a collection's schema.
|
|
661
|
+
|
|
662
|
+
.. deprecated::
|
|
663
|
+
Use :func:`ensure_collection` instead, which handles both create and update.
|
|
664
|
+
"""
|
|
665
|
+
warnings.warn(
|
|
666
|
+
"update_collection() is deprecated, use ensure_collection() instead",
|
|
667
|
+
DeprecationWarning,
|
|
668
|
+
stacklevel=2,
|
|
669
|
+
)
|
|
670
|
+
return ensure_collection(name, schema, indexes=indexes)
|