Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/cosmic-daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ on:
required: true
default: false
type: boolean
schedule:
# 12:00 UTC (08:00 EDT / 07:00 EST) — APOD for the current US day is
# already published by then. Always opens a PR for human review; it
# never auto-merges.
- cron: "0 12 * * *"

permissions:
contents: write
Expand Down Expand Up @@ -46,6 +51,18 @@ jobs:
fi
echo "value=$target" >> "$GITHUB_OUTPUT"

- name: Resolve publish flag
id: mode
shell: bash
run: |
# Manual runs use the publish input; scheduled runs always publish
# (i.e. generate + open a PR for review, never auto-merge).
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
else
echo "publish=${{ inputs.publish }}" >> "$GITHUB_OUTPUT"
fi

- name: Preview or generate APOD content
id: apod
if: always()
Expand All @@ -55,7 +72,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
if [ "${{ inputs.publish }}" = "true" ]; then
if [ "${{ steps.mode.outputs.publish }}" = "true" ]; then
python -m cosmic_daily generate --date "${{ steps.target.outputs.value }}" | tee /tmp/cosmic-daily.log
else
python -m cosmic_daily preview --date "${{ steps.target.outputs.value }}" | tee /tmp/cosmic-daily.log
Expand All @@ -67,15 +84,15 @@ jobs:
fi

- name: Validate generated post
if: ${{ inputs.publish == true && steps.apod.outputs.post_path != '' }}
if: ${{ steps.mode.outputs.publish == 'true' && steps.apod.outputs.post_path != '' }}
working-directory: tools/cosmic-daily
shell: bash
run: |
set -euo pipefail
python -m cosmic_daily check --post-path "${{ steps.apod.outputs.post_path }}"

- name: Create pull request
if: ${{ inputs.publish == true && steps.apod.outputs.post_path != '' }}
if: ${{ steps.mode.outputs.publish == 'true' && steps.apod.outputs.post_path != '' }}
uses: peter-evans/create-pull-request@v6
with:
branch: "cosmic-daily/${{ steps.target.outputs.value }}"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
!.env.example
__pycache__/
*.py[cod]
*.egg-info/
.pytest_cache/
.venv/
venv/
Expand Down
34 changes: 25 additions & 9 deletions tools/cosmic-daily/cosmic_daily/nasa_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import time
from dataclasses import dataclass
from datetime import date
from typing import Any, Dict, Optional
Expand All @@ -10,6 +11,11 @@


APOD_ENDPOINT = "https://api.nasa.gov/planetary/apod"
# DEMO_KEY is a shared, rate-limited key; requests can be slow under load, so
# use a generous read timeout and retry a couple of times before giving up.
REQUEST_TIMEOUT = (10, 45)
MAX_ATTEMPTS = 3
RETRY_BACKOFF_SECONDS = 5


@dataclass
Expand Down Expand Up @@ -51,15 +57,25 @@ def fetch_apod(day: str | date | None = None, api_key: str | None = None) -> APO
target_day = day.isoformat() if isinstance(day, date) else (str(day) if day else date.today().isoformat())
configured_key = api_key or os.getenv("NASA_API_KEY") or "DEMO_KEY"

try:
response = requests.get(
APOD_ENDPOINT,
params={"api_key": configured_key, "date": target_day},
timeout=20,
allow_redirects=False,
)
except requests.RequestException as exc:
raise RuntimeError(f"Failed to fetch APOD for {target_day}: {exc}") from exc
response = None
last_error: requests.RequestException | None = None
for attempt in range(1, MAX_ATTEMPTS + 1):
try:
response = requests.get(
APOD_ENDPOINT,
params={"api_key": configured_key, "date": target_day},
timeout=REQUEST_TIMEOUT,
allow_redirects=False,
)
break
except requests.RequestException as exc:
last_error = exc
if attempt < MAX_ATTEMPTS:
time.sleep(RETRY_BACKOFF_SECONDS * attempt)
if response is None:
raise RuntimeError(
f"Failed to fetch APOD for {target_day} after {MAX_ATTEMPTS} attempts: {last_error}"
) from last_error

if response.status_code != 200:
raise RuntimeError(f"NASA API returned HTTP {response.status_code} for {target_day}")
Comment on lines +60 to 81
Expand Down