Skip to content

Model

Model is the ActiveRecord-style base class every generated model subclasses. It combines SQLModel with a fluent Query builder, accessible both from instances (task.save()) and directly on the class (Task.where(...)) via the _ModelMeta metaclass.

from typing import Optional
from grace.model import Field, Model


class User(Model):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    email: str
    age: int
    active: bool = True


user = User.create(name="Test", email="test@example.com", age=20)
User.find(1)
User.find_by(name="Alice")
User.where(User.age > 25, active=True).order_by(User.age.desc()).limit(10).all()
User.with_("posts", "comments").where(User.active == True).all()

See Models & Migrations for the full guide.

grace.model.Model

Bases: SQLModel

set_engine classmethod

set_engine(engine)

Sets the database engine used by the model.

Must be called before performing any queries.

Examples
from sqlmodel import create_engine
engine = create_engine("sqlite:///db.sqlite3")
User.set_engine(engine)
Source code in grace/model.py
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
@classmethod
def set_engine(cls, engine: Engine):
    """
    Sets the database engine used by the model.

    Must be called before performing any queries.

    ## Examples
    ```python
    from sqlmodel import create_engine
    engine = create_engine("sqlite:///db.sqlite3")
    User.set_engine(engine)
    ```
    """
    cls._engine = engine

get_engine classmethod

get_engine()

Returns the engine currently associated with this model.

Raises a RuntimeError if no engine has been set.

Examples
engine = User.get_engine()
Source code in grace/model.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
@classmethod
def get_engine(cls) -> Engine:
    """
    Returns the engine currently associated with this model.

    Raises a `RuntimeError` if no engine has been set.

    ## Examples
    ```python
    engine = User.get_engine()
    ```
    """
    if cls._engine is None:
        raise RuntimeError(
            f"No session set for {cls.__name__}. Call Model.set_engine() first."
        )
    return cls._engine

query classmethod

query()

Returns a new query object for the model.

Enables chaining methods such as where, order_by, limit, etc.

Examples
User.query().where(User.active == True).order_by(User.created_at).all()
Source code in grace/model.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
@classmethod
def query(cls: Type[T]) -> Query:
    """
    Returns a new query object for the model.

    Enables chaining methods such as `where`, `order_by`, `limit`, etc.

    ## Examples
    ```python
    User.query().where(User.active == True).order_by(User.created_at).all()
    ```
    """
    if cls.__name__ == "Model":
        raise RuntimeError("Cannot query the base Model class")
    return Query(cls)

create classmethod

create(**kwargs)

Creates and saves a new record with the given attributes.

Examples
User.create(name="Alice", email="alice@example.com")
Source code in grace/model.py
427
428
429
430
431
432
433
434
435
436
437
438
@classmethod
def create(cls: Type[T], **kwargs) -> T:
    """
    Creates and saves a new record with the given attributes.

    ## Examples
    ```python
    User.create(name="Alice", email="alice@example.com")
    ```
    """
    instance = cls(**kwargs)
    return instance.save()

save

save()

Saves the current model instance to the database.

Commits changes immediately and refreshes the instance.

Examples
user = User(name="Alice")
user.save()
Source code in grace/model.py
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def save(self: T) -> T:
    """
    Saves the current model instance to the database.

    Commits changes immediately and refreshes the instance.

    ## Examples
    ```python
    user = User(name="Alice")
    user.save()
    ```
    """
    with Session(self.get_engine(), expire_on_commit=False) as session:
        session.add(self)
        session.commit()
        session.refresh(self)
        return self

delete

delete()

Deletes the current record from the database.

Examples
user = User.find(1)
user.delete()
Source code in grace/model.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
def delete(self) -> None:
    """
    Deletes the current record from the database.

    ## Examples
    ```python
    user = User.find(1)
    user.delete()
    ```
    """
    with Session(self.get_engine()) as session:
        # Merge the instance into the session if it's detached
        instance = session.merge(self)
        session.delete(instance)
        session.commit()

update

update(**kwargs)

Updates the current instance with the given attributes and saves the changes to the database.

Examples
user = User.find(1)
user.update(name="Alice Smith", active=False)
Source code in grace/model.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
def update(self, **kwargs) -> Self:
    """
    Updates the current instance with the given attributes
    and saves the changes to the database.

    ## Examples
    ```python
    user = User.find(1)
    user.update(name="Alice Smith", active=False)
    ```
    """
    for key, value in kwargs.items():
        if hasattr(self, key):
            setattr(self, key, value)
    return self.save()

reload

reload()

Reloads the instance from the database, discarding any unsaved changes.

Similar to ActiveRecord's reload method.

Examples
user = User.find(1)
user.name = "Changed"
user.reload()  # Discards the change
Source code in grace/model.py
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
def reload(self: T) -> T:
    """
    Reloads the instance from the database, discarding any unsaved changes.

    Similar to ActiveRecord's reload method.

    ## Examples
    ```python
    user = User.find(1)
    user.name = "Changed"
    user.reload()  # Discards the change
    ```
    """
    mapper = inspect(self.__class__)
    pk_columns = mapper.primary_key

    if not pk_columns:
        raise ValueError(
            f"Cannot reload: {self.__class__.__name__} has no primary key"
        )

    # Get the primary key value(s)
    pk_values = []
    for pk_column in pk_columns:
        pk_value = getattr(self, pk_column.key, None)
        if pk_value is None:
            raise ValueError(
                "Cannot reload an unsaved record (primary key is None)"
            )
        pk_values.append(pk_value)

    # For composite keys, use tuple; for single key, use scalar
    pk_identity = tuple(pk_values) if len(pk_values) > 1 else pk_values[0]

    with Session(self.get_engine(), expire_on_commit=False) as session:
        fresh = session.get(self.__class__, pk_identity)
        if not fresh:
            raise ValueError(f"Record no longer exists in database")

        # Copy all attributes from fresh instance
        for column in mapper.columns:
            setattr(self, column.key, getattr(fresh, column.key))

        return self

grace.model.Query

Query(model_class)
Source code in grace/model.py
17
18
19
20
def __init__(self, model_class: Type[T]):
    self.model_class = model_class
    self.engine: Engine = model_class.get_engine()
    self._statement: Optional[Union[Select, SelectOfScalar]] = None  # defer

model_class instance-attribute

model_class = model_class

engine instance-attribute

engine = model_class.get_engine()

statement property writable

statement

find

find(value)

Finds a record by its primary key (id only).

Returns None if the record does not exist.

Examples
user = User.find(1)
Source code in grace/model.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def find(self, value: Any) -> Optional[T]:
    """
    Finds a record by its primary key (id only).

    Returns `None` if the record does not exist.

    ## Examples
    ```python
    user = User.find(1)
    ```
    """
    mapper = inspect(self.model_class)
    pk_columns = mapper.primary_key

    if not pk_columns:
        raise ValueError(f"No primary key defined for {self.model_class.__name__}")

    if len(pk_columns) > 1:
        raise ValueError("Composite primary keys are not yet supported")

    return self.where(pk_columns[0] == value).first()

find_by

find_by(**kwargs)

Finds the first record matching the provided conditions.

Equivalent to calling .query().where(...).first().

Examples
User.find_by(name="Alice")
User.find_by(email="alice@example.com", active=True)
Source code in grace/model.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def find_by(self, **kwargs) -> Optional[T]:
    """
    Finds the first record matching the provided conditions.

    Equivalent to calling `.query().where(...).first()`.

    ## Examples
    ```python
    User.find_by(name="Alice")
    User.find_by(email="alice@example.com", active=True)
    ```
    """
    if not kwargs:
        raise ValueError("At least one keyword argument must be provided.")
    return self.where(**kwargs).first()

where

where(*conditions, **kwargs)

Adds one or more filtering conditions to the query.

Accepts both SQLAlchemy expressions (e.g. User.age > 18) and simple equality filters through keyword arguments.

Examples
# Using SQLAlchemy expressions
User.where(User.age > 18, User.active == True)

# Using keyword arguments for equality
User.where(name="Alice", active=True)

# Equivalent: combines both styles
User.where(User.age > 18, active=True)
Source code in grace/model.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def where(self, *conditions, **kwargs) -> Self:
    """
    Adds one or more filtering conditions to the query.

    Accepts both SQLAlchemy expressions (e.g. `User.age > 18`)
    and simple equality filters through keyword arguments.

    ## Examples
    ```python
    # Using SQLAlchemy expressions
    User.where(User.age > 18, User.active == True)

    # Using keyword arguments for equality
    User.where(name="Alice", active=True)

    # Equivalent: combines both styles
    User.where(User.age > 18, active=True)
    ```
    """
    for key, value in kwargs.items():
        column_ = getattr(self.model_class, key, None)
        if column_ is None:
            raise AttributeError(
                f"{self.model_class.__name__} has no column '{key}'"
            )
        conditions += (column_ == value,)

    for condition in conditions:
        self.statement = self.statement.where(condition)
    return self

not_

not_(*conditions, **kwargs)

Applies negation of one or more filtering conditions to the query.

Accepts SQLAlchemy expressions directly, or simple equality conditions expressed as keyword arguments. Keyword arguments are interpreted as equality checks that will then be negated.

Use this when you want to exclude records matching specific conditions.

Examples
# Exclude a specific user by name
User.not_(name="Alice").all()

# Exclude inactive users (negating an expression)
User.not_(User.active == False).all()

# Mixed usage
User.not_(User.age > 30, role="admin").all()
Source code in grace/model.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def not_(self, *conditions, **kwargs) -> Self:
    """
    Applies negation of one or more filtering conditions to the query.

    Accepts SQLAlchemy expressions directly, or simple equality conditions
    expressed as keyword arguments. Keyword arguments are interpreted as
    equality checks that will then be negated.

    Use this when you want to exclude records matching specific conditions.

    ## Examples
    ```python
    # Exclude a specific user by name
    User.not_(name="Alice").all()

    # Exclude inactive users (negating an expression)
    User.not_(User.active == False).all()

    # Mixed usage
    User.not_(User.age > 30, role="admin").all()
    ```
    """
    for key, value in kwargs.items():
        column_ = getattr(self.model_class, key, None)
        if column_ is None:
            raise AttributeError(
                f"{self.model_class.__name__} has no column '{key}'"
            )
        conditions += (column_ == value,)

    for condition in conditions:
        self.statement = self.statement.where(not_(condition))
    return self

with_

with_(*relationships)

Eagerly loads specified relationships for optimization.

Use this when querying multiple records to avoid N+1 queries. For relationships configured with lazy="selectin", this combines the relationship loading into the main query instead of a separate one.

Examples
# Loading a single record: with_() doesn't help much
trigger = Trigger.find_by(name="Linus")  # Already loads trigger_words efficiently

# Loading multiple records: with_() prevents N+1 queries
# Without with_: 1 query for triggers + 1 query for all trigger_words = 2 queries
triggers = Trigger.all()

# With with_: 1 combined query = 1 query (more efficient)
triggers = Trigger.with_("trigger_words").all()

# Load multiple relationships at once
User.with_("posts", "comments").where(User.active == True).all()
Source code in grace/model.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def with_(self, *relationships: str) -> Self:
    """
    Eagerly loads specified relationships for optimization.

    Use this when querying multiple records to avoid N+1 queries.
    For relationships configured with lazy="selectin", this combines
    the relationship loading into the main query instead of a separate one.

    ## Examples
    ```python
    # Loading a single record: with_() doesn't help much
    trigger = Trigger.find_by(name="Linus")  # Already loads trigger_words efficiently

    # Loading multiple records: with_() prevents N+1 queries
    # Without with_: 1 query for triggers + 1 query for all trigger_words = 2 queries
    triggers = Trigger.all()

    # With with_: 1 combined query = 1 query (more efficient)
    triggers = Trigger.with_("trigger_words").all()

    # Load multiple relationships at once
    User.with_("posts", "comments").where(User.active == True).all()
    ```
    """
    for relationship in relationships:
        relationship_attr = getattr(self.model_class, relationship, None)
        if relationship_attr is None:
            raise AttributeError(
                f"{self.model_class.__name__} has no relationship '{relationship}'"
            )
        self.statement = self.statement.options(selectinload(relationship_attr))
    return self

order_by

order_by(*args, **kwargs)

Orders query results by one or more columns.

Supports passing SQLAlchemy expressions directly, or using keyword arguments like name="asc" or age="desc".

Examples
User.order_by(User.name)
User.order_by(User.created_at.desc())
User.order_by(name="asc", age="desc")
Source code in grace/model.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def order_by(self, *args, **kwargs) -> Self:
    """
    Orders query results by one or more columns.

    Supports passing SQLAlchemy expressions directly,
    or using keyword arguments like `name="asc"` or `age="desc"`.

    ## Examples
    ```python
    User.order_by(User.name)
    User.order_by(User.created_at.desc())
    User.order_by(name="asc", age="desc")
    ```
    """
    for key, direction in kwargs.items():
        column_ = getattr(self.model_class, key, None)
        if column_ is None:
            raise AttributeError(
                f"{self.model_class.__name__} has no column '{key}'"
            )

        if isinstance(direction, str):
            if direction.lower() == "asc":
                args += (asc(column_),)
            elif direction.lower() == "desc":
                args += (desc(column_),)
            else:
                raise ValueError(
                    f"Order direction for '{key}' must be 'asc' or 'desc'"
                )
        else:
            # Allow passing SQLAlchemy ordering objects directly
            args += (direction,)

    if args:
        self.statement = self.statement.order_by(*args)
    return self

limit

limit(count)

Limits the number of results returned by the query.

Examples
User.limit(10).all()
Source code in grace/model.py
207
208
209
210
211
212
213
214
215
216
217
def limit(self, count: int) -> Self:
    """
    Limits the number of results returned by the query.

    ## Examples
    ```python
    User.limit(10).all()
    ```
    """
    self.statement = self.statement.limit(count)
    return self

offset

offset(count)

Skips a given number of records before returning results.

Useful for pagination.

Examples
User.offset(20).limit(10).all()
Source code in grace/model.py
219
220
221
222
223
224
225
226
227
228
229
230
231
def offset(self, count: int) -> Self:
    """
    Skips a given number of records before returning results.

    Useful for pagination.

    ## Examples
    ```python
    User.offset(20).limit(10).all()
    ```
    """
    self.statement = self.statement.offset(count)
    return self

distinct

distinct()

Returns only distinct/unique records.

Removes duplicate rows from the result set.

Examples
# Get unique users
User.distinct().all()

# Get distinct active users
User.where(User.active == True).distinct().all()

# Combined with other query methods
User.distinct().order_by(User.name).limit(10).all()
Source code in grace/model.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def distinct(self) -> Self:
    """
    Returns only distinct/unique records.

    Removes duplicate rows from the result set.

    ## Examples
    ```python
    # Get unique users
    User.distinct().all()

    # Get distinct active users
    User.where(User.active == True).distinct().all()

    # Combined with other query methods
    User.distinct().order_by(User.name).limit(10).all()
    ```
    """
    self.statement = self.statement.distinct()
    return self

all

all()

Executes the query and returns all matching records as a list.

Examples
users = User.where(User.active == True).all()
Source code in grace/model.py
254
255
256
257
258
259
260
261
262
263
264
def all(self) -> List[T]:
    """
    Executes the query and returns all matching records as a list.

    ## Examples
    ```python
    users = User.where(User.active == True).all()
    ```
    """
    with Session(self.engine, expire_on_commit=False) as session:
        return list(session.exec(self.statement).all())

first

first()

Executes the query and returns the first matching record.

Returns None if no result is found.

Examples
user = User.where(User.name == "Alice").first()
Source code in grace/model.py
266
267
268
269
270
271
272
273
274
275
276
277
278
def first(self) -> Optional[T]:
    """
    Executes the query and returns the first matching record.

    Returns `None` if no result is found.

    ## Examples
    ```python
    user = User.where(User.name == "Alice").first()
    ```
    """
    with Session(self.engine, expire_on_commit=False) as session:
        return session.exec(self.statement).first()

one

one()

Executes the query and returns exactly one result.

Raises an exception if no result or multiple results are found.

Examples
user = User.where(User.email == "alice@example.com").one()
Source code in grace/model.py
280
281
282
283
284
285
286
287
288
289
290
291
292
def one(self) -> Type[T]:
    """
    Executes the query and returns exactly one result.

    Raises an exception if no result or multiple results are found.

    ## Examples
    ```python
    user = User.where(User.email == "alice@example.com").one()
    ```
    """
    with Session(self.engine, expire_on_commit=False) as session:
        return session.exec(self.statement).one()

count

count()

Returns the number of records matching the current query.

Examples
total = User.where(User.active == True).count()
Source code in grace/model.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def count(self) -> int:
    """
    Returns the number of records matching the current query.

    ## Examples
    ```python
    total = User.where(User.active == True).count()
    ```
    """
    with Session(self.engine) as session:
        count_statement: SelectOfScalar[int] = select(func.count()).select_from(
            self.statement.subquery()
        )
        return session.exec(count_statement).one()