How Do I Validate a GTFS Feed in Python?

By Igor Akimov, maintainer of GTFS Guru

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?

• GTFS notice guide

Want to see a report first? Validate a feed in your browser. The file stays on your device.