axilog (Python)
Python bindings (PyO3) for axilog's Rust GW2 arcdps WvW
parsing core. Every export is a thin wrapper around the same
decode → resolve → analyze → build_report pipeline the CLI (axilog-cli)
and the Node SDK (crates/axilog-node) drive — see src/lib.rs's module
doc for the exact FFI contract (no Rust panic ever crosses the boundary;
every fallible step becomes a Python OSError/ValueError carrying the
underlying Rust error text).
Build
cd crates/axilog-py
python3 -m venv .venv # gitignored; may live anywhere
.venv/bin/pip install maturin
.venv/bin/maturin develop --release # builds the extension into .venv
maturin develop compiles this crate to a native extension module and
installs it editable into whichever interpreter you point it at. Re-run it
after changing src/lib.rs or axilog.pyi.
To build into a venv outside the crate — worth doing on a machine where the
repo lives on a small or slow volume — set VIRTUAL_ENV so maturin targets
it instead of guessing:
VIRTUAL_ENV=/path/to/venv /path/to/venv/bin/maturin develop --release
Usage
parse_file/parse_bytes return the native 1.0 container (schema: "1.0"), as a plain dict of dicts and lists. Its six top-level keys are
always present:
| Key | What it is |
|---|---|
axilog |
{ schema, version, generated_from } — format contract version, the binary that produced the document, and the input log's file name |
encounter |
One object: kind, map, duration_ms, teams[], markers[], objectives[], recorded_by, … |
entities |
The single roster — every tracked agent, identity only, no statistics |
catalogs |
skills/buffs/damage_mods/minions metadata, referenced by id from blocks |
blocks |
Id-keyed statistics maps — damage, defenses, boons, support, … |
coverage |
Per-block status: present, empty, not_computed or unsupported |
A seventh key, warnings, appears only when non-empty.
There is no top-level players[] and no schema_version. The roster is
entities[], and every per-entity statistic is a blocks.<name>.by_entity
map keyed by entities[].id — as a string, since JSON object keys are
strings while entities[].id is an int.
import axilog
report = axilog.parse_file("./fight.zevtc")
print(report["axilog"])
# {'generated_from': 'wvw-small.anon.zevtc', 'schema': '1.0', 'version': '0.3.2'}
print(report["encounter"]["map"], report["encounter"]["duration_ms"])
# Green Alpine Borderlands 49285
# The roster is filtered by `role`, not by a separate list. WvW logs carry
# four: "squad", "friendly_player", "enemy_player" and "npc".
squad = [e for e in report["entities"] if e["role"] == "squad"]
print(len(squad), "squad of", len(report["entities"]), "entities")
# 38 squad of 122 entities
damage = report["blocks"]["damage"]
print(damage["squad"]["total"])
# 2138414
top = max(squad, key=lambda e: damage["by_entity"][str(e["id"])]["total"])
row = damage["by_entity"][str(top["id"])]
print(top["account"], top["profession"], row["total"], round(row["dps"], 1))
# :Anon104.4848 Engineer 205612 4171.9
# Buffs are id-keyed too; names live once in the catalog, never repeated
# per entity.
boons = report["blocks"]["boons"]["by_entity"][str(top["id"])]
print(report["catalogs"]["buffs"]["1187"]["name"], round(boons["1187"]["uptime_pct"], 1))
# Quickness 6.0
# Same pipeline, from an already-read buffer.
with open("./fight.zevtc", "rb") as f:
report_from_bytes = axilog.parse_bytes(f.read())
Check coverage before reading a block
coverage is how a consumer tells "the flag was off" apart from "the log
had nothing to say", which is otherwise indistinguishable — both look like
a missing block.
report = axilog.parse_file("./fight.zevtc")
print(report["coverage"]["rotation"], report["coverage"]["damage_mods"])
# empty not_computed
| Value | Meaning | What to do |
|---|---|---|
present |
Computed, has rows | Read it |
empty |
Computed, genuinely nothing to report | Treat as zero rows, not an error. The block is still carried in blocks |
not_computed |
The gate for it was off | Re-parse with the gate on. Do not report this as "the log had none" |
unsupported |
This log cannot produce it — its era, encounter kind, or recorder | Do not retry with a gate; see docs/EI-PARITY.md |
Opt-in gates
Seven keyword arguments, all False by default:
| Keyword | Adds |
|---|---|
skill_damage=True |
blocks.damage.by_entity[].by_skill / .by_skill_taken — per-skill outgoing and incoming splits |
timeseries=True |
blocks.series per-entity channels, and the buff stack timelines in blocks.boons/blocks.conditions |
rotation=True |
blocks.rotation.by_entity[].{casts, aftercast} |
replay=True |
blocks.replay.tracks — position samples. The down/dead intervals under blocks.replay.by_entity are computed on every parse; only the positions are gated |
missiles=True |
blocks.missiles — projectile fired/hit/denied counts |
modifiers=True |
blocks.damage_mods and catalogs.damage_mods |
everything=True |
Every analysis pass this version knows about |
report = axilog.parse_file("./fight.zevtc", skill_damage=True, rotation=True)
row = report["blocks"]["damage"]["by_entity"]["1"]
print(sorted(row.keys()))
# ['breakbar_damage_dealt', 'by_skill', 'by_skill_taken', 'downs_dealt',
# 'dps', 'kills_dealt', 'per_target', 'taken', 'total']
Prefer everything=True over enumerating gates if you want complete
documents: it is defined as "everything that exists in this version", so a
pass added by a later release is included automatically. The first axibridge
cutover audit found 30 blank fields caused by exactly the opposite — a
consumer's option list drifting from the parser's. It is a union with the
individual gates, never an override.
Gates are not free, and they are not uniformly the same kind of cost.
skill_damage, timeseries and rotation only control whether an
already-computed result is serialized; replay and modifiers gate the
computation itself. Measured on the committed fixture
(fixtures/wvw-small.anon.zevtc — 38 squad entities, 49.3 s — release
build, compact JSON, 461,086-byte baseline at 0.07 s):
| Gate | Bytes | Wall |
|---|---|---|
| (none) | 461,086 | 0.07 s |
missiles |
466,565 | 0.07 s |
rotation |
708,367 | 0.07 s |
modifiers |
616,591 | 0.08 s |
replay |
1,679,284 | 0.12 s |
skill_damage |
3,338,819 | 0.08 s |
timeseries |
3,563,403 | 0.09 s |
everything |
8,067,599 | 0.16 s |
That fixture is a 49-second skirmish. The per-skill and per-second blocks
are combinatorial — entity × target × skill and entity × target × second —
so on a real multi-minute zerg log they grow far faster than the table
above suggests. See docs/BENCHMARKS.md for real-log numbers.
The other two functions
# Elite Insights-compatibility JSON (axilog_ei::to_ei_json) — the shape
# axibridge-style consumers read (players[].account, dpsAll[0].damage,
# support[0].condiCleanse, buffUptimes[], targets[].enemyPlayer,
# wvWMapData.{red,blue,green}TeamID). Returned as `Dict[str, Any]`; unlike
# the native container this one does still have a flat `players[]`, because
# EI's shape does. Gates are keyword-only and mirror `parse_file`'s, except
# `missiles`, which is accepted for signature parity and has no effect (EI's
# shape has no comparable field).
ei = axilog.parse_file_ei("./fight.zevtc")
# Rewrite every player's character/account name in a .zevtc to a
# deterministic Anon<N> placeholder (does not mutate metrics — safe for
# producing PII-safe fixtures). Returns the number of player agents
# rewritten.
rewritten = axilog.anonymize_file("./fight.zevtc", "./fight.anon.zevtc")
All four functions raise a real Python exception (never a raw Rust
panic) on failure: OSError for a file that can't be read/written,
ValueError for bytes that don't decode/parse as an arcdps log.
Further reference
docs/NATIVE-FORMAT.md in the repo root is the authoritative field-level
reference for the 1.0 container — the entities[].role rules, the catalog
join semantics, the series RLE envelope, the two replay halves, and the 1.x
compatibility rules a consumer can rely on. This README is the SDK surface;
that document is the format.
Types
This crate has no python-source tree — the compiled extension module
is the package (see src/lib.rs's #[pymodule] fn axilog). maturin
(>=1.5) auto-detects a <module_name>.pyi stub and a py.typed marker
sitting next to Cargo.toml/pyproject.toml at the crate root and
bundles both into the wheel as axilog/__init__.pyi and
axilog/py.typed, alongside the compiled axilog/axilog.abi3.so and the
auto-generated axilog/__init__.py — no [tool.maturin] include/
package-data configuration needed (verified directly against this crate:
maturin build logs Found type stub file at axilog.pyi, and the built
wheel contains axilog/__init__.pyi + axilog/py.typed).
axilog.pyi hand-transcribes the serialized shape into TypedDicts, with
Optional/omitted-key semantics matching each field's serde
Option/skip_serializing_if attributes exactly (see the top of
axilog.pyi for the precise rule). The 1.0 container's types live under the
# native format 1.0 heading — ReportV1, AxilogMeta, EncounterOutV1,
EntityOut, Catalogs, Coverage, SeriesOut and friends, transcribed
from crates/axilog-schema/src/v1/. It mirrors
crates/axilog-node/types.d.ts's equivalent transcription for the Node SDK;
keep the two in step.
parse_file/parse_bytes are typed to return ReportV1. parse_file_ei's
materially different, larger EI-JSON shape is intentionally left
Dict[str, Any] — typing it faithfully is out of scope, mirroring
types.d.ts's same call for parseFileEi.
Tests
tests/test_sdk.py (stdlib unittest only, no pytest/other
dependencies) covers, against the committed PII-safe fixture
fixtures/wvw-small.anon.zevtc (repo-root relative, resolved from the
test file's own location so it runs correctly from any cwd):
parse_file: the six-key container shape, player count, squad damage total, and the four support-stat squad sums — all pinned to this fixture's exact calibrated values (seecrates/axilog-core/tests/golden.rs/support_golden.rsfor how those numbers were derived against a real dps.report EI export). One boon uptime value is cross-checked live againstfixtures/wvw-small.ei.json'splayers[].boons["1187"].uptime, rather than only pinned to a hardcoded literal.parse_bytesequalsparse_file.replay,skill_damage,timeseries,missilesandmodifiers: absent by default, present and correctly shaped when requested, and absent again when passed explicitlyFalse.replay's test additionally pins the split halves — intervals always on,tracksgated — and asserts every track id joins to anentities[]row.rotationhas no such test — in either SDK. Its shape and size are guarded schema-side (crates/axilog-schema/tests/v1_shape.rs's keyset golden andv1_size.rs, both of which drive every gate), but there is no absent-by-default/present-when-requested assertion for it at the SDK boundary. Known gap.everything=True: every gate computes, leaving nothingnot_computedincoverage.parse_file_ei: the specific keys axibridge-style consumers read (players[].account,dpsAll[0].damage,support[0].condiCleanse, non-emptybuffUptimes,targets[].enemyPlayerbooleans,wvWMapData's three team ids).anonymize_fileround-trip: writes to a tempfile directory, re-parses, checks entity count/metrics are unchanged (anonymization only rewrites names).- Error paths: a missing file raises
OSError; corrupt bytes raiseValueError— both with a non-empty message. - CLI parity: shells out to the already-built CLI
(
cargo build -p axilog-cli; run that first iftarget/debug/axilogis missing) and assertsaxilog parse <fixture> --format json,json.loaded, exactly equalsparse_file's return value — with a small first-N-differing-paths diff helper so a future divergence prints something readable instead of two giant dicts.
Canonical invocation, using the interpreter the module was built into:
.venv/bin/python -m unittest discover -s tests
Equivalently from the repo root, crates/axilog-py/.venv/bin/python -m unittest discover -s crates/axilog-py/tests. Substitute your own venv path
if you built outside the crate.
Metadata
Release files for axilog 1.0.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 | |
|---|---|---|---|
| axilog-1.0.0.tar.gz | 1.3 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| axilog-1.0.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| axilog-1.0.0-cp39-abi3-manylinux_2_34_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.34+ x86-64 | Details |
| axilog-1.0.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| axilog-1.0.0-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 7.0 MB
Release files / axilog-1.0.0.tar.gz
| Download URL | axilog-1.0.0.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
493b671806d8cec4b0f4dd3bbe9c3cc9249de075be348995e9cf4c443ab8fdd4
|
|
BLAKE2b-256 checksum How to use checksums |
e02794440a0219e5800d0dc2cee593d49458fce464e4c7cc7268527f8fbd2015
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 18, 2026.
Transparency logRelease files / axilog-1.0.0-cp39-abi3-win_amd64.whl
| Download URL | axilog-1.0.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
ad4acbb3be74b60bbb1d965af97b97fb926bb7c24181dc3e26425426cb96cc37
|
|
BLAKE2b-256 checksum How to use checksums |
0807083ed8630106ef23371e5da242f52508665b5e2768a330eedf2c695c1e92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 18, 2026.
Transparency logRelease files / axilog-1.0.0-cp39-abi3-manylinux_2_34_x86_64.whl
| Download URL | axilog-1.0.0-cp39-abi3-manylinux_2_34_x86_64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.9 Linux glibc 2.34+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3ab1cc521a2944f4d1ca961ea75b9ad1671853de504d626a350d013bd867efe9
|
|
BLAKE2b-256 checksum How to use checksums |
4c6924a8c072bb99a8eb2a854697aaa18a4446c8c27d99eb7651aa33a49477ba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 18, 2026.
Transparency logRelease files / axilog-1.0.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | axilog-1.0.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d1b1de69b438026f1068bdad12ba6127822ab001c5e027cc46dadf78742e26dc
|
|
BLAKE2b-256 checksum How to use checksums |
e888edea2ea811f982bf97ef2c9a776a0162e31cb1152e011eaa358850d44240
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 18, 2026.
Transparency logRelease files / axilog-1.0.0-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | axilog-1.0.0-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
8ba721f5a25c29fb3b42f0cf725379c4899a35c3b4b601b4836ed3484a59954b
|
|
BLAKE2b-256 checksum How to use checksums |
3a4ef2a0346b34a3c98e669fb83c06e2202364b46cd2ad13ac319f9b9850be9c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 18, 2026.
Transparency log