Last active
April 9, 2023 13:41
-
-
Save seefalert/5872c4d831e7d58b792fecafd4b1dcf8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import NamedTuple | |
from geopy.geocoders import Nominatim # type: ignore | |
import config # type: ignore | |
from exceptions import CantGetCoordinates | |
class Coordinate(NamedTuple): | |
latitude: float | |
longitude: float | |
def get_address_coordinates() -> Coordinate: | |
coordinates = _get_geopy_coordinates() | |
return _round_coordinates(coordinates) | |
def _get_geopy_coordinates() -> Coordinate: | |
geopy_output = _get_geopy_output() | |
coordinates = _parse_coord(geopy_output) | |
return coordinates | |
def _get_geopy_output() -> dict[str, str]: | |
nominatim = Nominatim(user_agent=config.USER_AGENT) | |
output = nominatim.geocode(config.ADDRESS).raw | |
return output | |
def _parse_coord(output: dict[str, str]) -> Coordinate: | |
latitude = _parse_float_coordinates(output['lat']) | |
longitude = _parse_float_coordinates(output['lon']) | |
return Coordinate(latitude, longitude) | |
def _parse_float_coordinates(value: str) -> float: | |
try: | |
return float(value) | |
except ValueError: | |
raise CantGetCoordinates | |
def _round_coordinates(coordinates: Coordinate) -> Coordinate: | |
if not config.USE_ROUNDED_COORDINATES: | |
return coordinates | |
return Coordinate(*map( | |
lambda x: round(x, 1), | |
[coordinates.latitude, coordinates.longitude] | |
)) | |
if __name__ == '__main__': | |
print(get_address_coordinates()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CantGetCoordinates(Exception): | |
"""Program cant get coordinates""" | |
class ApiServiceError(Exception): | |
"""Program cant get weather from api service""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from datetime import datetime | |
from pathlib import Path | |
from typing import TypedDict | |
import json | |
from weather_api_service import Weather | |
from weather_formatter import format_weather | |
class WeatherStorage: | |
def save(self, weather: Weather) -> None: | |
raise NotImplementedError | |
class PlainFileWeatherStorage(WeatherStorage): | |
"""Store weather in plain text file""" | |
def __init__(self, file: Path) -> None: | |
self._file = file | |
def save(self, weather: Weather) -> None: | |
now = datetime.now() | |
formatted_weather = format_weather(weather) | |
with open(self._file, "a", encoding='utf-8') as f: | |
f.write(f'{now}\n{formatted_weather}\n') | |
class HistoryRecord(TypedDict): | |
date: str | |
weather: str | |
class JSONFileWeatherStorage(WeatherStorage): | |
"""Store weather in a JSON file.""" | |
def __init__(self, jsonfile: Path): | |
self._jsonfile = jsonfile | |
self._init_storage() | |
def save(self, weather: Weather) -> None: | |
history = self._read_history() | |
history.append({ | |
'date': str(datetime.now()), | |
'weather': format_weather(weather) | |
}) | |
self._write_history(history) | |
def _init_storage(self) -> None: | |
if not self._jsonfile.exists(): | |
self._jsonfile.write_text('[]') | |
def _read_history(self) -> list[HistoryRecord]: | |
with open(self._jsonfile, "r", encoding='utf-8') as f: | |
return json.load(f) | |
def _write_history(self, history: list[HistoryRecord]) -> None: | |
with open(self._jsonfile, "w", encoding='utf-8') as f: | |
json.dump(history, f, ensure_ascii=False, indent=4) | |
def save_weather(weather: Weather, storage: WeatherStorage) -> None: | |
"""Saves weather in the storage""" | |
storage.save(weather) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pathlib import Path | |
from coordinates import get_address_coordinates | |
from weather_api_service import get_weather | |
from weather_formatter import format_weather | |
from exceptions import CantGetCoordinates, ApiServiceError | |
from history import save_weather, PlainFileWeatherStorage, JSONFileWeatherStorage | |
def main(): | |
try: | |
coordinates = get_address_coordinates() | |
except CantGetCoordinates: | |
print('Не удалось получить координаты') | |
exit(1) | |
try: | |
weather = get_weather(coordinates) | |
except ApiServiceError: | |
print('Не удалось получить погоду') | |
exit(1) | |
print(format_weather(weather)) | |
save_weather( | |
weather, | |
PlainFileWeatherStorage(Path.cwd() / 'history.txt') | |
) | |
save_weather( | |
weather, | |
JSONFileWeatherStorage(Path.cwd() / 'history.json') | |
) | |
if __name__ == '__main__': | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import NamedTuple, Literal | |
from datetime import datetime | |
from enum import Enum | |
import urllib.request | |
from urllib.error import URLError | |
from json.decoder import JSONDecodeError | |
import ssl | |
import json | |
from coordinates import Coordinate | |
import config # type: ignore | |
from exceptions import ApiServiceError | |
Celsius = int | |
meters_second = int | |
class WeatherType(Enum): | |
THUNDERSTORM = 'Гроза' | |
DRIZZLE = 'Изморозь' | |
RAIN = 'Дождь' | |
SNOW = 'Снег' | |
CLEAR = 'Ясно' | |
FOG = 'Туман' | |
CLOUDS = 'Облачно' | |
class Weather(NamedTuple): | |
temperature: Celsius | |
weather_type: WeatherType | |
wind_speed: meters_second | |
sunrise: datetime | |
sunset: datetime | |
city: str | |
def get_weather(coordinates: Coordinate) -> Weather: | |
openweather_response = _get_openweather_response( | |
latitude=coordinates.latitude, longitude=coordinates.longitude) | |
weather = _parce_openweather_response(openweather_response) | |
return weather | |
def _get_openweather_response(latitude: float, longitude: float) -> str: | |
ssl._create_default_https_context = ssl._create_unverified_context | |
url = config.OPENWEATHER_URL.format( | |
latitude=latitude, longitude=longitude) | |
try: | |
return urllib.request.urlopen(url).read() | |
except URLError: | |
raise ApiServiceError | |
def _parce_openweather_response(openweather_response: str) -> Weather: | |
try: | |
openweather_dict = json.loads(openweather_response) | |
except JSONDecodeError: | |
raise ApiServiceError | |
return Weather( | |
temperature=_parce_temperature(openweather_dict), | |
weather_type=_parce_weather_type(openweather_dict), | |
wind_speed=_parce_wind_speed(openweather_dict), | |
sunrise=_parce_sun_time(openweather_dict, 'sunrise'), | |
sunset=_parce_sun_time(openweather_dict, 'sunset'), | |
city=config.ADDRESS_FOR_USER | |
) | |
def _parce_temperature(openweather_dict: dict) -> Celsius: | |
return round(openweather_dict['main']['temp']) | |
def _parce_weather_type(openweather_dict: dict) -> WeatherType: | |
try: | |
weather_type_id = str(openweather_dict['weather'][0]['id']) | |
except (IndexError, KeyError): | |
raise ApiServiceError | |
weather_type = { | |
'1': WeatherType.THUNDERSTORM, | |
'3': WeatherType.DRIZZLE, | |
'5': WeatherType.RAIN, | |
'6': WeatherType.SNOW, | |
'7': WeatherType.FOG, | |
'800': WeatherType.CLEAR, | |
'80': WeatherType.CLOUDS, | |
} | |
for _id, _weather_type in weather_type.items(): | |
if weather_type_id.startswith(_id): | |
return _weather_type | |
raise ApiServiceError | |
def _parce_sun_time( | |
openweather_dict: dict, | |
time: Literal['sunrise'] | Literal['sunset']) -> datetime: | |
return datetime.fromtimestamp(openweather_dict['sys'][time]) | |
def _parce_wind_speed(openweather_dict: dict) -> meters_second: | |
return round(openweather_dict['wind']['speed']) | |
if __name__ == '__main__': | |
print(get_weather(Coordinate(59.9, 30.3))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from weather_api_service import Weather | |
def format_weather(weather: Weather) -> str: | |
return (f'{weather.city} температура {weather.temperature}°С, ' | |
f'{weather.weather_type.value}\n' | |
f'Скорость ветра: {weather.wind_speed} м/с\n' | |
f'Восход: {weather.sunrise.strftime("%H:%M")}\n' | |
f'Закат: {weather.sunset.strftime("%H:%M")}') | |
if __name__ == '__main__': | |
from datetime import datetime | |
from weather_api_service import WeatherType | |
print(format_weather(Weather( | |
temperature=10, | |
weather_type=WeatherType.CLEAR, | |
wind_speed=5, | |
sunrise=datetime.fromisoformat('2023-04-09 04:00:00'), | |
sunset=datetime.fromisoformat('2023-04-09 20:00:00'), | |
city='Санкт-Петербург' | |
))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment