Skip to content

Commit

Permalink
feat: support attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
danfimov committed Sep 30, 2023
1 parent 06fb175 commit 2b9463e
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 43 deletions.
9 changes: 1 addition & 8 deletions examples/get_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ async def main() -> None:
oauth_token=API_TOKEN,
)

me = await client.get_myself()
print(me)

me = await client.get_user(me.login, me.uid)
print(me)

all_me = await client.get_users()
print(all_me)
...

await client.stop()

Expand Down
39 changes: 39 additions & 0 deletions examples/get_test_entities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import os
from asyncio import run

from dotenv import load_dotenv

from ya_tracker_client import YaTrackerClient


load_dotenv()
# from registered application at Yandex OAuth - https://oauth.yandex.ru/
API_TOKEN = os.getenv("API_TOKEN")
# from admin panel at Yandex Tracker - https://tracker.yandex.ru/admin/orgs
API_ORGANISATION_ID = os.getenv("API_ORGANISATION_ID")


async def main() -> None:
client = YaTrackerClient(
organisation_id=API_ORGANISATION_ID,
oauth_token=API_TOKEN,
)

# requests for tests
me = await client.get_myself()
await client.get_user(uid=me.uid)
await client.get_users()
await client.get_issue('TRACKER-1')
await client.get_queue('TRACKER')
await client.get_issue_relationships('TRACKER-1')
await client.get_checklist_items("TRACKER-1")
await client.get_components()
await client.get_worklog("TRACKER-1")
await client.get_worklog_records_by_parameters(me.login)
await client.get_attachments_list('TRACKER-1')

await client.stop()


if __name__ == "__main__":
run(main())
16 changes: 10 additions & 6 deletions ya_tracker_client/domain/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from logging import getLogger
from typing import Any

from aiohttp import BytesPayload
from aiohttp import BytesPayload, FormData

from ya_tracker_client.domain.client.errors import (
ClientAuthError,
Expand Down Expand Up @@ -56,11 +56,15 @@ async def request(
uri: str,
params: dict[str, Any] | None = None,
payload: dict[str, Any] | None = None,
form: FormData | None = None,
) -> bytes:
bytes_payload = BytesPayload(
value=bytes(serialize_entity(payload), encoding="utf-8"),
content_type="application/json",
)
if form:
bytes_payload = form
else:
bytes_payload = BytesPayload(
value=bytes(serialize_entity(payload), encoding="utf-8"),
content_type="application/json",
)

status, body = await self._make_request(
method=method,
Expand All @@ -77,7 +81,7 @@ async def _make_request(
method: str,
url: str,
params: dict[str, Any] | None = None,
data: bytes | BytesPayload | None = None,
data: bytes | BytesPayload | FormData | None = None,
) -> tuple[int, bytes]:
"""
Get raw response from via http-client.
Expand Down
23 changes: 23 additions & 0 deletions ya_tracker_client/domain/entities/attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import datetime

from pydantic import Field

from ya_tracker_client.domain.entities.base import AbstractEntity
from ya_tracker_client.domain.entities.user import UserShort


class AttachmentMetadata(AbstractEntity):
size: str


class Attachment(AbstractEntity):
url: str
id: int
name: str
content: str
thumbnail: str | None = None
created_by: UserShort
created_at: datetime
mimetype: str = Field(..., examples=['text/plain', 'image/png'])
size: int
metadata: AttachmentMetadata | None = None
2 changes: 2 additions & 0 deletions ya_tracker_client/domain/repositories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from ya_tracker_client.domain.repositories.queue import QueueRepository
from ya_tracker_client.domain.repositories.user import UserRepository
from ya_tracker_client.domain.repositories.worklog import WorklogRepository
from ya_tracker_client.domain.repositories.attachment import AttachmentRepository


__all__ = [
"AttachmentRepository",
"ChecklistRepository",
"ComponentRepository",
"IssueRelationshipRepository",
Expand Down
105 changes: 105 additions & 0 deletions ya_tracker_client/domain/repositories/attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from typing import BinaryIO

from aiohttp import FormData

from ya_tracker_client.domain.repositories.base import EntityRepository
from ya_tracker_client.domain.entities.attachment import Attachment


class AttachmentRepository(EntityRepository):
async def get_attachments_list(self, issue_id: str) -> list[Attachment]:
"""
Use this request to get a list of files attached to an issue and to comments below it.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-attachments-list
"""
raw_response = await self._client.request(
method='GET',
uri=f"/issues/{issue_id}/attachments",
)
return self._decode(raw_response, Attachment, plural=True)

async def download_attachment(
self,
issue_id: str,
attachment_id: str | int,
filename: str
) -> bytes:
"""
Use this request to download files attached to issues.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-attachment
"""
return await self._client.request(
method='GET',
uri=f"/issues/{issue_id}/attachments/{attachment_id}/{filename}",
)

async def download_thumbnail(
self,
issue_id: str,
attachment_id: str | int,
) -> bytes:
"""
Get thumbnails of image files attached to issues.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/get-attachment-preview
"""
return await self._client.request(
method="GET",
uri=f"/issues/{issue_id}/thumbnails/{attachment_id}",
)

async def attach_file(
self,
issue_id: str,
file: BinaryIO,
filename: str | None = None,
) -> Attachment:
"""
Attach a file to an issue.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/post-attachment
"""
form = FormData(fields={"file_data": file})
raw_response = await self._client.request(
method="POST",
uri=f"/issues/{issue_id}/attachments",
params={"filename": filename} if filename else None,
form=form,
)
return self._decode(raw_response, Attachment)

async def upload_temp_file(
self,
file: BinaryIO,
filename: str | None = None,
) -> Attachment:
"""
Upload temporary file.
Use this request to upload a file to Tracker first, and then
attach it when creating an issue or adding a comment.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/temp-attachment
"""
form = FormData()
form.add_field("file_data", file)
raw_response = await self._client.request(
method="POST",
uri="/attachments/",
params={"filename": filename} if filename else None,
form=form,
)
return self._decode(raw_response, Attachment)

async def delete_attachment(self, issue_id: str, attachment_id: str | int) -> None:
"""
Delete attached file.
YC docs: https://cloud.yandex.com/en/docs/tracker/concepts/issues/delete-attachment
"""
await self._client.request(
method="DELETE",
uri=f"/issues/{issue_id}/attachments/{attachment_id}/",
)
2 changes: 1 addition & 1 deletion ya_tracker_client/domain/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class DeserializationMixin:
@staticmethod
def deserialize(
def _decode(
value: bytes,
return_type: Type[BaseModel] | None = None,
plural: bool = False,
Expand Down
10 changes: 5 additions & 5 deletions ya_tracker_client/domain/repositories/checklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async def create_checklist_item(
deadline=deadline,
).model_dump(exclude_none=True, by_alias=True),
)
return self.deserialize(raw_response, IssueWithChecklist)
return self._decode(raw_response, IssueWithChecklist)

async def get_checklist_items(self, issue_id: str) -> list[ChecklistItem]:
"""
Expand All @@ -40,7 +40,7 @@ async def get_checklist_items(self, issue_id: str) -> list[ChecklistItem]:
method="GET",
uri=f"/issues/{issue_id}/checklistItems",
)
return self.deserialize(raw_response, ChecklistItem, plural=True)
return self._decode(raw_response, ChecklistItem, plural=True)

async def edit_checklist_item(
self,
Expand All @@ -64,7 +64,7 @@ async def edit_checklist_item(
deadline=deadline,
).model_dump(exclude_none=True, by_alias=True),
)
return self.deserialize(raw_response, IssueWithChecklist)
return self._decode(raw_response, IssueWithChecklist)

async def delete_checklist(self, issue_id: str) -> IssueWithChecklist:
"""
Expand All @@ -74,7 +74,7 @@ async def delete_checklist(self, issue_id: str) -> IssueWithChecklist:
method="DELETE",
uri=f"/issues/{issue_id}/checklistItems",
)
return self.deserialize(raw_response, IssueWithChecklist)
return self._decode(raw_response, IssueWithChecklist)

async def delete_checklist_item(self, issue_id: str, checklist_item_id: str):
"""
Expand All @@ -84,4 +84,4 @@ async def delete_checklist_item(self, issue_id: str, checklist_item_id: str):
method="DELETE",
uri=f"/issues/{issue_id}/checklistItems/{checklist_item_id}",
)
return self.deserialize(raw_response, IssueWithChecklist)
return self._decode(raw_response, IssueWithChecklist)
2 changes: 1 addition & 1 deletion ya_tracker_client/domain/repositories/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ async def get_components(self) -> list[Component]:
method="GET",
uri="/components",
)
return self.deserialize(raw_response, Component, plural=True)
return self._decode(raw_response, Component, plural=True)
10 changes: 5 additions & 5 deletions ya_tracker_client/domain/repositories/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async def get_issue(self, issue_id: str) -> Issue:
method="GET",
uri=f"/issues/{issue_id}",
)
return self.deserialize(raw_response, Issue)
return self._decode(raw_response, Issue)

async def create_issue(
self,
Expand Down Expand Up @@ -53,7 +53,7 @@ async def create_issue(
attachment_ids=attachment_ids,
).model_dump(exclude_none=True, by_alias=True),
)
return self.deserialize(raw_response, Issue)
return self._decode(raw_response, Issue)

async def edit_issue(
self,
Expand All @@ -70,7 +70,7 @@ async def edit_issue(
params={"version": version} if version is not None else None,
payload=IssueEdit(**kwargs).model_dump(exclude_unset=True),
)
return self.deserialize(raw_response, Issue)
return self._decode(raw_response, Issue)

async def get_priorities(
self,
Expand All @@ -84,7 +84,7 @@ async def get_priorities(
uri="/priorities/",
params={"localized": str(localized)},
)
return self.deserialize(raw_response, Priority)
return self._decode(raw_response, Priority)

async def get_issue_transitions(
self,
Expand All @@ -97,4 +97,4 @@ async def get_issue_transitions(
method="GET",
uri=f"/issues/{issue_id}/transitions/",
)
return self.deserialize(raw_response, Transition, plural=True)
return self._decode(raw_response, Transition, plural=True)
4 changes: 2 additions & 2 deletions ya_tracker_client/domain/repositories/issue_relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def create_issue_relationship(
relationship=relationship,
).model_dump(exclude_none=True, by_alias=True),
)
return self.deserialize(raw_response, IssueRelationship)
return self._decode(raw_response, IssueRelationship)

async def get_issue_relationships(
self,
Expand All @@ -37,7 +37,7 @@ async def get_issue_relationships(
method="GET",
uri=f"/issues/{issue_id}/links",
)
return self.deserialize(raw_response, IssueRelationship, plural=True)
return self._decode(raw_response, IssueRelationship, plural=True)

async def delete_issue_relationships(
self,
Expand Down
Loading

0 comments on commit 2b9463e

Please sign in to comment.