Skip to content

Browse a database

In this tutorial, you connect a small database to Metanno and browse its tables from the JupyterLab sidebar, without writing a query. The same steps work with your own database, whether it is a DuckDB file, PostgreSQL, Spark or Trino.

Before you start

Follow Installation, including the metanno-explorer kernel. Metanno keeps its configuration and your views in a metanno-home folder at the root of the JupyterLab file browser, usually your home folder, so the code of these tutorials reads it as ~/metanno-home from any notebook folder. This tutorial uses DuckDB, install its driver in the same environment:

pip install 'ibis-framework[duckdb]'

Create a sample database

Run this cell in a notebook. It writes a few clinical notes to clinic.duckdb, next to the notebook, in French like the notes EDS-NLP pre-annotates in a later tutorial:

from pathlib import Path

import duckdb
import pandas as pd

rows = [
    (1, 10, "oncologie", "2026-01-05", "Cancer du sein, stade pT2N0M0."),
    (2, 10, "oncologie", "2026-02-10", "Pas de complication."),
    (3, 11, "cardiologie", "2026-01-20", "Fibrillation atriale."),
    (4, 12, "oncologie", "2026-03-02", "Tumeur cT3N1M0, pas de métastase."),
    (5, 12, "radiologie", "2026-03-15", "Mammographie sans lésion."),
    (6, 13, "cardiologie", "2026-04-01", "Insuffisance cardiaque."),
]
columns = ["note_id", "patient_id", "department", "note_date", "text"]
notes = pd.DataFrame(rows, columns=columns)
notes["note_date"] = pd.to_datetime(notes["note_date"])
path = Path("clinic.duckdb").resolve()
with duckdb.connect(str(path)) as connection:
    connection.register("new_notes", notes)
    connection.execute("CREATE OR REPLACE TABLE notes AS SELECT * FROM new_notes")
print(path)

Keep the printed path, you need it in the next step.

Tell Metanno where it is

The Metanno icon in the JupyterLab sidebar

Click the Metanno icon in the left sidebar of JupyterLab, the two overlapping windows shown on the right. The panel is empty for now: Metanno only lists the sources declared in its configuration file.

Click the settings button ⚙ at the top of the panel. It shows the metanno-home folder in the file browser and opens its metanno.yaml in an editor, both created at the root of the file browser the first time. Replace its content with the following, using the path printed above:

connections:
  clinic:
    url: duckdb:///home/you/clinic.duckdb
sources:
  clinic:
    connection: clinic

Save the file, click the Metanno icon again, then click the reload button ↻ next to the settings button.

connections says how to reach a database, and sources lists what the panel shows. Here a single source uses the clinic connection, but a connection can serve several sources, for instance one per schema of the same database.

Explore a table

Under Explore, a clinic source now appears. Expand it and click notes: the table opens in a tab, with the selected note on the right like in Annotate a table.

This tab reads the database directly, without copying it. To keep things fast on large tables, it shows a preview of at most 500 rows, in no particular order, and edits are disabled since they would change your database.

The boxes under the column headers filter the rows of this preview only. To search the whole table, type your filters in these boxes, then click Apply source filters above the table: Metanno sends them to the database and shows the matching rows. For instance, type oncologie under department and apply it to the source.

What else is in the tab toolbar?
Button What it does
View menu Shows the same rows with another view, table or one of your own
Connection Reads the tables with another connection their source allows, e.g. Spark when DuckDB is slow
Copy code Writes notebook code for the current query, the next tutorial uses it
Save view Saves the open view as a file of metanno-home/views, shown under Explore
Refresh Reloads the table, after the database changed or its connection failed
Fullscreen Fills the browser tab with the view, its URL reopens the same table

Right-click a table in the panel and choose Open in new tab to keep the current tab.

What the panel lists

The panel has three parts:

  • Recipes lists the Python files of your views, see Design your view
  • Explore lists your sources and their tables, and the saved views you will create in Design your view. Once you have annotation files, an annotations source also shows their tables, read only, to compare annotators or show them next to the tables of your database
  • Annotations lists the files saved in the annotations folder, next to metanno.yaml, such as the ones you will create in the next tutorial. Click one to reopen it, without opening its notebook
metanno-home/
    metanno.yaml       connections and sources
    recipes/           your recipes (*.py), the Python code of views
    views/             saved views (*.yml)
    annotations/       annotation files (*.pret)

Once you write your own recipes, as in Design your view, you open them from Recipes too: the tab asks which table of your sources each recipe reads and shows a field for each of its parameters, then opens the view on these tables without any notebook.

Other kinds of sources

Besides the tables of a database, a source can list the Parquet or Delta datasets of a folder, or the tables of one schema of a larger database. A connection can also be opened by your own Python function, for instance to read credentials:

connections:
  warehouse:
    factory: project_connections:connect
    kwargs:
      database: /data/warehouse.duckdb
sources:
  oncology:
    connection: warehouse
    database: oncology
  exports:
    connection: warehouse
    kind: files
    format: parquet
    uri: /data/exports
preview:
  rows: 1000
  • catalog and database select the catalogue and the database, or schema, holding the tables of a source
  • kind: files shows the folders under uri with their Parquet or Delta datasets as tables. uri is a local or mounted folder, or a remote one such as hdfs://, s3a:// or gs:// listed by a Spark connection or a connection with a list_storage(uri) method. A folder holding data files and no subfolder is one dataset, for instance a folder of part-*.parquet files, also when it is uri itself, and next to subfolders a .parquet file is a table of its own. format can be left out: a dataset is Delta when its folder has a _delta_log
  • factory names a function, here connect in a project_connections.py file next to metanno.yaml, which returns an Ibis connection or a context manager giving one, and receives the kwargs
  • a factory connection whose table() lists the table files at each call, for instance the current files of a Delta table, can set refresh_tables_on_view_open = True on the connection it returns: Metanno then calls table() again each time a tab opens, so the tab sees the files written since the previous one
  • preview.rows changes the 500 rows shown by source tabs
  • default_connection names the connection that reads a dataset opened by its location outside the sources, as in the next note
from contextlib import contextmanager

import ibis


@contextmanager
def connect(database):
    backend = ibis.duckdb.connect(database)
    try:
        yield backend
    finally:
        backend.disconnect()

Connections open the first time one of their tables is shown. Install the Ibis driver of each one, such as ibis-framework[postgres], in the environment of the metanno-explorer kernel.

Open a dataset from another JupyterLab extension

An extension, for instance a file browser of HDFS, opens a dataset in a Metanno tab with the metanno-viewer:open-resource command, without depending on Metanno:

if (app.commands.hasCommand("metanno-viewer:open-resource"))
  await app.commands.execute("metanno-viewer:open-resource", {
    resource: { kind: "path", uri: "hdfs:///user/you/runs/notes" },
  });

The dataset opens as a table of the deepest kind: files source whose uri contains it, here runs/notes of a source on hdfs:///user/you, so its tab is the one the sidebar opens. Outside the sources, its folder becomes a source read with default_connection. connection picks another connection, for instance { kind: "path", uri, connection: "spark" }, and { kind: "table", source, table } opens a table of a source by its names.

Next

You found the notes you want to work on: Pick documents turns a query into an annotation file.