diff --git a/doc/changes.rst b/doc/changes.rst index 406456db11..df38288eb7 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -4,6 +4,26 @@ Change log Stable versions ~~~~~~~~~~~~~~~ +Version 2.7.0 (XXXXXXX) +----------------------- + +Breaking Changes +^^^^^^^^^^^^^^^^ + +* Method ``Github.get_rate_limit()`` now returns ``RateLimitOverview`` rather than ``RateLimit``. + + Code like + + .. code-block:: python + + gh.get_rate_limit().core.remaining + + should be replaced with + + .. code-block:: python + + gh.get_rate_limit().resources.core.remaining + Version 2.6.0 (February 15, 2025) --------------------------------- @@ -15,19 +35,39 @@ Breaking Changes View and clones traffic information returned by ``Repository.get_views_traffic`` and ``Repository.get_clones_traffic`` now return proper PyGithub objects, instead of a ``dict``, with all information that used to be provided by the ``dict``: -Code like + Code like -.. code-block:: python + .. code-block:: python - repo.get_views_traffic().["views"].timestamp - repo.get_clones_traffic().["clones"].timestamp + repo.get_views_traffic().["views"].timestamp + repo.get_clones_traffic().["clones"].timestamp -should be replaced with + should be replaced with -.. code-block:: python + .. code-block:: python + + repo.get_views_traffic().views.timestamp + repo.get_clones_traffic().clones.timestamp + +* Add ``GitCommitVerification`` class (`#3028 `_) (`822e6d71 `_): + + Changes the return value of ``GitTag.verification`` and ``GitCommit.verification`` from ``dict`` to ``GitCommitVerification``. + + Code like + + .. code-block:: python + + tag.verification["reason"] + commit.verification["reason"] + + should be replaced with + + .. code-block:: python + + tag.verification.reason + commit.verification.reason - repo.get_views_traffic().views.timestamp - repo.get_clones_traffic().clones.timestamp +* Property ``AppAuth.private_key`` has been removed (`#3065 `_) (`36697b22 `_) * Fix typos (`#3086 `_) (`a50ae51b `_): diff --git a/github/Auth.py b/github/Auth.py index c80f7aacfc..7a71ec8ba1 100644 --- a/github/Auth.py +++ b/github/Auth.py @@ -186,6 +186,19 @@ def token_type(self) -> str: return "Bearer" +class JwtSigner: + def __init__(self, private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str): + self._private_key_or_func = private_key_or_func + self._jwt_algorithm = jwt_algorithm + + def jwt_sign(self, payload: dict) -> Union[str, bytes]: + if callable(self._private_key_or_func): + private_key = self._private_key_or_func() + else: + private_key = self._private_key_or_func + return jwt.encode(payload, key=private_key, algorithm=self._jwt_algorithm) + + class AppAuth(JWT): """ This class is used to authenticate as a GitHub App. @@ -196,14 +209,7 @@ class AppAuth(JWT): @staticmethod def create_jwt_sign(private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str) -> DictSignFunction: - def jwt_sign(payload: dict) -> Union[str, bytes]: - if callable(private_key_or_func): - private_key = private_key_or_func() - else: - private_key = private_key_or_func - return jwt.encode(payload, key=private_key, algorithm=jwt_algorithm) - - return jwt_sign + return JwtSigner(private_key_or_func, jwt_algorithm).jwt_sign # v3: move * above private_key def __init__( diff --git a/github/AuthenticatedUser.py b/github/AuthenticatedUser.py index e1334b61f4..84280a727f 100644 --- a/github/AuthenticatedUser.py +++ b/github/AuthenticatedUser.py @@ -126,6 +126,17 @@ class EmailData(NamedTuple): + """ + This class represents EmailData. + + The reference can be found here + http://docs.github.com/en/rest/reference/users#emails + + The OpenAPI schema can be found at + - /components/schemas/email + + """ + email: str primary: bool verified: bool diff --git a/github/CheckRun.py b/github/CheckRun.py index 180710b21b..3a3588aa66 100644 --- a/github/CheckRun.py +++ b/github/CheckRun.py @@ -34,7 +34,7 @@ from datetime import datetime from typing import TYPE_CHECKING, Any -from typing_extensions import deprecated +import deprecated import github.CheckRunAnnotation import github.CheckRunOutput @@ -109,7 +109,7 @@ def check_suite(self) -> CheckSuite: return self._check_suite.value @property - @deprecated("Use property check_suite.id instead") + @deprecated.deprecated("Use property check_suite.id instead") def check_suite_id(self) -> int: self._completeIfNotSet(self._check_suite_id) return self._check_suite_id.value diff --git a/github/CodeSecurityConfig.py b/github/CodeSecurityConfig.py index cefca6d264..fe28310c7e 100644 --- a/github/CodeSecurityConfig.py +++ b/github/CodeSecurityConfig.py @@ -36,11 +36,12 @@ class CodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-configuration + """ def _initAttributes(self) -> None: - self._id: Attribute[int] = NotSet - self._name: Attribute[str] = NotSet self._advanced_security: Attribute[str] = NotSet self._code_scanning_default_setup: Attribute[str] = NotSet self._created_at: Attribute[datetime] = NotSet @@ -48,18 +49,22 @@ def _initAttributes(self) -> None: self._dependabot_security_updates: Attribute[str] = NotSet self._dependency_graph: Attribute[str] = NotSet self._dependency_graph_autosubmit_action: Attribute[str] = NotSet + self._dependency_graph_autosubmit_action_options: Attribute[dict[str, Any]] = NotSet self._description: Attribute[str] = NotSet self._enforcement: Attribute[str] = NotSet self._html_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._name: Attribute[str] = NotSet self._private_vulnerability_reporting: Attribute[str] = NotSet self._secret_scanning: Attribute[str] = NotSet self._secret_scanning_delegated_bypass: Attribute[str] = NotSet + self._secret_scanning_delegated_bypass_options: Attribute[dict[str, Any]] = NotSet self._secret_scanning_non_provider_patterns: Attribute[str] = NotSet self._secret_scanning_push_protection: Attribute[str] = NotSet self._secret_scanning_validity_checks: Attribute[str] = NotSet self._target_type: Attribute[str] = NotSet - self._url: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet def __repr__(self) -> str: return self.get__repr__( @@ -98,6 +103,10 @@ def dependency_graph(self) -> str: def dependency_graph_autosubmit_action(self) -> str: return self._dependency_graph_autosubmit_action.value + @property + def dependency_graph_autosubmit_action_options(self) -> dict[str, Any]: + return self._dependency_graph_autosubmit_action_options.value + @property def description(self) -> str: return self._description.value @@ -130,6 +139,10 @@ def secret_scanning(self) -> str: def secret_scanning_delegated_bypass(self) -> str: return self._secret_scanning_delegated_bypass.value + @property + def secret_scanning_delegated_bypass_options(self) -> dict[str, Any]: + return self._secret_scanning_delegated_bypass_options.value + @property def secret_scanning_non_provider_patterns(self) -> str: return self._secret_scanning_non_provider_patterns.value @@ -174,6 +187,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._dependency_graph_autosubmit_action = self._makeStringAttribute( attributes["dependency_graph_autosubmit_action"] ) + if "dependency_graph_autosubmit_action_options" in attributes: # pragma no branch + self._dependency_graph_autosubmit_action_options = self._makeDictAttribute( + attributes["dependency_graph_autosubmit_action_options"] + ) if "description" in attributes: # pragma no branch self._description = self._makeStringAttribute(attributes["description"]) if "enforcement" in attributes: # pragma no branch @@ -194,6 +211,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._secret_scanning_delegated_bypass = self._makeStringAttribute( attributes["secret_scanning_delegated_bypass"] ) + if "secret_scanning_delegated_bypass_options" in attributes: # pragma no branch + self._secret_scanning_delegated_bypass_options = self._makeDictAttribute( + attributes["secret_scanning_delegated_bypass_options"] + ) if "secret_scanning_non_provider_patterns" in attributes: # pragma no branch self._secret_scanning_non_provider_patterns = self._makeStringAttribute( attributes["secret_scanning_non_provider_patterns"] diff --git a/github/Commit.py b/github/Commit.py index ee49f51262..63aa035e1c 100644 --- a/github/Commit.py +++ b/github/Commit.py @@ -89,7 +89,6 @@ class Commit(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/branch-short/properties/commit - /components/schemas/commit - - /components/schemas/commit-search-result-item - /components/schemas/commit-search-result-item/properties/parents/items - /components/schemas/commit/properties/parents/items - /components/schemas/short-branch/properties/commit @@ -107,7 +106,6 @@ def _initAttributes(self) -> None: self._node_id: Attribute[str] = NotSet self._parents: Attribute[list[Commit]] = NotSet self._repository: Attribute[Repository] = NotSet - self._score: Attribute[float] = NotSet self._sha: Attribute[str] = NotSet self._stats: Attribute[CommitStats] = NotSet self._text_matches: Attribute[dict[str, Any]] = NotSet @@ -176,11 +174,6 @@ def repository(self) -> Repository: self._completeIfNotSet(self._repository) return self._repository.value - @property - def score(self) -> float: - self._completeIfNotSet(self._score) - return self._score.value - @property def sha(self) -> str: self._completeIfNotSet(self._sha) @@ -358,8 +351,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._parents = self._makeListOfClassesAttribute(Commit, attributes["parents"]) if "repository" in attributes: # pragma no branch self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) - if "score" in attributes: # pragma no branch - self._score = self._makeFloatAttribute(attributes["score"]) if "sha" in attributes: # pragma no branch self._sha = self._makeStringAttribute(attributes["sha"]) if "stats" in attributes: # pragma no branch @@ -368,3 +359,33 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._text_matches = self._makeDictAttribute(attributes["text_matches"]) if "url" in attributes: # pragma no branch self._url = self._makeStringAttribute(attributes["url"]) + + +class CommitSearchResult(Commit): + """ + This class represents CommitSearchResult. + + The reference can be found here + https://docs.github.com/en/rest/reference/search#search-commits + + The OpenAPI schema can be found at + - /components/schemas/commit-search-result-item + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._score: Attribute[float] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"sha": self._sha.value, "score": self._score.value}) + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + super()._useAttributes(attributes) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) diff --git a/github/ContentFile.py b/github/ContentFile.py index d3fd49c722..d5c0f98f28 100644 --- a/github/ContentFile.py +++ b/github/ContentFile.py @@ -68,7 +68,6 @@ class ContentFile(CompletableGithubObject): https://docs.github.com/en/rest/reference/repos#contents The OpenAPI schema can be found at - - /components/schemas/code-search-result-item - /components/schemas/content-directory - /components/schemas/content-file - /components/schemas/content-submodule @@ -94,7 +93,6 @@ def _initAttributes(self) -> None: self._name: Attribute[str] = NotSet self._path: Attribute[str] = NotSet self._repository: Attribute[Repository] = NotSet - self._score: Attribute[float] = NotSet self._sha: Attribute[str] = NotSet self._size: Attribute[int] = NotSet self._submodule_git_url: Attribute[str] = NotSet @@ -191,11 +189,6 @@ def repository(self) -> Repository: ) # pragma no cover (Should be covered) return self._repository.value - @property - def score(self) -> float: - self._completeIfNotSet(self._score) - return self._score.value - @property def sha(self) -> str: self._completeIfNotSet(self._sha) @@ -262,8 +255,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._path = self._makeStringAttribute(attributes["path"]) if "repository" in attributes: # pragma no branch self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) - if "score" in attributes: # pragma no branch - self._score = self._makeFloatAttribute(attributes["score"]) if "sha" in attributes: # pragma no branch self._sha = self._makeStringAttribute(attributes["sha"]) if "size" in attributes: # pragma no branch @@ -278,3 +269,33 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._type = self._makeStringAttribute(attributes["type"]) if "url" in attributes: # pragma no branch self._url = self._makeStringAttribute(attributes["url"]) + + +class ContentFileSearchResult(ContentFile): + """ + This class represents ContentFileSearchResult. + + The reference can be found here + https://docs.github.com/en/rest/reference/search#search-code + + The OpenAPI schema can be found at + - /components/schemas/code-search-result-item + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._score: Attribute[float] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"path": self._path.value}) + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + super()._useAttributes(attributes) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) diff --git a/github/CopilotSeat.py b/github/CopilotSeat.py index ef2158c669..6cb93c2215 100644 --- a/github/CopilotSeat.py +++ b/github/CopilotSeat.py @@ -32,48 +32,41 @@ class CopilotSeat(NonCompletableGithubObject): + """ + This class represents CopilotSeat. + + The reference can be found here + https://docs.github.com/en/rest/copilot/copilot-user-management + + The OpenAPI schema can be found at + - /components/schemas/copilot-seat-details + + """ + def _initAttributes(self) -> None: + self._assignee: Attribute[github.NamedUser.NamedUser] | _NotSetType = NotSet + self._assigning_team: Attribute[github.Team.Team] | _NotSetType = NotSet self._created_at: Attribute[datetime] | _NotSetType = NotSet - self._updated_at: Attribute[datetime] | _NotSetType = NotSet - self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet self._last_activity_at: Attribute[datetime] | _NotSetType = NotSet self._last_activity_editor: Attribute[str] | _NotSetType = NotSet + self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet self._plan_type: Attribute[str] | _NotSetType = NotSet - self._assignee: Attribute[github.NamedUser.NamedUser] | _NotSetType = NotSet - self._assigning_team: Attribute[github.Team.Team] | _NotSetType = NotSet - - def _useAttributes(self, attributes: dict[str, Any]) -> None: - if "created_at" in attributes: - self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) - if "updated_at" in attributes: - self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) - if "pending_cancellation_date" in attributes: - self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) - if "last_activity_at" in attributes: - self._last_activity_at = self._makeDatetimeAttribute(attributes["last_activity_at"]) - if "last_activity_editor" in attributes: - self._last_activity_editor = self._makeStringAttribute(attributes["last_activity_editor"]) - if "plan_type" in attributes: - self._plan_type = self._makeStringAttribute(attributes["plan_type"]) - if "assignee" in attributes: - self._assignee = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["assignee"]) - if "assigning_team" in attributes: - self._assigning_team = self._makeClassAttribute(github.Team.Team, attributes["assigning_team"]) + self._updated_at: Attribute[datetime] | _NotSetType = NotSet def __repr__(self) -> str: return self.get__repr__({"assignee": self._assignee.value}) @property - def created_at(self) -> datetime: - return self._created_at.value + def assignee(self) -> github.NamedUser.NamedUser: + return self._assignee.value @property - def updated_at(self) -> datetime: - return self._updated_at.value + def assigning_team(self) -> github.Team.Team: + return self._assigning_team.value @property - def pending_cancellation_date(self) -> datetime: - return self._pending_cancellation_date.value + def created_at(self) -> datetime: + return self._created_at.value @property def last_activity_at(self) -> datetime: @@ -83,14 +76,32 @@ def last_activity_at(self) -> datetime: def last_activity_editor(self) -> str: return self._last_activity_editor.value + @property + def pending_cancellation_date(self) -> datetime: + return self._pending_cancellation_date.value + @property def plan_type(self) -> str: return self._plan_type.value @property - def assignee(self) -> github.NamedUser.NamedUser: - return self._assignee.value + def updated_at(self) -> datetime: + return self._updated_at.value - @property - def assigning_team(self) -> github.Team.Team: - return self._assigning_team.value + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "assignee" in attributes: + self._assignee = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["assignee"]) + if "assigning_team" in attributes: + self._assigning_team = self._makeClassAttribute(github.Team.Team, attributes["assigning_team"]) + if "created_at" in attributes: + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "last_activity_at" in attributes: + self._last_activity_at = self._makeDatetimeAttribute(attributes["last_activity_at"]) + if "last_activity_editor" in attributes: + self._last_activity_editor = self._makeStringAttribute(attributes["last_activity_editor"]) + if "pending_cancellation_date" in attributes: + self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) + if "plan_type" in attributes: + self._plan_type = self._makeStringAttribute(attributes["plan_type"]) + if "updated_at" in attributes: + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) diff --git a/github/DefaultCodeSecurityConfig.py b/github/DefaultCodeSecurityConfig.py index dabdb71335..e01e62b2fa 100644 --- a/github/DefaultCodeSecurityConfig.py +++ b/github/DefaultCodeSecurityConfig.py @@ -54,6 +54,9 @@ class DefaultCodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-default-configurations + """ def _initAttributes(self) -> None: diff --git a/github/Deployment.py b/github/Deployment.py index 0b1d12b5e4..c7b02fe4dd 100644 --- a/github/Deployment.py +++ b/github/Deployment.py @@ -70,7 +70,6 @@ class Deployment(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/deployment - /components/schemas/deployment-simple - - /paths/"/repos/{owner}/{repo}/deployments"/post/responses/202/content/"application/json"/schema """ @@ -80,7 +79,6 @@ def _initAttributes(self) -> None: self._description: Attribute[str] = NotSet self._environment: Attribute[str] = NotSet self._id: Attribute[int] = NotSet - self._message: Attribute[str] = NotSet self._node_id: Attribute[str] = NotSet self._original_environment: Attribute[str] = NotSet self._payload: Attribute[dict[str, Any]] = NotSet @@ -123,11 +121,6 @@ def id(self) -> int: self._completeIfNotSet(self._id) return self._id.value - @property - def message(self) -> str: - self._completeIfNotSet(self._message) - return self._message.value - @property def node_id(self) -> str: self._completeIfNotSet(self._node_id) @@ -275,8 +268,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._environment = self._makeStringAttribute(attributes["environment"]) if "id" in attributes: # pragma no branch self._id = self._makeIntAttribute(attributes["id"]) - if "message" in attributes: # pragma no branch - self._message = self._makeStringAttribute(attributes["message"]) if "node_id" in attributes: # pragma no branch self._node_id = self._makeStringAttribute(attributes["node_id"]) if "original_environment" in attributes: # pragma no branch diff --git a/github/GitCommitVerification.py b/github/GitCommitVerification.py index 24015bd813..8da952e2de 100644 --- a/github/GitCommitVerification.py +++ b/github/GitCommitVerification.py @@ -55,24 +55,22 @@ class GitCommitVerification(NonCompletableGithubObject): https://docs.github.com/en/rest/commits/commits The OpenAPI schema can be found at + - /components/schemas/file-commit/properties/commit/properties/verification - /components/schemas/git-commit/properties/verification + - /components/schemas/verification """ def _initAttributes(self) -> None: - self._verified: Attribute[bool] = NotSet - self._verified_at: Attribute[datetime] = NotSet + self._payload: Attribute[str] = NotSet self._reason: Attribute[str] = NotSet self._signature: Attribute[str] = NotSet - self._payload: Attribute[str] = NotSet - - @property - def verified(self) -> bool: - return self._verified.value + self._verified: Attribute[bool] = NotSet + self._verified_at: Attribute[datetime] = NotSet @property - def verified_at(self) -> datetime: - return self._verified_at.value + def payload(self) -> str: + return self._payload.value @property def reason(self) -> str: @@ -83,17 +81,21 @@ def signature(self) -> str: return self._signature.value @property - def payload(self) -> str: - return self._payload.value + def verified(self) -> bool: + return self._verified.value + + @property + def verified_at(self) -> datetime: + return self._verified_at.value def _useAttributes(self, attributes: dict[str, Any]) -> None: - if "verified" in attributes: # pragma no branch - self._verified = self._makeBoolAttribute(attributes["verified"]) - if "verified_at" in attributes: # pragma no branch - self._verified_at = self._makeDatetimeAttribute(attributes["verified_at"]) + if "payload" in attributes: # pragma no branch + self._payload = self._makeStringAttribute(attributes["payload"]) if "reason" in attributes: # pragma no branch self._reason = self._makeStringAttribute(attributes["reason"]) if "signature" in attributes: # pragma no branch self._signature = self._makeStringAttribute(attributes["signature"]) - if "payload" in attributes: # pragma no branch - self._payload = self._makeStringAttribute(attributes["payload"]) + if "verified" in attributes: # pragma no branch + self._verified = self._makeBoolAttribute(attributes["verified"]) + if "verified_at" in attributes: # pragma no branch + self._verified_at = self._makeDatetimeAttribute(attributes["verified_at"]) diff --git a/github/GitRelease.py b/github/GitRelease.py index 11a930adc8..58d41c0469 100644 --- a/github/GitRelease.py +++ b/github/GitRelease.py @@ -78,7 +78,6 @@ class GitRelease(CompletableGithubObject): https://docs.github.com/en/rest/reference/repos#releases The OpenAPI schema can be found at - - /components/schemas/basic-error - /components/schemas/release """ diff --git a/github/GitTag.py b/github/GitTag.py index 1f50b40f66..f70b655b77 100644 --- a/github/GitTag.py +++ b/github/GitTag.py @@ -43,6 +43,7 @@ from typing import TYPE_CHECKING, Any import github.GitAuthor +import github.GitCommitVerification import github.GithubObject import github.GitObject import github.GitTreeElement @@ -50,6 +51,7 @@ if TYPE_CHECKING: from github.GitAuthor import GitAuthor + from github.GitCommitVerification import GitCommitVerification from github.GitObject import GitObject @@ -73,7 +75,7 @@ def _initAttributes(self) -> None: self._tag: Attribute[str] = NotSet self._tagger: Attribute[GitAuthor] = NotSet self._url: Attribute[str] = NotSet - self._verification: Attribute[dict[str, Any]] = NotSet + self._verification: Attribute[GitCommitVerification] = NotSet def __repr__(self) -> str: return self.get__repr__({"sha": self._sha.value, "tag": self._tag.value}) @@ -114,7 +116,7 @@ def url(self) -> str: return self._url.value @property - def verification(self) -> dict[str, Any]: + def verification(self) -> GitCommitVerification: self._completeIfNotSet(self._verification) return self._verification.value @@ -134,4 +136,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: if "url" in attributes: # pragma no branch self._url = self._makeStringAttribute(attributes["url"]) if "verification" in attributes: # pragma no branch - self._verification = self._makeDictAttribute(attributes["verification"]) + self._verification = self._makeClassAttribute( + github.GitCommitVerification.GitCommitVerification, attributes["verification"] + ) diff --git a/github/HookDelivery.py b/github/HookDelivery.py index 2e94d0966e..e43c70b9b6 100644 --- a/github/HookDelivery.py +++ b/github/HookDelivery.py @@ -146,15 +146,15 @@ class HookDeliveryRequest(github.GithubObject.NonCompletableGithubObject): """ def _initAttributes(self) -> None: + self.__headers: Attribute[dict] = NotSet self._payload: Attribute[dict] = NotSet - self._request_headers: Attribute[dict] = NotSet def __repr__(self) -> str: return self.get__repr__({"payload": self._payload.value}) @property def headers(self) -> dict | None: - return self._request_headers.value + return self.__headers.value @property def payload(self) -> dict | None: @@ -162,7 +162,7 @@ def payload(self) -> dict | None: def _useAttributes(self, attributes: dict[str, Any]) -> None: if "headers" in attributes: # pragma no branch - self._request_headers = self._makeDictAttribute(attributes["headers"]) + self.__headers = self._makeDictAttribute(attributes["headers"]) if "payload" in attributes: # pragma no branch self._payload = self._makeDictAttribute(attributes["payload"]) @@ -177,15 +177,15 @@ class HookDeliveryResponse(github.GithubObject.NonCompletableGithubObject): """ def _initAttributes(self) -> None: + self.__headers: Attribute[dict] = NotSet self._payload: Attribute[str] = NotSet - self._response_headers: Attribute[dict] = NotSet def __repr__(self) -> str: return self.get__repr__({"payload": self._payload.value}) @property def headers(self) -> dict | None: - return self._response_headers.value + return self.__headers.value @property def payload(self) -> str | None: @@ -193,7 +193,7 @@ def payload(self) -> str | None: def _useAttributes(self, attributes: dict[str, Any]) -> None: if "headers" in attributes: # pragma no branch - self._response_headers = self._makeDictAttribute(attributes["headers"]) + self.__headers = self._makeDictAttribute(attributes["headers"]) if "payload" in attributes: # pragma no branch self._payload = self._makeStringAttribute(attributes["payload"]) diff --git a/github/Issue.py b/github/Issue.py index e6a8f21845..2eb3fa10f7 100644 --- a/github/Issue.py +++ b/github/Issue.py @@ -112,7 +112,6 @@ class Issue(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/issue - - /components/schemas/issue-search-result-item - /components/schemas/nullable-issue """ @@ -145,7 +144,6 @@ def _initAttributes(self) -> None: self._reactions: Attribute[dict] = NotSet self._repository: Attribute[Repository] = NotSet self._repository_url: Attribute[str] = NotSet - self._score: Attribute[float] = NotSet self._state: Attribute[str] = NotSet self._state_reason: Attribute[str | None] = NotSet self._text_matches: Attribute[dict[str, Any]] = NotSet @@ -303,11 +301,6 @@ def repository_url(self) -> str: self._completeIfNotSet(self._repository_url) return self._repository_url.value - @property - def score(self) -> float: - self._completeIfNotSet(self._score) - return self._score.value - @property def state(self) -> str: self._completeIfNotSet(self._state) @@ -653,8 +646,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) if "repository_url" in attributes: # pragma no branch self._repository_url = self._makeStringAttribute(attributes["repository_url"]) - if "score" in attributes: # pragma no branch - self._score = self._makeFloatAttribute(attributes["score"]) if "state" in attributes: # pragma no branch self._state = self._makeStringAttribute(attributes["state"]) if "state_reason" in attributes: # pragma no branch @@ -671,3 +662,35 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._url = self._makeStringAttribute(attributes["url"]) if "user" in attributes: # pragma no branch self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["user"]) + + +class IssueSearchResult(Issue): + """ + This class represents IssueSearchResult. + + The reference can be found here + https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests + + The OpenAPI schema can be found at + - /components/schemas/issue-search-result-item + + """ + + def _initAttributes(self) -> None: + # TODO: remove if parent does not implement this + super()._initAttributes() + self._score: Attribute[float] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"number": self._number.value, "title": self._title.value, "score": self._score.value}) + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + # TODO: remove if parent does not implement this + super()._useAttributes(attributes) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) diff --git a/github/MainClass.py b/github/MainClass.py index 1983857366..52c6317354 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -107,6 +107,7 @@ import github.ApplicationOAuth import github.Auth import github.AuthenticatedUser +import github.Commit import github.Enterprise import github.Event import github.Gist @@ -115,6 +116,7 @@ import github.GithubRetry import github.GitignoreTemplate import github.GlobalAdvisory +import github.Issue import github.License import github.NamedUser import github.Topic @@ -125,27 +127,27 @@ from github.HookDelivery import HookDelivery, HookDeliverySummary from github.HookDescription import HookDescription from github.PaginatedList import PaginatedList -from github.RateLimit import RateLimit +from github.RateLimitOverview import RateLimitOverview from github.Requester import Requester if TYPE_CHECKING: from github.AppAuthentication import AppAuthentication from github.ApplicationOAuth import ApplicationOAuth from github.AuthenticatedUser import AuthenticatedUser - from github.Commit import Commit - from github.ContentFile import ContentFile + from github.Commit import CommitSearchResult + from github.ContentFile import ContentFileSearchResult from github.Event import Event from github.Gist import Gist from github.GithubApp import GithubApp from github.GitignoreTemplate import GitignoreTemplate from github.GlobalAdvisory import GlobalAdvisory - from github.Issue import Issue + from github.Issue import IssueSearchResult from github.License import License - from github.NamedUser import NamedUser + from github.NamedUser import NamedUser, NamedUserSearchResult from github.Organization import Organization from github.Project import Project from github.ProjectColumn import ProjectColumn - from github.Repository import Repository + from github.Repository import Repository, RepositorySearchResult from github.RepositoryDiscussion import RepositoryDiscussion from github.Topic import Topic @@ -345,15 +347,15 @@ def rate_limiting_resettime(self) -> int: self.get_rate_limit() return self.__requester.rate_limiting_resettime - def get_rate_limit(self) -> RateLimit: + def get_rate_limit(self) -> RateLimitOverview: """ - Rate limit status for different resources (core/search/graphql). + Rate limit overview that provides general status and status for different resources (core/search/graphql). :calls:`GET /rate_limit `_ """ headers, data = self.__requester.requestJsonAndCheck("GET", "/rate_limit") - return RateLimit(self.__requester, headers, data["resources"]) + return RateLimitOverview(self.__requester, headers, data) @property def oauth_scopes(self) -> list[str] | None: @@ -520,7 +522,7 @@ def get_project_column(self, id: int) -> ProjectColumn: "/projects/columns/%d" % id, headers={"Accept": Consts.mediaTypeProjectsPreview}, ) - return github.ProjectColumn.ProjectColumn(self.__requester, headers, data, completed=True) + return github.ProjectColumn.ProjectColumn(self.__requester, headers, data) def get_gist(self, id: str) -> Gist: """ @@ -660,7 +662,7 @@ def search_repositories( sort: Opt[str] = NotSet, order: Opt[str] = NotSet, **qualifiers: Any, - ) -> PaginatedList[Repository]: + ) -> PaginatedList[RepositorySearchResult]: """ :calls: `GET /search/repositories `_ :param query: string @@ -688,7 +690,7 @@ def search_repositories( assert url_parameters["q"], "need at least one qualifier" return PaginatedList( - github.Repository.Repository, + github.Repository.RepositorySearchResult, self.__requester, "/search/repositories", url_parameters, @@ -700,14 +702,14 @@ def search_users( sort: Opt[str] = NotSet, order: Opt[str] = NotSet, **qualifiers: Any, - ) -> PaginatedList[NamedUser]: + ) -> PaginatedList[NamedUserSearchResult]: """ :calls: `GET /search/users `_ :param query: string :param sort: string ('followers', 'repositories', 'joined') :param order: string ('asc', 'desc') :param qualifiers: keyword dict query qualifiers - :rtype: :class:`PaginatedList` of :class:`github.NamedUser.NamedUser` + :rtype: :class:`PaginatedList` of :class:`github.NamedUser.NamedUserSearchResult` """ assert isinstance(query, str), query url_parameters = dict() @@ -729,7 +731,7 @@ def search_users( assert url_parameters["q"], "need at least one qualifier" return PaginatedList( - github.NamedUser.NamedUser, + github.NamedUser.NamedUserSearchResult, self.__requester, "/search/users", url_parameters, @@ -741,14 +743,14 @@ def search_issues( sort: Opt[str] = NotSet, order: Opt[str] = NotSet, **qualifiers: Any, - ) -> PaginatedList[Issue]: + ) -> PaginatedList[IssueSearchResult]: """ :calls: `GET /search/issues `_ :param query: string :param sort: string ('comments', 'created', 'updated') :param order: string ('asc', 'desc') :param qualifiers: keyword dict query qualifiers - :rtype: :class:`PaginatedList` of :class:`github.Issue.Issue` + :rtype: :class:`PaginatedList` of :class:`github.Issue.IssueSearchResult` """ assert isinstance(query, str), query url_parameters = dict() @@ -769,7 +771,7 @@ def search_issues( url_parameters["q"] = " ".join(query_chunks) assert url_parameters["q"], "need at least one qualifier" - return PaginatedList(github.Issue.Issue, self.__requester, "/search/issues", url_parameters) + return PaginatedList(github.Issue.IssueSearchResult, self.__requester, "/search/issues", url_parameters) def search_code( self, @@ -778,7 +780,7 @@ def search_code( order: Opt[str] = NotSet, highlight: bool = False, **qualifiers: Any, - ) -> PaginatedList[ContentFile]: + ) -> PaginatedList[ContentFileSearchResult]: """ :calls: `GET /search/code `_ :param query: string @@ -786,7 +788,7 @@ def search_code( :param order: string ('asc', 'desc') :param highlight: boolean (True, False) :param qualifiers: keyword dict query qualifiers - :rtype: :class:`PaginatedList` of :class:`github.ContentFile.ContentFile` + :rtype: :class:`PaginatedList` of :class:`github.ContentFile.ContentFileSearchResult` """ assert isinstance(query, str), query url_parameters = dict() @@ -810,7 +812,7 @@ def search_code( headers = {"Accept": Consts.highLightSearchPreview} if highlight else None return PaginatedList( - github.ContentFile.ContentFile, + github.ContentFile.ContentFileSearchResult, self.__requester, "/search/code", url_parameters, @@ -823,14 +825,14 @@ def search_commits( sort: Opt[str] = NotSet, order: Opt[str] = NotSet, **qualifiers: Any, - ) -> PaginatedList[Commit]: + ) -> PaginatedList[CommitSearchResult]: """ :calls: `GET /search/commits `_ :param query: string :param sort: string ('author-date', 'committer-date') :param order: string ('asc', 'desc') :param qualifiers: keyword dict query qualifiers - :rtype: :class:`PaginatedList` of :class:`github.Commit.Commit` + :rtype: :class:`PaginatedList` of :class:`github.Commit.CommitSearchResult` """ assert isinstance(query, str), query url_parameters = dict() @@ -852,7 +854,7 @@ def search_commits( assert url_parameters["q"], "need at least one qualifier" return PaginatedList( - github.Commit.Commit, + github.Commit.CommitSearchResult, self.__requester, "/search/commits", url_parameters, diff --git a/github/NamedUser.py b/github/NamedUser.py index d79229c2be..6a1dfaaeea 100644 --- a/github/NamedUser.py +++ b/github/NamedUser.py @@ -68,7 +68,7 @@ import github.Plan import github.Repository from github import Consts -from github.GithubObject import Attribute, NotSet, Opt, is_defined +from github.GithubObject import Attribute, NotSet, Opt, is_defined, is_undefined from github.PaginatedList import PaginatedList if TYPE_CHECKING: @@ -94,10 +94,10 @@ class NamedUser(github.GithubObject.CompletableGithubObject): - /components/schemas/actor - /components/schemas/collaborator - /components/schemas/contributor + - /components/schemas/empty-object - /components/schemas/nullable-simple-user - /components/schemas/public-user - /components/schemas/simple-user - - /components/schemas/user-search-result-item """ @@ -140,7 +140,6 @@ def _initAttributes(self) -> None: self._repos_url: Attribute[str] = NotSet self._role: Attribute[str] = NotSet self._role_name: Attribute[str] = NotSet - self._score: Attribute[float] = NotSet self._site_admin: Attribute[bool] = NotSet self._starred_at: Attribute[str] = NotSet self._starred_url: Attribute[str] = NotSet @@ -355,10 +354,6 @@ def role(self) -> str: def role_name(self) -> str: return self._role_name.value - @property - def score(self) -> float: - return self._score.value - @property def site_admin(self) -> bool: self._completeIfNotSet(self._site_admin) @@ -663,8 +658,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._role = self._makeStringAttribute(attributes["role"]) if "role_name" in attributes: # pragma no branch self._role_name = self._makeStringAttribute(attributes["role_name"]) - if "score" in attributes: # pragma no branch - self._score = self._makeFloatAttribute(attributes["score"]) if "site_admin" in attributes: # pragma no branch self._site_admin = self._makeBoolAttribute(attributes["site_admin"]) if "starred_at" in attributes: # pragma no branch @@ -691,3 +684,88 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._url = self._makeStringAttribute(attributes["url"]) if "user_view_type" in attributes: # pragma no branch self._user_view_type = self._makeStringAttribute(attributes["user_view_type"]) + + +class NamedUserSearchResult(NamedUser): + """ + This class represents NamedUserSearchResult. + + The reference can be found here + https://docs.github.com/en/rest/reference/search#search-users + + The OpenAPI schema can be found at + - /components/schemas/user-search-result-item + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._score: Attribute[float] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"login": self._login.value, "score": self._score.value}) + + @property + def score(self) -> float: + return self._score.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + super()._useAttributes(attributes) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) + + +# A better place would be github.OrganizationInvitation.OrganizationInvitation +# but that causes an import cycle. This is a specialization of NamedUser any way. +class OrganizationInvitation(NamedUser): + """ + This class represents OrganizationInvitation. + + The reference can be found here + https://docs.github.com/en/rest/orgs/members + + The OpenAPI schema can be found at + - /components/schemas/organization-invitation + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._failed_at: Attribute[str] = NotSet + self._failed_reason: Attribute[str] = NotSet + self._invitation_source: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"id": self._id.value}) + + @property + def failed_at(self) -> str: + return self._failed_at.value + + @property + def failed_reason(self) -> str: + return self._failed_reason.value + + @property + def invitation_source(self) -> str: + return self._invitation_source.value + + def cancel(self) -> bool: + """ + :calls: `DELETE /orgs/{org}/invitations/{invitation_id} `_ + :rtype: bool + """ + status, headers, data = self._requester.requestJson("DELETE", self.url) + return status == 204 + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + super()._useAttributes(attributes) + if "failed_at" in attributes: # pragma no branch + self._failed_at = self._makeStringAttribute(attributes["failed_at"]) + if "failed_reason" in attributes: # pragma no branch + self._failed_reason = self._makeStringAttribute(attributes["failed_reason"]) + if "invitation_source" in attributes: # pragma no branch + self._invitation_source = self._makeStringAttribute(attributes["invitation_source"]) + if "invitation_teams_url" in attributes and is_undefined(self._url): # pragma no branch + url = "/".join(attributes["invitation_teams_url"].split("/")[:-1]) + self._url = self._makeStringAttribute(url) diff --git a/github/Organization.py b/github/Organization.py index fbbca673ac..3713f05ab3 100644 --- a/github/Organization.py +++ b/github/Organization.py @@ -127,7 +127,7 @@ from github.Issue import Issue from github.Label import Label from github.Migration import Migration - from github.NamedUser import NamedUser + from github.NamedUser import NamedUser, OrganizationInvitation from github.OrganizationCustomProperty import ( CustomProperty, OrganizationCustomProperty, @@ -152,6 +152,7 @@ class Organization(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/actor + - /components/schemas/nullable-simple-user - /components/schemas/organization-full - /components/schemas/organization-simple - /components/schemas/team-organization @@ -1264,12 +1265,12 @@ def get_teams(self) -> PaginatedList[Team]: """ return PaginatedList(github.Team.Team, self._requester, f"{self.url}/teams", None) - def invitations(self) -> PaginatedList[NamedUser]: + def invitations(self) -> PaginatedList[OrganizationInvitation]: """ :calls: `GET /orgs/{org}/invitations `_ """ return PaginatedList( - github.NamedUser.NamedUser, + github.NamedUser.OrganizationInvitation, self._requester, f"{self.url}/invitations", None, @@ -1316,7 +1317,7 @@ def cancel_invitation(self, invitee: NamedUser) -> bool: """ :calls: `DELETE /orgs/{org}/invitations/{invitation_id} `_ :param invitee: :class:`github.NamedUser.NamedUser` - :rtype: None + :rtype: bool """ assert isinstance(invitee, github.NamedUser.NamedUser), invitee status, headers, data = self._requester.requestJson("DELETE", f"{self.url}/invitations/{invitee.id}") diff --git a/github/Permissions.py b/github/Permissions.py index 4abba20551..2eceddf9d8 100644 --- a/github/Permissions.py +++ b/github/Permissions.py @@ -50,6 +50,7 @@ class Permissions(NonCompletableGithubObject): - /components/schemas/collaborator/properties/permissions - /components/schemas/full-repository/properties/permissions - /components/schemas/minimal-repository/properties/permissions + - /components/schemas/nullable-repository/properties/permissions - /components/schemas/repo-search-result-item/properties/permissions - /components/schemas/repository/properties/permissions - /components/schemas/team/properties/permissions diff --git a/github/Project.py b/github/Project.py index 900e76a540..cd6dc6b10a 100644 --- a/github/Project.py +++ b/github/Project.py @@ -230,7 +230,7 @@ def create_column(self, name: str) -> github.ProjectColumn.ProjectColumn: headers, data = self._requester.requestJsonAndCheck( "POST", f"{self.url}/columns", headers=import_header, input=post_parameters ) - return github.ProjectColumn.ProjectColumn(self._requester, headers, data, completed=True) + return github.ProjectColumn.ProjectColumn(self._requester, headers, data) def _useAttributes(self, attributes: dict[str, Any]) -> None: if "body" in attributes: # pragma no branch diff --git a/github/ProjectCard.py b/github/ProjectCard.py index 34e2ce94be..a9312b48f7 100644 --- a/github/ProjectCard.py +++ b/github/ProjectCard.py @@ -51,10 +51,11 @@ import github.Issue import github.NamedUser +import github.Organization import github.ProjectColumn import github.PullRequest from github import Consts -from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, Opt # NOTE: There is currently no way to get cards "in triage" for a project. # https://platform.github.community/t/moving-github-project-cards-that-are-in-triage/3784 @@ -63,7 +64,7 @@ # which may point the way to where the API is likely headed and what might come back to v3. E.g. ProjectCard.content member. -class ProjectCard(CompletableGithubObject): +class ProjectCard(NonCompletableGithubObject): """ This class represents Project Cards. @@ -72,14 +73,12 @@ class ProjectCard(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/project-card - - /components/schemas/validation-error - - /components/schemas/validation-error-simple - - /paths/"/projects/columns/{column_id}/cards"/post/responses/503/content/"application/json"/schema """ def _initAttributes(self) -> None: self._archived: Attribute[bool] = NotSet + self._column_name: Attribute[str] = NotSet self._column_url: Attribute[str] = NotSet self._content_url: Attribute[str] = NotSet self._created_at: Attribute[datetime] = NotSet @@ -87,6 +86,8 @@ def _initAttributes(self) -> None: self._id: Attribute[int] = NotSet self._node_id: Attribute[str] = NotSet self._note: Attribute[str] = NotSet + self._project_id: Attribute[str] = NotSet + self._project_url: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet self._url: Attribute[str] = NotSet @@ -97,6 +98,10 @@ def __repr__(self) -> str: def archived(self) -> bool: return self._archived.value + @property + def column_name(self) -> str: + return self._column_name.value + @property def column_url(self) -> str: return self._column_url.value @@ -125,6 +130,14 @@ def node_id(self) -> str: def note(self) -> str: return self._note.value + @property + def project_id(self) -> str: + return self._project_id.value + + @property + def project_url(self) -> str: + return self._project_url.value + @property def updated_at(self) -> datetime: return self._updated_at.value @@ -205,6 +218,8 @@ def edit(self, note: Opt[str] = NotSet, archived: Opt[bool] = NotSet) -> None: def _useAttributes(self, attributes: dict[str, Any]) -> None: if "archived" in attributes: # pragma no branch self._archived = self._makeBoolAttribute(attributes["archived"]) + if "column_name" in attributes: # pragma no branch + self._column_name = self._makeStringAttribute(attributes["column_name"]) if "column_url" in attributes: # pragma no branch self._column_url = self._makeStringAttribute(attributes["column_url"]) if "content_url" in attributes: # pragma no branch @@ -219,6 +234,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._node_id = self._makeStringAttribute(attributes["node_id"]) if "note" in attributes: # pragma no branch self._note = self._makeStringAttribute(attributes["note"]) + if "project_id" in attributes: # pragma no branch + self._project_id = self._makeStringAttribute(attributes["project_id"]) + if "project_url" in attributes: # pragma no branch + self._project_url = self._makeStringAttribute(attributes["project_url"]) if "updated_at" in attributes: # pragma no branch self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) if "url" in attributes: # pragma no branch diff --git a/github/ProjectColumn.py b/github/ProjectColumn.py index ad4cfb4fb9..6e338ecb97 100644 --- a/github/ProjectColumn.py +++ b/github/ProjectColumn.py @@ -55,13 +55,13 @@ import github.GithubObject import github.Project import github.ProjectCard -from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, Opt from github.PaginatedList import PaginatedList from . import Consts -class ProjectColumn(CompletableGithubObject): +class ProjectColumn(NonCompletableGithubObject): """ This class represents Project Columns. @@ -159,7 +159,7 @@ def create_card( headers, data = self._requester.requestJsonAndCheck( "POST", f"{self.url}/cards", headers=import_header, input=post_parameters ) - return github.ProjectCard.ProjectCard(self._requester, headers, data, completed=True) + return github.ProjectCard.ProjectCard(self._requester, headers, data) def move(self, position: str) -> bool: """ diff --git a/github/RateLimitOverview.py b/github/RateLimitOverview.py new file mode 100644 index 0000000000..ccfedeb89c --- /dev/null +++ b/github/RateLimitOverview.py @@ -0,0 +1,66 @@ +############################ Copyrights and license ############################ +# # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import github.Rate +import github.RateLimit +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.Rate import Rate + from github.RateLimit import RateLimit + + +class RateLimitOverview(NonCompletableGithubObject): + """ + This class represents RateLimitOverview. + + The reference can be found here + https://docs.github.com/en/rest/reference/rate-limit + + The OpenAPI schema can be found at + - /components/schemas/rate-limit-overview + + """ + + def _initAttributes(self) -> None: + self._rate: Attribute[Rate] = NotSet + self._resources: Attribute[RateLimit] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"rate": self._rate.value}) + + @property + def rate(self) -> Rate: + return self._rate.value + + @property + def resources(self) -> RateLimit: + return self._resources.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "rate" in attributes: # pragma no branch + self._rate = self._makeClassAttribute(github.Rate.Rate, attributes["rate"]) + if "resources" in attributes: # pragma no branch + self._resources = self._makeClassAttribute(github.RateLimit.RateLimit, attributes["resources"]) diff --git a/github/RepoCodeSecurityConfig.py b/github/RepoCodeSecurityConfig.py index 29c1c7e1cb..99bd395ea7 100644 --- a/github/RepoCodeSecurityConfig.py +++ b/github/RepoCodeSecurityConfig.py @@ -57,6 +57,9 @@ class RepoCodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-configuration-for-repository + """ def _initAttributes(self) -> None: diff --git a/github/Repository.py b/github/Repository.py index 66d0afc9c7..5032fbf564 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -4654,3 +4654,41 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._watchers_count = self._makeIntAttribute(attributes["watchers_count"]) if "web_commit_signoff_required" in attributes: # pragma no branch self._web_commit_signoff_required = self._makeBoolAttribute(attributes["web_commit_signoff_required"]) + + +class RepositorySearchResult(Repository): + """ + This class represents RepositorySearchResult. + + The reference can be found here + https://docs.github.com/en/rest/reference/search#search-repositories + + The OpenAPI schema can be found at + - /components/schemas/repo-search-result-item + + """ + + def _initAttributes(self) -> None: + super()._initAttributes() + self._score: Attribute[float] = NotSet + self._text_matches: Attribute[dict[str, Any]] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"full_name": self._full_name.value, "score": self._score.value}) + + @property + def score(self) -> float: + self._completeIfNotSet(self._score) + return self._score.value + + @property + def text_matches(self) -> dict[str, Any]: + self._completeIfNotSet(self._text_matches) + return self._text_matches.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + super()._useAttributes(attributes) + if "score" in attributes: # pragma no branch + self._score = self._makeFloatAttribute(attributes["score"]) + if "text_matches" in attributes: # pragma no branch + self._text_matches = self._makeDictAttribute(attributes["text_matches"]) diff --git a/github/Team.py b/github/Team.py index 6e32e9d6e7..1eda4f37c0 100644 --- a/github/Team.py +++ b/github/Team.py @@ -81,7 +81,7 @@ if TYPE_CHECKING: from github.Membership import Membership - from github.NamedUser import NamedUser + from github.NamedUser import NamedUser, OrganizationInvitation from github.Organization import Organization from github.PaginatedList import PaginatedList from github.Permissions import Permissions @@ -97,6 +97,7 @@ class Team(CompletableGithubObject): https://docs.github.com/en/rest/reference/teams The OpenAPI schema can be found at + - /components/schemas/enterprise-team - /components/schemas/nullable-team-simple - /components/schemas/team - /components/schemas/team-full @@ -426,12 +427,12 @@ def get_repos(self) -> PaginatedList[Repository]: github.Repository.Repository, self._requester, f"{self.url}/repos", None ) - def invitations(self) -> PaginatedList[NamedUser]: + def invitations(self) -> PaginatedList[OrganizationInvitation]: """ :calls: `GET /teams/{id}/invitations `_ """ return github.PaginatedList.PaginatedList( - github.NamedUser.NamedUser, + github.NamedUser.OrganizationInvitation, self._requester, f"{self.url}/invitations", None, diff --git a/github/WorkflowRun.py b/github/WorkflowRun.py index 928b930fcf..08eaea216a 100644 --- a/github/WorkflowRun.py +++ b/github/WorkflowRun.py @@ -57,6 +57,17 @@ class TimingData(NamedTuple): + """ + This class represents workflow run usage. + + The reference can be found here + https://docs.github.com/en/rest/actions/workflows#get-workflow-usage + + The OpenAPI schema can be found at + - /components/schemas/workflow-run-usage + + """ + billable: dict[str, dict[str, int]] run_duration_ms: int diff --git a/tests/Commit.py b/tests/Commit.py index b1880dcbc8..80d8f39146 100644 --- a/tests/Commit.py +++ b/tests/Commit.py @@ -86,7 +86,6 @@ def testAttributes(self): self.assertEqual(len(self.commit.parents), 1) self.assertEqual(self.commit.parents[0].sha, "b46ed0dfde5ad02d3b91eb54a41c5ed960710eae") self.assertIsNone(self.commit.repository) - self.assertEqual(self.commit.score, None) self.assertEqual(self.commit.sha, "1292bf0e22c796e91cc3d6e24b544aece8c21f2a") self.assertEqual(self.commit.stats.deletions, 20) self.assertEqual(self.commit.stats.additions, 0) diff --git a/tests/ContentFile.py b/tests/ContentFile.py index de07d90050..c7b01de662 100644 --- a/tests/ContentFile.py +++ b/tests/ContentFile.py @@ -69,7 +69,6 @@ def testAttributes(self): self.assertIsNone(self.file.line_numbers) self.assertEqual(self.file.name, "README.md") self.assertEqual(self.file.path, "README.md") - self.assertEqual(self.file.score, None) self.assertEqual(self.file.sha, "0d9df5bfb7d4b93443c130bc8f4eea5dd3f01205") self.assertEqual(self.file.size, 2524) self.assertIsNone(self.file.submodule_git_url) diff --git a/tests/Deployment.py b/tests/Deployment.py index 11f9fd8a9d..53d64bb3c4 100644 --- a/tests/Deployment.py +++ b/tests/Deployment.py @@ -53,7 +53,6 @@ def testAttributes(self): self.assertEqual(self.deployment.description, "Test deployment") self.assertEqual(self.deployment.environment, "test") self.assertEqual(self.deployment.id, 263877258) - self.assertIsNone(self.deployment.message) self.assertEqual(self.deployment.node_id, "MDEwOkRlcGxveW1lbnQyNjIzNTE3NzY=") self.assertEqual(self.deployment.original_environment, "test") self.assertEqual(self.deployment.payload, {"test": True}) diff --git a/tests/GitCommitVerification.py b/tests/GitCommitVerification.py index 54faaaead5..34572dbf99 100644 --- a/tests/GitCommitVerification.py +++ b/tests/GitCommitVerification.py @@ -22,6 +22,8 @@ # # ################################################################################ +from __future__ import annotations + from . import Framework @@ -34,6 +36,15 @@ def setUp(self): def testAttributes(self): verification = self.commit.commit.verification + self.assertEqual( + verification.payload, + "tree 4f502b4c4e5f0bdc7ee611f914bbdef51aef5efa\nparent 85087354078e426125dbbf88041bbaa6f35d8199\nauthor Tim Gates 1724830907 +1000\ncommitter Tim Gates 1724830915 +1000\n\nCommit verification support\n\nAdd support for verification component of Commit API response to see if\ncommit has been signed and the signature has been checked by Github\n", + ) + self.assertEqual(verification.reason, "valid") + self.assertEqual( + verification.signature, + "-----BEGIN PGP SIGNATURE-----\n\niMgEAAEKADIWIQRbWfY9TlELdM/5UaauO+DVOCPPBQUCZs7U0RQcdGltLmdhdGVz\nQGlyZXNzLmNvbQAKCRCuO+DVOCPPBQ2tBACAaRNONEWlDDNyYkAnIv8bZ55BuIuy\nTvbxVPjI8KLDqKLzgHO60HhQ3h/hCiug6g5fvVzyIrmayj3eEzaWAfa3+f37f3xK\nflZRMtNFUBYQoLTuyTkKvW85UA2AkUvKp3bHT5W6ZoCKqR5xw6pwKxuYQi7eJG9h\nNPrZTezkL6EDng==\n=Ng9G\n-----END PGP SIGNATURE-----", + ) self.assertEqual(verification.verified, True) self.assertIsNone(verification.verified_at) self.assertEqual(verification.reason, "valid") diff --git a/tests/GitTag.py b/tests/GitTag.py index f76d7ed79a..a84a316f35 100644 --- a/tests/GitTag.py +++ b/tests/GitTag.py @@ -65,7 +65,4 @@ def testAttributes(self): "https://api.github.com/repos/PyGithub/PyGithub/git/tags/f5f37322407b02a80de4526ad88d5f188977bc3c", ) self.assertEqual(repr(self.tag), 'GitTag(tag="v0.6", sha="f5f37322407b02a80de4526ad88d5f188977bc3c")') - self.assertEqual( - self.tag.verification, - {"verified": False, "reason": "unsigned", "signature": None, "payload": None, "verified_at": None}, - ) + self.assertEqual(self.tag.verification.reason, "unsigned") diff --git a/tests/Issue.py b/tests/Issue.py index dba7bb063f..ed373b44f7 100644 --- a/tests/Issue.py +++ b/tests/Issue.py @@ -100,7 +100,6 @@ def testAttributes(self): ) self.assertEqual(self.issue.repository.full_name, "PyGithub/PyGithub") self.assertEqual(self.issue.repository_url, "https://api.github.com/repos/PyGithub/PyGithub") - self.assertEqual(self.issue.score, None) self.assertEqual(self.issue.state, "closed") self.assertEqual(self.issue.state_reason, "completed") self.assertIsNone(self.issue.text_matches) diff --git a/tests/Issue142.py b/tests/Issue142.py index d319a56a12..ea8c0ae319 100644 --- a/tests/Issue142.py +++ b/tests/Issue142.py @@ -40,4 +40,4 @@ class Issue142(Framework.TestCase): def testDecodeJson(self): - self.assertEqual(github.Github().get_rate_limit().core.limit, 60) + self.assertEqual(github.Github().get_rate_limit().resources.core.limit, 60) diff --git a/tests/NamedUser.py b/tests/NamedUser.py index 76f04998b0..efa486bd6f 100644 --- a/tests/NamedUser.py +++ b/tests/NamedUser.py @@ -90,7 +90,6 @@ def testAttributes(self): self.assertEqual(self.user.received_events_url, "https://api.github.com/users/jacquev6/received_events") self.assertEqual(self.user.repos_url, "https://api.github.com/users/jacquev6/repos") self.assertIsNone(self.user.role_name) - self.assertEqual(self.user.score, None) self.assertEqual(self.user.site_admin, False) self.assertIsNone(self.user.starred_at) self.assertEqual(self.user.starred_url, "https://api.github.com/users/jacquev6/starred{/owner}{/repo}") @@ -465,3 +464,43 @@ def testUserEquality(self): self.assertTrue(u1 == u2) self.assertEqual(u1, u2) self.assertEqual(u1.__hash__(), u2.__hash__()) + + +class OrganizationInvitation(Framework.TestCase): + def setUp(self): + super().setUp() + # TODO: create an instance of type OrganizationInvitation and assign to self.attr, then run: + # pytest tests/OrganizationInvitation.py -k testAttributes --record --auth_with_token + # sed -i -e "s/token private_token_removed/Basic login_and_password_removed/" tests/ReplayData/OrganizationInvitation.setUp.txt + # ./scripts/update-assertions.sh tests/OrganizationInvitation.py testAttributes + self.org = self.g.get_organization("TestOrganization2072") + self.invitations = list(self.org.invitations()) + self.assertGreater(len(self.invitations), 0) + self.invitation = self.invitations[0] + + def testAttributes(self): + self.assertIsNotNone(self.invitation) + self.assertEqual(self.invitation.created_at, datetime(2021, 10, 12, 13, 32, 33, tzinfo=timezone.utc)) + self.assertEqual(self.invitation.email, "foo@bar.org") + self.assertIsNone(self.invitation.failed_at) + self.assertIsNone(self.invitation.failed_reason) + self.assertEqual(self.invitation.id, 28984230) + self.assertIsNone(self.invitation.invitation_source) + self.assertEqual( + self.invitation.invitation_teams_url, + "https://api.github.com/organizations/92288976/invitations/28984230/teams", + ) + self.assertEqual(self.invitation.inviter.login, "jsimpso") + self.assertIsNone(self.invitation.login) + self.assertEqual(self.invitation.node_id, "OI_kwDOBYA30M4BukOm") + self.assertEqual(self.invitation.role, "direct_member") + self.assertEqual(self.invitation.team_count, 0) + + def testCancel(self): + self.assertFalse(any([i for i in self.org.invitations() if i.email == "foo@bar.org"])) + self.org.invite_user(email="foo@bar.org") + self.assertTrue(any([i for i in self.org.invitations() if i.email == "foo@bar.org"])) + invitation = [i for i in self.org.invitations() if i.email == "foo@bar.org"][0] + self.assertTrue(self.org.cancel_invitation(invitation)) + # copy replay data of self.org.cancel_invitation(invitation) call, fix HTTP path + self.assertTrue(invitation.cancel()) diff --git a/tests/Organization2072.py b/tests/Organization2072.py deleted file mode 100644 index 1996d487da..0000000000 --- a/tests/Organization2072.py +++ /dev/null @@ -1,53 +0,0 @@ -############################ Copyrights and license ############################ -# # -# Copyright 2012 Vincent Jacques # -# Copyright 2012 Zearin # -# Copyright 2013 Vincent Jacques # -# Copyright 2014 Vincent Jacques # -# Copyright 2016 Matthew Neal # -# Copyright 2016 Peter Buckley # -# Copyright 2016 Sam Corbett # -# Copyright 2018 sfdye # -# Copyright 2019 Adam Baratz # -# Copyright 2019 Olof-Joachim Frahm (欧雅福) # -# Copyright 2019 Steve Kowalik # -# Copyright 2019 TechnicalPirate <35609336+TechnicalPirate@users.noreply.github.com># -# Copyright 2019 Wan Liuyang # -# Copyright 2020 Anuj Bansal # -# Copyright 2020 Steve Kowalik # -# Copyright 2021 James Simpson # -# Copyright 2023 Enrico Minack # -# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> # -# # -# This file is part of PyGithub. # -# http://pygithub.readthedocs.io/ # -# # -# PyGithub is free software: you can redistribute it and/or modify it under # -# the terms of the GNU Lesser General Public License as published by the Free # -# Software Foundation, either version 3 of the License, or (at your option) # -# any later version. # -# # -# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # -# details. # -# # -# You should have received a copy of the GNU Lesser General Public License # -# along with PyGithub. If not, see . # -# # -################################################################################ - -from . import Framework - - -class Organization2072(Framework.TestCase): - def setUp(self): - super().setUp() - self.org = self.g.get_organization("TestOrganization2072") - - def testCancelInvitation(self): - self.assertFalse(any([i for i in self.org.invitations() if i.email == "foo@bar.org"])) - self.org.invite_user(email="foo@bar.org") - self.assertTrue(any([i for i in self.org.invitations() if i.email == "foo@bar.org"])) - invitation = [i for i in self.org.invitations() if i.email == "foo@bar.org"][0] - self.assertTrue(self.org.cancel_invitation(invitation)) diff --git a/tests/Pickle.py b/tests/Pickle.py index 613516f1cd..2d28f98ffa 100644 --- a/tests/Pickle.py +++ b/tests/Pickle.py @@ -24,9 +24,13 @@ # # ################################################################################ +import inspect import pickle +import sys +from abc import ABC import github +from github.Auth import AppAuth, AppAuthToken, AppInstallationAuth, AppUserAuth, Auth, Login, NetrcAuth, Token from github.PaginatedList import PaginatedList from github.Repository import Repository @@ -59,3 +63,30 @@ def testPicklePaginatedList(self): branches = repo.get_branches() branches2 = pickle.loads(pickle.dumps(branches)) self.assertIsInstance(branches2, PaginatedList) + + auths = [ + Login("login", "password"), + Token("token"), + AppAuth("id", "key"), + AppAuthToken("token"), + AppInstallationAuth(AppAuth("id", "key"), 123), + AppUserAuth("client_id", "client_secret", "access_token"), + NetrcAuth(), + ] + + def testPickleAuthSetup(self): + # check we are testing *all* exiting auth classes + auth_module = sys.modules[github.Auth.__name__] + existing_auths = [ + clazz_type.__name__ + for clazz, clazz_type in inspect.getmembers(auth_module, inspect.isclass) + if Auth in clazz_type.mro() and ABC not in clazz_type.__bases__ + ] + tested_auths = [type(auth).__name__ for auth in self.auths] + self.assertSequenceEqual(sorted(existing_auths), sorted(tested_auths)) + + def testPickleAuth(self): + for auth in self.auths: + with self.subTest(auth=type(auth).__name__): + auth2 = pickle.loads(pickle.dumps(auth)) + self.assertIsInstance(auth2, type(auth)) diff --git a/tests/ProjectCard.py b/tests/ProjectCard.py index dc6dbbeee3..156584e9ad 100644 --- a/tests/ProjectCard.py +++ b/tests/ProjectCard.py @@ -22,6 +22,8 @@ from __future__ import annotations +from datetime import datetime, timezone + import github from . import Framework @@ -43,16 +45,20 @@ def setUp(self): # See https://developer.github.com/v3/projects/cards/#get-a-project-card def testAttributes(self): card = self.pull_card - self.assertEqual(card.url, "https://api.github.com/projects/columns/cards/11780055") + self.assertFalse(card.archived) + self.assertIsNone(card.column_name) self.assertEqual(card.column_url, "https://api.github.com/projects/columns/3138831") self.assertEqual(card.content_url, "https://api.github.com/repos/bbi-yggy/PyGithub/issues/1") - self.assertEqual(card.id, 11780055) - self.assertEqual(card.node_id, "MDExOlByb2plY3RDYXJkMTE3ODAwNTU=") - self.assertEqual(card.note, None) # No notes for cards with content. + self.assertEqual(card.created_at, datetime(2018, 8, 1, 4, 53, 59, tzinfo=timezone.utc)) self.assertEqual(card.creator, self.repo.owner) self.assertEqual(card.created_at.year, 2018) - self.assertTrue(card.updated_at >= card.created_at) - self.assertFalse(card.archived) + self.assertEqual(card.id, 11780055) + self.assertEqual(card.node_id, "MDExOlByb2plY3RDYXJkMTE3ODAwNTU=") + self.assertIsNone(card.note, None) + self.assertIsNone(card.project_id) + self.assertIsNone(card.project_url) + self.assertEqual(card.updated_at, datetime(2018, 8, 1, 4, 54, 16, tzinfo=timezone.utc)) + self.assertEqual(card.url, "https://api.github.com/projects/columns/cards/11780055") self.assertEqual(repr(card), "ProjectCard(id=11780055)") def testGetContent(self): diff --git a/tests/ProjectColumn.py b/tests/ProjectColumn.py index c02ae9473b..443af1a14a 100644 --- a/tests/ProjectColumn.py +++ b/tests/ProjectColumn.py @@ -24,6 +24,8 @@ # # ################################################################################ +from __future__ import annotations + from datetime import datetime, timezone from . import Framework @@ -40,14 +42,14 @@ def setUp(self): # See https://developer.github.com/v3/projects/columns/#get-a-project-column def testAttributes(self): col = self.col + self.assertEqual(col.cards_url, "https://api.github.com/projects/columns/3138830/cards") + self.assertEqual(col.created_at.year, 2018) self.assertEqual(col.id, 3138830) - self.assertEqual(col.node_id, "MDEzOlByb2plY3RDb2x1bW4zMTM4ODMw") self.assertEqual(col.name, "To Do") - self.assertEqual(col.url, "https://api.github.com/projects/columns/3138830") + self.assertEqual(col.node_id, "MDEzOlByb2plY3RDb2x1bW4zMTM4ODMw") self.assertEqual(col.project_url, "https://api.github.com/projects/1682941") - self.assertEqual(col.cards_url, "https://api.github.com/projects/columns/3138830/cards") - self.assertEqual(col.created_at.year, 2018) - self.assertTrue(col.updated_at >= col.created_at) + self.assertEqual(col.updated_at, datetime(2018, 8, 1, 4, 7, 35, tzinfo=timezone.utc)) + self.assertEqual(col.url, "https://api.github.com/projects/columns/3138830") self.assertEqual(repr(col), 'ProjectColumn(name="To Do")') def testCreate(self): diff --git a/tests/RateLimitOverview.py b/tests/RateLimitOverview.py new file mode 100644 index 0000000000..5d5fa01346 --- /dev/null +++ b/tests/RateLimitOverview.py @@ -0,0 +1,79 @@ +############################ Copyrights and license ############################ +# # +# # +# This file is part of PyGithub. # +# http://pygithub.readthedocs.io/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +from __future__ import annotations + +from datetime import datetime, timezone + +from . import Framework + + +class RateLimitOverview(Framework.TestCase): + def setUp(self): + super().setUp() + self.rate_limit_overview = self.g.get_rate_limit() + + def testAttributes(self): + self.assertEqual(self.rate_limit_overview.rate.limit, 5000) + self.assertEqual(self.rate_limit_overview.rate.remaining, 4988) + self.assertEqual(self.rate_limit_overview.rate.reset, datetime(2024, 12, 13, 6, 43, 18, tzinfo=timezone.utc)) + self.assertEqual(self.rate_limit_overview.rate.used, 12) + + self.assertEqual( + str(self.rate_limit_overview.resources.actions_runner_registration), + "Rate(reset=2024-12-13 07:28:18+00:00, remaining=10000, limit=10000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.code_scanning_upload), + "Rate(reset=2024-12-13 07:28:18+00:00, remaining=1000, limit=1000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.code_search), + "Rate(reset=2024-12-13 06:29:18+00:00, remaining=10, limit=10)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.core), + "Rate(reset=2024-12-13 06:43:18+00:00, remaining=4988, limit=5000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.dependency_snapshots), + "Rate(reset=2024-12-13 06:29:18+00:00, remaining=100, limit=100)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.graphql), + "Rate(reset=2024-12-13 06:43:42+00:00, remaining=4808, limit=5000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.integration_manifest), + "Rate(reset=2024-12-13 07:28:18+00:00, remaining=5000, limit=5000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.scim), + "Rate(reset=2024-12-13 07:28:18+00:00, remaining=15000, limit=15000)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.search), + "Rate(reset=2024-12-13 06:29:18+00:00, remaining=30, limit=30)", + ) + self.assertEqual( + str(self.rate_limit_overview.resources.source_import), + "Rate(reset=2024-12-13 06:29:18+00:00, remaining=100, limit=100)", + ) diff --git a/tests/RateLimiting.py b/tests/RateLimiting.py index 16909ead48..c6d1057ef0 100644 --- a/tests/RateLimiting.py +++ b/tests/RateLimiting.py @@ -52,7 +52,19 @@ def testResetTime(self): self.assertEqual(self.g.rate_limiting_resettime, 1684195041) def testGetRateLimit(self): - rateLimit = self.g.get_rate_limit() + rateLimitOverview = self.g.get_rate_limit() + + rate = rateLimitOverview.rate + self.assertEqual( + repr(rate), + "Rate(reset=2024-12-13 06:43:18+00:00, remaining=4988, limit=5000)", + ) + self.assertEqual(rate.limit, 5000) + self.assertEqual(rate.remaining, 4988) + self.assertEqual(rate.used, 12) + self.assertEqual(rate.reset, datetime(2024, 12, 13, 6, 43, 18, tzinfo=timezone.utc)) + + rateLimit = rateLimitOverview.resources self.assertEqual( repr(rateLimit), "RateLimit(core=Rate(reset=2024-12-13 06:43:18+00:00, remaining=4988, limit=5000))", diff --git a/tests/ReplayData/Organization2072.setUp.txt b/tests/ReplayData/OrganizationInvitation.setUp.txt similarity index 53% rename from tests/ReplayData/Organization2072.setUp.txt rename to tests/ReplayData/OrganizationInvitation.setUp.txt index d640410052..d406d7fc3d 100644 --- a/tests/ReplayData/Organization2072.setUp.txt +++ b/tests/ReplayData/OrganizationInvitation.setUp.txt @@ -8,3 +8,14 @@ None 200 [('Server', 'GitHub.com'), ('Date', 'Tue, 12 Oct 2021 05:32:32 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"0e8e9cdb8a79214111e22bae109cc8cd2ad163242dd751ce30d501009b7776d6"'), ('Last-Modified', 'Mon, 11 Oct 2021 05:17:24 GMT'), ('X-OAuth-Scopes', 'admin:org, admin:org_hook'), ('X-Accepted-OAuth-Scopes', 'admin:org, read:org, repo, user, write:org'), ('github-authentication-token-expiration', '2021-10-18 05:11:21 UTC'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4977'), ('X-RateLimit-Reset', '1634019426'), ('X-RateLimit-Used', '23'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'ADB2:4907:F9EF7D:10E5C1E:61651DEF')] {"login":"TestOrganization2072","id":92288976,"node_id":"O_kgDOBYA30A","url":"https://api.github.com/orgs/TestOrganization2072","repos_url":"https://api.github.com/orgs/TestOrganization2072/repos","events_url":"https://api.github.com/orgs/TestOrganization2072/events","hooks_url":"https://api.github.com/orgs/TestOrganization2072/hooks","issues_url":"https://api.github.com/orgs/TestOrganization2072/issues","members_url":"https://api.github.com/orgs/TestOrganization2072/members{/member}","public_members_url":"https://api.github.com/orgs/TestOrganization2072/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/92288976?v=4","description":null,"is_verified":false,"has_organization_projects":true,"has_repository_projects":true,"public_repos":0,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/TestOrganization2072","created_at":"2021-10-11T05:17:24Z","updated_at":"2021-10-11T05:17:24Z","type":"Organization","total_private_repos":0,"owned_private_repos":0,"private_gists":0,"disk_usage":0,"collaborators":0,"billing_email":"james@snowterminal.com","default_repository_permission":"read","members_can_create_repositories":true,"two_factor_requirement_enabled":false,"members_allowed_repository_creation_type":"all","members_can_create_public_repositories":true,"members_can_create_private_repositories":true,"members_can_create_internal_repositories":false,"members_can_create_pages":true,"members_can_create_public_pages":true,"members_can_create_private_pages":true,"plan":{"name":"free","space":976562499,"private_repos":10000,"filled_seats":1,"seats":0}} + +https +GET +api.github.com +None +/orgs/TestOrganization2072/invitations +{'Accept': 'application/vnd.github.dazzler-preview+json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('Server', 'GitHub.com'), ('Date', 'Tue, 12 Oct 2021 05:32:33 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"da2a9a62ed895da63af620e18d93b07f09c1511c7f08df957a1bbe33ba67ed7c"'), ('X-OAuth-Scopes', 'admin:org, admin:org_hook'), ('X-Accepted-OAuth-Scopes', 'admin:org, repo'), ('github-authentication-token-expiration', '2021-10-18 05:11:21 UTC'), ('X-GitHub-Media-Type', 'github.v3; param=dazzler-preview; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4974'), ('X-RateLimit-Reset', '1634019426'), ('X-RateLimit-Used', '26'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'ADB8:4907:F9EFD9:10E5C87:61651DF1')] +[{"id":28984230,"node_id":"OI_kwDOBYA30M4BukOm","login":null,"email":"foo@bar.org","role":"direct_member","created_at":"2021-10-12T13:32:33Z","failed_at":null,"failed_reason":null,"inviter":{"login":"jsimpso","id":10970100,"node_id":"MDQ6VXNlcjEwOTcwMTAw","avatar_url":"https://avatars.githubusercontent.com/u/10970100?v=4","gravatar_id":"","url":"https://api.github.com/users/jsimpso","html_url":"https://github.com/jsimpso","followers_url":"https://api.github.com/users/jsimpso/followers","following_url":"https://api.github.com/users/jsimpso/following{/other_user}","gists_url":"https://api.github.com/users/jsimpso/gists{/gist_id}","starred_url":"https://api.github.com/users/jsimpso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsimpso/subscriptions","organizations_url":"https://api.github.com/users/jsimpso/orgs","repos_url":"https://api.github.com/users/jsimpso/repos","events_url":"https://api.github.com/users/jsimpso/events{/privacy}","received_events_url":"https://api.github.com/users/jsimpso/received_events","type":"User","site_admin":false},"team_count":0,"invitation_teams_url":"https://api.github.com/organizations/92288976/invitations/28984230/teams"}] diff --git a/tests/ReplayData/Organization2072.testCancelInvitation.txt b/tests/ReplayData/OrganizationInvitation.testCancel.txt similarity index 89% rename from tests/ReplayData/Organization2072.testCancelInvitation.txt rename to tests/ReplayData/OrganizationInvitation.testCancel.txt index ddc8dbd046..4a530a062a 100644 --- a/tests/ReplayData/Organization2072.testCancelInvitation.txt +++ b/tests/ReplayData/OrganizationInvitation.testCancel.txt @@ -51,3 +51,14 @@ None None 204 [('Server', 'GitHub.com'), ('Date', 'Tue, 12 Oct 2021 05:32:34 GMT'), ('X-OAuth-Scopes', 'admin:org, admin:org_hook'), ('X-Accepted-OAuth-Scopes', 'admin:org'), ('github-authentication-token-expiration', '2021-10-18 05:11:21 UTC'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4972'), ('X-RateLimit-Reset', '1634019426'), ('X-RateLimit-Used', '28'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('X-GitHub-Request-Id', 'ADBC:057F:44AD76:4B0F5B:61651DF2')] + + +https +DELETE +api.github.com +None +/organizations/92288976/invitations/28984230 +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +204 +[('Server', 'GitHub.com'), ('Date', 'Tue, 12 Oct 2021 05:32:34 GMT'), ('X-OAuth-Scopes', 'admin:org, admin:org_hook'), ('X-Accepted-OAuth-Scopes', 'admin:org'), ('github-authentication-token-expiration', '2021-10-18 05:11:21 UTC'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4972'), ('X-RateLimit-Reset', '1634019426'), ('X-RateLimit-Used', '28'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('X-GitHub-Request-Id', 'ADBC:057F:44AD76:4B0F5B:61651DF2')] diff --git a/tests/ReplayData/RateLimitOverview.setUp.txt b/tests/ReplayData/RateLimitOverview.setUp.txt new file mode 100644 index 0000000000..72f3365890 --- /dev/null +++ b/tests/ReplayData/RateLimitOverview.setUp.txt @@ -0,0 +1,10 @@ +https +GET +api.github.com +None +/rate_limit +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('Server', 'GitHub.com'), ('Date', 'Mon, 15 May 2023 22:59:21 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'no-cache'), ('github-authentication-token-expiration', '2023-06-14 15:09:46 -0700'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('x-github-api-version-selected', '2022-11-28'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4904'), ('X-RateLimit-Reset', '1684195041'), ('X-RateLimit-Used', '96'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Vary', 'Accept-Encoding, Accept, X-Requested-With'), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'CA6A:617C:4252CD4:44D5F2B:6462B949')] +{"resources":{"core":{"limit":5000,"used":12,"remaining":4988,"reset":1734072198},"search":{"limit":30,"used":0,"remaining":30,"reset":1734071358},"graphql":{"limit":5000,"used":192,"remaining":4808,"reset":1734072222},"integration_manifest":{"limit":5000,"used":0,"remaining":5000,"reset":1734074898},"source_import":{"limit":100,"used":0,"remaining":100,"reset":1734071358},"code_scanning_upload":{"limit":1000,"used":0,"remaining":1000,"reset":1734074898},"code_scanning_autofix":{"limit":10,"used":0,"remaining":10,"reset":1734071358},"actions_runner_registration":{"limit":10000,"used":0,"remaining":10000,"reset":1734074898},"scim":{"limit":15000,"used":0,"remaining":15000,"reset":1734074898},"dependency_snapshots":{"limit":100,"used":0,"remaining":100,"reset":1734071358},"audit_log":{"limit":1750,"used":0,"remaining":1750,"reset":1734074898},"audit_log_streaming":{"limit":15,"used":0,"remaining":15,"reset":1734074898},"code_search":{"limit":10,"used":0,"remaining":10,"reset":1734071358}},"rate":{"limit":5000,"used":12,"remaining":4988,"reset":1734072198}} diff --git a/tests/RepositoryAdvisory.py b/tests/RepositoryAdvisory.py index 4e4fed0b8e..fd1972683e 100644 --- a/tests/RepositoryAdvisory.py +++ b/tests/RepositoryAdvisory.py @@ -46,6 +46,7 @@ def testAttributes(self): self.assertEqual(self.advisory.author.login, "JLLeitschuh") self.assertEqual(self.advisory.closed_at, None) self.assertIsNone(self.advisory.collaborating_teams) + self.assertIsNone(self.advisory.collaborating_users) self.assertEqual( self.advisory.created_at, datetime(2023, 3, 28, 21, 41, 40, tzinfo=timezone.utc),