Skip to content

Lab7 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
125 changes: 124 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -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.


Тестирование
2 changes: 1 addition & 1 deletion src/clients/countries.py
Original file line number Diff line number Diff line change
@@ -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
22 changes: 20 additions & 2 deletions src/clients/places.py
Original file line number Diff line number Diff line change
@@ -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:
"""
Удаление объекта любимого места по его идентификатору.
10 changes: 10 additions & 0 deletions src/models/places.py
Original file line number Diff line number Diff line change
@@ -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="Описание")
38 changes: 37 additions & 1 deletion src/schema/mutation.py
Original file line number Diff line number Diff line change
@@ -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):
"""
Функции для удаления объекта любимого места.
13 changes: 11 additions & 2 deletions src/services/places.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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:
"""
Сервис для работы с данными о любимых местах.
"""

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:
"""
Удаление объекта любимого места по его идентификатору.