|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Local stub for the Open-Meteo weather API. |
| 3 | +
|
| 4 | +Used by the HTTPClient example so that integration tests do not depend on |
| 5 | +api.open-meteo.com (which has occasional 502s and breaks unrelated CI runs). |
| 6 | +
|
| 7 | +Behaviour: |
| 8 | +- Listens on 127.0.0.1:<port> (default 18765, override with $1). |
| 9 | +- Responds to any GET with a canned forecast JSON payload that mirrors the |
| 10 | + fields the example expects (current_weather, current_weather_units, etc.). |
| 11 | +- Self-terminates after STUB_TIMEOUT seconds (default 1800) so a leaked |
| 12 | + process cannot survive a CI job. |
| 13 | +""" |
| 14 | +import http.server |
| 15 | +import json |
| 16 | +import os |
| 17 | +import sys |
| 18 | +import threading |
| 19 | + |
| 20 | +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else int(os.environ.get("STUB_PORT", "18765")) |
| 21 | + |
| 22 | +PAYLOAD = { |
| 23 | + "latitude": 52.52, |
| 24 | + "longitude": 13.41, |
| 25 | + "elevation": 38.0, |
| 26 | + "timezone": "GMT", |
| 27 | + "timezone_abbreviation": "GMT", |
| 28 | + "utc_offset_seconds": 0, |
| 29 | + "current_weather": { |
| 30 | + "interval": 900, |
| 31 | + "is_day": 1, |
| 32 | + "temperature": 12.3, |
| 33 | + "time": "2026-04-07T08:00", |
| 34 | + "weathercode": 3, |
| 35 | + "winddirection": 210, |
| 36 | + "windspeed": 9.4, |
| 37 | + }, |
| 38 | + "current_weather_units": { |
| 39 | + "interval": "seconds", |
| 40 | + "temperature": "°C", |
| 41 | + "time": "iso8601", |
| 42 | + "weathercode": "wmo code", |
| 43 | + "winddirection": "°", |
| 44 | + "windspeed": "km/h", |
| 45 | + }, |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | +class Handler(http.server.BaseHTTPRequestHandler): |
| 50 | + def do_GET(self): |
| 51 | + body = json.dumps(PAYLOAD).encode("utf-8") |
| 52 | + self.send_response(200) |
| 53 | + self.send_header("Content-Type", "application/json") |
| 54 | + self.send_header("Content-Length", str(len(body))) |
| 55 | + self.end_headers() |
| 56 | + self.wfile.write(body) |
| 57 | + |
| 58 | + def log_message(self, *_args, **_kwargs): |
| 59 | + pass |
| 60 | + |
| 61 | + |
| 62 | +def main(): |
| 63 | + # Set SO_REUSEADDR so a quick restart doesn't hit TIME_WAIT. |
| 64 | + http.server.HTTPServer.allow_reuse_address = True |
| 65 | + server = http.server.HTTPServer(("127.0.0.1", PORT), Handler) |
| 66 | + timeout = float(os.environ.get("STUB_TIMEOUT", "1800")) |
| 67 | + threading.Timer(timeout, server.shutdown).start() |
| 68 | + server.serve_forever() |
| 69 | + |
| 70 | + |
| 71 | +if __name__ == "__main__": |
| 72 | + main() |
0 commit comments