Remove journey-client
This commit is contained in:
parent
bd0dd34b6e
commit
6b1539a3e7
5 changed files with 63 additions and 1278 deletions
1170
Journey.yaml
1170
Journey.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue