Semolina

Semolina: the ORM for your Semantic Layer.

Typed models in Python, supporting IDE autocomplete, and a Django-like fluent query interface for the semantic layer of your data warehouse backend.

Start here

Six tutorials, in order, building one application. They run on a local DuckDB database, so you can follow all of them without a warehouse.

1. Installation

Install Semolina and the extra for your backend.

Installation
2. Your first query

Define a model, register an engine, and read the rows back.

Your first query
3. Shaping a report

Narrow, sort, and trim the result with .where(), .order_by() and .limit().

Shaping a report
4. Serve a dashboard endpoint

A FastAPI service that answers GET /revenue with a typed JSON body.

Serve a dashboard endpoint
5. Test without a warehouse

Run your real query code against a semantic view built inside the test process.

Test queries without a warehouse
6. Generate models

Stop hand-writing the model and the DTO: semolina codegen and codegen-dto read them off the warehouse.

Generate models from your warehouse

Go further

The how-to guides extend the tutorials one topic at a time, and the explanation pages cover the ideas underneath. Read them when you hit the thing they cover.

New to semantic views?

What they are, and why AGG(), MEASURE() and semantic_view() are three spellings of the same idea.

What is a semantic view?
Configure your warehouse backend

Snowflake, Databricks and DuckDB connection settings, and the .semolina.toml that holds them.

How to configure your warehouse backend
Define models

Map Metric and Dimension fields to your warehouse semantic views.

How to define models
Build queries

Chain .metrics(), .dimensions(), .where(), .order_by(), .limit().

How to build queries
Typed results

Convert rows into Pydantic models with .into(), or generate the class with semolina codegen-dto.

How to map results into Pydantic models with .into()
API reference

Auto-generated reference for every public class, function, and field.

semolina

Quick example

from semolina import (
    SemanticView,
    Metric,
    Dimension,
    create_engine,
)


class Sales(SemanticView, view="sales"):
    revenue = Metric()
    country = Dimension()


# reads .semolina.toml
create_engine("default", register=True)

with (
    Sales.query()
    .metrics(Sales.revenue)
    .dimensions(Sales.country)
    .limit(10)
    .execute()
) as cursor:
    for row in cursor.fetchall_rows():
        print(row.country, row.revenue)

Write the query once. Change the type in your .semolina.toml and the same code runs on Databricks.

Each selected column comes back under its field name on every backend, so row.country and row.revenue read the same on Snowflake, Databricks and DuckDB.