resolvespec (Python)
Python client for ResolveSpec REST, HeaderSpec (restheadspec), FunctionSpec and WebSocketSpec. Port of resolvespec-js.
- Python >= 3.11,
httpx(REST, sync + async),websockets(WS, async) - Options/filters/sorts are plain dicts using the wire key names (
TypedDicthints inresolvespec.types)
pip install resolvespec
Clients
| Protocol | Sync | Async | Transport |
|---|---|---|---|
| ResolveSpec | ResolveSpecClient |
AsyncResolveSpecClient |
POST + JSON body {operation, id, data, options} |
| HeaderSpec | HeaderSpecClient |
AsyncHeaderSpecClient |
GET/POST/PUT/DELETE, options as X-* headers |
| FunctionSpec | FuncSpecClient |
AsyncFuncSpecClient |
user-defined SQL endpoints; params via query string + X-* headers |
| WebSocketSpec | - | WebSocketClient |
WebSocket JSON messages |
Constructor (REST): Client(base_url, token=None, headers=None, timeout=30.0)
token->Authorization: Bearer; wins overheadersheaders: custom headers, merged case-insensitively; snapshot at construction- Sync: context manager /
close(). Async:async with/await aclose() - Cached sync factories:
get_resolvespec_client(),get_headerspec_client()(same args -> same instance)
ResolveSpec
URL: {base}/{schema}/{entity}[/{id}]
| Method | Signature |
|---|---|
get_metadata |
(schema, entity) (GET) |
read |
(schema, entity, id=None, options=None) |
create |
(schema, entity, data, options=None) |
update |
(schema, entity, data, id=None, options=None) |
delete |
(schema, entity, id) |
id: int/str -> URL path; list[str] -> body id.
Returns {"success", "data", "metadata"?, "error"?}.
HeaderSpec
| Method | HTTP | Signature |
|---|---|---|
read |
GET | (schema, entity, id=None, options=None) |
create |
POST | (schema, entity, data, options=None) |
update |
PUT | (schema, entity, id, data, options=None) |
delete |
DELETE | (schema, entity, id) |
Response metadata derived from Content-Range (offset-end/total) and X-Limit.
build_headers(options), encode_header_value() / decode_header_value() (ZIP_ / __ base64) are exported.
Option -> header
| Option | Header |
|---|---|
columns / omit_columns |
X-Select-Fields / X-Not-Select-Fields |
filter eq + AND |
X-FieldFilter-{col} |
| filter AND / OR | X-SearchOp-{op}-{col} / X-SearchOr-{op}-{col} |
spatial (st_*, bbox) / vector (*_within) filter |
X-SpatialFilter-{col} / X-VectorFilter-{col} (JSON) |
sort |
X-Sort (+col,-col) |
limit / offset |
X-Limit / X-Offset |
cursor_forward / cursor_backward |
X-Cursor-Forward / X-Cursor-Backward |
preload |
X-Preload (Rel:c1,c2|Rel2), X-Preload-Where, X-Preload-{n}[-Where] |
expand |
X-Expand |
custom_sql_joins / custom_sql_or |
X-Custom-SQL-Join / X-Custom-SQL-Or |
search_columns |
X-SearchCols |
advanced_sql |
X-AdvSQL-{col} |
computedColumns |
X-CQL-SEL-{name} |
customOperators |
X-Custom-SQL-W (AND-joined) |
vector_search |
X-Vector-Search-{col}, -Vector, -As, -Dir |
fetch_row_number |
X-Fetch-RowNumber |
clean_json, distinct, skip_count, skip_cache, atomic_transaction, single_record_as_object |
X-Clean-JSON, X-Distinct, X-SkipCount, X-SkipCache, X-Transaction-Atomic, X-Single-Record-As-Object |
pk_row |
X-PKRow |
response_format (simple/detail/syncfusion) |
X-SimpleApi / X-DetailApi / X-Syncfusion |
xfiles |
X-Files (ZIP_ base64 JSON) |
Filter operator -> header op: eq equals, neq notequals, gt greaterthan, gte greaterthanorequal, lt lessthan, lte lessthanorequal, like/ilike/contains contains, startswith beginswith, endswith, in, between, between_inclusive betweeninclusive, is_null empty, is_not_null notempty.
FunctionSpec
Routes are defined by the server app, so calls take a path. The server never reads a request body.
| Method | Server handler | Result |
|---|---|---|
query(path, params=None, options=None, *, method="GET") |
SqlQuery (single record) |
{success, data} |
query_list(path, params=None, options=None, *, method="GET") |
SqlQueryList |
{success, data, metadata} (from Content-Range: items a-b/total) |
params-> query string.bool->true/false,Noneskipped,list-> repeated key (server:INfilter).p-prefixed names are substituted into the SQL.options->X-*headers. Query values override headers of the same name.- 206 Partial Content (more rows than returned) is treated as success.
| Option | Header |
|---|---|
filters (eq+AND) |
X-FieldFilter-{col} |
filters (other) |
X-SearchOp-{op}-{col} / X-SearchOr-{op}-{col} |
search_filters {col: text} |
X-SearchFilter-{col} (ILIKE) |
custom_sql_where / custom_sql_or |
X-Custom-SQL-W / X-Custom-SQL-Or |
sort |
X-Sort as SQL terms: col ASC,col DESC |
limit / offset |
X-Limit / X-Offset |
distinct, skip_count, skip_cache |
X-Distinct, X-SkipCount, X-SkipCache |
response_format |
X-SimpleApi / X-DetailApi / X-Syncfusion (data shape changes: array / {items,...} / {result,count}) |
Server limits:
sortgoes verbatim intoORDER BY;-col(restheadspec style) does not mean DESC.X-Select-Fields/X-Not-Select-Fieldsare no-ops server-side, so not exposed.- One search operator per column; same column twice keeps the last.
- Values starting with
ZIP_/__are base64-decoded by the server; such plaintext cannot be sent. - Non-ASCII / control-char values are sent
ZIP_-encoded automatically.
WebSocketSpec
WebSocketClient(url, *, reconnect=True, reconnect_interval=3.0, max_reconnect_attempts=10, heartbeat_interval=30.0, request_timeout=30.0, subscribe_timeout=10.0, headers=None)
| Method | Notes |
|---|---|
connect() / close() |
also async with |
request(operation, entity, *, schema, record_id, data, options) |
returns response data |
read(entity, *, schema, record_id, filters, columns, sort, preload, limit, offset) |
|
create(entity, data, *, schema) |
|
update(entity, id, data, *, schema) |
|
delete(entity, id, *, schema) |
|
meta(entity, *, schema) |
|
subscribe(entity, callback, *, schema, filters) |
returns subscription id; callback gets notification dict (sync or async) |
unsubscribe(subscription_id) |
|
on(event, cb) / off(event) |
events: connect, disconnect, error, message, state_change |
state, is_connected(), get_subscriptions() |
Auto-reconnect does not restore subscriptions; re-subscribe on connect.
Errors
ResolveSpecError(message, status_code, code, details) on non-2xx (REST) or failed response / timeout / not connected (WS).
Dev
pip install -e '.[dev]'
pytest