02.04.02 — Catalog of builtin assertions#

Source file: src/ocarina/dsl/invariants/assertions.py — ~25 predicates.

Every assertion follows the same contract:

  • A direct predicate (value: T) -> None that raises InvariantViolationError on contract violation.
  • Or a closure when you need a config argument (is_equal_to(cmp), etc.).
  • Or, occasionally, a higher-order function (HOF) like each. Lives here pragmatically — a dedicated file for a single case would be overkill.

Full table#

AssertionDirect / closure / HOFDomainDescription
is_str(value)directAnyRaises if value is not a str
is_none(value)directAnyRaises if value is not None
is_not_none(value)directAnyRaises if value is None
is_equal_to(cmp)closureAnyReturns (value) -> None that raises if value != cmp
is_not_equal_to(cmp)closureAnyReturns (value) -> None that raises if value == cmp
is_less_than(cmp)closurefloatvalue < cmp
is_less_than_or_equal_to(cmp)closurefloatvalue <= cmp
is_greater_than(cmp)closurefloatvalue > cmp
is_greater_than_or_equal_to(cmp)closurefloatvalue >= cmp
is_positive(value)directfloatvalue >= 0
is_not_zero(value)directfloatvalue != 0
is_in(elements)closureAnyvalue in tuple(elements)
is_file(value)directstr | PathPath(value).is_file()
is_dir(value)directstr | PathPath(value).is_dir()
is_iso_date_string(value)directstrdatetime.fromisoformat(value)
is_iso_utc_date_string(value)directstrChecks ISO + tzinfo == UTC
is_email(value)directstrNo whitespace, exactly one @, non-empty parts, domain contains .
has_unique_elements(*, key=None)closureIterable[Any]Detects duplicates (raises DuplicatesError). Type-strict (1 ≠ True), supports unhashables.
is_empty(value)directSizedlen(value) == 0
is_truthy(value)directAnybool(value) is True
is_valid_filename(value)directstrCross-platform: forbidden chars, Windows reserved words, no leading/trailing space or dot, length ≤ 255
each(predicate)HOFIterable[Any]Applies predicate to each element

A few implementations in detail#

is_email#

def is_email(value: str) -> None:
    """Assert that the string is a valid email address (fast check)."""
    if " " in value:
        raise InvariantViolationError(f"'{value}' must not contain whitespace.")
    if value.count("@") != 1:
        raise InvariantViolationError(f"'{value}' must contain exactly one '@' character.")
    local_part, domain_part = value.split("@")
    if not local_part or not domain_part:
        raise InvariantViolationError(f"'{value}' must have non-empty local and domain parts.")
    if "." not in domain_part:
        raise InvariantViolationError(f"Domain part of '{value}' must contain at least one '.'.")

Four bare-minimum checks. No RFC 5322 regex. Deliberate: real email validation happens by sending an email, not by regex.

has_unique_elements#

def has_unique_elements(*, key: Callable[[Any], Any] | None = None):
    def unwrapped(value: Iterable[Any]) -> None:
        items = list(value)
        key_fn = key or (lambda x: x)

        seen: list[tuple[type, Any]] = []
        duplicates = []

        for item in items:
            keyed = key_fn(item)
            typed_key = (type(keyed), keyed)

            found = False
            for seen_key in seen:
                if typed_key[0] == seen_key[0] and typed_key[1] == seen_key[1]:
                    found = True
                    if keyed not in duplicates:
                        duplicates.append(keyed)
                    break

            if not found:
                seen.append(typed_key)

        if duplicates:
            raise DuplicatesError(duplicates)
    return unwrapped
  1. Type-strict: (type(keyed), keyed) is the comparison key. So 1, 1.0, and True are different. Idiomatic Python says 1 == True, but here we don’t care — we want to distinguish them.
  2. Handles unhashables: list-based comparison, O(n²) instead of using a set. Hashables still work fine (list, dict, set as values are OK).
  3. Raises DuplicatesError: subclass of InvariantViolationError, formats the duplicates list cleanly.

Used to validate uniqueness of test names / IDs. Since 1.1.10, the name validators (campaigns, suites, cases) pass a key that NFC-normalizes then case-folds the name, so name uniqueness is case-insensitive (Login clashes with login); ID uniqueness stays exact.

is_valid_filename#

def is_valid_filename(value: str) -> None:
    _forbidden_chars = re.compile(r'[\x00-\x1f\\/:*?"<>|]')
    _windows_reserved = re.compile(
        r"^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", re.IGNORECASE
    )

    if not value:
        raise InvariantViolationError("Filename must not be empty.")
    if len(value) > 255:
        raise InvariantViolationError(f"Filename '{value}' exceeds 255 characters.")
    if _forbidden_chars.search(value):
        raise InvariantViolationError(
            f"Filename '{value}' contains forbidden characters "
            '(control chars or one of: \\ / : * ? " < > |).'
        )
    if value[0] in (".", " ") or value[-1] in (".", " "):
        raise InvariantViolationError(f"Filename '{value}' must not start or end with a dot or a space.")

    stem = value.split(".", 1)[0]
    if _windows_reserved.match(stem):
        raise InvariantViolationError(f"Filename '{value}' uses a reserved Windows device name.")
  1. Forbidden chars (control + \ / : * ? " < > |)
  2. No leading/trailing . or
  3. No Windows-reserved name (CON, PRN, AUX, NUL, COM1-9, LPT1-9)
  4. Length between 1 and 255

Used to validate test names. Every test produces a .log file whose name comes straight from the test name. See validate_test_runners_names.

is_iso_utc_date_string#

def is_iso_utc_date_string(value: str) -> None:
    try:
        dt = datetime.fromisoformat(value)
    except Exception as exc:
        raise InvariantViolationError(f"'{value}' is not a valid ISO date string.") from exc
    if dt.tzinfo != UTC:
        raise InvariantViolationError(f"'{value}' is not in UTC (tz={dt.tzinfo}).")

Accepts "2025-12-18T10:30:00+00:00", "2025-12-18T10:30:00Z". Rejects "2025-12-18" (no timezone) and "2025-12-18T10:30:00+02:00" (not UTC).

Used on the ocarina-example side to validate OTP_CACHE_DATE pulled from the L1 cache.

each#

def each(predicate: Predicate[Any]) -> Predicate[Iterable[Any]]:
    def unwrapped(value: Iterable[Any]) -> None:
        for item in value:
            predicate(item)
    return unwrapped
validate(filenames).assert_that(each(is_valid_filename)).execute().raise_if_invalid()

How to write your own predicate#

Direct case:

def is_str(value: Any) -> None:
    if not isinstance(value, str):
        raise InvariantViolationError("Expected value to be string.")

Parameterized case:

def is_equal_to(cmp: Any) -> Predicate[Any]:
    def unwrapped(value: Any) -> None:
        if value != cmp:
            raise InvariantViolationError(f"{value} is not equal to {cmp}.")
    return unwrapped
  1. Raise InvariantViolationError — nothing else.
  2. The message has to be diagnosable: include value, cmp, useful context.
  3. A predicate is a Callable[[T], None].