Skip to content

Formats

JSON and HTML assertables. See the JSON and HTML guides for task-focused examples.

pyssertive.formats.AssertableJson

AssertableJson(data: Any, *, _path: str = '')

Fluent assertions over a parsed JSON document or a scoped sub-tree.

Created implicitly by FluentResponse.assert_json(). Scoping via :meth:json returns a new instance bound to a sub-tree of the already-parsed data — no re-parsing.

All path arguments use dot-notation relative to the current scope (e.g. "user.profile.age"). Array indices are numeric strings (e.g. "items.0.id").

Source code in src/pyssertive/formats/json.py
def __init__(self, data: Any, *, _path: str = "") -> None:
    if not _path:
        if isinstance(data, (bytes, bytearray)):
            data = stdjson.loads(bytes(data))
        elif isinstance(data, str):
            data = stdjson.loads(data)
    self._data = data
    self._path = _path

has

has(key: str, count: int | None = None) -> Self
Source code in src/pyssertive/formats/json.py
def has(self, key: str, count: int | None = None) -> Self:
    assert self._path_exists(key), f"Expected path '{self._full_path(key)}' to exist in JSON"
    if count is not None:
        value = self._resolve(key)
        assert isinstance(value, (list, dict)), (
            f"Expected '{self._full_path(key)}' to be countable, got {type(value).__name__}"
        )
        actual = len(value)
        assert actual == count, f"Expected {count} items at '{self._full_path(key)}', got {actual}"
    return self

missing

missing(key: str) -> Self
Source code in src/pyssertive/formats/json.py
def missing(self, key: str) -> Self:
    if self._path_exists(key):
        value = self._resolve(key)
        raise AssertionError(f"Path '{self._full_path(key)}' should not exist but has value: {value}")
    return self

where

where(key: str, expected: Any) -> Self
Source code in src/pyssertive/formats/json.py
def where(self, key: str, expected: Any) -> Self:
    actual = self._resolve(key)
    if callable(expected) and not isinstance(expected, (str, bytes, int, float, bool, list, dict, type(None))):
        assert expected(actual), f"Value at '{self._full_path(key)}' did not satisfy predicate, got: {actual!r}"
    else:
        assert actual == expected, f"Expected '{expected}' at '{self._full_path(key)}', got '{actual}'"
    return self

where_not

where_not(key: str, unexpected: Any) -> Self
Source code in src/pyssertive/formats/json.py
def where_not(self, key: str, unexpected: Any) -> Self:
    actual = self._resolve(key)
    assert actual != unexpected, f"Expected '{self._full_path(key)}' to not be '{unexpected}', but it was"
    return self

where_truthy

where_truthy(key: str) -> Self
Source code in src/pyssertive/formats/json.py
def where_truthy(self, key: str) -> Self:
    actual = self._resolve(key)
    assert actual, f"Expected truthy value at '{self._full_path(key)}', got '{actual}'"
    return self

where_falsy

where_falsy(key: str) -> Self
Source code in src/pyssertive/formats/json.py
def where_falsy(self, key: str) -> Self:
    actual = self._resolve(key)
    assert not actual, f"Expected falsy value at '{self._full_path(key)}', got '{actual}'"
    return self

where_type

where_type(
    key: str, expected_type: type | tuple[type, ...]
) -> Self
Source code in src/pyssertive/formats/json.py
def where_type(self, key: str, expected_type: type | tuple[type, ...]) -> Self:
    actual = self._resolve(key)
    if isinstance(expected_type, tuple):
        type_names = " | ".join(t.__name__ for t in expected_type)
    else:
        type_names = expected_type.__name__
    assert isinstance(actual, expected_type), (
        f"Expected type {type_names} at '{self._full_path(key)}', got {type(actual).__name__}"
    )
    return self

count

count(expected: int) -> Self
Source code in src/pyssertive/formats/json.py
def count(self, expected: int) -> Self:
    assert isinstance(self._data, (list, dict)), (
        f"Expected countable data at scope '{self._path or 'root'}', got {type(self._data).__name__}"
    )
    actual = len(self._data)
    assert actual == expected, f"Expected {expected} items at scope '{self._path or 'root'}', got {actual}"
    return self

fragment

fragment(expected: dict) -> Self
Source code in src/pyssertive/formats/json.py
def fragment(self, expected: dict) -> Self:
    flat = stdjson.dumps(self._data)
    for key, value in expected.items():
        pair = f'"{key}": {stdjson.dumps(value)}'
        assert pair in flat, f"Fragment {key}: {value} not found at scope '{self._path or 'root'}'"
    return self

missing_fragment

missing_fragment(expected: dict) -> Self
Source code in src/pyssertive/formats/json.py
def missing_fragment(self, expected: dict) -> Self:
    flat = stdjson.dumps(self._data)
    for key, value in expected.items():
        pair = f'"{key}": {stdjson.dumps(value)}'
        assert pair not in flat, f"Unexpected fragment {key}: {value} found at scope '{self._path or 'root'}'"
    return self

exact

exact(expected: Any) -> Self
Source code in src/pyssertive/formats/json.py
def exact(self, expected: Any) -> Self:
    assert self._data == expected, f"Expected exact JSON: {expected}, got: {self._data}"
    return self

is_dict

is_dict() -> Self
Source code in src/pyssertive/formats/json.py
def is_dict(self) -> Self:
    assert isinstance(self._data, dict), (
        f"Expected JSON dict at scope '{self._path or 'root'}', got {type(self._data).__name__}"
    )
    return self

is_list

is_list() -> Self
Source code in src/pyssertive/formats/json.py
def is_list(self) -> Self:
    assert isinstance(self._data, list), (
        f"Expected JSON list at scope '{self._path or 'root'}', got {type(self._data).__name__}"
    )
    return self

structure

structure(schema: dict) -> Self
Source code in src/pyssertive/formats/json.py
def structure(self, schema: dict) -> Self:
    assert isinstance(self._data, dict), (
        f"Expected JSON dict at scope '{self._path or 'root'}', got {type(self._data).__name__}"
    )
    for key, expected_type in schema.items():
        assert key in self._data, f"Key '{key}' missing from JSON at scope '{self._path or 'root'}'"
        if expected_type is not None:
            actual_type = type(self._data[key])
            assert isinstance(self._data[key], expected_type), (
                f"Key '{key}' expected type {expected_type.__name__}, got {actual_type.__name__}"
            )
    return self

matches_schema

matches_schema(schema: dict[str, Any] | str | Path) -> Self
Source code in src/pyssertive/formats/json.py
def matches_schema(self, schema: dict[str, Any] | str | Path) -> Self:
    resolved = _resolve_schema(schema)
    try:
        jsonschema.validate(instance=self._data, schema=resolved)
    except jsonschema.ValidationError as exc:
        path_str = ".".join(str(p) for p in exc.absolute_path) if exc.absolute_path else "root"
        scope = f" at scope '{self._path}'" if self._path else ""
        raise AssertionError(f"JSON schema validation failed{scope} at '{path_str}': {exc.message}") from None
    return self

json

json(
    path: str,
    callback: Callable[[AssertableJson], Any] | None = None,
) -> AssertableJson | Self
Source code in src/pyssertive/formats/json.py
def json(
    self,
    path: str,
    callback: Callable[[AssertableJson], Any] | None = None,
) -> AssertableJson | Self:
    data = self._resolve(path)
    child = AssertableJson(data, _path=self._full_path(path))
    if callback is None:
        return child
    callback(child)
    return self

pyssertive.formats.AssertableHtml

AssertableHtml(
    markup: str | bytes,
    *,
    _tag: Tag | None = None,
    _scope: str = "document",
)

Fluent assertions over an HTML document or a scoped sub-tree.

Created implicitly by FluentResponse.assert_html(). Scoping via :meth:html returns a new instance bound to a sub-tree of the already-parsed document — no re-parsing.

Two families of content assertions:

  • see_html / dont_see_html — operate on the raw HTML markup (tags preserved). Use when asserting tag structure, class names, attribute values, or specific HTML fragments.
  • see_text / dont_see_text — operate on the rendered visible text (tags stripped). Use when asserting what a reader would see.

Selector-based assertions (count, exists, missing, html) use CSS selectors via BeautifulSoup/soupsieve.

Source code in src/pyssertive/formats/html.py
def __init__(self, markup: str | bytes, *, _tag: Tag | None = None, _scope: str = "document") -> None:
    if isinstance(markup, (bytes, bytearray)):
        markup = bytes(markup).decode("utf-8", errors="replace")
    self._markup = markup
    self._tag: Any = _tag if _tag is not None else BeautifulSoup(markup, "html.parser")
    self._scope = _scope

see_html

see_html(fragment: str) -> Self
Source code in src/pyssertive/formats/html.py
def see_html(self, fragment: str) -> Self:
    markup = self._raw()
    assert fragment in markup, f"Expected to see HTML markup '{fragment}' in scope '{self._scope}', got: {markup}"
    return self

dont_see_html

dont_see_html(fragment: str) -> Self
Source code in src/pyssertive/formats/html.py
def dont_see_html(self, fragment: str) -> Self:
    markup = self._raw()
    assert fragment not in markup, (
        f"Did not expect HTML markup '{fragment}' in scope '{self._scope}', got: {markup}"
    )
    return self

see_html_in_order

see_html_in_order(fragments: list[str]) -> Self
Source code in src/pyssertive/formats/html.py
def see_html_in_order(self, fragments: list[str]) -> Self:
    markup = self._raw()
    last_index = -1
    last_fragment = ""
    for key, fragment in enumerate(fragments):
        index = markup.find(fragment, last_index + 1)
        location = "" if last_fragment == "" else f"after '{last_fragment}'"
        assert index != -1, f"HTML markup '{fragment}' ({key}) not found {location} in scope '{self._scope}'"
        last_index = index
        last_fragment = fragment
    return self

see_text

see_text(text: str) -> Self
Source code in src/pyssertive/formats/html.py
def see_text(self, text: str) -> Self:
    plain = self._text()
    assert text in plain, f"Expected to see rendered text '{text}' in scope '{self._scope}', got: {plain}"
    return self

dont_see_text

dont_see_text(text: str) -> Self
Source code in src/pyssertive/formats/html.py
def dont_see_text(self, text: str) -> Self:
    plain = self._text()
    assert text not in plain, f"Did not expect rendered text '{text}' in scope '{self._scope}', got: {plain}"
    return self

see_text_in_order

see_text_in_order(texts: list[str]) -> Self
Source code in src/pyssertive/formats/html.py
def see_text_in_order(self, texts: list[str]) -> Self:
    plain = self._text()
    last_index = -1
    last_text = ""
    for key, text in enumerate(texts):
        index = plain.find(text, last_index + 1)
        location = "" if last_text == "" else f"after '{last_text}'"
        assert index != -1, f"Rendered text '{text}' ({key}) not found {location} in scope '{self._scope}'"
        last_index = index
        last_text = text
    return self

html_contains

html_contains(fragment: str) -> Self
Source code in src/pyssertive/formats/html.py
def html_contains(self, fragment: str) -> Self:
    assert _html_contains_fragment(self._tag, fragment), (
        f"HTML fragment '{fragment}' not found in scope '{self._scope}'"
    )
    return self

count

count(selector: str, expected: int) -> Self
Source code in src/pyssertive/formats/html.py
def count(self, selector: str, expected: int) -> Self:
    actual = len(self._tag.select(selector))
    assert actual == expected, (
        f"Expected {expected} elements matching '{selector}' in scope '{self._scope}', got {actual}"
    )
    return self

exists

exists(selector: str) -> Self
Source code in src/pyssertive/formats/html.py
def exists(self, selector: str) -> Self:
    assert self._tag.select_one(selector) is not None, (
        f"Expected at least one element matching '{selector}' in scope '{self._scope}'"
    )
    return self

missing

missing(selector: str) -> Self
Source code in src/pyssertive/formats/html.py
def missing(self, selector: str) -> Self:
    assert self._tag.select_one(selector) is None, (
        f"Expected no elements matching '{selector}' in scope '{self._scope}'"
    )
    return self

html

html(
    selector: str,
    callback: Callable[[AssertableHtml], Any] | None = None,
) -> AssertableHtml | Self
Source code in src/pyssertive/formats/html.py
def html(
    self,
    selector: str,
    callback: Callable[[AssertableHtml], Any] | None = None,
) -> AssertableHtml | Self:
    tag = self._tag.select_one(selector)
    assert tag is not None, f"Selector '{selector}' not found in scope '{self._scope}'"
    child = AssertableHtml(str(tag), _tag=tag, _scope=self._child_scope(selector))
    if callback is None:
        return child
    callback(child)
    return self