From 7d9bfb4f2d6823923c1a7c0ed26246293bdc04c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=90=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=BC=D0=BE=D0=B2?= Date: Thu, 28 Sep 2023 16:50:00 +0300 Subject: [PATCH] feat: add checklist support --- ya_tracker_client/domain/client/base.py | 4 +- .../domain/entities/checklist.py | 22 ++++-- ya_tracker_client/domain/entities/deadline.py | 3 +- ya_tracker_client/domain/entities/issue.py | 35 ++++++++++ .../domain/repositories/checklist.py | 70 ++++++++++++++++--- .../domain/repositories/queue.py | 5 -- ya_tracker_client/utils/__init__.py | 6 ++ ya_tracker_client/utils/encoder.py | 32 +++++++++ 8 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 ya_tracker_client/utils/__init__.py create mode 100644 ya_tracker_client/utils/encoder.py diff --git a/ya_tracker_client/domain/client/base.py b/ya_tracker_client/domain/client/base.py index ac19adf..b9b270e 100644 --- a/ya_tracker_client/domain/client/base.py +++ b/ya_tracker_client/domain/client/base.py @@ -1,6 +1,5 @@ from abc import ABC, abstractmethod from http import HTTPStatus -from json import dumps from logging import getLogger from typing import Any @@ -14,6 +13,7 @@ ClientObjectNotFoundError, ClientSufficientRightsError, ) +from ya_tracker_client.utils import serialize_entity logger = getLogger(__name__) @@ -60,7 +60,7 @@ async def request( uri = f"{self._base_url}/{self._api_version}{uri}" bytes_payload = BytesPayload( - value=bytes(dumps(payload), encoding="utf-8"), + value=bytes(serialize_entity(payload), encoding="utf-8"), content_type="application/json", ) diff --git a/ya_tracker_client/domain/entities/checklist.py b/ya_tracker_client/domain/entities/checklist.py index a3e6aae..3188caf 100644 --- a/ya_tracker_client/domain/entities/checklist.py +++ b/ya_tracker_client/domain/entities/checklist.py @@ -1,5 +1,7 @@ + from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.deadline import Deadline +from ya_tracker_client.domain.entities.user import UserShort class ChecklistCreate(AbstractEntity): @@ -9,12 +11,18 @@ class ChecklistCreate(AbstractEntity): deadline: Deadline | None = None -class Checklist(AbstractEntity): - ... +class ChecklistItem(AbstractEntity): + id: str + text: str + text_html: str + checked: bool + checklist_item_type: str + deadline: Deadline | None = None + assignee: UserShort | None = None -class IssueWithoutChecklist(AbstractEntity): - url: str - id: str - key: str - version: int +class ChecklistItemEdit(AbstractEntity): + text: str + checked: bool | None = None + assignee: str | None = None + deadline: Deadline | None = None diff --git a/ya_tracker_client/domain/entities/deadline.py b/ya_tracker_client/domain/entities/deadline.py index d09225b..f74a5ed 100644 --- a/ya_tracker_client/domain/entities/deadline.py +++ b/ya_tracker_client/domain/entities/deadline.py @@ -5,4 +5,5 @@ class Deadline(AbstractEntity): date: datetime - deadline_type: str = "deadline" + deadline_type: str = "date" + is_exceeded: bool | None = None diff --git a/ya_tracker_client/domain/entities/issue.py b/ya_tracker_client/domain/entities/issue.py index 70db161..6638026 100644 --- a/ya_tracker_client/domain/entities/issue.py +++ b/ya_tracker_client/domain/entities/issue.py @@ -1,6 +1,10 @@ from datetime import datetime +from pydantic import Field + from ya_tracker_client.domain.entities.base import AbstractEntity +from ya_tracker_client.domain.entities.checklist import ChecklistItem +from ya_tracker_client.domain.entities.issue_status import IssueStatus from ya_tracker_client.domain.entities.issue_type import IssueType from ya_tracker_client.domain.entities.priority import Priority from ya_tracker_client.domain.entities.queue import QueueIdentifier, QueueShort @@ -68,3 +72,34 @@ class IssueEdit(AbstractEntity): type: IssueType | None = None priority: Priority | None = None followers: list[UserShort | str] | None = None + + +class IssueWithChecklist(AbstractEntity): + url: str + id: str + key: str + version: int + + summary: str + description: str | None = None + type: IssueType + priority: Priority + followers: list[UserShort] | None = None + queue: QueueShort + favorite: bool + assignee: UserShort | None = None + + last_comment_updated_at: datetime | None = None + pending_reply_from: UserShort | None = None + created_at: datetime + updated_at: datetime + created_by: UserShort + updated_by: UserShort | None = None + votes: int + status: IssueStatus + previous_status: IssueStatus | None = None + status_start_time: datetime + previous_status_last_assignee: UserShort | None = None + deadline: datetime | None = None + + checklist_items: list[ChecklistItem] = Field(default_factory=list) diff --git a/ya_tracker_client/domain/repositories/checklist.py b/ya_tracker_client/domain/repositories/checklist.py index f78437d..5b6b009 100644 --- a/ya_tracker_client/domain/repositories/checklist.py +++ b/ya_tracker_client/domain/repositories/checklist.py @@ -1,14 +1,11 @@ from json import loads -from logging import getLogger -from ya_tracker_client.domain.entities.checklist import Checklist, ChecklistCreate +from ya_tracker_client.domain.entities.checklist import ChecklistCreate, ChecklistItem, ChecklistItemEdit from ya_tracker_client.domain.entities.deadline import Deadline +from ya_tracker_client.domain.entities.issue import IssueWithChecklist from ya_tracker_client.domain.repositories.base import EntityRepository -logger = getLogger(__name__) - - class ChecklistRepository(EntityRepository): async def create_checklist_item( self, @@ -17,8 +14,10 @@ async def create_checklist_item( checked: bool | None = None, assignee: str | None = None, deadline: Deadline | None = None, - ) -> Checklist: + ) -> IssueWithChecklist: """ + Use this request to create a checklist and add new items to it. + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/add-checklist-item """ raw_response = await self._client.request( @@ -31,5 +30,60 @@ async def create_checklist_item( deadline=deadline, ).model_dump(exclude_none=True, by_alias=True), ) - print(raw_response) - return Checklist(**loads(raw_response)) + return IssueWithChecklist(**loads(raw_response)) + + async def get_checklist_items(self, issue_id: str) -> list[ChecklistItem]: + """ + Use this request to get the parameters of an issue's checklist. + + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-checklist + """ + raw_response = await self._client.request( + method="GET", + uri=f"/issues/{issue_id}/checklistItems", + ) + return [ChecklistItem(**raw_checklist_item) for raw_checklist_item in loads(raw_response)] + + async def edit_checklist_item( + self, + issue_id: str, + checklist_item_id: str, + text: str, + checked: bool | None = None, + assignee: str | None = None, + deadline: Deadline | None = None, + ) -> IssueWithChecklist: + """ + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/edit-checklist + """ + raw_response = await self._client.request( + method="PATCH", + uri=f"/issues/{issue_id}/checklistItems/{checklist_item_id}", + payload=ChecklistItemEdit( + text=text, + checked=checked, + assignee=assignee, + deadline=deadline, + ).model_dump(exclude_none=True, by_alias=True), + ) + return IssueWithChecklist(**loads(raw_response)) + + async def delete_checklist(self, issue_id: str) -> IssueWithChecklist: + """ + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/delete-checklist + """ + raw_response = await self._client.request( + method="DELETE", + uri=f"/issues/{issue_id}/checklistItems", + ) + return IssueWithChecklist(**loads(raw_response)) + + async def delete_checklist_item(self, issue_id: str, checklist_item_id: str): + """ + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/delete-checklist-item + """ + raw_response = await self._client.request( + method="DELETE", + uri=f"/issues/{issue_id}/checklistItems/{checklist_item_id}", + ) + return IssueWithChecklist(**loads(raw_response)) diff --git a/ya_tracker_client/domain/repositories/queue.py b/ya_tracker_client/domain/repositories/queue.py index 1bb9b70..08817d7 100644 --- a/ya_tracker_client/domain/repositories/queue.py +++ b/ya_tracker_client/domain/repositories/queue.py @@ -72,11 +72,6 @@ async def get_queue_fields(self, queue_id: str | int) -> list[QueueField]: method="GET", uri=f"/queues/{queue_id}/fields", ) - for raw_queue_field in loads(raw_response): - print(*raw_queue_field.items(), sep="\n") - print() - QueueField(**raw_queue_field) - return [QueueField(**raw_queue_field) for raw_queue_field in loads(raw_response)] async def delete_queue(self, queue_id: str | int) -> None: diff --git a/ya_tracker_client/utils/__init__.py b/ya_tracker_client/utils/__init__.py new file mode 100644 index 0000000..3148016 --- /dev/null +++ b/ya_tracker_client/utils/__init__.py @@ -0,0 +1,6 @@ +from ya_tracker_client.utils.encoder import serialize_entity + + +__all__ = [ + "serialize_entity", +] diff --git a/ya_tracker_client/utils/encoder.py b/ya_tracker_client/utils/encoder.py new file mode 100644 index 0000000..9f360f2 --- /dev/null +++ b/ya_tracker_client/utils/encoder.py @@ -0,0 +1,32 @@ +from datetime import datetime +from decimal import Decimal +from json import JSONEncoder, dumps +from uuid import UUID +from zoneinfo import ZoneInfo + +from yarl import URL + + +class UpgradedJSONEncoder(JSONEncoder): + def default(self, entity): + if isinstance(entity, datetime): + if entity.tzinfo is None: # for YYYY-MM-DDThh:mm:ss.sss±hhmm format + entity = entity.replace(tzinfo=ZoneInfo("UTC")) + return entity.isoformat() + + if isinstance(entity, Decimal) or isinstance(entity, UUID): + return str(entity) + + if isinstance(entity, URL): + return str(entity) + + return JSONEncoder.default(self, entity) + + +def serialize_entity(entity): + return dumps(entity, cls=UpgradedJSONEncoder) + + +__all__ = [ + "serialize_entity", +]