From 944fe24662494642b9d375478254a0c6a1fa151b Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Sun, 8 Jan 2023 12:22:36 +0000 Subject: [PATCH] Improve display of cabins list --- ferry/__init__.py | 23 ++++++++++++ ferry/api.py | 86 +++++++++++++++++++++++++++++++++++++++++++ ferry/read_config.py | 17 +++++++++ main.py | 41 +++++++++++---------- templates/cabins.html | 5 ++- 5 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 ferry/__init__.py create mode 100644 ferry/api.py create mode 100644 ferry/read_config.py diff --git a/ferry/__init__.py b/ferry/__init__.py new file mode 100644 index 0000000..4436037 --- /dev/null +++ b/ferry/__init__.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + + +@dataclass +class Vehicle: + """What type of vehicle is going on the ferry.""" + + type: str + registration: str + height: int + length: int + + +ports = { + "PORTSMOUTH": "GBPME", + "PLYMOUTH": "GBPLY", + "POOLE": "GBPOO", + "CAEN": "FROUI", + "CHERBOURG": "FRCER", + "ST MALO": "FRSML", +} + +port_lookup = {code: name for name, code in ports.items()} diff --git a/ferry/api.py b/ferry/api.py new file mode 100644 index 0000000..40221aa --- /dev/null +++ b/ferry/api.py @@ -0,0 +1,86 @@ +"""Interface with the Brittany Ferries API.""" + +from typing import Any, TypedDict + +import requests + +from . import Vehicle + +api_root_url = "https://www.brittany-ferries.co.uk/api/ferry/v1/" + + +class VehicleDict(TypedDict): + """Description of vechicle in the format expected by the API.""" + + type: str + registrations: list[str] + height: int + length: int + extras: dict[str, None] + + +def vehicle_dict(v: Vehicle) -> VehicleDict: + """Return vehicle detail in the format for the Brittany Ferries API.""" + return { + "type": v.type, + "registrations": [v.registration], + "height": v.height, + "length": v.length, + "extras": {"rearMountedBikeCarrier": None}, + } + + +def get_prices( + departure_port: str, + arrival_port: str, + from_date: str, + to_date: str, + vehicle: Vehicle, +) -> dict[str, Any]: + """Call Brittany Ferries API to get details of crossings.""" + url = api_root_url + "crossing/prices" + + post_data = { + "bookingReference": None, + "pets": {"smallDogs": 1, "largeDogs": 0, "cats": 0}, + "passengers": {"adults": 2, "children": 0, "infants": 0}, + "vehicle": vehicle_dict(vehicle), + "departurePort": departure_port, + "arrivalPort": arrival_port, + "disability": None, + "sponsor": None, + "fromDate": f"{from_date}T00:00:00", + "toDate": f"{to_date}T23:59:59", + } + + r = requests.post(url, json=post_data) + data: dict[str, Any] = r.json() + return data + + +def get_accommodations( + departure_port: str, + arrival_port: str, + departure_date: str, + ticket_tier: str, + vehicle: Vehicle, +) -> dict[str, Any]: + """Grab cabin details.""" + url = api_root_url + "crossing/accommodations" + post_data = { + "bookingReference": None, + "departurePort": departure_port, + "arrivalPort": arrival_port, + "departureDate": departure_date, + "passengers": {"adults": 2, "children": 0, "infants": 0}, + "disability": None, + "vehicle": vehicle_dict(vehicle), + "petCabinsNeeded": True, + "ticketTier": ticket_tier, + "pets": {"smallDogs": 1, "largeDogs": 0, "cats": 0}, + "sponsor": None, + "offerType": "NONE", + } + + json_data: dict[str, Any] = requests.post(url, json=post_data).json() + return json_data diff --git a/ferry/read_config.py b/ferry/read_config.py new file mode 100644 index 0000000..d03d03f --- /dev/null +++ b/ferry/read_config.py @@ -0,0 +1,17 @@ +import configparser +import os.path + +from . import Vehicle + +ferry_config = configparser.ConfigParser() +ferry_config.read(os.path.expanduser("~edward/config/brittany-ferries/config")) + + +def vehicle_from_config(config: configparser.ConfigParser) -> Vehicle: + """Generate a vehicle object from config.""" + return Vehicle( + type=config.get("vehicle", "type"), + registration=config.get("vehicle", "registration"), + height=config.getint("vehicle", "height"), + length=config.getint("vehicle", "length"), + ) diff --git a/main.py b/main.py index 83f213a..c098478 100755 --- a/main.py +++ b/main.py @@ -9,14 +9,14 @@ import re from datetime import date, datetime, timedelta from typing import Any +import dateutil.parser import flask import pytz -import routes import werkzeug.exceptions from werkzeug.debug.tbtools import get_current_traceback from werkzeug.wrappers import Response -from ferry import ports +import ferry from ferry.api import get_accommodations, get_prices from ferry.read_config import ferry_config, vehicle_from_config @@ -86,22 +86,19 @@ def show_route( """Page showing list of prices.""" prices = get_prices(departure_port, arrival_port) - port_lookup = {code: name for name, code in ports.items()} - return flask.render_template( "route.html", - departure_port=port_lookup[departure_port], - arrival_port=port_lookup[arrival_port], + departure_port=ferry.port_lookup[departure_port], + arrival_port=ferry.port_lookup[arrival_port], days=prices["crossings"], parse_date=parse_date, ) @app.route("/") -def start() -> Response: +def start() -> Response | str: """Start page.""" return flask.render_template("index.html") - return flask.redirect(flask.url_for("outbound_page")) def cabins_url(dep, arr, crossing, ticket_tier): @@ -110,8 +107,8 @@ def cabins_url(dep, arr, crossing, ticket_tier): return flask.url_for( "cabins", - departure_port=ports[dep], - arrival_port=ports[arr], + departure_port=ferry.ports[dep], + arrival_port=ferry.ports[arr], departure_date=utc_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z"), ticket_tier=ticket_tier, ) @@ -150,7 +147,13 @@ def get_prices_with_cache( filename = cache_filename(params) all_data = [ - (dep, arr, get_prices(ports[dep], ports[arr], start, end, vehicle)["crossings"]) + ( + dep, + arr, + get_prices(ferry.ports[dep], ferry.ports[arr], start, end, vehicle)[ + "crossings" + ], + ) for dep, arr in selection ] @@ -180,7 +183,7 @@ def build_outbound(section: str) -> str: "all_routes.html", data=all_data, days_until_start=get_days_until_start(), - ports=ports, + ports=ferry.ports, parse_date=parse_date, from_date=start, to_date=end, @@ -232,7 +235,7 @@ def return_page() -> str: return flask.render_template( "all_routes.html", data=all_data, - ports=ports, + ports=ferry.ports, days_until_start=get_days_until_start(), parse_date=parse_date, from_date=start, @@ -286,22 +289,20 @@ def cabins( if a["quantityAvailable"] > 0 and a["code"] != "RS" # and "Inside" not in a["description"] ] + + dep = dateutil.parser.isoparse(departure_date) + return flask.render_template( "cabins.html", + port_lookup=ferry.port_lookup, departure_port=departure_port, arrival_port=arrival_port, - departure_date=departure_date, + departure_date=dep, ticket_tier=ticket_tier, accommodations=accommodations, pet_accommodations=cabin_data["petAccommodations"], ) -@app.route("/routes") -def route_list() -> str: - """List of routes.""" - return flask.render_template("index.html", routes=routes, ports=ports) - - if __name__ == "__main__": app.run(host="0.0.0.0", port=5001) diff --git a/templates/cabins.html b/templates/cabins.html index 1d71ed7..67b3ce9 100644 --- a/templates/cabins.html +++ b/templates/cabins.html @@ -29,9 +29,10 @@ a:link {
-

{{ departure_port }} to {{ arrival_port }}

+

{{ port_lookup[departure_port].title() }} + to {{ port_lookup[arrival_port].title() }}

-

{{ departure_date }} {{ ticket_tier }}

+

{{ departure_date.strftime("%A, %d %B %Y %H:%M UTC") }} {{ ticket_tier }}