pyquickbase 0.2.0a1__tar.gz → 1.0.0rc1__tar.gz
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.
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/CHANGELOG.md +24 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/CONTRIBUTING.md +13 -1
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/PKG-INFO +5 -5
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/README.md +3 -3
- pyquickbase-1.0.0rc1/ROADMAP.md +69 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/api-notes.md +37 -37
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/getting-started.md +1 -1
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/clients.md +6 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/codegen.md +19 -3
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/errors.md +8 -2
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/files.md +2 -2
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/reading.md +8 -3
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/index.md +4 -4
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/reference.md +2 -1
- pyquickbase-1.0.0rc1/docs/stability.md +49 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/mkdocs.yml +1 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/pyproject.toml +1 -1
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_async/_resources.py +46 -27
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_codegen.py +35 -3
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_models.py +95 -91
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_query.py +3 -1
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_sync/_resources.py +46 -27
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_typed.py +15 -6
- pyquickbase-1.0.0rc1/src/pyquickbase/_version.py +1 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_writes.py +28 -25
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/errors.py +1 -1
- pyquickbase-1.0.0rc1/tests/_async/test_generated_contract.py +237 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_typed.py +35 -7
- pyquickbase-1.0.0rc1/tests/_sync/test_generated_contract.py +236 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_typed.py +35 -7
- pyquickbase-1.0.0rc1/tests/fixtures/generated_tables.py +114 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_codegen.py +23 -8
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_docs.py +12 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_files.py +15 -9
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_formulas.py +6 -5
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_query.py +25 -16
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_writes.py +36 -31
- pyquickbase-0.2.0a1/src/pyquickbase/_version.py +0 -1
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.env.example +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.github/workflows/ci.yml +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.github/workflows/docs.yml +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.github/workflows/release.yml +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.gitignore +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/.python-version +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/LICENSE +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/SECURITY.md +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/changelog.md +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/formulas.md +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/schema.md +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/docs/guides/writing.md +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/scripts/generate_sync.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/__init__.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_async/__init__.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_async/_client.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_async/_transport.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_auth.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_cli.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_config.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_response.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_retry.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_schema.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_sync/__init__.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_sync/_client.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/_sync/_transport.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/src/pyquickbase/py.typed +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/__init__.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_client.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_files.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_formulas.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_reports.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_resources.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_async/test_writes.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_client.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_files.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_formulas.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_reports.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_resources.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/_sync/test_writes.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/conftest.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/tests/test_core.py +0 -0
- {pyquickbase-0.2.0a1 → pyquickbase-1.0.0rc1}/uv.lock +0 -0
|
@@ -6,6 +6,30 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.0.0rc1] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- A stability and versioning policy, which applies from 1.0.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `TypedTable.check_schema()` now always fetches the table's fields again, instead of doing nothing
|
|
18
|
+
after the first check. Other methods still check once, on first use, against cached metadata.
|
|
19
|
+
- A generated class no longer fails when a field becomes writable. Otherwise, supporting a new field
|
|
20
|
+
type in a later version would break classes generated earlier.
|
|
21
|
+
- Generated names no longer depend on the Python version running `pyquickbase codegen`, so a label
|
|
22
|
+
can't get a different name on a newer Python.
|
|
23
|
+
- The error from `TypedRecord.from_record()` now mentions that a report may have left out a field.
|
|
24
|
+
- `SchemaMismatchError` and `UnknownFieldError` now say a field may be hidden from the token, because
|
|
25
|
+
Quickbase leaves out fields a role can't see, as if they didn't exist.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
- `from_api()` on `AppInfo`, `TableInfo`, `Field`, `Relationship`, `ReportInfo`, `FormulaResult`, and
|
|
30
|
+
`Page`, `FileDownload.from_response()`, `FileUpload.to_api()`, and `WriteResult.merged()` and
|
|
31
|
+
`summary()`. These were internal helpers, and they're now private so they can change freely.
|
|
32
|
+
|
|
9
33
|
## [0.2.0a1] - 2026-11-02
|
|
10
34
|
|
|
11
35
|
### Added
|
|
@@ -34,10 +34,22 @@ Anything exported from `pyquickbase/__init__.py` or defined in `errors.py` is pu
|
|
|
34
34
|
starting with `_` are private and may change at any time. Update `CHANGELOG.md` for changes that
|
|
35
35
|
users would notice.
|
|
36
36
|
|
|
37
|
+
`docs/stability.md` says what 1.x promises, so check it before changing anything public. From 1.0, a
|
|
38
|
+
breaking change needs a major version, and removing something needs a `DeprecationWarning` in a minor
|
|
39
|
+
release first. Generated code must keep working on later 1.x versions, so treat what it relies on as
|
|
40
|
+
public, even when the name is private. `tests/fixtures/generated_tables.py` is an old generated file
|
|
41
|
+
that the tests run against the current code. From 1.0, don't regenerate it to make a test pass, because
|
|
42
|
+
a failure there means you've broken users' files.
|
|
43
|
+
|
|
37
44
|
## Secrets
|
|
38
45
|
|
|
39
46
|
Never commit real tokens, realm names, or customer data, including in test fixtures. Tokens must
|
|
40
47
|
never appear in exceptions, `repr`s, or logs.
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
## Notes and plans
|
|
50
|
+
|
|
51
|
+
`docs/api-notes.md` holds what's been confirmed about the Quickbase API. Please update it if you
|
|
43
52
|
confirm or disprove something.
|
|
53
|
+
|
|
54
|
+
`ROADMAP.md` holds what's left to do, including anything untested and any limit we may want to change.
|
|
55
|
+
Delete an item once it's done.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyquickbase
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0rc1
|
|
4
4
|
Summary: An unofficial, typed Python SDK for the Quickbase API, with sync and async clients.
|
|
5
5
|
Project-URL: Documentation, https://matthew-williams-solutions.github.io/pyquickbase/
|
|
6
6
|
Project-URL: Repository, https://github.com/matthew-williams-solutions/pyquickbase
|
|
@@ -10,7 +10,7 @@ Author: Matthew Williams
|
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: api,async,client,quickbase,sdk
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Framework :: AnyIO
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -31,7 +31,7 @@ sync and async clients.
|
|
|
31
31
|
|
|
32
32
|
**[Read the documentation](https://matthew-williams-solutions.github.io/pyquickbase/)**
|
|
33
33
|
|
|
34
|
-
> **Status:
|
|
34
|
+
> **Status: release candidate.** Small changes are still possible before 1.0. This project is not affiliated with or endorsed by Quickbase, Inc.
|
|
35
35
|
|
|
36
36
|
## What works today
|
|
37
37
|
|
|
@@ -54,10 +54,10 @@ sync and async clients.
|
|
|
54
54
|
## Usage
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
pip install pyquickbase==0.
|
|
57
|
+
pip install pyquickbase==1.0.0rc1
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
This is
|
|
60
|
+
This is a release candidate, so pin the exact version.
|
|
61
61
|
|
|
62
62
|
```python
|
|
63
63
|
from datetime import date
|
|
@@ -5,7 +5,7 @@ sync and async clients.
|
|
|
5
5
|
|
|
6
6
|
**[Read the documentation](https://matthew-williams-solutions.github.io/pyquickbase/)**
|
|
7
7
|
|
|
8
|
-
> **Status:
|
|
8
|
+
> **Status: release candidate.** Small changes are still possible before 1.0. This project is not affiliated with or endorsed by Quickbase, Inc.
|
|
9
9
|
|
|
10
10
|
## What works today
|
|
11
11
|
|
|
@@ -28,10 +28,10 @@ sync and async clients.
|
|
|
28
28
|
## Usage
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
pip install pyquickbase==0.
|
|
31
|
+
pip install pyquickbase==1.0.0rc1
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
This is
|
|
34
|
+
This is a release candidate, so pin the exact version.
|
|
35
35
|
|
|
36
36
|
```python
|
|
37
37
|
from datetime import date
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
What's left to do. Delete an item once it's done: `CHANGELOG.md` and git history record what's
|
|
4
|
+
finished, and `docs/api-notes.md` records what's been confirmed about Quickbase. Limits we've decided
|
|
5
|
+
to keep are explained in the guides, not here.
|
|
6
|
+
|
|
7
|
+
## Before 1.0
|
|
8
|
+
|
|
9
|
+
- Install `1.0.0rc1` from PyPI and try it live on a scratch table.
|
|
10
|
+
- In the 1.0 release commit:
|
|
11
|
+
- Change the classifier in `pyproject.toml` from Beta to Production/Stable.
|
|
12
|
+
- Remove the release candidate warnings from `README.md` and `docs/index.md`, and the advice to pin
|
|
13
|
+
the exact version. If the pins go too, update `test_install_pins_match_the_version`.
|
|
14
|
+
- If codegen's output has changed, refresh `tests/fixtures/generated_tables.py` one last time.
|
|
15
|
+
|
|
16
|
+
## After 1.0
|
|
17
|
+
|
|
18
|
+
Nothing here should break anyone.
|
|
19
|
+
|
|
20
|
+
- Switch the docs from Material to Zensical, **by 2027-05-05**, when Material's maintenance ends. A
|
|
21
|
+
trial build of 0.0.74 worked, and its strict mode also catches broken anchors, which MkDocs doesn't.
|
|
22
|
+
Wait for 0.1.0, announced for 2026-11-05.
|
|
23
|
+
- A method for summary and chart reports, returning `{label: value}` rows.
|
|
24
|
+
- A per-call timeout for uploads and downloads.
|
|
25
|
+
- A small config file to choose generated names.
|
|
26
|
+
- `query_page()`, `select`, and `batch_size` on typed tables. `select` needs a design, because a
|
|
27
|
+
generated class requires every column.
|
|
28
|
+
- A setting that repeats a typed table's schema check after a set time.
|
|
29
|
+
- Typed access to file and user values, as a dictionary subclass, which could leave out uploader emails.
|
|
30
|
+
- Export `FieldRef`, `OrderBy`, and `Expression`, which appear in public signatures.
|
|
31
|
+
- Tie a `Row` to its class when the code runs. Only a type checker refuses a row from another class.
|
|
32
|
+
|
|
33
|
+
## Open questions
|
|
34
|
+
|
|
35
|
+
Nothing is waiting on these. Test one against a sandbox app before depending on the answer, then move
|
|
36
|
+
the result to `docs/api-notes.md`.
|
|
37
|
+
|
|
38
|
+
**Queries and limits**
|
|
39
|
+
|
|
40
|
+
- Can a query return fewer than `top` records before the end? The SDK already advances by the number
|
|
41
|
+
returned.
|
|
42
|
+
- Which endpoints have different rate limits?
|
|
43
|
+
- Do other endpoints or realms have a different request size limit?
|
|
44
|
+
|
|
45
|
+
**Reports**
|
|
46
|
+
|
|
47
|
+
- Can a deleted report's ID be reused? A new report got ID 8, the highest so far, but the deleted
|
|
48
|
+
report's own ID wasn't recorded. If it can, cached report details could misread a total as a Record
|
|
49
|
+
ID#.
|
|
50
|
+
- Do pages stay stable with ties above 1,200 records, or while records are added or removed?
|
|
51
|
+
- Does a run without `top` return every record above 1,200 records? The SDK always sends `top`, so this
|
|
52
|
+
only affects `qb.request()`.
|
|
53
|
+
|
|
54
|
+
**Fields and files**
|
|
55
|
+
|
|
56
|
+
- What do lookups return for types other than text, numeric, and date, and what about field types only
|
|
57
|
+
the UI can create? These are `Any` and read-only in generated classes until confirmed.
|
|
58
|
+
- How do file fields behave with other version settings, and are there limits on total storage?
|
|
59
|
+
|
|
60
|
+
**Realms and apps**
|
|
61
|
+
|
|
62
|
+
- What's the API host for EU realms?
|
|
63
|
+
- Can a formula read a table in another app?
|
|
64
|
+
- How do roles with write access or record-level rules behave, and a report that sorts or filters on a
|
|
65
|
+
hidden field? Only a view-only role with hidden fields has been tested.
|
|
66
|
+
- Can a restricted user's formula read a table their role has no access to? A role's hidden fields held
|
|
67
|
+
up, but this wasn't tested, because the role's other tables were the seeded ones.
|
|
68
|
+
- Can renaming a table, or using the UI, give two tables the same name? If so, `app.table()` takes the
|
|
69
|
+
first match, unlike fields and reports, which raise an error.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Quickbase API notes
|
|
2
2
|
|
|
3
3
|
What this SDK relies on, and how far each point has been confirmed. Much of the developer portal
|
|
4
|
-
renders in the browser and has no published spec that we've found, so check anything
|
|
5
|
-
|
|
4
|
+
renders in the browser and has no published spec that we've found, so check anything not listed here
|
|
5
|
+
against a real app before depending on it. Questions we haven't answered yet are in the
|
|
6
|
+
[roadmap](https://github.com/matthew-williams-solutions/pyquickbase/blob/main/ROADMAP.md#open-questions).
|
|
6
7
|
|
|
7
8
|
## Confirmed
|
|
8
9
|
|
|
@@ -26,6 +27,7 @@ renders in the browser and has no published spec that we've found, so check anyt
|
|
|
26
27
|
| With no `select`, only fields in the table's default set return, and Record ID# may not be one | Live API, 2026-11-02 |
|
|
27
28
|
| Timestamps return as `2026-10-21T11:49:56Z`, dates as `2026-10-18` | Live API, 2026-11-02 |
|
|
28
29
|
| A nonexistent app or table returns 401 with a valid token, not 404 | Live API, 2026-11-02 |
|
|
30
|
+
| Creating a table with a name already in the app, ignoring case, returns 400, `Invalid input` | Live API, 2026-11-02 |
|
|
29
31
|
|
|
30
32
|
A bad realm or app can return 400 rather than 401 or 404, even with an invalid token, so don't
|
|
31
33
|
assume a 400 means the request body was wrong.
|
|
@@ -103,6 +105,11 @@ and chart reports built in the UI. The API can't create reports.
|
|
|
103
105
|
| Unknown or non-numeric report ID | 404 |
|
|
104
106
|
| The report's filter, sort, and columns | Applied. Rows hold only the report's columns |
|
|
105
107
|
| A report whose columns leave out Record ID#, including the default `List All` | Rows have no field 3 |
|
|
108
|
+
| A table report with Record ID# as a column, even in second position | `fields` uses the real ID, 3, with type `recordid`, unlike a summary report's positions. All 1,200 rows matched a query's IDs |
|
|
109
|
+
| A table report with a formula column | `fields` uses the formula field's real ID and its type, such as `numeric`. All 1,200 values matched a query |
|
|
110
|
+
| A sort with ties, 400 records on each of 3 values | Ties came back in Record ID# order. Six full runs, with page sizes of 7, 37, 100, and 1,000, gave the same order, with nothing repeated or missing. Editing a tied record's other field didn't move it |
|
|
111
|
+
| `top` omitted, in a 1,200-record table | Returned every record, and `metadata` has no `top` |
|
|
112
|
+
| Changing a saved report's type | The UI doesn't offer this |
|
|
106
113
|
| Running a report | **Adds 1 to `usedCount`, even when the run fails.** A GET doesn't, and records aren't modified |
|
|
107
114
|
| Ask-the-user report | `incomplete: true`, and running it returns 400, `Incomplete reports not supported` |
|
|
108
115
|
| Summary and chart reports | **Same response shape as a table report, but `fields` IDs are column positions starting at 2.** The second column has ID 3, the Record ID# field in a table report, so nothing in the response tells them apart. Only `type` on the report does |
|
|
@@ -200,7 +207,11 @@ Tested on 2026-11-02 with short bursts from 12 threads against temporary tables.
|
|
|
200
207
|
|
|
201
208
|
## Temporary tokens
|
|
202
209
|
|
|
203
|
-
**The SDK doesn't support temporary tokens.**
|
|
210
|
+
**The SDK doesn't support temporary tokens.** Their main use is browser code pages, a script can't get
|
|
211
|
+
one, and the SDK's table lookup needs `GET /tables`, which a table-scoped token can't call. These
|
|
212
|
+
findings are kept for when that's revisited. Supporting them would need a `temporary_token` client
|
|
213
|
+
option that treats `app.table()` as an ID and skips that lookup, for a backend acting for a logged-in
|
|
214
|
+
user.
|
|
204
215
|
|
|
205
216
|
Tested on 2026-11-02 against temporary tables in a sandbox app, with tokens fetched from a logged-in
|
|
206
217
|
browser's console, because a script can't get one.
|
|
@@ -230,6 +241,29 @@ doesn't contain a formula**: `GetRecord()` read a table the token couldn't other
|
|
|
230
241
|
downloads, and file deletes weren't tested with a temporary token, and neither were tokens from users
|
|
231
242
|
with fewer permissions.
|
|
232
243
|
|
|
244
|
+
## Restricted users
|
|
245
|
+
|
|
246
|
+
Tested on 2026-11-02 against a temporary table in a sandbox app, with a second user's token. Its role
|
|
247
|
+
could only view that table, had no access to the app's other tables, and had access `None` on one text
|
|
248
|
+
field and one file field. A role with write access or record-level rules wasn't tested.
|
|
249
|
+
|
|
250
|
+
| Behavior | Result |
|
|
251
|
+
|---|---|
|
|
252
|
+
| A token's permissions | Its user's, through their role |
|
|
253
|
+
| `GET /fields` | **A hidden field is left out**, exactly as if it didn't exist, so a deleted field and a hidden one can't be told apart |
|
|
254
|
+
| `GET /fields/{id}` for a hidden field | 500, `QuickBase is experiencing technical difficulties` |
|
|
255
|
+
| `GET /tables` | Lists only tables the role can use |
|
|
256
|
+
| `GET /apps/{id}` | 403, for this role |
|
|
257
|
+
| `select` with a hidden field and a visible one | **200, and the hidden field is silently left out** |
|
|
258
|
+
| `select` with only hidden fields | 400, `Report contains a restricted field` |
|
|
259
|
+
| `where` or `sortBy` on a hidden field | 403, whether or not anything matches |
|
|
260
|
+
| Running a table report with a hidden column | **200, and the column is silently left out.** A report with a hidden field in its sort or filter wasn't tested |
|
|
261
|
+
| A file field's value, read with view access | Includes `creator`, with **the uploader's email address** |
|
|
262
|
+
| Downloading from a visible file field | 200 |
|
|
263
|
+
| Downloading from a hidden file field | 404, `No such field` |
|
|
264
|
+
| A formula using a hidden field, directly or through `GetRecords()` | 403, including when `GetRecords()` only filters on it. A visible field works |
|
|
265
|
+
| Insert, update, delete, upload, deleting a file version, creating a field | 403, `Insufficient permissions`, and nothing changed |
|
|
266
|
+
|
|
233
267
|
## Field types
|
|
234
268
|
|
|
235
269
|
Tested on 2026-11-02 against temporary tables in a sandbox app. These decide the types in generated
|
|
@@ -267,37 +301,3 @@ A `datetime` field reports its type as `timestamp`.
|
|
|
267
301
|
| Label uniqueness | Exact duplicates are refused, ignoring case, but `Due Date` and `Due-Date` can both exist |
|
|
268
302
|
| Field IDs | Assigned in order, and address subfields use up IDs |
|
|
269
303
|
| `GET /tables/{id}` | Returned an error without its `appId`, so use `GET /tables?appId=` |
|
|
270
|
-
|
|
271
|
-
## Unverified
|
|
272
|
-
|
|
273
|
-
Check these against a sandbox app before the feature that needs them ships.
|
|
274
|
-
|
|
275
|
-
| Question | Needed for |
|
|
276
|
-
|---|---|
|
|
277
|
-
| Can a query return fewer than `top` records before the end? The test table was too small to show it, so advance by `numRecords` | Paging |
|
|
278
|
-
| Which endpoints have different rate limits? | Transport |
|
|
279
|
-
| Whether a formula can read a table in another app | Formulas |
|
|
280
|
-
| Lookups that hold a value, for types other than text, numeric, and date, and field types only the UI can create | Generated classes |
|
|
281
|
-
| Correct API host for EU realms (pass `base_url` if it differs) | Realm handling |
|
|
282
|
-
| Table reports with a formula column, or with Record ID# as a column | Reports |
|
|
283
|
-
| Whether pages of a report are stable when its sort has ties | Reports |
|
|
284
|
-
| Whether a report's `top`-less run returns every record in a large table | Reports |
|
|
285
|
-
| Whether other endpoints or realms have a different request size limit | Writes |
|
|
286
|
-
| File fields with other version settings, and limits on total storage | File attachments |
|
|
287
|
-
| How reports and files behave for tokens with fewer permissions, since all testing used one token | Reports, file attachments |
|
|
288
|
-
|
|
289
|
-
## Known limits to revisit
|
|
290
|
-
|
|
291
|
-
Choices made in the SDK that are worth another look before 1.0.
|
|
292
|
-
|
|
293
|
-
| Limit | Why it's there | Possible change |
|
|
294
|
-
|---|---|---|
|
|
295
|
-
| Report details are cached for `schema_ttl`, so a report changed in the UI can be checked against old details | Avoids an extra request for every page | Refresh before each run, or compare the run response |
|
|
296
|
-
| Summary and chart reports can only be read through `qb.request()` | Their column IDs are positions, so they can't be records | A separate method returning `{label: value}` rows |
|
|
297
|
-
| `timeout` is set per client, so a large upload on a slow connection may need a larger one for every request | The transport has one timeout | A per-call timeout for uploads and downloads |
|
|
298
|
-
| Files are held in memory as bytes and base64 while being sent | The API takes base64 inside JSON, so there's nothing to stream | Probably none, unless Quickbase adds another upload route |
|
|
299
|
-
| `record["Attachment"]` returns Quickbase's raw dictionary | Kept simple for the first version | A typed value with versions, which could leave out uploader emails |
|
|
300
|
-
| Quickbase's formula error messages can contain HTML, which errors pass on unchanged | Errors keep Quickbase's message intact | Strip HTML from error messages, in case they're displayed |
|
|
301
|
-
| `FormulaResult.value` leaves dates, timestamps, and durations as display text | They depend on app settings, and timestamps have no time zone | Parse them using the app's settings, if a safe way appears |
|
|
302
|
-
| Temporary tokens aren't supported | Their main use is browser code pages, a script can't get one, and the SDK's table lookup needs `GET /tables`, which a table-scoped token can't call | After 1.0, a `temporary_token` client option that treats `app.table()` as an ID and skips that lookup, for a backend acting for a logged-in user |
|
|
303
|
-
| `codegen` writes one class per table, with no way to rename one | Names come from labels, so renaming a label renames the attribute | Keep names in a small config file, if this proves a problem |
|
|
@@ -44,6 +44,12 @@ qb = Quickbase(realm="acme", token="your_user_token", retry=policy)
|
|
|
44
44
|
`max_retry_after` is the longest `Retry-After` the client will wait for. Past that, it raises
|
|
45
45
|
`RateLimitError`. Use `max_retries=0` to disable retrying.
|
|
46
46
|
|
|
47
|
+
## Using another API host
|
|
48
|
+
|
|
49
|
+
`base_url` sets the API root, which is `https://api.quickbase.com/v1` by default. The right host for EU
|
|
50
|
+
realms hasn't been confirmed, so pass `base_url` if yours differs. It must use HTTPS, except for
|
|
51
|
+
loopback addresses.
|
|
52
|
+
|
|
47
53
|
## Proxies, TLS, and logging
|
|
48
54
|
|
|
49
55
|
Pass your own [httpx](https://www.python-httpx.org/) client for proxies, custom TLS, event hooks, or
|
|
@@ -53,6 +53,8 @@ selects all of its fields. A record's `repr` leaves out its values, because they
|
|
|
53
53
|
| `file` | `dict[str, Any]`, as described in [File attachments](files.md) |
|
|
54
54
|
| Anything else | `Any` |
|
|
55
55
|
|
|
56
|
+
Numbers are `float`, as in `Record`, so currency values aren't exact decimals.
|
|
57
|
+
|
|
56
58
|
## Writing
|
|
57
59
|
|
|
58
60
|
`Tasks.new()` builds a row for a new record, and `Tasks.change()` builds one for an existing record.
|
|
@@ -75,9 +77,18 @@ take a `FileUpload`.
|
|
|
75
77
|
## Keeping it up to date
|
|
76
78
|
|
|
77
79
|
A generated class is compared with its table the first time it's used, and `SchemaMismatchError` lists
|
|
78
|
-
what's different if a field was deleted, changed type, or became
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
what's different if a field was deleted, changed type, or became read-only. Reads and writes stop rather
|
|
81
|
+
than risk returning wrong types. Run the command again to fix it. Adding a field, renaming one, or a
|
|
82
|
+
field becoming writable doesn't cause an error, because the class uses field IDs and is still safe.
|
|
83
|
+
|
|
84
|
+
That first check uses cached metadata, 5 minutes old at most by default (see `schema_ttl`), and later
|
|
85
|
+
changes aren't noticed. A long-running process can call `tasks.check_schema()` from time to time, which
|
|
86
|
+
always fetches the fields again.
|
|
87
|
+
|
|
88
|
+
**Restricted users.** Quickbase leaves out fields a user's role can't see, so a hidden field looks the
|
|
89
|
+
same as a deleted one, and the class raises `SchemaMismatchError` for that user. Generate a class with a
|
|
90
|
+
token that has the same access as the code using it. A class generated by the most restricted user also
|
|
91
|
+
works for anyone who can see more.
|
|
81
92
|
|
|
82
93
|
To catch changes sooner, run this in CI. It changes nothing, and fails if the file is out of date.
|
|
83
94
|
|
|
@@ -100,3 +111,8 @@ turned into underscores, so a label can't inject code into the module.
|
|
|
100
111
|
If two labels give the same name, such as `Due Date` and `Due-Date`, the field with the lowest ID keeps
|
|
101
112
|
it and the other gets its ID added: `due_date_9`. A new field therefore never takes an existing field's
|
|
102
113
|
name. Record ID# is always `id`. Renaming a label renames its attribute the next time you generate.
|
|
114
|
+
|
|
115
|
+
**Names can move.** If the field holding a plain name is deleted, the next field with that name takes it
|
|
116
|
+
when you generate again, so `due_date` can come to mean a different field with no error. Class names work
|
|
117
|
+
the same way, and a clash between them is settled only among the tables you generate, so `--table` can
|
|
118
|
+
change a class's name. Review the diff when you generate again, and use `--check` in CI.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Handling errors
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Every exception this library defines derives from `QuickbaseError`. Reading a missing key from a
|
|
4
|
+
`Record`, including `record.id` on a report row without Record ID#, raises Python's `KeyError`, as any
|
|
5
|
+
mapping does.
|
|
4
6
|
|
|
5
7
|
```python
|
|
6
8
|
from pyquickbase import AuthenticationError, QuickbaseError, RateLimitError
|
|
@@ -26,7 +28,7 @@ These mean nothing was sent to Quickbase.
|
|
|
26
28
|
| `InvalidValueError` | A row, value, or file request that can't be sent |
|
|
27
29
|
| `SchemaMismatchError` | A generated class no longer matches its table |
|
|
28
30
|
| `UnknownTableError` | No table with that name or ID in this app |
|
|
29
|
-
| `UnknownFieldError` | No field with that label or ID in this table |
|
|
31
|
+
| `UnknownFieldError` | No field with that label or ID in this table, or the token's role can't see it |
|
|
30
32
|
| `UnknownReportError` | No report with that name or ID on this table, or several with that name |
|
|
31
33
|
| `UnsupportedReportError` | A report that can't be read as records, such as a summary or chart |
|
|
32
34
|
| `ReadOnlyError` | The client isn't allowed to send this request |
|
|
@@ -50,6 +52,10 @@ and `request_id`. Include `request_id` when reporting a problem to Quickbase sup
|
|
|
50
52
|
`AuthenticationError` can mean either a bad token or a wrong ID. A bad realm or app can also
|
|
51
53
|
return a 400.
|
|
52
54
|
|
|
55
|
+
!!! warning "Messages can contain HTML"
|
|
56
|
+
Quickbase's formula error messages can include HTML, such as a `javascript:` link, and errors
|
|
57
|
+
pass them on unchanged. Escape them before showing them in a web page.
|
|
58
|
+
|
|
53
59
|
## Other failures
|
|
54
60
|
|
|
55
61
|
| Exception | Cause |
|
|
@@ -73,8 +73,8 @@ are whatever was uploaded. Treat both as untrusted, especially before opening or
|
|
|
73
73
|
|
|
74
74
|
## Listing versions
|
|
75
75
|
|
|
76
|
-
Reading a file field in a query returns Quickbase's value unchanged. `url` is an
|
|
77
|
-
`versions` an empty list, when the field has no file.
|
|
76
|
+
Reading a file field in a query returns Quickbase's value unchanged, as a plain dictionary. `url` is an
|
|
77
|
+
empty string, and `versions` an empty list, when the field has no file.
|
|
78
78
|
|
|
79
79
|
```python
|
|
80
80
|
task = next(iter(tasks.query(where="{3.EX.7}", select=["Attachment"])))
|
|
@@ -144,9 +144,13 @@ print(len(page.records), page.total, page.has_more)
|
|
|
144
144
|
The report decides the filter, sort, and columns:
|
|
145
145
|
|
|
146
146
|
- **Record ID# may be missing.** `record.id` raises `KeyError` if the report doesn't include that
|
|
147
|
-
column, and the default `List All` report doesn't.
|
|
147
|
+
column, and the default `List All` report doesn't. To check first, use `3 in record`, because
|
|
148
|
+
`hasattr(record, "id")` raises `KeyError` too.
|
|
148
149
|
- **Pages may not be stable.** `query()` adds Record ID# as the last sort key to keep pages stable,
|
|
149
|
-
but a report's sort can't be changed.
|
|
150
|
+
but a report's sort can't be changed. Ties stayed in Record ID# order in a 1,200-record test, but
|
|
151
|
+
that isn't confirmed for larger tables or while records change. If it matters, prefer `query()`.
|
|
152
|
+
- **Columns can be missing for restricted users.** A column the user's role can't see is left out with
|
|
153
|
+
no error, so `record["Salary"]` raises `KeyError` for them.
|
|
150
154
|
- **Report IDs are only unique within a table.** Different tables reuse the same IDs, so always get a
|
|
151
155
|
report from its own table.
|
|
152
156
|
- **Names can repeat.** A repeated name raises `UnknownReportError`, which lists the IDs to choose
|
|
@@ -160,4 +164,5 @@ ask-the-user reports through the API.
|
|
|
160
164
|
|
|
161
165
|
Running a report adds to its usage count in Quickbase, even if the run fails, but changes no records.
|
|
162
166
|
Report details come from the same cache as other metadata, so `usedCount` in `raw` can be out of
|
|
163
|
-
date.
|
|
167
|
+
date, and so can the details used to check that a report is a table report. Quickbase's UI doesn't let
|
|
168
|
+
you change a saved report's type.
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
An unofficial, typed Python SDK for the [Quickbase](https://www.quickbase.com/) API, with matching
|
|
4
4
|
sync and async clients.
|
|
5
5
|
|
|
6
|
-
!!! warning "
|
|
7
|
-
This
|
|
8
|
-
install. It isn't affiliated with or endorsed by Quickbase, Inc.
|
|
6
|
+
!!! warning "Release candidate"
|
|
7
|
+
This is a release candidate, so small changes are still possible before 1.0. Pin the exact version
|
|
8
|
+
you install. It isn't affiliated with or endorsed by Quickbase, Inc.
|
|
9
9
|
|
|
10
10
|
```python
|
|
11
11
|
from datetime import date
|
|
@@ -39,7 +39,7 @@ writing `null` to a field does nothing. This SDK turns those into errors or corr
|
|
|
39
39
|
## Install
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
pip install pyquickbase==0.
|
|
42
|
+
pip install pyquickbase==1.0.0rc1
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
It needs Python 3.11 or newer. Next, see [Getting started](getting-started.md).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# API reference
|
|
2
2
|
|
|
3
|
-
Everything listed here is public
|
|
3
|
+
Everything listed here is public, as described in [Stability and versioning](stability.md). Modules
|
|
4
|
+
starting with `_` are private and may change at any time.
|
|
4
5
|
|
|
5
6
|
## Clients
|
|
6
7
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Stability and versioning
|
|
2
|
+
|
|
3
|
+
This policy applies from version 1.0. Until then, anything may change in any release.
|
|
4
|
+
|
|
5
|
+
From 1.0, pyquickbase follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Breaking
|
|
6
|
+
changes come only in a major version, and anything removed first gets a `DeprecationWarning` in a
|
|
7
|
+
minor release.
|
|
8
|
+
|
|
9
|
+
## What's covered
|
|
10
|
+
|
|
11
|
+
- Every name in `pyquickbase.__all__` and `pyquickbase.errors`, with its documented methods,
|
|
12
|
+
signatures, return types, and defaults.
|
|
13
|
+
- The `pyquickbase` command and its options.
|
|
14
|
+
- The promise that a `read_only` client changes no records. It can still run reports, which adds to
|
|
15
|
+
their usage count in Quickbase, and formulas.
|
|
16
|
+
- File and user values staying plain dictionaries. Their contents come from Quickbase.
|
|
17
|
+
|
|
18
|
+
## What isn't covered
|
|
19
|
+
|
|
20
|
+
- Modules and names starting with `_`.
|
|
21
|
+
- Constructors of objects the client creates for you, such as `App`, `Table`, and `Report`. Use
|
|
22
|
+
`qb.app()`, `app.table()`, and `table.report()` instead.
|
|
23
|
+
- The wording of error messages and `repr`s, so don't parse them.
|
|
24
|
+
- Quickbase's own behavior, including any change it makes to its responses, and the contents of `raw`
|
|
25
|
+
attributes.
|
|
26
|
+
|
|
27
|
+
## Generated code
|
|
28
|
+
|
|
29
|
+
A file generated by any 1.x version works on every later 1.x version, so upgrading doesn't force you
|
|
30
|
+
to generate it again.
|
|
31
|
+
|
|
32
|
+
Generating it again can rename attributes, when a label changes or a field with the same name is
|
|
33
|
+
deleted. See [how names are chosen](guides/codegen.md#how-names-are-chosen). Run
|
|
34
|
+
`pyquickbase codegen --check` in CI to catch this.
|
|
35
|
+
|
|
36
|
+
## Minor and patch releases
|
|
37
|
+
|
|
38
|
+
A minor release can add features, optional arguments, and support for more field types. A generated
|
|
39
|
+
attribute typed `Any` can become more specific, but only once you generate the file again.
|
|
40
|
+
|
|
41
|
+
A patch release only fixes bugs.
|
|
42
|
+
|
|
43
|
+
## Dependencies and Python versions
|
|
44
|
+
|
|
45
|
+
A Python version is dropped only after it reaches end of life, and only in a minor release. pip
|
|
46
|
+
won't install a release on a Python version it doesn't support, so older Python versions keep the last
|
|
47
|
+
release that worked.
|
|
48
|
+
|
|
49
|
+
`httpx` stays below version 1 until a major version of pyquickbase, because `http_client` exposes it.
|
|
@@ -9,7 +9,7 @@ authors = [{ name = "Matthew Williams" }]
|
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
keywords = ["quickbase", "api", "sdk", "client", "async"]
|
|
11
11
|
classifiers = [
|
|
12
|
-
"Development Status ::
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
13
|
"Framework :: AnyIO",
|
|
14
14
|
"Intended Audience :: Developers",
|
|
15
15
|
"Programming Language :: Python :: 3 :: Only",
|