Skip to content

Commit c31b4cf

Browse files
committed
implement support for Forgejo comments and reactions
1 parent b0d0094 commit c31b4cf

File tree

2 files changed

+127
-58
lines changed

2 files changed

+127
-58
lines changed

ogr/abstract.py

Lines changed: 126 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717

1818
import github
1919
import gitlab
20-
import pyforgejo
2120
import requests
2221

2322
from ogr.deprecation import deprecate_and_set_removal
2423
from ogr.exceptions import (
2524
APIException,
26-
ForgejoAPIException,
2725
GitForgeInternalError,
2826
GithubAPIException,
2927
GitlabAPIException,
@@ -61,11 +59,7 @@ def __check_for_internal_failure(ex: APIException):
6159

6260

6361
def __wrap_exception(
64-
ex: Union[
65-
github.GithubException,
66-
gitlab.GitlabError,
67-
pyforgejo.core.api_error.ApiError,
68-
],
62+
ex: Union[github.GithubException, gitlab.GitlabError],
6963
) -> APIException:
7064
"""
7165
Wraps uncaught exception in one of ogr exceptions.
@@ -82,7 +76,6 @@ def __wrap_exception(
8276
MAPPING = {
8377
github.GithubException: GithubAPIException,
8478
gitlab.GitlabError: GitlabAPIException,
85-
pyforgejo.core.api_error.ApiError: ForgejoAPIException,
8679
}
8780

8881
for caught_exception, ogr_exception in MAPPING.items():
@@ -121,11 +114,7 @@ def wrapper(*args, **kwargs):
121114
) from ex
122115
except APIException as ex:
123116
__check_for_internal_failure(ex)
124-
except (
125-
github.GithubException,
126-
gitlab.GitlabError,
127-
pyforgejo.core.api_error.ApiError,
128-
) as ex:
117+
except (github.GithubException, gitlab.GitlabError) as ex:
129118
__check_for_internal_failure(__wrap_exception(ex))
130119

131120
return wrapper
@@ -168,34 +157,15 @@ def __repr__(self) -> str:
168157

169158

170159
class Reaction(OgrAbstractClass):
171-
def __init__(
172-
self,
173-
raw_reaction: Any,
174-
reaction_id: Optional[int] = None,
175-
parent: Optional[Any] = None,
176-
) -> None:
160+
def __init__(self, raw_reaction: Any) -> None:
177161
self._raw_reaction = raw_reaction
178-
self._id = reaction_id
179-
self._parent = parent
180162

181163
def __str__(self):
182164
return f"Reaction(raw_reaction={self._raw_reaction})"
183165

184166
def delete(self) -> None:
185167
"""Delete a reaction."""
186-
if not self._id:
187-
raise ValueError("Cannot delete a reaction without an ID.")
188-
if not self._parent:
189-
raise ValueError("Cannot delete a reaction without a parent object.")
190-
if not self._raw_reaction or "content" not in self._raw_reaction:
191-
raise ValueError("Invalid raw reaction data.")
192-
193-
self._parent.client.issue.delete_comment_reaction(
194-
owner=self._parent.repo_namespace,
195-
repo=self._parent.repo_name,
196-
id=self._id,
197-
content=self._raw_reaction["content"],
198-
)
168+
raise NotImplementedError()
199169

200170

201171
class Comment(OgrAbstractClass):
@@ -234,11 +204,7 @@ def __str__(self) -> str:
234204

235205
def _from_raw_comment(self, raw_comment: Any) -> None:
236206
"""Constructs Comment object from raw_comment given from API."""
237-
self._id = raw_comment["id"]
238-
self._body = raw_comment["body"]
239-
self._author = raw_comment["user"]["login"]
240-
self._created = raw_comment["created_at"]
241-
self._edited = raw_comment.get("updated_at")
207+
raise NotImplementedError()
242208

243209
@property
244210
def body(self) -> str:
@@ -270,15 +236,7 @@ def edited(self) -> datetime.datetime:
270236

271237
def get_reactions(self) -> list[Reaction]:
272238
"""Returns list of reactions."""
273-
if not self._id or not self._parent:
274-
raise ValueError("Cannot fetch reactions without a comment ID and parent.")
275-
276-
reactions_data = self._parent.client.issue.get_comment_reactions(
277-
owner=self._parent.repo_namespace,
278-
repo=self._parent.repo_name,
279-
id=self._id,
280-
)
281-
return [Reaction(raw_reaction=react) for react in reactions_data]
239+
raise NotImplementedError()
282240

283241
def add_reaction(self, reaction: str) -> Reaction:
284242
"""
@@ -292,16 +250,7 @@ def add_reaction(self, reaction: str) -> Reaction:
292250
Returns:
293251
Object representing newly added reaction.
294252
"""
295-
if not self._id or not self._parent:
296-
raise ValueError("Cannot add reaction without a comment ID and parent.")
297-
298-
reaction_data = self._parent.client.issue.add_comment_reaction(
299-
owner=self._parent.repo_namespace,
300-
repo=self._parent.repo_name,
301-
id=self._id,
302-
content=reaction,
303-
)
304-
return Reaction(raw_reaction=reaction_data)
253+
raise NotImplementedError()
305254

306255

307256
class IssueComment(Comment):
@@ -324,6 +273,125 @@ def __str__(self) -> str:
324273
return "PR" + super().__str__()
325274

326275

276+
class ForgejoAPI:
277+
278+
def __init__(self, base_url, token):
279+
self.base_url = base_url.rstrip("/")
280+
self.headers = {
281+
"Authorization": f"token {token}",
282+
"Content-Type": "application/json",
283+
}
284+
285+
def create_comment(self, owner, repo, issue_index, body):
286+
"""Create a comment on an issue/pull request"""
287+
url = (
288+
f"{self.base_url}/api/v1/repos/{owner}/{repo}/issues/{issue_index}/comments"
289+
)
290+
payload = {"body": body}
291+
response = requests.post(url, headers=self.headers, json=payload)
292+
response.raise_for_status()
293+
return response.json()
294+
295+
def list_comments(self, owner, repo, issue_index):
296+
"""List all comments on an issue/pull request"""
297+
url = (
298+
f"{self.base_url}/api/v1/repos/{owner}/{repo}/issues/{issue_index}/comments"
299+
)
300+
response = requests.get(url, headers=self.headers)
301+
response.raise_for_status()
302+
return response.json()
303+
304+
def add_reaction(self, owner, repo, comment_id, reaction_type):
305+
"""Add a reaction to a comment"""
306+
url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"
307+
payload = {"content": reaction_type}
308+
response = requests.post(url, headers=self.headers, json=payload)
309+
response.raise_for_status()
310+
return response.json()
311+
312+
def delete_reaction(self, owner, repo, comment_id, reaction_type):
313+
"""Remove a reaction from a comment"""
314+
url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"
315+
params = {"content": reaction_type}
316+
response = requests.delete(url, headers=self.headers, params=params)
317+
response.raise_for_status()
318+
return response.json()
319+
320+
def list_reactions(self, owner, repo, comment_id):
321+
"""List reactions on a comment"""
322+
url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"
323+
response = requests.get(url, headers=self.headers)
324+
response.raise_for_status()
325+
return response.json()
326+
327+
328+
class ForgejoReaction(Reaction):
329+
def __init__(self, raw_reaction, api_client, owner, repo, comment_id):
330+
super().__init__(raw_reaction)
331+
self._api = api_client
332+
self._owner = owner
333+
self._repo = repo
334+
self._comment_id = comment_id
335+
self.reaction_type = raw_reaction.get("content")
336+
337+
def delete(self) -> None:
338+
self._api.delete_reaction(
339+
self._owner,
340+
self._repo,
341+
self._comment_id,
342+
self.reaction_type,
343+
)
344+
345+
346+
class ForgejoIssueComment(IssueComment):
347+
def _from_raw_comment(self, raw_comment: dict) -> None:
348+
self._body = raw_comment.get("body")
349+
self._id = raw_comment.get("id")
350+
self._author = raw_comment.get("user", {}).get("login")
351+
self._created = datetime.datetime.fromisoformat(raw_comment.get("created_at"))
352+
self._edited = (
353+
datetime.datetime.fromisoformat(raw_comment["updated_at"])
354+
if raw_comment.get("updated_at")
355+
else None
356+
)
357+
358+
def get_reactions(self) -> list[Reaction]:
359+
owner = self._parent.project.owner
360+
repo = self._parent.project.repo
361+
reactions = self._parent.service.forgejo_api.list_reactions(
362+
owner,
363+
repo,
364+
self.id,
365+
)
366+
return [
367+
ForgejoReaction(
368+
raw_reaction=r,
369+
api_client=self._parent.service.forgejo_api,
370+
owner=owner,
371+
repo=repo,
372+
comment_id=self.id,
373+
)
374+
for r in reactions
375+
]
376+
377+
def add_reaction(self, reaction: str) -> ForgejoReaction:
378+
owner = self._parent.project.owner
379+
repo = self._parent.project.repo
380+
reaction_data = self._parent.service.forgejo_api.add_reaction(
381+
owner,
382+
repo,
383+
self.id,
384+
reaction,
385+
)
386+
return ForgejoReaction(
387+
raw_reaction=reaction_data,
388+
api_client=self._parent.service.forgejo_api,
389+
owner=owner,
390+
repo=repo,
391+
comment_id=self.id,
392+
)
393+
394+
327395
class IssueStatus(IntEnum):
328396
"""Enumeration for issue statuses."""
329397

recipe.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
name:
2828
- twine # we need newest twine, b/c of the check command
2929
- readme_renderer[md]
30+
- pytest-cov
3031

3132
state: latest
3233
become: true

0 commit comments

Comments
 (0)