Convert compiled Microsoft Dynamics 365 Business Central AL packages (.app) into DBML schemas.
Project description
al2dbml
al2dbml is a small Python CLI that converts a compiled Microsoft Dynamics 365 Business Central AL package (.app) into a DBML schema you can paste straight into dbdiagram.io or dbdocs.io. The pipeline reads SymbolReference.json from the .app archive (tolerating AL's 40-byte header), normalises tables, extensions, enums, and TableRelations, and emits one valid DBML document with Table, Ref, Enum, and TableGroup sections.
What's new in 0.3.3
- Enum items now carry their AL ordinal as a note — every enum value is rendered as
"Name" [note: '<n>']so you can read the integer-to-name mapping directly off the diagram. BC stores enum values as integers in SQL, so this is what you need when you seeType = 2in a row dump and want to know which entry it was without flipping back to the AL source.Approval Actionfrom Base Application, for example, deliberately starts at ordinal 1; you can see that gap in the diagram now.
What's new in 0.3.2
--statsis now fast — on Microsoft's Base Application it went from ~3–5 minutes to ~4 seconds. Two stacked fixes: when--statsis the only output requested, we skip the DBML render entirely (it's O(n²)); and we bypasspydbml.Database.add_reference's redundant duplicate-check on every ref we add (we already dedupe upstream by id pair). Generating to a file (-o) still pays the pydbml render cost — separate slice on the roadmap.
What's new in 0.3.1
al2dbml-validate FILE— second console script that parses a DBML file through pydbml and reports syntax errors with line/column. Exit code 0 on success, non-zero on parse error. For the authoritative check matching dbdiagram.io exactly, install@dbml/cli(npm i -g @dbml/cli) and rundbml2sql FILE --postgres.- Empty enum values fixed — AL sometimes encodes a default/blank enum slot as
"", which broke DBML's parser. Now silently substituted with" "(single space) so the slot still appears. - Self-referential refs dropped — some BC tables declare a
TableRelationfrom a field back to itself (e.g. Production Order.No. → Production Order.No.). Those are now skipped instead of being emitted as a meaninglessRef { T.f > T.f }.
What's new in 0.3.0
--include/--excludetable filters — carve out a slice of a large package by name pattern (essential for Microsoft's Base Application, which has 1,500+ tables).- Namespace-aware grouping —
TableGroups now default to the last segment of each table's AL namespace (soMicrosoft.Finance.GeneralLedger->GeneralLedger); switch back with--group-by wordor off with--group-by none. --statsflag + empty-output warning — quick post-run sanity check; codeunit-only extensions (like Sales and Inventory Forecast) now tell you "0 tables and 0 enums" up front instead of silently producing an empty file.- DBML provenance header — every output begins with
// Generated by al2dbml <version> from <Name> <Version> by <Publisher>and an// AppId:line, so you can tell a stray.dbmlapart from its sibling six months later.
Install
Python 3.10+ is required. The runtime depends only on click and pydbml.
Recommended: uv tool install
uv installs CLI tools into isolated environments and puts the entry point on your PATH, so al2dbml is available globally without touching your system Python.
uv tool install al2dbml
If you don't already have uv:
# Fedora / RHEL / CentOS
sudo dnf install uv
# macOS (Homebrew)
brew install uv
# Anywhere (standalone installer)
curl -LsSf https://astral.sh/uv/install.sh | sh
Upgrade later with uv tool upgrade al2dbml, uninstall with uv tool uninstall al2dbml.
Alternative: pipx
pipx install al2dbml
Alternative: plain pip
Works inside an activated virtualenv. On modern distros that mark system Python as externally-managed (PEP 668), prefer uv tool or pipx instead.
pip install al2dbml
Verify
al2dbml --version
al2dbml --help
Quickstart
al2dbml MyApp.app -o schema.dbml
Drop schema.dbml into https://dbdiagram.io. Without -o, the DBML is streamed to stdout so you can pipe it elsewhere.
al2dbml MyApp.app | less
Grouping
By default tables are bucketed into TableGroups by the last segment of their AL namespace (so Microsoft.Finance.GeneralLedger -> group GeneralLedger). Tables that have no namespace tag fall back to the first whitespace-separated word in their name (so Sales Header + Sales Line -> group Sales). Buckets smaller than two tables are dropped so single-table groups don't clutter the diagram.
Override the source with --group-by:
al2dbml MyApp.app --group-by namespace # default
al2dbml MyApp.app --group-by word # legacy first-word grouping
al2dbml MyApp.app --group-by none # no auto groups (only explicit --group rules apply)
# Auto grouping (default)
al2dbml MyApp.app -o schema.dbml
# Explicit rules; the value is NAME=PATTERN[,PATTERN...] and -g is repeatable
al2dbml MyApp.app -g "Documents=Sales*,Purch*" -g "Master=Customer,Vendor,Item"
# Disable grouping entirely
al2dbml MyApp.app --no-groups
# Keep singleton groups too
al2dbml MyApp.app --min-group-size 1
--no-auto-groups switches off the first-word fallback so only your explicit -g rules apply.
TableExtensions
Extensions are merged into their target tables by default. Use --no-merge-extensions to emit them as separate <Target> (Extension) tables instead.
Public Python API
from al2dbml import Generator, generate, GroupingConfig
# One-shot helper
dbml = generate("MyApp.app", output_path="schema.dbml")
# Or step-by-step for custom grouping
gen = Generator.from_app(
"MyApp.app",
grouping=GroupingConfig(rules={"Documents": ["Sales*", "Purch*"]}),
)
print(gen.dbml())
Limitations
- FlowFields are treated as regular fields — the underlying CalcFormula is not interpreted.
- Obsolete fields are emitted alongside active ones; no filtering by
ObsoleteState. - Multi-field primary keys are represented as multiple
[pk]flags rather than a composite index, matching DBML's single-PK convention. - Multi-column secondary keys are not yet emitted as DBML indexes; only single-column secondary keys are surfaced (as
[unique]on the column). - Cross-package references (table relations that point to a table outside the current
.app) are preserved as notes on the source column, since the target table is not present in the diagram. IF (...) ... ELSE IF (...) ... ELSE ...conditionalTableRelationexpressions are parsed into one DBMLRefper resolved branch, with each branch's condition recorded in the source column's note. Branches whose target table is missing from the current.appdegrade to notes only.- Render time scales quadratically with the table count inside the underlying
pydbmllibrary. Small/medium packages (up to a few hundred tables) finish in under a second. Microsoft's full Base Application (~1,500 tables) currently takes several minutes to render, even though parsing itself is fast. A custom DBML emitter is on the roadmap to remove this cliff.
Development
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q
.venv/bin/ruff check .
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 al2dbml-0.3.3.tar.gz.
File metadata
- Download URL: al2dbml-0.3.3.tar.gz
- Upload date:
- Size: 32.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0551bc3003b74c1aee83c2151f03a1afe54c0e31e34a5d3ed5565ee46000789f
|
|
| MD5 |
6cdb3e4cdf0392f195d4da56109bf901
|
|
| BLAKE2b-256 |
fc0e87f63e74c9fc854f412bd9f4f4288dc4de91794364209ece402290f9411a
|
Provenance
The following attestation bundles were made for al2dbml-0.3.3.tar.gz:
Publisher:
publish.yml on mykola-kharchenko/al2dbml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
al2dbml-0.3.3.tar.gz -
Subject digest:
0551bc3003b74c1aee83c2151f03a1afe54c0e31e34a5d3ed5565ee46000789f - Sigstore transparency entry: 1645363237
- Sigstore integration time:
-
Permalink:
mykola-kharchenko/al2dbml@686aca2b9f9dd62ff3c8d49b104c977c15901135 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/mykola-kharchenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@686aca2b9f9dd62ff3c8d49b104c977c15901135 -
Trigger Event:
push
-
Statement type:
File details
Details for the file al2dbml-0.3.3-py3-none-any.whl.
File metadata
- Download URL: al2dbml-0.3.3-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86d378e1502256cb3596b957d94fd01fbe5d51f8bd8b26b92223a9fc82610472
|
|
| MD5 |
c0a6ada908ee273a8aad16862f6ea1da
|
|
| BLAKE2b-256 |
7c11fd219b0dddb734625de365f4cc9402f16fce0c7649416315474a5a3b94aa
|
Provenance
The following attestation bundles were made for al2dbml-0.3.3-py3-none-any.whl:
Publisher:
publish.yml on mykola-kharchenko/al2dbml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
al2dbml-0.3.3-py3-none-any.whl -
Subject digest:
86d378e1502256cb3596b957d94fd01fbe5d51f8bd8b26b92223a9fc82610472 - Sigstore transparency entry: 1645363288
- Sigstore integration time:
-
Permalink:
mykola-kharchenko/al2dbml@686aca2b9f9dd62ff3c8d49b104c977c15901135 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/mykola-kharchenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@686aca2b9f9dd62ff3c8d49b104c977c15901135 -
Trigger Event:
push
-
Statement type: