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/base.py b/ya_tracker_client/domain/entities/base.py index 011640c..5642f5b 100644 --- a/ya_tracker_client/domain/entities/base.py +++ b/ya_tracker_client/domain/entities/base.py @@ -1,7 +1,15 @@ from abc import ABCMeta -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel + + +def tracker_alias_generator(s: str) -> str: + """Convert a string from snake case to camel case and rename url to self""" + if s == "url": + return "self" + return to_camel(s) class AbstractEntity(BaseModel, metaclass=ABCMeta): - ... + model_config = ConfigDict(alias_generator=tracker_alias_generator, populate_by_name=True) diff --git a/ya_tracker_client/domain/entities/checklist.py b/ya_tracker_client/domain/entities/checklist.py new file mode 100644 index 0000000..3188caf --- /dev/null +++ b/ya_tracker_client/domain/entities/checklist.py @@ -0,0 +1,28 @@ + +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): + text: str + checked: bool | None = None + assignee: str | None = None + deadline: Deadline | None = None + + +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 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 new file mode 100644 index 0000000..f74a5ed --- /dev/null +++ b/ya_tracker_client/domain/entities/deadline.py @@ -0,0 +1,9 @@ +from datetime import datetime + +from ya_tracker_client.domain.entities.base import AbstractEntity + + +class Deadline(AbstractEntity): + date: datetime + 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 7f314be..6638026 100644 --- a/ya_tracker_client/domain/entities/issue.py +++ b/ya_tracker_client/domain/entities/issue.py @@ -1,8 +1,10 @@ from datetime import datetime -from pydantic import AliasChoices, Field +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 @@ -12,14 +14,14 @@ class IssueShort(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str class Issue(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str version: int @@ -39,8 +41,8 @@ class Issue(AbstractEntity): last_comment_update_at: datetime | None = None aliases: list[str] | None = None updated_by: UserShort | None = None - created_at: datetime = Field(validation_alias=AliasChoices("createdAt", "created_at")) - created_by: UserShort = Field(validation_alias=AliasChoices("createdBy", "created_by")) + created_at: datetime + created_by: UserShort votes: int updated_at: datetime | None = None status: Status @@ -59,10 +61,7 @@ class IssueCreate(AbstractEntity): followers: list[UserShort | str] | None = None assignee: list[UserShort | str] | None = None unique: str | None = None - attachment_ids: list[str] | None = Field( - default=None, - validation_alias=AliasChoices("attachmentIds", "attachment_ids"), - ) + attachment_ids: list[str] | None = None class IssueEdit(AbstractEntity): @@ -73,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/entities/issue_relationship.py b/ya_tracker_client/domain/entities/issue_relationship.py index 57c126c..9328685 100644 --- a/ya_tracker_client/domain/entities/issue_relationship.py +++ b/ya_tracker_client/domain/entities/issue_relationship.py @@ -1,8 +1,6 @@ from datetime import datetime from enum import Enum -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.issue import IssueShort from ya_tracker_client.domain.entities.issue_status import IssueStatus @@ -22,22 +20,22 @@ class IssueRelationshipTypeEnum(str, Enum): class IssueRelationshipType(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str inward: str outward: str class IssueRelationship(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: int type: IssueRelationshipType direction: str object: IssueShort - created_at: datetime = Field(validation_alias=AliasChoices("createdAt", "created_at")) - updated_at: datetime = Field(validation_alias=AliasChoices("updatedAt", "updated_at")) - created_by: UserShort = Field(validation_alias=AliasChoices("createdBy", "created_by")) - updated_by: UserShort = Field(validation_alias=AliasChoices("updatedBy", "updated_by")) + created_at: datetime + updated_at: datetime + created_by: UserShort + updated_by: UserShort assignee: UserShort | None = None status: IssueStatus diff --git a/ya_tracker_client/domain/entities/issue_status.py b/ya_tracker_client/domain/entities/issue_status.py index a11c981..5e1d7f4 100644 --- a/ya_tracker_client/domain/entities/issue_status.py +++ b/ya_tracker_client/domain/entities/issue_status.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class IssueStatus(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str diff --git a/ya_tracker_client/domain/entities/issue_type.py b/ya_tracker_client/domain/entities/issue_type.py index 9158600..0e7ddf2 100644 --- a/ya_tracker_client/domain/entities/issue_type.py +++ b/ya_tracker_client/domain/entities/issue_type.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class IssueType(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str diff --git a/ya_tracker_client/domain/entities/issue_type_config.py b/ya_tracker_client/domain/entities/issue_type_config.py index e502371..7c1461c 100644 --- a/ya_tracker_client/domain/entities/issue_type_config.py +++ b/ya_tracker_client/domain/entities/issue_type_config.py @@ -1,5 +1,3 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.issue_type import IssueType from ya_tracker_client.domain.entities.resolution import ResolutionShort @@ -7,6 +5,6 @@ class IssueTypeConfig(AbstractEntity): - issue_type: IssueType = Field(validation_alias=AliasChoices("issueType", "issue_type")) + issue_type: IssueType workflow: WorkflowShort resolutions: list[ResolutionShort] diff --git a/ya_tracker_client/domain/entities/priority.py b/ya_tracker_client/domain/entities/priority.py index 41457b9..9a7ae37 100644 --- a/ya_tracker_client/domain/entities/priority.py +++ b/ya_tracker_client/domain/entities/priority.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class Priority(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: int key: str display: str | None = None diff --git a/ya_tracker_client/domain/entities/queue.py b/ya_tracker_client/domain/entities/queue.py index 2885193..75f946a 100644 --- a/ya_tracker_client/domain/entities/queue.py +++ b/ya_tracker_client/domain/entities/queue.py @@ -1,4 +1,4 @@ -from pydantic import AliasChoices, Field +from pydantic import Field from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.issue_type import IssueType @@ -14,55 +14,42 @@ class QueueIdentifier(AbstractEntity): class QueueShort(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str class QueueVersion(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str display: str class Queue(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: int key: str version: int name: str description: str | None = None # TODO: string in documentation, but may be missing in response - TB145879 lead: UserShort - assign_auto: bool = Field(validation_alias=AliasChoices("assignAuto", "assign_auto")) - default_type: IssueType = Field(validation_alias=AliasChoices("defaultType", "default_type")) - default_priority: Priority = Field(validation_alias=AliasChoices("defaultPriority", "default_priority")) - team_users: list[UserShort] = Field(default=list, validation_alias=AliasChoices("teamUsers", "team_users")) - issue_types: list[IssueType] = Field(default=list, validation_alias=AliasChoices("issueTypes", "issue_types")) - versions: list[QueueVersion] = Field(default=list) - workflows: list[WorkflowShort] = Field(default=list) - deny_voting: bool = Field(validation_alias=AliasChoices("denyVoting", "deny_voting")) - issue_types_config: list[IssueTypeConfig] = Field( - default=list, - validation_alias=AliasChoices("issueTypesConfig", "issue_types_config"), - ) + assign_auto: bool + default_type: IssueType + default_priority: Priority + team_users: list[UserShort] = Field(default_factory=list) + issue_types: list[IssueType] = Field(default_factory=list) + versions: list[QueueVersion] = Field(default_factory=list) + workflows: list[WorkflowShort] = Field(default_factory=list) + deny_voting: bool + issue_types_config: list[IssueTypeConfig] = Field(default_factory=list) # TODO: documentation does not contain this fields - TB145879 - deny_conductor_autolink: bool = Field( - validation_alias=AliasChoices("denyConductorAutolink", "deny_conductor_autolink"), - ) - deny_tracker_auto_link: bool = Field( - validation_alias=AliasChoices("denyTrackerAutolink", "deny_tracker_auto_link"), - ) - use_component_permissions_intersection: bool = Field( - validation_alias=AliasChoices("useComponentPermissionsIntersection", "use_component_permissions_intersection"), - ) - workflow_actions_style: str = Field( - validation_alias=AliasChoices("workflowActionsStyle", "workflow_actions_style"), - ) - use_last_signature: bool = Field( - validation_alias=AliasChoices("useLastSignature", "use_last_signature"), - ) + deny_conductor_autolink: bool | None = None + deny_tracker_auto_link: bool | None = None + use_component_permissions_intersection: bool | None = None + workflow_actions_style: str | None = None + use_last_signature: bool | None = None class QueueCreate(AbstractEntity): @@ -70,10 +57,5 @@ class QueueCreate(AbstractEntity): name: str lead: str default_type: str - default_priority: str = Field( - validation_alias=AliasChoices("defaultPriority", "default_priority"), - ) - issue_types_config: list[IssueTypeConfig] = Field( - default=list, - validation_alias=AliasChoices("issueTypesConfig", "issue_types_config"), - ) + default_priority: str + issue_types_config: list[IssueTypeConfig] = Field(default_factory=list) diff --git a/ya_tracker_client/domain/entities/queue_field.py b/ya_tracker_client/domain/entities/queue_field.py index c851924..03bcd01 100644 --- a/ya_tracker_client/domain/entities/queue_field.py +++ b/ya_tracker_client/domain/entities/queue_field.py @@ -9,7 +9,7 @@ class QueueField(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str name: str version: int @@ -19,17 +19,11 @@ class QueueField(AbstractEntity): readonly: bool options: bool suggest: bool - options_provider: QueueFieldOptionsProvider | None = Field( # TODO: not required in response - not documented - default=None, validation_alias=AliasChoices("optionsProvider", "options_provider"), - ) - query_provider: QueueFieldQueryProvider | None = Field( - default=None, validation_alias=AliasChoices("queryProvider", "query_provider"), - ) + options_provider: QueueFieldOptionsProvider | None = None # TODO: not required in response - not documented + query_provider: QueueFieldQueryProvider | None = None order: int # TODO: documentation does not contain this fields - suggest_provider: QueueFieldSuggestProvider | None = Field( - default=None, validation_alias=AliasChoices("suggestProvider", "suggest_provider"), - ) + suggest_provider: QueueFieldSuggestProvider | None = None type: str category: QueueFieldCategory diff --git a/ya_tracker_client/domain/entities/queue_field_category.py b/ya_tracker_client/domain/entities/queue_field_category.py index 554f9a9..4382823 100644 --- a/ya_tracker_client/domain/entities/queue_field_category.py +++ b/ya_tracker_client/domain/entities/queue_field_category.py @@ -1,9 +1,7 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class QueueFieldCategory(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str display: str diff --git a/ya_tracker_client/domain/entities/queue_version.py b/ya_tracker_client/domain/entities/queue_version.py index b191003..8eb0204 100644 --- a/ya_tracker_client/domain/entities/queue_version.py +++ b/ya_tracker_client/domain/entities/queue_version.py @@ -1,19 +1,17 @@ from datetime import date -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.queue import QueueShort class QueueVersion(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: int version: int queue: QueueShort name: str description: str | None = None # TODO: string in documentation, but may be missing in response - TB145879 - start_date: date | None = Field(default=None, validation_alias=AliasChoices("startDate", "start_date")) - due_date: date | None = Field(default=None, validation_alias=AliasChoices("dueDate", "due_date")) + start_date: date | None = None + due_date: date | None = None released: bool archived: bool diff --git a/ya_tracker_client/domain/entities/resolution.py b/ya_tracker_client/domain/entities/resolution.py index 4855cac..eaacbcc 100644 --- a/ya_tracker_client/domain/entities/resolution.py +++ b/ya_tracker_client/domain/entities/resolution.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class ResolutionShort(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str diff --git a/ya_tracker_client/domain/entities/sprint.py b/ya_tracker_client/domain/entities/sprint.py index 24c55b4..dfc48df 100644 --- a/ya_tracker_client/domain/entities/sprint.py +++ b/ya_tracker_client/domain/entities/sprint.py @@ -1,9 +1,7 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class Sprint(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str display: str diff --git a/ya_tracker_client/domain/entities/status.py b/ya_tracker_client/domain/entities/status.py index fd53a16..86b73fe 100644 --- a/ya_tracker_client/domain/entities/status.py +++ b/ya_tracker_client/domain/entities/status.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class Status(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str diff --git a/ya_tracker_client/domain/entities/transition.py b/ya_tracker_client/domain/entities/transition.py index 43b3519..b63401f 100644 --- a/ya_tracker_client/domain/entities/transition.py +++ b/ya_tracker_client/domain/entities/transition.py @@ -1,11 +1,9 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity from ya_tracker_client.domain.entities.issue_status import IssueStatus class Transition(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str to: IssueStatus display: str diff --git a/ya_tracker_client/domain/entities/user.py b/ya_tracker_client/domain/entities/user.py index 8e1c1f7..4fdabcc 100644 --- a/ya_tracker_client/domain/entities/user.py +++ b/ya_tracker_client/domain/entities/user.py @@ -1,50 +1,30 @@ from datetime import datetime -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class UserShort(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str display: str class User(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str uid: int login: str - tracker_uid: int = Field(validation_alias=AliasChoices("trackerUid", "tracker_uid")) - passport_uid: int = Field(validation_alias=AliasChoices("passportUid", "passport_uid")) - cloud_uid: str | None = Field( - default=None, - validation_alias=AliasChoices("cloudUid", "cloud_uid"), - ) - first_name: str = Field(validation_alias=AliasChoices("firstName", "first_name")) - last_name: str = Field(validation_alias=AliasChoices("lastName", "last_name")) + tracker_uid: int + passport_uid: int + cloud_uid: str | None = None + first_name: str + last_name: str display: str email: str external: bool - has_licence: bool | None = Field( - default=None, - validation_alias=AliasChoices("hasLicence", "has_licence"), - ) + has_licence: bool | None = None dismissed: bool - user_new_filters: bool | None = Field( - default=None, - validation_alias=AliasChoices("userNewFilters", "user_new_filters"), - ) - disable_notifications: bool = Field(validation_alias=AliasChoices("disableNotifications", "disable_notifications")) - first_login_date: datetime | None = Field( - default=None, - validation_alias=AliasChoices("firstLoginDate", "first_login_date"), - ) - last_login_date: datetime | None = Field( - default=None, - validation_alias=AliasChoices("lastLoginDate", "last_login_date"), - ) - welcome_mail_sent: bool | None = Field( - default=None, - validation_alias=AliasChoices("welcomeMailSent", "welcome_mail_sent"), - ) + user_new_filters: bool | None = None + disable_notifications: bool + first_login_date: datetime | None = None + last_login_date: datetime | None = None + welcome_mail_sent: bool | None = None diff --git a/ya_tracker_client/domain/entities/workflow.py b/ya_tracker_client/domain/entities/workflow.py index 88ac0c9..64c18f3 100644 --- a/ya_tracker_client/domain/entities/workflow.py +++ b/ya_tracker_client/domain/entities/workflow.py @@ -1,10 +1,8 @@ -from pydantic import AliasChoices, Field - from ya_tracker_client.domain.entities.base import AbstractEntity class WorkflowShort(AbstractEntity): - url: str = Field(validation_alias=AliasChoices("self", "url")) + url: str id: str key: str display: str diff --git a/ya_tracker_client/domain/repositories/__init__.py b/ya_tracker_client/domain/repositories/__init__.py index e69de29..8e32fe6 100644 --- a/ya_tracker_client/domain/repositories/__init__.py +++ b/ya_tracker_client/domain/repositories/__init__.py @@ -0,0 +1,14 @@ +from ya_tracker_client.domain.repositories.checklist import ChecklistRepository +from ya_tracker_client.domain.repositories.issue import IssueRepository +from ya_tracker_client.domain.repositories.issue_relationship import IssueRelationshipRepository +from ya_tracker_client.domain.repositories.queue import QueueRepository +from ya_tracker_client.domain.repositories.user import UserRepository + + +__all__ = [ + "ChecklistRepository", + "IssueRepository", + "IssueRelationshipRepository", + "QueueRepository", + "UserRepository", +] diff --git a/ya_tracker_client/domain/repositories/checklist.py b/ya_tracker_client/domain/repositories/checklist.py new file mode 100644 index 0000000..5b6b009 --- /dev/null +++ b/ya_tracker_client/domain/repositories/checklist.py @@ -0,0 +1,89 @@ +from json import loads + +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 + + +class ChecklistRepository(EntityRepository): + async def create_checklist_item( + self, + issue_id: str, + text: str, + checked: bool | None = None, + assignee: str | None = None, + deadline: Deadline | None = None, + ) -> 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( + method="POST", + uri=f"/issues/{issue_id}/checklistItems", + payload=ChecklistCreate( + text=text, + checked=checked, + assignee=assignee, + deadline=deadline, + ).model_dump(exclude_none=True, by_alias=True), + ) + 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/issue.py b/ya_tracker_client/domain/repositories/issue.py index fc30095..54a7ea6 100644 --- a/ya_tracker_client/domain/repositories/issue.py +++ b/ya_tracker_client/domain/repositories/issue.py @@ -13,7 +13,7 @@ class IssueRepository(EntityRepository): async def get_issue(self, issue_id: str) -> Issue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/get-issue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-issue """ raw_response = await self._client.request( method="GET", @@ -36,7 +36,7 @@ async def create_issue( attachment_ids: list[str] | None = None, ) -> Issue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/create-issue#queue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/create-issue#queue """ raw_response = await self._client.request( method="POST", @@ -64,7 +64,7 @@ async def edit_issue( **kwargs, ) -> Issue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/patch-issue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/patch-issue """ raw_response = await self._client.request( method="PATCH", @@ -79,7 +79,7 @@ async def get_priorities( localized: bool = True, ) -> list[Priority]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/get-priorities + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-priorities """ raw_response = await self._client.request( method="GET", @@ -93,7 +93,7 @@ async def get_issue_transitions( issue_id: str, ) -> list[Transition]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/get-transitions + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-transitions """ raw_response = await self._client.request( method="GET", diff --git a/ya_tracker_client/domain/repositories/issue_relationship.py b/ya_tracker_client/domain/repositories/issue_relationship.py index 3459822..debee33 100644 --- a/ya_tracker_client/domain/repositories/issue_relationship.py +++ b/ya_tracker_client/domain/repositories/issue_relationship.py @@ -16,7 +16,7 @@ async def create_issue_relationship( relationship: IssueRelationshipTypeEnum, ) -> IssueRelationship: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/link-issue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/link-issue """ raw_response = await self._client.request( method="POST", @@ -33,7 +33,7 @@ async def get_issue_relationships( issue_id: str, ) -> list[IssueRelationship]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/get-links + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-links """ raw_response = await self._client.request( method="GET", @@ -47,7 +47,7 @@ async def delete_issue_relationships( link_id: int, ) -> None: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/issues/delete-link-issue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/delete-link-issue """ await self._client.request( method="DELETE", diff --git a/ya_tracker_client/domain/repositories/queue.py b/ya_tracker_client/domain/repositories/queue.py index d00efd9..08817d7 100644 --- a/ya_tracker_client/domain/repositories/queue.py +++ b/ya_tracker_client/domain/repositories/queue.py @@ -18,7 +18,7 @@ async def create_queue( issue_types_config: list[IssueTypeConfig], ) -> Queue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/create-queue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/create-queue """ raw_response = await self._client.request( method="POST", @@ -36,7 +36,7 @@ async def create_queue( async def get_queue(self, queue_id: str | int) -> Queue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/get-queue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/get-queue """ raw_response = await self._client.request( method="GET", @@ -46,7 +46,7 @@ async def get_queue(self, queue_id: str | int) -> Queue: async def get_queues(self) -> list[Queue]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/get-queues + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/get-queues """ raw_response = await self._client.request( method="GET", @@ -56,7 +56,7 @@ async def get_queues(self) -> list[Queue]: async def get_queue_versions(self, queue_id: str | int) -> list[QueueVersion]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/get-versions + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/get-versions """ raw_response = await self._client.request( method="GET", @@ -66,22 +66,17 @@ async def get_queue_versions(self, queue_id: str | int) -> list[QueueVersion]: async def get_queue_fields(self, queue_id: str | int) -> list[QueueField]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/get-fields + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/get-fields """ raw_response = await self._client.request( 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: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/delete-queue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/delete-queue """ await self._client.request( method="DELETE", @@ -90,7 +85,7 @@ async def delete_queue(self, queue_id: str | int) -> None: async def restore_queue(self, queue_id: str | int) -> Queue: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/restore-queue + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/restore-queue """ raw_response = await self._client.request( method="POST", @@ -100,7 +95,7 @@ async def restore_queue(self, queue_id: str | int) -> Queue: async def delete_tag_in_queue(self, queue_id: str | int, tag_name: str) -> None: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/concepts/queues/delete-tag + YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/queues/delete-tag """ await self._client.request( method="DELETE", diff --git a/ya_tracker_client/domain/repositories/user.py b/ya_tracker_client/domain/repositories/user.py index 93add34..ba90896 100644 --- a/ya_tracker_client/domain/repositories/user.py +++ b/ya_tracker_client/domain/repositories/user.py @@ -12,7 +12,7 @@ class UserRepository(EntityRepository): async def get_myself(self) -> User: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/get-user-info + YC docs: https://cloud.yandex.com/en/docs/tracker/get-user-info """ raw_response = await self._client.request( method="GET", @@ -26,7 +26,7 @@ async def get_user( uid: int | None = None, ) -> User: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/get-user + YC docs: https://cloud.yandex.com/en/docs/tracker/get-user """ if (login is None) and (uid is None): raise ClientError("Please provide login or uid for this request") @@ -43,7 +43,7 @@ async def get_user( async def get_users(self) -> list[User]: """ - Yandex Cloud documentation for method: https://cloud.yandex.ru/docs/tracker/get-users + YC docs: https://cloud.yandex.com/en/docs/tracker/get-users """ raw_response = await self._client.request( method="GET", diff --git a/ya_tracker_client/service/api.py b/ya_tracker_client/service/api.py index 0a7c21c..191f90a 100644 --- a/ya_tracker_client/service/api.py +++ b/ya_tracker_client/service/api.py @@ -1,7 +1,10 @@ -from ya_tracker_client.domain.repositories.issue import IssueRepository -from ya_tracker_client.domain.repositories.issue_relationship import IssueRelationshipRepository -from ya_tracker_client.domain.repositories.queue import QueueRepository -from ya_tracker_client.domain.repositories.user import UserRepository +from ya_tracker_client.domain.repositories import ( + ChecklistRepository, + IssueRelationshipRepository, + IssueRepository, + QueueRepository, + UserRepository, +) from ya_tracker_client.infrastructure.client import AiohttpClient @@ -10,6 +13,7 @@ class YaTrackerClient( IssueRepository, QueueRepository, UserRepository, + ChecklistRepository, ): def __init__( self, 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", +]