nakas commited on
Commit
88e6644
·
1 Parent(s): 558e0b2

Switch Space to Gradio interface

Browse files
Files changed (4) hide show
  1. Dockerfile +0 -14
  2. README.md +5 -5
  3. app.py +59 -39
  4. 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: docker
7
  pinned: false
8
  license: cc-by-4.0
9
  ---
10
 
11
  ## GEFS Wave Control Member Downloader
12
 
13
- This Space runs a Flask service that automatically fetches the latest GEFS Wave control member (`c00`, “wave 0”) global 0.25° GRIB2 dataset from NOAA NOMADS. When a new cycle is published, the data is downloaded, archived into a tarball, and exposed through simple endpoints:
14
 
15
- - `/` and `/status` – View downloader status, including the most recent cycle timestamp and tarball path.
16
- - `/refresh` (POST) – Request a background refresh to capture a newer cycle if available.
17
- - `/wave/download` Download the tarball containing every `global.0p25` GRIB2 file for the latest 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.
 
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
- from flask import Flask, jsonify, send_file
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
- """Start the background refresh thread once."""
18
  if not _refresh_started.is_set():
19
  _refresh_started.set()
20
  manager.trigger_refresh()
21
 
22
 
23
- @app.route("/", methods=["GET"])
24
- def index():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  ensure_refresh()
26
- return jsonify(manager.status())
 
27
 
28
 
29
- @app.route("/status", methods=["GET"])
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
- return jsonify({"message": "Refresh started."})
 
40
 
41
 
42
- @app.route("/wave/download", methods=["GET"])
43
- def download_wave():
44
  ensure_refresh()
45
  state = manager.downloader.current_state()
46
  if not state or "tarball" not in state:
47
- return jsonify({"error": "Wave dataset not available yet."}), 503
48
 
49
  tarball_path = DATA_ROOT / state["tarball"]
50
  if not tarball_path.exists():
51
- return jsonify({"error": "Tarball missing on disk."}), 404
52
-
53
- filename = tarball_path.name
54
- if cycle := state.get("cycle"):
55
- filename = f"gefswave_wave0_{cycle.replace(' ', '_')}.tar.gz"
56
- return send_file(
57
- tarball_path,
58
- as_attachment=True,
59
- download_name=filename,
60
- mimetype="application/gzip",
61
- max_age=0,
 
 
 
62
  )
 
 
63
 
 
 
 
64
 
65
- @app.route("/voice", methods=["GET", "POST"])
66
- def voice():
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
- return str(resp)
72
 
73
 
74
  if __name__ == "__main__":
75
  ensure_refresh()
76
  port = int(os.environ.get("PORT", 7860))
77
- debug_mode = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes"}
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
- flask
2
- requests
3
- twilio
 
1
+ gradio>=5.0.0
2
+ requests>=2.31.0