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.
Files changed (125) hide show
  1. transitmatters-0.1.0/.github/workflows/docs.yml +49 -0
  2. transitmatters-0.1.0/.github/workflows/publish.yml +49 -0
  3. transitmatters-0.1.0/.github/workflows/sync-spec.yml +76 -0
  4. transitmatters-0.1.0/.gitignore +13 -0
  5. transitmatters-0.1.0/.python-version +1 -0
  6. transitmatters-0.1.0/LICENSE +21 -0
  7. transitmatters-0.1.0/PKG-INFO +178 -0
  8. transitmatters-0.1.0/README.md +162 -0
  9. transitmatters-0.1.0/main.py +6 -0
  10. transitmatters-0.1.0/openapi-config.yml +10 -0
  11. transitmatters-0.1.0/pyproject.toml +38 -0
  12. transitmatters-0.1.0/scripts/docs.py +42 -0
  13. transitmatters-0.1.0/scripts/sync_spec.py +115 -0
  14. transitmatters-0.1.0/spec/UPSTREAM_SHA +1 -0
  15. transitmatters-0.1.0/spec/openapi.json +3410 -0
  16. transitmatters-0.1.0/src/transitmatters/__init__.py +21 -0
  17. transitmatters-0.1.0/src/transitmatters/_generated/__init__.py +8 -0
  18. transitmatters-0.1.0/src/transitmatters/_generated/api/__init__.py +1 -0
  19. transitmatters-0.1.0/src/transitmatters/_generated/api/api/__init__.py +1 -0
  20. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_dwells.py +214 -0
  21. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_headways.py +214 -0
  22. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_travel_times.py +233 -0
  23. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_aggregate_travel_times_v2.py +224 -0
  24. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_current_alerts.py +136 -0
  25. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_dwells.py +224 -0
  26. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_facilities.py +129 -0
  27. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_headways.py +224 -0
  28. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_healthcheck.py +134 -0
  29. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_historical_alerts.py +244 -0
  30. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_line_delays.py +228 -0
  31. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_ridership.py +207 -0
  32. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_routes.py +145 -0
  33. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_scheduled_service.py +217 -0
  34. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_service_hours.py +224 -0
  35. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_service_ridership_dashboard.py +131 -0
  36. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_speed_restrictions.py +184 -0
  37. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_stops_for_route.py +190 -0
  38. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_time_predictions.py +176 -0
  39. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_travel_times.py +247 -0
  40. transitmatters-0.1.0/src/transitmatters/_generated/api/api/get_trip_metrics.py +229 -0
  41. transitmatters-0.1.0/src/transitmatters/_generated/client.py +272 -0
  42. transitmatters-0.1.0/src/transitmatters/_generated/errors.py +16 -0
  43. transitmatters-0.1.0/src/transitmatters/_generated/models/__init__.py +161 -0
  44. transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_by_date.py +157 -0
  45. transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_by_time.py +140 -0
  46. transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_dwells_params.py +93 -0
  47. transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_headways_params.py +93 -0
  48. transitmatters-0.1.0/src/transitmatters/_generated/models/aggregate_travel_times_params.py +107 -0
  49. transitmatters-0.1.0/src/transitmatters/_generated/models/alert.py +109 -0
  50. transitmatters-0.1.0/src/transitmatters/_generated/models/alert_active_period.py +116 -0
  51. transitmatters-0.1.0/src/transitmatters/_generated/models/alert_delays_by_line_params.py +106 -0
  52. transitmatters-0.1.0/src/transitmatters/_generated/models/alert_delays_by_line_params_agg.py +9 -0
  53. transitmatters-0.1.0/src/transitmatters/_generated/models/alerts_by_date_params.py +71 -0
  54. transitmatters-0.1.0/src/transitmatters/_generated/models/by_day_kind_service_levels.py +75 -0
  55. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line.py +202 -0
  56. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line_ridershiphistory.py +46 -0
  57. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_line_servicehistory.py +46 -0
  58. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_day.py +107 -0
  59. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_regime.py +104 -0
  60. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_service_regimes.py +104 -0
  61. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary.py +232 -0
  62. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary_totalridershiphistory.py +46 -0
  63. transitmatters-0.1.0/src/transitmatters/_generated/models/dashboard_summary_totalservicehistory.py +46 -0
  64. transitmatters-0.1.0/src/transitmatters/_generated/models/dwell_event.py +130 -0
  65. transitmatters-0.1.0/src/transitmatters/_generated/models/dwell_params.py +71 -0
  66. transitmatters-0.1.0/src/transitmatters/_generated/models/error_response.py +72 -0
  67. transitmatters-0.1.0/src/transitmatters/_generated/models/facilities_response.py +156 -0
  68. transitmatters-0.1.0/src/transitmatters/_generated/models/facilities_response_links.py +64 -0
  69. transitmatters-0.1.0/src/transitmatters/_generated/models/facility.py +171 -0
  70. transitmatters-0.1.0/src/transitmatters/_generated/models/facility_attributes.py +194 -0
  71. transitmatters-0.1.0/src/transitmatters/_generated/models/facility_links.py +64 -0
  72. transitmatters-0.1.0/src/transitmatters/_generated/models/facility_property.py +88 -0
  73. transitmatters-0.1.0/src/transitmatters/_generated/models/facility_relationships.py +101 -0
  74. transitmatters-0.1.0/src/transitmatters/_generated/models/facility_stop_relationship.py +102 -0
  75. transitmatters-0.1.0/src/transitmatters/_generated/models/get_line_delays_agg.py +9 -0
  76. transitmatters-0.1.0/src/transitmatters/_generated/models/get_scheduled_service_agg.py +10 -0
  77. transitmatters-0.1.0/src/transitmatters/_generated/models/get_scheduled_service_response.py +128 -0
  78. transitmatters-0.1.0/src/transitmatters/_generated/models/get_service_hours_agg.py +10 -0
  79. transitmatters-0.1.0/src/transitmatters/_generated/models/get_trip_metrics_agg.py +10 -0
  80. transitmatters-0.1.0/src/transitmatters/_generated/models/headway_aggregate_by_date.py +173 -0
  81. transitmatters-0.1.0/src/transitmatters/_generated/models/headway_event.py +144 -0
  82. transitmatters-0.1.0/src/transitmatters/_generated/models/headway_params.py +71 -0
  83. transitmatters-0.1.0/src/transitmatters/_generated/models/healthcheck_response.py +133 -0
  84. transitmatters-0.1.0/src/transitmatters/_generated/models/healthcheck_response_failed_checks.py +46 -0
  85. transitmatters-0.1.0/src/transitmatters/_generated/models/json_api_resource_identifier.py +72 -0
  86. transitmatters-0.1.0/src/transitmatters/_generated/models/json_api_version.py +78 -0
  87. transitmatters-0.1.0/src/transitmatters/_generated/models/line_delay_entry.py +200 -0
  88. transitmatters-0.1.0/src/transitmatters/_generated/models/live_alert.py +182 -0
  89. transitmatters-0.1.0/src/transitmatters/_generated/models/ridership_entry.py +72 -0
  90. transitmatters-0.1.0/src/transitmatters/_generated/models/ridership_params.py +88 -0
  91. transitmatters-0.1.0/src/transitmatters/_generated/models/route_not_found_response.py +64 -0
  92. transitmatters-0.1.0/src/transitmatters/_generated/models/routes_response.py +91 -0
  93. transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_count.py +72 -0
  94. transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_params.py +98 -0
  95. transitmatters-0.1.0/src/transitmatters/_generated/models/scheduled_service_params_agg.py +10 -0
  96. transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_entry.py +80 -0
  97. transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_params.py +97 -0
  98. transitmatters-0.1.0/src/transitmatters/_generated/models/service_hours_params_agg.py +10 -0
  99. transitmatters-0.1.0/src/transitmatters/_generated/models/service_levels_by_day_kind.py +176 -0
  100. transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response.py +116 -0
  101. transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response_linedata.py +63 -0
  102. transitmatters-0.1.0/src/transitmatters/_generated/models/service_ridership_dashboard_response_modedata.py +63 -0
  103. transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restriction_zone.py +172 -0
  104. transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restrictions_params.py +78 -0
  105. transitmatters-0.1.0/src/transitmatters/_generated/models/speed_restrictions_response.py +134 -0
  106. transitmatters-0.1.0/src/transitmatters/_generated/models/station_stop.py +250 -0
  107. transitmatters-0.1.0/src/transitmatters/_generated/models/station_stop_stops.py +56 -0
  108. transitmatters-0.1.0/src/transitmatters/_generated/models/stops_response.py +139 -0
  109. transitmatters-0.1.0/src/transitmatters/_generated/models/stops_response_direction.py +46 -0
  110. transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_bucket.py +112 -0
  111. transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_params.py +67 -0
  112. transitmatters-0.1.0/src/transitmatters/_generated/models/time_prediction_week.py +94 -0
  113. transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_aggregate_2_response.py +96 -0
  114. transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_event.py +175 -0
  115. transitmatters-0.1.0/src/transitmatters/_generated/models/travel_time_params.py +85 -0
  116. transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_by_line_params.py +97 -0
  117. transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_by_line_params_agg.py +10 -0
  118. transitmatters-0.1.0/src/transitmatters/_generated/models/trip_metrics_entry.py +143 -0
  119. transitmatters-0.1.0/src/transitmatters/_generated/types.py +54 -0
  120. transitmatters-0.1.0/src/transitmatters/client.py +761 -0
  121. transitmatters-0.1.0/src/transitmatters/errors.py +33 -0
  122. transitmatters-0.1.0/src/transitmatters/models.py +12 -0
  123. transitmatters-0.1.0/src/transitmatters/py.typed +0 -0
  124. transitmatters-0.1.0/tests/test_client.py +207 -0
  125. 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,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Built API docs
13
+ site/
@@ -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,6 @@
1
+ def main():
2
+ print("Hello from transitmatters-py!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -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()