Odoo Abstract Model Generator from XSD Schemas
Generate Odoo abstract models (mixins) from XSD schemas using xsdata. Heavily inspired by xsdata-plantuml.
Video Tutorial: YouTube - xsdata-odoo Overview
Table of Contents
- Install
- Quick Start
- Real-World Usage
- Architecture Pattern
- Configuration
- Advanced Examples
- Field Prefixing Strategy
- Custom Filters
- Troubleshooting
Install
$ pip install xsdata[cli]
$ pip install git+https://github.com/akretion/xsdata-odoo
Quick Start
Microsoft Purchase Order Demo
$ xsdata generate tests/fixtures/po/po.xsd --output=odoo
Parsing schema po.xsd
Compiling schema po.xsd
Builder: 6 main and 1 inner classes
Analyzer input: 6 main and 1 inner classes
Analyzer output: 5 main and 1 inner classes
Generating package: generated.po
Brazilian NF-e (Electronic Invoice)
$ export XSDATA_SCHEMA=nfe
$ export XSDATA_VERSION=40
$ export XSDATA_SKIP="^ICMS.ICMS\d+|^ICMS.ICMSSN\d+"
$ export XSDATA_LANG="portuguese"
$ xsdata generate nfelib/nfe/schemas/v4_0 \
--package nfelib.nfe.odoo.v4_0 \
--output=odoo
Real-World Usage
xsdata-odoo is used in production by the OCA Brazilian Localization (l10n-brazil) to generate models for complex fiscal documents:
Brazilian Electronic Fiscal Documents
| Document | Fields | Model | Version | OCA Module |
|---|---|---|---|---|
| NF-e (Nota Fiscal Eletrônica) | 800+ | 55/65 | 4.0 | l10n_br_nfe_spec |
| CT-e (Transport Document) | 1000+ | 57 | 4.0 | l10n_br_cte_spec |
| MDF-e (Manifest) | 500+ | 58 | 3.0 | l10n_br_mdfe_spec |
| SPED (Fiscal Reports) | Variable | - | - | l10n_br_sped_* |
- NF-e: 800+ fields across multiple hierarchical structures (identification, items, taxes, transport, payment)
- CT-e: Multiple transport modes (road, air, waterway, rail, pipeline, multimodal) with specific fields each
- MDF-e: Aggregation of multiple NF-e/CT-e documents with municipal grouping
- SPED: Complex register hierarchies with parent-child relationships
Integration with nfelib: xsdata-odoo generates Odoo models, while nfelib handles XML serialization. Both use the same XSD schemas for consistency.
Architecture Pattern
Two-Layer Architecture
The OCA l10n-brazil uses a proven two-layer architecture:
1. Spec Modules (l10n_br_*_spec)
- 100% generated from XSD schemas
- Abstract models (mixins) with field definitions
- No business logic
- Versioned per schema version (nfe40, cte40, mdfe30)
2. Implementation Modules (l10n_br_nfe, etc.)
- Inherits from spec modules
- Maps to Odoo fiscal documents (
l10n_br_fiscal.document) - Business rules and validations
- Web service communication (SEFAZ)
- User interface
Benefits
- Schema Updates: Regenerate spec modules without touching business logic
- Version Migration: Different schema versions coexist (nfe40 vs nfe50)
- Testing: Business logic tested separately from generated code
- Maintainability: Clear separation of concerns
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
XSDATA_SCHEMA |
Schema name | spec |
XSDATA_VERSION |
Schema version | 10 |
XSDATA_SKIP |
Regex patterns to skip (pipe-separated) | [] |
XSDATA_LANG |
Language for text processing | "" |
XSDATA_MONETARY_TYPE |
XSD type to force fields.Monetary |
"" |
XSDATA_NUM_TYPE |
XSD type for numeric detection | TDec_[5:7.7:9] |
XSDATA_CURRENCY_FIELD |
Currency field name | currency_id |
XSDATA_GENDS |
GenerateDS compatibility mode | false |
Python API
from xsdata_odoo import get_config
config = get_config()
print(config.schema) # "spec"
print(config.version) # "10"
print(config.field_safe_prefix) # "spec10_"
print(config.inherit_model) # "spec.mixin.spec"
Advanced Examples
Brazilian NF-e with ICMS Handling
The NF-e schema defines ICMS taxes with multiple groups (ICMS00, ICMS10, ICMS40, etc.) that share field names. Skip them to avoid conflicts:
export XSDATA_SCHEMA=nfe
export XSDATA_VERSION=40
export XSDATA_SKIP="^ICMS.ICMS\d+|^ICMS.ICMSSN\d+"
export XSDATA_LANG="portuguese"
export XSDATA_CURRENCY_FIELD="brl_currency_id"
xsdata generate nfelib/nfe/schemas/v4_0 \
--package nfelib.nfe.odoo.v4_0 \
--output=odoo
# Move generated files to your module
mv nfelib/odoo/nfe/v4_0/* your_addon/models/v4_0/
CT-e (Transport Document)
export XSDATA_SCHEMA=cte
export XSDATA_VERSION=40
export XSDATA_SKIP="^ICMS\d+|^ICMSSN+|ICMSOutraUF|ICMSUFFim|INFESPECIE_TPESPECIE"
export XSDATA_LANG="portuguese"
xsdata generate nfelib/cte/schemas/v4_0 \
--package nfelib.cte.odoo.v4_0 \
--output=odoo
MDF-e (Manifest Document)
export XSDATA_SCHEMA=mdfe
export XSDATA_VERSION=30
export XSDATA_LANG="portuguese"
xsdata generate nfelib/mdfe/schemas/v3_0 \
--package nfelib.mdfe.odoo.v3_0 \
--output=odoo
SPED Fiscal Reports (Advanced)
For complex non-XML fiscal reports with register hierarchies:
from xsdata_odoo.generator import OdooGenerator
from xsdata_odoo.filters import OdooFilters
class SpedFilters(OdooFilters):
def registry_name(self, name="", parents=[], type_names=[]):
# Custom naming: schema.version.register_code
name = self.class_name(name)
return f"{self.schema}.{self.version}.{name[-4:].lower()}"
def class_properties(self, obj, parents):
# Add custom metadata
register = lookup_register(obj.name)
return f"_sped_level = {register['level']}"
Field Prefixing Strategy
Problem
With 800+ fields in NF-e alone, plus thousands of OCA modules, field name collisions are a real risk. Additionally, schemas evolve (3.0 → 4.0 → 5.0).
Solution
Each field gets a prefix combining schema name + version digits:
- NF-e v4.0:
nfe40_prefix →nfe40_vBC,nfe40_vICMS - CT-e v4.0:
cte40_prefix →cte40_vTPrest - MDF-e v3.0:
mdfe30_prefix →mdfe30_qNFe
Versioning Strategy
| Scenario | Example | Database Impact |
|---|---|---|
| Minor update (4.00 → 4.01) | Same nfe40_ prefix |
Fields updated in place, --update sufficient |
| Major update (3.0 → 4.0) | nfe30_ → nfe40_ prefix |
New fields/tables, data migration needed |
Automatic Prefix Generation
from xsdata_odoo import get_config
config = get_config()
config.schema = "nfe"
config.version = "40"
print(config.field_safe_prefix) # "nfe40_"
Custom Filters
Extend OdooFilters for specialized use cases:
from xsdata_odoo.filters import OdooFilters
from collections import OrderedDict
class MyCustomFilters(OdooFilters):
def registry_name(self, name="", parents=[], type_names=[]):
# Custom model naming logic
return f"my_module.{self.version}.{name.lower()}"
def _extract_field_attributes(self, parents, attr):
# Add custom field attributes
kwargs = super()._extract_field_attributes(parents, attr)
# Add custom metadata
kwargs["my_custom_attr"] = True
return kwargs
# Use custom filters
generator = OdooGenerator(config)
generator.filters = MyCustomFilters(
config,
all_simple_types=[],
all_complex_types=[],
registry_names={},
implicit_many2ones={},
)
generator.filters.register(generator.env)
Field Name Conflicts
Problem: XSD defines multiple groups with same field names (e.g., ICMS00.vBC, ICMS10.vBC).
Solution: Use XSDATA_SKIP to skip conflicting classes:
export XSDATA_SKIP="^ICMS.ICMS\d+|^ICMS.ICMSSN\d+"
Then implement business logic in your implementation module to handle the different ICMS groups.
Currency Fields
Problem: Monetary fields reference non-existent currency field.
Solution: Set XSDATA_CURRENCY_FIELD to match your Odoo module's currency field:
export XSDATA_CURRENCY_FIELD="company_currency_id" # or "currency_id", "brl_currency_id"
Language-Specific Text Processing
Problem: Field labels not extracted properly from XSD documentation.
Solution: Set XSDATA_LANG for stopword processing:
export XSDATA_LANG="portuguese" # or "english", "spanish", etc.
Links
License
MIT License - See LICENSE file for details
Copyright
2025 Akretion - Raphaël Valyi raphael.valyi@akretion.com
Metadata
Release files for xsdata-odoo 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xsdata_odoo-1.1.0.tar.gz | 27.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xsdata_odoo-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.1 kB
Release files / xsdata_odoo-1.1.0.tar.gz
| Download URL | xsdata_odoo-1.1.0.tar.gz |
|---|---|
| Size | 27.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f15420fc29956ff3fbba96f76836d3d420c69fdffeeec2720d96e0501b36588
|
|
BLAKE2b-256 checksum How to use checksums |
598d3ca082631a33b45ca94fb17b5a5c570785c6bd2134c28979c05c1be087df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 27, 2026.
Transparency logRelease files / xsdata_odoo-1.1.0-py3-none-any.whl
| Download URL | xsdata_odoo-1.1.0-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
829d61af9883aac910f859268a5c7e0ec6f1093f96f0d5956e08af22d103bc29
|
|
BLAKE2b-256 checksum How to use checksums |
2714abb43505fd434826943c048371b03a01a1fe72b4f25c9816b7187beb351e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 27, 2026.
Transparency log