Static analyzer that reads Odoo community/enterprise source code and produces a merged, versioned knowledge base of models, fields, and methods.
Project description
odoo-inspect
Static analyzer for Odoo source code. It reads Odoo community and enterprise
addons directly from disk (no running Odoo instance needed), parses every
.py / .xml file with the standard library, and produces a merged,
versioned knowledge base of models, fields, and methods — tagged with the
module that contributed each piece.
odoo-inspect is built for Odoo developers who have to reason about
schema changes between versions, plan module upgrades, or answer
questions like "when did account.move.payment_state appear?" without
spinning up four Odoo instances in parallel.
Why
Odoo models are extended across many modules. sale.order is defined in
sale/, but account/, sale_stock/, sale_timesheet/ and dozens of
others add fields and methods on top. A plain grep can tell you where a
field is declared, but not what the complete shape of the model looks
like once every addon you enable has been merged together.
odoo-inspect walks every module on disk, runs each .py file through
ast, and merges every extension into a single JSON file per model — so
you can open knowledge/v17/models/sale.order.json and see the full
contributed surface in one place.
Install
pip install odoo-inspect
Requires Python 3.12 or newer.
Quickstart — CLI
Scan a single Odoo version:
odoo-inspect scan \
--community /path/to/odoo17/odoo/addons /path/to/odoo17/addons \
--enterprise /path/to/enterprise17 \
--version 17.0 \
--output knowledge/v17
--community accepts multiple paths (space-separated) so you can include
both odoo/addons/ and the top-level addons/ where base lives.
--enterprise is optional. The output directory is overwritten on
every scan — each run is a fresh snapshot.
Compare two previously-scanned versions:
odoo-inspect diff \
--old knowledge/v16 \
--new knowledge/v17 \
--output knowledge/diffs/v16_to_v17.json
Build a keyword → model router from a knowledge directory:
odoo-inspect router \
--knowledge knowledge/v17 \
--output router/v17
Render an upgrade report
Once you have a diff JSON, report turns it into something a human can
actually read. Two formats:
HTML — a single self-contained file you can open in any browser. No server, no external assets, no build step. Inline CSS + JS. Key features:
- Overview cards at the top (models added / removed / changed counts).
- One collapsible
<details>card per model with field changes. Click to expand; badges on each card show+Nadded,−Nremoved,~Ntype/selection changes at a glance. - A sticky search box that filters the model cards client-side as you type — works fine with thousands of models.
- Separate "Models added" / "Models removed" / "Financial changes" sections.
odoo-inspect report \
--diff knowledge/diffs/v16_to_v17.json \
--format html \
--output upgrade_v16_to_v17.html
# then just open it
xdg-open upgrade_v16_to_v17.html # Linux
open upgrade_v16_to_v17.html # macOS
Markdown — for pasting into GitHub issues, PR descriptions, Slack,
or committing into a repo alongside a release. Produces a single
.md file with tables per model and a summary section at the bottom.
odoo-inspect report \
--diff knowledge/diffs/v16_to_v17.json \
--format markdown \
--output upgrade_v16_to_v17.md
If you omit --output, the report is printed to stdout — handy for
piping into a pager or another tool.
Full end-to-end example
# 1. Scan both versions
odoo-inspect scan --community /path/to/odoo16/odoo/addons \
--version 16.0 --output knowledge/v16
odoo-inspect scan --community /path/to/odoo17/odoo/addons \
--version 17.0 --output knowledge/v17
# 2. Compute the diff
odoo-inspect diff --old knowledge/v16 --new knowledge/v17 \
--output knowledge/diffs/v16_to_v17.json
# 3. Render a human-readable report
odoo-inspect report --diff knowledge/diffs/v16_to_v17.json \
--format html --output upgrade_v16_to_v17.html
Quickstart — Python API
from pathlib import Path
from odoo_inspect import Scanner, Differ, Router, KnowledgeLoader
# Scan from source
scanner = Scanner(
community_paths=[Path("/path/to/odoo17/odoo/addons")],
enterprise_paths=[Path("/path/to/enterprise17")],
version="17.0",
)
result = scanner.scan()
result.write(Path("knowledge/v17"))
# Load and diff
old = KnowledgeLoader(Path("knowledge/v16")).load()
new = KnowledgeLoader(Path("knowledge/v17")).load()
diff = Differ(old, new).diff()
print(f"Added models: {len(diff.models_added)}")
print(f"Removed models: {len(diff.models_removed)}")
print(f"Models with field changes: {len(diff.field_changes)}")
# Build a router
router = Router(new).build()
router.write(Path("router/v17"))
# Render the diff as a single-file HTML report
from odoo_inspect.writers import write_html_diff, write_markdown_diff
write_html_diff(diff, Path("upgrade_v16_to_v17.html"))
write_markdown_diff(diff, Path("upgrade_v16_to_v17.md"))
Output format
knowledge/v17/
models/<model.name>.json one file per merged model
_meta.json version info, module index, model index, stats
_financial.json account-type classification for this version
Every field entry records the module that contributed it in a source
field. When the same field is declared in a base module and then replaced
by an extension, the base module's declaration is kept under
original_source.
See docs/output-format.md for the full schema.
How merging works
For each models.Model / TransientModel / AbstractModel class:
_name |
_inherit |
Treated as |
|---|---|---|
set, not in _inherit |
— | definition (locks base module) |
set, equals _inherit (_name = _inherit = 'x') |
— | in-place extension |
set, _inherit = different model |
— | new model from parent (parent tracked in inherits) |
not set, _inherit = existing model |
— | extension (fields/methods merged in, source-tagged) |
set, _inherit = list of mixins |
— | definition with mixins |
The scanner makes two passes:
- Collect every class across every scanned module.
- Apply definitions first, so every model's
base_moduleis locked by its real owner before any extension can influence it. Only after all definitions are in does the merger walk the extensions.
This is what keeps res.partner.base_module equal to base instead of
getting overwritten by whichever _inherit = 'res.partner' extension
happened to be scanned first.
Module filtering
test_*, hw_*, theme_*, most l10n_* (except l10n_in and
l10n_generic_coa), bus, web, base_setup, iap, gamification,
auth_*, google_*, and microsoft_* are skipped by default. Priority
modules (base, sale, account, stock, crm, hr, mail, …) are
tagged priority in _meta.json; everything else is still scanned but
tagged extra.
Override these lists by editing the constants in
src/odoo_inspect/constants.py, or by passing a custom ScanConfig to
the Scanner class.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odoo_inspect-0.1.0.tar.gz.
File metadata
- Download URL: odoo_inspect-0.1.0.tar.gz
- Upload date:
- Size: 35.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3b2e440da2dd9f705e26330bba460f01692f77b9577ea6c2936f0308d9c098c
|
|
| MD5 |
3390e09b53835ea3e1d6441bd4ff4b8b
|
|
| BLAKE2b-256 |
7d1be546d4c8e094b689bbdf9638296ab3fac16f101f6924dabec0dc8e8d0aef
|
File details
Details for the file odoo_inspect-0.1.0-py3-none-any.whl.
File metadata
- Download URL: odoo_inspect-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47fd89ea5626aee869222bd4b4230c30b674a26188a20867251331152e1c92ca
|
|
| MD5 |
f740564c999cb8872a54097e45f5b72a
|
|
| BLAKE2b-256 |
78dae9f14ae2b8c8a5310581ceb825ece8994e7a5b395118d02f838627351a25
|