diff --git a/servers/weather/.dockerignore b/servers/weather/.dockerignore new file mode 100644 index 0000000..03a268b --- /dev/null +++ b/servers/weather/.dockerignore @@ -0,0 +1,34 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/__pycache__ +**/.venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md diff --git a/servers/weather/Dockerfile b/servers/weather/Dockerfile new file mode 100644 index 0000000..e91fca9 --- /dev/null +++ b/servers/weather/Dockerfile @@ -0,0 +1,51 @@ +# syntax=docker/dockerfile:1 + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/go/dockerfile-reference/ + +# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 + +ARG PYTHON_VERSION=3.10.12 +FROM python:${PYTHON_VERSION}-slim as base + +# Prevents Python from writing pyc files. +ENV PYTHONDONTWRITEBYTECODE=1 + +# Keeps Python from buffering stdout and stderr to avoid situations where +# the application crashes without emitting any logs due to buffering. +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser + +# Download dependencies as a separate step to take advantage of Docker's caching. +# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds. +# Leverage a bind mount to requirements.txt to avoid having to copy them into +# into this layer. +RUN --mount=type=cache,target=/root/.cache/pip \ + --mount=type=bind,source=requirements.txt,target=requirements.txt \ + python -m pip install -r requirements.txt + +# Switch to the non-privileged user to run the application. +USER appuser + +# Copy the source code into the container. +COPY . . + +# Expose the port that the application listens on. +EXPOSE 8000 + +# Run the application. +CMD uvicorn 'main:app' --host=0.0.0.0 --port=8000 diff --git a/servers/weather/compose.yaml b/servers/weather/compose.yaml new file mode 100644 index 0000000..9fc4d98 --- /dev/null +++ b/servers/weather/compose.yaml @@ -0,0 +1,7 @@ +services: + server: + build: + context: . + ports: + - 8000:8000 + diff --git a/servers/weather/main.py b/servers/weather/main.py new file mode 100644 index 0000000..b847094 --- /dev/null +++ b/servers/weather/main.py @@ -0,0 +1,94 @@ +import requests # Added for making HTTP requests +from fastapi import FastAPI, HTTPException, Query # Added Query for GET params +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from typing import Optional, List # Added Optional and List + +app = FastAPI( + title="Weather API", + version="1.0.0", + description="Provides weather retrieval by latitude and longitude using Open-Meteo.", # Updated description +) + +origins = ["*"] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# ------------------------------- +# Pydantic models +# ------------------------------- + +class CurrentWeather(BaseModel): + time: str = Field(..., description="ISO 8601 format timestamp") + temperature_2m: float = Field(..., alias="temperature_2m", description="Air temperature at 2 meters above ground") + wind_speed_10m: float = Field(..., alias="wind_speed_10m", description="Wind speed at 10 meters above ground") + +class HourlyUnits(BaseModel): + time: str + temperature_2m: str + relative_humidity_2m: str + wind_speed_10m: str + +class HourlyData(BaseModel): + time: List[str] + temperature_2m: List[float] + relative_humidity_2m: List[int] # Assuming humidity is integer percentage + wind_speed_10m: List[float] + +class WeatherForecastOutput(BaseModel): + latitude: float + longitude: float + generationtime_ms: float + utc_offset_seconds: int + timezone: str + timezone_abbreviation: str + elevation: float + current: CurrentWeather = Field(..., description="Current weather conditions") + hourly_units: HourlyUnits + hourly: HourlyData + +# ------------------------------- +# Routes +# ------------------------------- + +OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast" + +@app.get("/forecast", response_model=WeatherForecastOutput, summary="Get current weather and forecast") +def get_weather_forecast( + latitude: float = Query(..., description="Latitude for the location (e.g., 52.52)"), + longitude: float = Query(..., description="Longitude for the location (e.g., 13.41)") +): + """ + Retrieves current weather conditions and hourly forecast data + for the specified latitude and longitude using the Open-Meteo API. + """ + params = { + "latitude": latitude, + "longitude": longitude, + "current": "temperature_2m,wind_speed_10m", + "hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m", + "timezone": "auto" # Automatically detect timezone + } + try: + response = requests.get(OPEN_METEO_URL, params=params) + response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx) + data = response.json() + + # Basic validation to ensure expected keys are present + if "current" not in data or "hourly" not in data: + raise HTTPException(status_code=500, detail="Unexpected response format from Open-Meteo API") + + # Pydantic will automatically validate the structure based on WeatherForecastOutput + return data + + except requests.exceptions.RequestException as e: + raise HTTPException(status_code=503, detail=f"Error connecting to Open-Meteo API: {e}") + except Exception as e: + # Catch other potential errors during processing + raise HTTPException(status_code=500, detail=f"An internal error occurred: {e}") diff --git a/servers/weather/requirements.txt b/servers/weather/requirements.txt new file mode 100644 index 0000000..e5f0316 --- /dev/null +++ b/servers/weather/requirements.txt @@ -0,0 +1,7 @@ +fastapi +uvicorn[standard] +pydantic +python-multipart +pytz +python-dateutil +requests \ No newline at end of file