Switch Space to Gradio interface
Browse files- Dockerfile +0 -14
- README.md +5 -5
- app.py +59 -39
- requirements.txt +2 -3
Dockerfile
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
FROM python:3.8-slim
|
| 2 |
-
|
| 3 |
-
# Set the working directory
|
| 4 |
-
WORKDIR /app
|
| 5 |
-
|
| 6 |
-
# Copy the requirements file and install dependencies
|
| 7 |
-
COPY requirements.txt /app/
|
| 8 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 9 |
-
|
| 10 |
-
# Copy the rest of the code
|
| 11 |
-
COPY . /app
|
| 12 |
-
|
| 13 |
-
# Command to run the app
|
| 14 |
-
CMD ["python", "app.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -3,17 +3,17 @@ title: GFES Wave 0 Downloader
|
|
| 3 |
emoji: 🌍
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: gray
|
| 6 |
-
sdk:
|
| 7 |
pinned: false
|
| 8 |
license: cc-by-4.0
|
| 9 |
---
|
| 10 |
|
| 11 |
## GEFS Wave Control Member Downloader
|
| 12 |
|
| 13 |
-
This Space
|
| 14 |
|
| 15 |
-
-
|
| 16 |
-
-
|
| 17 |
-
-
|
| 18 |
|
| 19 |
Data is stored under `data/gefswave` inside the Space. Override this path with the `GEFS_WAVE_DATA_DIR` environment variable if needed. The downloader searches up to five days back to locate the newest complete cycle.
|
|
|
|
| 3 |
emoji: 🌍
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: gray
|
| 6 |
+
sdk: gradio
|
| 7 |
pinned: false
|
| 8 |
license: cc-by-4.0
|
| 9 |
---
|
| 10 |
|
| 11 |
## GEFS Wave Control Member Downloader
|
| 12 |
|
| 13 |
+
This Space now provides a Gradio dashboard that keeps a local cache of the latest GEFS Wave control member (`c00`, “wave 0”) global 0.25° GRIB2 dataset from NOAA NOMADS. Use the interface to:
|
| 14 |
|
| 15 |
+
- Inspect the current downloader status and metadata for the newest available cycle.
|
| 16 |
+
- Trigger a refresh to fetch a more recent cycle if one has been published.
|
| 17 |
+
- Download a `.tar.gz` archive containing every `global.0p25` GRIB2 file for that cycle.
|
| 18 |
|
| 19 |
Data is stored under `data/gefswave` inside the Space. Override this path with the `GEFS_WAVE_DATA_DIR` environment variable if needed. The downloader searches up to five days back to locate the newest complete cycle.
|
app.py
CHANGED
|
@@ -1,78 +1,98 @@
|
|
| 1 |
import os
|
| 2 |
from pathlib import Path
|
| 3 |
from threading import Event
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
from twilio.twiml.voice_response import VoiceResponse
|
| 7 |
|
| 8 |
from gefs_wave import WaveDownloadManager
|
| 9 |
|
| 10 |
-
app = Flask(__name__)
|
| 11 |
DATA_ROOT = Path(os.environ.get("GEFS_WAVE_DATA_DIR", "data/gefswave"))
|
| 12 |
manager = WaveDownloadManager(DATA_ROOT)
|
| 13 |
_refresh_started = Event()
|
| 14 |
|
| 15 |
|
| 16 |
-
def ensure_refresh():
|
| 17 |
-
"""
|
| 18 |
if not _refresh_started.is_set():
|
| 19 |
_refresh_started.set()
|
| 20 |
manager.trigger_refresh()
|
| 21 |
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
ensure_refresh()
|
| 26 |
-
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
-
|
| 30 |
-
def status():
|
| 31 |
-
ensure_refresh()
|
| 32 |
-
return jsonify(manager.status())
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
@app.route("/refresh", methods=["POST"])
|
| 36 |
-
def refresh():
|
| 37 |
ensure_refresh()
|
| 38 |
manager.trigger_refresh()
|
| 39 |
-
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
-
|
| 43 |
-
def download_wave():
|
| 44 |
ensure_refresh()
|
| 45 |
state = manager.downloader.current_state()
|
| 46 |
if not state or "tarball" not in state:
|
| 47 |
-
|
| 48 |
|
| 49 |
tarball_path = DATA_ROOT / state["tarball"]
|
| 50 |
if not tarball_path.exists():
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
| 62 |
)
|
|
|
|
|
|
|
| 63 |
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
"""Responds to incoming calls with a simple text-to-speech message."""
|
| 68 |
-
resp = VoiceResponse()
|
| 69 |
-
resp.say("The GEFS wave zero dataset downloader is online.", voice="alice")
|
| 70 |
|
| 71 |
-
|
| 72 |
|
| 73 |
|
| 74 |
if __name__ == "__main__":
|
| 75 |
ensure_refresh()
|
| 76 |
port = int(os.environ.get("PORT", 7860))
|
| 77 |
-
|
| 78 |
-
app.run(host="0.0.0.0", port=port, debug=debug_mode)
|
|
|
|
| 1 |
import os
|
| 2 |
from pathlib import Path
|
| 3 |
from threading import Event
|
| 4 |
+
from typing import Dict, Tuple
|
| 5 |
|
| 6 |
+
import gradio as gr
|
|
|
|
| 7 |
|
| 8 |
from gefs_wave import WaveDownloadManager
|
| 9 |
|
|
|
|
| 10 |
DATA_ROOT = Path(os.environ.get("GEFS_WAVE_DATA_DIR", "data/gefswave"))
|
| 11 |
manager = WaveDownloadManager(DATA_ROOT)
|
| 12 |
_refresh_started = Event()
|
| 13 |
|
| 14 |
|
| 15 |
+
def ensure_refresh() -> None:
|
| 16 |
+
"""Kick off the downloader thread once per process."""
|
| 17 |
if not _refresh_started.is_set():
|
| 18 |
_refresh_started.set()
|
| 19 |
manager.trigger_refresh()
|
| 20 |
|
| 21 |
|
| 22 |
+
def format_status(status: Dict) -> Tuple[Dict, str]:
|
| 23 |
+
"""Return status JSON and a markdown summary."""
|
| 24 |
+
latest = status.get("latest_state", {})
|
| 25 |
+
lines = [
|
| 26 |
+
f"**Downloader status:** `{status.get('status', 'unknown')}`",
|
| 27 |
+
]
|
| 28 |
+
if "message" in status:
|
| 29 |
+
lines.append(f"- Message: {status['message']}")
|
| 30 |
+
if latest:
|
| 31 |
+
lines.extend(
|
| 32 |
+
[
|
| 33 |
+
f"- Latest cycle: `{latest.get('cycle', 'N/A')}`",
|
| 34 |
+
f"- Updated at: `{latest.get('updated_at', 'N/A')}`",
|
| 35 |
+
f"- Files cached: {len(latest.get('files', []))}",
|
| 36 |
+
]
|
| 37 |
+
)
|
| 38 |
+
if tarball := latest.get("tarball"):
|
| 39 |
+
lines.append(f"- Tarball path: `{tarball}`")
|
| 40 |
+
else:
|
| 41 |
+
lines.append("- No cycle downloaded yet.")
|
| 42 |
+
lines.append("\nUse **Trigger Refresh** to fetch a newer cycle when available.")
|
| 43 |
+
return status, "\n".join(lines)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def get_status() -> Tuple[Dict, str]:
|
| 47 |
ensure_refresh()
|
| 48 |
+
status = manager.status()
|
| 49 |
+
return format_status(status)
|
| 50 |
|
| 51 |
|
| 52 |
+
def trigger_refresh() -> Tuple[Dict, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
ensure_refresh()
|
| 54 |
manager.trigger_refresh()
|
| 55 |
+
status = manager.status()
|
| 56 |
+
return format_status(status)
|
| 57 |
|
| 58 |
|
| 59 |
+
def download_tarball() -> str:
|
|
|
|
| 60 |
ensure_refresh()
|
| 61 |
state = manager.downloader.current_state()
|
| 62 |
if not state or "tarball" not in state:
|
| 63 |
+
raise gr.Error("Wave dataset not available yet. Try triggering a refresh.")
|
| 64 |
|
| 65 |
tarball_path = DATA_ROOT / state["tarball"]
|
| 66 |
if not tarball_path.exists():
|
| 67 |
+
raise gr.Error("Tarball missing on disk. Trigger a refresh and try again.")
|
| 68 |
+
return str(tarball_path)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
with gr.Blocks(title="GFES Wave 0 Downloader") as demo:
|
| 72 |
+
gr.Markdown(
|
| 73 |
+
"""
|
| 74 |
+
## GFES Wave 0 Downloader
|
| 75 |
+
Fetch the latest NOAA GEFS Wave control member (`c00`) global 0.25° dataset.
|
| 76 |
+
|
| 77 |
+
1. Check the status panel to confirm the current cycle.
|
| 78 |
+
2. Press **Trigger Refresh** to request a newer cycle if one is available.
|
| 79 |
+
3. Use **Download Latest Tarball** to grab a `.tar.gz` of every GRIB2 file for that cycle.
|
| 80 |
+
"""
|
| 81 |
)
|
| 82 |
+
status_json = gr.JSON(label="Downloader Status")
|
| 83 |
+
status_md = gr.Markdown()
|
| 84 |
|
| 85 |
+
with gr.Row():
|
| 86 |
+
refresh_button = gr.Button("Trigger Refresh", variant="secondary")
|
| 87 |
+
download_button = gr.DownloadButton("Download Latest Tarball", value=download_tarball, variant="primary")
|
| 88 |
|
| 89 |
+
demo.load(get_status, outputs=[status_json, status_md])
|
| 90 |
+
refresh_button.click(trigger_refresh, outputs=[status_json, status_md])
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
+
demo.queue(default_concurrency_count=2, max_size=8)
|
| 93 |
|
| 94 |
|
| 95 |
if __name__ == "__main__":
|
| 96 |
ensure_refresh()
|
| 97 |
port = int(os.environ.get("PORT", 7860))
|
| 98 |
+
demo.launch(server_name="0.0.0.0", server_port=port, show_api=False)
|
|
|
requirements.txt
CHANGED
|
@@ -1,3 +1,2 @@
|
|
| 1 |
-
|
| 2 |
-
requests
|
| 3 |
-
twilio
|
|
|
|
| 1 |
+
gradio>=5.0.0
|
| 2 |
+
requests>=2.31.0
|
|
|