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
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
favorite-places-app:
build: .
container_name: favorite-places-app
command: uvicorn main:app --log-config logging.conf --reload --host 0.0.0.0 --port=8000
command: uvicorn main:app --log-config logging.conf --reload --host 0.0.0.0 --port=8000 --proxy-headers --forwarded-allow-ips *
ports:
- "8010:8000"
volumes:
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from exceptions import setup_exception_handlers
from routes import metadata_tags, setup_routes
from settings import settings
from fastapi_pagination import add_pagination


def build_app() -> FastAPI:
Expand All @@ -19,5 +20,6 @@ def build_app() -> FastAPI:

setup_routes(app)
setup_exception_handlers(app)
add_pagination(app)

return app
5 changes: 3 additions & 2 deletions src/clients/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from urllib.parse import urlencode, urljoin

import httpx

import os
from clients.base.base import BaseClient
from clients.shemas import LocalityDTO

Expand Down Expand Up @@ -42,11 +42,12 @@ async def get_location(
:return:
"""

endpoint = "reverse-geocode-client"
endpoint = "reverse-geocode"
query_params = {
"latitude": latitude,
"longitude": longitude,
"localityLanguage": "en",
"key": os.getenv("API_KEY"),
}
url = urljoin(
self.base_url,
Expand Down
2 changes: 1 addition & 1 deletion src/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ApiHTTPException(HTTPException):
"""Обработка ошибок API."""

status_code: int
code: str
code: str = "api-error"
detail: str

def __init__(
Expand Down
6 changes: 6 additions & 0 deletions src/schemas/places.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ class PlacesListResponse(ListResponse):
"""

data: list[Place]


class Description(BaseModel):
"""Модель для описания любимых мест"""

description: str = Field(None, min_length=3, max_length=255)
39 changes: 35 additions & 4 deletions src/services/places_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,48 @@ async def update_place(self, primary_key: int, place: PlaceUpdate) -> Optional[i
:return:
"""

# при изменении координат – обогащение данных путем получения дополнительной информации от API
# todo
original_place = await self.places_repository.find(primary_key)
only_description_updated = (original_place.latitude == place.latitude
and original_place.longitude == place.longitude)

if only_description_updated:
updated_place = original_place
updated_place.description = place.description

else:
updated_place = Place(
latitude=place.latitude,
longitude=place.longitude,
description=place.description,
)
# при изменении координат – обогащение данных путем получения дополнительной информации от API
if location := await LocationClient().get_location(
latitude=place.latitude, longitude=place.longitude
):
updated_place.country = location.alpha2code
updated_place.city = location.city
updated_place.locality = location.locality

matched_rows = await self.places_repository.update_model(
primary_key, **place.dict(exclude_unset=True)
primary_key, **updated_place.dict(exclude_unset=True)
)
await self.session.commit()

# публикация события для попытки импорта информации
# по обновленному объекту любимого места в сервисе Countries Informer
# todo
try:
place_data = CountryCityDTO(
city=updated_place.city,
alpha2code=updated_place.country,
)
EventProducer().publish(
queue_name=settings.rabbitmq.queue.places_import, body=place_data.json()
)
except ValidationError:
logger.warning(
"The message was not well-formed during publishing event.",
exc_info=True,
)

return matched_rows

Expand Down
39 changes: 30 additions & 9 deletions src/transport/handlers/places.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from exceptions import ApiHTTPException, ObjectNotFoundException
from models.places import Place
from schemas.places import PlaceResponse, PlacesListResponse, PlaceUpdate
from schemas.places import PlaceResponse, Description, PlaceUpdate
from schemas.routes import MetadataTag
from services.places_service import PlacesService

Expand All @@ -18,14 +18,14 @@
@router.get(
"",
summary="Получение списка объектов",
response_model=PlacesListResponse,
response_model=Page[Place],
)
async def get_list(
limit: int = Query(
20, gt=0, le=100, description="Ограничение на количество объектов в выборке"
),
places_service: PlacesService = Depends(),
) -> PlacesListResponse:
) -> Page[Place]:
"""
Получение списка любимых мест.

Expand All @@ -34,7 +34,7 @@ async def get_list(
:return:
"""

return PlacesListResponse(data=await places_service.get_places_list(limit=limit))
return paginate(await places_service.get_places_list(limit=limit))


@router.get(
Expand Down Expand Up @@ -66,7 +66,7 @@ async def get_one(
status_code=status.HTTP_201_CREATED,
)
async def create(
place: Place, places_service: PlacesService = Depends()
place_update: PlaceUpdate, places_service: PlacesService = Depends()
) -> PlaceResponse:
"""
Создание нового объекта любимого места по переданным данным.
Expand Down Expand Up @@ -97,11 +97,15 @@ async def update(
Обновление объекта любимого места по переданным данным.

:param primary_key: Идентификатор объекта.
:param place: Данные для обновления объекта.
:param place_request: Данные для обновления объекта.
:param places_service: Сервис для работы с информацией о любимых местах.
:return:
"""

place = Place(
description=place_update.description,
longitude=place_update.longitude,
latitude=place_update.latitude,
)
if not await places_service.update_place(primary_key, place):
raise ObjectNotFoundException

Expand All @@ -127,18 +131,35 @@ async def delete(primary_key: int, places_service: PlacesService = Depends()) ->


@router.post(
"",
"/auto",
summary="Создание нового объекта с автоматическим определением координат",
response_model=PlaceResponse,
status_code=status.HTTP_201_CREATED,
)
async def create_auto() -> PlaceResponse:
async def create_auto(request: Request,
description: Description,
places_service: PlacesService = Depends(),) -> PlaceResponse:
"""
Создание нового объекта любимого места с автоматическим определением координат.

:return:
"""
geo_info: IpinfoQuery = geocoder.ip("me")
if geo_info.latlng:
latitude, longitude = geo_info.latlng
place = Place(
description=description.description,
longitude=longitude,
latitude=latitude,
)

if primary_key := await places_service.create_place(place):
return PlaceResponse(data=await places_service.get_place(primary_key))

raise ApiHTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Объект не был создан",
)
# Пример:
#
# import geocoder
Expand Down