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
|
|
None
|
field
|
str | None
|
column name to match. |
None
|
comp
|
str | None
|
comparator ( |
'='
|
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 |
()
|
msg
|
str | None
|
custom message, overrides the generated diagnostic. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
|
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 |
()
|
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
|
|
None
|
field
|
str | None
|
column name to match. |
None
|
comp
|
str | None
|
comparator ( |
'='
|
value
|
Python value to match. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
|
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 |
required |
table
|
str | None
|
|
None
|
field
|
str | None
|
column name to filter on. |
None
|
comp
|
str | None
|
comparator ( |
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
|
|
None
|
field
|
str | None
|
column name to filter on. |
None
|
comp
|
str | None
|
comparator ( |
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 |
|
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 |
required |
Returns:
| Type | Description |
|---|---|
|
set[str]: e.g. |
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 |
required |
Returns:
| Type | Description |
|---|---|
|
set[str]: e.g. |
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 |
required |
Returns:
| Type | Description |
|---|---|
|
list[dict]: flat list of constraint dicts, each with keys |
|
|
|
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 |
required |
Returns:
| Type | Description |
|---|---|
|
set[str]: e.g. |
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 |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
all |