You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A fast, async Python client for the [Bubble Data API](https://manual.bubble.io/core-resources/api/the-bubble-api/the-data-api) with a Pydantic-based ORM and connection pooling.
12
+
**Query your Bubble.io database from Python.**A fast, async client and Pydantic ORM for the [Bubble Data API](https://manual.bubble.io/core-resources/api/the-bubble-api/the-data-api), with type-safe CRUD, connection pooling, and configurable retries.
10
13
11
14
## Why use this?
12
15
@@ -102,11 +105,12 @@ The library handles Bubble's API quirks automatically:
102
105
Bubble doesn't enforce unique constraints, so duplicates can occur. The `create_or_update` method provides strategies to handle this:
103
106
104
107
```python
105
-
#If duplicates exist, keep the oldest and delete the rest
108
+
#if duplicates exist, keep the oldest (by created date) and delete the rest
106
109
user, created =await User.create_or_update(
107
110
match={"external_id": "ext-123"},
108
-
data={"name": "Canonical Name"},
109
-
on_multiple=OnMultiple.DEDUPE_OLDEST,
111
+
create_data={"name": "Canonical Name"},
112
+
update_data={"name": "Canonical Name"},
113
+
on_multiple=OnMultiple.DEDUPE_OLDEST_CREATED,
110
114
)
111
115
```
112
116
@@ -222,12 +226,15 @@ from bubble_data_api_client import OnMultiple
222
226
# basic upsert, matches by external_id and creates if not found
create_data={"name": "New User", "email": "new@example.com"},
230
+
update_data={"email": "new@example.com"},
226
231
on_multiple=OnMultiple.ERROR,
227
232
)
228
233
# returns (User, bool): the instance and whether it was created
229
234
```
230
235
236
+
`match` fields locate the record. `create_data` is merged with `match` when inserting a new record; `update_data` is applied when a record already exists. At least one of `create_data` or `update_data` must be provided.
237
+
231
238
### Duplicate Handling Strategies
232
239
233
240
Since Bubble doesn't enforce unique constraints, duplicates can occur. Choose how to handle them:
@@ -237,15 +244,18 @@ Since Bubble doesn't enforce unique constraints, duplicates can occur. Choose ho
|`OnMultiple.UPDATE_FIRST`| Update first match (arbitrary order) |
239
246
|`OnMultiple.UPDATE_ALL`| Update all matches concurrently |
240
-
|`OnMultiple.DEDUPE_OLDEST`| Keep oldest record, delete others, then update |
241
-
|`OnMultiple.DEDUPE_NEWEST`| Keep newest record, delete others, then update |
247
+
|`OnMultiple.DEDUPE_OLDEST_CREATED`| Keep oldest by Created Date, delete others, then update |
248
+
|`OnMultiple.DEDUPE_NEWEST_CREATED`| Keep newest by Created Date, delete others, then update |
249
+
|`OnMultiple.DEDUPE_OLDEST_MODIFIED`| Keep oldest by Modified Date, delete others, then update |
250
+
|`OnMultiple.DEDUPE_NEWEST_MODIFIED`| Keep newest by Modified Date, delete others, then update |
242
251
243
252
```python
244
-
# auto-deduplicate, keeping the oldest record
253
+
# auto-deduplicate, keeping the oldest record by Created Date
245
254
user, created =await User.create_or_update(
246
255
match={"external_id": "ext-123"},
247
-
data={"name": "Canonical Name"},
248
-
on_multiple=OnMultiple.DEDUPE_OLDEST,
256
+
create_data={"name": "Canonical Name"},
257
+
update_data={"name": "Canonical Name"},
258
+
on_multiple=OnMultiple.DEDUPE_OLDEST_CREATED,
249
259
)
250
260
```
251
261
@@ -406,13 +416,48 @@ if user is None:
406
416
try:
407
417
user, created =await User.create_or_update(
408
418
match={"external_id": "ext-123"},
409
-
data={"name": "Test"},
419
+
create_data={"name": "Test"},
420
+
update_data={"name": "Test"},
410
421
on_multiple=OnMultiple.ERROR,
411
422
)
412
423
except MultipleMatchesError as e:
413
424
print(f"Found {e.count} duplicates for {e.match}")
414
425
```
415
426
427
+
## FAQ
428
+
429
+
### How do I connect to a Bubble.io app from Python?
430
+
431
+
Install the package, then call `configure()` with your Bubble Data API root URL and API key. See [Quick Start](#quick-start). The Data API must be enabled in your Bubble app under Settings → API.
432
+
433
+
### How do I query Bubble.io records by field value from Python?
434
+
435
+
Use `find()` (or `find_all()` / `find_iter()`) with a list of `constraint(...)` objects. Each constraint takes a field name, a `ConstraintType`, and a value. See [Constraints](#constraints) for the full operator list.
436
+
437
+
### How do I handle Bubble Data API pagination?
438
+
439
+
The library handles pagination for you. Use `find_all()` to collect every matching record into a list, or `find_iter()` to stream records with constant memory. Both walk all pages internally. Use `find()` only if you want manual `cursor` / `limit` control. See [Querying Records](#querying-records).
440
+
441
+
### Does this support upserts?
442
+
443
+
Yes. `create_or_update()` matches by any field, creates if missing, updates if found, and offers configurable strategies for handling Bubble's lack of unique constraints (error, update first, update all, dedupe oldest, dedupe newest). See [Smart Upserts](#smart-upserts).
444
+
445
+
### Can I use this with FastAPI, Starlette, or other async frameworks?
446
+
447
+
Yes. The library is async-first and reuses HTTP connections per event loop, so it drops into any `asyncio`-based framework without extra configuration. Call the model methods directly from your route handlers.
448
+
449
+
### Can I use this in synchronous Python code?
450
+
451
+
Yes, by wrapping calls in `asyncio.run()` or running an async block. See [Usage in Sync Contexts](#usage-in-sync-contexts).
452
+
453
+
### How do I handle Bubble.io rate limits and retries?
454
+
455
+
Pass a `tenacity.AsyncRetrying` policy to `configure(retry=...)`. You control the wait strategy, attempt count, and which exceptions to retry. See [Retry Configuration](#retry-configuration).
456
+
457
+
### What Python versions are supported?
458
+
459
+
Python 3.12 and newer. The library uses modern type-hint syntax and async features that require 3.12+.
Copy file name to clipboardExpand all lines: pyproject.toml
+40-4Lines changed: 40 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -1,15 +1,51 @@
1
1
[project]
2
2
name = "bubble-data-api-client"
3
3
version = "0.7.0"
4
-
description = "Async Python client for Bubble Data API with Pydantic ORM, type safety, connection pooling, and configurable retries"
4
+
description = "Query your Bubble.io database from Python with an async client and Pydantic ORM. Type-safe CRUD for the Bubble Data API with connection pooling and retries."
0 commit comments