Skip to content

Validation

validate

Optional checks on a layout, for things the format allows but TouchOSC won't like.

Validation is deliberately advisory and never raises. TouchOSC accepts properties it does not recognise -- that is what makes custom properties useful -- so a strict schema would reject valid layouts. What this catches instead is the narrower and more useful case: a property or value that is part of the format but belongs to a different control type, plus the few things TouchOSC genuinely cannot load.

for issue in doc.validate():
    print(issue)

Every rule below is corroborated against layouts the TouchOSC editor wrote; see tests/test_validate.py, which requires every editor-written file in the corpus to validate without errors.

Issue dataclass

One finding from validate.

Attributes:

Name Type Description
level str

error for something TouchOSC cannot load, warning for something it tolerates but probably did not intend.

path str

Slash-separated control names from the root, for locating it.

message str

What is wrong.

Source code in src/py2tosc/validate.py
@dataclass(frozen=True)
class Issue:
    """One finding from [`validate`][py2tosc.validate].

    Attributes:
        level: `error` for something TouchOSC cannot load, `warning` for
            something it tolerates but probably did not intend.
        path: Slash-separated control names from the root, for locating it.
        message: What is wrong.
    """

    level: str
    path: str
    message: str

    def __str__(self) -> str:
        return f"{self.level}: {self.path}: {self.message}"

ValidationError

Bases: Py2ToscError

Raised by save(validate=True) when a layout has errors.

Attributes:

Name Type Description
issues

Every finding, not only the errors, so a caller catching this can report the warnings too.

Source code in src/py2tosc/validate.py
class ValidationError(Py2ToscError):
    """Raised by `save(validate=True)` when a layout has errors.

    Attributes:
        issues: Every finding, not only the errors, so a caller catching this
            can report the warnings too.
    """

    def __init__(self, issues: list[Issue]):
        self.issues = issues
        errors = [i for i in issues if i.level == ERROR]
        super().__init__(
            f"{len(errors)} error(s) in the layout:\n"
            + "\n".join(f"  {i}" for i in errors)
        )

validate

validate(target: Control | Document) -> list[Issue]

Check a control tree for things TouchOSC will reject or ignore.

Parameters:

Name Type Description Default
target Control | Document

A Document or any Control; a control is checked along with everything beneath it.

required

Local message destinations are resolved against target and nothing above it, so validating a subtree that is wired to a control outside itself reports a destination it cannot see. Validate the whole Document to avoid that.

Returns:

Type Description
list[Issue]

Every finding, errors first, then in tree order. An empty list means

list[Issue]

nothing was found -- it is not a guarantee the layout opens.

Source code in src/py2tosc/validate.py
def validate(target: Control | Document) -> list[Issue]:
    """Check a control tree for things TouchOSC will reject or ignore.

    Args:
        target: A [`Document`][py2tosc.Document] or any
            [`Control`][py2tosc.Control]; a control is checked along with
            everything beneath it.

    Local message destinations are resolved against `target` and nothing above
    it, so validating a subtree that is wired to a control outside itself
    reports a destination it cannot see. Validate the whole
    [`Document`][py2tosc.Document] to avoid that.

    Returns:
        Every finding, errors first, then in tree order. An empty list means
        nothing was found -- it is not a guarantee the layout opens.
    """
    root = target if isinstance(target, Control) else target.root
    name = str(root.get("name") or "<root>")

    known = {control.id: control for control in root.walk()}
    issues = list(_check_control(root, [name], known))

    # The root node is the canvas, and TouchOSC gives it none of the behaviour
    # its type would otherwise have: a PAGER there draws its tab bar but never
    # pages, stacking every child instead. All 35 layouts in the corpus root at
    # a GROUP, and no PAGER appears above depth 1. Only checked for a document,
    # since validating a subtree says nothing about what sits at the top of it.
    if not isinstance(target, Control) and root.control_type is not ControlType.GROUP:
        issues.append(
            Issue(
                WARNING,
                name,
                f"the root is a {root.control_type.value}; TouchOSC treats the "
                f"root as a plain container, so put it inside a GROUP instead",
            )
        )

    # Node ids must be unique across the whole layout, so this cannot be done
    # per control.
    counts = Counter(control.id for control in root.walk())
    for node_id, count in sorted(counts.items()):
        if count > 1:
            issues.append(
                Issue(ERROR, name, f"node id {node_id} is used by {count} controls")
            )

    issues.sort(key=lambda i: i.level != ERROR)
    return issues

Issue dataclass

One finding from validate.

Attributes:

Name Type Description
level str

error for something TouchOSC cannot load, warning for something it tolerates but probably did not intend.

path str

Slash-separated control names from the root, for locating it.

message str

What is wrong.

Source code in src/py2tosc/validate.py
@dataclass(frozen=True)
class Issue:
    """One finding from [`validate`][py2tosc.validate].

    Attributes:
        level: `error` for something TouchOSC cannot load, `warning` for
            something it tolerates but probably did not intend.
        path: Slash-separated control names from the root, for locating it.
        message: What is wrong.
    """

    level: str
    path: str
    message: str

    def __str__(self) -> str:
        return f"{self.level}: {self.path}: {self.message}"

ValidationError, raised by save(validate=True), is documented with the rest of the errors.