Skip to main content

Python client for the Synthefy API: time-series forecasting and Nori in-context tabular regression

Project description

Synthefy Python Client

Branch Status
dev Tests
main Tests

A Python client for the Synthefy API. It provides time series forecasting (synchronous and asynchronous) and tabular in-context regression via SynthefyNoriClient — which runs against the hosted endpoint or fully locally — with an easy-to-use interface, full type hints, and pydantic validation.

Features

  • Tabular In-Context Regression: SynthefyNoriClient predicts from labeled context rows in a single forward pass — hosted on Baseten or fully local, no training step
  • Sync & Async Support: Separate clients for synchronous and asynchronous operations
  • Professional Error Handling: Comprehensive exception hierarchy with detailed error messages
  • Retry Logic: Built-in exponential backoff for transient errors (rate limits, server errors)
  • Context Managers: Automatic resource cleanup with with and async with statements
  • Pandas Integration: Built-in support for pandas DataFrames
  • Type Safety: Full type hints and Pydantic validation

Installation

pip install synthefy

For optional fully-local tabular inference (no API key, runs in-process), install the extra:

pip install "synthefy[local]"

Quick Start

Basic Usage

from synthefy import SynthefyAPIClient, SynthefyAsyncAPIClient
import pandas as pd

# Synchronous client
with SynthefyAPIClient(api_key="your_api_key_here") as client:
    # Make requests...
    pass

# Asynchronous client
async with SynthefyAsyncAPIClient() as client:  # Uses SYNTHEFY_API_KEY env var
    # Make async requests...
    pass

Making a Forecast Request

from synthefy import SynthefyAPIClient
import pandas as pd
import numpy as np

# Create sample data with numeric metadata
history_data = {
    'date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'sales': np.random.normal(100, 10, 100),
    'store_id': 1,
    'category_id': 101,
    'promotion_active': 0
}

target_data = {
    'date': pd.date_range('2024-04-11', periods=30, freq='D'),
    'sales': np.nan,  # Values to forecast
    'store_id': 1,
    'category_id': 101,
    'promotion_active': 1  # Promotion active in forecast period
}

history_df = pd.DataFrame(history_data)
target_df = pd.DataFrame(target_data)

# Synchronous forecast
with SynthefyAPIClient() as client:
    forecast_dfs = client.forecast_dfs(
        history_dfs=[history_df],
        target_dfs=[target_df],
        target_col='sales',
        timestamp_col='date',
        metadata_cols=['store_id', 'category_id', 'promotion_active'],
        leak_cols=[],
        model='sfm-moe-v1'
    )

# Result is a list of DataFrames with forecasts
forecast_df = forecast_dfs[0]
print(forecast_df[['timestamps', 'sales']].head())

Asynchronous Usage

import asyncio
from synthefy.api_client import SynthefyAsyncAPIClient

async def main():
    async with SynthefyAsyncAPIClient() as client:
        # Single async forecast
        forecast_dfs = await client.forecast_dfs(
            history_dfs=[history_df],
            target_dfs=[target_df],
            target_col='sales',
            timestamp_col='date',
            metadata_cols=['store_id', 'category_id', 'promotion_active'],
            leak_cols=[],
            model='sfm-moe-v1'
        )

        # Concurrent forecasts for multiple datasets
        tasks = []
        for i in range(3):
            # Create variations of your data
            modified_history = history_df.copy()
            modified_target = target_df.copy()
            modified_history['store_id'] = i + 1
            modified_target['store_id'] = i + 1

            task = client.forecast_dfs(
                history_dfs=[modified_history],
                target_dfs=[modified_target],
                target_col='sales',
                timestamp_col='date',
                metadata_cols=['store_id', 'category_id', 'promotion_active'],
                leak_cols=[],
                model='sfm-moe-v1'
            )
            tasks.append(task)

        # Execute all forecasts concurrently
        results = await asyncio.gather(*tasks)

        for i, forecast_dfs in enumerate(results):
            print(f"Forecast for store {i+1}: {len(forecast_dfs[0])} predictions")

# Run the async function
asyncio.run(main())

Backtesting

import asyncio
import pandas as pd
import numpy as np
from synthefy.data_models import ForecastV2Request
from synthefy.api_client import SynthefyAsyncAPIClient

async def main():

    # Create sample time series data
    dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')
    data = {
        'date': dates,
        'sales': np.random.normal(100, 10, len(dates)),
        'store_id': 1,
        'category_id': 101,
        'promotion_active': np.random.choice([0, 1], len(dates), p=[0.7, 0.3])
    }
    df = pd.DataFrame(data)

    print(f"Created dataset with {len(df)} rows from {df['date'].min()} to {df['date'].max()}")

    # Use from_dfs_pre_split for backtesting with date-based windows
    request = ForecastV2Request.from_dfs_pre_split(
        dfs=[df],
        timestamp_col='date',
        target_cols=['sales'],
        model='sfm-moe-v1',
        cutoff_date='2023-06-01',  # Start backtesting from June 1st
        forecast_window='7D',      # 7-day forecast windows
        stride='14D',              # Move forward 14 days between windows
        metadata_cols=['store_id', 'category_id', 'promotion_active'],
        leak_cols=['promotion_active']  # Promotion data may leak into target
    )

    print(f"Created {len(request.samples)} forecast windows for backtesting")
    print("Window details:")
    for i, sample in enumerate(request.samples):
        history_start = sample[0].history_timestamps[0]
        history_end = sample[0].history_timestamps[-1]
        target_start = sample[0].target_timestamps[0]
        target_end = sample[0].target_timestamps[-1]
        print(f"  Window {i+1}: History {history_start} to {history_end}, Target {target_start} to {target_end}")

    # Make async forecast request
    async with SynthefyAsyncAPIClient() as client:
        response = await client.forecast(request)

        print(f"\nBacktesting completed with {len(response.samples)} forecast windows")

        # Process results for each window
        for i, sample in enumerate(response.samples):
            print(f"Window {i+1}: {len(sample.history_timestamps)} history points, "
                f"{len(sample.target_timestamps)} target points")

            # Access forecast values
            if hasattr(sample, 'forecast_values') and sample.forecast_values:
                print(f"  Forecast values: {sample.forecast_values[:3]}...")  # First 3 values
asyncio.run(main())

Advanced Configuration

from synthefy import SynthefyAPIClient
from synthefy.api_client import BadRequestError, RateLimitError

# Client with custom configuration
with SynthefyAPIClient(
    api_key="your_key",
    timeout=600.0,  # 10 minutes
    max_retries=3,
    organization="your_org_id",
    base_url="https://custom.synthefy.com"  # For enterprise customers
) as client:
    try:
        # Per-request configuration
        forecast_dfs = client.forecast_dfs(
            history_dfs=[history_df],
            target_dfs=[target_df],
            target_col='sales',
            timestamp_col='date',
            metadata_cols=['store_id'],
            leak_cols=[],
            model='sfm-moe-v1',
            timeout=120.0,  # Override client timeout for this request
            idempotency_key="unique-request-id",  # Prevent duplicate processing
            extra_headers={"X-Custom-Header": "value"}
        )
    except BadRequestError as e:
        print(f"Invalid request: {e}")
        print(f"Status code: {e.status_code}")
        print(f"Request ID: {e.request_id}")
    except RateLimitError as e:
        print(f"Rate limited: {e}")
        # Client automatically retries with exponential backoff
    except Exception as e:
        print(f"Unexpected error: {e}")

Nori — Tabular In-Context Regression

SynthefyNoriClient is a standalone client for Synthefy Nori, an in-context learning regressor. Each call supplies labeled context rows (X_train, y_train) and query rows (X_test); the model returns one predicted value per query row in a single forward pass — there is no training step.

It is independent of the forecasting client above (different model, different endpoint, different credential) but is exported from the same package. A single SynthefyNoriClient runs predictions either against the hosted endpoint or locally, selected with the mode argument ("remote" (default), "local", or "auto").

Hosted Usage (mode="remote")

from synthefy import SynthefyNoriClient

# Auth uses a Baseten API key, sent as `Authorization: Bearer <key>` (gateway default).
# Pass it explicitly or set the BASETEN_API_KEY environment variable.
client = SynthefyNoriClient(api_key="your_baseten_api_key")

predictions = client.predict(
    X_train=[[0.0, 1.0], [1.0, 0.0], [1.0, 1.0]],  # context features
    y_train=[1.0, 1.0, 2.0],                        # context targets
    X_test=[[2.0, 2.0], [0.5, 0.5]],                # query features
)
print(predictions)  # -> [<float>, <float>]  (one per X_test row)

X_train, y_train, and X_test accept Python lists, numpy arrays, or pandas objects (a DataFrame for the feature matrices; a Series or single-column DataFrame for y_train). When both X_train and X_test are DataFrames, non-numeric columns are encoded for you — fit on X_train and applied to X_test — so you can pass raw categorical columns directly:

import pandas as pd

X_train = pd.DataFrame({"price": [9.99, 4.50, 7.25], "region": ["NW", "SE", "NW"]})
y_train = pd.Series([120.0, 305.0, 180.0])
X_test  = pd.DataFrame({"region": ["SE"], "price": [5.00]})  # order need not match

predictions = client.predict(X_train, y_train, X_test)  # 'region' is encoded

By default each categorical column becomes a single column of ordinal codes (categories from X_train in sorted order — the model's own server-side convention): a value seen only in X_test maps to -1, and a missing value (NaN) stays NaN for server-side imputation. Pass categorical_encoding="onehot" for the previous one-hot behavior (indicator columns per category; missing values get their own indicator; unseen values map to an all-zeros group). Datetime columns and categorical columns with more than max_categorical_cardinality (default 100) distinct training values are dropped with a warning; timedelta columns are unsupported and raise (convert them to a number or string first). Numeric columns (including bool) pass through unchanged, with NaN imputed server-side. Any object-dtype column is treated as categorical (including numeric-looking strings such as IDs or zip codes, and object date values) — cast genuine numeric columns to a numeric dtype if you want them kept as magnitudes. (Plain lists/numpy arrays must already be numeric — encoding needs column names.)

Shapes are validated client-side: X_train and y_train must have the same number of rows, and X_test must have the same number of features as X_train. When both X_train and X_test are DataFrames, X_test is aligned to X_train's columns by name (so column order is irrelevant), and a mismatch in the column sets raises. Missing values (NaN) are allowed — you don't need to fill them in beforehand; the model imputes them server-side.

predict returns a plain list[float] by default. Pass as_pandas=True to get a pandas Series instead — one value per X_test row, named after y_train and indexed by X_test's index (when X_test is a DataFrame), so predictions join straight back:

preds = client.predict(X_train, y_train, X_test, as_pandas=True)
# preds is a pd.Series named after y_train, sharing X_test's index

By default the client targets the Baseten inference gateway (https://inference.baseten.co/predict, model synthefy/nori). To target a dedicated deployment instead, point base_url/endpoint at it and set model=None (the dedicated endpoint takes the body verbatim, with no model field):

from synthefy.nori_client import DEDICATED_BASE_URL, DEDICATED_ENDPOINT

client = SynthefyNoriClient(
    api_key="your_baseten_api_key",
    base_url=DEDICATED_BASE_URL,    # https://model-3m5j7y9w.api.baseten.co
    endpoint=DEDICATED_ENDPOINT,    # /environments/production/predict
    model=None,
    auth_scheme="Api-Key",          # dedicated endpoints use Api-Key, not Bearer
)

timeout and max_retries are also configurable on the constructor.

Authentication

  • The only credential is a Baseten API key (there is no separate Synthefy key for this endpoint).
  • Provide it via the api_key argument or the BASETEN_API_KEY environment variable. It is sent as the header Authorization: <auth_scheme> <key>. The scheme defaults to Bearer (required by the gateway); pass auth_scheme="Api-Key" for a dedicated deployment.

Errors

The Nori client reuses the package's exception hierarchy:

  • HTTP 400BadRequestError, carrying the server's error string as the message (e.g. a missing field or unsupported task).
  • HTTP 401AuthenticationError (bad or missing key).
  • Transient errors (timeouts, connection errors, 429, 5xx) are retried with exponential backoff, then surface as RateLimitError / InternalServerError / APITimeoutError / APIConnectionError.

Local Usage (mode="local", Optional, No Network)

The same prediction can run locally — no network call and no API key — via the optional synthefy-nori package. Install the extra:

pip install "synthefy[local]"

The local extra (via synthefy-nori>=0.9.0) supports Python >= 3.9, the same floor as the base package.

from synthefy import SynthefyNoriClient

client = SynthefyNoriClient(mode="local")  # no API key needed
predictions = client.predict(
    X_train=[[0.0, 1.0], [1.0, 0.0], [1.0, 1.0]],
    y_train=[1.0, 1.0, 2.0],
    X_test=[[2.0, 2.0]],
)

predict has the same signature in every mode. The synthefy-nori dependency is imported lazily on first use; if it is not installed, a clear ImportError is raised telling you to pip install "synthefy[local]".

Use mode="auto" to prefer local when synthefy-nori is installed and transparently fall back to the hosted endpoint (which then requires an API key) otherwise:

client = SynthefyNoriClient(api_key="your_baseten_api_key", mode="auto")
print(client.mode)  # "local" if synthefy-nori is installed, else "remote"

API Reference

SynthefyNoriClient (Tabular Regression)

  • SynthefyNoriClient(api_key=None, *, mode="remote", timeout=300.0, max_retries=2, base_url=..., endpoint=..., model="synthefy/nori", user_agent=None)
    • mode: "remote" (hosted, default), "local" (in-process via synthefy-nori), or "auto" (local if installed, else remote).
    • api_key (remote mode) falls back to the BASETEN_API_KEY environment variable. Not required in local mode.
    • For a dedicated deployment, pass base_url/endpoint and model=None.
  • predict(X_train, y_train, X_test, task="regression", *, timeout=None, extra_headers=None) -> List[float]
    • Returns one predicted value per row of X_test. timeout/extra_headers apply to remote mode only.
    • Inputs accept Python lists, numpy arrays, or pandas DataFrames/Series. Feature columns must be numeric; DataFrame X_test is aligned to X_train by column name; non-numeric columns are encoded (fit on X_train; categorical_encoding="ordinal" by default, "onehot" available); missing values (NaN) are imputed server-side.
    • max_categorical_cardinality (default 100): non-numeric columns with more distinct training values than this — and datetime columns — are dropped with a warning instead of encoded.
    • as_pandas=True returns a pandas Series (named after y_train, indexed by X_test) instead of the default list[float].
  • mode: the resolved mode ("auto" becomes "local"/"remote" at construction).
  • close() / context manager support (with SynthefyNoriClient(...) as client:).

SynthefyAPIClient (Synchronous)

The synchronous client class for interacting with the Synthefy API.

Constructor Parameters

  • api_key: Your Synthefy API key (can also be set via SYNTHEFY_API_KEY environment variable)
  • timeout: Request timeout in seconds (default: 300.0 / 5 minutes)
  • max_retries: Number of retries for transient errors (default: 2)
  • base_url: API base URL (default: "https://forecast.synthefy.com")
  • organization: Optional organization ID for multi-tenant setups
  • user_agent: Custom user agent string

Methods

  • forecast(request, *, timeout=None, idempotency_key=None, extra_headers=None) -> ForecastV2Response
    • Make a direct forecast request with a ForecastV2Request object
  • forecast_dfs(history_dfs, target_dfs, target_col, timestamp_col, metadata_cols, leak_cols, model) -> List[pd.DataFrame]
    • Convenience method for working directly with pandas DataFrames
  • close(): Manually close the HTTP client
  • Context manager support: Use with with SynthefyAPIClient() as client:

SynthefyAsyncAPIClient (Asynchronous)

The asynchronous client class for non-blocking operations and concurrent requests.

Constructor Parameters

Same as SynthefyAPIClient.

Methods

  • async forecast(request, *, timeout=None, idempotency_key=None, extra_headers=None) -> ForecastV2Response
    • Async version of forecast method
  • async forecast_dfs(history_dfs, target_dfs, target_col, timestamp_col, metadata_cols, leak_cols, model) -> List[pd.DataFrame]
    • Async version of forecast_dfs method
  • async aclose(): Manually close the async HTTP client
  • Async context manager support: Use with async with SynthefyAsyncAPIClient() as client:

Exception Hierarchy

All exceptions inherit from SynthefyError:

  • APITimeoutError: Request timed out
  • APIConnectionError: Network/connection issues
  • APIStatusError: Base class for HTTP status errors
    • BadRequestError (400, 422): Invalid request data
    • AuthenticationError (401): Invalid API key
    • PermissionDeniedError (403): Access denied
    • NotFoundError (404): Resource not found
    • RateLimitError (429): Rate limit exceeded
    • InternalServerError (5xx): Server errors

Each status error includes:

  • status_code: HTTP status code
  • request_id: Request ID for debugging (if available)
  • error_code: API-specific error code (if available)
  • response_body: Raw response body

Configuration

Environment Variables

  • SYNTHEFY_API_KEY: Your Synthefy API key (forecasting client)
  • BASETEN_API_KEY: Your Baseten API key (SynthefyNoriClient)

Support

For support and questions:

License

MIT License - see LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

synthefy-4.3.0.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

synthefy-4.3.0-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file synthefy-4.3.0.tar.gz.

File metadata

  • Download URL: synthefy-4.3.0.tar.gz
  • Upload date:
  • Size: 37.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for synthefy-4.3.0.tar.gz
Algorithm Hash digest
SHA256 082f6d60604c803cfe4480d7bea869b6d02d31e655e6edc94360a8fbeeaa5141
MD5 060ff63f39371c538271d80bb7a1915f
BLAKE2b-256 03864c2b8464d041847dc20ef91f7b7acc67c1fa087acde8c2878585d1728c30

See more details on using hashes here.

Provenance

The following attestation bundles were made for synthefy-4.3.0.tar.gz:

Publisher: publish.yaml on Synthefy/synthefy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file synthefy-4.3.0-py3-none-any.whl.

File metadata

  • Download URL: synthefy-4.3.0-py3-none-any.whl
  • Upload date:
  • Size: 36.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for synthefy-4.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c88c5a4df3f20924c5a76e18a0f7e24f258cf1f1e159be33cb156408485807c
MD5 4491eeb8bed9feba80b9179384a8d8c5
BLAKE2b-256 be04fa297c6d7c41e38fedbc7d4eddbd24335138be2e58c7e66a3afb6ca0e1ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for synthefy-4.3.0-py3-none-any.whl:

Publisher: publish.yaml on Synthefy/synthefy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page