diff --git a/src/geo/clients/currency.py b/src/geo/clients/currency.py new file mode 100644 index 0000000..616458f --- /dev/null +++ b/src/geo/clients/currency.py @@ -0,0 +1,43 @@ +""" +Функции для взаимодействия с внешним сервисом-провайдером данных о курсах валют. +""" +from http import HTTPStatus +from typing import Optional + +import httpx + +from app.settings import REQUESTS_TIMEOUT, API_KEY_APILAYER +from base.clients.base import BaseClient + + +class CurrencyClient(BaseClient): + """ + Реализация функций для взаимодействия с внешним сервисом-провайдером данных о курсах валют. + """ + + def get_base_url(self) -> str: + return "https://api.apilayer.com/fixer/latest" + + def _request(self, endpoint: str) -> Optional[dict]: + + # формирование заголовков запроса + headers = {"apikey": API_KEY_APILAYER} + + with httpx.Client(timeout=REQUESTS_TIMEOUT) as client: + # получение ответа + response = client.get(endpoint, headers={'apikey': API_KEY_APILAYER}) + + if response.status_code == HTTPStatus.OK: + return response.json() + + return None + + def get_rates(self, base: str = 'rub') -> Optional[dict]: + """ + Получение данных о курсах валют. + + :param base: Базовая валюта + :return: + """ + + return self._request(f"{self.get_base_url()}?base={base}") diff --git a/src/geo/serializers.py b/src/geo/serializers.py index e4a3a08..a686258 100644 --- a/src/geo/serializers.py +++ b/src/geo/serializers.py @@ -47,3 +47,25 @@ class Meta: "longitude", "country", ] + + +class WeatherSerializer(serializers.Serializer): + """ + Сериализатор для данных о погоде. + """ + + temp = serializers.FloatField() + pressure = serializers.IntegerField() + humidity = serializers.IntegerField() + wind_speed = serializers.FloatField() + description = serializers.CharField() + + +class CurrencySerializer(serializers.Serializer): + """ + Cериализатор для данных о курсах валют. + """ + + base = serializers.CharField() + date = serializers.DateField() + rates = serializers.DictField() diff --git a/src/geo/services/currency.py b/src/geo/services/currency.py new file mode 100644 index 0000000..d2085d4 --- /dev/null +++ b/src/geo/services/currency.py @@ -0,0 +1,29 @@ +from typing import Optional + +from geo.clients.shemas import CountryDTO, CurrencyRatesDTO +from geo.clients.currency import CurrencyClient +from geo.models import Country + + +class CurrencyService: + """ + Сервис для работы с данными о курсах валют. + """ + + def get_currency(self, base: str) -> Optional[CurrencyRatesDTO]: + """ + Получение списка курсов валют для базовой валюты. + :param base: Базовая валюта + :return: + """ + + data = CurrencyClient().get_rates(base) + + if data: + return CurrencyRatesDTO( + base=data["base"], + date=data["date"], + rates=data["rates"], + ) + + return None diff --git a/src/geo/services/shemas.py b/src/geo/services/shemas.py index f20044b..dffa12e 100644 --- a/src/geo/services/shemas.py +++ b/src/geo/services/shemas.py @@ -2,7 +2,7 @@ Описание моделей данных (DTO). """ -from pydantic import Field +from pydantic import Field, BaseModel from base.clients.shemas import HashableBaseModel @@ -22,3 +22,193 @@ class CountryCityDTO(HashableBaseModel): city: str alpha2code: str = Field(min_length=2, max_length=2) + + +class LocationDTO(HashableBaseModel): + """ + Модель локации для получения сведений о погоде. + + .. code-block:: + + LocationDTO( + capital="Mariehamn", + alpha2code="AX", + ) + """ + + capital: str + alpha2code: str = Field(min_length=2, max_length=2) # country alpha‑2 code + + +class CurrencyInfoDTO(HashableBaseModel): + """ + Модель данных о валюте. + + .. code-block:: + + CurrencyInfoDTO( + code="EUR", + ) + """ + + code: str + + +class LanguagesInfoDTO(HashableBaseModel): + """ + Модель данных о языке. + + .. code-block:: + + LanguagesInfoDTO( + name="Swedish", + native_name="svenska" + ) + """ + + name: str + native_name: str + + +class CountryDTO(BaseModel): + """ + Модель данных о стране. + + .. code-block:: + + CountryDTO( + capital="Mariehamn", + alpha2code="AX", + alt_spellings=[ + "AX", + "Aaland", + "Aland", + "Ahvenanmaa" + ], + currencies={ + CurrencyInfoDTO( + code="EUR", + ) + }, + flag="http://assets.promptapi.com/flags/AX.svg", + languages={ + LanguagesInfoDTO( + name="Swedish", + native_name="svenska" + ) + }, + name="\u00c5land Islands", + population=28875, + subregion="Northern Europe", + timezones=[ + "UTC+02:00", + ], + ) + """ + + capital: str + alpha2code: str + alt_spellings: list[str] + currencies: set[CurrencyInfoDTO] + flag: str + languages: set[LanguagesInfoDTO] + name: str + population: int + subregion: str + timezones: list[str] + + +class CurrencyRatesDTO(BaseModel): + """ + Модель данных о курсах валют. + + .. code-block:: + + CurrencyRatesDTO( + base="RUB", + date="2022-09-14", + rates={ + "EUR": 0.016503, + } + ) + """ + + base: str + date: str + rates: dict[str, float] + + +class WeatherInfoDTO(BaseModel): + """ + Модель данных о погоде. + + .. code-block:: + + WeatherInfoDTO( + temp=13.92, + pressure=1023, + humidity=54, + wind_speed=4.63, + description="scattered clouds", + ) + """ + + temp: float + pressure: int + humidity: int + + wind_speed: float + description: str + + +class LocationInfoDTO(BaseModel): + """ + Модель данных для представления общей информации о месте. + + .. code-block:: + + LocationInfoDTO( + location=CountryDTO( + capital="Mariehamn", + alpha2code="AX", + alt_spellings=[ + "AX", + "Aaland", + "Aland", + "Ahvenanmaa" + ], + currencies={ + CurrencyInfoDTO( + code="EUR", + ) + }, + flag="http://assets.promptapi.com/flags/AX.svg", + languages={ + LanguagesInfoDTO( + name="Swedish", + native_name="svenska" + ) + }, + name="\u00c5land Islands", + population=28875, + subregion="Northern Europe", + timezones=[ + "UTC+02:00", + ], + ), + weather=WeatherInfoDTO( + temp=13.92, + pressure=1023, + humidity=54, + wind_speed=4.63, + description="scattered clouds", + ), + currency_rates={ + "EUR": 0.016503, + }, + ) + """ + + location: CountryDTO + weather: WeatherInfoDTO + currency_rates: dict[str, float] diff --git a/src/geo/services/weather.py b/src/geo/services/weather.py index d9fefd3..e225461 100644 --- a/src/geo/services/weather.py +++ b/src/geo/services/weather.py @@ -1,6 +1,6 @@ from typing import Optional -from geo.clients.shemas import CountryDTO +from geo.clients.shemas import CountryDTO, WeatherInfoDTO from geo.clients.weather import WeatherClient from geo.models import Country @@ -10,7 +10,7 @@ class WeatherService: Сервис для работы с данными о погоде. """ - def get_weather(self, alpha2code: str, city: str) -> Optional[dict]: + def get_weather(self, alpha2code: str, city: str) -> Optional[WeatherInfoDTO]: """ Получение списка стран по названию. @@ -19,8 +19,16 @@ def get_weather(self, alpha2code: str, city: str) -> Optional[dict]: :return: """ - if data := WeatherClient().get_weather(f"{city},{alpha2code}"): - return data + data = WeatherClient().get_weather(f"{city},{alpha2code}") + + if data: + return WeatherInfoDTO( + temp=data["main"]["temp"], + pressure=data["main"]["pressure"], + humidity=data["main"]["humidity"], + wind_speed=data["wind"]["speed"], + description=data["weather"][0]["description"], + ) return None diff --git a/src/geo/urls.py b/src/geo/urls.py index 7c95bde..d5edbf5 100644 --- a/src/geo/urls.py +++ b/src/geo/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from geo.views import get_city, get_cities, get_countries, get_country, get_weather +from geo.views import get_city, get_cities, get_countries, get_country, get_weather, get_currency urlpatterns = [ path("city", get_cities, name="cities"), @@ -8,4 +8,5 @@ path("country", get_countries, name="countries"), path("country/", get_country, name="country"), path("weather//", get_weather, name="weather"), + path("currency/", get_currency, name="currency"), ] diff --git a/src/geo/views.py b/src/geo/views.py index d85d138..857d6ec 100644 --- a/src/geo/views.py +++ b/src/geo/views.py @@ -8,12 +8,15 @@ from rest_framework.exceptions import NotFound, ValidationError from rest_framework.request import Request -from app.settings import CACHE_WEATHER +from app.settings import CACHE_WEATHER, CACHE_CURRENCY from geo.serializers import CountrySerializer, CitySerializer from geo.services.city import CityService from geo.services.country import CountryService from geo.services.shemas import CountryCityDTO from geo.services.weather import WeatherService +from geo.services.currency import CurrencyService + +from geo.serializers import WeatherSerializer, CurrencySerializer @api_view(["GET"]) @@ -136,11 +139,30 @@ def get_weather(request: Request, alpha2code: str, city: str) -> JsonResponse: caches[CACHE_WEATHER].set(cache_key, data) if data: - return JsonResponse(data) + serialized_obj = WeatherSerializer(data) + return JsonResponse(serialized_obj.data, safe=False) raise NotFound @api_view(["GET"]) -def get_currency(*args: Any, **kwargs: Any) -> None: - pass +def get_currency(request: Request, currency: str) -> JsonResponse: + """ + Получение информации о курсе указанной валюты. + :param Request request: Объект запроса + :param str currency: базовая валюта + :return: + """ + + cache_key = f"{currency}_info" + data = caches[CACHE_CURRENCY].get(cache_key) + + if not data: + if data := CurrencyService().get_currency(base=currency): + caches[CACHE_CURRENCY].set(cache_key, data) + + if data: + serialized_obj = CurrencySerializer(data) + return JsonResponse(serialized_obj.data, safe=False) + + raise NotFound