Skip to content

Instantly share code, notes, and snippets.

@seefalert
Last active April 9, 2023 13:41
Show Gist options
  • Save seefalert/5872c4d831e7d58b792fecafd4b1dcf8 to your computer and use it in GitHub Desktop.
Save seefalert/5872c4d831e7d58b792fecafd4b1dcf8 to your computer and use it in GitHub Desktop.
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())
class CantGetCoordinates(Exception):
"""Program cant get coordinates"""
class ApiServiceError(Exception):
"""Program cant get weather from api service"""
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)
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()
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)))
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