Skip to main content

CubePy

Python implementation of an analytical semantic layer + permission layer + API layer, modelled on Cube.js and tuned for Hologres/Postgres backends.

CubePy is an independent project, not affiliated with, sponsored by, or endorsed by Cube Dev, Inc. See THIRD_PARTY_NOTICES.md for licensing and trademark attribution.

Cube.js source (for cross-checking the contract) is vendored under cube.js/ (shallow clone, gitignored). Porting notes live in docs/06-cubejs-contract-notes.md.

Status

Layer State
Schema (measures/dimensions/joins/segments, decorator + YAML) ✅
Permission (SecurityContext, JWT, RLS via check_permission, field-level shown) ✅
SQL generator (SQLAlchemy 2.0 text, full filter-operator table, time dimensions, joins) ✅
Query orchestrator (Redis cache, ctx-scoped cache key, in-flight dedupe, refreshKey probe) ✅
Pre-aggregation (rollup materialisation + fail-closed navigation + scheduled refresh) ✅ MVP (off by default)
REST (/cubejs-api/v1/{load,sql,meta,subscribe} + /readyz) ✅
WebSocket subscribe (/cubejs-api/v1/subscribe, hash-change push) ✅
GraphQL (Strawberry, /cubejs-api/graphql) ✅
Async client SDK (client/, REST + WS subscribe) ✅
DuckDB data source (sync engine on a worker thread) ✅
Real Postgres integration tests (testcontainers) ✅

Pre-aggregation is a same-DB materialised-rollup + aggregate-navigation MVP (see docs/07, docs/08), off by default (CUBEPY_PREAGG_ENABLED=false). When enabled, PreAggRouter.match(query, ctx) routes fail-closed — single cube, additive SUM/COUNT measures, UTC timezone, granularity roll-up, RLS column coverage — and the orchestrator transparently falls back to the base cube on any miss or error. Refresh is a per-rollup asyncio task in scheduler.py driven by refresh_key.every (no scheduler dependency). Hologres dynamic tables already maintain aggregates inside the engine, so the app-level rollup layer is an optional accelerator rather than a requirement (see docs/02, docs/05).

Stack

FastAPI · SQLAlchemy 2.0 (async, asyncpg) · Pydantic v2 · Redis (redis.asyncio) · Strawberry · uv · pytest/testcontainers. Executors: asyncpg/aiosqlite run on an async engine; DuckDB (no async dialect) runs on a sync engine in a worker thread.

How a query flows

HTTP / WS / GraphQL
  └─ security_context (FastAPI dependency)   JWT → SecurityContext
     └─ QueryOrchestrator.load()
        ├─ Redis cache lookup (key scoped to the security context)
        ├─ refreshKey probe (source-data freshness signature)
        ├─ PreAggRouter.match()              rollup rewrite, or base cube
        ├─ SQLBuilder(query, ctx).build()    injects the RLS WHERE
        └─ QueryExecutor.execute()           SQL → envelope

Run

uv sync --extra dev
export CUBEPY_JWT_SECRET=...                 # required in prod
export CUBEPY_PG_DSN=postgresql+asyncpg://...# Hologres speaks the PG wire protocol
export CUBEPY_REDIS_URL=redis://localhost:6379/0
uv run uvicorn cubepy.api.app:app --reload   # http://127.0.0.1:8000/docs

One-shot demo (testcontainers Postgres, auto-seeded, + local Redis) on port 8765:

uv run python run_server.py

Define a cube

from cubepy.schema.loader import cube, measure, dimension
from cubepy.schema.meta import MeasureType, PreAggregation

@cube("Orders", "orders",
      joins={"Users": {"relationship": "belongsTo", "sql": "Orders.user_id = Users.id"}},
      security_context={"check_permission": lambda ctx: [f"orders.tenant_id = {ctx.tenant_id}"]},
      security_columns=("tenant_id",),
      pre_aggregations=(
          PreAggregation(
              "daily", ("Orders.revenue", "Orders.count"), ("Orders.status",),
              "Orders.created_at", "day",
              refresh_key={"every": 300}, security_columns=("tenant_id",),
          ),
      ))
class OrdersCube:
    revenue  = measure("amount", MeasureType.SUM)
    count    = measure(None, MeasureType.COUNT)
    status   = dimension("status", "string")
    created  = dimension("created_at", "time")

Member names are the declared attribute names (no auto-camelCase). Cube aliases are lowercased so Postgres's case-folding makes Orders.user_id / Users.id resolve consistently. Full pre-aggregation usage guide: docs/08-preaggregation-usage.md.

Query

# REST /load
curl -X POST localhost:8000/cubejs-api/v1/load \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"query":{"measures":["Orders.revenue"],"dimensions":["Orders.status"]}}'

WebSocket subscribe and GraphQL (load(query: JSON)) follow the cube.js shapes — see docs/06-cubejs-contract-notes.md for the exact protocol.

Test

uv run pytest                       # unit + integration (testcontainers Postgres)
uv run pytest -m "not integration"  # skip the container tests
CUBEPY_TEST_PG_DSN=... uv run pytest  # use a real/local Postgres instead of a container

Docs

License

MIT

Metadata

Release files for cubepy-semantic 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cubepy-semantic 0.1.0
File Size Uploaded
cubepy_semantic-0.1.0.tar.gz 228.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cubepy-semantic 0.1.0
File Interpreter ABI Platform
cubepy_semantic-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 278.7 kB

Release files / cubepy_semantic-0.1.0.tar.gz

Download URL cubepy_semantic-0.1.0.tar.gz
Size 228.7 kB
Tags Source
SHA-256 checksum
How to use checksums
9ee15e483e7ed38220910226110eefb2aa536f1aad656b1d153ae1f8e7162862
BLAKE2b-256 checksum
How to use checksums
83034637729baab1a54e3e2d4f1c303e82ad1f0a83bc5c4df253223251c91b33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.14

Release files / cubepy_semantic-0.1.0-py3-none-any.whl

Download URL cubepy_semantic-0.1.0-py3-none-any.whl
Size 50.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
321e9ef7427301b594664bc2c6e39791b7b19ddd6d943091c2c343695cb38e71
BLAKE2b-256 checksum
How to use checksums
cd71e6c58c074d1128b183b290b7037619cdc93d9d5956a78a76148c3e8c72ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.14

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page