pyegp-parser
Parse SAS Enterprise Guide .egp project files into structured, machine-readable JSON.
An .egp file is a ZIP archive containing project.xml plus SAS code, task
configs, execution logs, and ODS results. pyegp-parser reads all of it and
produces a single structured representation — think of it as decompiling a SAS
Enterprise Guide project into JSON you can query, diff, and analyse.
Features
- Full extraction of project metadata, elements, tasks, queries, data references, and shortcuts
- Reconstructs the execution DAG (process-flow dependencies)
- Extracts embedded SAS code, execution logs, and ODS output references
- Data-lineage tracing across tasks
- Best-effort credential redaction for passwords embedded in SAS code
- Bulk-parse an entire directory tree of
.egpfiles - Optional JSON-schema validation of the output
- Optional MCP server for AI-assisted exploration
- No heavy dependencies (just
jsonschema); pure-Python, cross-platform
Supported Enterprise Guide versions
The parser is version-agnostic by design: it reads whatever structure a
project contains and has no version gates or version-specific branches. The
EGVersion attribute on the root ProjectCollection element is recorded and
surfaced as project.metadata.eg_version, but it never changes how a file is
parsed. In practice that means an unlisted version is likely to parse, and
anything the parser does not recognise is reported rather than dropped — see
Handling unknown content below.
| EG version | Status | How it is verified |
|---|---|---|
| 8.1 | Verified against a real project | tests/fixtures/real_world/eg81_process_flows.egp, plus most of the synthetic suite |
| 7.1 | Verified against a real project | tests/fixtures/real_world/eg71_code_tasks.egp |
| Other 7.x / 8.x | Expected to work, untested | No version gating exists, but no sample was available |
| 9.x and later | Unknown | Not released at the time of writing |
Two caveats worth stating plainly:
- The 7.1 fixture was produced by a third-party 8→7 downgrade converter rather
than written by Enterprise Guide 7.1 itself. Its
project.xml, embedded SAS code, and execution logs are EG-authored, but it is not a pristine 7.1 file. - The 8.1 fixture is a migrated project: its elements carry
ModifiedByEGVervalues spanning7.100.5.xand8.1.0.x, so mixed-version element metadata within a single project is covered.
Real-world coverage is limited by sample availability — genuine .egp files are
rarely published, since they are binary ZIP archives. Provenance and the exact
sanitisation applied to both fixtures are documented in
tests/fixtures/real_world/README.md.
Handling unknown content
Because there is no version gating, an unlisted version is likely to parse. How the parser behaves when it meets something it does not understand depends on what that something is:
- Unrecognised archive entries are listed in
project.unprocessed_entriesand counted inproject.completeness_summary, so nothing is silently discarded. Comparingprocessed_entriesagainsttotal_entriestells you how much of a project was understood. - Missing optional artifacts — an absent
code.sasor task-config file — log a warning and leave the corresponding fieldNone. - A missing required section inside an element raises
ValueErrorand aborts the whole parse. This is deliberate: the parser surfaces structural gaps rather than emitting quietly incomplete output. Log elements and process-flow containers are the two exceptions — they are recorded with whatever metadata was readable, because a display-settings or DAG failure does not undermine the rest of the project.
So a newer EG version introducing a new element type or archive entry is
handled gracefully, whereas one that restructures an existing element will
fail loudly. If you hit either, completeness_summary and unprocessed_entries
are the place to look first, and a bug report quoting the EG version plus those
fields is the most useful thing you can send.
Installation
pip install pyegp-parser
Requires Python 3.11+.
Quick start
from pyegp_parser import parse_file
project = parse_file("path/to/project.egp")
print(project.metadata.label) # project name
print(len(project.tasks)) # number of tasks
print(len(project.queries)) # number of Query Builder queries
Write JSON to disk:
parse_file("project.egp", output_dir="./output") # writes ./output/project.json
CLI
# Parse a single file
pyegp-parser parse path/to/project.egp --output-dir ./output
# Recursively parse every .egp under a directory (mirrors the tree)
pyegp-parser bulk ./source-pipeline --output-dir ./output
# Pretty-print a parsed project.json
pyegp-parser print ./output/project.json
Credential redaction
.egp projects embed SAS programs and logs verbatim, and SAS code routinely
carries live credentials. Passwords are therefore redacted automatically when
output is written:
from pyegp_parser.redaction import redact_text
redact_text("libname dw oracle user=etluser password=hunter2 path=prod;")
# ('libname dw oracle user=etluser password=[SENSITIVE - REDACTED] path=prod;', 1)
SAS-encoded passwords ({SAS002}...) are redacted too — PROC PWENCODE output
is reversible, so it is treated as plaintext. Column names such as
PASSWORD_HASH are deliberately left intact: a column name is schema
metadata, not a secret.
Redaction applies when project.json is written and to MCP server output.
to_dict() is a pure serializer and does not redact.
Handling real projects
Redaction covers credentials, not everything an .egp file reveals. Output also
includes library paths, server names, schema names, and usernames. Review parser
output before attaching it to a public issue or sharing it outside your
organisation — see SECURITY.md for the full security model.
AI integration
.egp projects are dense and hard to read by hand, which makes them a natural fit
for AI-assisted exploration. Two complementary options ship with this project:
MCP server
An MCP server exposes the parser as tools
(parse_egp, parse_egp_directory, get_project_summary, get_sas_code,
get_data_lineage, get_queries) to any MCP-compatible client (Claude Desktop,
Claude Code, Cursor, …).
pip install "pyegp-parser[mcp]"
Then register it with your client. Example config:
{
"mcpServers": {
"pyegp-parser": {
"command": "pyegp-parser-mcp"
}
}
}
Claude Skill
skills/pyegp-parser/SKILL.md is a portable
Agent Skill
that teaches Claude when and how to parse .egp files and interpret the JSON —
no running server required. Copy the skills/pyegp-parser/ folder into your
.claude/skills/ directory to use it.
Use the MCP server for interactive, on-demand parsing inside a client; use the Skill for a portable, dependency-light way to give any Claude the know-how.
Documentation
Full documentation lives at lamiskin.github.io/pyegp-parser.
Development
This project uses uv and ruff.
git clone https://github.com/lamiskin/pyegp-parser
cd pyegp-parser
uv sync # create the venv and install deps (incl. dev tools)
uv run pytest # run the test suite
uv run ruff check # lint
uv run ruff format # format
Acknowledgements
This library was developed with substantial assistance from AI coding tools. It
was originally built and validated against a corpus of real SAS Enterprise
Guide .egp files during a data-migration project. None of that source data — and
no files, identifiers, or history derived from it — is included in this repository;
the test suite runs entirely on synthetic fixtures generated in-memory.
License
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 pyegp_parser-0.1.1.tar.gz.
File metadata
- Download URL: pyegp_parser-0.1.1.tar.gz
- Upload date:
- Size: 179.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1ddd9a84c7bef62852a8e59ba58ec3f5928a1f4011506233536600034c34836
|
|
| MD5 |
20c1ffe98a886f327411a609544d540b
|
|
| BLAKE2b-256 |
56e4d05a67d135c959b61ecb41dd057188dd3089ba7fdd2949ee125a83553bb0
|
Provenance
The following attestation bundles were made for pyegp_parser-0.1.1.tar.gz:
Publisher:
publish.yml on lamiskin/pyegp-parser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyegp_parser-0.1.1.tar.gz -
Subject digest:
d1ddd9a84c7bef62852a8e59ba58ec3f5928a1f4011506233536600034c34836 - Sigstore transparency entry: 2637689637
- Sigstore integration time:
-
Permalink:
lamiskin/pyegp-parser@a83a04499f5d5f12934c267e4727c76759e47809 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/lamiskin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a83a04499f5d5f12934c267e4727c76759e47809 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyegp_parser-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pyegp_parser-0.1.1-py3-none-any.whl
- Upload date:
- Size: 80.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd90d760bc01bfa0e64e77fa09ea50755f3ad91a4d2a52f02dd0a34790bbe60e
|
|
| MD5 |
66b7c9856318a8eb1105aa034ef31687
|
|
| BLAKE2b-256 |
70b99bd9ef0c9247b35d9bbc840a635621e9fd8d96a726f489183a60e375850f
|
Provenance
The following attestation bundles were made for pyegp_parser-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on lamiskin/pyegp-parser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyegp_parser-0.1.1-py3-none-any.whl -
Subject digest:
dd90d760bc01bfa0e64e77fa09ea50755f3ad91a4d2a52f02dd0a34790bbe60e - Sigstore transparency entry: 2637689706
- Sigstore integration time:
-
Permalink:
lamiskin/pyegp-parser@a83a04499f5d5f12934c267e4727c76759e47809 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/lamiskin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a83a04499f5d5f12934c267e4727c76759e47809 -
Trigger Event:
release
-
Statement type: