Remove journey-client

This commit is contained in:
Andras Schmelczer 2026-01-27 22:38:51 +00:00
parent bd0dd34b6e
commit 6b1539a3e7
5 changed files with 63 additions and 1278 deletions

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,6 @@ tasks:
install: install:
desc: Install dependencies, generate client, and download data desc: Install dependencies, generate client, and download data
cmds: cmds:
- uv run generate_tfl_client.py
- uv sync - uv sync
- cd frontend && npm install - cd frontend && npm install

View file

@ -1,49 +0,0 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.12"
# dependencies = ["openapi-python-client"]
# ///
"""Regenerate the TfL Journey API client from the OpenAPI specification."""
# Run it with:
# uv run generate_tfl_client.py
import subprocess
from pathlib import Path
OPENAPI_SPEC = Path("Journey.yaml")
OUTPUT_PATH = Path("tfl_journey_client")
def main() -> None:
if not OPENAPI_SPEC.exists():
raise FileNotFoundError(f"OpenAPI spec not found: {OPENAPI_SPEC}")
# Skip if client already exists
if OUTPUT_PATH.exists():
print(f"TfL client already exists at {OUTPUT_PATH}, skipping")
return
# Generate the client
print(f"Generating client from {OPENAPI_SPEC}")
result = subprocess.run(
[
"openapi-python-client",
"generate",
"--path",
str(OPENAPI_SPEC),
"--output-path",
str(OUTPUT_PATH),
],
check=True,
)
if result.returncode == 0:
print(f"Client generated successfully at {OUTPUT_PATH}")
else:
print("Client generation failed")
raise SystemExit(1)
if __name__ == "__main__":
main()

View file

@ -1,35 +1,22 @@
import asyncio import asyncio
import os
from typing import Literal from typing import Literal
import warnings import warnings
from collections.abc import Callable from collections.abc import Callable
from http import HTTPStatus from http import HTTPStatus
from httpx import Timeout import httpx
from journey_client import Client
from journey_client.api.journey import (
journey_journey_results_by_path_from_path_to_query_via_query_national_search_query_date_qu as journey_api,
)
from journey_client.models import (
JourneyJourneyResultsByPathFromPathToQueryViaQueryNationalSearchQueryDateQuTimeIs as TimeIs,
)
from journey_client.models import (
JourneyJourneyResultsByPathFromPathToQueryViaQueryNationalSearchQueryDateQuJourneyPreference as JourneyPreference,
)
from journey_client.models import (
JourneyJourneyResultsByPathFromPathToQueryViaQueryNationalSearchQueryDateQuCyclePreference as CyclePreference,
)
from journey_client.models import (
JourneyJourneyResultsByPathFromPathToQueryViaQueryNationalSearchQueryDateQuBikeProficiency as BikeProficiency,
)
from journey_client.types import Unset
from .config import MAX_DELAY from .config import MAX_DELAY
from .models import Destination, JourneyResult from .models import Destination, JourneyResult
from .rate_limiter import RateLimiter from .rate_limiter import RateLimiter
BASE_URL = "https://api.tfl.gov.uk"
async def fetch_journey_for_mode( async def fetch_journey_for_mode(
client: Client, client: httpx.AsyncClient,
rate_limiter: RateLimiter, rate_limiter: RateLimiter,
from_location: str, from_location: str,
to_location: str, to_location: str,
@ -44,65 +31,77 @@ async def fetch_journey_for_mode(
try: try:
await rate_limiter.acquire() await rate_limiter.acquire()
cycle_preference = { journey_preference = {
"quick": CyclePreference.TAKEONTRANSPORT, "quick": "LeastTime",
"easy": CyclePreference.NONE, "easy": "LeastInterchange",
"cycle": CyclePreference.ALLTHEWAY, "cycle": None,
}[journey_type] }[journey_type]
# options: public-bus,overground,train,tube,coach,dlr,cablecar,tram,river,walking,cycle cycle_preference = {
"quick": None,
"easy": None,
"cycle": "AllTheWay",
}[journey_type]
# curl -s "https://api.tfl.gov.uk/Journey/Meta/Modes" | jq '.[].modeName'
mode = { mode = {
"quick": [ "quick": [
"public-bus", "bus",
"overground", "overground",
"train", "national-rail",
"international-rail",
"elizabeth-line",
"tube", "tube",
"coach", "coach",
"dlr", "dlr",
"cablecar", "cable-car",
"replacement-bus",
"tram", "tram",
"river", "river-bus",
"walking", "walking",
"cycle", "cycle",
], ],
"easy": [ "easy": [
"public-bus", "bus",
"overground", "overground",
"train", "national-rail",
"international-rail",
"elizabeth-line",
"replacement-bus",
"tube", "tube",
"coach", "coach",
"dlr", "dlr",
"cablecar", "cable-car",
"tram", "tram",
"river", "river-bus",
], ],
"cycle": ["cycle"], "cycle": ["cycle"],
}[journey_type] }[journey_type]
response = await journey_api.asyncio_detailed( params: dict = {
from_=from_location, "date": journey_date,
to=to_location, "time": journey_time,
client=client, "nationalSearch": "true",
date=journey_date, "timeIs": "Arriving",
time=journey_time, "cyclePreference": cycle_preference,
national_search=True, "bikeProficiency": "Fast",
time_is=TimeIs.ARRIVING, "walkingOptimization": str(journey_type == "quick").lower(),
journey_preference=JourneyPreference.LEASTINTERCHANGE "mode": ",".join(mode),
if journey_type == "easy" }
else JourneyPreference.LEASTINTERCHANGE, if journey_preference:
cycle_preference=cycle_preference, params["journeyPreference"] = journey_preference
bike_proficiency=BikeProficiency.FAST,
walking_optimization=journey_type == "quick",
mode=mode,
)
if response.status_code == HTTPStatus.OK and response.parsed: url = f"/Journey/JourneyResults/{from_location}/to/{to_location}"
journeys = response.parsed.journeys response = await client.get(url, params=params)
if not isinstance(journeys, Unset) and journeys:
if response.status_code == HTTPStatus.OK:
data = response.json()
journeys = data.get("journeys", [])
if journeys:
durations = [ durations = [
j.duration j["duration"]
for j in journeys for j in journeys
if not isinstance(j.duration, Unset) if j.get("duration") is not None
] ]
if durations: if durations:
return min(durations) return min(durations)
@ -115,7 +114,7 @@ async def fetch_journey_for_mode(
HTTPStatus.GATEWAY_TIMEOUT, HTTPStatus.GATEWAY_TIMEOUT,
): ):
warnings.warn( warnings.warn(
f"HTTP {response.status_code.value} for {journey_type} from {from_location}, " f"HTTP {response.status_code} for {journey_type} from {from_location}, "
f"retrying in {backoff:.1f}s (attempt {attempt + 1}/{retry_count})", f"retrying in {backoff:.1f}s (attempt {attempt + 1}/{retry_count})",
stacklevel=2, stacklevel=2,
) )
@ -141,7 +140,7 @@ async def fetch_journey_for_mode(
async def fetch_all_modes( async def fetch_all_modes(
client: Client, client: httpx.AsyncClient,
rate_limiter: RateLimiter, rate_limiter: RateLimiter,
postcode: str, postcode: str,
lat: float, lat: float,
@ -220,8 +219,15 @@ async def fetch_journey_times(
to_location = dest.to_tfl_location() to_location = dest.to_tfl_location()
rate_limiter = RateLimiter() rate_limiter = RateLimiter()
client = Client(base_url="https://api.tfl.gov.uk").with_timeout(Timeout(30)) # TFL API authentication via app_key query parameter
async with client as client: tfl_token = os.environ.get("TFL_TOKEN")
params = {"app_key": tfl_token} if tfl_token else {}
async with httpx.AsyncClient(
base_url=BASE_URL,
params=params,
timeout=httpx.Timeout(30),
) as client:
tasks = [ tasks = [
fetch_all_modes( fetch_all_modes(
client, client,

View file

@ -8,7 +8,6 @@ dependencies = [
"attrs>=22.2.0", "attrs>=22.2.0",
"httpx>=0.28.1", "httpx>=0.28.1",
"ipywidgets>=8.0.0", "ipywidgets>=8.0.0",
"journey-client",
"jupyter>=1.0.0", "jupyter>=1.0.0",
"nest-asyncio>=1.6.0", "nest-asyncio>=1.6.0",
"numpy>=1.26.0", "numpy>=1.26.0",