Metadata-Version: 2.4
Name: pygtfsie
Version: 0.1.0
Summary: GTFS static and GTFS-Realtime engine: ingest, storage and departure queries
Author-email: "Gary T. Giesen" <ggiesen@giesen.me>
License: MPL-2.0
Project-URL: Homepage, https://gitlab.com/ggiesen/pygtfsie
Project-URL: Source, https://gitlab.com/ggiesen/pygtfsie
Keywords: gtfs,gtfs-realtime,transit,departures
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: realtime
Requires-Dist: gtfs-realtime-bindings==1.0.0; extra == "realtime"
Requires-Dist: protobuf>=5.28; extra == "realtime"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# pygtfsie

A GTFS static and GTFS-Realtime engine: ingest a feed archive into SQLite,
resolve every stop event to an absolute instant, and answer departure queries.

This is the engine half of a pair. The Home Assistant integration built on it
lives in [ha-gtfsie](https://gitlab.com/ggiesen/ha-gtfsie). Nothing here imports
Home Assistant, and a CI job fails the build if anything ever does.

## Why it is a separate package

The great majority of the logic in a transit-departure integration is not
integration logic. It is date arithmetic, CSV tolerance, protobuf decoding and
SQL -- none of which needs an event loop, a config entry or a pinned Home
Assistant version to be exercised.

Splitting the engine out means its tests run on bare pytest across every
supported Python in well under a second, so the parts most likely to be wrong are
also the cheapest to check. The integration keeps the parts that genuinely need
Home Assistant: config entries, coordinators, entities and services.

## Design rules

Three rules account for most of what this library does differently, and each
exists because the alternative has a known, reported failure mode.

**Time is resolved at ingest, never at query time.** Every `(trip, stop time,
service date)` becomes an absolute UTC epoch integer when the feed is loaded. No
SQL statement contains `date()`, `datetime()`, `strftime()` or `'now'`. SQLite
evaluates those in C against the *process* timezone in UTC, which no Python-level
clock control can reach -- so a suite that fakes the clock still gets real dates
from the database, and the disagreement surfaces as departures on the wrong day.

**The service day is anchored at local noon minus twelve hours.** Not midnight.
Local midnight does not exist on spring-forward days in Cairo, Havana, Asunción
and around 70 other zone-dates in the current decade, and it happens twice on the
corresponding fall-back days. Anchoring there yields a silently shifted instant
rather than an error. Noon has never been moved by any jurisdiction.

**Nothing reads the current time except the caller.** Every query takes a
`now_utc` argument. `scripts/time-sweep.sh` runs the whole suite at six instants
in six timezones and fails if the results differ, which is what keeps the rule
from decaying.

## Layout

```
pygtfsie/
  const.py          GTFS enumerations, route type names and icons
  exceptions.py     one hierarchy, so a consumer can catch the library
  helpers/
    tz.py           the only place timezone reasoning exists
    text.py         the only place str() is called on a raw feed value
    geo.py          bounding boxes and great-circle distance
    logthrottle.py  rate limiting for repeated messages
```

Later phases add `store/` (schema, connections, queries), `ingest/` (download,
CSV, calendar expansion, materialisation) and `realtime/` (protobuf and SIRI
decoding, trip matching).

## Install

```
pip install pygtfsie              # engine only
pip install pygtfsie[realtime]    # adds gtfs-realtime-bindings and protobuf
```

Realtime support is an extra because a consumer that only needs the static
timetable should not pull in protobuf.

## Development

```
python3 -m venv .venv
./.venv/bin/pip install -e ".[dev]"
./.venv/bin/python -m pytest -q
```

Before pushing anything that touches date handling:

```
./.venv/bin/python -m ruff check pygtfsie tests
./.venv/bin/python -m ruff format --check pygtfsie tests
scripts/time-sweep.sh              # needs libfaketime
```

## Branching

`master` is the release branch and is what tags are cut from. Work happens on
topic branches merged back into `master`.

## Pipeline

The GitLab pipeline has three stages:

- **lint** -- `ruff check` and `ruff format --check`, plus a grep gate that fails
  if any module under `pygtfsie/` imports Home Assistant.
- **test** -- pytest on Python 3.11, 3.12, 3.13 and 3.14.
- **publish** -- builds and uploads to PyPI on a `vMAJOR.MINOR.PATCH` tag using
  PyPI Trusted Publishing (OIDC), so no API token is stored anywhere. The job is
  `when: manual`: tagging never publishes on its own.

## Licence

Mozilla Public License 2.0. See [LICENSE](LICENSE).
