pyisyox.schema.profile module

Profile loader for /rest/profiles JSON.

Parses families → instances → editors/linkdefs/nodedefs and builds the (nodedef_id, family_id, instance_id) NodeDef lookup. Knows the wire shape but does no HTTP — callers pass dicts to Profile.load_from_json().

The controller inline-resolves visible strings into the name fields, so the NLS string table isn’t needed for /rest/profiles families. Dynamically generated Z-Wave nodedefs (from def/get XML) are the exception: they arrive label-less, so pyisyox.client overlays the per-family NLS tables onto Profile.nls after loading them.

class Instance(id, name, editors=<factory>, linkdefs=<factory>, nodedefs=<factory>)[source]

Bases: object

One instance within a family — a self-contained set of editors, linkdefs, and nodedefs.

For built-in families there is typically one instance with id "1". For PG3 plugin families (family "10" in the captured fixture), the instance id matches the plugin slot number and is encoded in node addresses as the n0XX_ prefix.

Parameters:
id: str
name: str
editors: dict[str, Editor]
linkdefs: dict[str, LinkDef]
nodedefs: dict[str, NodeDef]
class Family(id, name, instances=<factory>)[source]

Bases: object

A family of instances. Family id is a string so plugin slots ("10", "11", …) and the special "common" family can coexist.

Parameters:
id: str
name: str
instances: dict[str, Instance]
class Profile(timestamp='', families=<factory>, nodedef_lookup=<factory>, nls=<factory>)[source]

Bases: object

The decoded result of one /rest/profiles JSON blob.

The nodedef_lookup is the load-bearing artifact callers use to resolve a node (which carries family_id, instance_id, and nodeDefId) to its NodeDef.

Parameters:
timestamp: str
families: dict[str, Family]
nodedef_lookup: dict[tuple[str, str, str], NodeDef]
nls: NLSTable

Merged NLS string table for any dynamically-loaded families (Z-Wave). Empty unless pyisyox.client fetched per-family NLS during load.

classmethod load_from_json(raw)[source]

Build a Profile from a parsed /rest/profiles response.

Parameters:

raw (dict) – Top-level dict with timestamp and families[].

Returns:

A populated Profile with families, instances, and a built lookup table.

Return type:

Profile

merge(other)[source]

Merge other into self in place; return a diff summary.

Designed for PG3 dynamic profile reload. Existing runtime Node instances hold references to the resolved NodeDef, so a wholesale replace would leave them clinging to stale lookups — instead the merge mutates the existing dicts and replaces individual NodeDef/Editor/LinkDef objects. Additive only: items absent from other are kept.

Parameters:

other (Profile)

Return type:

ProfileMergeResult

register_nodedefs(family_id, instance_id, nodedefs)[source]

Add a batch of nodedefs to family_id/instance_id in place.

Used for dynamic Z-Wave nodedefs (fetched from def/get XML — the Z-Wave families already exist in /rest/profiles with their ZW_* editors but no nodedefs). Overwrites by id.

Parameters:
Return type:

None

find_nodedef(nodedef_id, family_id, instance_id)[source]

Resolve a nodedef by its (id, family, instance) join key.

Returns None when no matching nodedef exists — callers should treat that as the unknown-type case (e.g. fall back to the nodedef-driven HA platform classifier rather than the type-based one).

Parameters:
  • nodedef_id (str)

  • family_id (str)

  • instance_id (str)

Return type:

NodeDef | None

find_editor(editor_id, family_id, instance_id)[source]

Resolve an editor by id within a family/instance scope.

Editors are scoped to their instance — the same id (e.g. "bool") may appear in multiple instances with different ranges, so the family/instance must be supplied.

An encoded editor id — one that fully describes its range, e.g. "_51_0_R_0_101_N_IX_DIM_REP" — is decoded directly via Editor.from_encoded_id(); this is how the dynamically- generated Z-Wave nodedefs spell most of their editors. (The check is “does it parse as an encoding”, not just “starts with _” — UDI also ships named editors that begin with _ such as _sys_notify_full, which fall through to the table lookup.)

Table-lookup fallback chain on miss:

  1. family_id / instance_id (the requested scope)

  2. The "common" family / instance "1" — UDI publishes a shared set of editors there (_sys_notify_full, etc.) that any plugin nodedef can reference

Returns None if it’s neither a valid encoding nor present in either scope.

Parameters:
  • editor_id (str)

  • family_id (str)

  • instance_id (str)

Return type:

Editor | None

to_dict()[source]

Flatten the profile to a JSON-compatible dict.

nodedef_lookup is dropped — its (nodedef_id, family_id, instance_id) tuple keys are not JSON-encodable and the same data lives under families[fam].instances[inst].nodedefs. nodedef_lookup_count is surfaced as a sanity-check counter.

pyisyox.schema.editor.EditorRange carries subset: set[int] which JSON can’t encode either; the walker below normalises every set into a sorted list so the snapshot round-trips through json.dumps.

Return type:

dict[str, Any]

class ProfileMergeResult(nodedefs_added=<factory>, nodedefs_replaced=<factory>, editors_added=<factory>, editors_replaced=<factory>, linkdefs_added=<factory>, linkdefs_replaced=<factory>)[source]

Bases: object

Diff produced by Profile.merge().

Variables:
  • nodedefs_added (list[tuple[str, str, str]]) – (nodedef_id, family_id, instance_id) triples for nodedefs that didn’t exist in the destination profile before the merge.

  • nodedefs_replaced (list[tuple[str, str, str]]) – Same shape, for nodedefs whose existing entry was overwritten with a fresh NodeDef. The old object is no longer in Profile.nodedef_lookup; consumers caching it should refresh.

  • editors_replaced (editors_added /) – (editor_id, family_id, instance_id) triples for editors.

  • linkdefs_replaced (linkdefs_added /) – same for linkdefs.

Parameters:
nodedefs_added: list[tuple[str, str, str]]
nodedefs_replaced: list[tuple[str, str, str]]
editors_added: list[tuple[str, str, str]]
editors_replaced: list[tuple[str, str, str]]
linkdefs_added: list[tuple[str, str, str]]
linkdefs_replaced: list[tuple[str, str, str]]
property changed: bool

True when anything differed — nodedefs, editors, or linkdefs.