This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 1.0.0rc13 instead.
Reason given by maintainers: A value in F can act as query syntax. Use 1.0.0rc2 or later.
pyquickbase
An unofficial, typed Python SDK for the Quickbase API, with matching sync and async clients.
Status: early alpha. Anything may change before 1.0. This project is not affiliated with or endorsed by Quickbase, Inc.
What works today
- Sync and async clients with the same API, and support for asyncio and Trio
- Field labels instead of numeric IDs, and table names instead of table IDs
- A Python query builder that escapes values, and checks for hand-written queries that Quickbase would otherwise accept and answer incorrectly
- Automatic paging, and records with real
dateanddatetimevalues - Automatic retries for rate limits, honoring
Retry-After, and for connection failures - Specific exceptions that include Quickbase's error message and request ID
- Tokens kept out of
reprs, exceptions, and logs, and never sent to other hosts or over plain HTTP - Creating, updating, and deleting records, with errors reported for each failed row
- Creating and changing tables, fields, and relationships
qb.request()for calling any Quickbase endpoint directly
Coming next
Reports, formulas, file attachments, temporary tokens, and generated typed tables.
Usage
pip install pyquickbase==0.1.0a1
This is an alpha release, so pin the exact version.
from datetime import date
from pyquickbase import F, Quickbase
with Quickbase(realm="acme", token="your_user_token") as qb:
tasks = qb.app("bq1234567").table("Tasks")
overdue = (F("Status") != "Done") & (F("Due Date") < date.today())
for task in tasks.query(where=overdue, select=["Title", "Due Date"], order_by="-Due Date"):
print(task.id, task["Title"], task["Due Date"])
print(tasks.count(F("Status") == "Done"))
The async client has the same API:
from pyquickbase import AsyncQuickbase, F
async with AsyncQuickbase(realm="acme", token="your_user_token") as qb:
tasks = qb.app("bq1234567").table("Tasks")
async for task in tasks.query(where=F("Status") == "Done"):
print(task["Title"])
Quickbase.from_env() reads QB_REALM and QB_USER_TOKEN from the environment instead.
Writing queries
Build conditions with F, combining them with & and | and wrapping each in parentheses.
| Python | Matches |
|---|---|
F("Status") == "Done", != |
Equal, not equal, ignoring case |
F("Hours") > 10, >=, <, <= |
Numbers and dates |
F("Notes") == None |
Empty fields |
F("Title").contains("x"), .not_contains() |
Text anywhere in the value |
F("Title").starts_with("x"), .not_starts_with() |
Text at the start of the value |
F("Status").is_in(["Done", "Blocked"]) |
Any of the values |
F("Tags").op("HAS", "x") |
Any other Quickbase operator |
Use F for any text you don't control. It escapes quotes and rejects values containing { or },
which Quickbase can't search for and which could otherwise change the meaning of the query.
You can also pass a Quickbase query string as where, using field IDs:
"({7.EX.'Done'}OR{7.EX.'Blocked'})AND{9.BF.'today'}". The SDK raises QueryError before sending a
string with a mistake that Quickbase would silently accept, such as a lowercase operator, which it
treats as "contains", or an unknown field, which matches nothing.
Reading records
query() fetches further pages as you iterate, and limit stops it early. Read values by label or
ID, with record["Status"] or record[7]. dict(record) gives you {label: value}. Record ID# is
always selected, and without order_by, records come back in Record ID# order so that pages stay
stable. For one page at a time, use query_page(), which returns the records, the total number of
matches, has_more, and next_skip.
Table and field metadata is cached for five minutes. Set schema_ttl on the client to change this,
or call qb.clear_schema_cache() after changing a table.
Writing records
Rows are dictionaries keyed by field label or ID. Python dates, timezone-aware datetimes, and
Decimal values are converted for you, and None clears a field.
| Method | What it does |
|---|---|
table.insert(rows) |
Creates records, and never changes existing ones |
table.update(rows) |
Changes records. Each row needs a Record ID#, and a missing record is an error |
table.upsert(rows, merge_on="Code") |
Updates the record whose unique field matches, or creates one |
table.delete(where) |
Deletes matching records and returns the count. where is required |
from pyquickbase import F, WriteError
result = tasks.insert([{"Title": "Pour slab", "Status": "Not Started", "Due Date": date.today()}])
print(result.created_ids)
tasks.update([{"Record ID#": 7, "Status": "Done", "Notes": None}])
tasks.delete(F("Status") == "Cancelled")
try:
tasks.insert(rows)
except WriteError as error:
print(error.result.created_ids) # these were written
for failed in error.result.errors:
print(rows[failed.index], failed.messages)
Quickbase writes the rows it can and reports the rest, so a WriteError doesn't mean nothing changed.
Large writes are split into batches of 1,000 rows, which you can change with batch_size. Check
error.result before trying again.
Changing the schema
projects = app.create_table("Projects", single_record_name="Project")
projects.create_field("Name", "text")
projects.create_field("Status", "text-multiple-choice", choices=["Active", "Done"])
projects.create_field("Code", "text", unique=True)
tasks.create_relationship(projects, foreign_key_label="Project", lookup_fields=["Name"])
tasks.update_field("Notes", label="Comments")
tasks.delete_fields("Old Field")
tasks.drop(confirm="Tasks") # deletes the table and its records
Handling errors
Every exception derives from QuickbaseError.
from pyquickbase import AuthenticationError, QuickbaseError, RateLimitError
try:
app = qb.request("GET", "/apps/bq1234567")
except AuthenticationError:
app = None
except RateLimitError as error:
print(f"Still rate limited. Try again in {error.retry_after} seconds.")
except QuickbaseError as error:
print(error)
Quickbase answers with a 401, not a 404, for an app or table that doesn't exist, so
AuthenticationError can mean either a bad token or a wrong ID.
Only rate limits and connection failures are retried. A timed-out write isn't retried because Quickbase may already have applied it, so check the data before trying again.
Limiting what a client can do
Pass read_only=True to guarantee a client can't change data. It only sends GET requests and record
queries, including through qb.request(), and raises ReadOnlyError for anything else without
contacting Quickbase.
To allow specific writes and nothing else, pass allowed_writes={("POST", "/records")} instead.
Customizing
Adjust retries with RetryPolicy. For proxies, custom TLS, logging hooks, or custom transports,
pass your own httpx client, which you remain responsible for
closing.
import httpx
from pyquickbase import Quickbase, RetryPolicy
http_client = httpx.Client(proxy="http://proxy.internal:8080")
qb = Quickbase(
realm="acme",
token="your_user_token",
retry=RetryPolicy(max_retries=2),
user_agent="my-app/1.0",
http_client=http_client,
)
Contributing
See CONTRIBUTING.md. To report a vulnerability, see SECURITY.md.
License
Metadata
Release files for pyquickbase 0.1.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyquickbase-0.1.0a1.tar.gz | 75.2 kB | Without provenance | Details for pyquickbase-0.1.0a1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyquickbase-0.1.0a1-py3-none-any.whl | Python 3 | none | any | Details for pyquickbase-0.1.0a1-py3-none-any.whl |
Total release size: 114.7 kB
Release files / pyquickbase-0.1.0a1.tar.gz
| Download URL | pyquickbase-0.1.0a1.tar.gz |
|---|---|
| Size | 75.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
467c0eaf558751c5c13df2a2630e214e051d545002a25bad965a2201d785997f
|
|
BLAKE2b-256 checksum How to use checksums |
9c924bac897f4df348ba4323a0178d8679edd5b1df3452bdeae13879e4ca07df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pyquickbase-0.1.0a1-py3-none-any.whl
| Download URL | pyquickbase-0.1.0a1-py3-none-any.whl |
|---|---|
| Size | 39.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ae783205b68b201e6d2f663272b53c4b204719a6a0bace42daa2515bd3efa87
|
|
BLAKE2b-256 checksum How to use checksums |
be70d8cd2c07f82887b17c50c580b0fe4e770d9d66126c3145e41d0919d78ae5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for pyquickbase 0.1.0a1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.