diff --git a/CHANGELOG-versions.md b/CHANGELOG-versions.md index 3962e697..71c6b362 100644 --- a/CHANGELOG-versions.md +++ b/CHANGELOG-versions.md @@ -1,5 +1,6 @@ + diff --git a/CHANGELOG.md b/CHANGELOG.md index d5bd0497..c8105c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to OrionBelt Semantic Layer are documented here. +## [2.22.0] - 2026-07-15 + +### Added + +- **Python 3.14 support.** All three Docker images (API, UI, Flight) now build on `python:3.14-slim`, and 3.14 joined the CI test matrix alongside 3.12 and 3.13. Reaching it required `snowflake-connector-python` 4.3.0 -> 4.6.0 and `pyarrow` 19.0.1 -> 25.0.0: the previously locked versions had no cp314 wheels, so the slim image tried to build them from source and failed. + +### Changed + +- **sqlglot 30.** The constraint moved from `>=26.0,<27.0` to `>=30.0,<31.0`. sqlglot 30 renamed the `Select` args that collide with Python keywords (`from` -> `from_`, `with` -> `with_`) and made `exp.Expr` the common base class, so the OBSQL translator (`compiler/sql_translator.py`) and the pgwire federation-subquery flattening (`pgwire/router.py`) were updated to match. No behavior change, but installs pinning sqlglot 26 will need to move. +- **Dependency constraints widened**: `pyarrow` `>=16.0,<20.0` -> `>=16.0,<26.0` (the old cap had no version-specific rationale and blocked the cp314 wheels the 3.14 images need) and `structlog` `>=25.1,<26.0` -> `>=25.1,<27.0`. + +### Fixed + +- **OSI converter dropped metrics whose SQL referenced physical codes** (`osi-orionbelt` 0.1.1). `OBMLtoOSI` emits metric SQL against the physical code (e.g. `SUM(fact_orders.amount)`), but `OSItoOBML` resolved references by OSI dataset/field name only, so any metric over a data object whose code differed from its display name was dropped on the return trip. References now resolve by both name and physical code, including quoted physical identifiers (a Snowflake/Databricks `"net_amount"` field expression or a `WH.PUBLIC."fact_orders"` source table) that previously fell through to a LOSSY unconverted metric. +- **OSI converter silently overwrote colliding dimension names** (`osi-orionbelt` 0.1.1). `_extract_dimensions` keyed dimensions by bare field name, so the same field name in two datasets (`Orders.date`, `Invoices.date`) meant the second silently replaced the first. The later one is now qualified with its data object and a warning is recorded. +- **OSI converter emitted invalid OBML for fields with spaces in the display name** (`osi-orionbelt` 0.1.1). Resolving a physical code to a display name containing a space spliced `Orders.Net Amount` into the intermediate SQL, which the downstream parsers split on the space, emitting `{[Orders].[Net]} Amount` with no LOSSY warning. Resolved names that are not bare SQL words are now bracket-quoted, and the `dataset.column` parsers accept and unquote bracketed identifiers. +- **`validate_osi` raised `AttributeError` on malformed input** (`osi-orionbelt` 0.1.1). Its semantic loops now guard against datasets and fields that are not lists of dicts, returning schema errors instead of crashing. + ## [2.21.1] - 2026-07-13 ### Fixed diff --git a/README.md b/README.md index 88fe1e89..ce652115 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ralforion/orionbelt-semantic-layer/blob/main/examples/quickstart_colab.ipynb) [![GitHub stars](https://img.shields.io/github/stars/ralforion/orionbelt-semantic-layer?style=social)](https://github.com/ralforion/orionbelt-semantic-layer) -[![Version 2.21.1](https://img.shields.io/badge/version-2.21.1-purple.svg)](https://github.com/ralforion/orionbelt-semantic-layer/releases) +[![Version 2.22.0](https://img.shields.io/badge/version-2.22.0-purple.svg)](https://github.com/ralforion/orionbelt-semantic-layer/releases) [![PyPI](https://img.shields.io/pypi/v/orionbelt-semantic-layer?logo=pypi&logoColor=white)](https://pypi.org/project/orionbelt-semantic-layer/) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) [![License: BSL 1.1](https://img.shields.io/badge/License-BSL_1.1-orange.svg)](https://github.com/ralforion/orionbelt-semantic-layer/blob/main/LICENSE) @@ -242,7 +242,7 @@ Open [http://localhost:8080/docs](http://localhost:8080/docs) to explore the API # docker-compose.yml services: api: - image: ralforion/orionbelt-semantic-layer-api:2.21.1 + image: ralforion/orionbelt-semantic-layer-api:2.22.0 ports: ["8080:8080"] env_file: .env volumes: @@ -251,7 +251,7 @@ services: MODEL_FILES: /app/models/my-model.obml.yml ui: - image: ralforion/orionbelt-semantic-layer-ui:2.21.1 + image: ralforion/orionbelt-semantic-layer-ui:2.22.0 ports: ["7860:7860"] environment: API_BASE_URL: http://api:8080 @@ -267,7 +267,7 @@ See [`.env.template`](.env.template) for the full environment variable reference > - `API_SERVER_HOST` is already `0.0.0.0` inside the container — no override needed. > - MCP via stdio does not work in Docker. Use the [MCP HTTP client](https://github.com/ralforion/orionbelt-semantic-layer-mcp) for containerized deployments. > - Mount models to `/app/models` (or any path) and set `MODEL_FILES` (comma-separated paths) to pre-load on startup. -> - For production, pin a version tag (`:2.21.1`) rather than `:latest`. +> - For production, pin a version tag (`:2.22.0`) rather than `:latest`. ### Claude Desktop / MCP diff --git a/integrations/chatgpt-custom-gpt/openapi-gpt-action.yaml b/integrations/chatgpt-custom-gpt/openapi-gpt-action.yaml index 9e8341a5..8f6ef613 100644 --- a/integrations/chatgpt-custom-gpt/openapi-gpt-action.yaml +++ b/integrations/chatgpt-custom-gpt/openapi-gpt-action.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OrionBelt Semantic Layer - version: 2.21.1 + version: 2.22.0 description: > Compile YAML semantic models (OBML) as analytical SQL across 8 database dialects. This API runs in single-model mode with a pre-loaded semantic model. diff --git a/mkdocs.yml b/mkdocs.yml index a4d92eda..9e40edc0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,7 +9,7 @@ dev_addr: 127.0.0.1:8080 extra: obml_version: "1.0" - project_version: "2.21.1" + project_version: "2.22.0" analytics: provider: google property: G-X19K4GX3EX diff --git a/packages/osi-orionbelt/pyproject.toml b/packages/osi-orionbelt/pyproject.toml index 27f6a315..f02f1f23 100644 --- a/packages/osi-orionbelt/pyproject.toml +++ b/packages/osi-orionbelt/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "osi-orionbelt" -version = "0.1.0" +version = "0.1.1" description = "Bidirectional OBML <-> OSI (Open Semantic Interchange) converter for OrionBelt semantic models" license = { text = "Apache-2.0" } readme = "README.md" diff --git a/packages/osi-orionbelt/src/osi_orionbelt/__init__.py b/packages/osi-orionbelt/src/osi_orionbelt/__init__.py index 9bccf42c..1506c4a8 100644 --- a/packages/osi-orionbelt/src/osi_orionbelt/__init__.py +++ b/packages/osi-orionbelt/src/osi_orionbelt/__init__.py @@ -27,7 +27,7 @@ validate_osi_ontology, ) -__version__ = "0.1.0" +__version__ = "0.1.1" __all__ = [ "OBMLtoOSI", diff --git a/pyproject.toml b/pyproject.toml index b5c050bf..6360e408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "orionbelt-semantic-layer" -version = "2.21.1" +version = "2.22.0" description = "OrionBelt Semantic Layer - Compiles and executes YAML semantic models as analytical SQL" license = {text = "BSL-1.1"} authors = [{name = "Ralf Becher, RALFORION d.o.o.", email = "info@ralforion.com"}] diff --git a/src/orionbelt/__init__.py b/src/orionbelt/__init__.py index 13d5e8e7..3e95a8d4 100644 --- a/src/orionbelt/__init__.py +++ b/src/orionbelt/__init__.py @@ -1,3 +1,3 @@ """OrionBelt Semantic Layer — Compiles and executes YAML semantic models as analytical SQL.""" -__version__ = "2.21.1" +__version__ = "2.22.0" diff --git a/tests/unit/test_version_consistency.py b/tests/unit/test_version_consistency.py new file mode 100644 index 00000000..b87103d9 --- /dev/null +++ b/tests/unit/test_version_consistency.py @@ -0,0 +1,40 @@ +"""Every package's exported ``__version__`` must match its pyproject version. + +A published wheel self-reports through ``.__version__``, so a version bump +that updates pyproject.toml but not the module constant ships a wheel whose +metadata and exported version disagree. This has happened: osi-orionbelt 0.1.1 +was tagged while the module still exported 0.1.0. + +Both packages are checked, because osi-orionbelt is versioned independently of +orionbelt-semantic-layer and is easy to miss when grepping for the main +package's version. +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).parents[2] + +PACKAGES = [ + ("orionbelt", REPO_ROOT / "pyproject.toml"), + ("osi_orionbelt", REPO_ROOT / "packages" / "osi-orionbelt" / "pyproject.toml"), +] + + +def _pyproject_version(pyproject: Path) -> str: + with pyproject.open("rb") as fh: + return str(tomllib.load(fh)["project"]["version"]) + + +@pytest.mark.parametrize(("module_name", "pyproject"), PACKAGES) +def test_exported_version_matches_pyproject(module_name: str, pyproject: Path) -> None: + module = pytest.importorskip(module_name) + assert module.__version__ == _pyproject_version(pyproject), ( + f"{module_name}.__version__ is {module.__version__!r} but " + f"{pyproject.relative_to(REPO_ROOT)} declares " + f"{_pyproject_version(pyproject)!r}. Bump both." + ) diff --git a/uv.lock b/uv.lock index 358c98cb..c71ecc84 100644 --- a/uv.lock +++ b/uv.lock @@ -2285,7 +2285,7 @@ wheels = [ [[package]] name = "orionbelt-semantic-layer" -version = "2.21.1" +version = "2.22.0" source = { editable = "." } dependencies = [ { name = "fastapi" }, @@ -2546,7 +2546,7 @@ wheels = [ [[package]] name = "osi-orionbelt" -version = "0.1.0" +version = "0.1.1" source = { editable = "packages/osi-orionbelt" } dependencies = [ { name = "jsonschema" },
VersionDatesCommitsHighlights
v2.22.0'26 Jul 153
  • Python 3.14 support. All three Docker images (API, UI, Flight) build on python:3.14-slim and 3.14 joined the CI matrix; snowflake-connector-python and pyarrow were raised to versions that ship cp314 wheels, since the locked ones had none and the slim image failed building them from source.
  • sqlglot 30. Constraint moved >=26,<27 to >=30,<31. sqlglot 30 renamed the Select args colliding with Python keywords and made exp.Expr the common base class; the OBSQL translator and pgwire subquery flattening were updated to match. No behavior change, but installs pinning sqlglot 26 must move. pyarrow widened to <26 and structlog to <27.
  • OSI converter roundtrip fidelity (osi-orionbelt 0.1.1). Metrics whose SQL referenced physical codes rather than display names were dropped on the return trip; colliding dimension names silently overwrote each other; fields with spaces in the display name emitted invalid OBML with no LOSSY warning; and validate_osi crashed on malformed input instead of reporting schema errors.
v2.21.1'26 Jul 131
  • Arrow Flight SQL result cache restored. Flight's cache read/write called a result-codec API removed in 2.20.0, so every Flight query silently missed the cache and never stored a result; Flight now uses the current data-only codec and shares one entry per compiled query with REST and pgwire.
  • Flight cache hits keep the advertised schema. Empty / all-null results no longer stream null-typed columns on a hit: Flight preserves the exact Arrow schema on write and casts a hit back to the schema advertised in FlightInfo.
  • Cache key + TTL derivation shared across surfaces via a single layer-clean service module, replacing three copies that could drift (internal refactor, no behavior change).
v2.21.0'26 Jul 073
  • Cross-session content-addressed model cache. Identical OBML loaded into different sessions now compiles once and is shared under a stable content-derived model_id, instead of each session recompiling its own copy under a random id. In admin-curated mode the curated model is compiled once for the process and every user session references it; shared models are refcounted and evicted once no session uses them. Because model_id is part of the result-cache key, identical model plus SQL now shares result-cache entries across sessions too.
  • Single-model shortcut resolution scoped to protected sessions. In admin-curated single-model mode the top-level shortcut endpoints ignore transient user sessions, so a duplicated per-session copy of the curated model no longer triggers a spurious "multiple models loaded across sessions" error.
v2.20.0'26 Jul 063
  • Ontology Graph rendered from the OBSL ontology. The UI graph is now a rendering of the exported RDF (single source of truth), so it and the ontology can't drift; synthesized row-count measures appear in it, and an Export Onto button downloads the Turtle. New toolbar: rotate-left/right controls and a clearer PNG export.
  • New ontology predicates. obsl:anchoredTo (grain-anchored counts, a new measure source form) and obsl:referencesColumn (columns an expression measure reads, distinct from declared-columns[] obsl:sourceColumn). Ontology, SHACL, and spec updated together; exported graphs stay SHACL-valid.
  • Cache stores row data + column schema only. The response envelope (sql, explain, timing, cached, columns) is rebuilt fresh per request, so a cache hit reports the cache read time (not the stale DB time) on every surface and empty/all-null column types survive. format=arrow now returns a length-prefixed frame (JSON envelope + gzip'd Arrow data); JSON/TSV unchanged.