|
""" |
|
Data structures to interact with Discussions and Pull Requests on the Hub. |
|
|
|
See [the Discussions and Pull Requests guide](https://huggingface.co/docs/hub/repositories-pull-requests-discussions) |
|
for more information on Pull Requests, Discussions, and the community tab. |
|
""" |
|
|
|
from dataclasses import dataclass |
|
from datetime import datetime |
|
from typing import List, Literal, Optional, Union |
|
|
|
from . import constants |
|
from .utils import parse_datetime |
|
|
|
|
|
DiscussionStatus = Literal["open", "closed", "merged", "draft"] |
|
|
|
|
|
@dataclass |
|
class Discussion: |
|
""" |
|
A Discussion or Pull Request on the Hub. |
|
|
|
This dataclass is not intended to be instantiated directly. |
|
|
|
Attributes: |
|
title (`str`): |
|
The title of the Discussion / Pull Request |
|
status (`str`): |
|
The status of the Discussion / Pull Request. |
|
It must be one of: |
|
* `"open"` |
|
* `"closed"` |
|
* `"merged"` (only for Pull Requests ) |
|
* `"draft"` (only for Pull Requests ) |
|
num (`int`): |
|
The number of the Discussion / Pull Request. |
|
repo_id (`str`): |
|
The id (`"{namespace}/{repo_name}"`) of the repo on which |
|
the Discussion / Pull Request was open. |
|
repo_type (`str`): |
|
The type of the repo on which the Discussion / Pull Request was open. |
|
Possible values are: `"model"`, `"dataset"`, `"space"`. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
is_pull_request (`bool`): |
|
Whether or not this is a Pull Request. |
|
created_at (`datetime`): |
|
The `datetime` of creation of the Discussion / Pull Request. |
|
endpoint (`str`): |
|
Endpoint of the Hub. Default is https://huggingface.co. |
|
git_reference (`str`, *optional*): |
|
(property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise. |
|
url (`str`): |
|
(property) URL of the discussion on the Hub. |
|
""" |
|
|
|
title: str |
|
status: DiscussionStatus |
|
num: int |
|
repo_id: str |
|
repo_type: str |
|
author: str |
|
is_pull_request: bool |
|
created_at: datetime |
|
endpoint: str |
|
|
|
@property |
|
def git_reference(self) -> Optional[str]: |
|
""" |
|
If this is a Pull Request , returns the git reference to which changes can be pushed. |
|
Returns `None` otherwise. |
|
""" |
|
if self.is_pull_request: |
|
return f"refs/pr/{self.num}" |
|
return None |
|
|
|
@property |
|
def url(self) -> str: |
|
"""Returns the URL of the discussion on the Hub.""" |
|
if self.repo_type is None or self.repo_type == constants.REPO_TYPE_MODEL: |
|
return f"{self.endpoint}/{self.repo_id}/discussions/{self.num}" |
|
return f"{self.endpoint}/{self.repo_type}s/{self.repo_id}/discussions/{self.num}" |
|
|
|
|
|
@dataclass |
|
class DiscussionWithDetails(Discussion): |
|
""" |
|
Subclass of [`Discussion`]. |
|
|
|
Attributes: |
|
title (`str`): |
|
The title of the Discussion / Pull Request |
|
status (`str`): |
|
The status of the Discussion / Pull Request. |
|
It can be one of: |
|
* `"open"` |
|
* `"closed"` |
|
* `"merged"` (only for Pull Requests ) |
|
* `"draft"` (only for Pull Requests ) |
|
num (`int`): |
|
The number of the Discussion / Pull Request. |
|
repo_id (`str`): |
|
The id (`"{namespace}/{repo_name}"`) of the repo on which |
|
the Discussion / Pull Request was open. |
|
repo_type (`str`): |
|
The type of the repo on which the Discussion / Pull Request was open. |
|
Possible values are: `"model"`, `"dataset"`, `"space"`. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
is_pull_request (`bool`): |
|
Whether or not this is a Pull Request. |
|
created_at (`datetime`): |
|
The `datetime` of creation of the Discussion / Pull Request. |
|
events (`list` of [`DiscussionEvent`]) |
|
The list of [`DiscussionEvents`] in this Discussion or Pull Request. |
|
conflicting_files (`Union[List[str], bool, None]`, *optional*): |
|
A list of conflicting files if this is a Pull Request. |
|
`None` if `self.is_pull_request` is `False`. |
|
`True` if there are conflicting files but the list can't be retrieved. |
|
target_branch (`str`, *optional*): |
|
The branch into which changes are to be merged if this is a |
|
Pull Request . `None` if `self.is_pull_request` is `False`. |
|
merge_commit_oid (`str`, *optional*): |
|
If this is a merged Pull Request , this is set to the OID / SHA of |
|
the merge commit, `None` otherwise. |
|
diff (`str`, *optional*): |
|
The git diff if this is a Pull Request , `None` otherwise. |
|
endpoint (`str`): |
|
Endpoint of the Hub. Default is https://huggingface.co. |
|
git_reference (`str`, *optional*): |
|
(property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise. |
|
url (`str`): |
|
(property) URL of the discussion on the Hub. |
|
""" |
|
|
|
events: List["DiscussionEvent"] |
|
conflicting_files: Union[List[str], bool, None] |
|
target_branch: Optional[str] |
|
merge_commit_oid: Optional[str] |
|
diff: Optional[str] |
|
|
|
|
|
@dataclass |
|
class DiscussionEvent: |
|
""" |
|
An event in a Discussion or Pull Request. |
|
|
|
Use concrete classes: |
|
* [`DiscussionComment`] |
|
* [`DiscussionStatusChange`] |
|
* [`DiscussionCommit`] |
|
* [`DiscussionTitleChange`] |
|
|
|
Attributes: |
|
id (`str`): |
|
The ID of the event. An hexadecimal string. |
|
type (`str`): |
|
The type of the event. |
|
created_at (`datetime`): |
|
A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime) |
|
object holding the creation timestamp for the event. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
""" |
|
|
|
id: str |
|
type: str |
|
created_at: datetime |
|
author: str |
|
|
|
_event: dict |
|
"""Stores the original event data, in case we need to access it later.""" |
|
|
|
|
|
@dataclass |
|
class DiscussionComment(DiscussionEvent): |
|
"""A comment in a Discussion / Pull Request. |
|
|
|
Subclass of [`DiscussionEvent`]. |
|
|
|
|
|
Attributes: |
|
id (`str`): |
|
The ID of the event. An hexadecimal string. |
|
type (`str`): |
|
The type of the event. |
|
created_at (`datetime`): |
|
A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime) |
|
object holding the creation timestamp for the event. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
content (`str`): |
|
The raw markdown content of the comment. Mentions, links and images are not rendered. |
|
edited (`bool`): |
|
Whether or not this comment has been edited. |
|
hidden (`bool`): |
|
Whether or not this comment has been hidden. |
|
""" |
|
|
|
content: str |
|
edited: bool |
|
hidden: bool |
|
|
|
@property |
|
def rendered(self) -> str: |
|
"""The rendered comment, as a HTML string""" |
|
return self._event["data"]["latest"]["html"] |
|
|
|
@property |
|
def last_edited_at(self) -> datetime: |
|
"""The last edit time, as a `datetime` object.""" |
|
return parse_datetime(self._event["data"]["latest"]["updatedAt"]) |
|
|
|
@property |
|
def last_edited_by(self) -> str: |
|
"""The last edit time, as a `datetime` object.""" |
|
return self._event["data"]["latest"].get("author", {}).get("name", "deleted") |
|
|
|
@property |
|
def edit_history(self) -> List[dict]: |
|
"""The edit history of the comment""" |
|
return self._event["data"]["history"] |
|
|
|
@property |
|
def number_of_edits(self) -> int: |
|
return len(self.edit_history) |
|
|
|
|
|
@dataclass |
|
class DiscussionStatusChange(DiscussionEvent): |
|
"""A change of status in a Discussion / Pull Request. |
|
|
|
Subclass of [`DiscussionEvent`]. |
|
|
|
Attributes: |
|
id (`str`): |
|
The ID of the event. An hexadecimal string. |
|
type (`str`): |
|
The type of the event. |
|
created_at (`datetime`): |
|
A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime) |
|
object holding the creation timestamp for the event. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
new_status (`str`): |
|
The status of the Discussion / Pull Request after the change. |
|
It can be one of: |
|
* `"open"` |
|
* `"closed"` |
|
* `"merged"` (only for Pull Requests ) |
|
""" |
|
|
|
new_status: str |
|
|
|
|
|
@dataclass |
|
class DiscussionCommit(DiscussionEvent): |
|
"""A commit in a Pull Request. |
|
|
|
Subclass of [`DiscussionEvent`]. |
|
|
|
Attributes: |
|
id (`str`): |
|
The ID of the event. An hexadecimal string. |
|
type (`str`): |
|
The type of the event. |
|
created_at (`datetime`): |
|
A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime) |
|
object holding the creation timestamp for the event. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
summary (`str`): |
|
The summary of the commit. |
|
oid (`str`): |
|
The OID / SHA of the commit, as a hexadecimal string. |
|
""" |
|
|
|
summary: str |
|
oid: str |
|
|
|
|
|
@dataclass |
|
class DiscussionTitleChange(DiscussionEvent): |
|
"""A rename event in a Discussion / Pull Request. |
|
|
|
Subclass of [`DiscussionEvent`]. |
|
|
|
Attributes: |
|
id (`str`): |
|
The ID of the event. An hexadecimal string. |
|
type (`str`): |
|
The type of the event. |
|
created_at (`datetime`): |
|
A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime) |
|
object holding the creation timestamp for the event. |
|
author (`str`): |
|
The username of the Discussion / Pull Request author. |
|
Can be `"deleted"` if the user has been deleted since. |
|
old_title (`str`): |
|
The previous title for the Discussion / Pull Request. |
|
new_title (`str`): |
|
The new title. |
|
""" |
|
|
|
old_title: str |
|
new_title: str |
|
|
|
|
|
def deserialize_event(event: dict) -> DiscussionEvent: |
|
"""Instantiates a [`DiscussionEvent`] from a dict""" |
|
event_id: str = event["id"] |
|
event_type: str = event["type"] |
|
created_at = parse_datetime(event["createdAt"]) |
|
|
|
common_args = dict( |
|
id=event_id, |
|
type=event_type, |
|
created_at=created_at, |
|
author=event.get("author", {}).get("name", "deleted"), |
|
_event=event, |
|
) |
|
|
|
if event_type == "comment": |
|
return DiscussionComment( |
|
**common_args, |
|
edited=event["data"]["edited"], |
|
hidden=event["data"]["hidden"], |
|
content=event["data"]["latest"]["raw"], |
|
) |
|
if event_type == "status-change": |
|
return DiscussionStatusChange( |
|
**common_args, |
|
new_status=event["data"]["status"], |
|
) |
|
if event_type == "commit": |
|
return DiscussionCommit( |
|
**common_args, |
|
summary=event["data"]["subject"], |
|
oid=event["data"]["oid"], |
|
) |
|
if event_type == "title-change": |
|
return DiscussionTitleChange( |
|
**common_args, |
|
old_title=event["data"]["from"], |
|
new_title=event["data"]["to"], |
|
) |
|
|
|
return DiscussionEvent(**common_args) |
|
|