From 626a705c578531892ad3d7c0a8ac135c02bbfafb Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Sun, 31 Mar 2024 00:15:56 +0500 Subject: [PATCH] Lab7 --- docs/source/index.rst | 125 ++++++++++++++++++++++++++++++++++++++- src/clients/countries.py | 2 +- src/clients/places.py | 22 ++++++- src/models/places.py | 10 ++++ src/schema/mutation.py | 38 +++++++++++- src/services/places.py | 13 +++- 6 files changed, 203 insertions(+), 7 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 9c22508..7136694 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,22 +5,145 @@ GraphQL API шлюз для взаимодействия с микросерви Зависимости =========== +Install the appropriate software: +Docker Desktop. +Git. +PyCharm (optional). Установка ========= +Clone the repository to your computer: +git clone https://github.com/mnv/python-course-graphql-gateway +To configure the application copy .env.sample into .env file: + +cp .env.sample .env +This file contains environment variables that will share their values across the application. The sample file (.env.sample) contains a set of variables with default values. So it can be configured depending on the environment. + +Build the container using Docker Compose: + +docker compose build +This command should be run from the root directory where Dockerfile is located. You also need to build the docker container again in case if you have updated requirements.txt. + +To run the project inside the Docker container: + +docker compose up +When containers are up server starts at http://0.0.0.0:8000/graphql. You can open it in your browser. Использование ============= - +Query example to request a list of favorite places: + +query { + places { + latitude + longitude + description + city + locality + } +} +Query example to request a list of favorite places with countries information: + +query { + places { + latitude + longitude + description + city + locality + country { + name + capital + alpha2code + alpha3code + capital + region + subregion + population + latitude + longitude + demonym + area + numericCode + flag + currencies + languages + } + } +} +Query example to get specific favorite place: + +{ + place(placeId:1) { + id + latitude + longitude + description + city + locality + } +} +Query example to create new favorite place: + +mutation { + createPlace ( + latitude: 25.20485, + longitude: 55.27078, + description: "Nice food." + ) { + place { + id + latitude + longitude + description + city + locality + } + result + } +} +Query example to delete specific favorite place: + +mutation { + deletePlace(placeId: 1) { + result + } +} +Query example to create a favorite place: + + +This query will request additional information about related countries in optimal way using data loaders to prevent N + 1 requests problem. Автоматизация ============= +The project contains a special Makefile that provides shortcuts for a set of commands: + +Build the Docker container: + +make build +Generate Sphinx documentation run: + +make docs-html +Autoformat source code: + +make format +Static analysis (linters): + +make lint +Autotests: + +make test +The test coverage report will be located at src/htmlcov/index.html. So you can estimate the quality of automated test coverage. + +Run autoformat, linters and tests in one command: +make all +Run these commands from the source directory where Makefile is located. Тестирование diff --git a/src/clients/countries.py b/src/clients/countries.py index 8ca1260..9668e46 100644 --- a/src/clients/countries.py +++ b/src/clients/countries.py @@ -54,7 +54,7 @@ def get_list_by_codes(self, alpha2codes: set[str]) -> Optional[list[CountryModel currencies=country.get("currencies"), languages=country.get("languages"), ) - for country in response + for country in response.get("results", []) ] return None diff --git a/src/clients/places.py b/src/clients/places.py index bc7139e..a3e8dcd 100644 --- a/src/clients/places.py +++ b/src/clients/places.py @@ -3,7 +3,7 @@ from urllib.parse import urlencode, urljoin from src.clients.base.base import BaseClient -from src.models.places import PlaceModel +from src.models.places import PlaceModel, UpdatePlaceModel from src.settings import settings @@ -37,7 +37,6 @@ def get_list(self) -> Optional[list[PlaceModel]]: """ Получение списка любимых мест. TODO: добавить пагинацию. - :return: """ endpoint = "/api/v1/places" @@ -66,6 +65,25 @@ def create_place(self, place: PlaceModel) -> Optional[PlaceModel]: return None + def update_place( + self, place_id: int, place: UpdatePlaceModel + ) -> tuple[bool, PlaceModel | None]: + """ + Обновление объекта любимого места по его идентификатору. + :param place_id: Идентификатор объекта. + :param place: Объект любимого места для обновления. + :return: + """ + + endpoint = f"/api/v1/places/{place_id}" + url = urljoin(self.base_url, endpoint) + if response := self._request(self.PATCH, url, body=place.dict()): + if place_data := response.get("data"): + return True, self.__build_model(place_data) + + return False, None + + def delete_place(self, place_id: int) -> bool: """ Удаление объекта любимого места по его идентификатору. diff --git a/src/models/places.py b/src/models/places.py index 3c3cca6..6741174 100644 --- a/src/models/places.py +++ b/src/models/places.py @@ -17,3 +17,13 @@ class PlaceModel(TimeStampMixin, BaseModel): country: Optional[str] = Field(title="ISO Alpha2-код страны") city: Optional[str] = Field(title="Название города") locality: Optional[str] = Field(title="Местонахождение") + + +class UpdatePlaceModel(TimeStampMixin, BaseModel): + """ + Модель для обновления места. + """ + + latitude: float | None = Field(title="Широта") + longitude: float | None = Field(title="Долгота") + description: str | None = Field(title="Описание") \ No newline at end of file diff --git a/src/schema/mutation.py b/src/schema/mutation.py index ad5a467..767e297 100644 --- a/src/schema/mutation.py +++ b/src/schema/mutation.py @@ -3,7 +3,7 @@ import graphene from graphql import ResolveInfo -from src.models.places import PlaceModel +from src.models.places import PlaceModel, UpdatePlaceModel from src.schema.query import Place from src.services.places import PlacesService @@ -48,6 +48,42 @@ def mutate( return CreatePlace(place=place, result=bool(place)) +class UpdatePlace(graphene.Mutation): + """ + Функции для обновления объекта любимого места. + """ + + class Arguments: + place_id = graphene.Int() + latitude = graphene.Float() + longitude = graphene.Float() + description = graphene.String() + + result = graphene.Boolean() + place = graphene.Field(Place) + + @staticmethod + def mutate(parent: Optional[dict], info: ResolveInfo, place_id: int, latitude: float | None = None, longitude: float | None = None, description: str | None = None) -> "UpdatePlace": + """ + Обработка запроса для обновления объекта по его идентификатору. + :param parent: Информация о родительском объекте (при наличии). + :param info: Объект с метаинформацией и данных о контексте запроса. + :param place_id: Идентификатор объекта для обновления. + :param latitude: Широта. + :param longitude: Долгота. + :param description: Описание. + :return: + """ + + # получение результата обновления объекта + model = UpdatePlaceModel( + latitude=latitude, longitude=longitude, description=description + ) + result, place = PlacesService().update_place(place_id, model) + + return UpdatePlace(result=result, place=place) + + class DeletePlace(graphene.Mutation): """ Функции для удаления объекта любимого места. diff --git a/src/services/places.py b/src/services/places.py index e28e604..ff7d621 100644 --- a/src/services/places.py +++ b/src/services/places.py @@ -1,7 +1,7 @@ from typing import Optional from src.clients.places import PlacesClient -from src.models.places import PlaceModel +from src.models.places import PlaceModel, UpdatePlaceModel class PlacesService: @@ -9,7 +9,7 @@ class PlacesService: Сервис для работы с данными о любимых местах. """ - def get_place(self, place_id: int) -> Optional[PlaceModel]: + def get_place(self) -> Optional[PlaceModel]: """ Получение объекта любимого места по его идентификатору. @@ -37,6 +37,15 @@ def create_place(self, place: PlaceModel) -> Optional[PlaceModel]: return PlacesClient().create_place(place) + def update_place(self, place_id: int, model: UpdatePlaceModel) -> tuple[bool, PlaceModel | None]: + """ + Обновление объекта любимого места по его идентификатору. + + :param place_id: Идентификатор объекта. + :return: + """ + return PlacesClient().update_place(place_id, model) + def delete_place(self, place_id: int) -> bool: """ Удаление объекта любимого места по его идентификатору.