Skip to main content
Glama
ASXRND

MCP Weather & Accruals Server

by ASXRND
weather_api.py11.7 kB
#!/usr/bin/env python3 """ Модуль для работы с OpenWeatherMap API. Содержит функции для получения текущей погоды и прогноза. """ import logging from typing import Any, Dict, Optional import httpx from modules.config import Config logger = logging.getLogger("WeatherAPI") # Вспомогательная функция для выполнения запросов к OpenWeatherMap API. async def make_weather_request( endpoint: str, params: Dict[str, Any] ) -> Optional[Dict[str, Any]]: """ Вспомогательная функция для выполнения запросов к OpenWeatherMap API. Обрабатывает HTTP-ошибки и другие исключения. Args: endpoint: Конечная точка API (например, "weather" или "forecast") params: Параметры запроса (q, units и т.д.) Returns: JSON ответ от API (словарь) или None в случае ошибки. Если произошла ошибка, словарь содержит "cod" и "message". """ # Добавляем API ключ к параметрам запроса params["appid"] = Config.OPENWEATHER_API_KEY params["lang"] = "ru" # Русский язык для описаний params["units"] = params.get("units", "metric") # Формируем полный URL url = f"{Config.OPENWEATHER_BASE_URL}/{endpoint}" try: async with httpx.AsyncClient() as client: logger.debug(f"Запрос к {url} с параметрами: {params}") response = await client.get(url, params=params, timeout=Config.API_TIMEOUT) response.raise_for_status() data = response.json() logger.debug(f"Получен ответ от {endpoint}") return data except httpx.HTTPStatusError as e: error_msg = f"HTTP ошибка {e.response.status_code}: {e.response.text}" logger.error(error_msg) return {"cod": e.response.status_code, "message": e.response.text} except httpx.RequestError as e: error_msg = f"Ошибка сети: {str(e)}" logger.error(error_msg) return {"cod": 500, "message": f"Ошибка сети: {str(e)}"} except Exception as e: error_msg = f"Непредвиденная ошибка: {str(e)}" logger.error(error_msg) return {"cod": 500, "message": f"Внутренняя ошибка: {str(e)}"} # Получить текущую погоду для указанного города. async def get_current_weather(city: str, units: str = "metric") -> str: """ Получить текущую погоду для указанного города. Args: city: Название города на русском или английском (например, "Москва" или "Moscow") units: Система измерения - "metric" (Цельсий) или "imperial" (Фаренгейт) Returns: Строка с описанием текущей погоды или сообщение об ошибке. """ logger.info(f"Запрос текущей погоды для города: '{city}'") params = { "q": city, "units": units } data = await make_weather_request("weather", params) # Проверяем, вернул ли запрос ошибку if not data or ("cod" in data and data["cod"] not in [200, "200"]): error_message = data.get("message", "Неизвестная ошибка API") if data else "Ошибка соединения" logger.warning(f"Не удалось получить погоду для '{city}': {error_message}") return f"❌ Не удалось получить погоду для города '{city}'. Причина: {error_message}." try: main = data.get("main", {}) weather_list = data.get("weather", []) weather = weather_list[0] if weather_list else {} wind = data.get("wind", {}) sys_info = data.get("sys", {}) temp_unit = "°C" if units == "metric" else "°F" wind_unit = "м/с" if units == "metric" else "миль/ч" city_name = data.get('name', city.capitalize()) country_code = sys_info.get('country', 'Н/Д') result = f""" 🌍 Погода в городе {city_name}, {country_code} 🌡️ Температура: {main.get('temp', 0.0):.1f}{temp_unit} 🤔 Ощущается как: {main.get('feels_like', 0.0):.1f}{temp_unit} 📊 Мин/Макс: {main.get('temp_min', 0.0):.1f}{temp_unit} / {main.get('temp_max', 0.0):.1f}{temp_unit} ☁️ Условия: {weather.get('description', 'Неизвестно').capitalize()} 💧 Влажность: {main.get('humidity', 0)}% 🎚️ Давление: {main.get('pressure', 0)} гПа 💨 Ветер: {wind.get('speed', 0.0):.1f} {wind_unit}, направление {wind.get('deg', 'н/д')}° """.strip() logger.info(f"Успешно получена погода для '{city_name}'") return result except Exception as e: logger.error(f"Ошибка при обработке данных текущей погоды: {str(e)}") return f"❌ Ошибка при обработке данных о погоде для города '{city}'." # Форматирует прогноз на один день на основе данных за несколько временных интервалов. def format_day_forecast(day_data: list, temp_unit: str) -> str: """ Форматирует прогноз на один день на основе данных за несколько временных интервалов. Args: day_data: Список данных о погоде за день (каждые 3 часа) temp_unit: Символ единицы температуры (°C или °F) Returns: Отформатированная строка с прогнозом на день. """ if not day_data: return "Нет данных для этого дня." date_str = day_data[0].get("dt_txt", "Неизвестная дата").split(" ")[0] try: temps = [item.get("main", {}).get("temp", 0.0) for item in day_data] avg_temp = sum(temps) / len(temps) if temps else 0.0 min_temp = min(temps) if temps else 0.0 max_temp = max(temps) if temps else 0.0 descriptions = [item.get("weather", [{}])[0].get("description", "Неизвестно") for item in day_data] most_common_desc = max(set(descriptions), key=descriptions.count) if descriptions else "Неизвестно" humidities = [item.get("main", {}).get("humidity", 0) for item in day_data] avg_humidity = sum(humidities) / len(humidities) if humidities else 0 wind_speeds = [item.get("wind", {}).get("speed", 0.0) for item in day_data] avg_wind = sum(wind_speeds) / len(wind_speeds) if wind_speeds else 0.0 return f"""📆 Дата: {date_str} 🌡️ Средняя температура: {avg_temp:.1f}{temp_unit} 📊 Мин/Макс: {min_temp:.1f}{temp_unit} / {max_temp:.1f}{temp_unit} ☁️ Условия: {most_common_desc.capitalize()} 💧 Влажность: {avg_humidity:.0f}% 💨 Ветер: {avg_wind:.1f} м/с""" except Exception as e: logger.error(f"Ошибка при форматировании прогноза: {str(e)}") return f"❌ Ошибка при форматировании прогноза за {date_str}." # Получить прогноз погоды на несколько дней. async def get_forecast(city: str, days: int = 3, units: str = "metric") -> str: """ Получить прогноз погоды на несколько дней. Args: city: Название города на русском или английском days: Количество дней для прогноза (автоматически ограничивается от 1 до 5) units: Система измерения - "metric" (Цельсий) или "imperial" (Фаренгейт) Returns: Строка с прогнозом погоды или сообщение об ошибке. """ logger.info(f"Запрос прогноза для города: '{city}' на {days} дней") # Ограничиваем количество дней days = min(max(days, Config.MIN_FORECAST_DAYS), Config.MAX_FORECAST_DAYS) params = { "q": city, "units": units, "cnt": days * 8 # API возвращает данные каждые 3 часа, 8 записей = 1 день } data = await make_weather_request("forecast", params) # Проверяем, вернул ли запрос ошибку if not data or ("cod" in data and str(data["cod"]) not in ["200", 200]): error_message = data.get("message", "Неизвестная ошибка API") if data else "Ошибка соединения" logger.warning(f"Не удалось получить прогноз для '{city}': {error_message}") return f"❌ Не удалось получить прогноз для города '{city}'. Причина: {error_message}." try: city_info = data.get("city", {}) forecast_list = data.get("list", []) if not forecast_list: return f"❌ Прогноз для города '{city}' доступен, но данных о прогнозе нет." temp_unit = "°C" if units == "metric" else "°F" # Группируем данные по дням forecasts_by_day = {} for item in forecast_list: date_time = item.get("dt_txt", "").split(" ") if len(date_time) > 0: date = date_time[0] if date not in forecasts_by_day: forecasts_by_day[date] = [] forecasts_by_day[date].append(item) formatted_forecasts = [] # Сортируем дни, чтобы они шли по порядку sorted_dates = sorted(forecasts_by_day.keys()) for date in sorted_dates: if len(formatted_forecasts) >= days: break formatted_forecasts.append(format_day_forecast(forecasts_by_day[date], temp_unit)) city_name = city_info.get('name', city.capitalize()) country_code = city_info.get('country', 'Н/Д') result = f"📅 Прогноз погоды для {city_name}, {country_code} на {len(formatted_forecasts)} {'день' if len(formatted_forecasts) == 1 else 'дня' if 1 < len(formatted_forecasts) < 5 else 'дней'}:\n\n" result += "\n\n".join(formatted_forecasts) logger.info(f"Успешно получен прогноз для '{city_name}' на {len(formatted_forecasts)} дней") return result except Exception as e: logger.error(f"Ошибка при обработке данных прогноза: {str(e)}") return f"❌ Ошибка при обработке данных прогноза для города '{city}'."

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ASXRND/MCP_deepseek'

If you have feedback or need assistance with the MCP directory API, please join our Discord server