from awpy import Demo
demo = Demo("match.dem")
demo.load("kills", "damages", "shots")
print(demo.header["map_name"])
print(demo.stats.select("name", "kills", "deaths", "assists", "adr", "kast"))
print(demo.snapshots(seconds=1.0))Awpy parses Counter-Strike 2 demo files in Rust. Its Python API returns typed Polars DataFrames. Use Polars to filter and join results, write Apache Parquet files, or convert tables to pandas. Use the plotting extra to draw CS2 data with matplotlib.
Awpy and the Deadlock parser Boon use the shared pbdems2 core. Awpy adds CS2 protobufs, field decoders, and name tables. See the PBDEMS2 format guide for the shared format.
- Why Awpy?
- Install Awpy
- Parse CS2 demos with Awpy
- Select Awpy datasets
- Find Awpy workspace crates
- Read the Awpy documentation
- Build and test Awpy
- Awpy license
- Read match events, player state, rounds, and statistics as DataFrames.
- Select the datasets, ticks, or entity properties that you need.
- Reconstruct rounds from game state, including demos without round events.
- Use the Python API, Rust library, or command-line interface.
Use uv:
uv add awpyYou can also use pip:
pip install awpyPython 3.11 or later is required. Awpy requires Polars 2.x. Releases include wheels for Python 3.11–3.15 on Linux, macOS, and Windows. Installing a wheel does not require a Rust compiler.
Installing the Python package also installs the awpy command for demo
inspection and map-data management:
awpy info match.dem
awpy kills match.dem --limit 20
awpy stats match.dem --json
awpy get # download the latest map-data release(For parser-internals debugging there is also a Rust developer binary,
awpy-dev: cargo install --path crates/awpy-dev.)
Add the Awpy Rust crate:
[dependencies]
awpy = "3.1"Use the Python example above to read a scoreboard and sample player state. See the getting started guide for installation, Polars queries, and map assets.
Pass files in recording order. Strings and Path objects can be mixed:
from awpy import Demo
demo = Demo(["match-p1.dem", "match-p2.dem"])
print(demo.stats)
state = demo.snapshots(part=1, ticks=5333)Multipart tables keep each file's ticks and add a zero-based part column.
stats and players combine Steam IDs. Explicit tick queries require part.
Awpy checks map, build, tick rate, round continuity, and recording boundaries.
Splits during live play are not supported. See the
multipart guide.
# File header and playback info
awpy info match.dem
# Per-round table (winner, reason, timings)
awpy rounds match.dem
# Kills, with players resolved
awpy kills match.dem --limit 20
# Game events (--summary for counts, a name to dump one event)
awpy events match.dem --summary
awpy events match.dem player_ping --json
# All available commands
awpy --helpuse std::path::Path;
use awpy::Parser;
let parser = Parser::from_file(Path::new("match.dem"))?;
println!("map: {:?}", parser.file_header()?.map_name);
// Game events
for event in parser.events(None)? {
if event.name == "player_death" {
println!("[{}] {:?}", event.tick, event.keys);
}
}
// Entities, tick by tick
parser.run_to_end(|ctx| {
for (_, entity) in ctx.entities.iter() {
if entity.active && entity.class_name == "CCSPlayerPawn" {
// read entity fields
}
}
})?;
# Ok::<(), awpy::Error>(())Dataset properties return cached DataFrames. Use demo.load("kills", "damages", "shots") to collect compatible datasets in one pass. See the dataset reference for columns and selection rules.
| Dataset | Description |
|---|---|
rounds |
Round timings, winners, and end reasons from game state. |
kills |
Kills, participants, positions, and trade flags. |
damages |
Damage events, participants, and health and armor changes. |
bomb |
Bomb actions (pickup, drop, plant start/finish, defuse) with the acting player, position, and bombsite. |
grenades |
Per-tick thrown-grenade trajectories (smoke, HE, flashbang, molotov, decoy). |
fires |
One row per burning inferno (molotov / incendiary), with position, thrower, and [start_tick, end_tick]. |
smokes |
One row per deployed smoke cloud, with position, thrower, and [start_tick, end_tick]. |
shots |
Every weapon_fire event with the shooter's state and active-weapon state (clip, inaccuracy, scoped). |
blinds |
One row per flash event: the thrower, the blinded player (both resolved), and the blind duration. |
item_events |
One row per weapon-item transaction — purchase, pickup, or drop — with the acting player, item, position, and (for buys) cost. |
stats |
Player statistics, including KAST, ADR, trades, clutches, and utility. |
round_economy |
One row per (round, side): team equipment value at freeze end and its buy-type classification (eco / force / full). |
players |
The roster: one row per player seen in the demo (Steam id, name, last side, and team/organization name). |
chat |
Recorded chat messages and channels. |
convars |
Server console variables (mp_maxrounds, ...) as a dict[str, str]. |
snapshots(*, ticks / every / seconds / events / start_tick / end_tick) |
Per-player game state (position, eye angles, health, armor, economy, and loadout) at a tick, a list of ticks, a contiguous range, or sampled across the match by a stride and/or event ticks. |
For events without a dedicated dataset, the demo.events mapping returns any game event as a DataFrame, and ticks(props) returns one row per player per tick for any networked properties.
Use awpy.SNAPSHOT_PROPERTIES to find snapshot fields and their engine properties. awpy.GAME_EVENTS lists common events; demo.events.names lists events in the demo.
| Crate | Description |
|---|---|
awpy |
Core parsing library (io, entity system, demo parser, name tables). |
awpy-dev |
Developer CLI for parser internals (the awpy-dev binary). |
awpy-proto |
Generated CS2 protobuf types (prost). |
awpy-python |
Python bindings via PyO3 + Polars (published as awpy on PyPI). |
See awpy.readthedocs.io:
- Getting Started
- Examples — runnable scripts, also in
crates/awpy-python/examples/ - Datasets
- API Reference
- CLI Reference
- FAQ
- LLM documentation index
- Changelog
# Rust
cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo nextest run --workspace --exclude awpy-python
# Python (from crates/awpy-python)
uv sync --locked --no-install-project
uv run --no-sync maturin develop --release
uv run --no-sync pytest -m "not fixtures"
uv run --no-sync ruff check python tests
uv run --no-sync ty check pythonUse Python 3.11–3.14 for source builds with maturin develop.
The release workflow builds a stable-ABI wheel with Python 3.14 for Python 3.15.
The fixtures marker selects all tests that need demo files, including schema,
CLI, and example tests. CI runs these tests in a separate job. Run the offline
tests with pytest -m "not fixtures". Unknown markers and configuration keys
are errors.
For demo-backed tests, fetch the fixture manifest and allow downloads. Run these
commands from crates/awpy-python after the development install:
mkdir -p tests/fixtures
curl -fSL https://raw.githubusercontent.com/pnxenopoulos/awpy-fixtures/main/manifest.json \
-o tests/fixtures/manifest.json
AWPY_RUN_FIXTURES=1 uv run --no-sync pytest -m fixtures --durations=10By default, pytest uses cached demos and skips unavailable demos.
AWPY_RUN_FIXTURES=1 permits downloads and makes missing required demos or an
empty manifest fail the tests.
For a faster run, prepare the files before starting two test workers:
uv run --no-sync python tests/fixture_store.py
AWPY_RUN_FIXTURES=1 AWPY_TICK_SEGMENTS=2 POLARS_MAX_THREADS=2 \
uv run --no-sync pytest -m fixtures -n 2 --dist=loadfile --durations=10CI uses two workers and assigns complete test files to each worker. This keeps
module fixtures shared. Use one worker if memory is limited. Avoid -n auto:
each worker can also start parser and Polars threads.
Fixtures verify each demo file once per worker session. Read-only checks share a parsed demo within a module or parameter; cache and parser-state tests create fresh instances. Do not change the cached DataFrames on a shared demo. Temporary asset caches and monkeypatches remain local to each test.
CI installs dependencies from the lockfile and tests the prebuilt wheel with
uv run --no-sync. This prevents uv from replacing the wheel with a source
build. Keep timing reports enabled when changing fixtures or test scheduling.
Each test checks one behavior. Several assertions can describe the same result. Use named tests for independent results and parameters for input variants. Share expensive, unchanged query results through module fixtures. Keep mutable test doubles, files, and settings local to each test. Remove duplicate checks only when the same behavior and parsing path remain covered. Use exact DataFrame comparisons when the result must match exactly.
Update the package version and finalize the changelog. Push to main and wait
for CI Check to pass on the release commit.
In GitHub Actions, select Release Awpy (release.yml) on main.
Select awpy-proto, awpy, or awpy-python and enter its Cargo version.
Release in this order: awpy-proto, awpy, then the Python package.
The workflow checks that the preceding Rust package version is on crates.io.
Skip a Rust package if its version is already published.
Enter the full version without a leading v. The workflow checks the version,
publishes the package, then creates its tag and GitHub release. Tags use
awpy-proto-v<version>, awpy-v<version>, or awpy-python-v<version>.
Existing tags must refer to the release commit. A retry skips published files
and existing releases. A tag push does not start publication.
The awpy-python choice publishes a source archive and wheels for Python 3.11–3.15:
- Linux: x86-64 and ARM64.
- macOS: Intel and Apple silicon.
- Windows: x86-64.
Python 3.15 uses a stable-ABI wheel built with Python 3.14, as in CI.
Configure the crates.io trusted publishers for release.yml with environment
crates, and the PyPI trusted publisher for release.yml with environment pypi.
Update existing publishers that refer to the old workflow filenames.
Publish each new Rust crate once with an API token before you configure its
trusted publisher.
Run the Criterion suite with a local demo:
AWPY_BENCH_DEMO=/absolute/path/to/match.dem cargo criterion -p awpy --bench parseSee the benchmark guide for runner installation, workload filters, fixed parallelism, and baseline comparisons.
Use the Python API benchmark guide
for isolated timing, peak memory, and output checks through Demo.
crates/awpy-proto/src/proto.rs is generated from the .proto files in
crates/awpy-proto/proto/ (synced from
GameTracking-CS2):
cargo run --locked --manifest-path scripts/build-protos/Cargo.tomlThe generator uses a bundled protoc. It removes unsupported C++ annotations
from temporary compiler inputs. It keeps the upstream .proto files unchanged.
Run its tests with cargo test --locked --manifest-path scripts/build-protos/Cargo.toml.
MIT — see LICENSE for details.