odoo-model-inspector
This skill analyzes Odoo model inheritance chains and fields, showing "who stands on whom" with complete inheritance hierarchy including all fields and methods from all modules. Should be used when understanding model structure, checking available fields before adding new ones, or tracing inheritance dependencies.
SKILL.md
| Name | odoo-model-inspector |
| Description | This skill analyzes Odoo model inheritance chains and fields, showing "who stands on whom" with complete inheritance hierarchy including all fields and methods from all modules. Should be used when understanding model structure, checking available fields before adding new ones, or tracing inheritance dependencies. |
Odoo Model Inspector - Developer Documentation
Technical documentation for developers who want to understand or extend this skill.
Architecture
Core Components
odoo_model_inspector/
├── inspect.py # Entry point
├── config.py # Configuration (addon paths, output dir)
├── parsers/ # Code and manifest parsing
│ ├── manifest_parser.py # Parse __manifest__.py files
│ ├── model_parser.py # AST parsing of Python models
│ └── dependency_resolver.py # Build inheritance chain
└── formatters/ # Output formatting
├── json_formatter.py # JSON output for AI assistants
└── markdown_formatter.py # Markdown for human reading
Design Principles
- Zero code execution - AST parsing only, never import/run code
- Recursive dependencies - Follow complete dependency graph
- Circular dependency handling - Correct handling of module cycles
- Fast & token efficient - Direct file parsing, no database queries
Circular Dependency Handling
The Problem
Odoo modules often have circular dependencies:
purchase → purchase_stock → stock_dropshipping → purchase (CYCLE!)
↓ ↑
└──────────────────────────────┘
This is normal in Odoo, but complicates dependency analysis.
Solution: Topological Sort + Recovery
Step 1: Topological Sort (Kahn's Algorithm)
Algorithm:
1. Count in-degree (incoming edges) for each module
2. Take modules with in-degree == 0 (no dependencies)
3. Add to result
4. Remove from graph → decrease in-degree for dependents
5. Repeat while modules with in-degree == 0 exist
Result:
- Modules WITHOUT cycles: correctly sorted (base → extensions)
- Modules IN cycle: NOT in result (in-degree never reaches 0)
Step 2: Cycle Detection
if len(result) != len(graph):
# Not all modules processed = cycle exists!
modules_in_cycle = set(graph.keys()) - set(result)
print(f"Warning: Circular dependency detected in modules: {modules_in_cycle}")
Step 3: Recovery - Add "Lost" Modules
for module_name in modules_with_model:
if module_name not in sorted_modules:
sorted_modules.append(module_name) # Add to END
Result:
- ALL modules present in final chain
- Modules without cycles: in correct order
- Modules in cycle: at end of list (order within cycle may be approximate)
Step 4: Base Module First Guarantee
if base_module:
sorted_modules.remove(base_module) # Remove from wherever
sorted_modules.insert(0, base_module) # Put first
Guarantee: Base model (_name = '...') ALWAYS first in chain.
Example: purchase.order
Input:
- 17 modules extend purchase.order
- 8 modules in circular dependencies
Dependency Graph:
purchase (BASE)
├─> purchase_repair (no cycle)
└─> [8 modules in cycle]:
purchase_stock ↔ stock_dropshipping
sale_purchase ↔ purchase_mrp
...
Result:
purchase (BASE) - 40 fields
└─> purchase_requisition_stock - +1 fields
└─> purchase_repair - +1 fields
└─> ...modules without cycles...
└─> purchase_stock - +11 fields ← modules from cycle
└─> stock_dropshipping - +1 fields
└─> sale_purchase - +1 fields
✅ All 17 modules present ✅ Base model first ✅ Modules without cycles correctly sorted ✅ Complete dependency picture
Field and Method Parsing
Field Extraction via AST
What is extracted:
-
Field type:
name = fields.Char(...) → type: "Char" partner_id = fields.Many2one(...) → type: "Many2one" -
Required flag:
name = fields.Char(required=True) → required: true email = fields.Char() → required: false
Output format:
- `name`: Char [required]
- `email`: Char
- `partner_id`: Many2one [required]
Method Extraction via AST
What is extracted:
-
Method name:
def action_confirm(self): → method: "action_confirm" -
super() call detection:
def write(self, vals): super().write(vals) → has_super: true -
Parent module identification:
- Analyze inheritance chain
- Find method with same name in parent modules
- Show which module is being overridden
Output format:
Methods Added (12):
- `_compute_total`
- `action_confirm`
- `write` [super from purchase] ← overrides method from purchase
- `create` [super from mail.thread]
Building Inheritance Chain
Process
-
Find all modules with model:
- Parse all
models/*.pyfiles - Look for classes with
_name = 'model.name'or_inherit = 'model.name'
- Parse all
-
Build dependency graph:
- From
__manifest__.py→depends: [...] - Only modules that extend our model
- From
-
Topological sort + recovery:
- Sort by dependencies
- Handle cycles (see above)
-
Find base model:
- Look for module with
_name = 'model.name'(not_inherit) - Put FIRST in chain
- Look for module with
-
Collect information:
- For each module: fields, methods, dependencies
- Determine super methods (analyze parent modules)
Context Analysis
With context-module:
--context-module product
- Analyze only dependencies of this module
- Recursively: dependencies of dependencies
- Result: modules available from product module
Without context-module:
# NO --context-module
- Analyze ALL installed modules
- Complete picture of all model extensions
- Result: all modules in system that touch the model
Output Formatting
JSON Format
For AI assistants - compact and parseable:
{
"model": "sale.order",
"total_fields": 160,
"inheritance_chain": [
{
"module": "sale",
"is_base": true,
"fields": {
"name": {"type": "Char", "required": true}
},
"methods": {
"action_confirm": false,
"write": true
}
}
]
}
Markdown Format
For humans - readable with dependency tree:
# Model: sale.order
**Total Fields:** 160
## Inheritance Chain
sale (BASE) - 60 fields └─> sale_stock - +12 fields └─> sale_management - +6 fields
## Module Details
### 1. sale (base definition)
**Fields Defined (60):**
- `name`: Char [required]
- `partner_id`: Many2one [required]
**Methods Defined (45):**
- `action_confirm`
- `write`
Token Efficiency
Problem Without Skill
Manual analysis of sale.order:
- Read
sale/models/sale_order.py(~2000 lines) - Search for all
_inherit = 'sale.order'in all modules - Open each file (~20 files)
- Extract fields manually
Cost: ~10,000 tokens, 10-15 minutes
With Skill
python .claude/skills/odoo_model_inspector/inspect.py \
--model sale.order \
--context-module sale
Cost: ~300 tokens, 30 seconds ✅
Savings: 97% tokens, 95% time
Usage Examples
1. Quick Model Overview
# What's in sale.order?
python .claude/skills/odoo_model_inspector/inspect.py \
--model sale.order \
--context-module sale
Result: JSON with fields + methods
2. Full Analysis with Documentation
# Complete picture of purchase.order
python .claude/skills/odoo_model_inspector/inspect.py \
--model purchase.order \
--output-markdown .odoo_inspect/purchase_order_FULL.md
Result:
- JSON to stdout
- Markdown file in
.odoo_inspect/
3. Check Before Adding Field
# Does delivery_date already exist?
python .claude/skills/odoo_model_inspector/inspect.py \
--model sale.order | grep delivery_date
Result: See where field is defined (if exists)
4. Understanding super Methods
# Which methods are overridden?
python .claude/skills/odoo_model_inspector/inspect.py \
--model sale.order \
--output-markdown .odoo_inspect/sale.md
Result: See [super from MODULE] for overridden methods
Limitations and Features
What is Parsed
✅ Fields: name + type + required ✅ Methods: name + has super() ✅ Inheritance chains ✅ Circular dependencies
What is NOT Parsed
❌ Compute methods (which method) ❌ Domains and constraints ❌ Method content ❌ Decorators (@api.depends, etc.)
Reason: Focus on structure, not logic. For logic use other tools (code reading, Serena, etc.).
AST vs Code Execution
Why AST:
- Safe (don't run code)
- Fast (only parsing)
- Works without Odoo server
- No dependencies needed
Downsides:
- Don't see dynamic fields
- Don't see computed attributes
- Approximate super method analysis
Conclusion: For structural analysis, AST is ideal.
Configuration
Edit config.py to customize for your project:
from pathlib import Path
# Project root (4 levels up from this file)
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
# Odoo addon directories (relative to PROJECT_ROOT)
ADDON_DIRECTORIES = [
'server/addons', # Core Odoo
'server/odoo/addons', # Base addons
'addons_custom', # Your custom addons
'addons_external', # External modules
]
# Output directory for Markdown files
OUTPUT_DIRECTORY = '.odoo_inspect'
FAQ
Q: Why warning about circular dependencies?
A: Informational message. Cycles are normal in Odoo (purchase ↔ stock). Skill handles them correctly via recovery mechanism.
Q: Why can module order in cycle differ?
A: Inside cycle there's no "correct" order (A depends on B, B on A). Important that all modules are present.
Q: How to add new field type?
A: Add to model_parser.py → FIELD_TYPES:
FIELD_TYPES = {
'Char', 'Text', ..., 'NewFieldType'
}
Q: Why don't I see dynamic fields?
A: AST parsing doesn't execute code. Dynamic fields are created at runtime, so not visible in static analysis.
Q: Can I use this for Odoo versions before 18?
A: Yes! AST parsing works for any Python-based Odoo version (8.0+). Only model structure matters, not version.
Contributing
To extend this skill:
- Add new parser: Create in
parsers/directory - Add new formatter: Create in
formatters/directory - Update config: Add new options to
config.py - Test: Run on various models to ensure correctness
- Document: Update this README with your changes
License
MIT License - see repository root for details.