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 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/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) + ) 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 @@
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) %}