mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-01 04:21:58 +00:00
143 lines
6.8 KiB
Markdown
143 lines
6.8 KiB
Markdown
# 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 (`TypedDict` hints in `resolvespec.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 over `headers`
|
|
- `headers`: 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`, `None` skipped, `list` -> repeated key (server: `IN` filter). `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:
|
|
- `sort` goes verbatim into `ORDER BY`; `-col` (restheadspec style) does **not** mean DESC.
|
|
- `X-Select-Fields` / `X-Not-Select-Fields` are 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
|
|
```
|