From 057ab41232c94dcc62ef393518679030eee80404 Mon Sep 17 00:00:00 2001 From: Danil Date: Sun, 24 Mar 2024 18:05:00 +0500 Subject: [PATCH 1/2] task 1 --- src/geo/clients/currency.py | 43 ++++++++++++++++++++++++++++++++++++ src/geo/services/currency.py | 23 +++++++++++++++++++ src/geo/urls.py | 3 ++- src/geo/views.py | 24 +++++++++++++++++--- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/geo/clients/currency.py create mode 100644 src/geo/services/currency.py 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/services/currency.py b/src/geo/services/currency.py new file mode 100644 index 0000000..bfb75d5 --- /dev/null +++ b/src/geo/services/currency.py @@ -0,0 +1,23 @@ +from typing import Optional + +from geo.clients.shemas import CountryDTO +from geo.clients.currency import CurrencyClient +from geo.models import Country + + +class CurrencyService: + """ + Сервис для работы с данными о курсах валют. + """ + + def get_currency(self, base: str) -> Optional[dict]: + """ + Получение списка курсов валют для базовой валюты. + :param base: Базовая валюта + :return: + """ + + if data := CurrencyClient().get_rates(base): + return data + + 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..85c7ae5 100644 --- a/src/geo/views.py +++ b/src/geo/views.py @@ -8,12 +8,13 @@ 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 @api_view(["GET"]) @@ -142,5 +143,22 @@ def get_weather(request: Request, alpha2code: str, city: str) -> JsonResponse: @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: + return JsonResponse(data) + + raise NotFound From 0f2841aff9e07050592501044b3da01c7f54e21b Mon Sep 17 00:00:00 2001 From: Danil Date: Sun, 24 Mar 2024 18:50:02 +0500 Subject: [PATCH 2/2] task 2 & 3 --- src/geo/serializers.py | 22 ++++ src/geo/services/currency.py | 14 ++- src/geo/services/shemas.py | 192 ++++++++++++++++++++++++++++++++++- src/geo/services/weather.py | 16 ++- src/geo/views.py | 8 +- 5 files changed, 241 insertions(+), 11 deletions(-) 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 index bfb75d5..d2085d4 100644 --- a/src/geo/services/currency.py +++ b/src/geo/services/currency.py @@ -1,6 +1,6 @@ from typing import Optional -from geo.clients.shemas import CountryDTO +from geo.clients.shemas import CountryDTO, CurrencyRatesDTO from geo.clients.currency import CurrencyClient from geo.models import Country @@ -10,14 +10,20 @@ class CurrencyService: Сервис для работы с данными о курсах валют. """ - def get_currency(self, base: str) -> Optional[dict]: + def get_currency(self, base: str) -> Optional[CurrencyRatesDTO]: """ Получение списка курсов валют для базовой валюты. :param base: Базовая валюта :return: """ - if data := CurrencyClient().get_rates(base): - return data + 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/views.py b/src/geo/views.py index 85c7ae5..857d6ec 100644 --- a/src/geo/views.py +++ b/src/geo/views.py @@ -16,6 +16,8 @@ from geo.services.weather import WeatherService from geo.services.currency import CurrencyService +from geo.serializers import WeatherSerializer, CurrencySerializer + @api_view(["GET"]) def get_city(request: Request, name: str) -> JsonResponse: @@ -137,7 +139,8 @@ 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 @@ -159,6 +162,7 @@ def get_currency(request: Request, currency: str) -> JsonResponse: caches[CACHE_CURRENCY].set(cache_key, data) if data: - return JsonResponse(data) + serialized_obj = CurrencySerializer(data) + return JsonResponse(serialized_obj.data, safe=False) raise NotFound