Features
Rigorously update your features as their meaning changes.
Feature versions allow you to manage a feature as its definition changes over time. You can reference versions of a feature when querying, in resolvers, in migrations, and anywhere you use features!
The ability to maintain multiple versions of a feature is especially useful as your use cases evolve and the definition of a feature changes. This might be because your old feature had a bug, or maybe you want to improve the feature to be even more useful.
Changing the definition of a feature can have unexpected side effects, especially when there might be many consumers of a feature, some of which you may be unaware of. Feature versions allow you to update the feature definition without worrying that you might affect these consumers unknowingly.
Try it out yourself with this tutorial.
To define a versioned feature, supply the version
keyword
argument to the feature
function. This allows you to
continue adding versions without affecting past versions.
from chalk.features import features, feature
@features
class Trial:
id: int
code: str = feature(version=2)
You can refer to a specific version of a feature using the
@
operator. You can query versioned features from any client,
including the CLI, Python, etc.
$ chalk query --in trial.id=1 --out trial.code@2
from chalk.client import ChalkClient
from src.models import Trial
client = ChalkClient()
result = client.query(
input={Trial.id: 1},
output=[Trial.code@2]
)
When you define a versioned feature, that feature always
has a default version. When you reference a versioned feature
without the @
operator, you reference the default version.
The default version can be controlled by adding the
default_version
keyword argument to the feature function.
If no default_version
is provided, the default version is 1
.
It is best practice to leave the default as 1
until all
consumers have been updated to use a newer version.
from chalk.features import features, feature
@features
class Trial:
id: int
code: str = feature(version=2, default_version=2)
As you migrate to newer feature versions or phase out old features, you can mark them as deprecated to signal that they should no longer be used. This helps teams understand which features to avoid and provides a clear migration path.
You can deprecate features that aren’t versioned to indicate they should be phased out:
from chalk.features import features, feature
@features
class User:
id: str
# Mark the old feature as deprecated
legacy_risk_score: float = feature(
deprecated=True,
description="Use risk_score_v2 instead"
)
# The new feature that should be used instead
risk_score_v2: float
When using multiple versions of a feature, you can mark specific versions as deprecated:
from chalk.features import features, feature
from chalk import _
@features
class User:
id: str
score: int
banned: bool = feature(versions={
1: feature(
deprecated=True,
description="Prior thresholds we used",
expression=_.score > 900,
),
2: feature(
description="New cutoff released by Monica's team",
expression=_.score > 300 | (_.score < 100 & _.id == "admin"),
),
})
When a deprecated feature is used in queries or resolvers, Chalk will log warnings to help you track and eliminate usage of deprecated features across your codebase. This is particularly useful during gradual migrations when you need to maintain backward compatibility while encouraging adoption of new features.
To monitor the migration progress and identify which deprecated features can be safely removed, you can use the Chalk web interface to sort features by usage and see when each feature was last queried. This helps you make data-driven decisions about when it’s safe to fully retire deprecated features.
Resolvers also reference versioned features with @
.
Resolvers can take versions of a feature as input, return
them as output, or both. Chalk will always generate the
right version of a feature when chaining resolvers that
require versioned features.
from chalk.features import online
@online
def detect_trial_failure(code: Trial.code) -> Trial.failed:
# Default version is 1.
return code in {'failed', 'err'}
@online
def detect_trial_failure_v2(code: Trial.code@2) -> Trial.failed:
# Version 2 is specifically requested.
return code in {'FAILED', 'ERR'}
@online
def extract_trial_code_v1(d: Trial.raw_data) -> Trial.code @ 1:
return d['code']
@online
def extract_trial_code_v2(d: Trial.raw_data) -> Trial.code @ 2:
return d['data']['code']
-- resolves: Trial@2
-- source: pg_trial
select id, code from trial_table;
Only scalar features can be versioned. Other kinds of features cannot be versioned. These include:
has_one
and has_many
FeatureTime
Windowed
Primary