Skip to content

Testing utilities

half_orm.testing provides helpers for writing predicate-level tests — assertions on the structure and values of a query predicate without executing SQL or inserting test data.

The helpers work with the dict returned by Relation.ho_where_display(), which exposes the JOIN/WHERE tree built by your business methods.


Why predicate-level tests?

Business methods that traverse foreign keys (e.g. Person.posts(), Post.commenters()) build a predicate over multiple tables. You can verify that predicate without hitting the database:

from half_orm.testing import assertConstraintsMatch
from myapp.actor.person import Person

def test_posts_carries_last_name():
    assertConstraintsMatch(
        Person(last_name='Martin').posts(),
        table='actor.person', field='last_name', value='Martin',
    )

def test_commenters_traverses_full_path():
    assertInvolvesTables(
        Post(title='Hello').commenters(),
        'blog.post', 'blog.comment', 'actor.person',
    )

Tests pass or fail based on the structure of the query, making them fast, deterministic, and database-free.


Assertion helpers

These functions raise :class:AssertionError with a diagnostic message on failure, following the assert* convention of :class:unittest.TestCase.

assertConstraintsMatch(relation, *, table=None, field=None, comp='=', value=None, msg=None)

Assert that at least one constraint in relation's predicate matches all provided criteria.

Raises :class:AssertionError with a diagnostic listing every leaf constraint that was actually found when no match exists — much more useful than the bare AssertionError: False produced by assertTrue(constraints_match(...)).

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required
table str | None

'schema.table' to match.

None
field str | None

column name to match.

None
comp str | None

comparator ('=', '>', 'like', …).

'='
value

Python value to match.

None
msg str | None

custom message, overrides the generated one.

None

Raises:

Type Description
AssertionError

when no constraint satisfies all criteria.

Example::

from half_orm.testing import assertConstraintsMatch

assertConstraintsMatch(
    Person(last_name='Martin').posts(),
    table='actor.person', field='last_name', value='Martin',
)

assertInvolvesTables(relation, *tables, msg=None)

Return True if all given tables appear in the predicate tree, or raise :class:AssertionError with a diagnostic if any are missing.

Includes tables that are only traversed via JOIN with no field constraint set on them, not just tables that carry a constraint. To check only tables with constraints, use :func:constraint_tables directly.

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required
*tables str

one or more 'schema.table' strings that must all be present.

()
msg str | None

custom message, overrides the generated diagnostic.

None

Returns:

Name Type Description
bool

True when every table in tables is reached.

Raises:

Type Description
AssertionError

with a list of missing tables and found tables when at least one expected table is absent.

Example::

from half_orm.testing import assertInvolvesTables

assertInvolvesTables(
    Post(title='Hello').commenters(),
    'blog.post', 'blog.comment', 'actor.person',
)

assertSamePredicate(rel1, rel2, msg=None)

Assert that two relations produce the same logical predicate.

Compares predicates structurally, ignoring relation aliases (r{ho_id}) which differ between object instances even for equivalent queries. Commutative operators (or, and) are normalised so that A | B and B | A are considered equal; and not is not commutative and is compared as-is.

Parameters:

Name Type Description Default
rel1

first halfORM relation object.

required
rel2

second halfORM relation object.

required
msg str | None

custom message, overrides the generated diagnostic.

None

Raises:

Type Description
AssertionError

when the two predicates differ structurally.

Example::

from half_orm.testing import assertSamePredicate

assertSamePredicate(
    post.reviewers(),
    post.rfk_comments().author_fk(),
)

assertTablePath(relation, *tables, msg=None)

Assert that the FK traversal path of relation matches tables exactly.

The path lists tables in navigation order (the order you traverse foreign keys to reach the result), which may differ from the SQL JOIN order. A table that is visited more than once (e.g. a self-referential path) appears multiple times.

Unlike :func:assertInvolvesTables (which is order- and duplicate-free), this function checks the exact ordered sequence including repetitions.

Only works on simple (non-compound) predicates; raises :class:AssertionError if the relation uses |, &, or -.

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required
*tables str

expected 'schema.table' sequence, in traversal order.

()
msg str | None

custom message, overrides the generated diagnostic.

None

Raises:

Type Description
AssertionError

when the path differs from tables, or the predicate is compound / unconstrained.

Example::

from half_orm.testing import assertTablePath

assertTablePath(
    Post(title='Hello').commenters(),
    'blog.post', 'blog.comment', 'actor.person',
)

Query helpers

These functions return data for use in custom assertions or for building more complex checks.

constraints_match(relation, *, table=None, field=None, comp='=', value=None)

Return True if at least one constraint in relation's predicate matches all provided criteria.

Takes a :class:~half_orm.relation.Relation object directly — calls :meth:~half_orm.relation.Relation.ho_where_display internally.

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required
table str | None

'schema.table' to match.

None
field str | None

column name to match.

None
comp str | None

comparator ('=', '>', 'like', …).

'='
value

Python value to match.

None

Returns:

Name Type Description
bool

True if at least one constraint satisfies all criteria.

Example::

from half_orm.testing import constraints_match

posts = Person(last_name='Martin').posts()
assert constraints_match(posts, table='actor.person',
                         field='last_name', comp='=', value='Martin')

find_constraints(node, *, table=None, field=None, comp=None, value=None)

Return constraints matching all provided criteria.

All keyword arguments are optional; only the provided ones are used as filters (AND logic).

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required
table str | None

'schema.table' to filter on.

None
field str | None

column name to filter on.

None
comp str | None

comparator ('=', '>', 'like', …).

None
value

Python value to filter on.

None

Returns:

Type Description

list[dict]: matching constraint dicts.

Example::

find_constraints(result, table='blog.comment_type', value='review')

constraint_count(relation, *, table=None, field=None, comp=None, value=None)

Return the number of constraints in relation's predicate that match all provided criteria.

All keyword arguments are optional; only the provided ones are used as filters (AND logic). With no criteria, returns the total number of leaf constraints.

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required
table str | None

'schema.table' to filter on.

None
field str | None

column name to filter on.

None
comp str | None

comparator ('=', '>', 'like', …).

None
value

Python value to filter on.

None

Returns:

Name Type Description
int

number of matching constraints.

Example::

from half_orm.testing import constraint_count

# exactly one constraint on actor.person
assert constraint_count(Person(last_name='Martin').posts(),
                        table='actor.person') == 1

is_unconstrained(relation)

Return True if relation has no constraint at all.

Equivalent to relation.ho_where_display() is None, but names the intent explicitly.

Parameters:

Name Type Description Default
relation

a halfORM relation object.

required

Returns:

Name Type Description
bool

True when the relation is fully unconstrained.

Example::

from half_orm.testing import is_unconstrained

assert is_unconstrained(Person())          # no fields set
assert not is_unconstrained(Person(id=1))

traversed_tables(node)

Return the set of all schema.table names reached by the predicate, including tables that are only traversed via JOIN without any field constraint.

Since ho_where_display pre-aggregates tables at every level, this is a direct read of node['tables'] — no tree traversal needed.

For tables with at least one constraint, use :func:constraint_tables instead.

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required

Returns:

Type Description

set[str]: e.g. {'blog.post', 'blog.comment', 'actor.person'}.

constraint_tables(node)

Return the set of all schema.table names present in the predicate tree.

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required

Returns:

Type Description

set[str]: e.g. {'blog.post', 'blog.author'}.

leaf_constraints(node)

Return all constraints from a :meth:ho_where_display tree.

Since ho_where_display pre-aggregates constraints at every level, this is a direct read of node['constraints'] — no tree traversal needed.

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required

Returns:

Type Description

list[dict]: flat list of constraint dicts, each with keys

relation, field, comp, value.

constraint_fields(node)

Return the set of all field names constrained in the predicate tree.

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required

Returns:

Type Description

set[str]: e.g. {'id', 'comment_type'}.

constraint_values(node)

Return the list of all constraint values in the predicate tree.

Parameters:

Name Type Description Default
node dict | None

the value returned by ho_where_display().

required

Returns:

Name Type Description
list

all value entries across every leaf constraint.