Feature Store
The Feature Store defines features once and serves them consistently for training and inference. Use this resource to define entities, feature views, and feature services, ingest and materialize values, resolve online features for serving, and generate point-in-time correct training data.
Access it as client.feature_store on a Strongly client, or the same path on AsyncStrongly with await. All methods exist on both with identical signatures.
Feature stores are created in the MLOps user interface (that is where you bind the Postgres and Redis storage). The SDK works against a store you already have access to.
Quick start
from strongly import Strongly
client = Strongly()
# The store is created in the UI; take the first one this key can access.
store_id = client.feature_store.list_stores()[0]["_id"]
# 1) Define an entity, a feature view, and a model-facing feature service.
client.feature_store.apply(
store_id,
entities=[{"name": "driver", "joinKeys": ["driver_id"], "valueType": "int64"}],
views=[{
"name": "driver_stats",
"entities": ["driver"],
"mode": "batch",
"ttl": "2d",
"schema": [
{"name": "conv_rate", "dtype": "float64"},
{"name": "acc_rate", "dtype": "float64"},
],
"source": {
"serviceKind": "addon",
"serviceId": "<your-postgres-addon-id>",
"type": "postgres",
"table": "driver_stats_raw",
"timestampField": "event_timestamp",
"entityKeyColumns": {"driver_id": "driver_id"},
},
}],
services=[{
"name": "driver_model_v1",
"features": [
{"view": "driver_stats", "feature": "conv_rate"},
{"view": "driver_stats", "feature": "acc_rate"},
],
}],
)
# 2) Ingest feature rows, then publish the latest values for online serving.
client.feature_store.write(store_id, "driver_stats", rows=[
{"driver_id": 1001, "event_timestamp": "2026-07-30T00:00:00Z", "conv_rate": 0.75, "acc_rate": 0.9},
])
client.feature_store.materialize(store_id, "driver_stats")
# 3) Serve the latest features by entity id.
online = client.feature_store.get_online_features(
store_id, "driver_model_v1", entity_rows=[{"driver_id": 1001}]
)
print(online["results"])
# 4) Point-in-time training data (delivered as a Parquet snapshot).
training = client.feature_store.get_historical_features(
store_id, "driver_model_v1",
entity_rows=[{"driver_id": 1001, "event_timestamp": "2026-07-30T12:00:00Z", "label": 1}],
)
print(training["snapshot_url"], training["num_rows"])
Methods
list_stores
list_stores() -> list[dict]
Returns the feature stores this key can access.
get_store
get_store(store_id: str) -> dict
Returns a single feature store.
apply
apply(store_id, *, entities=None, views=None, services=None) -> dict
Registers or updates entities, feature views, and feature services. They are applied in dependency order (entities, then views, then services). Applying is idempotent: an unchanged view is a no-op, an additive change updates in place, and a breaking change creates a new version. Pass only the object kinds you want to change.
write
write(store_id, view: str, rows: Sequence[Mapping]) -> dict
Ingests feature rows into a view's history. Each row carries the entity join keys, an event_timestamp, and the view's feature columns.
materialize
materialize(store_id, view: str) -> dict
Runs the incremental refresh that publishes a view's latest values to the online store. Only changed values are moved, and an older value never overwrites a newer one.
get_online_features
get_online_features(store_id, feature_service: str, entity_rows: Sequence[Mapping], *, max_staleness_seconds: Optional[int] = None) -> dict
Resolves the latest online feature values for a feature service. Send only the entity keys per row. Each result reports the resolved features and lists any that were missing. Set max_staleness_seconds to treat a value older than your tolerance as missing rather than serving it stale.
get_historical_features
get_historical_features(store_id, feature_service: str, entity_rows=None, *, entity_s3_uri=None, entity_format=None) -> dict
Generates leak-free, point-in-time training data: for each labeled row, features are resolved as of that row's event_timestamp. Supply the entity and label frame exactly one way:
entity_rows: inline rows, convenient for small sets.entity_s3_uri: ans3://URI (or bare key) of a Parquet or CSV frame, the bulk path for training-scale label sets. Setentity_formattoparquetorcsvif it cannot be inferred from the key.
The response includes snapshot_url (a Parquet snapshot you read directly) and num_rows.
Async
Every method is available on AsyncStrongly with the same signature:
from strongly import AsyncStrongly
async with AsyncStrongly() as client:
stores = await client.feature_store.list_stores()
online = await client.feature_store.get_online_features(
stores[0]["_id"], "driver_model_v1", entity_rows=[{"driver_id": 1001}]
)