Tracked nr_requests.py and added fetch_nr_timetable_files.
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Expose classes and functions to avoid unneeded nesting.
|
||||||
|
"""
|
||||||
|
# pyright: reportUnusedImport=false
|
||||||
|
# ruff: noqa F401
|
||||||
|
|
||||||
|
# Imports
|
||||||
|
from national_rail_timetable.nr_requests import (
|
||||||
|
NRConfig,
|
||||||
|
fetch_nr_token,
|
||||||
|
fetch_nr_timetable_files,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from national_rail_timetable.nr_requests import fetch_nr_token
|
from national_rail_timetable.nr_requests import fetch_nr_token, fetch_nr_timetable_files
|
||||||
|
|
||||||
print(fetch_nr_token())
|
print(fetch_nr_token())
|
||||||
|
print(fetch_nr_timetable_files())
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
"""
|
||||||
|
Functions for authentication / timetable requests with National Rail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Imports
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from zipfile import ZipFile
|
||||||
|
from io import BytesIO
|
||||||
|
from time import sleep
|
||||||
|
import requests
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
|
|
||||||
|
# Init.
|
||||||
|
@dataclass
|
||||||
|
class NRConfig:
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_env(cls) -> "NRConfig":
|
||||||
|
try:
|
||||||
|
return cls(
|
||||||
|
username=os.environ["NR_USER"],
|
||||||
|
password=os.environ["NR_PASS"],
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(
|
||||||
|
"Not all necessary environment variables are available. "
|
||||||
|
+ "Make sure NR_USER and NR_PASS are defined at config generation time. "
|
||||||
|
+ "Alternatively, provide an NRConfig instance defined manually. "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def fetch_nr_token(
|
||||||
|
config: NRConfig | None = None, # pyright: ignore[reportRedeclaration]
|
||||||
|
attempts: int = 3,
|
||||||
|
) -> str:
|
||||||
|
config: NRConfig = config if config is not None else NRConfig.from_env()
|
||||||
|
response: Response = requests.post(
|
||||||
|
url="https://opendata.nationalrail.co.uk/authenticate",
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
data=config.__dict__,
|
||||||
|
)
|
||||||
|
if not response.ok:
|
||||||
|
if attempts > 1:
|
||||||
|
sleep(3)
|
||||||
|
return fetch_nr_token(
|
||||||
|
config=config,
|
||||||
|
attempts=attempts - 1,
|
||||||
|
)
|
||||||
|
raise requests.HTTPError(
|
||||||
|
"Failed to fetch token using credentials provided in config. Response: "
|
||||||
|
+ f"[{response.status_code}] "
|
||||||
|
+ response.content.decode()
|
||||||
|
)
|
||||||
|
content: str = response.content.decode()
|
||||||
|
assert isinstance(token := json.loads(s=content)["token"], str) # pyright: ignore[reportAny]
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_nr_timetable_files(
|
||||||
|
config: NRConfig | None = None, # pyright: ignore[reportRedeclaration]
|
||||||
|
token: str | None = None, # pyright: ignore[reportRedeclaration]
|
||||||
|
attempts: int = 1,
|
||||||
|
) -> ZipFile:
|
||||||
|
config: NRConfig = config if config is not None else NRConfig.from_env()
|
||||||
|
token: str = (
|
||||||
|
token
|
||||||
|
if token is not None
|
||||||
|
else fetch_nr_token(
|
||||||
|
config=config,
|
||||||
|
attempts=attempts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
response: Response = requests.get(
|
||||||
|
url="https://opendata.nationalrail.co.uk/api/staticfeeds/3.0/timetable",
|
||||||
|
headers={"X-Auth-Token": token, "Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
if not response.ok:
|
||||||
|
if attempts > 1:
|
||||||
|
sleep(3)
|
||||||
|
return fetch_nr_timetable_files(
|
||||||
|
config=config,
|
||||||
|
token=token,
|
||||||
|
attempts=attempts - 1,
|
||||||
|
)
|
||||||
|
raise requests.HTTPError(
|
||||||
|
"Failed to fetch timetable files using token and config provided. Response: "
|
||||||
|
+ f"[{response.status_code}] "
|
||||||
|
+ response.content.decode()
|
||||||
|
)
|
||||||
|
zipped_bytes: bytes = response.content
|
||||||
|
return ZipFile(
|
||||||
|
BytesIO(initial_bytes=zipped_bytes),
|
||||||
|
mode="r",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user