Skip to main content
Yanked

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 date and datetime values
  • 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

MIT

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)

Source distribution for pyquickbase 0.1.0a1
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)

Table of built distributions (wheels) for pyquickbase 0.1.0a1
File Interpreter ABI Platform Actions
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page