#!/usr/bin/env python3 """Static validation for the activeblue_familylaw Odoo 18 module. Not a substitute for running the Odoo test suite — it catches the classes of error that are cheap to find without a running Odoo/DB: * Python files compile * XML files are well-formed * no Odoo-18-forbidden constructs in views (attrs=, states=, ) * every type="object" button name maps to a real method in the model layer * every file listed in __manifest__["data"] exists * ir.model.access.csv references models that are defined Run: python3 scripts/validate_module.py """ import ast import csv import os import re import sys import xml.etree.ElementTree as ET ROOT = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "activeblue_familylaw_handoff", "activeblue_familylaw_build", "activeblue_familylaw_v2", ) errors = [] ok_count = 0 def ok(_msg): global ok_count ok_count += 1 def walk(ext): for dirpath, _dirs, files in os.walk(ROOT): for f in files: if f.endswith(ext): yield os.path.join(dirpath, f) # 1. Python compiles + collect method names + model names methods = set() model_names = set() model_tech_names = set() # familylaw.document -> model_familylaw_document for f in walk(".py"): src = open(f).read() try: tree = ast.parse(src) except SyntaxError as e: errors.append(f"PYTHON SYNTAX {f}: {e}") continue ok(f) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): methods.add(node.name) for m in re.finditer(r'_name\s*=\s*["\']([a-z0-9_.]+)["\']', src): model_names.add(m.group(1)) model_tech_names.add("model_" + m.group(1).replace(".", "_")) # 2. XML well-formed + forbidden constructs + collect buttons buttons = [] for f in walk(".xml"): raw = open(f).read() try: root = ET.fromstring(raw) except ET.ParseError as e: errors.append(f"XML PARSE {f}: {e}") continue ok(f) # strip XML comments before scanning for forbidden tokens no_comments = re.sub(r"", "", raw, flags=re.DOTALL) for bad in ("attrs=", "states=", "