From 0e49d1872137f37f18a01c2c5bd35182586196ed Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 1 Jul 2024 18:50:08 +0100 Subject: [PATCH 1/5] Correct spelling mistake --- web_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_view.py b/web_view.py index ca643c4..2cd1e80 100755 --- a/web_view.py +++ b/web_view.py @@ -35,7 +35,7 @@ agenda.error_mail.setup_error_mail(app) @app.before_request def handle_auth() -> None: - """Handle autentication and set global user.""" + """Handle authentication and set global user.""" flask.g.user = UniAuth.auth.get_current_user() From 01b42845c3974da7b79b738f05399c34f87372c8 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 1 Jul 2024 22:22:01 +0300 Subject: [PATCH 2/5] Move some functions into a utils module --- agenda/types.py | 42 +++++++++++------------------------------- agenda/utils.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 agenda/utils.py diff --git a/agenda/types.py b/agenda/types.py index 6d18909..a8f3a8f 100644 --- a/agenda/types.py +++ b/agenda/types.py @@ -12,28 +12,12 @@ from pycountry.db import Country import agenda from agenda import format_list_with_ampersand +from . import utils + StrDict = dict[str, typing.Any] DateOrDateTime = datetime.datetime | datetime.date -def as_date(d: DateOrDateTime) -> datetime.date: - """Convert datetime to date.""" - if isinstance(d, datetime.datetime): - return d.date() - assert isinstance(d, datetime.date) - return d - - -def as_datetime(d: DateOrDateTime) -> datetime.datetime: - """Date/time of event.""" - t0 = datetime.datetime.min.time() - return ( - d - if isinstance(d, datetime.datetime) - else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc) - ) - - @dataclass class TripElement: """Trip element.""" @@ -106,18 +90,20 @@ class Trip: def end(self) -> datetime.date | None: """End date for trip.""" max_conference_end = ( - max(as_date(item["end"]) for item in self.conferences) + max(utils.as_date(item["end"]) for item in self.conferences) if self.conferences else datetime.date.min ) assert isinstance(max_conference_end, datetime.date) - arrive = [as_date(item["arrive"]) for item in self.travel if "arrive" in item] + arrive = [ + utils.as_date(item["arrive"]) for item in self.travel if "arrive" in item + ] travel_end = max(arrive) if arrive else datetime.date.min assert isinstance(travel_end, datetime.date) accommodation_end = ( - max(as_date(item["to"]) for item in self.accommodation) + max(utils.as_date(item["to"]) for item in self.accommodation) if self.accommodation else datetime.date.min ) @@ -314,7 +300,7 @@ class Trip: ) ) - return sorted(elements, key=lambda e: as_datetime(e.start_time)) + return sorted(elements, key=lambda e: utils.as_datetime(e.start_time)) def elements_grouped_by_day(self) -> list[tuple[datetime.date, list[TripElement]]]: """Group trip elements by day.""" @@ -325,7 +311,7 @@ class Trip: for element in self.elements(): # Extract the date part of the 'when' attribute - day = as_date(element.start_time) + day = utils.as_date(element.start_time) grouped_elements[day].append(element) # Sort elements within each day @@ -334,7 +320,7 @@ class Trip: key=lambda e: ( e.element_type == "check-in", # check-out elements last e.element_type != "check-out", # check-in elements first - as_datetime(e.start_time), # then sort by time + utils.as_datetime(e.start_time), # then sort by time ) ) @@ -403,13 +389,7 @@ class Event: @property def as_datetime(self) -> datetime.datetime: """Date/time of event.""" - d = self.date - t0 = datetime.datetime.min.time() - return ( - d - if isinstance(d, datetime.datetime) - else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc) - ) + return utils.as_datetime(self.date) @property def has_time(self) -> bool: diff --git a/agenda/utils.py b/agenda/utils.py new file mode 100644 index 0000000..0324df3 --- /dev/null +++ b/agenda/utils.py @@ -0,0 +1,23 @@ +"""Utility functions.""" + +import datetime + +DateOrDateTime = datetime.datetime | datetime.date + + +def as_date(d: DateOrDateTime) -> datetime.date: + """Convert datetime to date.""" + if isinstance(d, datetime.datetime): + return d.date() + assert isinstance(d, datetime.date) + return d + + +def as_datetime(d: DateOrDateTime) -> datetime.datetime: + """Date/time of event.""" + t0 = datetime.datetime.min.time() + return ( + d + if isinstance(d, datetime.datetime) + else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc) + ) From c41bcc33041adc488fc020cf48e54f59adad85dd Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 1 Jul 2024 22:22:23 +0300 Subject: [PATCH 3/5] Catch errors retrieving FX rates and return cached version --- agenda/fx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/agenda/fx.py b/agenda/fx.py index 90cbc5f..efd4d7d 100644 --- a/agenda/fx.py +++ b/agenda/fx.py @@ -90,10 +90,14 @@ def get_rates(config: flask.config.Config) -> dict[str, Decimal]: except httpx.ConnectError: return read_cached_rates(full_path, currencies) + try: + data = json.loads(response.text, parse_float=Decimal) + except json.decoder.JSONDecodeError: + return read_cached_rates(full_path, currencies) + with open(os.path.join(fx_dir, filename), "w") as file: file.write(response.text) - data = json.loads(response.text, parse_float=Decimal) return { cur: Decimal(data["quotes"][f"GBP{cur}"]) for cur in currencies From b65d79cb633a0d83ef696f5efda70d0e8b2c1e96 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 1 Jul 2024 22:27:19 +0300 Subject: [PATCH 4/5] Add filters for space launches --- agenda/thespacedevs.py | 7 +++++-- templates/launches.html | 37 +++++++++++++++++++++++++++++++++++-- web_view.py | 28 ++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/agenda/thespacedevs.py b/agenda/thespacedevs.py index 15ad455..3192448 100644 --- a/agenda/thespacedevs.py +++ b/agenda/thespacedevs.py @@ -136,7 +136,7 @@ def summarize_launch(launch: Launch) -> Summary: "launch_provider": launch_provider, "launch_provider_abbrev": launch_provider_abbrev, "launch_provider_type": get_nested(launch, ["launch_service_provider", "type"]), - "rocket": launch["rocket"]["configuration"]["full_name"], + "rocket": launch["rocket"]["configuration"], "mission": launch.get("mission"), "mission_name": get_nested(launch, ["mission", "name"]), "pad_name": launch["pad"]["name"], @@ -174,7 +174,10 @@ def get_launches( existing.sort(reverse=True) if refresh or not existing or (now - existing[0][0]).seconds > ttl: - return next_launch_api(rocket_dir, limit=limit) + try: + return next_launch_api(rocket_dir, limit=limit) + except Exception: + pass # fallback to cached version f = existing[0][1] diff --git a/templates/launches.html b/templates/launches.html index a8baede..1756424 100644 --- a/templates/launches.html +++ b/templates/launches.html @@ -6,7 +6,40 @@

Space launches

- {% for launch in rockets %} +

Filters

+ +

Mission type: + + {% if request.args.type %}🗙{% endif %} + + {% for t in mission_types | sort %} + {% if t == request.args.type %} + {{ t }} + {% else %} + + {{ t }} + + {% endif %} + {% if not loop.last %} | {% endif %} + {% endfor %} +

+ +

Vehicle: + {% if request.args.rocket %}🗙{% endif %} + + {% for r in rockets | sort %} + {% if r == request.args.rockets %} + {{ r }} + {% else %} + + {{ r }} + + {% endif %} + {% if not loop.last %} | {% endif %} + {% endfor %} +

+ + {% for launch in launches %} {% set highlight =" bg-primary-subtle" if launch.slug in config.FOLLOW_LAUNCHES else "" %} {% set country = get_country(launch.country_code) %}
@@ -24,7 +57,7 @@
{{ country.flag }} - {{ launch.rocket }} + {{ launch.rocket.full_name }} – {{launch.mission.name }} – diff --git a/web_view.py b/web_view.py index 2cd1e80..c3e663a 100755 --- a/web_view.py +++ b/web_view.py @@ -145,10 +145,34 @@ def launch_list() -> str: now = datetime.now() data_dir = app.config["DATA_DIR"] rocket_dir = os.path.join(data_dir, "thespacedevs") - rockets = agenda.thespacedevs.get_launches(rocket_dir, limit=100) + launches = agenda.thespacedevs.get_launches(rocket_dir, limit=100) + + mission_type_filter = flask.request.args.get("type") + rocket_filter = flask.request.args.get("rocket") + + mission_types = { + launch["mission"]["type"] for launch in launches if launch["mission"] + } + + rockets = {launch["rocket"]["full_name"] for launch in launches} + + launches = [ + launch + for launch in launches + if ( + not mission_type_filter + or (launch["mission"] and launch["mission"]["type"] == mission_type_filter) + ) + and (not rocket_filter or launch["rocket"]["full_name"] == rocket_filter) + ] return flask.render_template( - "launches.html", rockets=rockets, now=now, get_country=agenda.get_country + "launches.html", + launches=launches, + rockets=rockets, + now=now, + get_country=agenda.get_country, + mission_types=mission_types, ) From 4e328d401e89089e0c5c8e2e383c359b7b520181 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 1 Jul 2024 22:28:15 +0300 Subject: [PATCH 5/5] No wrap for date in weekend list --- templates/weekends.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/weekends.html b/templates/weekends.html index e787aa6..f443ff6 100644 --- a/templates/weekends.html +++ b/templates/weekends.html @@ -21,7 +21,7 @@ {{ weekend.date.isocalendar().week }} - + {{ weekend.date.strftime("%-d %b %Y") }} {% for day in "saturday", "sunday" %}