Features
Define one-to-one relationships between feature classes.
Has-one relationships link a feature to a single instance of another feature.
The simplest way to specify a join for a has-one relationship is implicitly. In the example below,
a User
is linked to their Profile
.
from chalk.features import features
@features
class Profile:
id: str
user_id: "User.id"
email_age_years: float
@features
class User:
id: str
profile: Profile
With a has-one relationship established, you can reference features on Profile
through
User
. For example:
user_email_age = User.profile.email_age_years
In the following snippet, the has-one join is explicitly defined. This is functionally equivalent to the recommended implementation:
from chalk.features import features, has_one, ...
@features
class Profile:
id: str
user_id: str
email_age_years: float
@features
class User:
id: str
uid: str
profile: Profile = has_one(lambda: Profile.user_id == User.uid)
The lambda
solves forward references, letting you reference User
before it is defined.
You can also add a back-reference to User
from Profile
.
However, you don’t have to explicitly set the join on Profile
.
Instead, the join condition is assumed to be symmetric and copied over.
To complete the one-to-one relationship from our example, add a User
to the Profile
class:
@features
class Profile:
...
user_id: "User.uid"
email_age_years: float
user: "User"
@features
class User:
...
uid: str
profile: Profile
Here you need to use quotes around `User` to use a forward reference.
When a has-one relationship is specified, the default behavior is to treat the linked Feature
as required. Following the example above, specifying a User
without a Profile
and
querying for a User
’s profile or using the User.profile
in a resolver raises an
error.
To define optional relationships, use the typing.Optional[...]
keyword:
from typing import Optional
@features
class User:
...
uid: Profile.user_id
profile: Profile
profile: Optional[Profile]
Note, resolvers that take optional features as inputs need to handle the None
case. This is
covered in more detail in the resolver’s section of the docs.
You can chain has-one joins to traverse multiple relationships. For example, you could define the following features to represent a user’s profile and preferences in an application.
from chalk.features import features, Primary
@features
class User:
id: str
email: str
@features
class Profile:
id: Primary[User.id]
username: str
@features
class Preferences:
id: Primary[Profile.id]
dark_mode: bool
You can also query for a feature that is joined through a has-one relationship by
referencing the root namespace. For example, to query for the Profile
features
associated with a User
continuing from the example above, you can write:
from chalk.client import ChalkClient
from src.features import User
client = ChalkClient()
client.query(
input={User.id: "1"},
output=[User.profile.id, User.profile.email_age_years]
)