factcat
An open-source alternative to Amplitude and Mixpanel that runs in your own data warehouse. Factcat generates SQL and runs it in your BigQuery or Snowflake — no SDK, no ingestion, nothing hosted.
Product analytics tools make your modelling decisions for you: entity = a user,
period = a calendar bucket, retained = did an event. Real definitions violate all
three:
from factcat import RetentionSpec, retention_sql
spec = RetentionSpec(
table="analytics.fct_subscription_payments",
entity="subscription_id", # not the user
entity_time="sub_start",
event_time="paid_at",
period_days=35, # a billing cycle plus dunning, not a calendar bucket
n_periods=12,
retained="status = 'collected' AND within_period_offset <= 5",
)
print(retention_sql(spec, dialect="snowflake"))
from factcat import EventsSpec, events_sql
print(events_sql(EventsSpec(
table="analytics.fct_events",
entity="subscription_id",
event_time="occurred_at",
measure="uniques",
)))
Breakdowns are caller SQL plus optional top_n (default 8) and
include_other (default True). Value semantics ride per column: a plain
string means breakdown_at (rows / first / last /
carried); a Breakdown entry adds fill_from, since /
until / strict before bounds, backfill, and
own_value_first (carried: the row's own value outranks a narrowed
fill_from stream). carried is the last non-null value at or
before each row's instant over the entity's unfiltered history.
None of it replaces the expression.
print(events_sql(EventsSpec(
table="analytics.fct_events",
entity="subscription_id",
event_time="occurred_at",
measure="uniques",
breakdowns=("country", "browser"),
top_n=8,
)))
Event measures: total, uniques, average (Total / Uniques). Property
measures (on="property", of= a column): sum, average, median,
distinct (mean distinct values per entity). Uniques is COUNT DISTINCT of
entity when exact=True; default exact=False is approx NDV, approx
median, and approx top-N breakdown labels (BigQuery and Snowflake included).
The same exact field turns every sketch off.
retained is arbitrary SQL over any column in your table, plus the derived columns
offset_days, period_index and within_period_offset.
Generates SQL and queries in place. No SDK, no ingestion, no copy of your data.
SQL generation supports DuckDB, Postgres, BigQuery, Snowflake, Databricks, Spark, Trino,
Presto, ClickHouse and Redshift. Execute adapters push that SQL into the caller's
warehouse. Factcat has no warehouse of its own. pip install factcat is the
product (SQL + chart) and includes no warehouse SDK. Run queries with
pip install factcat[bigquery] or factcat[snowflake]. factcat[all] is
every shipped driver. The adapter contract is dialect plus run(sql).
from factcat import RetentionSpec, retention_sql
from factcat.warehouses import connect
sql = retention_sql(spec, dialect="bigquery")
bq = connect("bigquery", project="my-proj", location="EU")
result = bq.run(sql)
Snowflake is the same shape with that warehouse's fields (account, user,
warehouse, database, schema, private_key_path). It has no scan-cap dry-run.
Application-default credentials by default (gcloud auth application-default login), or
pass a service-account JSON path as credentials. BigQuery queries are capped at 10 GiB
scanned unless you raise maximum_bytes_billed or pass None for unlimited. project and
location are required so an EU dataset is not sent to US.
Full documentation: https://github.com/gordonkjlee/factcat
Metadata
Release files for factcat 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| factcat-0.5.0.tar.gz | 1.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| factcat-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.0 MB
Release files / factcat-0.5.0.tar.gz
| Download URL | factcat-0.5.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2914910447de01097547f9d813e40f2c2ca67de60a5236d59547d29a3c57b10
|
|
BLAKE2b-256 checksum How to use checksums |
889aa2faab934b0d4e2243a790953c1218062361e80854066cc027c36d4f3718
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency logRelease files / factcat-0.5.0-py3-none-any.whl
| Download URL | factcat-0.5.0-py3-none-any.whl |
|---|---|
| Size | 938.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3abf3bedf2490b33ab60cdeefbd180c0d94d8f272edc5491fb86ec2df81ddd52
|
|
BLAKE2b-256 checksum How to use checksums |
bd2d88cb4b7c2b06b4fb43488d266b30b82db8f78a65eab7c59336043b545efc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency log