{"record":{"id":"8bc13d0699cad671","repo":"openmediavault/openmediavault","slug":"no-properties-attribute-defined-at-s","errorCode":null,"errorMessage":"No 'properties' attribute defined at '%s'.","messagePattern":"No 'properties' attribute defined at '(.+?)'\\.","errorType":"validation","errorClass":"SchemaException","httpStatus":null,"severity":"error","filePath":"deb/openmediavault/usr/lib/python3/dist-packages/openmediavault/config/datamodel.py","lineNumber":335,"sourceCode":"            if not \"type\" in schema:\n                raise openmediavault.json.SchemaException(\n                    \"No 'type' attribute defined at '%s'.\" % path\n                )\n            if \"array\" == schema['type']:\n                # Validate the node.\n                if not \"items\" in schema:\n                    raise openmediavault.json.SchemaException(\n                        \"No 'items' attribute defined at '%s'.\" % path\n                    )\n                # Call the callback function.\n                if callback(self, name, path, schema, user_data) is False:\n                    return\n                # Process the array items.\n                _walk_schema(name, path, schema['items'], callback, user_data)\n            elif \"object\" == schema['type']:\n                # Validate the node.\n                if not \"properties\" in schema:\n                    raise openmediavault.json.SchemaException(\n                        \"No 'properties' attribute defined at '%s'.\" % path\n                    )\n                # Call the callback function.\n                if callback(self, name, path, schema, user_data) is False:\n                    return\n                # Process the object properties.\n                for prop_name, prop_schema in schema['properties'].items():\n                    # Build the property path. Take care that a valid path\n                    # is generated. To ensure this, empty parts are removed.\n                    prop_path = \".\".join([x for x in [path, prop_name] if x])\n                    # Process the property node.\n                    _walk_schema(\n                        prop_name, prop_path, prop_schema, callback, user_data\n                    )\n            else:\n                callback(self, name, path, schema, user_data)\n\n        _walk_schema(","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/openmediavault/openmediavault/blob/dce610eb66f624c640d0eb7ce401ddd65b763520/deb/openmediavault/usr/lib/python3/dist-packages/openmediavault/config/datamodel.py#L317-L353","documentation":"Datamodel._walk_schema requires every node with type 'object' to define a 'properties' attribute, because it iterates schema['properties'] to recurse into child nodes. An object node without 'properties' is treated as an untraversable malformed schema and raises json.SchemaException with the failing path. Like error 150 this is a schema-definition bug, not bad data.","triggerScenarios":"Datamodel.walk_schema() (called by DatabaseQuery walks and ConfigObject) reaches a schema node {\"type\": \"object\"} with no 'properties' key — typically an empty or truncated object definition in the datamodel schema.","commonSituations":"Plugin authors declaring a nested object but forgetting its properties block; schema generated/merged by tooling that strips empty 'properties'; hand-editing config.xml.rng-derived schemas and dropping the properties map.","solutions":["Locate the path from the message in the datamodel schema and add a 'properties' object listing the child fields (use \"properties\": {} only if intentionally empty and the walker should not recurse).","Run the schema through openmediavault.json.Schema validation in a unit test to catch structural gaps before shipping.","Rebuild/re-register the plugin schema (dpkg install or omv-mkconf) so the fixed schema is loaded.","Diff the schema against a known-good upstream version to see what got lost."],"exampleFix":"// before\n\"notification\": { \"type\": \"object\" }\n// after\n\"notification\": { \"type\": \"object\", \"properties\": { \"enabled\": { \"type\": \"boolean\" } } }","handlingStrategy":"validation","validationCode":"def validate_object_schemas(schema):\n    for key, node in schema.items():\n        if isinstance(node, dict):\n            if node.get(\"type\") == \"object\" and \"properties\" not in node:\n                raise ValueError(f\"Object '{key}' missing 'properties'\")\n            validate_object_schemas(node)","typeGuard":"def has_properties(node: dict) -> bool:\n    return isinstance(node, dict) and node.get(\"type\") == \"object\" and \"properties\" in node","tryCatchPattern":"try:\n    datamodel.walk_schema(path, callback)\nexcept openmediavault.json.SchemaException as e:\n    logging.error(\"Malformed datamodel schema: %s\", e)","preventionTips":["Never declare an object node without a 'properties' map in datamodel schemas.","Add CI tests walking all datamodel schemas to surface structural gaps.","Keep 'properties': {} explicitly when an object is intentionally empty.","Diff schemas against upstream versions when upgrading plugins."],"tags":["python","json-schema","openmediavault","datamodel"],"backgroundTag":"missing-required-config-field","analyzedSha":"dce610eb66f624c640d0eb7ce401ddd65b763520","analyzedAt":"2026-09-15T09:48:09.960Z","contentChangedAt":"2026-09-15T09:48:09.960Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}