> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Inspecting Datasets

Before you query a dataset, you need to know its tables, their columns, and what each column holds. The SDK reads them
from the dataset's [manifest](/datasets/manifests), without querying any data, in two forms:

* `inspect()` prints them, for exploring.
* `describe()` returns them as a dict, for code that adapts to a dataset, like finding every address column.

Both work on the datasets of an Amp, through the [admin client](/sdks/python/admin).

## Prerequisites

* The Python SDK, installed (see [Installation](/sdks/python#installation)).
* For an Amp's datasets: its Admin API, which [solo mode](/running-amp/solo-mode) serves at `http://localhost:1610`.
  The examples inspect the `acme/eth_mainnet` dataset from the [Quickstart](/get-started/quickstart).

## Print a dataset's tables

`client.datasets.inspect()` prints each table, with its columns' names and types, and whether they can be `NULL`:

```python [inspect_dataset.py]
from amp import Client

client = Client(query_url='grpc://localhost:1602', admin_url='http://localhost:1610')
client.datasets.inspect('acme', 'eth_mainnet')
```

:::terminal
```bash
python inspect_dataset.py
```

```
Dataset: acme/eth_mainnet@latest
Kind: evm-rpc

blocks (26 columns)
  _block_num           UInt64               NOT NULL
  _ts                  Timestamp(Nanosecond) NOT NULL
  block_num            UInt64               NOT NULL
  timestamp            Timestamp(Nanosecond) NOT NULL
  hash                 FixedSizeBinary(32)  NOT NULL
  parent_hash          FixedSizeBinary(32)  NOT NULL
  ommers_hash          FixedSizeBinary(32)  NOT NULL
  miner                FixedSizeBinary(20)  NOT NULL
  …

logs (14 columns)
  _block_num           UInt64               NOT NULL
  _ts                  Timestamp(Nanosecond) NOT NULL
  block_hash           FixedSizeBinary(32)  NOT NULL
  block_num            UInt64               NOT NULL
  timestamp            Timestamp(Nanosecond) NOT NULL
  tx_hash              FixedSizeBinary(32)  NOT NULL
  tx_index             UInt32               NOT NULL
  log_index            UInt32               NOT NULL
  address              FixedSizeBinary(20)  NOT NULL
  topic0               FixedSizeBinary(32)  NULL
  topic1               FixedSizeBinary(32)  NULL
  topic2               FixedSizeBinary(32)  NULL
  topic3               FixedSizeBinary(32)  NULL
  data                 Binary               NOT NULL

transactions (28 columns)
  _block_num           UInt64               NOT NULL
  _ts                  Timestamp(Nanosecond) NOT NULL
  …
```
:::

The version defaults to `latest`; pass another as the third argument, like `inspect('acme', 'eth_mainnet', '1.0.0')`.
For a raw dataset, [EVM RPC](/datasets/tables/evm-rpc) and the other table references explain each column.
`_block_num` and `_ts` are the [reserved columns](/datasets/manifests#reserved-columns) that Amp adds: the block a row
comes from, and its time.

### Column types

Types are [Apache Arrow](https://arrow.apache.org) types, the format Amp returns data in. The ones blockchain data uses
most:

| Type                    | What it holds                                                                                      |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `UInt64`, `UInt32`      | Unsigned integers, like block numbers and indexes.                                                 |
| `Int32`                 | Signed integers, like a transaction's type.                                                        |
| `Decimal128(38,0)`      | Integers too large for 64 bits, like gas prices in wei.                                            |
| `Timestamp(Nanosecond)` | A time, like a block's, in UTC.                                                                    |
| `FixedSizeBinary(20)`   | 20 bytes: an address. `evm_decode_hex()` turns it into `0x…` (see [EVM](/querying/functions/evm)). |
| `FixedSizeBinary(32)`   | 32 bytes: a hash, like a block's or a transaction's, or a log's topic.                             |
| `Binary`                | Bytes of any length, like a transaction's input or a log's data.                                   |
| `Utf8`                  | Text.                                                                                              |
| `Boolean`               | `true` or `false`.                                                                                 |

Lists and structs, like `transactions.access_list`, print as the manifest stores them: a nested Python dict, on one
long line.

## Get the tables as data

`client.datasets.describe()` returns a dict with an entry for each table, which lists its columns. Each column is a dict
with its `name`, its `type` (a string, as `inspect()` prints it), and `nullable`:

```python
schema = client.datasets.describe('acme', 'eth_mainnet')
print(schema['logs'][8])  # {'name': 'address', 'type': 'FixedSizeBinary(20)', 'nullable': False}
```

This script finds the columns that hold addresses:

```python [find_addresses.py]
from amp import Client

client = Client(query_url='grpc://localhost:1602', admin_url='http://localhost:1610')
schema = client.datasets.describe('acme', 'eth_mainnet')

for table, columns in schema.items():
    addresses = [column['name'] for column in columns if column['type'] == 'FixedSizeBinary(20)']
    print(f'{table}: {", ".join(addresses)}')
```

:::terminal
```bash
python find_addresses.py
```

```
blocks: miner
logs: address
transactions: to, from
```
:::

This one lists the columns that can be `NULL`, which queries should handle:

```python [nullable.py]
from amp import Client

client = Client(query_url='grpc://localhost:1602', admin_url='http://localhost:1610')
schema = client.datasets.describe('acme', 'eth_mainnet')

for table, columns in schema.items():
    nullable = [column['name'] for column in columns if column['nullable']]
    print(f'{table}: {", ".join(nullable)}')
```

:::terminal
```bash
python nullable.py
```

```
blocks: difficulty, base_fee_per_gas, withdrawals_root, blob_gas_used, excess_blob_gas, parent_beacon_root, requests_hash, block_access_list_hash, slot_number
logs: topic0, topic1, topic2, topic3
transactions: to, gas_price, chain_id, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas, state_root, access_list, blob_versioned_hashes, authorization_list
```
:::

And this one builds queries from what it finds, counting the rows of each table that has a `block_num` column:

```python [count_rows.py]
from amp import Client

client = Client(query_url='grpc://localhost:1602', admin_url='http://localhost:1610')
schema = client.datasets.describe('acme', 'eth_mainnet')

for table, columns in schema.items():
    if any(column['name'] == 'block_num' for column in columns):
        result = client.sql(f'SELECT count(*) AS n FROM "acme/eth_mainnet".{table}').to_arrow()
        print(f'{table}: {result["n"][0].as_py():,} rows')
```

:::terminal
```bash
python count_rows.py
```

```
blocks: 100 rows
logs: 223,498 rows
transactions: 17,153 rows
```
:::

## Inspect a dataset you already have

A `Dataset` handle, which `client.datasets.get()` and the methods that register datasets return, has `inspect()` and
`describe()` too. They take no arguments, and read the manifest the handle already has, without asking Amp again:

```python
dataset = client.datasets.get('acme', 'eth_mainnet', '1.0.0')
dataset.inspect()
```

The [Admin reference](/sdks/python/reference/admin#datasetsdescribe) has the methods' signatures.
