transitmatters 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transitmatters-0.1.0/.github/workflows/docs.yml +49 -0
- transitmatters-0.1.0/.github/workflows/publish.yml +49 -0
- transitmatters-0.1.0/.github/workflows/sync-spec.yml +76 -0
- transitmatters-0.1.0/.gitignore +13 -0
- transitmatters-0.1.0/.python-version +1 -0
- transitmatters-0.1.0/LICENSE +21 -0
- transitmatters-0.1.0/PKG-INFO +178 -0
- transitmatters-0.1.0/README.md +162 -0
- transitmatters-0.1.0/main.py +6 -0
- transitmatters-0.1.0/openapi-config.yml +10 -0
- transitmatters-0.1.0/pyproject.toml +38 -0
- transitmatters-0.1.0/scripts/docs.py +42 -0
- transitmatters-0.1.0/scripts/sync_spec.py +115 -0
- transitmatters-0.1.0/spec/UPSTREAM_SHA +1 -0
- transitmatters-0.1.0/spec/openapi.json +3410 -0
- transitmatters-0.1.0/src/transitmatters/__init__.py +21 -0
- transitmatters-0.1.0/src/transitmatters/_generated/__init__.py +8 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/__init__.py +1 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/__init__.py +1 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_dwells.py +214 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_headways.py +214 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_travel_times.py +233 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_travel_times_v2.py +224 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_current_alerts.py +136 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_dwells.py +224 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_facilities.py +129 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_headways.py +224 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_healthcheck.py +134 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_historical_alerts.py +244 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_line_delays.py +228 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_ridership.py +207 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_routes.py +145 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_scheduled_service.py +217 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_service_hours.py +224 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_service_ridership_dashboard.py +131 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_speed_restrictions.py +184 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_stops_for_route.py +190 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_time_predictions.py +176 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_travel_times.py +247 -0
- transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_trip_metrics.py +229 -0
- transitmatters-0.1.0/src/transitmatters/_generated/client.py +272 -0
- transitmatters-0.1.0/src/transitmatters/_generated/errors.py +16 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/__init__.py +161 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_by_date.py +157 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_by_time.py +140 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_dwells_params.py +93 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_headways_params.py +93 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_travel_times_params.py +107 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/alert.py +109 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/alert_active_period.py +116 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/alert_delays_by_line_params.py +106 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/alert_delays_by_line_params_agg.py +9 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/alerts_by_date_params.py +71 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/by_day_kind_service_levels.py +75 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line.py +202 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line_ridershiphistory.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line_servicehistory.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_day.py +107 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_regime.py +104 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_regimes.py +104 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary.py +232 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary_totalridershiphistory.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary_totalservicehistory.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dwell_event.py +130 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/dwell_params.py +71 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/error_response.py +72 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facilities_response.py +156 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facilities_response_links.py +64 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility.py +171 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility_attributes.py +194 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility_links.py +64 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility_property.py +88 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility_relationships.py +101 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/facility_stop_relationship.py +102 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/get_line_delays_agg.py +9 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/get_scheduled_service_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/get_scheduled_service_response.py +128 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/get_service_hours_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/get_trip_metrics_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/headway_aggregate_by_date.py +173 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/headway_event.py +144 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/headway_params.py +71 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/healthcheck_response.py +133 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/healthcheck_response_failed_checks.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/json_api_resource_identifier.py +72 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/json_api_version.py +78 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/line_delay_entry.py +200 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/live_alert.py +182 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/ridership_entry.py +72 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/ridership_params.py +88 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/route_not_found_response.py +64 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/routes_response.py +91 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_count.py +72 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_params.py +98 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_params_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_entry.py +80 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_params.py +97 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_params_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_levels_by_day_kind.py +176 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response.py +116 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response_linedata.py +63 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response_modedata.py +63 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restriction_zone.py +172 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restrictions_params.py +78 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restrictions_response.py +134 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/station_stop.py +250 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/station_stop_stops.py +56 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/stops_response.py +139 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/stops_response_direction.py +46 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_bucket.py +112 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_params.py +67 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_week.py +94 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_aggregate_2_response.py +96 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_event.py +175 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_params.py +85 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_by_line_params.py +97 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_by_line_params_agg.py +10 -0
- transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_entry.py +143 -0
- transitmatters-0.1.0/src/transitmatters/_generated/types.py +54 -0
- transitmatters-0.1.0/src/transitmatters/client.py +761 -0
- transitmatters-0.1.0/src/transitmatters/errors.py +33 -0
- transitmatters-0.1.0/src/transitmatters/models.py +12 -0
- transitmatters-0.1.0/src/transitmatters/py.typed +0 -0
- transitmatters-0.1.0/tests/test_client.py +207 -0
- transitmatters-0.1.0/uv.lock +582 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: docs-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Install dev dependencies
|
|
27
|
+
run: uv sync --dev
|
|
28
|
+
|
|
29
|
+
- name: Build API docs
|
|
30
|
+
run: uv run python scripts/docs.py -o site
|
|
31
|
+
|
|
32
|
+
- uses: actions/upload-pages-artifact@v3
|
|
33
|
+
with:
|
|
34
|
+
path: site
|
|
35
|
+
|
|
36
|
+
deploy:
|
|
37
|
+
# Pull requests only check that the docs build.
|
|
38
|
+
if: github.ref == 'refs/heads/main'
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
permissions:
|
|
42
|
+
pages: write
|
|
43
|
+
id-token: write # required to deploy to GitHub Pages
|
|
44
|
+
environment:
|
|
45
|
+
name: github-pages
|
|
46
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
47
|
+
steps:
|
|
48
|
+
- id: deployment
|
|
49
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: astral-sh/setup-uv@v6
|
|
14
|
+
|
|
15
|
+
- name: Check tag matches version
|
|
16
|
+
run: |
|
|
17
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
18
|
+
version="$(uv version --short)"
|
|
19
|
+
if [ "$tag" != "$version" ]; then
|
|
20
|
+
echo "::error::Release tag $GITHUB_REF_NAME does not match pyproject version $version"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Smoke test wheel
|
|
28
|
+
run: uv run --isolated --no-project --with dist/*.whl python -c "import transitmatters._generated"
|
|
29
|
+
|
|
30
|
+
- uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/transitmatters
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write # required for trusted publishing
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: Sync upstream OpenAPI spec
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "17 13 * * 1" # Mondays 13:17 UTC (~9am Boston)
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
ref:
|
|
9
|
+
description: "Upstream branch, tag, or SHA to sync to"
|
|
10
|
+
required: false
|
|
11
|
+
default: main
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: sync-spec
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
sync:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Install dev dependencies
|
|
32
|
+
run: uv sync --dev
|
|
33
|
+
|
|
34
|
+
- name: Fetch upstream spec
|
|
35
|
+
id: sync
|
|
36
|
+
env:
|
|
37
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
+
run: uv run python scripts/sync_spec.py --ref "${{ inputs.ref || 'main' }}"
|
|
39
|
+
|
|
40
|
+
- name: Regenerate client
|
|
41
|
+
if: steps.sync.outputs.changed == 'true'
|
|
42
|
+
run: |
|
|
43
|
+
uv run openapi-python-client generate \
|
|
44
|
+
--path spec/openapi.json \
|
|
45
|
+
--config openapi-config.yml \
|
|
46
|
+
--meta none \
|
|
47
|
+
--output-path src/transitmatters/_generated \
|
|
48
|
+
--overwrite
|
|
49
|
+
|
|
50
|
+
- name: Run tests
|
|
51
|
+
if: steps.sync.outputs.changed == 'true'
|
|
52
|
+
continue-on-error: true # still open the PR; failures show up in its checks
|
|
53
|
+
run: uv run pytest -q
|
|
54
|
+
|
|
55
|
+
- name: Open pull request
|
|
56
|
+
if: steps.sync.outputs.changed == 'true'
|
|
57
|
+
uses: peter-evans/create-pull-request@v7
|
|
58
|
+
with:
|
|
59
|
+
branch: chore/sync-openapi-spec
|
|
60
|
+
delete-branch: true
|
|
61
|
+
commit-message: "chore: sync OpenAPI spec to upstream ${{ steps.sync.outputs.short_sha }}"
|
|
62
|
+
title: "Sync OpenAPI spec to upstream ${{ steps.sync.outputs.short_sha }}"
|
|
63
|
+
body: |
|
|
64
|
+
Automated sync of `server/openapi.json` from
|
|
65
|
+
[transitmatters/t-performance-dash@${{ steps.sync.outputs.short_sha }}](https://github.com/transitmatters/t-performance-dash/commit/${{ steps.sync.outputs.sha }}).
|
|
66
|
+
|
|
67
|
+
- `spec/openapi.json` updated
|
|
68
|
+
- `spec/UPSTREAM_SHA` bumped
|
|
69
|
+
- `src/transitmatters/_generated/` regenerated
|
|
70
|
+
|
|
71
|
+
Review the spec diff for new or changed endpoints, and update the
|
|
72
|
+
wrapper in `src/transitmatters/` if needed.
|
|
73
|
+
labels: dependencies
|
|
74
|
+
add-paths: |
|
|
75
|
+
spec/
|
|
76
|
+
src/transitmatters/_generated/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TransitMatters
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: transitmatters
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the TransitMatters Data Dashboard API
|
|
5
|
+
Project-URL: Repository, https://github.com/transitmatters/transitmatters-py
|
|
6
|
+
Project-URL: Upstream API, https://github.com/transitmatters/t-performance-dash
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Typing :: Typed
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: attrs>=23.1
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
Requires-Dist: python-dateutil>=2.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# transitmatters-py
|
|
18
|
+
|
|
19
|
+
<!-- The API docs include this file from here down. -->
|
|
20
|
+
|
|
21
|
+
MBTA performance data from the [TransitMatters Data Dashboard](https://dashboard.transitmatters.org),
|
|
22
|
+
as Python objects: travel times, headways, dwells, ridership, service levels,
|
|
23
|
+
delays, speed restrictions, and alerts.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install transitmatters
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from transitmatters import TransitMatters
|
|
31
|
+
|
|
32
|
+
tm = TransitMatters()
|
|
33
|
+
|
|
34
|
+
# Every southbound Red Line trip from Alewife to Braintree on March 4, 2025
|
|
35
|
+
trips = tm.travel_times("2025-03-04", from_stop="70061", to_stop="70105")
|
|
36
|
+
print(trips[0].dep_dt, trips[0].travel_time_sec)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Dates can be `datetime.date` objects or `"YYYY-MM-DD"` strings, and date ranges
|
|
40
|
+
include both ends. Parameters that take IDs accept one string or a list.
|
|
41
|
+
|
|
42
|
+
## Which method do I want?
|
|
43
|
+
|
|
44
|
+
**What happened on one day.** Each item is a single event.
|
|
45
|
+
|
|
46
|
+
| Question | Method | Each item is |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| How long did trips between two stops take? | `TransitMatters.travel_times` | a trip |
|
|
49
|
+
| How long between departures at a stop? | `TransitMatters.headways` | a departure |
|
|
50
|
+
| How long did vehicles sit at a stop? | `TransitMatters.dwells` | a stop a vehicle made |
|
|
51
|
+
| Which alerts were posted for a route? | `TransitMatters.historical_alerts` | an alert |
|
|
52
|
+
| Where were speed restrictions on a line? | `TransitMatters.speed_restrictions` | a zone, in `.zones` |
|
|
53
|
+
|
|
54
|
+
**Trends over a date range.** Each item summarizes one day, week, or month.
|
|
55
|
+
|
|
56
|
+
| Question | Method | Each item is |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Travel-time statistics between two stops | `TransitMatters.aggregate_travel_times` | a day |
|
|
59
|
+
| ...also broken down by time of day | `TransitMatters.aggregate_travel_times_v2` | a day, or a 30-minute slot |
|
|
60
|
+
| Headway statistics, bunching, and on-time counts at a stop | `TransitMatters.aggregate_headways` | a day |
|
|
61
|
+
| Dwell-time statistics at a stop | `TransitMatters.aggregate_dwells` | a day |
|
|
62
|
+
| Trip count, speed, and miles for a rapid transit line | `TransitMatters.trip_metrics` | a day, week, or month |
|
|
63
|
+
| Minutes of delay on a line, by cause | `TransitMatters.line_delays` | a day or week |
|
|
64
|
+
| Number of scheduled trips on a route | `TransitMatters.scheduled_service` | a day, week, or month, in `.counts` |
|
|
65
|
+
| Scheduled vs. delivered service hours | `TransitMatters.service_hours` | a day, week, or month |
|
|
66
|
+
| Ridership on a line | `TransitMatters.ridership` | a week |
|
|
67
|
+
| How accurate arrival predictions were | `TransitMatters.time_predictions` | a week |
|
|
68
|
+
|
|
69
|
+
**Right now, and system-wide.**
|
|
70
|
+
|
|
71
|
+
| Question | Method |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| Which alerts are in effect now? | `TransitMatters.current_alerts` |
|
|
74
|
+
| Where are the elevators and escalators? | `TransitMatters.facilities` |
|
|
75
|
+
| Service and ridership across the whole system | `TransitMatters.service_ridership_dashboard` |
|
|
76
|
+
| Is the API healthy? | `TransitMatters.healthcheck` |
|
|
77
|
+
|
|
78
|
+
## Finding IDs
|
|
79
|
+
|
|
80
|
+
Each kind of ID has its own format, and some endpoints spell the same line
|
|
81
|
+
differently. Check this table before guessing:
|
|
82
|
+
|
|
83
|
+
| Parameter | Used by | Looks like |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `stop`, `from_stop`, `to_stop` | `headways`, `dwells`, `travel_times`, `aggregate_*` | `"70061"` (rapid transit), `"1-0-110"` (bus), `"CR-Fairmount_0_DB-2205-01"` (commuter rail) |
|
|
86
|
+
| `route`, `route_id` | `historical_alerts`, `scheduled_service`, `time_predictions`, `stops` | `"Red"`, `"66"`, `"CR-Fairmount"`, `"Boat-F1"` |
|
|
87
|
+
| `line` | `line_delays` | `"Red"`, `"CR-Fairmount"` |
|
|
88
|
+
| `line`, `line_id` | `trip_metrics`, `service_hours` | `"line-red"` (lowercase; rapid transit only) |
|
|
89
|
+
| `line_id` | `ridership` | `"line-Red"`, `"line-66"`, `"line-Fairmount"` |
|
|
90
|
+
| `line_id` | `speed_restrictions` | `"line-Red"` (case-sensitive; rapid transit only) |
|
|
91
|
+
|
|
92
|
+
`TransitMatters.routes` lists every route ID. `TransitMatters.stops` lists the
|
|
93
|
+
stations on a route, with the stop ID of each platform by direction:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
red = tm.stops("Red")
|
|
97
|
+
print(red.direction.to_dict()) # {'0': 'northbound', '1': 'southbound'}
|
|
98
|
+
for station in red.stations[:3]:
|
|
99
|
+
print(station.stop_name, station.stops.to_dict())
|
|
100
|
+
# Alewife {'0': ['70061'], '1': ['70061']}
|
|
101
|
+
# Davis {'0': ['70064'], '1': ['70063']}
|
|
102
|
+
# Porter {'0': ['70066'], '1': ['70065']}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Platforms have separate IDs for each direction, so `from_stop` and `to_stop`
|
|
106
|
+
must both be platforms for the direction you're traveling. Southbound from
|
|
107
|
+
Davis to Porter is `from_stop="70063", to_stop="70065"`.
|
|
108
|
+
|
|
109
|
+
A stop ID that doesn't exist isn't an error; you just get an empty list back.
|
|
110
|
+
|
|
111
|
+
## Working with pandas
|
|
112
|
+
|
|
113
|
+
Every result is a model object (see `transitmatters.models`), and every model
|
|
114
|
+
has `to_dict()`:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import pandas as pd
|
|
118
|
+
|
|
119
|
+
events = tm.headways("2025-03-04", stop="70061")
|
|
120
|
+
df = pd.DataFrame([e.to_dict() for e in events])
|
|
121
|
+
df["current_dep_dt"] = pd.to_datetime(df["current_dep_dt"])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- Timestamps are Eastern local time, formatted `YYYY-MM-DDTHH:MM:SS` with no
|
|
125
|
+
offset.
|
|
126
|
+
- `to_dict()` uses the API's field names. A few differ from the Python
|
|
127
|
+
attribute names, e.g. `min` and `25%` instead of `min_` and `field_25`.
|
|
128
|
+
- When a response wraps its list in a field, such as
|
|
129
|
+
`scheduled_service(...).counts` or `speed_restrictions(...).zones`, build
|
|
130
|
+
the DataFrame from that field.
|
|
131
|
+
|
|
132
|
+
## Errors
|
|
133
|
+
|
|
134
|
+
When the API rejects a request, methods raise `transitmatters.APIError`:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from transitmatters import APIError
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
tm.stops("Purple")
|
|
141
|
+
except APIError as e:
|
|
142
|
+
print(e.status_code, e.message) # 404 Route 'Purple' not found
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Async
|
|
146
|
+
|
|
147
|
+
`transitmatters.AsyncTransitMatters` has the same methods, as coroutines. It's
|
|
148
|
+
useful for fetching many days at once. In Jupyter you can `await` directly in
|
|
149
|
+
a cell.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import asyncio
|
|
153
|
+
from transitmatters import AsyncTransitMatters
|
|
154
|
+
|
|
155
|
+
async def main():
|
|
156
|
+
async with AsyncTransitMatters() as tm:
|
|
157
|
+
days = ["2025-03-03", "2025-03-04", "2025-03-05"]
|
|
158
|
+
return await asyncio.gather(*(tm.headways(d, stop="70061") for d in days))
|
|
159
|
+
|
|
160
|
+
per_day = asyncio.run(main())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
uv sync
|
|
167
|
+
uv run pytest
|
|
168
|
+
uv run python scripts/docs.py # preview the API docs at http://localhost:8080
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The API docs are built from this README and the docstrings in
|
|
172
|
+
`src/transitmatters/`, and are published to GitHub Pages on every push to
|
|
173
|
+
`main`.
|
|
174
|
+
|
|
175
|
+
`src/transitmatters/_generated/` is generated from the upstream OpenAPI spec by
|
|
176
|
+
`openapi-python-client`; don't edit it by hand. A weekly workflow syncs the
|
|
177
|
+
spec and opens a PR (see `scripts/sync_spec.py`). The public API lives in
|
|
178
|
+
`src/transitmatters/client.py`.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# transitmatters-py
|
|
2
|
+
|
|
3
|
+
<!-- The API docs include this file from here down. -->
|
|
4
|
+
|
|
5
|
+
MBTA performance data from the [TransitMatters Data Dashboard](https://dashboard.transitmatters.org),
|
|
6
|
+
as Python objects: travel times, headways, dwells, ridership, service levels,
|
|
7
|
+
delays, speed restrictions, and alerts.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install transitmatters
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from transitmatters import TransitMatters
|
|
15
|
+
|
|
16
|
+
tm = TransitMatters()
|
|
17
|
+
|
|
18
|
+
# Every southbound Red Line trip from Alewife to Braintree on March 4, 2025
|
|
19
|
+
trips = tm.travel_times("2025-03-04", from_stop="70061", to_stop="70105")
|
|
20
|
+
print(trips[0].dep_dt, trips[0].travel_time_sec)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Dates can be `datetime.date` objects or `"YYYY-MM-DD"` strings, and date ranges
|
|
24
|
+
include both ends. Parameters that take IDs accept one string or a list.
|
|
25
|
+
|
|
26
|
+
## Which method do I want?
|
|
27
|
+
|
|
28
|
+
**What happened on one day.** Each item is a single event.
|
|
29
|
+
|
|
30
|
+
| Question | Method | Each item is |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| How long did trips between two stops take? | `TransitMatters.travel_times` | a trip |
|
|
33
|
+
| How long between departures at a stop? | `TransitMatters.headways` | a departure |
|
|
34
|
+
| How long did vehicles sit at a stop? | `TransitMatters.dwells` | a stop a vehicle made |
|
|
35
|
+
| Which alerts were posted for a route? | `TransitMatters.historical_alerts` | an alert |
|
|
36
|
+
| Where were speed restrictions on a line? | `TransitMatters.speed_restrictions` | a zone, in `.zones` |
|
|
37
|
+
|
|
38
|
+
**Trends over a date range.** Each item summarizes one day, week, or month.
|
|
39
|
+
|
|
40
|
+
| Question | Method | Each item is |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Travel-time statistics between two stops | `TransitMatters.aggregate_travel_times` | a day |
|
|
43
|
+
| ...also broken down by time of day | `TransitMatters.aggregate_travel_times_v2` | a day, or a 30-minute slot |
|
|
44
|
+
| Headway statistics, bunching, and on-time counts at a stop | `TransitMatters.aggregate_headways` | a day |
|
|
45
|
+
| Dwell-time statistics at a stop | `TransitMatters.aggregate_dwells` | a day |
|
|
46
|
+
| Trip count, speed, and miles for a rapid transit line | `TransitMatters.trip_metrics` | a day, week, or month |
|
|
47
|
+
| Minutes of delay on a line, by cause | `TransitMatters.line_delays` | a day or week |
|
|
48
|
+
| Number of scheduled trips on a route | `TransitMatters.scheduled_service` | a day, week, or month, in `.counts` |
|
|
49
|
+
| Scheduled vs. delivered service hours | `TransitMatters.service_hours` | a day, week, or month |
|
|
50
|
+
| Ridership on a line | `TransitMatters.ridership` | a week |
|
|
51
|
+
| How accurate arrival predictions were | `TransitMatters.time_predictions` | a week |
|
|
52
|
+
|
|
53
|
+
**Right now, and system-wide.**
|
|
54
|
+
|
|
55
|
+
| Question | Method |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Which alerts are in effect now? | `TransitMatters.current_alerts` |
|
|
58
|
+
| Where are the elevators and escalators? | `TransitMatters.facilities` |
|
|
59
|
+
| Service and ridership across the whole system | `TransitMatters.service_ridership_dashboard` |
|
|
60
|
+
| Is the API healthy? | `TransitMatters.healthcheck` |
|
|
61
|
+
|
|
62
|
+
## Finding IDs
|
|
63
|
+
|
|
64
|
+
Each kind of ID has its own format, and some endpoints spell the same line
|
|
65
|
+
differently. Check this table before guessing:
|
|
66
|
+
|
|
67
|
+
| Parameter | Used by | Looks like |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `stop`, `from_stop`, `to_stop` | `headways`, `dwells`, `travel_times`, `aggregate_*` | `"70061"` (rapid transit), `"1-0-110"` (bus), `"CR-Fairmount_0_DB-2205-01"` (commuter rail) |
|
|
70
|
+
| `route`, `route_id` | `historical_alerts`, `scheduled_service`, `time_predictions`, `stops` | `"Red"`, `"66"`, `"CR-Fairmount"`, `"Boat-F1"` |
|
|
71
|
+
| `line` | `line_delays` | `"Red"`, `"CR-Fairmount"` |
|
|
72
|
+
| `line`, `line_id` | `trip_metrics`, `service_hours` | `"line-red"` (lowercase; rapid transit only) |
|
|
73
|
+
| `line_id` | `ridership` | `"line-Red"`, `"line-66"`, `"line-Fairmount"` |
|
|
74
|
+
| `line_id` | `speed_restrictions` | `"line-Red"` (case-sensitive; rapid transit only) |
|
|
75
|
+
|
|
76
|
+
`TransitMatters.routes` lists every route ID. `TransitMatters.stops` lists the
|
|
77
|
+
stations on a route, with the stop ID of each platform by direction:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
red = tm.stops("Red")
|
|
81
|
+
print(red.direction.to_dict()) # {'0': 'northbound', '1': 'southbound'}
|
|
82
|
+
for station in red.stations[:3]:
|
|
83
|
+
print(station.stop_name, station.stops.to_dict())
|
|
84
|
+
# Alewife {'0': ['70061'], '1': ['70061']}
|
|
85
|
+
# Davis {'0': ['70064'], '1': ['70063']}
|
|
86
|
+
# Porter {'0': ['70066'], '1': ['70065']}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Platforms have separate IDs for each direction, so `from_stop` and `to_stop`
|
|
90
|
+
must both be platforms for the direction you're traveling. Southbound from
|
|
91
|
+
Davis to Porter is `from_stop="70063", to_stop="70065"`.
|
|
92
|
+
|
|
93
|
+
A stop ID that doesn't exist isn't an error; you just get an empty list back.
|
|
94
|
+
|
|
95
|
+
## Working with pandas
|
|
96
|
+
|
|
97
|
+
Every result is a model object (see `transitmatters.models`), and every model
|
|
98
|
+
has `to_dict()`:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import pandas as pd
|
|
102
|
+
|
|
103
|
+
events = tm.headways("2025-03-04", stop="70061")
|
|
104
|
+
df = pd.DataFrame([e.to_dict() for e in events])
|
|
105
|
+
df["current_dep_dt"] = pd.to_datetime(df["current_dep_dt"])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Timestamps are Eastern local time, formatted `YYYY-MM-DDTHH:MM:SS` with no
|
|
109
|
+
offset.
|
|
110
|
+
- `to_dict()` uses the API's field names. A few differ from the Python
|
|
111
|
+
attribute names, e.g. `min` and `25%` instead of `min_` and `field_25`.
|
|
112
|
+
- When a response wraps its list in a field, such as
|
|
113
|
+
`scheduled_service(...).counts` or `speed_restrictions(...).zones`, build
|
|
114
|
+
the DataFrame from that field.
|
|
115
|
+
|
|
116
|
+
## Errors
|
|
117
|
+
|
|
118
|
+
When the API rejects a request, methods raise `transitmatters.APIError`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from transitmatters import APIError
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
tm.stops("Purple")
|
|
125
|
+
except APIError as e:
|
|
126
|
+
print(e.status_code, e.message) # 404 Route 'Purple' not found
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Async
|
|
130
|
+
|
|
131
|
+
`transitmatters.AsyncTransitMatters` has the same methods, as coroutines. It's
|
|
132
|
+
useful for fetching many days at once. In Jupyter you can `await` directly in
|
|
133
|
+
a cell.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import asyncio
|
|
137
|
+
from transitmatters import AsyncTransitMatters
|
|
138
|
+
|
|
139
|
+
async def main():
|
|
140
|
+
async with AsyncTransitMatters() as tm:
|
|
141
|
+
days = ["2025-03-03", "2025-03-04", "2025-03-05"]
|
|
142
|
+
return await asyncio.gather(*(tm.headways(d, stop="70061") for d in days))
|
|
143
|
+
|
|
144
|
+
per_day = asyncio.run(main())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
uv sync
|
|
151
|
+
uv run pytest
|
|
152
|
+
uv run python scripts/docs.py # preview the API docs at http://localhost:8080
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The API docs are built from this README and the docstrings in
|
|
156
|
+
`src/transitmatters/`, and are published to GitHub Pages on every push to
|
|
157
|
+
`main`.
|
|
158
|
+
|
|
159
|
+
`src/transitmatters/_generated/` is generated from the upstream OpenAPI spec by
|
|
160
|
+
`openapi-python-client`; don't edit it by hand. A weekly workflow syncs the
|
|
161
|
+
spec and opens a PR (see `scripts/sync_spec.py`). The public API lives in
|
|
162
|
+
`src/transitmatters/client.py`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
project_name_override: transitmatters
|
|
2
|
+
package_name_override: _generated
|
|
3
|
+
post_hooks:
|
|
4
|
+
# Pin the rules: ruff's defaults grew in 0.17, and the generated code can't
|
|
5
|
+
# be auto-fixed to pass them all, which fails generation.
|
|
6
|
+
- "ruff check --fix --select E4,E7,E9,F ."
|
|
7
|
+
- "ruff format ."
|
|
8
|
+
# Put property descriptions on the attributes themselves. Otherwise the generator
|
|
9
|
+
# appends its own "Attributes:" list after the one in the spec's descriptions.
|
|
10
|
+
docstrings_on_attributes: true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "transitmatters"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Official Python client for the TransitMatters Data Dashboard API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"httpx>=0.27",
|
|
10
|
+
"attrs>=23.1",
|
|
11
|
+
"python-dateutil>=2.8",
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Typing :: Typed",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Repository = "https://github.com/transitmatters/transitmatters-py"
|
|
20
|
+
"Upstream API" = "https://github.com/transitmatters/t-performance-dash"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"openapi-python-client",
|
|
25
|
+
"ruff",
|
|
26
|
+
"pytest",
|
|
27
|
+
"pdoc>=16.0.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["hatchling"]
|
|
32
|
+
build-backend = "hatchling.build"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["src/transitmatters"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
extend-exclude = ["src/transitmatters/_generated"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build or preview the API docs for the transitmatters package with pdoc.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python scripts/docs.py # serve at http://localhost:8080, reloading on change
|
|
6
|
+
python scripts/docs.py -o site # write static HTML to site/
|
|
7
|
+
|
|
8
|
+
Takes the same options as the `pdoc` command.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
|
|
16
|
+
import pdoc.__main__
|
|
17
|
+
import pdoc.render_helpers
|
|
18
|
+
|
|
19
|
+
PACKAGE = "transitmatters"
|
|
20
|
+
GENERATED_MODELS = f"{PACKAGE}._generated.models."
|
|
21
|
+
|
|
22
|
+
_module_candidates = pdoc.render_helpers.module_candidates
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def module_candidates(identifier: str, current_module: str) -> Iterator[str]:
|
|
26
|
+
# pdoc links a type only to where it's documented on the current page or
|
|
27
|
+
# in a parent package of the module that defines it. Models are defined
|
|
28
|
+
# in transitmatters._generated.models.* but documented in the sibling
|
|
29
|
+
# transitmatters.models, so without this, return types show their private
|
|
30
|
+
# path and don't link.
|
|
31
|
+
yield from _module_candidates(identifier, current_module)
|
|
32
|
+
if identifier.startswith(GENERATED_MODELS):
|
|
33
|
+
yield f"{PACKAGE}.models"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> None:
|
|
37
|
+
pdoc.render_helpers.module_candidates = module_candidates
|
|
38
|
+
pdoc.__main__.cli([*sys.argv[1:], PACKAGE])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|