Skip to content

mnbf9rca/pydantic_tfl_api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pydantic-tfl-api

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.

What's changed in V3?

  • major change: changed default http library has changed from requests to httpx. The Clients and APIs are all exactly the same and you should not notice any difference. See below for instructions on how to use requests if 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 ResponseModel or ApiError when making requests (previously you may have had ResponseModel[ApiError] which was not expected behaviour).
  • Removed CHANGELOG as it wasnt being maintained. Please refer to git history for changes.

Installation

# Default installation (httpx - supports both sync and async)
pip install pydantic-tfl-api

# Or with uv
uv add pydantic-tfl-api

To use requests instead of httpx (sync only):

pip install pydantic-tfl-api[requests]

Available API Clients

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).

How It Works

This package is automatically generated from TfL's OpenAPI specification, which means:

  1. Client methods match TfL API operation IDs - Method names come directly from the OpenAPI spec (e.g., MetaModes, GetByModeByPathModes, StatusByIdsByPathIdsQueryDetail)
  2. Parameters match the API exactly - Path parameters include ByPath in the name, query parameters include Query
  3. All responses are Pydantic models - Full validation, type hints, and IDE autocomplete
  4. 117 Pydantic models - Covering all TfL API response types

Usage

Basic Example (Annotated)

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}")

Understanding Method Names

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
)

Discovering Available Methods

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)

Working with Response Models

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}")

Async Example

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)

HTTP Client Selection

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()

Class Structure

Models

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., classclass_)
  • Array responses are wrapped in RootModel (access via .root attribute)
  • Unknown fields use Dict[str, Any] when TfL provides no schema

See the Mermaid class diagram for a visualization of all models.

Clients

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.

Development Environment

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 package

To test the build:

./build.sh "/workspaces/pydantic_tfl_api/pydantic_tfl_api" "/workspaces/pydantic_tfl_api/TfL_OpenAPI_specs" True

Contributing

Contributions 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/.

License

MIT License