Configuration
Environment Variables
Section titled “Environment Variables”| Variable | Description |
|---|---|
QUALIA_API_KEY | Your API key (used if not passed to constructor) |
QUALIA_BASE_URL | Override the API base URL (default: https://api.qualiastudios.dev) |
QUALIA_ACCOUNT | Optional account UUID; default the API key’s own (see Account) |
QUALIA_WORKSPACE | Workspace UUID to act in instead of the API key’s own workspace; rarely needed |
QUALIA_QUIET | Set to 1 to silence the live per-phase upload progress written to stderr |
QUALIA_UPLOAD_SERVER_BUDGET_SECONDS | How long the server may go without reporting an upload step before the client gives up (default 3600) |
QUALIA_SERVER_STALE_SECONDS | How long the server’s live preparation heartbeat may go stale before the client gives up (default 240) |
QUALIA_UPLOAD_BOUNDARY_CACHE_DIR | Directory for the upload boundary cache; set to an empty string to disable it (see below) |
from qualia import Qualia
# Uses QUALIA_API_KEY environment variableclient = Qualia()
# Or pass explicitlyclient = Qualia(api_key="your-api-key")Each setting comes from the constructor argument, then the environment
variable, then the CLI’s config file (written by qualia auth login). The
file’s base URL and account are used only together with its key: once
QUALIA_API_KEY is set or api_key= is passed, nothing is read from the file.
Account
Section titled “Account”Tasks, datasets, episodes, versions, mixtures and checks live under an account,
at accounts/{account}/…. A new user has one personal account: an organization
of one, with one workspace.
An API key belongs to exactly one account, so the client needs no account
setting. It names the account accounts/- in every call, and the API reads -
as the key’s own account. No call is made first.
from qualia import Qualia
client = Qualia(api_key="your-api-key")client.tasks.list() # GET /v1/accounts/-/tasks: the key's own accountaccount=, QUALIA_ACCOUNT and qualia auth login --account remain as an
explicit override; with a key they can only name the key’s own account.
To learn the account’s UUID, ask GET /v1/whoami (any valid key may).
client.whoami() returns that answer: the account and workspace ids and
names, and the key’s name, prefix, scope (organization, workspace or
restricted), scopes and expiry.
A full resource name, such as accounts/<ACCOUNT_UUID>/datasets/<DATASET_UUID>,
carries its own account. An explicit account must be a lowercase UUID;
accounts/<uuid> is accepted too. To find yours, see
Finding your account id.
Waiting on a long upload
Section titled “Waiting on a long upload”An upload’s server-side phases — staged-data verification and ingest — are bounded by silence, not elapsed time. Ingest has no upper bound: it parses every bag and transcodes every camera, so a large upload is free to take as long as it takes.
QUALIA_UPLOAD_SERVER_BUDGET_SECONDS is how long the server may go without
reporting a step on your upload; each step it does report restarts that clock.
Two signals end the wait earlier: the server publishing a failure for the
upload, and its live preparation heartbeat going stale for
QUALIA_SERVER_STALE_SECONDS (which must stay under the 300 s lease TTL).
Raise them for a slow link or a very large session; lower them to fail faster in
CI. To watch progress instead of waiting blind, pass on_progress to
client.data.upload().
Upload speed
Section titled “Upload speed”client.data.upload() sizes its send from the machine it runs on, so most
uploads need no setting. Every value below is bounded; a value out of range
fails at import and names the variable.
| Variable | Default | What it bounds |
|---|---|---|
QUALIA_UPLOAD_BUFFER_BUDGET_BYTES | 1/64 of memory, 256 MiB–1 GiB | Packed bytes held in memory at once, across every file. Peak memory is about 3–4× this |
QUALIA_UPLOAD_R2_CONCURRENCY | 16 | Storage connections, so PUTs in flight (1–64) |
QUALIA_UPLOAD_WINDOWS_IN_FLIGHT | starts at 3, adapts between a floor and 8 | 64 MiB windows in flight. The floor keeps every storage connection busy; setting this caps it (1–8) |
QUALIA_UPLOAD_TREE_WINDOWS | 2 with a budget of 512 MiB or more, else 1 | 256 MiB groups of small files packed and sent at once, at most one per 192 MiB of budget (1–4) |
QUALIA_UPLOAD_FILE_CONCURRENCY | half the cores, 4–8 | Large (over 64 MiB) or edited files packed at once; the CLI’s --upload-workers (1–32) |
“Memory” is the lower of the machine’s RAM and the smallest memory limit on
the process’s cgroup or any parent of it, such as a container’s limit or a
systemd service’s MemoryMax/MemoryHigh. “Cores” are the ones the process
may run on.
The live progress reports the window depth it settled at (upload window depth …), and the receipt’s transfer_metrics has each phase’s time and request
count. On a thin uplink, lower QUALIA_UPLOAD_R2_CONCURRENCY before anything
else; on a small machine, lower QUALIA_UPLOAD_BUFFER_BUDGET_BYTES.
Upload boundary cache
Section titled “Upload boundary cache”client.data.upload() keeps a small on-disk cache of chunk boundaries so that
an edited file can reuse its unchanged chunks even after the client process
restarts. It holds chunk IDs, offsets and sizes only — no file bytes, API keys,
presigned URLs or manifest IDs — and every reuse is re-authorized and
re-checked for presence on the server at upload time. A file’s cached
boundaries are only ever compared against a fresh chunking of the current
file; the cache cannot change how a file is cut.
It saves stored bytes on changed-file re-uploads after a restart. It does not make uploads faster, and no speedup is claimed.
Location. $XDG_CACHE_HOME/qualia/upload-boundaries-v1, or
~/.cache/qualia/upload-boundaries-v1 when XDG_CACHE_HOME is unset. The
cache is one SQLite file, boundaries.sqlite3, in that directory. The
directory is created with mode 0700 and must stay private and owned by you,
otherwise the cache is treated as unavailable.
Overriding or disabling. Set QUALIA_UPLOAD_BOUNDARY_CACHE_DIR to a
private directory to move it, or to an empty string to disable it. The cache
is also disabled automatically when its directory is inside the directory being
uploaded (for example, uploading your home directory with the default
location), so cache files never become part of a dataset.
What is cached. Changed files of at most 64 MiB, cut into between 2 and 8,192 chunks, in ordinary directory uploads. A file small enough to be a single chunk is skipped: there is no interior boundary for a later edit to reuse. Entries are keyed by the API base URL, the repository authorized for the upload, the file’s format label, the native chunker profile, and the file’s content hash, so a different account, server, repository or SDK build never matches another’s entry.
Bounds and eviction. The cache holds at most 128 entries and 32 MiB of encoded hints, and the database file is capped at 64 MiB. When a new entry would exceed the entry or payload limit, the least recently used entries (by last store or load time) are deleted first, before the new one is written.
When it is written. An upload collects its hints in memory, bounded by the same 128-entry and 32 MiB limits, and writes them once after the transfer finishes and before the dataset is published. An upload that fails during transfer writes nothing. One that fails after that, such as a source file changing before publication, may leave its hints behind; they are harmless, because every hint is checked against the server before it is used. When a directory offers more hints than the cache can hold, the largest files win, since they are the ones a later edit can save the most bytes on. Nothing is vacuumed, repaired or migrated automatically.
Failure behaviour. Any filesystem, SQLite or data error is a cache miss, and a failed write is skipped; the upload continues on the ordinary path. A cache damaged by a crash or power loss stays a miss until you delete the directory. Deleting it at any time is safe.
Custom HTTP Client
Section titled “Custom HTTP Client”Use a custom httpx client for advanced configuration like timeouts and connection limits.
import httpxfrom qualia import Qualia
custom_client = httpx.Client( timeout=60.0, limits=httpx.Limits(max_connections=10),)
client = Qualia(api_key="...", httpx_client=custom_client)Context Manager
Section titled “Context Manager”Automatically close the client when done using a context manager.
from qualia import Qualia
with Qualia(api_key="...") as client: models = client.models.types()# Client is automatically closed