Chalk home page
Docs
API
CLI
  1. Features
  2. Has One

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

Explicit Join

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.


Back-references

One-to-one

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.

Optional relationships

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.


Chained Has-One Joins

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


Querying for Has-One Relationships

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]
)