Skip to main content

SQLGlot-based semantic layer with multi-format adapter support

Project description

Sidemantic

SQLGlot-based semantic layer with multi-format adapter support.

Features

  • Simple API: Define measures once, use them everywhere
  • SQL query interface: Write familiar SQL that gets rewritten to use semantic layer
  • Automatic joins: Define relationships, joins happen automatically via graph traversal
  • Multi-format adapters: Import/export from Cube, MetricFlow (dbt), and native YAML
  • Rich measure types: Aggregations, ratios, formulas, cumulative, time comparisons, conversions
  • SQLGlot-powered: Dialect-agnostic SQL generation with transpilation support
  • Multi-hop joins: Automatic 2+ hop join discovery with intermediate models
  • Type-safe: Pydantic models with validation

Quick Start

Define your semantic layer (YAML)

# semantic_layer.yml
# yaml-language-server: $schema=./sidemantic-schema.json

models:
  - name: orders
    table: orders
    primary_key: id

    dimensions:
      - name: status
        type: categorical
        sql: status

      - name: order_date
        type: time
        sql: order_date
        granularity: day

    measures:
      - name: revenue
        agg: sum
        expr: amount

      - name: order_count
        agg: count

Query with SQL

from sidemantic import SemanticLayer

# Load semantic layer
layer = SemanticLayer.from_yaml("semantic_layer.yml")

# Query with familiar SQL - automatically rewritten
result = layer.sql("""
    SELECT revenue, status
    FROM orders
    WHERE status = 'completed'
""")

df = result.fetchdf()
Alternative: Python API
from sidemantic import SemanticLayer, Model, Measure, Dimension

layer = SemanticLayer()

orders = Model(
    name="orders",
    table="orders",
    primary_key="id",
    dimensions=[
        Dimension(name="status", type="categorical", sql="status"),
        Dimension(name="order_date", type="time", sql="order_date", granularity="day"),
    ],
    measures=[
        Measure(name="revenue", agg="sum", expr="amount"),
        Measure(name="order_count", agg="count"),
    ]
)
layer.add_model(orders)

# Programmatic query
result = layer.query(
    metrics=["orders.revenue"],
    dimensions=["orders.status"],
    filters=["orders.status = 'completed'"]
)
df = result.fetchdf()

Editor Support

Generate JSON Schema for autocomplete in VS Code, IntelliJ, etc:

uv run python -m sidemantic.schema

Add to your YAML files:

# yaml-language-server: $schema=./sidemantic-schema.json

Adapters

Import

from sidemantic.adapters import CubeAdapter, MetricFlowAdapter, SidemanticAdapter

# From Cube
cube_adapter = CubeAdapter()
graph = cube_adapter.parse("cube_schema.yml")

# From MetricFlow (dbt)
mf_adapter = MetricFlowAdapter()
graph = mf_adapter.parse("semantic_models.yml")

# From native Sidemantic
native_adapter = SidemanticAdapter()
graph = native_adapter.parse("semantic_layer.yml")

Export

# Export to Cube
cube_adapter.export(sl.graph, "output_cube.yml")

# Export to MetricFlow
mf_adapter.export(sl.graph, "output_metricflow.yml")

# Export to native
sl.to_yaml("output_sidemantic.yml")

Full round-trip support: Sidemantic ↔ Cube ↔ MetricFlow

Advanced Features

Complex Measures

Define ratios, formulas, cumulative metrics:

models:
  - name: orders
    table: orders
    primary_key: id

    measures:
      # Simple aggregation
      - name: revenue
        agg: sum
        expr: amount

      # Ratio
      - name: conversion_rate
        type: ratio
        numerator: completed_revenue
        denominator: total_revenue

      # Formula
      - name: profit_margin
        type: derived
        expr: "(revenue - cost) / revenue"

      # Cumulative
      - name: running_total
        type: cumulative
        expr: revenue
        window: "7 days"
Python alternative
Measure(name="conversion_rate", type="ratio",
        numerator="completed_revenue", denominator="total_revenue")

Measure(name="profit_margin", type="derived",
        expr="(revenue - cost) / revenue")

Measure(name="running_total", type="cumulative",
        expr="revenue", window="7 days")

Automatic Joins

Define relationships once, query across models:

models:
  - name: orders
    table: orders
    primary_key: id
    joins:
      - name: customers
        type: belongs_to
        foreign_key: customer_id

  - name: customers
    table: customers
    primary_key: id
    joins:
      - name: regions
        type: belongs_to
        foreign_key: region_id

Query spans 2 hops automatically:

# Automatically joins orders -> customers -> regions
result = layer.sql("""
    SELECT orders.revenue, regions.region_name
    FROM orders
""")

Test Coverage

  • 117 passing tests
  • Real DuckDB integration
  • SQL query rewriting
  • Round-trip adapter tests
  • Multi-hop join verification
  • Formula parsing validation

Run tests:

uv run pytest -v

Status

See docs/STATUS.md for detailed implementation status.

Completed:

  • ✅ SQL query interface with automatic rewriting
  • ✅ Core semantic layer with SQLGlot generation
  • ✅ Entity-based automatic joins
  • ✅ Multi-hop join discovery
  • ✅ Derived metrics with formula parsing
  • ✅ Native YAML format with import/export
  • ✅ Cube and MetricFlow adapters (import/export)
  • ✅ DuckDB integration

In Progress:

  • ⚠️ Cumulative metrics (basic structure exists, needs subquery pattern)

Future:

  • Query optimization
  • Pre-aggregations/caching
  • LookML adapter (requires grammar parser)

Examples

See examples/ directory:

  • sql_query_example.py - SQL query interface demonstration
  • basic_example.py - Core usage patterns
  • export_example.py - Multi-format export demonstration
  • sidemantic/orders.yml - Native YAML example
  • cube/orders.yml - Cube format example
  • metricflow/semantic_models.yml - MetricFlow format example

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sidemantic-0.1.1.tar.gz (42.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sidemantic-0.1.1-py3-none-any.whl (53.9 kB view details)

Uploaded Python 3

File details

Details for the file sidemantic-0.1.1.tar.gz.

File metadata

  • Download URL: sidemantic-0.1.1.tar.gz
  • Upload date:
  • Size: 42.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.30

File hashes

Hashes for sidemantic-0.1.1.tar.gz
Algorithm Hash digest
SHA256 df5d1415a7c3398df0debfd42560f6ee0388218aed6bac9e3e7af0c64dc2c9e5
MD5 bdd0d1124a5f841debe9c0eed2c2706c
BLAKE2b-256 4c528b8501e4fbeb97c1c7c1dbda07a07351f9796e0e734c574ec80205a4733a

See more details on using hashes here.

File details

Details for the file sidemantic-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: sidemantic-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 53.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.30

File hashes

Hashes for sidemantic-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f8be25c5e640de31d226d4e9235709057aef15de531dce2d8c95f0503887ad11
MD5 dc07ff453d86e22a1cbccec2be4d5e00
BLAKE2b-256 8b6180541b3b64e9914265b8dff8338470b9ca76a9aadf76b23268906fdc9799

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page