Skip to content

Commit 0fabd6c

Browse files
committed
update docs
1 parent 4e0d687 commit 0fabd6c

2 files changed

Lines changed: 98 additions & 17 deletions

File tree

README.md

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11

22
# bubble-data-api-client
33

4-
[![Downloads](https://static.pepy.tech/badge/bubble-data-api-client/month)](https://pepy.tech/project/bubble-data-api-client)
4+
[![PyPI](https://img.shields.io/pypi/v/bubble-data-api-client)](https://pypi.org/project/bubble-data-api-client/)
55
[![Python Version](https://img.shields.io/pypi/pyversions/bubble-data-api-client)](https://pypi.org/project/bubble-data-api-client/)
66
[![License](https://img.shields.io/pypi/l/bubble-data-api-client)](https://pypi.org/project/bubble-data-api-client/)
7-
[![PyPI](https://img.shields.io/pypi/v/bubble-data-api-client)](https://pypi.org/project/bubble-data-api-client/)
7+
[![CI](https://img.shields.io/github/actions/workflow/status/bubble-python/bubble-data-api-client/test.yml?branch=main&label=tests)](https://github.com/bubble-python/bubble-data-api-client/actions/workflows/test.yml)
8+
[![Downloads](https://static.pepy.tech/badge/bubble-data-api-client/month)](https://pepy.tech/project/bubble-data-api-client)
9+
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
10+
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
811

9-
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.
1013

1114
## Why use this?
1215

@@ -102,11 +105,12 @@ The library handles Bubble's API quirks automatically:
102105
Bubble doesn't enforce unique constraints, so duplicates can occur. The `create_or_update` method provides strategies to handle this:
103106

104107
```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
106109
user, created = await User.create_or_update(
107110
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,
110114
)
111115
```
112116

@@ -222,12 +226,15 @@ from bubble_data_api_client import OnMultiple
222226
# basic upsert, matches by external_id and creates if not found
223227
user, created = await User.create_or_update(
224228
match={"external_id": "ext-123"},
225-
data={"name": "Updated Name", "email": "new@example.com"},
229+
create_data={"name": "New User", "email": "new@example.com"},
230+
update_data={"email": "new@example.com"},
226231
on_multiple=OnMultiple.ERROR,
227232
)
228233
# returns (User, bool): the instance and whether it was created
229234
```
230235

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+
231238
### Duplicate Handling Strategies
232239

233240
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
237244
| `OnMultiple.ERROR` | Raise `MultipleMatchesError` (fail-fast) |
238245
| `OnMultiple.UPDATE_FIRST` | Update first match (arbitrary order) |
239246
| `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 |
242251

243252
```python
244-
# auto-deduplicate, keeping the oldest record
253+
# auto-deduplicate, keeping the oldest record by Created Date
245254
user, created = await User.create_or_update(
246255
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,
249259
)
250260
```
251261

@@ -406,13 +416,48 @@ if user is None:
406416
try:
407417
user, created = await User.create_or_update(
408418
match={"external_id": "ext-123"},
409-
data={"name": "Test"},
419+
create_data={"name": "Test"},
420+
update_data={"name": "Test"},
410421
on_multiple=OnMultiple.ERROR,
411422
)
412423
except MultipleMatchesError as e:
413424
print(f"Found {e.count} duplicates for {e.match}")
414425
```
415426

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+.
460+
416461
## License
417462

418463
MIT

pyproject.toml

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,51 @@
11
[project]
22
name = "bubble-data-api-client"
33
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."
55
license = "MIT"
66
readme = "README.md"
77
requires-python = ">=3.12"
8-
keywords = ["bubble", "bubble.io", "async", "pydantic", "orm", "api"]
8+
keywords = [
9+
"bubble",
10+
"bubble.io",
11+
"bubble-api",
12+
"bubbleio",
13+
"async",
14+
"asyncio",
15+
"pydantic",
16+
"orm",
17+
"api",
18+
"sdk",
19+
"client",
20+
"data-api",
21+
"rest-api",
22+
"typed",
23+
"crud",
24+
"database",
25+
"integration",
26+
"etl",
27+
"httpx",
28+
"low-code",
29+
"nocode",
30+
]
931
classifiers = [
10-
"Programming Language :: Python :: 3.14",
11-
"Programming Language :: Python :: 3.13",
32+
"Framework :: AsyncIO",
33+
"Framework :: Pydantic",
34+
"Framework :: Pydantic :: 2",
35+
"Intended Audience :: Developers",
36+
"License :: OSI Approved :: MIT License",
37+
"Natural Language :: English",
38+
"Operating System :: OS Independent",
39+
"Programming Language :: Python",
40+
"Programming Language :: Python :: 3",
41+
"Programming Language :: Python :: 3 :: Only",
1242
"Programming Language :: Python :: 3.12",
43+
"Programming Language :: Python :: 3.13",
44+
"Programming Language :: Python :: 3.14",
45+
"Programming Language :: Python :: Implementation :: CPython",
46+
"Topic :: Internet :: WWW/HTTP",
47+
"Topic :: Software Development :: Libraries",
48+
"Topic :: Software Development :: Libraries :: Python Modules",
1349
"Typing :: Typed",
1450
]
1551
dependencies = [

0 commit comments

Comments
 (0)