Fully typed Python client for Transport for London (TfL) Unified API, using Pydantic for response validation and httpx as the default HTTP client (with optional requests support).
Note: This package is auto-generated from TfL's OpenAPI specification. All client methods and response models exactly mirror the official TfL Unified API structure. This means method names and parameters match the TfL API documentation directly. You can search the API portal to find what you need, and then use the same names here.
Originally created as a replacement for TfL-python-api which depends on the deprecated msrest package.
- major change: changed default http library has changed from
requeststohttpx. The Clients and APIs are all exactly the same and you should not notice any difference. See below for instructions on how to userequestsif you prefer that. - Added async clients. So for each client (e.g.
LineClient) there's now an async couterpart (AsyncLineClient). Other behavior of the clients is identical. - Fixed some parsing errors with the ApiError response. You will now always get either
ResponseModelorApiErrorwhen making requests (previously you may have hadResponseModel[ApiError]which was not expected behaviour). - Removed CHANGELOG as it wasnt being maintained. Please refer to git history for changes.
# Default installation (httpx - supports both sync and async)
pip install pydantic-tfl-api
# Or with uv
uv add pydantic-tfl-apiTo use requests instead of httpx (sync only):
pip install pydantic-tfl-api[requests]The package provides 14 API clients, each corresponding to a TfL API endpoint category. Import directly from pydantic_tfl_api:
from pydantic_tfl_api import LineClient, StopPointClient, JourneyClient| Client | Description |
|---|---|
LineClient |
Tube, Overground, DLR, Elizabeth line status and information |
StopPointClient |
Bus stops, tube stations, piers, and other stop points |
JourneyClient |
Journey planning between locations |
VehicleClient |
Vehicle arrival predictions |
BikePointClient |
Santander Cycles docking stations |
AirQualityClient |
London air quality data and forecasts |
AccidentStatsClient |
Road accident statistics |
CrowdingClient |
Station crowding data |
OccupancyClient |
Bike point and car park occupancy |
PlaceClient |
Points of interest and places |
RoadClient |
Road corridor status and disruptions |
SearchClient |
Search for stops, stations, postcodes |
ModeClient |
Transport mode information |
LiftDisruptionsClient |
Lift and escalator disruption data |
Async clients: For async operations, prefix with Async (e.g., AsyncLineClient, AsyncStopPointClient).
This package is automatically generated from TfL's OpenAPI specification, which means:
- Client methods match TfL API operation IDs - Method names come directly from the OpenAPI spec (e.g.,
MetaModes,GetByModeByPathModes,StatusByIdsByPathIdsQueryDetail) - Parameters match the API exactly - Path parameters include
ByPathin the name, query parameters includeQuery - All responses are Pydantic models - Full validation, type hints, and IDE autocomplete
- 117 Pydantic models - Covering all TfL API response types
from pydantic_tfl_api import LineClient
# Initialize client with your TfL API key
# Get a free key at: https://api-portal.tfl.gov.uk/profile
# Note: You only need a key for >1 request per second
client = LineClient(api_token="your_api_token_here")
# Call a method - names match TfL API operation IDs
# 'GetByModeByPathModes' = Get lines by mode, where mode is a path parameter
response = client.GetByModeByPathModes(modes="tube")
# Response is wrapped in ResponseModel with cache info
# - response.content: The actual data (Pydantic model)
# - response.content_expires: Cache expiry time
# - response.shared_expires: Shared cache expiry time
# For array responses, data is in a RootModel with .root attribute
lines = response.content.root
# Each item is a fully-typed Pydantic model
for line in lines:
print(f"{line.id}: {line.name}") # e.g., "victoria: Victoria"
# Nested data is also typed
if line.lineStatuses:
for status in line.lineStatuses:
print(f" Status: {status.statusSeverityDescription}")Method names are generated from TfL's OpenAPI operation IDs. Here's the pattern:
from pydantic_tfl_api import LineClient
client = LineClient(api_token="your_key")
# Simple methods - just the operation name
response = client.MetaModes() # GET /Line/Meta/Modes
# Methods with path parameters - "ByPath{ParamName}"
response = client.GetByModeByPathModes(modes="tube") # GET /Line/Mode/{modes}
# Methods with query parameters - "Query{ParamName}"
response = client.StatusByIdsByPathIdsQueryDetail(
ids="victoria", # Path parameter
detail=True # Query parameter
) # GET /Line/{ids}/Status?detail=true
# Complex method example
response = client.StatusByModeByPathModesQueryDetailQuerySeverityLevel(
modes="tube", # Path parameter
detail=True, # Query parameter
severityLevel="Minor" # Query parameter
)from pydantic_tfl_api import LineClient
client = LineClient()
# List all available methods
public_methods = [m for m in dir(client) if not m.startswith('_') and callable(getattr(client, m))]
print(public_methods)
# ['ArrivalsByPathIds', 'DisruptionByModeByPathModes', 'GetByModeByPathModes',
# 'GetByPathIds', 'MetaDisruptionCategories', 'MetaModes', 'MetaSeverity', ...]
# Get help on a specific method (shows signature and parameters)
help(client.StatusByIdsByPathIdsQueryDetail)All responses are Pydantic models with full type hints and validation:
from pydantic_tfl_api import LineClient
client = LineClient(api_token="your_key")
response = client.StatusByIdsByPathIdsQueryDetail(ids="victoria", detail=True)
# Check for errors
from pydantic_tfl_api.core import ApiError
if isinstance(response, ApiError):
print(f"Error {response.httpStatusCode}: {response.message}")
else:
# Access the typed response data
for line in response.content.root:
print(f"Line: {line.name}")
print(f"Mode: {line.modeName}")
for status in line.lineStatuses:
print(f" Severity: {status.statusSeverity}")
print(f" Description: {status.statusSeverityDescription}")
print(f" Reason: {status.reason}")
# Convert to JSON
print(response.content.model_dump_json(indent=2))
# Access cache information
print(f"Expires: {response.content_expires}")For concurrent requests, use async clients (requires httpx - the default):
import asyncio
from pydantic_tfl_api import AsyncLineClient
async def get_multiple_lines():
# Create async client
client = AsyncLineClient(api_token="your_key")
# Fetch multiple lines concurrently
victoria, central, northern = await asyncio.gather(
client.StatusByIdsByPathIdsQueryDetail(ids="victoria"),
client.StatusByIdsByPathIdsQueryDetail(ids="central"),
client.StatusByIdsByPathIdsQueryDetail(ids="northern")
)
return victoria, central, northern
# Run the async function
results = asyncio.run(get_multiple_lines())
for result in results:
print(result.content.root[0].name)By default, the package uses httpx which supports both sync and async operations.
To use requests instead (sync only):
from pydantic_tfl_api import LineClient
from pydantic_tfl_api.core import RequestsClient
# Create a requests-based HTTP client
http_client = RequestsClient()
# Pass it to any API client
client = LineClient(api_token="your_key", http_client=http_client)
response = client.MetaModes()The package includes 117 Pydantic models representing all TfL API response types. Models are in the models module:
from pydantic_tfl_api import models
# Access any model directly
line = models.Line(id="victoria", name="Victoria", ...)Key points about models:
- Circular references are handled using
ForwardRef - Field names match the API except for Python reserved words (e.g.,
class→class_) - Array responses are wrapped in
RootModel(access via.rootattribute) - Unknown fields use
Dict[str, Any]when TfL provides no schema
See the Mermaid class diagram for a visualization of all models.
Clients are in the endpoints module and inherit from core.Client (sync) or core.AsyncClient (async). All 14 API categories have both sync and async versions.
The devcontainer uses the uv package manager. The uv.lock file is checked in, so dependencies are installed automatically.
Common development commands:
uv sync # Install dependencies
uv run pytest # Run tests
uv run black . # Format code
uv run flake8 . # Lint code
uv build # Build the packageTo test the build:
./build.sh "/workspaces/pydantic_tfl_api/pydantic_tfl_api" "/workspaces/pydantic_tfl_api/TfL_OpenAPI_specs" TrueContributions are welcome! Please note that this is a code-generated package - modifications should be made to the generation scripts in /scripts/build_system/, not to the generated files in /pydantic_tfl_api/endpoints/ or /pydantic_tfl_api/models/.
MIT License