How Do I Validate a GTFS Feed in Python?
Install the package with pip install gtfs-guru, then call
gtfs_guru.validate("feed.zip"). You get back a result object with error, warning and info
counts and a list of notices, each pointing at the file, row and field that caused it.
The package wraps the same Rust validator that runs on gtfs.guru and in the command-line tool, so a feed gets the same notice codes whichever one you use. It is a compiled extension, not a wrapper that shells out to Java, and it needs Python 3.8 or newer.
Install
pip install gtfs-guru
The package on PyPI is gtfs-guru; the module you import is gtfs_guru. Prebuilt
wheels are listed for macOS (Apple Silicon and Intel), Windows x86_64 and Linux x86_64. On other platforms
you can build the wheel from a checkout with maturin, as described in the
package README.
Minimal example
import gtfs_guru
result = gtfs_guru.validate("/path/to/gtfs.zip")
print(f"Valid: {result.is_valid}")
print(f"Errors: {result.error_count}")
print(f"Warnings: {result.warning_count}")
validate() takes a path to a GTFS zip file or an unzipped feed directory. Two optional
arguments match the CLI options: country_code (an ISO code such as "US" or
"DE", which turns on region-specific rules) and date (a
YYYY-MM-DD string to validate as of that day instead of today).
result = gtfs_guru.validate(
"/path/to/gtfs.zip",
country_code="US",
date="2025-01-15",
)
Pinning date is useful in tests: notices such as
expired_calendar and
feed_expiration_date30_days depend on the validation
date, so a fixed date keeps the result stable. If the path is invalid or the feed cannot be loaded,
validate() raises ValueError.
Reading the results
result.errors(), result.warnings() and result.infos() return
notices by severity, and result.by_code() filters by notice code. Every notice has
code, severity, message, and, when they apply, file,
row and field.
for error in result.errors():
print(f"{error.code}: {error.message}")
if error.file:
print(f" at {error.file}:{error.row}")
for notice in result.by_code("missing_required_field"):
print(notice.file, notice.row, notice.get("fieldName"))
notice.get(key) reads one context value and notice.context() returns all of them
as a dict. The context keys for each code are listed on its page in the
notice code reference; see for example
missing_required_field or
foreign_key_violation.
Saving reports
result.save_json("report.json")
result.save_html("report.html")
report = result.to_dict() # or result.to_json() for a string
summary = report["summary"]
save_json() writes a report in the same JSON format as the canonical Java validator's
report.json, so scripts that already parse that file can read this one.
save_html() writes a basic page with the counts and the raw report. For the full HTML report,
run the CLI or validate in the browser.
Notebooks and ETL jobs
In a notebook, validate the feed and summarise the notice codes before looking at individual rows:
from collections import Counter
import gtfs_guru
result = gtfs_guru.validate("/path/to/gtfs.zip")
error_counts = Counter(e.code for e in result.errors())
for code, count in error_counts.most_common(10):
print(f"{code}: {count}")
In a pipeline, use the result as a gate before you load or publish the feed:
result = gtfs_guru.validate(feed_path)
result.save_json("report.json")
if not result.is_valid:
raise RuntimeError(f"{result.error_count} GTFS errors, see report.json")
For async code, await gtfs_guru.validate_async(path, on_progress=callback) runs the same
validation without blocking the event loop and reports progress through the callback. Other helpers:
gtfs_guru.version(), gtfs_guru.notice_codes() and
gtfs_guru.notice_schema().
Full reference
The Python API reference and the package README document every function, attribute and method. If you validate from a shell script or a CI job instead, see how to validate GTFS in CI.
FAQ
Does the Python package give the same results as the website?
Yes. The browser app, the CLI and the Python package all run the same Rust validator, so the notice codes and counts match for the same feed, date and country code.
Do I need Java installed?
No. The package is a compiled Rust extension. Only the canonical MobilityData validator needs a Java runtime; see how the two compare.
Does it check Google-specific rules?
The Python validate() function takes only path, country_code and
date. The Google-specific rules and the extra --thorough checks are switched on
through CLI flags.
Related Questions
• How do I validate my GTFS feed?
• How do I validate GTFS in CI?
Want to see a report first? Validate a feed in your browser. The file stays on your device.