Skip to main content
Edison

Edison

An EDIFACT parser with a real syntax tree.

Build Version Python versions

Edison is a Python library for parsing EDIFACT messages. Instead of treating a message as a flat stream of segments, it models valid messages as a tree. You define the schema once, then parse, validate, or pretty-print messages against that structure.

Highlights

  • Define EDIFACT syntax trees directly in Python.
  • Segments can define groups of child segments, which can be conditional, mandatory, or repeatable.
  • Attach parsing callbacks to the segments you care about.
  • Select schemas by message version.
  • Handle escaped EDIFACT separators and EBCDIC control characters.
  • Properly tokenize segments with specified lengths like BLB
  • Export transition graphs with pydot.

Installation

Edison requires Python 3.12 or newer.

pip install edison-parser

Quick start

Subclass BaseParser, define one or more schemas, and attach callback method names to the schema nodes you want to process.

from edison import BaseParser


class PIZZAQParser(BaseParser):
    schemas = {
        (0, 0): [
            ["UNH"],
            ["CVR"],
            ["REF"],
            [
                "PIZ",
                [
                    ["TYP", [], "parse_typ"],
                    ["REF", [], "parse_ref"],
                ],
            ],
            ["UNT"],
        ],
    }

    def __init__(self):
        self.summary = {}
        self.pizza = None

    def parse_typ(self, segment: str):
        self.pizza = segment[4:]

    def parse_ref(self, segment: str):
        refs = segment[4:].split("*")
        self.summary[self.pizza] = len(refs)


edi = """
UNH+1+PIZZAQ:1:1:1A+00012480825035'
CVR+USD'
REF+LUIS'
PIZ+1'
TYP+MARGARITA'
REF+1*2'
PIZ+2'
TYP+PESTO'
REF+3'
UNT+19+1'
"""

# Create the parser and parse this EDIFACT message.
# The `summary` attribute is populated by the callbacks.
parser = PIZZAQParser()
parser.parse(edi)
parser.summary  # {"MARGARITA": 2, "PESTO": 1}

# Validate and pretty-print the message.
PIZZAQParser.validate(edi)  # True/False
PIZZAQParser.show(edi)  # to print

Here is the output of PIZZAQParser.show(edi).

Edison

Tree-based structure.

Schema format

A schema is a dictionary keyed by the minimum supported version:

schemas = {
    (0, 0): [
        ["UNH"],
        ["BGM"],
        ["UNT"],
    ],
}

Each segment node accepts these positional fields:

["TAG", children, action, repetition, flag]
Field Default Meaning
TAG required Three-character EDIFACT segment tag.
children [] Nested segment group.
action None Parser method name called with the raw segment.
repetition 99999 1 means single-use; any higher value means repeatable.
flag "C" "C" for conditional, "M" for mandatory.

Main API

API Use
parser.parse(edi, version=None) Walk an EDI message and call matching segment actions.
Parser.validate(edi, version=None) Return True when all segments follow the schema transitions.
Parser.show(edi, version=None, colorize=True) Return a formatted, indented representation of an EDI message.
Parser.build_dot_object(version=None, include_up_links=True) Build a pydot.Dot transition graph.
extract_envelope_headers(edi) Quickly return the UNB and UNH envelope segments.
extract_envelope_footers(edi) Quickly return the UNT and UNZ envelope segments.
escape(data) / unescape(data) Escape or unescape EDIFACT separators: +, :, *, '.
normalize_ebcdic_separators(edi) Convert EBCDIC EDIFACT separators to ASCII safely.

Advanced usage

Versioned syntax trees

Define multiple syntax trees when the same message evolves across versions. The parser selects the highest schema version less than or equal to the version argument, while parsing callbacks can stay the same.

from edison import BaseParser


class RouteParser(BaseParser):
    schemas = {
        (0, 0): [
            ["UNH"],
            ["ORG", [], "parse_origin"],
            ["DST", [], "parse_destination"],
            ["UNT"],
        ],
        (2, 0): [
            ["UNH"],
            [
                "LEG",
                [
                    ["ORG", [], "parse_origin"],
                    ["DST", [], "parse_destination"],
                ],
            ],
            ["UNT"],
        ],
    }

    def __init__(self):
        self.route = {}

    def parse_origin(self, segment: str):
        self.route["origin"] = segment[4:]

    def parse_destination(self, segment: str):
        self.route["destination"] = segment[4:]


legacy = RouteParser()
legacy.parse("UNH'ORG+PAR'DST+NCE'UNT'", version=(1, 0))

modern = RouteParser()
modern.parse("UNH'LEG+1'ORG+PAR'DST+NCE'UNT'", version=(2, 0))

assert legacy.route == modern.route == {
    "origin": "PAR",
    "destination": "NCE",
}
assert RouteParser.validate("UNH'ORG+PAR'DST+NCE'UNT'", version=(1, 0))
assert RouteParser.validate("UNH'LEG+1'ORG+PAR'DST+NCE'UNT'", version=(2, 0))

When version is omitted, incomplete, or newer than all known schemas, Edison uses the latest schema. If a requested version is older than the first schema, find_transitions() raises ValueError.

Mandatory and repeatable segments

repetition controls whether the transition graph can come back to the same node:

  • 1 means the node can be used once in a given sequence.
  • Any value greater than 1 means the node is repeatable. The transition graph only distinguishes 1 from "repeatable"; it does not count exact occurrence limits such as 2 versus 99999.

Mandatory nodes constrain valid paths through the tree. Once a mandatory node is next in a sequence, the parser cannot skip over it to reach a later sibling, a parent, or a child that would bypass it. A mandatory child does not make its parent mandatory by itself: if the whole group is required, mark the group node as "M" too.

from edison import BaseParser


class OrderParser(BaseParser):
    schemas = {
        (0, 0): [
            ["UNH"],
            [
                "ITM",
                [
                    ["DSC"],
                    ["QTY", [], "parse_quantity", 1, "M"],
                    ["PRI"],
                ],
                None,
                99999,
                "M",
            ],
            ["UNT"],
        ],
    }

    def __init__(self):
        self.quantities = []

    def parse_quantity(self, segment: str):
        self.quantities.append(segment[4:])


assert OrderParser.validate("UNH'ITM'DSC'QTY+2'PRI'ITM'QTY+1'UNT'")
assert not OrderParser.validate("UNH'UNT'")  # ITM is mandatory.
assert not OrderParser.validate("UNH'ITM'PRI'UNT'")  # QTY is mandatory.
assert not OrderParser.validate("UNH'ITM'QTY+2'QTY+3'UNT'")  # QTY repeats once.
Shared headers, footers, and parser hooks

Use header and footer when several schemas share envelope segments. They are automatically prepended and appended to every schema at class compilation time.

from edison import BaseParser


class EnvelopeParser(BaseParser):
    header = [["UNH", [], "on_segment"]]
    footer = [["UNT", [], "on_segment"]]
    schemas = {(0, 0): [["BGM", [], "on_segment"]]}

    def __init__(self):
        self.segments = []

    def set_up(self):
        self.segments.clear()

    def on_segment(self, segment: str):
        self.segments.append(segment)

    def tear_down(self):
        self.count = len(self.segments)

set_up() runs before parsing starts, and tear_down() runs after parsing finishes.

Transition graphs

Internally, Edison builds a directed graph of transitions between segments, when the parser class is created.
Each node is a specific segment, and each edge is a valid transition to the next segment.
With this, parsing is just a walk through the graph, and validation is checking if the path exists.

You can visualize the transition graph with build_dot_object(), which returns a pydot.Dot object.
pydot is installed with Edison. To write PNG files, make sure Graphviz is available on your system.

from edison import BaseParser


class Parser(BaseParser):
    schemas = {
        (0, 0): [
            ["UNH"],
            [
                "BGM",
                [
                    ["DTM"],
                ],
            ],
            ["UNT"],
        ],
    }


dot = Parser.build_dot_object()
dot.write_png("transitions.png")

Here is the resulting graph for the above schema.

pydot

You can pass the same version selector used by parsing, and hide upward links for a simpler tree-like graph:

dot = Parser.build_dot_object(version=(13, 2), include_up_links=False)
dot.write_png("transitions-v13.png")

Because build_dot_object() returns the underlying pydot.Dot object, you can customize it before writing.

Escaping and EBCDIC EDIFACT separators

Edison understands escaped EDIFACT separators and can normalize EBCDIC separator characters often found in binary payloads.

from edison import normalize_ebcdic_separators, escape, unescape


assert escape("REC+") == r"REC\+"
assert unescape(r"REC\+") == "REC+"
assert normalize_ebcdic_separators("UNB\x1dSENDER\x1fREC") == "UNB+SENDER:REC"

The segment tokenizer also handles escaped quotes, so content such as IFT+1+AIRLINE\'SERVER' stays in a single EDIFACT segment.

EDIFACT envelope extraction

For the common case of reading EDIFACT envelope boundaries, use extract_envelope_headers() to lazily return the first UNB/UNH segments or extract_envelope_footers() to lazily return the last UNT/UNZ segments:

from edison import extract_envelope_footers, extract_envelope_headers

edi = "UNB+SENDER+RECEIVER'UNH+1+PIZZAQ:1:1:1A'BGM+1'UNT+3+1'UNZ+1+1'"
headers = extract_envelope_headers(edi)
footers = extract_envelope_footers(edi)

assert headers.unb == "UNB+SENDER+RECEIVER"
assert headers.unh == "UNH+1+PIZZAQ:1:1:1A"
assert footers.unt == "UNT+3+1"
assert footers.unz == "UNZ+1+1"

Development

Local commands

This repository uses uv, tox, pytest, ruff, black, ty, and pre-commit hooks.

uv tool install tox --with tox-uv
tox -e py

Build distributions with:

uv build --sdist --wheel -o dist/

Run the full pre-commit suite with:

uv tool install prek==0.4.10
prek run --all-files

CI runs tests through tox on CPython 3.12 and 3.14 across Linux, Windows, and macOS, then builds source and wheel distributions.

Metadata

Release files for edison-parser 0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for edison-parser 0.4
File Size Uploaded Provenance
edison_parser-0.4.tar.gz 21.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for edison-parser 0.4
File Interpreter ABI Platform
edison_parser-0.4-py3-none-any.whl Python 3 none any Details

Total release size: 38.4 kB

Release files / edison_parser-0.4.tar.gz

Download URL edison_parser-0.4.tar.gz
Size 21.6 kB
Tags Source
SHA-256 checksum
How to use checksums
56c1ce3e481f0378ba91a9ec3f7d294ca4d931de925d76e527f12571f5b9c9fe
BLAKE2b-256 checksum
How to use checksums
0ac01957017b18ae0464d009aea818d60a22e4b054ae3ce17b34b47a97cdc9c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 21, 2026.

Transparency log

Release files / edison_parser-0.4-py3-none-any.whl

Download URL edison_parser-0.4-py3-none-any.whl
Size 16.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c25c64fbf647a887100ac0355139282e3da646841bbf3b50cdb2e0d0fdf622e8
BLAKE2b-256 checksum
How to use checksums
d7528bd1d55ad1ebcd16e7fd749ba3f67e21b1bc46cf577d34b500aa3c3a5aef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 21, 2026.

Transparency log

Security for edison-parser 0.4

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for edison_parser-0.4.tar.gz

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 Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page