Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ sphinx-rtd-theme>=1.0.0,<2.0.0
asyncclick>=8.1.3.2,<8.2.0
colorama>=0.4.5,<0.5.0
anyio>=3.6.1,<3.7.0
prettytable>=3.6.0

# работа с Excel-файлами
openpyxl>=3.0.10,<3.1.0
Expand Down Expand Up @@ -35,3 +36,8 @@ mypy>=0.971,<1.0

# автоматическое форматирование кода
black>=22.8.0,<22.9.0

# Определение часового пояса
timezonefinder>=6.1.9
pytz>=2022.1
geopy>=2.3.0
81 changes: 79 additions & 2 deletions src/collectors/collector.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
"""
Функции сбора информации о странах.
"""

from __future__ import annotations

import asyncio
import json
from datetime import datetime
from typing import Any, Optional, FrozenSet

from timezonefinder import TimezoneFinder
from geopy.geocoders import Nominatim

import aiofiles
import aiofiles.os

import pytz
from clients.country import CountryClient
from clients.currency import CurrencyClient
from clients.weather import WeatherClient
from collectors.base import BaseCollector
from collectors.models import (
LocationDTO,
CountryDTO,
CapitalDTO,
CurrencyRatesDTO,
CurrencyInfoDTO,
WeatherInfoDTO,
Expand Down Expand Up @@ -101,6 +105,7 @@ async def read(cls) -> Optional[list[CountryDTO]]:
languages=item["languages"],
name=item["name"],
population=item["population"],
area=item["area"],
subregion=item["subregion"],
timezones=item["timezones"],
)
Expand All @@ -111,6 +116,77 @@ async def read(cls) -> Optional[list[CountryDTO]]:
return None


class CapitalCollector:
"""
Сбор информации о столице (географическое описание).
"""

@classmethod
def get_timezone(
cls, latitude: float | None, longitude: float | None
) -> str | None:
"""
Получение часового пояса по координатам.

:param latitude: Широта
:param longitude: Долгота
:return: Часовой пояс или None, если не найден
"""
if longitude is None or latitude is None:
return None
return TimezoneFinder().timezone_at(lng=longitude, lat=latitude)

@classmethod
async def get_coordinates(cls, city: str) -> tuple[float | None, float | None]:
"""
Получение координат по названию столицы.

:param city: Название столицы
:return: Координаты или None, None, если не найдены
"""
geolocator = Nominatim(user_agent="geoapiExercises")
location = geolocator.geocode(city)
if location is not None:
return location.latitude, location.longitude
return None, None

@classmethod
def capital_time(
cls, latitude: float | None, longitude: float | None
) -> tuple[str | None, str | None]:
"""
Получение часового пояса и текущего времени в столице по координатам.

:param latitude: Широта
:param longitude: Долгота
:return: Часовой пояс и текущее время или None, None, если не найдены
"""
timezone_raw = cls.get_timezone(latitude, longitude)
if timezone_raw is None:
return None, None
timezone = pytz.timezone(timezone_raw)
time = datetime.now(timezone)
return timezone_raw, time.strftime("%H:%M:%S on %A, %B %d, %Y")

@classmethod
async def collect(cls, name: str) -> CapitalDTO:
"""
Получение данных о столице в формате CapitalDTO.

:param name: Название столицы
:return: Модель столицы в формате CapitalDTO
"""
latitude, longitude = await cls.get_coordinates(name)
timezone, current_time = cls.capital_time(latitude, longitude)
return CapitalDTO(
name=name,
latitude=latitude,
longitude=longitude,
timezone=timezone,
current_time=current_time,
)


class CurrencyRatesCollector(BaseCollector):
"""
Сбор информации о курсах валют.
Expand Down Expand Up @@ -217,6 +293,7 @@ async def read(cls, location: LocationDTO) -> Optional[WeatherInfoDTO]:
temp=result["main"]["temp"],
pressure=result["main"]["pressure"],
humidity=result["main"]["humidity"],
visibility=result["visibility"],
wind_speed=result["wind"]["speed"],
description=result["weather"][0]["description"],
)
Expand Down
35 changes: 35 additions & 0 deletions src/collectors/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ class LanguagesInfoDTO(HashableBaseModel):
native_name: str


class CapitalDTO(BaseModel):
"""
Модель данных о столице.

.. code-block::

CapitalDTO(
name="Moscow",
longitude=55.75,
latitude=37.62,
timezone="Europe/Moscow",
current_time="22:33:51 on Thursday, February 16, 2023"
)
"""

name: str
longitude: float | None
latitude: float | None
timezone: str | None
current_time: str | None


class CountryDTO(BaseModel):
"""
Модель данных о стране.
Expand Down Expand Up @@ -89,6 +111,7 @@ class CountryDTO(BaseModel):
},
name="\u00c5land Islands",
population=28875,
area=17124442.0,
subregion="Northern Europe",
timezones=[
"UTC+02:00",
Expand All @@ -104,6 +127,7 @@ class CountryDTO(BaseModel):
languages: set[LanguagesInfoDTO]
name: str
population: int
area: float | None
subregion: str
timezones: list[str]

Expand Down Expand Up @@ -140,6 +164,7 @@ class WeatherInfoDTO(BaseModel):
humidity=54,
wind_speed=4.63,
description="scattered clouds",
visibility="3161.0"
)
"""

Expand All @@ -148,6 +173,7 @@ class WeatherInfoDTO(BaseModel):
humidity: int
wind_speed: float
description: str
visibility: float


class LocationInfoDTO(BaseModel):
Expand Down Expand Up @@ -180,11 +206,19 @@ class LocationInfoDTO(BaseModel):
},
name="\u00c5land Islands",
population=28875,
area=17124442.0,
subregion="Northern Europe",
timezones=[
"UTC+02:00",
],
),
capital=CapitalDTO(
name="Moscow",
longitude=55.75,
latitude=37.62,
timezone="Europe/Moscow",
current_time="22:33:51 on Thursday, February 16, 2023"
),
weather=WeatherInfoDTO(
temp=13.92,
pressure=1023,
Expand All @@ -199,5 +233,6 @@ class LocationInfoDTO(BaseModel):
"""

location: CountryDTO
capital: CapitalDTO
weather: WeatherInfoDTO
currency_rates: dict[str, float]
30 changes: 25 additions & 5 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@
"""

import asyncclick as click
from prettytable import PrettyTable

from reader import Reader
from renderer import Renderer


def print_info(title: str, render_result: PrettyTable) -> None:
"""
Вывод информации о сущности в консоль

:param title: Название таблицы.
:param render_result: Таблица.
"""

lines = render_result
click.secho(title, bold=True, fg="green")
for line in lines:
click.secho(line, fg="green")


@click.command()
@click.option(
"--location",
Expand All @@ -19,17 +34,22 @@
)
async def process_input(location: str) -> None:
"""
Поиск и вывод информации о стране, погоде и курсах валют.
Поиск и вывод информации о стране, столице, погоде и курсах валют.

:param str location: Страна и/или город
"""

location_info = await Reader().find(location)
if location_info:
lines = await Renderer(location_info).render()

for line in lines:
click.secho(line, fg="green")
print_info(
"Информация о стране", await Renderer(location_info).render_country()
)
print_info(
"Информация о столице", await Renderer(location_info).render_capital()
)
print_info(
"Информация о погоде", await Renderer(location_info).render_weather()
)
else:
click.secho("Информация отсутствует.", fg="yellow")

Expand Down
14 changes: 14 additions & 0 deletions src/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from typing import Optional

from collectors.collector import (
CapitalCollector,
CountryCollector,
CurrencyRatesCollector,
WeatherCollector,
)
from collectors.models import (
CapitalDTO,
CountryDTO,
CurrencyInfoDTO,
LocationDTO,
Expand All @@ -34,13 +36,15 @@ async def find(self, location: str) -> Optional[LocationInfoDTO]:

country = await self.find_country(location)
if country:
capital = await self.get_capital(country.capital)
weather = await self.get_weather(
LocationDTO(capital=country.capital, alpha2code=country.alpha2code)
)
currency_rates = await self.get_currency_rates(country.currencies)

return LocationInfoDTO(
location=country,
capital=capital,
weather=weather,
currency_rates=currency_rates,
)
Expand Down Expand Up @@ -90,6 +94,16 @@ async def find_country(self, search: str) -> Optional[CountryDTO]:

return None

@staticmethod
async def get_capital(capital_name: str) -> CapitalDTO:
"""
Получение данных о столице.

:param capital_name: Название столицы
:return:
"""
return await CapitalCollector.collect(capital_name)

@staticmethod
async def _match(search: str, country: CountryDTO) -> bool:
"""
Expand Down
Loading