> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# blockdb-py

> Python reference for Normal, block, and Time Tables, block subscriptions, and filters.

Import public interfaces from `blockdb`. Use writes and subscriptions in Chaintable Notebooks; Functions can use permitted read interfaces. See the [source](https://github.com/Chaintable/blockdb-py) for the full implementation.

## Normal Tables

### `NormalTable(table_id)`

`table_id` is the full table ID. Returns a Normal Table object.

| Method                                                    | Parameters                                   | Return value                                            |
| --------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------- |
| `get(row_id)`                                             | Primary key                                  | Record dictionary, or `None` if missing                 |
| `get_many(row_ids)`                                       | Primary-key list                             | Records in input order, with `None` for missing entries |
| `filter_rows(filters='', order_by='', limit=0, offset=0)` | SQL condition, sort order, limit, and offset | Record list                                             |
| `scan(sql)`                                               | Complete supported SQL statement             | List of all results                                     |
| `scan_iter(sql)`                                          | Same as `scan()`                             | Iterator yielding individual rows                       |
| `write(rows, condition=None)`                             | Record list and optional update condition    | `True` when the request is accepted                     |
| `batch_write(rows, condition=None)`                       | Batch records and optional update condition  | `True` after asynchronous job submission                |
| `delete(row_ids)`                                         | Primary keys to delete                       | `True` when the request is accepted                     |

```python theme={null}
from blockdb import NormalTable

notes = NormalTable('demo.asset_notes')
print(notes.get('usdc'))
print(notes.filter_rows("symbol = 'USDC'", limit=5))
print(notes.scan("SELECT id, symbol FROM `demo.asset_notes` WHERE id = 'usdc'"))
```

First [create the example table](/guides/tables/create-and-manage), then [write its records](/guides/tables/read-and-write).

`filter_rows()` conditions omit the `WHERE` keyword; use `limit` to restrict the number of rows. In `scan()`, quote the full table ID with backticks. The SDK does not fill in `FROM` from the table object. This is the BlockDB scan interface, whose supported SQL differs from the analytics engine.

## Conditional writes

```python theme={null}
from blockdb import NormalTable, IF_LARGER

NormalTable('demo.asset_notes').write(
    [{'id': 'usdc', 'symbol': 'USDC', 'amount': 2000000}],
    condition=('amount', IF_LARGER),
)
```

`condition` is `(column_name, mode)`. `IF_LARGER` updates an existing record only when the new value is larger; `IF_SMALLER` updates only when it is smaller. Missing records are still inserted. The condition column cannot be the primary key, and all rows in a batch share one condition.

Every row in `batch_write()` needs the same field set. Success does not guarantee an immediately visible write. To check batch-write status, see [Inspect asynchronous write jobs](/guides/tables/advanced#inspect-asynchronous-write-jobs).

## Block Event and Block State Tables

### `EventTable(table_id, block_id=None)` / `StateTable(table_id, block_id=None)`

`table_id` identifies the table; `block_id` sets the read position. Event tables read by event ID, while state tables read a business ID's state at that block. Without `block_id`, state tables read the object's latest stored state, while event tables read the record at the highest block height for that event ID, without a consensus-height limit. When you specify a block, the server checks that the table's consensus height covers it.

| Method                         | Parameters                                            | Return value                             |
| ------------------------------ | ----------------------------------------------------- | ---------------------------------------- |
| `get(row_id)`                  | Event or state ID                                     | Record dictionary or `None`              |
| `get_many(row_ids)`            | ID list                                               | Records corresponding to the inputs      |
| `get_block_rows(block)`        | `Block` object or object containing block information | Records written in that block            |
| `get_consensus_height()`       | None                                                  | Integer table consensus height           |
| `write(rows, block_id)`        | Result rows and block ID                              | `True` when accepted                     |
| `batch_write(rows, bundle_id)` | Rows with block fields and a block-bundle number      | `True` after asynchronous job submission |

```python theme={null}
from blockdb import StateTable

reserves = StateTable('demo.pool_reserves.eth')
print(reserves.get('0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc'))
```

This example reads the latest state from the table built in [Track liquidity pool reserves](/quickstart/build/track-pool-reserves). Reading state at a specified block requires the table's consensus height to cover that block; see [How tables work](/guides/tables/model#block-data-and-processing-progress).

For per-block `write()`, the server fills in block fields. Every row in `batch_write()` must include `block_id`, `block_height`, and `block_timestamp`. A bundle number is not a block height.

## Time Tables

### `TimeTable(table_id, time_at=None)`

`time_at` sets the read time; omit it for the latest value. It accepts RFC3339 strings or Python `datetime` values. Times without a timezone are interpreted as UTC.

| Method                 | Parameters                                        | Return value                                              |
| ---------------------- | ------------------------------------------------- | --------------------------------------------------------- |
| `get(row_id)`          | Business ID                                       | Record containing `id`, `time_at`, and `value`, or `None` |
| `get_many(row_ids)`    | ID list                                           | Records in input order, with `None` for missing entries   |
| `write(rows, time_at)` | `{id, value}` records and a shared timestamp      | `True` when accepted                                      |
| `batch_write(rows)`    | Rows each containing `id`, `time_at`, and `value` | `True` after asynchronous job submission                  |

```python theme={null}
from blockdb import TimeTable

price = TimeTable('price.price.eth').get(
    '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
)
print(price)
```

`write()` uses its timestamp argument, ignoring any row-level `time_at`. Use `batch_write()` for multiple timestamps, with each row containing only the three fixed fields.

The server groups time points into buckets and keeps the latest point in each bucket. Resolution changes with data age; see [How tables work](/guides/tables/model).

## Blocks and subscriptions

| Interface                                           | Parameters and return value                                     |
| --------------------------------------------------- | --------------------------------------------------------------- |
| `Block(id, height, timestamp)`                      | Block ID, integer height, and timestamp; returns a block object |
| `Subscribe(tables=None, start_at=None)`             | Table objects or IDs, with an optional event-time cursor        |
| `sub.listen()`                                      | Continuously yields `(table_id, Block)`                         |
| `sub.close()`                                       | Stops the subscription; callable from another thread            |
| `Aligned(sub, loose_align=None, strict_align=None)` | Subscription, trigger tables, and consensus dependency tables   |
| `aligned.listen()`                                  | Yields `Block` when the tables meet height requirements         |

`start_at` is a subscription event-time cursor accepting an RFC3339 string or Unix seconds, not a historical block height. Reconnection does not guarantee recovery of arbitrarily old events; combine subscriptions with backfills.

`loose_align` requires at least one trigger table. `strict_align` requires dependency consensus to reach the block being processed. See [Subscribe to block events](/guides/notebooks/advanced#subscribe-to-block-events) for a runnable example.

## Filter expressions and types

```python theme={null}
from blockdb import Column, filter

operator = filter(
    (Column('contract_id') == '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc')
    & (Column('name') == 'Sync')
)
```

`Column` and `filter()` build filters for Pipeline sources. Combine expressions with parenthesized `&` and `|`. Do not pass these expressions to `filter_rows()`, which expects a SQL condition string.

`LogicalType` provides type names, and `restore_value(logical_type, raw)` restores Python values according to metadata. For example, `restore_value('UINT256', '1500000')` retains a decimal string; use `int()` for computation. See [Data types](/reference/data-types).
