Getting Started with pyA2L#

You’ll find the example code here.

Import an .a2l file to database#

from pya2l import import_a2l

session = import_a2l(
    "ASAP2_Demo_V161.a2l",
    # encoding="latin-1" is default; override if your file differs
    # progress_bar=False or loglevel="ERROR" to silence progress
)

If nothing went wrong, your working directory now contains a file named ASAP2_Demo_V161.a2ldb, which is simply a Sqlite3 database file.

Unlike other ASAP2 toolkits, you are not required to parse your .a2l files over and over again, which can be quite expensive.

Re-importing an existing file#

By default, re-importing a file whose .a2ldb already exists triggers a confirmation prompt instead of raising an OSError. To suppress the prompt and overwrite unconditionally, pass force_overwrite=True:

from pya2l import import_a2l

session = import_a2l("ASAP2_Demo_V161.a2l", force_overwrite=True)

The CLI flag -f / --force-overwrite provides the same behaviour:

a2ldb-imex -i ASAP2_Demo_V161.a2l -f

Open an existing .a2ldb database#

from pya2l import open_existing

session = open_existing("ASAP2_Demo_V161")   # No need to specify extension .a2ldb

You may have noticed, that in both cases the return value is stored in an object named session:

Enter SQLAlchemy!

SQLAlchemy offers, amongst other things, a powerful expression language.

Running a first database query#

from pya2l import open_existing
import pya2l.model as model

session = open_existing("ASAP2_Demo_V161")
measurements = session.query(model.Measurement).order_by(model.Measurement.name).all()
for m in measurements:
    print(f"{m.name:48} {m.datatype:12} 0x{m.ecu_address.address:08x}")

Yields the following output:

ASAM.M.ARRAY_SIZE_16.UBYTE.IDENTICAL             UBYTE        0x00013a30
ASAM.M.MATRIX_DIM_16_1_1.UBYTE.IDENTICAL         UBYTE        0x00013a30
ASAM.M.MATRIX_DIM_8_2_1.UBYTE.IDENTICAL          UBYTE        0x00013a30
ASAM.M.MATRIX_DIM_8_4_2.UBYTE.IDENTICAL          UBYTE        0x00013a30
ASAM.M.SCALAR.FLOAT32.IDENTICAL                  FLOAT32_IEEE 0x00013a10
ASAM.M.SCALAR.FLOAT64.IDENTICAL                  FLOAT64_IEEE 0x00013a14
ASAM.M.SCALAR.SBYTE.IDENTICAL                    SBYTE        0x00013a01
ASAM.M.SCALAR.SBYTE.LINEAR_MUL_2                 SBYTE        0x00013a01
ASAM.M.SCALAR.SLONG.IDENTICAL                    SLONG        0x00013a0c
ASAM.M.SCALAR.SWORD.IDENTICAL                    SWORD        0x00013a04
ASAM.M.SCALAR.UBYTE.FORM_X_PLUS_4                UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.IDENTICAL                    UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_INTP_DEFAULT_VALUE       UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_INTP_NO_DEFAULT_VALUE    UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_NOINTP_DEFAULT_VALUE     UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_NOINTP_NO_DEFAULT_VALUE  UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_VERB_DEFAULT_VALUE       UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.TAB_VERB_NO_DEFAULT_VALUE    UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.VTAB_RANGE_DEFAULT_VALUE     UBYTE        0x00013a00
ASAM.M.SCALAR.UBYTE.VTAB_RANGE_NO_DEFAULT_VALUE  UBYTE        0x00013a00
ASAM.M.SCALAR.ULONG.IDENTICAL                    ULONG        0x00013a08
ASAM.M.SCALAR.UWORD.IDENTICAL                    UWORD        0x00013a02
ASAM.M.SCALAR.UWORD.IDENTICAL.BITMASK_0008       UWORD        0x00013a20
ASAM.M.SCALAR.UWORD.IDENTICAL.BITMASK_0FF0       UWORD        0x00013a20
ASAM.M.VIRTUAL.SCALAR.SWORD.PHYSICAL             SWORD        0x00000000

The classes describing an .a2ldb database live in pya2l.model, they are required to query, modify, and add model instances.

Using the Inspect API#

The inspect API provides high-level, read-only wrappers with automatic conversion and caching:

from pya2l import open_existing
from pya2l.api.inspect import Project, Measurement, Characteristic

session = open_existing("ASAP2_Demo_V161")

# Navigate the project hierarchy
project = Project(session)
module = project.module[0]
print(f"Project: {project.name}")
print(f"Module:  {module.name}")

# Get a measurement by name
m = Measurement.get(session, "ASAM.M.SCALAR.UBYTE.IDENTICAL")
print(f"Name:      {m.name}")
print(f"Data type: {m.datatype}")
print(f"Address:   0x{m.ecuAddress:08X}")
print(f"Limits:    [{m.lowerLimit}, {m.upperLimit}]")
print(f"CompuMethod: {m.compuMethod.conversionType}")

# Convert a raw ECU value to physical
raw_value = 42
phys = m.compuMethod.int_to_physical(raw_value)
print(f"  {raw_value} raw → {phys} physical")

# Query all FLOAT32 measurements
float_meas = list(module.measurement.query(
    lambda row: row.datatype == "FLOAT32_IEEE"
))
print(f"\nFLOAT32 measurements: {len(float_meas)}")
for fm in float_meas:
    print(f"  {fm.name}")

# Query characteristics by type
maps = list(module.characteristic.query(
    lambda row: row.type == "MAP"
))
print(f"\nMAP characteristics: {len(maps)}")
for c in maps:
    print(f"  {c.name}: {c.num_axes} axes, shape={c.fnc_np_shape}")

Working with IF_DATA#

IF_DATA sections carry vendor-specific protocol information (XCP, CCP, …). The if_data attribute on every inspect object returns an IfData instance with parsed and raw data:

from pya2l import open_create
from pya2l.api.inspect import Module

session = open_create("xcp_demo_autodetect.a2l")
module = Module(session)

# Access the IfData dataclass
ifd = module.if_data

# Parsed structure (list of dicts)
for block in ifd.if_data_parsed:
    print(block)

# Quick key look-up via flatmap
for key in ifd.flatmap:
    print(f"  {key}: {len(ifd.flatmap[key])} occurrence(s)")

# Raw text for debugging
for raw in ifd.if_data_raw:
    print(raw.raw[:200], "...")

See Working with IF_DATA for a comprehensive guide.

Validate and export#

Basic validation:

from pya2l import open_existing
from pya2l.api.validate import Validator

session = open_existing("ASAP2_Demo_V161")
for msg in Validator(session)():
    print(msg.type.name, msg.category.name, msg.diag_code.name, "-", msg.text)

Export back to text:

from pya2l import export_a2l

export_a2l("ASAP2_Demo_V161", "exported.a2l")

See HOW‑TOs for Excel export and other short recipes.

The test-suite found here is a good starting point for further experimentations, because it touches virtually every A2L element/attribute.

Next steps#

Migrating from pya2l.DB#

The DB wrapper class is deprecated and will be removed in a future release. Replace each call pattern as shown below — the behaviour is identical:

Old (deprecated)

New (preferred)

DB().import_a2l("file.a2l")

import_a2l("file.a2l")

DB().open_existing("file")

open_existing("file")

DB().open_create("file.a2l")

open_create("file.a2l")

DB.import_a2l(db, "file.a2l")

import_a2l("file.a2l")

All keyword arguments (encoding, loglevel, local, progress_bar, force_overwrite) are available on the module-level functions.

# Before
from pya2l import DB
session = DB().import_a2l("my.a2l", loglevel="ERROR")

# After
from pya2l import import_a2l
session = import_a2l("my.a2l", loglevel="ERROR")