Skip to content
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

feat: support attachments #26

Merged
merged 2 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: support attachments
  • Loading branch information
danfimov committed Sep 30, 2023
commit 2b9463e8e570fe94053e9cd1f5cf079c18946a7b
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
Loading