Metadata-Version: 2.4
Name: nori-rel
Version: 0.3.0
Summary: Relational database prediction with Nori
Author: Synthefy
License-Expression: MIT AND Apache-2.0
License-File: ATTRIBUTION.md
License-File: LICENSE
License-File: NOTICE
License-File: src/nori_rel/pql/_vendor/kumo/LICENSE
License-File: src/nori_rel/pql/_vendor/kumo/NOTICE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Dist: antlr4-python3-runtime==4.13.2
Requires-Dist: numpy>=2.0
Requires-Dist: pandas>=2.3.3,<3.0
Requires-Dist: pydantic>=2.10,<3
Requires-Dist: boto3>=1.34,<2 ; extra == 'database'
Requires-Dist: fastdfs>=1.1,<2 ; extra == 'database'
Requires-Dist: psycopg[binary]>=3.2,<4 ; extra == 'database'
Requires-Dist: sqlalchemy>=2.0,<3 ; extra == 'database'
Requires-Dist: synthefy>=7.2.0,<8 ; extra == 'database'
Requires-Dist: urllib3>=2.8,<3 ; extra == 'database'
Requires-Dist: boto3>=1.34,<2 ; extra == 'databricks'
Requires-Dist: databricks-sdk>=0.151,<1 ; extra == 'databricks'
Requires-Dist: databricks-sql-connector>=4.6,<5 ; extra == 'databricks'
Requires-Dist: fastdfs>=1.1,<2 ; extra == 'databricks'
Requires-Dist: sqlalchemy>=2.0,<3 ; extra == 'databricks'
Requires-Dist: synthefy>=7.2.0,<8 ; extra == 'databricks'
Requires-Dist: urllib3>=2.8,<3 ; extra == 'databricks'
Requires-Dist: synthefy-nori==0.20.1 ; extra == 'local'
Requires-Dist: fsspec>=2026.6.0 ; extra == 'local'
Requires-Dist: boto3>=1.34,<2 ; extra == 'snowflake'
Requires-Dist: fastdfs>=1.1,<2 ; extra == 'snowflake'
Requires-Dist: snowflake-connector-python>=4.8,<5 ; extra == 'snowflake'
Requires-Dist: sqlalchemy>=2.0,<3 ; extra == 'snowflake'
Requires-Dist: synthefy>=7.2.0,<8 ; extra == 'snowflake'
Requires-Dist: urllib3>=2.8,<3 ; extra == 'snowflake'
Requires-Python: >=3.11, <3.13
Project-URL: Repository, https://github.com/Synthefy/nori-rel
Project-URL: Issues, https://github.com/Synthefy/nori-rel/issues
Project-URL: Changelog, https://github.com/Synthefy/nori-rel/blob/main/CHANGELOG.md
Provides-Extra: database
Provides-Extra: databricks
Provides-Extra: local
Provides-Extra: snowflake
Description-Content-Type: text/markdown

# Nori-Rel

Predict from relational databases with [Nori](https://docs.synthefy.com/nori).
Nori-Rel reads a bounded database snapshot, constructs historical targets and
FastDFS features, and runs local or hosted inference through one Python client.

The supported package covers PostgreSQL, Snowflake standard tables, and
Databricks Unity Catalog Delta tables. Releases come from reviewed commits on
`main`.

Review the [support contract](docs/support.md) and
[dependency security assessment](docs/dependency-security.md) before deployment.
The assessment includes scoped, expiring exceptions for upstream dependencies.

## Install

Python 3.11 and 3.12 are supported. Install your connector and, for the default
local inference mode, the model runtime:

```bash
python -m pip install "nori-rel[database,local]"
# Other connectors:
python -m pip install "nori-rel[snowflake,local]"
python -m pip install "nori-rel[databricks,local]"
```

For hosted inference, omit `local` and set `SYNTHEFY_NORI_API_KEY` privately.
A hosted installation does not require the local model runtime or model weights.
See [supported capabilities](docs/support.md) for platform and target limits.

## Predict customer spend

This example expects existing `users(user_id, created_at)` and
`orders(order_id, user_id, event_time, amount)` tables with declared primary and
foreign keys. Set `DATABASE_URL` to a TLS-enabled PostgreSQL connection string.
Use [the synthetic example](examples/README.md) for a complete disposable dataset.

```python
from datetime import UTC, datetime

from nori_rel import EnvironmentCredential, NoriRelClient

with NoriRelClient() as client:
    source = client.connect(
        engine="postgresql",
        credential=EnvironmentCredential(variable="DATABASE_URL"),
        tables=["users", "orders"],
        time_columns={"orders": "event_time"},
    )
    details = client.predict(
        source=source,
        query="PREDICT SUM(orders.amount,0,30,days) FOR EACH users.user_id",
        anchor_times=[datetime(2026, 1, 1, tzinfo=UTC)],
        anchor_time=datetime(2026, 3, 1, tzinfo=UTC),
        observed_through=datetime(2026, 3, 1, tzinfo=UTC),
        entity_created_at="created_at",
        return_details=True,
    )
    predictions = details.predictions
    print(predictions[["entity_id", "prediction"]])
```

The query predicts each user's total order amount over the next 30 days.
`anchor_times` supplies historical examples; `anchor_time` is the scoring time.
`observed_through` is your verified data-completeness watermark, not an estimate
of completeness from the latest event. Use dates appropriate to your data and
at least 32 usable historical rows. Register every event table's timestamp;
other entity attributes must be immutable or valid at every requested anchor.
See [PQL and time semantics](docs/pql.md).

The default mode runs Nori in your process. The first local prediction downloads
model weights; subsequent predictions can use the populated cache offline.
Use `NoriRelClient(mode="hosted")` to send the generated context/query feature
matrices and labels to hosted Nori. Database connections, credentials, and raw
source tables stay in the caller's process. Generated features can still contain
sensitive information derived from those tables.

## Save and reload

Request prediction details to obtain a pipeline, then reconnect when using it:

```python
from nori_rel import NoriRelPipeline

if details.pipeline is None:
    raise RuntimeError("This prediction cannot be exported as a pipeline")
details.pipeline.save("customer_spend.json")

pipeline = NoriRelPipeline.load("customer_spend.json")
with NoriRelClient() as client:
    source = client.connect(
        engine="postgresql",
        credential=EnvironmentCredential(variable="DATABASE_URL"),
        tables=["users", "orders"],
        time_columns={"orders": "event_time"},
    )
    predictions = client.predict(
        source=source,
        pipeline=pipeline,
        as_of=datetime(2026, 4, 1, tzinfo=UTC),
        observed_through=datetime(2026, 4, 1, tzinfo=UTC),
    )
```

A pipeline stores the task, feature recipe, schema contract, context policy, and
model settings. Each replay reads fresh rows. It contains no credentials,
source rows, or model weights. See [pipeline export](docs/pipeline-export.md)
and the runnable [save/reload example](examples/pipeline_reload.py).

## Operations and safety boundaries

Use the client as a context manager to close connection pools. PostgreSQL uses
TLS and read-only transactions; warehouse connectors use pinned reads and require
a dedicated identity without write privileges. Warehouse capabilities and
snapshot guarantees are documented in the [connector guide](docs/warehouse-connectors.md).

`RelationalLimits` bounds tables, rows, snapshot memory, feature size, and SQL
statement time. Exceeding an extraction limit raises an error. These limits do
not cap total warehouse billing or the process's total memory. See
[configuration and errors](docs/api.md) and [progress/cancellation](docs/run-control.md).

## Development and licensing

See the [public API inventory](docs/public-api.md),
[CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
[RELEASING.md](RELEASING.md). Public release changes are recorded in
[CHANGELOG.md](CHANGELOG.md).

Original Synthefy code is [MIT-licensed](LICENSE). The vendored PQL grammar and
derived parser retain Apache-2.0 licensing. Dependencies, downloaded model weights,
and external datasets have their own terms; see [ATTRIBUTION.md](ATTRIBUTION.md).
