Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ae2121d
Add Lightdash converter
ota2000 Jul 23, 2026
5780885
Self-review: add CI workflow, joined-table references, percentile fid…
ota2000 Jul 23, 2026
80c775d
Address review feedback: protect structural keys, validate relationsh…
ota2000 Jul 23, 2026
bb09df3
Address review feedback: report invalid extension JSON, skip sql-less…
ota2000 Jul 23, 2026
41b8192
Align CI workflow path filter with the repo-wide update
ota2000 Jul 29, 2026
ee053bb
Merge branch 'main' into add-lightdash-converter
ota2000 Aug 20, 2026
049a528
Adopt the datatype field and is_time role-marker semantics
ota2000 Aug 20, 2026
adac280
Merge upstream main into add-lightdash-converter
Spissable Sep 2, 2026
c6d8ce2
refactor(lightdash): rename OSI references to Ossie after upstream re…
Spissable Sep 2, 2026
1611644
fix(lightdash): serialize enums when importing to a YAML document
Spissable Sep 2, 2026
8d6281c
feat(lightdash): keep joins, references and aggregations intact on im…
Spissable Sep 2, 2026
35e378b
feat(lightdash): honour dialects and export metrics across declared j…
Spissable Sep 2, 2026
770d602
feat(lightdash): map primary keys, AI hints, cardinality and time roles
Spissable Sep 2, 2026
b3a88b5
fix(lightdash): treat Liquid templating as non-portable on import
Spissable Sep 2, 2026
caa00b2
feat(lightdash): read Lightdash meta from config.meta
Spissable Sep 2, 2026
17580a9
feat(lightdash): name Ossie metrics by their Lightdash field id
Spissable Sep 2, 2026
65e34b0
fix(lightdash): import seeds as datasets and skip joins to unknown ta…
Spissable Sep 2, 2026
0e382dc
fix(lightdash): qualify bare column names and remember a metric's model
Spissable Sep 2, 2026
d3dafc9
feat(lightdash): stash what Ossie cannot express on the dataset and f…
Spissable Sep 2, 2026
d737c02
feat(lightdash)!: every column is a dimension unless hidden
Spissable Sep 2, 2026
c159ccc
feat(lightdash): report filters that other consumers cannot see
Spissable Sep 2, 2026
70d78cd
feat: register LIGHTDASH as a well-known vendor
Spissable Sep 2, 2026
356f9c6
docs(lightdash): explain fidelity and unavoidable losses
Spissable Sep 2, 2026
bcfb095
feat(lightdash): export Lightdash's dbt-free model files by default
Spissable Sep 2, 2026
819f00e
feat(lightdash): import a whole dbt project directory
Spissable Sep 2, 2026
f28caf1
feat(lightdash): say what was written and explain each issue
Spissable Sep 2, 2026
9cd03e8
feat(lightdash): take column types from a dbt catalog on import
Spissable Sep 2, 2026
93f2b9e
docs(lightdash): installation without a checkout
Spissable Sep 2, 2026
25f9bdf
feat(lightdash): -i/--input and -o/--output like the other converters
Spissable Sep 3, 2026
4caef39
fix(lightdash): replace the placeholder config on the next export
Spissable Sep 3, 2026
90dc482
feat(lightdash): report issues as readable blocks, --verbose for ever…
Spissable Sep 3, 2026
6860cd8
fix(lightdash): skip unparseable YAML and virtualenvs when walking a …
Spissable Sep 3, 2026
f172517
fix(lightdash): a missing input path is an error, not an empty document
Spissable Sep 3, 2026
4b58c0d
feat(lightdash): import Lightdash's own dbt-free model files
Spissable Sep 3, 2026
9eef6f2
chore(lightdash): remove workflow trailing whitespace
Spissable Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/converter-lightdash-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

name: Converters Lightdash CI

on:
push:
branches: [ "main" ]
paths:
- 'converters/lightdash/**'
- '.github/workflows/converter-lightdash-ci.yml'
pull_request:
branches: [ "main" ]
paths:
- 'converters/lightdash/**'
- '.github/workflows/converter-lightdash-ci.yml'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]

steps:
- name: Checkout project
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"

- name: Sync dependencies
working-directory: converters/lightdash
run: |
uv sync

- name: Unit Tests
working-directory: converters/lightdash
run: |
uv run pytest
1 change: 1 addition & 0 deletions converters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ The Ossie specification currently defines extensions for the following vendors:
| `OMNI` | Omni semantic model |
| `WISDOM` | WisdomAI domain |
| `NVIDIA_GSF` | NVIDIA Generative Semantic Fabric standalone YAML |
| `LIGHTDASH` | Lightdash semantic layer (dbt `meta`) |

Each vendor may define custom extensions (via the `custom_extensions` field in the Ossie spec) to carry vendor-specific metadata that does not have an equivalent in the core specification.

Expand Down
301 changes: 301 additions & 0 deletions converters/lightdash/README.md

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions converters/lightdash/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
"pytest>=8.0",
]

[project]
name = "apache-ossie-lightdash"
version = "0.1.0.dev0"
description = "Lightdash (dbt meta semantic definitions) <> Apache Ossie converter"
authors = [{ name = "Apache Software Foundation", email = "dev@ossie.apache.org" }]
requires-python = ">=3.11"
readme = "README.md"
license = "Apache-2.0"
keywords = [
"Apache Ossie",
"Ossie",
"Lightdash"
]
dependencies = [
"apache-ossie>=0.2.0.dev0",
"PyYAML>=6.0",
]

[project.scripts]
ossie-lightdash = "ossie_lightdash.cli:main"

[project.urls]
homepage = "https://ossie.apache.org/"
repository = "https://github.com/apache/ossie/"

[tool.hatch.build.targets.wheel]
packages = ["src/ossie_lightdash"]

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.uv]
required-version = ">=0.9.0"
default-groups = [
"dev"
]

[tool.uv.sources]
# apache-ossie is not yet published to PyPI; resolve it from the in-repo
# package for now. Remove this block once apache-ossie published to PyPI.
apache-ossie = { path = "../../python", editable = true}
34 changes: 34 additions & 0 deletions converters/lightdash/src/ossie_lightdash/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

from ossie_lightdash.converter_issues import (
ConverterIssue,
ConverterIssueType,
ConverterResult,
)
from ossie_lightdash.dbt_project import load_schema
from ossie_lightdash.lightdash_to_ossie import LightdashToOssieConverter
from ossie_lightdash.ossie_to_lightdash import OssieToLightdashConverter

__all__ = [
"ConverterIssue",
"ConverterIssueType",
"ConverterResult",
"LightdashToOssieConverter",
"OssieToLightdashConverter",
"load_schema",
]
113 changes: 113 additions & 0 deletions converters/lightdash/src/ossie_lightdash/catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Column types from a dbt ``catalog.json``.

``dbt docs generate`` records the warehouse's real type for every column of
every model. Lightdash learns most dimension types from the warehouse rather
than from YAML, so a Lightdash project usually has few authored types; the
catalog fills the gaps with the physical type reduced to Ossie's logical
vocabulary.
"""

import json
import re
from pathlib import Path
from typing import Dict, Optional

from ossie import OssieDataType

# Column types keyed by model name, then by lower-cased column name.
Catalog = Dict[str, Dict[str, str]]

_TYPE_RE = re.compile(r"^\s*([A-Za-z_][A-Za-z_0-9 ]*?)\s*(?:\(\s*(\d+)\s*(?:,\s*(\d+))?\s*\))?\s*$")

_EXACT = {
"INT": OssieDataType.INTEGER,
"INTEGER": OssieDataType.INTEGER,
"INT64": OssieDataType.INTEGER,
"BIGINT": OssieDataType.INTEGER,
"SMALLINT": OssieDataType.INTEGER,
"TINYINT": OssieDataType.INTEGER,
"INT2": OssieDataType.INTEGER,
"INT4": OssieDataType.INTEGER,
"INT8": OssieDataType.INTEGER,
"FLOAT": OssieDataType.FLOAT,
"FLOAT4": OssieDataType.FLOAT,
"FLOAT8": OssieDataType.FLOAT,
"FLOAT64": OssieDataType.FLOAT,
"DOUBLE": OssieDataType.FLOAT,
"DOUBLE PRECISION": OssieDataType.FLOAT,
"REAL": OssieDataType.FLOAT,
"STRING": OssieDataType.STRING,
"VARCHAR": OssieDataType.STRING,
"NVARCHAR": OssieDataType.STRING,
"CHAR": OssieDataType.STRING,
"CHARACTER": OssieDataType.STRING,
"CHARACTER VARYING": OssieDataType.STRING,
"TEXT": OssieDataType.STRING,
"BOOL": OssieDataType.BOOLEAN,
"BOOLEAN": OssieDataType.BOOLEAN,
"DATE": OssieDataType.DATE,
"DATETIME": OssieDataType.DATE_TIME,
"TIMESTAMP": OssieDataType.DATE_TIME,
"TIMESTAMP_NTZ": OssieDataType.DATE_TIME,
"TIMESTAMP WITHOUT TIME ZONE": OssieDataType.DATE_TIME,
"TIMESTAMPTZ": OssieDataType.DATE_TIME_TZ,
"TIMESTAMP_TZ": OssieDataType.DATE_TIME_TZ,
"TIMESTAMP_LTZ": OssieDataType.DATE_TIME_TZ,
"TIMESTAMP WITH TIME ZONE": OssieDataType.DATE_TIME_TZ,
"TIME": OssieDataType.TIME,
}
# Exact-decimal families: integral when the scale is 0 (or, for NUMBER, when
# Snowflake's default NUMBER(38,0) is spelled without arguments).
_DECIMAL_FAMILY = {"NUMERIC", "BIGNUMERIC", "DECIMAL", "NUMBER"}


def warehouse_type_to_datatype(warehouse_type: str) -> Optional[OssieDataType]:
"""Reduce a physical column type to an Ossie datatype, or None when the
type is outside the portable vocabulary (arrays, structs, JSON, ...)."""
match = _TYPE_RE.match(warehouse_type or "")
if match is None:
return None
base = " ".join(match.group(1).upper().split())
scale = match.group(3)
if base in _DECIMAL_FAMILY:
if scale is not None:
return OssieDataType.INTEGER if int(scale) == 0 else OssieDataType.DECIMAL
return OssieDataType.INTEGER if base == "NUMBER" else OssieDataType.DECIMAL
return _EXACT.get(base)


def load_catalog(path: Path) -> Catalog:
"""Read a dbt ``catalog.json`` into ``{model: {column: type}}``.

Models and seeds are keyed by their dbt name (the last segment of the
node's unique id); column names are lower-cased because warehouses differ
in how they report case.
"""
document = json.loads(Path(path).read_text(encoding="utf-8"))
catalog: Catalog = {}
for unique_id, node in (document.get("nodes") or {}).items():
kind = unique_id.split(".", 1)[0]
if kind not in ("model", "seed"):
continue
name = unique_id.rsplit(".", 1)[-1]
catalog[name] = {
column_name.lower(): (column.get("type") or "")
for column_name, column in (node.get("columns") or {}).items()
}
return catalog
Loading