From aee3a35043b0e46efc7a76481c00e86a88c799ca Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:14:51 +0100 Subject: [PATCH 01/15] Remove wrong schema from Repository (#3220) See https://github.com/PyGithub/PyGithub/pull/3095#discussion_r1962048472. --- github/Repository.py | 1 - 1 file changed, 1 deletion(-) diff --git a/github/Repository.py b/github/Repository.py index 3c6846a6f7..5032fbf564 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -333,7 +333,6 @@ class Repository(CompletableGithubObject): https://docs.github.com/en/rest/reference/repos The OpenAPI schema can be found at - - /components/schemas/code-security-configuration-repositories - /components/schemas/event/properties/repo - /components/schemas/full-repository - /components/schemas/minimal-repository From 13236d5d166758e6dd89a6d3ec9778b8d97f3d7b Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:15:22 +0100 Subject: [PATCH 02/15] =?UTF-8?q?Rename=20`HookDeliveryRequest`=20and=20`?= =?UTF-8?q?=E2=80=A6Response`=20private=20headers=20fields=20(#3221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- github/HookDelivery.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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"]) From e91713e9e8b989d30db10878a75cc6c930f5b210 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:16:46 +0100 Subject: [PATCH 03/15] Remove schema from `Deployment`, remove `message` attribute (#3223) --- github/Deployment.py | 9 --------- tests/Deployment.py | 1 - 2 files changed, 10 deletions(-) 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/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}) From a2071d7005149b3af1fb9e3aa16eb5873d0c9ba1 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:17:45 +0100 Subject: [PATCH 04/15] Fix incorrect deprecated import (#3225) Fixes #3222. --- github/CheckRun.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 850932cca6eb588047fe27c2e74b942572f1488e Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:57:19 +0100 Subject: [PATCH 05/15] Make `GitTag.verification` return `GitCommitVerification` (#3226) Turns the return value of `GitTag.verification` from `dict` into a proper class. Completes #3028. --- doc/changes.rst | 18 ++++++++++++++++++ github/GitTag.py | 10 +++++++--- tests/GitTag.py | 5 +---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/changes.rst b/doc/changes.rst index 481ea229f1..df38288eb7 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -49,6 +49,24 @@ Breaking Changes 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 + * Property ``AppAuth.private_key`` has been removed (`#3065 `_) (`36697b22 `_) * Fix typos (`#3086 `_) (`a50ae51b `_): 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/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") From dbb32eed5c640c6e551481cfa32c44ec15c2102c Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 14:03:56 +0100 Subject: [PATCH 06/15] Add `CodeSecurityConfigRepository` returned by `get_repos_for_code_security_config` (#3219) Follow-up on #3095. --- github/CodeSecurityConfigRepository.py | 64 ++++++++++++++++++++++++++ github/Organization.py | 8 +++- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 github/CodeSecurityConfigRepository.py diff --git a/github/CodeSecurityConfigRepository.py b/github/CodeSecurityConfigRepository.py new file mode 100644 index 0000000000..4f69431a2f --- /dev/null +++ b/github/CodeSecurityConfigRepository.py @@ -0,0 +1,64 @@ +############################ 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.Repository +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet + +if TYPE_CHECKING: + from github.Repository import Repository + + +class CodeSecurityConfigRepository(NonCompletableGithubObject): + """ + This class represents CodeSecurityConfigRepository. + + 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-repositories + + """ + + def _initAttributes(self) -> None: + self._repository: Attribute[Repository] = NotSet + self._status: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.repository.__repr__() + + @property + def repository(self) -> Repository: + return self._repository.value + + @property + def status(self) -> str: + return self._status.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "repository" in attributes: # pragma no branch + self._repository = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "status" in attributes: # pragma no branch + self._status = self._makeStringAttribute(attributes["status"]) diff --git a/github/Organization.py b/github/Organization.py index 3713f05ab3..b081f7c0ed 100644 --- a/github/Organization.py +++ b/github/Organization.py @@ -90,6 +90,7 @@ from typing import TYPE_CHECKING, Any import github.CodeSecurityConfig +import github.CodeSecurityConfigRepository import github.Copilot import github.DefaultCodeSecurityConfig import github.Event @@ -119,6 +120,7 @@ if TYPE_CHECKING: from github.CodeSecurityConfig import CodeSecurityConfig + from github.CodeSecurityConfigRepository import CodeSecurityConfigRepository from github.Copilot import Copilot from github.DefaultCodeSecurityConfig import DefaultCodeSecurityConfig from github.Event import Event @@ -1821,7 +1823,9 @@ def detach_security_config_from_repositories(self, selected_repository_ids: list headers={"Accept": Consts.repoVisibilityPreview}, ) - def get_repos_for_code_security_config(self, id: int, status: Opt[str] = NotSet) -> PaginatedList[Repository]: + def get_repos_for_code_security_config( + self, id: int, status: Opt[str] = NotSet + ) -> PaginatedList[CodeSecurityConfigRepository]: """ :calls: `GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories `_ """ @@ -1831,7 +1835,7 @@ def get_repos_for_code_security_config(self, id: int, status: Opt[str] = NotSet) url_parameters = NotSet.remove_unset_items({"status": status}) return PaginatedList( - github.Repository.Repository, + github.CodeSecurityConfigRepository.CodeSecurityConfigRepository, self._requester, f"{self.url}/code-security/configurations/{id}/repositories", url_parameters, From 66a3cc1cdcc448e9b8ea18940a92999d358344c8 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:06:31 +0100 Subject: [PATCH 07/15] Fix `Branch.get_required_status_checks` return type (#3235) As a result removes wrong schema from `RequiredStatusChecks`. --- github/Branch.py | 6 ++++-- github/RequiredStatusChecks.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/github/Branch.py b/github/Branch.py index f77116f79d..0dcfde0587 100644 --- a/github/Branch.py +++ b/github/Branch.py @@ -426,7 +426,7 @@ def edit_required_pull_request_reviews( require_code_owner_reviews: Opt[bool] = NotSet, required_approving_review_count: Opt[int] = NotSet, require_last_push_approval: Opt[bool] = NotSet, - ) -> RequiredStatusChecks: + ) -> RequiredPullRequestReviews: """ :calls: `PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews `_ """ @@ -460,7 +460,9 @@ def edit_required_pull_request_reviews( input=post_parameters, ) - return github.RequiredStatusChecks.RequiredStatusChecks(self._requester, headers, data, completed=True) + return github.RequiredPullRequestReviews.RequiredPullRequestReviews( + self._requester, headers, data, completed=True + ) def remove_required_pull_request_reviews(self) -> None: """ diff --git a/github/RequiredStatusChecks.py b/github/RequiredStatusChecks.py index 13bd5d962b..4e3ec0db62 100644 --- a/github/RequiredStatusChecks.py +++ b/github/RequiredStatusChecks.py @@ -54,7 +54,6 @@ class RequiredStatusChecks(CompletableGithubObject): https://docs.github.com/en/rest/reference/repos#get-status-checks-protection The OpenAPI schema can be found at - - /components/schemas/protected-branch-pull-request-review - /components/schemas/protected-branch-required-status-check - /components/schemas/status-check-policy From bb00062de8f2a1737822e8307010febff430f8fc Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:43:23 +0100 Subject: [PATCH 08/15] Sort classes' functions (#3231) This allows for programmatic maintenance of Python classes. --- github/ApplicationOAuth.py | 8 +++---- github/Copilot.py | 8 +++---- github/Enterprise.py | 8 +++---- github/GithubObject.py | 32 ++++++++++++++-------------- github/Installation.py | 30 +++++++++++++------------- github/MergedUpstream.py | 14 ++++++------ github/OrganizationCustomProperty.py | 8 +++---- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/github/ApplicationOAuth.py b/github/ApplicationOAuth.py index 5143aa6217..f72a836527 100644 --- a/github/ApplicationOAuth.py +++ b/github/ApplicationOAuth.py @@ -54,10 +54,6 @@ class ApplicationOAuth(NonCompletableGithubObject): """ - def _initAttributes(self) -> None: - self._client_id: Attribute[str] = NotSet - self._client_secret: Attribute[str] = NotSet - def __init__( self, requester: Requester, @@ -68,6 +64,10 @@ def __init__( requester = requester.withAuth(auth=None) super().__init__(requester, headers, attributes) + def _initAttributes(self) -> None: + self._client_id: Attribute[str] = NotSet + self._client_secret: Attribute[str] = NotSet + def __repr__(self) -> str: return self.get__repr__({"client_id": self._client_id.value}) diff --git a/github/Copilot.py b/github/Copilot.py index ab63752192..4503e71794 100644 --- a/github/Copilot.py +++ b/github/Copilot.py @@ -41,10 +41,6 @@ def __init__(self, requester: Requester, org_name: str) -> None: def _initAttributes(self) -> None: self._org_name: Attribute[str] = NotSet - def _useAttributes(self, attributes: dict[str, Any]) -> None: - if "org_name" in attributes: # pragma no branch - self._org_name = self._makeStringAttribute(attributes["org_name"]) - def __repr__(self) -> str: return self.get__repr__({"org_name": self._org_name.value if self._org_name is not NotSet else NotSet}) @@ -94,3 +90,7 @@ def remove_seats(self, selected_usernames: list[str]) -> int: input={"selected_usernames": selected_usernames}, ) return data["seats_cancelled"] + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "org_name" in attributes: # pragma no branch + self._org_name = self._makeStringAttribute(attributes["org_name"]) diff --git a/github/Enterprise.py b/github/Enterprise.py index 46a382dcb3..d11b52137b 100644 --- a/github/Enterprise.py +++ b/github/Enterprise.py @@ -56,10 +56,6 @@ class Enterprise(NonCompletableGithubObject): """ - def _initAttributes(self) -> None: - self._enterprise: Attribute[str] = NotSet - self._url: Attribute[str] = NotSet - def __init__( self, requester: Requester, @@ -68,6 +64,10 @@ def __init__( enterprise = urllib.parse.quote(enterprise) super().__init__(requester, {}, {"enterprise": enterprise, "url": f"/enterprises/{enterprise}"}) + def _initAttributes(self) -> None: + self._enterprise: Attribute[str] = NotSet + self._url: Attribute[str] = NotSet + def __repr__(self) -> str: return self.get__repr__({"enterprise": self._enterprise.value}) diff --git a/github/GithubObject.py b/github/GithubObject.py index 1661594dfe..697f160138 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -534,6 +534,22 @@ def __ne__(self, other: Any) -> bool: def completed(self) -> bool: return self.__completed + @property + def raw_data(self) -> dict[str, Any]: + """ + :type: dict + """ + self._completeIfNeeded() + return super().raw_data + + @property + def raw_headers(self) -> dict[str, str | int]: + """ + :type: dict + """ + self._completeIfNeeded() + return super().raw_headers + def complete(self) -> Self: self._completeIfNeeded() return self @@ -553,22 +569,6 @@ def __complete(self) -> None: self._storeAndUseAttributes(headers, data) self.__completed = True - @property - def raw_data(self) -> dict[str, Any]: - """ - :type: dict - """ - self._completeIfNeeded() - return super().raw_data - - @property - def raw_headers(self) -> dict[str, str | int]: - """ - :type: dict - """ - self._completeIfNeeded() - return super().raw_headers - def update(self, additional_headers: dict[str, Any] | None = None) -> bool: """ Check and update the object with conditional request :rtype: Boolean value indicating whether the object is diff --git a/github/Installation.py b/github/Installation.py index caafbc2bac..be0dc65c14 100644 --- a/github/Installation.py +++ b/github/Installation.py @@ -85,6 +85,21 @@ class Installation(NonCompletableGithubObject): """ + def __init__( + self, + requester: Requester, + headers: dict[str, str | int], + attributes: Any, + ) -> None: + super().__init__(requester, headers, attributes) + + auth = self._requester.auth if self._requester is not None else None + # Usually, an Installation is created from a Requester with App authentication + if isinstance(auth, AppAuth): + # But the installation has to authenticate as an installation (e.g. for get_repos()) + auth = auth.get_installation_auth(self.id, requester=self._requester) + self._requester = self._requester.withAuth(auth) + def _initAttributes(self) -> None: self._access_tokens_url: Attribute[str] = NotSet self._account: Attribute[NamedUser | Organization] = NotSet @@ -107,21 +122,6 @@ def _initAttributes(self) -> None: self._target_type: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet - def __init__( - self, - requester: Requester, - headers: dict[str, str | int], - attributes: Any, - ) -> None: - super().__init__(requester, headers, attributes) - - auth = self._requester.auth if self._requester is not None else None - # Usually, an Installation is created from a Requester with App authentication - if isinstance(auth, AppAuth): - # But the installation has to authenticate as an installation (e.g. for get_repos()) - auth = auth.get_installation_auth(self.id, requester=self._requester) - self._requester = self._requester.withAuth(auth) - def __repr__(self) -> str: return self.get__repr__({"id": self._id.value}) diff --git a/github/MergedUpstream.py b/github/MergedUpstream.py index fbb0390432..d9790d6d9e 100644 --- a/github/MergedUpstream.py +++ b/github/MergedUpstream.py @@ -40,29 +40,29 @@ class MergedUpstream(NonCompletableGithubObject): """ def _initAttributes(self) -> None: - self._merge_type: Attribute[str] = NotSet self._base_branch: Attribute[str] = NotSet + self._merge_type: Attribute[str] = NotSet self._message: Attribute[str] = NotSet def __repr__(self) -> str: return self.get__repr__({"message": self._message.value}) - @property - def merge_type(self) -> str: - return self._merge_type.value - @property def base_branch(self) -> str: return self._base_branch.value + @property + def merge_type(self) -> str: + return self._merge_type.value + @property def message(self) -> str: return self._message.value def _useAttributes(self, attributes: Dict[str, Any]) -> None: - if "merge_type" in attributes: # pragma no branch - self._merge_type = self._makeStringAttribute(attributes["merge_type"]) if "base_branch" in attributes: # pragma no branch self._base_branch = self._makeStringAttribute(attributes["base_branch"]) + if "merge_type" in attributes: # pragma no branch + self._merge_type = self._makeStringAttribute(attributes["merge_type"]) if "message" in attributes: # pragma no branch self._message = self._makeStringAttribute(attributes["message"]) diff --git a/github/OrganizationCustomProperty.py b/github/OrganizationCustomProperty.py index 53f935ba1f..012aee1a5b 100644 --- a/github/OrganizationCustomProperty.py +++ b/github/OrganizationCustomProperty.py @@ -167,14 +167,14 @@ def properties(self) -> dict[str, str]: def repository_full_name(self) -> str: return self._repository_full_name.value - @property - def repository_name(self) -> str: - return self._repository_name.value - @property def repository_id(self) -> int: return self._repository_id.value + @property + def repository_name(self) -> str: + return self._repository_name.value + def _useAttributes(self, attributes: dict[str, Any]) -> None: self._repository_id = self._makeIntAttribute(attributes["repository_id"]) self._repository_name = self._makeStringAttribute(attributes["repository_name"]) From 45f26a1bb4813521bf1a69f5d40fb685d720fde7 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:43:41 +0100 Subject: [PATCH 09/15] Sync `CopilotSeat` class with API spec (#3232) Adds the following attributes: - organization --- github/CopilotSeat.py | 13 ++++++++++++- github/Organization.py | 1 + tests/Copilot.py | 1 + tests/ReplayData/Copilot.testAttributes.txt | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/github/CopilotSeat.py b/github/CopilotSeat.py index 6cb93c2215..e337ad37c1 100644 --- a/github/CopilotSeat.py +++ b/github/CopilotSeat.py @@ -24,12 +24,16 @@ from __future__ import annotations from datetime import datetime -from typing import Any +from typing import TYPE_CHECKING, Any import github.NamedUser +import github.Organization import github.Team from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, _NotSetType +if TYPE_CHECKING: + from github.Organization import Organization + class CopilotSeat(NonCompletableGithubObject): """ @@ -49,6 +53,7 @@ def _initAttributes(self) -> None: self._created_at: Attribute[datetime] | _NotSetType = NotSet self._last_activity_at: Attribute[datetime] | _NotSetType = NotSet self._last_activity_editor: Attribute[str] | _NotSetType = NotSet + self._organization: Attribute[Organization] = NotSet self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet self._plan_type: Attribute[str] | _NotSetType = NotSet self._updated_at: Attribute[datetime] | _NotSetType = NotSet @@ -76,6 +81,10 @@ def last_activity_at(self) -> datetime: def last_activity_editor(self) -> str: return self._last_activity_editor.value + @property + def organization(self) -> Organization: + return self._organization.value + @property def pending_cancellation_date(self) -> datetime: return self._pending_cancellation_date.value @@ -99,6 +108,8 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: 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 "organization" in attributes: # pragma no branch + self._organization = self._makeClassAttribute(github.Organization.Organization, attributes["organization"]) if "pending_cancellation_date" in attributes: self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) if "plan_type" in attributes: diff --git a/github/Organization.py b/github/Organization.py index b081f7c0ed..7a0d5414fe 100644 --- a/github/Organization.py +++ b/github/Organization.py @@ -154,6 +154,7 @@ class Organization(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/actor + - /components/schemas/nullable-organization-simple - /components/schemas/nullable-simple-user - /components/schemas/organization-full - /components/schemas/organization-simple diff --git a/tests/Copilot.py b/tests/Copilot.py index d49c06d4d6..0d3c0a5137 100644 --- a/tests/Copilot.py +++ b/tests/Copilot.py @@ -55,6 +55,7 @@ def testAttributes(self): self.assertEqual(seat.pending_cancellation_date, None) self.assertEqual(seat.last_activity_at, datetime(2012, 5, 26, 14, 59, 39, tzinfo=timezone.utc)) self.assertEqual(seat.last_activity_editor, "vscode/1.0.0") + self.assertEqual(seat.organization.login, self.org_name) self.assertEqual(seat.plan_type, "business") self.assertEqual(seat.assignee.login, "pashafateev") self.assertEqual(repr(seat), 'CopilotSeat(assignee=NamedUser(login="pashafateev"))') diff --git a/tests/ReplayData/Copilot.testAttributes.txt b/tests/ReplayData/Copilot.testAttributes.txt index 707fbf04fe..b0de3fd059 100644 --- a/tests/ReplayData/Copilot.testAttributes.txt +++ b/tests/ReplayData/Copilot.testAttributes.txt @@ -7,4 +7,4 @@ None None 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4973'), ('content-length', '716'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"4862bcec9fa538316e2fcd73be37b846"'), ('date', 'Sat, 26 May 2012 21:09:52 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"seats": [{"created_at": "2010-07-09T06:10:06Z", "updated_at": "2012-05-26T11:25:48Z", "pending_cancellation_date": null, "last_activity_at": "2012-05-26T14:59:39Z", "last_activity_editor": "vscode/1.0.0", "plan_type": "business", "assignee": {"login": "pashafateev", "id": 327146}}]} +{"seats": [{"created_at": "2010-07-09T06:10:06Z", "updated_at": "2012-05-26T11:25:48Z", "pending_cancellation_date": null, "last_activity_at": "2012-05-26T14:59:39Z", "last_activity_editor": "vscode/1.0.0", "organization": {"login": "BeaverSoftware"}, "plan_type": "business", "assignee": {"login": "pashafateev", "id": 327146}}]} From bc1c53756340e735075cab99549b052f303a04d4 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:43:56 +0100 Subject: [PATCH 10/15] Sync `HookDeliverySummary` class with API spec (#3233) Adds the following attributes: - throttled_at --- github/HookDelivery.py | 23 +++++++++---------- tests/Github_.py | 3 ++- .../Github.testGetHookDeliveries.txt | 2 +- .../ReplayData/Github.testGetHookDelivery.txt | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/github/HookDelivery.py b/github/HookDelivery.py index e43c70b9b6..0ab2d196a4 100644 --- a/github/HookDelivery.py +++ b/github/HookDelivery.py @@ -31,11 +31,10 @@ from datetime import datetime from typing import Any -import github.GithubObject -from github.GithubObject import Attribute, NotSet +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet -class HookDeliverySummary(github.GithubObject.NonCompletableGithubObject): +class HookDeliverySummary(NonCompletableGithubObject): """ This class represents a Summary of HookDeliveries. @@ -56,6 +55,7 @@ def _initAttributes(self) -> None: self._repository_id: Attribute[int] = NotSet self._status: Attribute[str] = NotSet self._status_code: Attribute[int] = NotSet + self._throttled_at: Attribute[datetime] = NotSet self._url: Attribute[str] = NotSet def __repr__(self) -> str: @@ -105,6 +105,10 @@ def status(self) -> str | None: def status_code(self) -> int | None: return self._status_code.value + @property + def throttled_at(self) -> datetime: + return self._throttled_at.value + @property def url(self) -> str | None: return self._url.value @@ -132,11 +136,13 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._status = self._makeStringAttribute(attributes["status"]) if "status_code" in attributes: # pragma no branch self._status_code = self._makeIntAttribute(attributes["status_code"]) + if "throttled_at" in attributes: # pragma no branch + self._throttled_at = self._makeDatetimeAttribute(attributes["throttled_at"]) if "url" in attributes: # pragma no branch self._url = self._makeStringAttribute(attributes["url"]) -class HookDeliveryRequest(github.GithubObject.NonCompletableGithubObject): +class HookDeliveryRequest(NonCompletableGithubObject): """ This class represents a HookDeliveryRequest. @@ -167,7 +173,7 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._payload = self._makeDictAttribute(attributes["payload"]) -class HookDeliveryResponse(github.GithubObject.NonCompletableGithubObject): +class HookDeliveryResponse(NonCompletableGithubObject): """ This class represents a HookDeliveryResponse. @@ -211,7 +217,6 @@ def _initAttributes(self) -> None: super()._initAttributes() self._request: Attribute[HookDeliveryRequest] = NotSet self._response: Attribute[HookDeliveryResponse] = NotSet - self._throttled_at: Attribute[datetime] = NotSet def __repr__(self) -> str: return self.get__repr__({"id": self._id.value}) @@ -224,10 +229,6 @@ def request(self) -> HookDeliveryRequest | None: def response(self) -> HookDeliveryResponse | None: return self._response.value - @property - def throttled_at(self) -> datetime: - return self._throttled_at.value - def _useAttributes(self, attributes: dict[str, Any]) -> None: super()._useAttributes(attributes) if "request" in attributes: # pragma no branch @@ -235,5 +236,3 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: if "response" in attributes: # pragma no branch self._response = self._makeClassAttribute(HookDeliveryResponse, attributes["response"]) # self._response = self._makeDictAttribute(attributes["response"]) - if "throttled_at" in attributes: # pragma no branch - self._throttled_at = self._makeDatetimeAttribute(attributes["throttled_at"]) diff --git a/tests/Github_.py b/tests/Github_.py index 4dd180d245..234e617fb2 100644 --- a/tests/Github_.py +++ b/tests/Github_.py @@ -283,7 +283,7 @@ def testGetHookDelivery(self): self.assertEqual(delivery.duration, 0.27) self.assertEqual(delivery.status, "OK") self.assertEqual(delivery.status_code, 200) - self.assertIsNone(delivery.throttled_at) + self.assertEqual(delivery.throttled_at, datetime(2024, 1, 2, 12, 34, 56, tzinfo=timezone.utc)) self.assertEqual(delivery.event, "issues") self.assertEqual(delivery.action, "opened") self.assertEqual(delivery.installation_id, 123) @@ -309,6 +309,7 @@ def testGetHookDeliveries(self): self.assertEqual(deliveries[0].duration, 0.27) self.assertEqual(deliveries[0].status, "OK") self.assertEqual(deliveries[0].status_code, 200) + self.assertEqual(deliveries[0].throttled_at, datetime(2024, 1, 2, 12, 34, 56, tzinfo=timezone.utc)) self.assertEqual(deliveries[0].event, "issues") self.assertEqual(deliveries[0].action, "opened") self.assertEqual(deliveries[0].installation_id, 123) diff --git a/tests/ReplayData/Github.testGetHookDeliveries.txt b/tests/ReplayData/Github.testGetHookDeliveries.txt index 40566ab663..468c8143ea 100644 --- a/tests/ReplayData/Github.testGetHookDeliveries.txt +++ b/tests/ReplayData/Github.testGetHookDeliveries.txt @@ -7,4 +7,4 @@ None None 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4999'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('x-github-request-id', 'b3cd8329-7f33-4611-84d1-4e2ecfd91812'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '191'), ('server', 'GitHub.com'), ('last-modified', 'Wed, 04 Sep 2013 18:03:57 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"678dd8e392d70d3a284c3d47221ec6f0"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Wed, 11 Sep 2013 21:10:37 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378937437')] -[{"id":12345,"guid":"abcde-12345","delivered_at":"2012-05-27T06:00:32Z","redelivery":false,"duration":0.27,"status":"OK","status_code":200,"event":"issues","action":"opened","installation_id":123,"repository_id":456,"url":"https://www.example-webhook.com"}] +[{"id":12345,"guid":"abcde-12345","delivered_at":"2012-05-27T06:00:32Z","redelivery":false,"duration":0.27,"status":"OK","status_code":200,"event":"issues","action":"opened","installation_id":123,"repository_id":456,"throttled_at":"2024-01-02T12:34:56Z","url":"https://www.example-webhook.com"}] diff --git a/tests/ReplayData/Github.testGetHookDelivery.txt b/tests/ReplayData/Github.testGetHookDelivery.txt index 44ccfcb6ba..d88895c638 100644 --- a/tests/ReplayData/Github.testGetHookDelivery.txt +++ b/tests/ReplayData/Github.testGetHookDelivery.txt @@ -7,4 +7,4 @@ None None 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4999'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('x-github-request-id', 'b3cd8329-7f33-4611-84d1-4e2ecfd91812'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '191'), ('server', 'GitHub.com'), ('last-modified', 'Wed, 04 Sep 2013 18:03:57 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"678dd8e392d70d3a284c3d47221ec6f0"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Wed, 11 Sep 2013 21:10:37 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378937437')] -{"id":12345,"guid":"abcde-12345","delivered_at":"2012-05-27T06:00:32Z","redelivery":false,"duration":0.27,"status":"OK","status_code":200,"event":"issues","action":"opened","installation_id":123,"repository_id":456,"url":"https://www.example-webhook.com","request":{"headers":{"content-type": "application/json"},"payload":{"action": "opened"}},"response":{"headers":{"content-type": "text/html;charset=utf-8"},"payload":"ok"}} +{"id":12345,"guid":"abcde-12345","delivered_at":"2012-05-27T06:00:32Z","redelivery":false,"duration":0.27,"status":"OK","status_code":200,"event":"issues","action":"opened","installation_id":123,"repository_id":456,"throttled_at":"2024-01-02T12:34:56Z","url":"https://www.example-webhook.com","request":{"headers":{"content-type": "application/json"},"payload":{"action": "opened"}},"response":{"headers":{"content-type": "text/html;charset=utf-8"},"payload":"ok"}} From 2f991c487e798d5653bdb24318946b1e7e6d4d48 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:44:21 +0100 Subject: [PATCH 11/15] Sync `RequiredPullRequestReviews` class with API spec (#3234) Adds the following attributes: - bypass_pull_request_allowances - dismissal_restrictions Deprecates these attributes: - dismissal_users - dismissal_teams Adds classes `BypassPullRequestAllowances` and `DismissalRestrictions`. --- doc/changes.rst | 6 + github/RequiredPullRequestReviews.py | 150 ++++++++++++++++-- tests/Branch.py | 4 + ...questReviews.testOrganizationOwnedTeam.txt | 2 +- tests/RequiredPullRequestReviews.py | 36 +++++ 5 files changed, 181 insertions(+), 17 deletions(-) diff --git a/doc/changes.rst b/doc/changes.rst index df38288eb7..83f5f9af97 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -24,6 +24,12 @@ Breaking Changes gh.get_rate_limit().resources.core.remaining +Deprecations +^^^^^^^^^^^^ + +* Methods ``dismissal_users`` and ``dismissal_teams`` of ``RequiredPullRequestReviews`` are deprecated, + use ``dismissal_restrictions.users`` and ``dismissal_restrictions.teams`` instead. + Version 2.6.0 (February 15, 2025) --------------------------------- diff --git a/github/RequiredPullRequestReviews.py b/github/RequiredPullRequestReviews.py index 410e0e745c..5e40e73504 100644 --- a/github/RequiredPullRequestReviews.py +++ b/github/RequiredPullRequestReviews.py @@ -46,15 +46,124 @@ from typing import TYPE_CHECKING, Any +import deprecated + +import github.GithubApp import github.NamedUser import github.Team -from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.GithubObject import Attribute, CompletableGithubObject, NonCompletableGithubObject, NotSet if TYPE_CHECKING: + from github.GithubApp import GithubApp from github.NamedUser import NamedUser from github.Team import Team +class BypassPullRequestAllowances(NonCompletableGithubObject): + """ + This class represents BypassPullRequestAllowances. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#get-pull-request-review-protection + + The OpenAPI schema can be found at + - /components/schemas/protected-branch-pull-request-review/properties/bypass_pull_request_allowances + - /components/schemas/protected-branch/properties/required_pull_request_reviews/properties/bypass_pull_request_allowances + + """ + + def _initAttributes(self) -> None: + self._apps: Attribute[list[GithubApp]] = NotSet + self._teams: Attribute[list[Team]] = NotSet + self._users: Attribute[list[NamedUser]] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"apps": self._apps.value, "teams": self._teams.value, "users": self._users.value}) + + @property + def apps(self) -> list[GithubApp]: + return self._apps.value + + @property + def teams(self) -> list[Team]: + return self._teams.value + + @property + def users(self) -> list[NamedUser]: + return self._users.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "apps" in attributes: # pragma no branch + self._apps = self._makeListOfClassesAttribute(github.GithubApp.GithubApp, attributes["apps"]) + if "teams" in attributes: # pragma no branch + self._teams = self._makeListOfClassesAttribute(github.Team.Team, attributes["teams"]) + if "users" in attributes: # pragma no branch + self._users = self._makeListOfClassesAttribute(github.NamedUser.NamedUser, attributes["users"]) + + +class DismissalRestrictions(NonCompletableGithubObject): + """ + This class represents DismissalRestrictions. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#get-pull-request-review-protection + + The OpenAPI schema can be found at + - /components/schemas/protected-branch-pull-request-review/properties/dismissal_restrictions + - /components/schemas/protected-branch/properties/required_pull_request_reviews/properties/dismissal_restrictions + + """ + + def _initAttributes(self) -> None: + self._apps: Attribute[list[GithubApp]] = NotSet + self._teams: Attribute[list[Team]] = NotSet + self._teams_url: Attribute[str] = NotSet + self._url: Attribute[str] = NotSet + self._users: Attribute[list[NamedUser]] = NotSet + self._users_url: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"apps": self._apps.value, "teams": self._teams.value, "users": self._users.value}) + + @property + def apps(self) -> list[GithubApp]: + return self._apps.value + + @property + def teams(self) -> list[Team]: + return self._teams.value + + @property + def teams_url(self) -> str: + return self._teams_url.value + + @property + def url(self) -> str: + return self._url.value + + @property + def users(self) -> list[NamedUser]: + return self._users.value + + @property + def users_url(self) -> str: + return self._users_url.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "apps" in attributes: # pragma no branch + self._apps = self._makeListOfClassesAttribute(github.GithubApp.GithubApp, attributes["apps"]) + if "teams" in attributes: # pragma no branch + self._teams = self._makeListOfClassesAttribute(github.Team.Team, attributes["teams"]) + if "teams_url" in attributes: # pragma no branch + self._teams_url = self._makeStringAttribute(attributes["teams_url"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "users" in attributes: # pragma no branch + self._users = self._makeListOfClassesAttribute(github.NamedUser.NamedUser, attributes["users"]) + if "users_url" in attributes: # pragma no branch + self._users_url = self._makeStringAttribute(attributes["users_url"]) + + class RequiredPullRequestReviews(CompletableGithubObject): """ This class represents Required Pull Request Reviews. @@ -69,12 +178,13 @@ class RequiredPullRequestReviews(CompletableGithubObject): """ def _initAttributes(self) -> None: + self._bypass_pull_request_allowances: Attribute[BypassPullRequestAllowances] = NotSet self._dismiss_stale_reviews: Attribute[bool] = NotSet + self._dismissal_restrictions: Attribute[DismissalRestrictions] = NotSet self._require_code_owner_reviews: Attribute[bool] = NotSet self._require_last_push_approval: Attribute[bool] = NotSet self._required_approving_review_count: Attribute[int] = NotSet - self._teams: Attribute[list[Team]] = NotSet - self._users: Attribute[list[NamedUser]] = NotSet + self._url: Attribute[str] = NotSet def __repr__(self) -> str: return self.get__repr__( @@ -86,20 +196,30 @@ def __repr__(self) -> str: } ) + @property + def bypass_pull_request_allowances(self) -> BypassPullRequestAllowances: + self._completeIfNotSet(self._bypass_pull_request_allowances) + return self._bypass_pull_request_allowances.value + @property def dismiss_stale_reviews(self) -> bool: self._completeIfNotSet(self._dismiss_stale_reviews) return self._dismiss_stale_reviews.value @property + def dismissal_restrictions(self) -> DismissalRestrictions: + self._completeIfNotSet(self._dismissal_restrictions) + return self._dismissal_restrictions.value + + @property + @deprecated.deprecated("Use dismissal_restrictions.teams") def dismissal_teams(self) -> list[Team]: - self._completeIfNotSet(self._teams) - return self._teams.value + return self.dismissal_restrictions.teams if self.dismissal_restrictions is not None else None @property + @deprecated.deprecated("Use dismissal_restrictions.users") def dismissal_users(self) -> list[NamedUser]: - self._completeIfNotSet(self._users) - return self._users.value + return self.dismissal_restrictions.users if self.dismissal_restrictions is not None else None @property def require_code_owner_reviews(self) -> bool: @@ -122,18 +242,16 @@ def url(self) -> str: return self._url.value def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "bypass_pull_request_allowances" in attributes: # pragma no branch + self._bypass_pull_request_allowances = self._makeClassAttribute( + BypassPullRequestAllowances, attributes["bypass_pull_request_allowances"] + ) if "dismiss_stale_reviews" in attributes: # pragma no branch self._dismiss_stale_reviews = self._makeBoolAttribute(attributes["dismiss_stale_reviews"]) if "dismissal_restrictions" in attributes: # pragma no branch - if "users" in attributes["dismissal_restrictions"]: - self._users = self._makeListOfClassesAttribute( - github.NamedUser.NamedUser, - attributes["dismissal_restrictions"]["users"], - ) - if "teams" in attributes["dismissal_restrictions"]: # pragma no branch - self._teams = self._makeListOfClassesAttribute( - github.Team.Team, attributes["dismissal_restrictions"]["teams"] - ) + self._dismissal_restrictions = self._makeClassAttribute( + DismissalRestrictions, attributes["dismissal_restrictions"] + ) if "require_code_owner_reviews" in attributes: # pragma no branch self._require_code_owner_reviews = self._makeBoolAttribute(attributes["require_code_owner_reviews"]) if "require_last_push_approval" in attributes: # pragma no branch diff --git a/tests/Branch.py b/tests/Branch.py index c44ba64ac2..e18de9d6a0 100644 --- a/tests/Branch.py +++ b/tests/Branch.py @@ -97,6 +97,10 @@ def testEditProtection(self): 2, ) self.assertTrue(branch_protection.required_pull_request_reviews.require_last_push_approval) + self.assertEqual( + branch_protection.required_pull_request_reviews.url, + "https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews", + ) def testEditProtectionDismissalUsersWithUserOwnedBranch(self): with self.assertRaises(github.GithubException) as raisedexp: diff --git a/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt b/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt index 31fe425e15..3ac6cf9015 100644 --- a/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt +++ b/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt @@ -18,4 +18,4 @@ None None 200 [('status', '200 OK'), ('x-runtime-rack', '0.049518'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"f972722557f6bbc814f109abae4df24e"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-ratelimit-remaining', '4996'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'C9A0:2DCE:4A1B5C:60E4A6:5AF302A0'), ('date', 'Wed, 09 May 2018 14:16:17 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1525878932')] -{"url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":true,"require_code_owner_reviews":true,"dismissal_restrictions":{"url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions","users_url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/users","teams_url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/teams","users":[{"owned_private_repos":5,"collaborators":0,"type":"User","hireable":false,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","public_gists":1,"company":"Criteo","bio":"","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","private_gists":5,"plan":{"collaborators":1,"private_repos":5,"name":"micro","space":614400},"public_repos":11,"followers":13,"login":"jacquev6","blog":"http://vincent-jacques.net","email":"vincent@vincent-jacques.net","disk_usage":16852,"html_url":"https://github.com/jacquev6","name":"Vincent Jacques","total_private_repos":5,"created_at":"2010-07-09T06:10:06Z","location":"Paris, France","id":327146,"following":24}],"teams":[{"name":"pygithub-owners","id":585225,"slug":"pygithub-owners","description":"","privacy":"closed","url":"https://api.github.com/teams/585225","members_url":"https://api.github.com/teams/585225/members{/member}","repositories_url":"https://api.github.com/teams/585225/repos","permission":"pull"}]}} +{"url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/required_pull_request_reviews","bypass_pull_request_allowances":{"users":[{"owned_private_repos":5,"collaborators":0,"type":"User","hireable":false,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","public_gists":1,"company":"Criteo","bio":"","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","private_gists":5,"plan":{"collaborators":1,"private_repos":5,"name":"micro","space":614400},"public_repos":11,"followers":13,"login":"jacquev6","blog":"http://vincent-jacques.net","email":"vincent@vincent-jacques.net","disk_usage":16852,"html_url":"https://github.com/jacquev6","name":"Vincent Jacques","total_private_repos":5,"created_at":"2010-07-09T06:10:06Z","location":"Paris, France","id":327146,"following":24}],"teams":[{"name":"pygithub-owners","id":585225,"slug":"pygithub-owners","description":"","privacy":"closed","url":"https://api.github.com/teams/585225","members_url":"https://api.github.com/teams/585225/members{/member}","repositories_url":"https://api.github.com/teams/585225/repos","permission":"pull"}]},"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"dismissal_restrictions":{"url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions","users_url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/users","teams_url":"https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/teams","users":[{"owned_private_repos":5,"collaborators":0,"type":"User","hireable":false,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","public_gists":1,"company":"Criteo","bio":"","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","private_gists":5,"plan":{"collaborators":1,"private_repos":5,"name":"micro","space":614400},"public_repos":11,"followers":13,"login":"jacquev6","blog":"http://vincent-jacques.net","email":"vincent@vincent-jacques.net","disk_usage":16852,"html_url":"https://github.com/jacquev6","name":"Vincent Jacques","total_private_repos":5,"created_at":"2010-07-09T06:10:06Z","location":"Paris, France","id":327146,"following":24}],"teams":[{"name":"pygithub-owners","id":585225,"slug":"pygithub-owners","description":"","privacy":"closed","url":"https://api.github.com/teams/585225","members_url":"https://api.github.com/teams/585225/members{/member}","repositories_url":"https://api.github.com/teams/585225/repos","permission":"pull"}]}} diff --git a/tests/RequiredPullRequestReviews.py b/tests/RequiredPullRequestReviews.py index d175448fae..6805c7551c 100644 --- a/tests/RequiredPullRequestReviews.py +++ b/tests/RequiredPullRequestReviews.py @@ -37,6 +37,8 @@ # # ################################################################################ +from __future__ import annotations + from . import Framework @@ -48,7 +50,9 @@ def setUp(self): ) def testAttributes(self): + self.assertIsNone(self.required_pull_request_reviews.bypass_pull_request_allowances) self.assertTrue(self.required_pull_request_reviews.dismiss_stale_reviews) + self.assertIsNone(self.required_pull_request_reviews.dismissal_restrictions) self.assertTrue(self.required_pull_request_reviews.require_code_owner_reviews) self.assertIsNone(self.required_pull_request_reviews.require_last_push_approval) self.assertEqual(self.required_pull_request_reviews.required_approving_review_count, 3) @@ -69,6 +73,38 @@ def testOrganizationOwnedTeam(self): .get_branch("integrations") .get_required_pull_request_reviews() ) + self.assertIsNone(required_pull_request_reviews.bypass_pull_request_allowances.apps) + self.assertListKeyEqual( + required_pull_request_reviews.bypass_pull_request_allowances.users, + lambda u: u.login, + ["jacquev6"], + ) + self.assertListKeyEqual( + required_pull_request_reviews.bypass_pull_request_allowances.teams, + lambda t: t.slug, + ["pygithub-owners"], + ) + + self.assertIsNone(required_pull_request_reviews.dismissal_restrictions.apps) + self.assertListKeyEqual( + required_pull_request_reviews.dismissal_restrictions.users, + lambda u: u.login, + ["jacquev6"], + ) + self.assertListKeyEqual( + required_pull_request_reviews.dismissal_restrictions.teams, + lambda t: t.slug, + ["pygithub-owners"], + ) + self.assertEqual( + required_pull_request_reviews.dismissal_restrictions.users_url, + "https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/users", + ) + self.assertEqual( + required_pull_request_reviews.dismissal_restrictions.teams_url, + "https://api.github.com/repos/PyGithub/PyGithub/branches/integrations/protection/dismissal_restrictions/teams", + ) + self.assertListKeyEqual( required_pull_request_reviews.dismissal_users, lambda u: u.login, From 0474507f8392f934050e8c8f9c5a04dfdf408857 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:44:46 +0100 Subject: [PATCH 12/15] Sync `RequiredStatusChecks` class with API spec (#3236) Adds the following attributes: - checks - contexts_url - enforcement_level --- github/RequiredStatusChecks.py | 61 ++++++++++++++++++- tests/Branch.py | 5 ++ .../ReplayData/RequiredStatusChecks.setUp.txt | 24 +------- tests/RequiredStatusChecks.py | 12 +++- 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/github/RequiredStatusChecks.py b/github/RequiredStatusChecks.py index 4e3ec0db62..a45ad602d3 100644 --- a/github/RequiredStatusChecks.py +++ b/github/RequiredStatusChecks.py @@ -43,7 +43,42 @@ from typing import Any -from github.GithubObject import Attribute, CompletableGithubObject, NotSet +from github.GithubObject import Attribute, CompletableGithubObject, NonCompletableGithubObject, NotSet + + +class Check(NonCompletableGithubObject): + """ + This class represents Check. + + The reference can be found here + https://docs.github.com/en/rest/reference/repos#get-status-checks-protection + + The OpenAPI schema can be found at + - /components/schemas/protected-branch-required-status-check/properties/checks/items + - /components/schemas/status-check-policy/properties/checks/items + + """ + + def _initAttributes(self) -> None: + self._app_id: Attribute[int] = NotSet + self._context: Attribute[str] = NotSet + + def __repr__(self) -> str: + return self.get__repr__({"app_id": self._app_id.value, "context": self._context.value}) + + @property + def app_id(self) -> int: + return self._app_id.value + + @property + def context(self) -> str: + return self._context.value + + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "app_id" in attributes: # pragma no branch + self._app_id = self._makeIntAttribute(attributes["app_id"]) + if "context" in attributes: # pragma no branch + self._context = self._makeStringAttribute(attributes["context"]) class RequiredStatusChecks(CompletableGithubObject): @@ -60,18 +95,36 @@ class RequiredStatusChecks(CompletableGithubObject): """ def _initAttributes(self) -> None: + self._checks: Attribute[list[Check]] = NotSet self._contexts: Attribute[list[str]] = NotSet + self._contexts_url: Attribute[str] = NotSet + self._enforcement_level: Attribute[str] = NotSet self._strict: Attribute[bool] = NotSet self._url: Attribute[str] = NotSet def __repr__(self) -> str: return self.get__repr__({"strict": self._strict.value, "url": self._url.value}) + @property + def checks(self) -> list[Check]: + self._completeIfNotSet(self._checks) + return self._checks.value + @property def contexts(self) -> list[str]: self._completeIfNotSet(self._contexts) return self._contexts.value + @property + def contexts_url(self) -> str: + self._completeIfNotSet(self._contexts_url) + return self._contexts_url.value + + @property + def enforcement_level(self) -> str: + self._completeIfNotSet(self._enforcement_level) + return self._enforcement_level.value + @property def strict(self) -> bool: self._completeIfNotSet(self._strict) @@ -83,8 +136,14 @@ def url(self) -> str: return self._url.value def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "checks" in attributes: # pragma no branch + self._checks = self._makeListOfClassesAttribute(Check, attributes["checks"]) if "contexts" in attributes: # pragma no branch self._contexts = self._makeListOfStringsAttribute(attributes["contexts"]) + if "contexts_url" in attributes: # pragma no branch + self._contexts_url = self._makeStringAttribute(attributes["contexts_url"]) + if "enforcement_level" in attributes: # pragma no branch + self._enforcement_level = self._makeStringAttribute(attributes["enforcement_level"]) if "strict" in attributes: # pragma no branch self._strict = self._makeBoolAttribute(attributes["strict"]) if "url" in attributes: # pragma no branch diff --git a/tests/Branch.py b/tests/Branch.py index e18de9d6a0..dcecb3a720 100644 --- a/tests/Branch.py +++ b/tests/Branch.py @@ -86,7 +86,12 @@ def testEditProtection(self): ) branch_protection = self.protected_branch.get_protection() self.assertTrue(branch_protection.required_status_checks.strict) + self.assertEqual(branch_protection.required_status_checks.checks, []) self.assertEqual(branch_protection.required_status_checks.contexts, []) + self.assertEqual( + branch_protection.required_status_checks.contexts_url, + "https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts", + ) self.assertTrue(branch_protection.enforce_admins) self.assertFalse(branch_protection.required_linear_history) self.assertFalse(branch_protection.allow_deletions) diff --git a/tests/ReplayData/RequiredStatusChecks.setUp.txt b/tests/ReplayData/RequiredStatusChecks.setUp.txt index 031dd22966..0bef599abb 100644 --- a/tests/ReplayData/RequiredStatusChecks.setUp.txt +++ b/tests/ReplayData/RequiredStatusChecks.setUp.txt @@ -1,25 +1,3 @@ -https -GET -api.github.com -None -/user -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} -None -200 -[('status', '200 OK'), ('x-ratelimit-remaining', '4967'), ('content-length', '801'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"27524c635501121933f4f78c95b1945a"'), ('date', 'Fri, 18 May 2012 20:12:19 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"owned_private_repos":5,"collaborators":0,"type":"User","hireable":false,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","public_gists":1,"company":"Criteo","bio":"","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","private_gists":5,"plan":{"collaborators":1,"private_repos":5,"name":"micro","space":614400},"public_repos":11,"followers":13,"login":"jacquev6","blog":"http://vincent-jacques.net","email":"vincent@vincent-jacques.net","disk_usage":16852,"html_url":"https://github.com/jacquev6","name":"Vincent Jacques","total_private_repos":5,"created_at":"2010-07-09T06:10:06Z","location":"Paris, France","id":327146,"following":24} - -https -GET -api.github.com -None -/repos/jacquev6/PyGithub -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} -None -200 -[('status', '200 OK'), ('x-ratelimit-remaining', '4966'), ('content-length', '1097'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"c5eec74d4b76b80283636a8efe1a132c"'), ('date', 'Fri, 18 May 2012 20:12:20 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"svn_url":"https://github.com/jacquev6/PyGithub","has_wiki":false,"has_issues":true,"updated_at":"2012-05-18T05:29:54Z","forks":2,"homepage":"http://vincent-jacques.net/PyGithub","git_url":"git://github.com/jacquev6/PyGithub.git","url":"https://api.github.com/repos/jacquev6/PyGithub","clone_url":"https://github.com/jacquev6/PyGithub.git","open_issues":17,"fork":false,"ssh_url":"git@github.com:jacquev6/PyGithub.git","pushed_at":"2012-05-18T05:18:16Z","size":304,"private":false,"has_downloads":true,"watchers":13,"html_url":"https://github.com/jacquev6/PyGithub","owner":{"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","login":"jacquev6","id":327146},"name":"PyGithub","permissions":{"pull":true,"admin":true,"push":true},"mirror_url":null,"language":"Python","description":"Python library implementing the full Github API v3","created_at":"2012-02-25T12:53:47Z","id":3544490} - https GET api.github.com @@ -40,4 +18,4 @@ None None 200 [('status', '200 OK'), ('x-runtime-rack', '0.049518'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"f972722557f6bbc814f109abae4df24e"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-ratelimit-remaining', '4996'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'C9A0:2DCE:4A1B5C:60E4A6:5AF302A0'), ('date', 'Wed, 09 May 2018 14:16:17 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1525878932')] -{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks","strict":true,"contexts":["foo/bar"],"contexts_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts"} +{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks","strict":true,"checks":[{"context":"continuous-integration/travis-ci","app_id":123}],"contexts":["foo/bar"],"contexts_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts","enforcement_level":"non_admins"} diff --git a/tests/RequiredStatusChecks.py b/tests/RequiredStatusChecks.py index 63264a25d5..787d2d877a 100644 --- a/tests/RequiredStatusChecks.py +++ b/tests/RequiredStatusChecks.py @@ -34,6 +34,8 @@ # # ################################################################################ +from __future__ import annotations + from . import Framework @@ -41,10 +43,18 @@ class RequiredStatusChecks(Framework.TestCase): def setUp(self): super().setUp() self.required_status_checks = ( - self.g.get_user().get_repo("PyGithub").get_branch("integrations").get_required_status_checks() + self.g.get_repo("jacquev6/PyGithub", lazy=True).get_branch("integrations").get_required_status_checks() ) def testAttributes(self): + self.assertEqual(len(self.required_status_checks.checks), 1) + self.assertEqual(self.required_status_checks.checks[0].context, "continuous-integration/travis-ci") + self.assertEqual(self.required_status_checks.checks[0].app_id, 123) + self.assertEqual( + self.required_status_checks.contexts_url, + "https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts", + ) + self.assertEqual(self.required_status_checks.enforcement_level, "non_admins") self.assertTrue(self.required_status_checks.strict) self.assertEqual(self.required_status_checks.contexts, ["foo/bar"]) self.assertEqual( From fa8f9dfeafb89440e461cdf55d361add839d9300 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 20:45:01 +0100 Subject: [PATCH 13/15] Sync `Team` class with API spec (#3237) Adds the following attributes: - group_id - sync_to_organizations --- github/Team.py | 16 ++++++++++++++++ tests/Team.py | 2 ++ 2 files changed, 18 insertions(+) diff --git a/github/Team.py b/github/Team.py index 1eda4f37c0..ecd291767f 100644 --- a/github/Team.py +++ b/github/Team.py @@ -108,6 +108,7 @@ class Team(CompletableGithubObject): def _initAttributes(self) -> None: self._created_at: Attribute[datetime] = NotSet self._description: Attribute[str] = NotSet + self._group_id: Attribute[int] = NotSet self._html_url: Attribute[str] = NotSet self._id: Attribute[int] = NotSet self._ldap_dn: Attribute[str] = NotSet @@ -124,6 +125,7 @@ def _initAttributes(self) -> None: self._repos_count: Attribute[int] = NotSet self._repositories_url: Attribute[str] = NotSet self._slug: Attribute[str] = NotSet + self._sync_to_organizations: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet self._url: Attribute[str] = NotSet @@ -144,6 +146,11 @@ def description(self) -> str: self._completeIfNotSet(self._description) return self._description.value + @property + def group_id(self) -> int: + self._completeIfNotSet(self._group_id) + return self._group_id.value + @property def html_url(self) -> str: self._completeIfNotSet(self._html_url) @@ -224,6 +231,11 @@ def slug(self) -> str: self._completeIfNotSet(self._slug) return self._slug.value + @property + def sync_to_organizations(self) -> str: + self._completeIfNotSet(self._sync_to_organizations) + return self._sync_to_organizations.value + @property def updated_at(self) -> datetime: self._completeIfNotSet(self._updated_at) @@ -484,6 +496,8 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) if "description" in attributes: # pragma no branch self._description = self._makeStringAttribute(attributes["description"]) + if "group_id" in attributes: # pragma no branch + self._group_id = self._makeIntAttribute(attributes["group_id"]) if "html_url" in attributes: self._html_url = self._makeStringAttribute(attributes["html_url"]) if "id" in attributes: # pragma no branch @@ -516,6 +530,8 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._repositories_url = self._makeStringAttribute(attributes["repositories_url"]) if "slug" in attributes: # pragma no branch self._slug = self._makeStringAttribute(attributes["slug"]) + if "sync_to_organizations" in attributes: # pragma no branch + self._sync_to_organizations = self._makeStringAttribute(attributes["sync_to_organizations"]) 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/tests/Team.py b/tests/Team.py index 34c4d8413c..15d5bbc7d0 100644 --- a/tests/Team.py +++ b/tests/Team.py @@ -66,6 +66,7 @@ def setUp(self): def testAttributes(self): self.assertEqual(self.team.created_at, datetime(2024, 6, 18, 10, 27, 23, tzinfo=timezone.utc)) self.assertEqual(self.team.description, "a team") + self.assertIsNone(self.team.group_id) self.assertEqual(self.team.html_url, "https://github.com/orgs/BeaverSoftware/teams/team-slug") self.assertEqual(self.team.id, 12345678) self.assertIsNone(self.team.ldap_dn) @@ -84,6 +85,7 @@ def testAttributes(self): self.assertEqual(self.team.repos_count, 0) self.assertEqual(self.team.repositories_url, "https://api.github.com/organizations/1234567/team/12345678/repos") self.assertEqual(self.team.slug, "team-slug") + self.assertIsNone(self.team.sync_to_organizations) self.assertEqual(self.team.updated_at, datetime(2024, 6, 18, 10, 27, 23, tzinfo=timezone.utc)) self.assertEqual(self.team.url, "https://api.github.com/organizations/1234567/team/12345678") self.assertEqual(self.team.organization, self.org) From 3602345aee8500067dbf21e4573e03312a045c75 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Mon, 24 Feb 2025 14:50:05 +0100 Subject: [PATCH 14/15] Move all Python files to future annotations (#3241) --- github/AppAuthentication.py | 8 +- github/Auth.py | 71 +++--- github/AuthorizationApplication.py | 5 +- github/Autolink.py | 5 +- github/CVSS.py | 5 +- github/CWE.py | 6 +- github/CheckRunOutput.py | 5 +- github/CodeScanAlertInstanceLocation.py | 5 +- github/CodeScanTool.py | 5 +- github/CommitStats.py | 5 +- github/Consts.py | 2 +- github/Enterprise.py | 5 +- github/EnterpriseConsumedLicenses.py | 7 +- github/EnvironmentDeploymentBranchPolicy.py | 5 +- github/File.py | 5 +- github/GistFile.py | 5 +- github/GitAuthor.py | 5 +- github/GitObject.py | 5 +- github/GitTreeElement.py | 5 +- github/GithubException.py | 39 +-- github/GithubRetry.py | 17 +- github/GitignoreTemplate.py | 5 +- github/HookResponse.py | 5 +- github/MergedUpstream.py | 6 +- github/NamedEnterpriseUser.py | 5 +- github/NotificationSubject.py | 6 +- github/OrganizationSecret.py | 7 +- github/OrganizationVariable.py | 7 +- github/PaginatedList.py | 63 ++--- github/Path.py | 5 +- github/Permissions.py | 5 +- github/Plan.py | 5 +- github/PullRequestMergeStatus.py | 5 +- github/Rate.py | 5 +- github/Referrer.py | 5 +- github/Requester.py | 255 ++++++++++---------- github/Secret.py | 5 +- github/SecurityAndAnalysisFeature.py | 5 +- github/StatsCommitActivity.py | 5 +- github/StatsPunchCard.py | 5 +- github/WorkflowStep.py | 5 +- tests/Framework.py | 6 +- tests/GraphQl.py | 5 +- 43 files changed, 336 insertions(+), 309 deletions(-) diff --git a/github/AppAuthentication.py b/github/AppAuthentication.py index 6b9c3b0e8a..59b64465d3 100644 --- a/github/AppAuthentication.py +++ b/github/AppAuthentication.py @@ -32,9 +32,7 @@ # along with PyGithub. If not, see . # # # ################################################################################ - - -from typing import Dict, Optional, Union +from __future__ import annotations import deprecated @@ -45,10 +43,10 @@ class AppAuthentication(AppInstallationAuth): def __init__( self, - app_id: Union[int, str], + app_id: int | str, private_key: str, installation_id: int, - token_permissions: Optional[Dict[str, str]] = None, + token_permissions: dict[str, str] | None = None, ): super().__init__( app_auth=AppAuth(app_id, private_key), diff --git a/github/Auth.py b/github/Auth.py index 7a71ec8ba1..ce881a3e12 100644 --- a/github/Auth.py +++ b/github/Auth.py @@ -26,13 +26,14 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import abc import base64 import time from abc import ABC from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, Callable, Dict, Optional, Union +from typing import TYPE_CHECKING, Callable, Union import jwt from requests import utils @@ -187,11 +188,11 @@ def token_type(self) -> str: class JwtSigner: - def __init__(self, private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str): + def __init__(self, private_key_or_func: 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]: + def jwt_sign(self, payload: dict) -> str | bytes: if callable(self._private_key_or_func): private_key = self._private_key_or_func() else: @@ -208,16 +209,16 @@ class AppAuth(JWT): """ @staticmethod - def create_jwt_sign(private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str) -> DictSignFunction: + def create_jwt_sign(private_key_or_func: str | PrivateKeyGenerator, jwt_algorithm: str) -> DictSignFunction: return JwtSigner(private_key_or_func, jwt_algorithm).jwt_sign # v3: move * above private_key def __init__( self, - app_id: Union[int, str], - private_key: Optional[Union[str, PrivateKeyGenerator]] = None, + app_id: int | str, + private_key: str | PrivateKeyGenerator | None = None, *, - sign_func: Optional[DictSignFunction] = None, + sign_func: DictSignFunction | None = None, jwt_expiry: int = Consts.DEFAULT_JWT_EXPIRY, jwt_issued_at: int = Consts.DEFAULT_JWT_ISSUED_AT, ): @@ -240,7 +241,7 @@ def __init__( self._jwt_issued_at = jwt_issued_at @property - def app_id(self) -> Union[int, str]: + def app_id(self) -> int | str: return self._app_id @property @@ -250,9 +251,9 @@ def token(self) -> str: def get_installation_auth( self, installation_id: int, - token_permissions: Optional[Dict[str, str]] = None, - requester: Optional[Requester] = None, - ) -> "AppInstallationAuth": + token_permissions: dict[str, str] | None = None, + requester: Requester | None = None, + ) -> AppInstallationAuth: """ Creates a github.Auth.AppInstallationAuth instance for an installation. @@ -264,7 +265,7 @@ def get_installation_auth( """ return AppInstallationAuth(self, installation_id, token_permissions, requester) - def create_jwt(self, expiration: Optional[int] = None) -> str: + def create_jwt(self, expiration: int | None = None) -> str: """ Create a signed JWT https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app @@ -316,15 +317,15 @@ class AppInstallationAuth(Auth, WithRequester["AppInstallationAuth"]): """ # used to fetch live access token when calling self.token - __integration: Optional["GithubIntegration"] = None - __installation_authorization: Optional[InstallationAuthorization] = None + __integration: GithubIntegration | None = None + __installation_authorization: InstallationAuthorization | None = None def __init__( self, app_auth: AppAuth, installation_id: int, - token_permissions: Optional[Dict[str, str]] = None, - requester: Optional[Requester] = None, + token_permissions: dict[str, str] | None = None, + requester: Requester | None = None, ): super().__init__() @@ -340,7 +341,7 @@ def __init__( if requester is not None: self.withRequester(requester) - def withRequester(self, requester: Requester) -> "AppInstallationAuth": + def withRequester(self, requester: Requester) -> AppInstallationAuth: assert isinstance(requester, Requester), requester super().withRequester(requester.withAuth(self._app_auth)) @@ -352,7 +353,7 @@ def withRequester(self, requester: Requester) -> "AppInstallationAuth": return self @property - def app_id(self) -> Union[int, str]: + def app_id(self) -> int | str: return self._app_auth.app_id @property @@ -360,7 +361,7 @@ def installation_id(self) -> int: return self._installation_id @property - def token_permissions(self) -> Optional[Dict[str, str]]: + def token_permissions(self) -> dict[str, str] | None: return self._token_permissions @property @@ -403,10 +404,10 @@ class AppUserAuth(Auth, WithRequester["AppUserAuth"]): _client_secret: str _token: str _type: str - _scope: Optional[str] - _expires_at: Optional[datetime] - _refresh_token: Optional[str] - _refresh_expires_at: Optional[datetime] + _scope: str | None + _expires_at: datetime | None + _refresh_token: str | None + _refresh_expires_at: datetime | None # imported here to avoid circular import from github.ApplicationOAuth import ApplicationOAuth @@ -418,11 +419,11 @@ def __init__( client_id: str, client_secret: str, token: str, - token_type: Optional[str] = None, - expires_at: Optional[datetime] = None, - refresh_token: Optional[str] = None, - refresh_expires_at: Optional[datetime] = None, - requester: Optional[Requester] = None, + token_type: str | None = None, + expires_at: datetime | None = None, + refresh_token: str | None = None, + refresh_expires_at: datetime | None = None, + requester: Requester | None = None, ) -> None: super().__init__() @@ -456,7 +457,7 @@ def token(self) -> str: self._refresh() return self._token - def withRequester(self, requester: Requester) -> "AppUserAuth": + def withRequester(self, requester: Requester) -> AppUserAuth: assert isinstance(requester, Requester), requester super().withRequester(requester.withAuth(None)) @@ -497,15 +498,15 @@ def _refresh(self) -> None: self._refresh_expires_at = token.refresh_expires_at @property - def expires_at(self) -> Optional[datetime]: + def expires_at(self) -> datetime | None: return self._expires_at @property - def refresh_token(self) -> Optional[str]: + def refresh_token(self) -> str | None: return self._refresh_token @property - def refresh_expires_at(self) -> Optional[datetime]: + def refresh_expires_at(self) -> datetime | None: return self._refresh_expires_at @property @@ -521,8 +522,8 @@ class NetrcAuth(HTTPBasicAuth, WithRequester["NetrcAuth"]): def __init__(self) -> None: super().__init__() - self._login: Optional[str] = None - self._password: Optional[str] = None + self._login: str | None = None + self._password: str | None = None @property def username(self) -> str: @@ -538,7 +539,7 @@ def password(self) -> str: assert self._password is not None, "Method withRequester(Requester) must be called first" return self._password - def withRequester(self, requester: Requester) -> "NetrcAuth": + def withRequester(self, requester: Requester) -> NetrcAuth: assert isinstance(requester, Requester), requester super().withRequester(requester) diff --git a/github/AuthorizationApplication.py b/github/AuthorizationApplication.py index afeaa97a38..012752544b 100644 --- a/github/AuthorizationApplication.py +++ b/github/AuthorizationApplication.py @@ -34,8 +34,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet @@ -62,7 +63,7 @@ def url(self) -> str: self._completeIfNotSet(self._url) return self._url.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "name" in attributes: # pragma no branch self._name = self._makeStringAttribute(attributes["name"]) if "url" in attributes: # pragma no branch diff --git a/github/Autolink.py b/github/Autolink.py index 27a6bea89c..0b67fa5784 100644 --- a/github/Autolink.py +++ b/github/Autolink.py @@ -38,8 +38,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -81,7 +82,7 @@ def key_prefix(self) -> str: def url_template(self) -> str: return self._url_template.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "id" in attributes: # pragma no branch self._id = self._makeIntAttribute(attributes["id"]) if "is_alphanumeric" in attributes: # pragma no branch diff --git a/github/CVSS.py b/github/CVSS.py index a9c37032c4..35f7e08743 100644 --- a/github/CVSS.py +++ b/github/CVSS.py @@ -38,9 +38,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from decimal import Decimal -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -71,7 +72,7 @@ def vector_string(self) -> str: def version(self) -> Decimal: return self._version.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "score" in attributes and attributes["score"] is not None: # pragma no branch # ensure string so we don't have all the float extra nonsense self._score = self._makeDecimalAttribute(Decimal(str(attributes["score"]))) diff --git a/github/CWE.py b/github/CWE.py index 173fba0322..5fe1c6cb9d 100644 --- a/github/CWE.py +++ b/github/CWE.py @@ -37,9 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations - -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet @@ -65,7 +65,7 @@ def cwe_id(self) -> str: def name(self) -> str: return self._name.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "cwe_id" in attributes: # pragma no branch self._cwe_id = self._makeStringAttribute(attributes["cwe_id"]) if "name" in attributes: # pragma no branch diff --git a/github/CheckRunOutput.py b/github/CheckRunOutput.py index f782573da7..1689a2f91e 100644 --- a/github/CheckRunOutput.py +++ b/github/CheckRunOutput.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -82,7 +83,7 @@ def text(self) -> str: def title(self) -> str: return self._title.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "annotations_count" in attributes: # pragma no branch self._annotations_count = self._makeIntAttribute(attributes["annotations_count"]) if "annotations_url" in attributes: # pragma no branch diff --git a/github/CodeScanAlertInstanceLocation.py b/github/CodeScanAlertInstanceLocation.py index 119982b6b5..ea7a652f9a 100644 --- a/github/CodeScanAlertInstanceLocation.py +++ b/github/CodeScanAlertInstanceLocation.py @@ -26,8 +26,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -82,7 +83,7 @@ def start_column(self) -> int: def start_line(self) -> int: return self._start_line.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "end_column" in attributes: # pragma no branch self._end_column = self._makeIntAttribute(attributes["end_column"]) if "end_line" in attributes: # pragma no branch diff --git a/github/CodeScanTool.py b/github/CodeScanTool.py index 40843b6169..16f1df2783 100644 --- a/github/CodeScanTool.py +++ b/github/CodeScanTool.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -78,7 +79,7 @@ def name(self) -> str: def version(self) -> str: return self._version.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "guid" in attributes: # pragma no branch self._guid = self._makeStringAttribute(attributes["guid"]) if "name" in attributes: # pragma no branch diff --git a/github/CommitStats.py b/github/CommitStats.py index 1b5a61ce65..5541cfacba 100644 --- a/github/CommitStats.py +++ b/github/CommitStats.py @@ -32,8 +32,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -65,7 +66,7 @@ def deletions(self) -> int: def total(self) -> int: return self._total.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "additions" in attributes: # pragma no branch self._additions = self._makeIntAttribute(attributes["additions"]) if "deletions" in attributes: # pragma no branch diff --git a/github/Consts.py b/github/Consts.py index da9f365ec4..197944c2d7 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -52,7 +52,7 @@ # along with PyGithub. If not, see . # # # ################################################################################ - +from __future__ import annotations REQ_IF_NONE_MATCH = "If-None-Match" REQ_IF_MODIFIED_SINCE = "If-Modified-Since" diff --git a/github/Enterprise.py b/github/Enterprise.py index d11b52137b..8184e95091 100644 --- a/github/Enterprise.py +++ b/github/Enterprise.py @@ -37,9 +37,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import urllib.parse -from typing import Any, Dict +from typing import Any from github.EnterpriseConsumedLicenses import EnterpriseConsumedLicenses from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -89,7 +90,7 @@ def get_consumed_licenses(self) -> EnterpriseConsumedLicenses: return EnterpriseConsumedLicenses(self._requester, headers, data, completed=True) - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "enterprise" in attributes: # pragma no branch self._enterprise = self._makeStringAttribute(attributes["enterprise"]) if "url" in attributes: # pragma no branch diff --git a/github/EnterpriseConsumedLicenses.py b/github/EnterpriseConsumedLicenses.py index f03e9f10c5..38f7cc29bd 100644 --- a/github/EnterpriseConsumedLicenses.py +++ b/github/EnterpriseConsumedLicenses.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet from github.NamedEnterpriseUser import NamedEnterpriseUser @@ -86,7 +87,7 @@ def get_users(self) -> PaginatedList[NamedEnterpriseUser]: :calls: `GET /enterprises/{enterprise}/consumed-licenses `_ """ - url_parameters: Dict[str, Any] = {} + url_parameters: dict[str, Any] = {} return PaginatedList( NamedEnterpriseUser, self._requester, @@ -98,7 +99,7 @@ def get_users(self) -> PaginatedList[NamedEnterpriseUser]: firstHeaders=self.raw_headers, ) - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "enterprise" in attributes: # pragma no branch self._enterprise = self._makeStringAttribute(attributes["enterprise"]) if "total_seats_consumed" in attributes: # pragma no branch diff --git a/github/EnvironmentDeploymentBranchPolicy.py b/github/EnvironmentDeploymentBranchPolicy.py index 55bade6132..5f3826856d 100644 --- a/github/EnvironmentDeploymentBranchPolicy.py +++ b/github/EnvironmentDeploymentBranchPolicy.py @@ -25,8 +25,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -55,7 +56,7 @@ def custom_branch_policies(self) -> bool: def protected_branches(self) -> bool: return self._protected_branches.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "custom_branch_policies" in attributes: # pragma no branch self._custom_branch_policies = self._makeBoolAttribute(attributes["custom_branch_policies"]) if "protected_branches" in attributes: # pragma no branch diff --git a/github/File.py b/github/File.py index aa4faff220..92b322157b 100644 --- a/github/File.py +++ b/github/File.py @@ -38,8 +38,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -113,7 +114,7 @@ def sha(self) -> str: def status(self) -> str: return self._status.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "additions" in attributes: # pragma no branch self._additions = self._makeIntAttribute(attributes["additions"]) if "blob_url" in attributes: # pragma no branch diff --git a/github/GistFile.py b/github/GistFile.py index 732e4ea15d..d9142aea0e 100644 --- a/github/GistFile.py +++ b/github/GistFile.py @@ -35,8 +35,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -87,7 +88,7 @@ def size(self) -> int: def type(self) -> str: return self._type.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "content" in attributes: # pragma no branch self._content = self._makeStringAttribute(attributes["content"]) if "filename" in attributes: # pragma no branch diff --git a/github/GitAuthor.py b/github/GitAuthor.py index 8a5c6aa7b8..d35b211460 100644 --- a/github/GitAuthor.py +++ b/github/GitAuthor.py @@ -35,9 +35,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -81,7 +82,7 @@ def email(self) -> str: def name(self) -> str: return self._name.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "date" in attributes: # pragma no branch self._date = self._makeDatetimeAttribute(attributes["date"]) if "email" in attributes: # pragma no branch diff --git a/github/GitObject.py b/github/GitObject.py index a02a11634c..7d2db327c4 100644 --- a/github/GitObject.py +++ b/github/GitObject.py @@ -35,8 +35,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -71,7 +72,7 @@ def type(self) -> str: def url(self) -> str: return self._url.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "sha" in attributes: # pragma no branch self._sha = self._makeStringAttribute(attributes["sha"]) if "type" in attributes: # pragma no branch diff --git a/github/GitTreeElement.py b/github/GitTreeElement.py index b2df181f7c..0a714c0d03 100644 --- a/github/GitTreeElement.py +++ b/github/GitTreeElement.py @@ -35,8 +35,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -85,7 +86,7 @@ def type(self) -> str: def url(self) -> str: return self._url.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "mode" in attributes: # pragma no branch self._mode = self._makeStringAttribute(attributes["mode"]) if "path" in attributes: # pragma no branch diff --git a/github/GithubException.py b/github/GithubException.py index d01e2d6493..966df22cc0 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -36,9 +36,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import json -from typing import Any, Dict, List, Optional, Tuple, Type, Union +from typing import Any class GithubException(Exception): @@ -54,8 +55,8 @@ def __init__( self, status: int, data: Any = None, - headers: Optional[Dict[str, str]] = None, - message: Optional[str] = None, + headers: dict[str, str] | None = None, + message: str | None = None, ): super().__init__() self.__status = status @@ -65,7 +66,7 @@ def __init__( self.args = (status, data, headers, message) @property - def message(self) -> Optional[str]: + def message(self) -> str | None: return self.__message @property @@ -83,7 +84,7 @@ def data(self) -> Any: return self.__data @property - def headers(self) -> Optional[Dict[str, str]]: + def headers(self) -> dict[str, str] | None: """ The headers returned by the Github API. """ @@ -138,13 +139,13 @@ class BadAttributeException(Exception): def __init__( self, actualValue: Any, - expectedType: Union[ - Dict[Tuple[Type[str], Type[str]], Type[dict]], - Tuple[Type[str], Type[str]], - List[Type[dict]], - List[Tuple[Type[str], Type[str]]], - ], - transformationException: Optional[Exception], + expectedType: ( + dict[tuple[type[str], type[str]], type[dict]] + | tuple[type[str], type[str]] + | list[type[dict]] + | list[tuple[type[str], type[str]]] + ), + transformationException: Exception | None, ): self.__actualValue = actualValue self.__expectedType = expectedType @@ -160,19 +161,19 @@ def actual_value(self) -> Any: @property def expected_type( self, - ) -> Union[ - List[Type[dict]], - Tuple[Type[str], Type[str]], - Dict[Tuple[Type[str], Type[str]], Type[dict]], - List[Tuple[Type[str], Type[str]]], - ]: + ) -> ( + list[type[dict]] + | tuple[type[str], type[str]] + | dict[tuple[type[str], type[str]], type[dict]] + | list[tuple[type[str], type[str]]] + ): """ The type PyGithub expected. """ return self.__expectedType @property - def transformation_exception(self) -> Optional[Exception]: + def transformation_exception(self) -> Exception | None: """ The exception raised when PyGithub tried to parse the value. """ diff --git a/github/GithubRetry.py b/github/GithubRetry.py index ae1f610168..aea4ad8f4b 100644 --- a/github/GithubRetry.py +++ b/github/GithubRetry.py @@ -25,13 +25,14 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import json import logging from datetime import datetime, timezone from logging import Logger from types import TracebackType -from typing import Any, Optional +from typing import Any from requests import Response from requests.models import CaseInsensitiveDict @@ -63,7 +64,7 @@ class GithubRetry(Retry): """ - __logger: Optional[Logger] = None + __logger: Logger | None = None # used to mock datetime, mock.patch("github.GithubRetry.date") does not work as this # references the class, not the module (due to re-exporting in github/__init__.py) @@ -88,12 +89,12 @@ def new(self, **kw: Any) -> Self: def increment( # type: ignore[override] self, - method: Optional[str] = None, - url: Optional[str] = None, - response: Optional[HTTPResponse] = None, # type: ignore[override] - error: Optional[Exception] = None, - _pool: Optional[ConnectionPool] = None, - _stacktrace: Optional[TracebackType] = None, + method: str | None = None, + url: str | None = None, + response: HTTPResponse | None = None, # type: ignore[override] + error: Exception | None = None, + _pool: ConnectionPool | None = None, + _stacktrace: TracebackType | None = None, ) -> Retry: if response: # we retry 403 only when there is a Retry-After header (indicating it is retry-able) diff --git a/github/GitignoreTemplate.py b/github/GitignoreTemplate.py index 8d11b569ba..640dc222b8 100644 --- a/github/GitignoreTemplate.py +++ b/github/GitignoreTemplate.py @@ -36,8 +36,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -69,7 +70,7 @@ def name(self) -> str: def source(self) -> str: return self._source.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "name" in attributes: # pragma no branch self._name = self._makeStringAttribute(attributes["name"]) if "source" in attributes: # pragma no branch diff --git a/github/HookResponse.py b/github/HookResponse.py index 4c117477d2..a526536078 100644 --- a/github/HookResponse.py +++ b/github/HookResponse.py @@ -35,8 +35,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -70,7 +71,7 @@ def message(self) -> str: def status(self) -> str: return self._status.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "code" in attributes: # pragma no branch self._code = self._makeIntAttribute(attributes["code"]) if "message" in attributes: # pragma no branch diff --git a/github/MergedUpstream.py b/github/MergedUpstream.py index d9790d6d9e..45b760a1c2 100644 --- a/github/MergedUpstream.py +++ b/github/MergedUpstream.py @@ -23,9 +23,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations - -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -59,7 +59,7 @@ def merge_type(self) -> str: def message(self) -> str: return self._message.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "base_branch" in attributes: # pragma no branch self._base_branch = self._makeStringAttribute(attributes["base_branch"]) if "merge_type" in attributes: # pragma no branch diff --git a/github/NamedEnterpriseUser.py b/github/NamedEnterpriseUser.py index f0f1100eba..e0f6d6a159 100644 --- a/github/NamedEnterpriseUser.py +++ b/github/NamedEnterpriseUser.py @@ -23,8 +23,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet @@ -151,7 +152,7 @@ def visual_studio_subscription_user(self) -> bool: self._completeIfNotSet(self._visual_studio_subscription_user) return self._visual_studio_subscription_user.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "enterprise_server_primary_emails" in attributes: # pragma no branch self._enterprise_server_primary_emails = self._makeListOfStringsAttribute( attributes["enterprise_server_primary_emails"] diff --git a/github/NotificationSubject.py b/github/NotificationSubject.py index 38ab68bd93..530b32de0d 100644 --- a/github/NotificationSubject.py +++ b/github/NotificationSubject.py @@ -38,9 +38,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations - -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -82,7 +82,7 @@ def type(self) -> str: def url(self) -> str: return self._url.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "latest_comment_url" in attributes: # pragma no branch self._latest_comment_url = self._makeStringAttribute(attributes["latest_comment_url"]) if "title" in attributes: # pragma no branch diff --git a/github/OrganizationSecret.py b/github/OrganizationSecret.py index 1a0cbd1d39..6fe20d4aa7 100644 --- a/github/OrganizationSecret.py +++ b/github/OrganizationSecret.py @@ -24,9 +24,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NotSet from github.PaginatedList import PaginatedList @@ -88,7 +89,7 @@ def edit( assert isinstance(visibility, str), visibility assert secret_type in ["actions", "dependabot"], "secret_type should be actions or dependabot" - patch_parameters: Dict[str, Any] = { + patch_parameters: dict[str, Any] = { "name": self.name, "value": value, "visibility": visibility, @@ -123,7 +124,7 @@ def remove_repo(self, repo: Repository) -> bool: self._requester.requestJsonAndCheck("DELETE", f"{self._selected_repositories_url.value}/{repo.id}") return True - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "created_at" in attributes: self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) if "name" in attributes: diff --git a/github/OrganizationVariable.py b/github/OrganizationVariable.py index 8abd430868..1be3c1ad5c 100644 --- a/github/OrganizationVariable.py +++ b/github/OrganizationVariable.py @@ -23,9 +23,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NotSet from github.PaginatedList import PaginatedList @@ -84,7 +85,7 @@ def edit( assert isinstance(value, str), value assert isinstance(visibility, str), visibility - patch_parameters: Dict[str, Any] = { + patch_parameters: dict[str, Any] = { "name": self.name, "value": value, "visibility": visibility, @@ -119,7 +120,7 @@ def remove_repo(self, repo: Repository) -> bool: self._requester.requestJsonAndCheck("DELETE", f"{self._selected_repositories_url.value}/{repo.id}") return True - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "created_at" in attributes: self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) if "name" in attributes: diff --git a/github/PaginatedList.py b/github/PaginatedList.py index cbbe4caae3..f1622eee85 100644 --- a/github/PaginatedList.py +++ b/github/PaginatedList.py @@ -48,8 +48,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, Type, TypeVar, Union +from typing import Any, Callable, Generic, Iterator, TypeVar from urllib.parse import parse_qs from github.GithubObject import GithubObject @@ -59,18 +60,18 @@ class PaginatedListBase(Generic[T]): - __elements: List[T] + __elements: list[T] def _couldGrow(self) -> bool: raise NotImplementedError - def _fetchNextPage(self) -> List[T]: + def _fetchNextPage(self) -> list[T]: raise NotImplementedError - def __init__(self, elements: Optional[List[T]] = None) -> None: + def __init__(self, elements: list[T] | None = None) -> None: self.__elements = [] if elements is None else elements - def __getitem__(self, index: Union[int, slice]) -> Any: + def __getitem__(self, index: int | slice) -> Any: assert isinstance(index, (int, slice)) if isinstance(index, int): self.__fetchToIndex(index) @@ -91,13 +92,13 @@ def __fetchToIndex(self, index: int) -> None: while len(self.__elements) <= index and self._couldGrow(): self._grow() - def _grow(self) -> List[T]: + def _grow(self) -> list[T]: newElements = self._fetchNextPage() self.__elements += newElements return newElements class _Slice: - def __init__(self, theList: "PaginatedListBase[T]", theSlice: slice): + def __init__(self, theList: PaginatedListBase[T], theSlice: slice): self.__list = theList self.__start = theSlice.start or 0 self.__stop = theSlice.stop @@ -152,19 +153,19 @@ class PaginatedList(PaginatedListBase[T]): # v3: move * before firstUrl and fix call sites def __init__( self, - contentClass: Type[T], + contentClass: type[T], requester: Requester, - firstUrl: Optional[str] = None, - firstParams: Optional[Dict[str, Any]] = None, + firstUrl: str | None = None, + firstParams: dict[str, Any] | None = None, *, - headers: Optional[Dict[str, str]] = None, - list_item: Union[str, List[str]] = "items", + headers: dict[str, str] | None = None, + list_item: str | list[str] = "items", total_count_item: str = "total_count", - firstData: Optional[Any] = None, - firstHeaders: Optional[Dict[str, Union[str, int]]] = None, - attributesTransformer: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None, - graphql_query: Optional[str] = None, - graphql_variables: Optional[Dict[str, Any]] = None, + firstData: Any | None = None, + firstHeaders: dict[str, str | int] | None = None, + attributesTransformer: Callable[[dict[str, Any]], dict[str, Any]] | None = None, + graphql_query: str | None = None, + graphql_variables: dict[str, Any] | None = None, ): if firstUrl is None and firstData is None and graphql_query is None: raise ValueError("Either firstUrl or graphql_query must be given") @@ -179,16 +180,16 @@ def __init__( self.__is_rest = firstUrl is not None or firstData is not None self.__firstUrl = firstUrl - self.__firstParams: Dict[str, Any] = firstParams or {} + self.__firstParams: dict[str, Any] = firstParams or {} self.__nextUrl = firstUrl - self.__nextParams: Dict[str, Any] = firstParams or {} + self.__nextParams: dict[str, Any] = firstParams or {} self.__headers = headers self.__list_item = list_item self.__total_count_item = total_count_item if self.__requester.per_page != 30: self.__nextParams["per_page"] = self.__requester.per_page self._reversed = False - self.__totalCount: Optional[int] = None + self.__totalCount: int | None = None self._attributesTransformer = attributesTransformer self.__graphql_query = graphql_query @@ -211,7 +212,7 @@ def is_rest(self) -> bool: def is_graphql(self) -> bool: return not self.is_rest - def _transformAttributes(self, element: Dict[str, Any]) -> Dict[str, Any]: + def _transformAttributes(self, element: dict[str, Any]) -> dict[str, Any]: if self._attributesTransformer is None: return element return self._attributesTransformer(element) @@ -256,7 +257,7 @@ def totalCount(self) -> int: self.__totalCount = pagination.get("totalCount") return self.__totalCount # type: ignore - def _getLastPageUrl(self) -> Optional[str]: + def _getLastPageUrl(self) -> str | None: headers, data = self.__requester.requestJsonAndCheck( "GET", self.__firstUrl, parameters=self.__nextParams, headers=self.__headers # type: ignore ) @@ -264,7 +265,7 @@ def _getLastPageUrl(self) -> Optional[str]: return links.get("last") @property - def reversed(self) -> "PaginatedList[T]": + def reversed(self) -> PaginatedList[T]: r = PaginatedList( self.__contentClass, self.__requester, @@ -307,14 +308,14 @@ def _couldGrow(self) -> bool: ) ) - def _get_graphql_pagination(self, data: Dict[str, Any], path: List[str]) -> Dict[str, Any]: + def _get_graphql_pagination(self, data: dict[str, Any], path: list[str]) -> dict[str, Any]: for item in path: if item not in data: raise RuntimeError(f"Pagination path {path} not found in data: {self.paths_of_dict(data)}") data = data[item] return data - def _fetchNextPage(self) -> List[T]: + def _fetchNextPage(self) -> list[T]: if self.is_rest: # REST API pagination headers, data = self.__requester.requestJsonAndCheck( @@ -339,7 +340,7 @@ def _fetchNextPage(self) -> List[T]: pagination = self._get_graphql_pagination(data["data"], self.__list_item) # type: ignore return self._getPage(pagination, {}) - def _getPage(self, data: Any, headers: Optional[Dict[str, Union[str, int]]]) -> List[T]: + def _getPage(self, data: Any, headers: dict[str, str | int] | None) -> list[T]: if self.is_rest: self.__nextUrl = None # type: ignore if len(data) > 0: @@ -387,7 +388,7 @@ def _getPage(self, data: Any, headers: Optional[Dict[str, Union[str, int]]]) -> nodes = nodes[::-1] return [self.__contentClass(self.__requester, {}, element) for element in nodes if element is not None] - def __parseLinkHeader(self, headers: Dict[str, Union[str, int]]) -> Dict[str, str]: + def __parseLinkHeader(self, headers: dict[str, str | int]) -> dict[str, str]: links = {} if "link" in headers and isinstance(headers["link"], str): linkHeaders = headers["link"].split(", ") @@ -398,7 +399,7 @@ def __parseLinkHeader(self, headers: Dict[str, Union[str, int]]) -> Dict[str, st links[rel] = url return links - def get_page(self, page: int) -> List[T]: + def get_page(self, page: int) -> list[T]: if self.is_graphql: raise RuntimeError("Not supported for GraphQL pagination") @@ -417,8 +418,8 @@ def get_page(self, page: int) -> List[T]: return [self.__contentClass(self.__requester, headers, self._transformAttributes(element)) for element in data] @classmethod - def override_attributes(cls, overrides: Dict[str, Any]) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def attributes_transformer(element: Dict[str, Any]) -> Dict[str, Any]: + def override_attributes(cls, overrides: dict[str, Any]) -> Callable[[dict[str, Any]], dict[str, Any]]: + def attributes_transformer(element: dict[str, Any]) -> dict[str, Any]: # Recursively merge overrides with attributes, overriding attributes with overrides element = cls.merge_dicts(element, overrides) return element @@ -426,7 +427,7 @@ def attributes_transformer(element: Dict[str, Any]) -> Dict[str, Any]: return attributes_transformer @classmethod - def merge_dicts(cls, d1: Dict[str, Any], d2: Dict[str, Any]) -> Dict[str, Any]: + def merge_dicts(cls, d1: dict[str, Any], d2: dict[str, Any]) -> dict[str, Any]: # clone d1 d1 = d1.copy() for k, v in d2.items(): diff --git a/github/Path.py b/github/Path.py index 07e036af09..93a84db45b 100644 --- a/github/Path.py +++ b/github/Path.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -87,7 +88,7 @@ def title(self) -> str: def uniques(self) -> int: return self._uniques.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "count" in attributes: # pragma no branch self._count = self._makeIntAttribute(attributes["count"]) if "path" in attributes: # pragma no branch diff --git a/github/Permissions.py b/github/Permissions.py index 2eceddf9d8..d3bd9f4593 100644 --- a/github/Permissions.py +++ b/github/Permissions.py @@ -36,8 +36,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -95,7 +96,7 @@ def push(self) -> bool: def triage(self) -> bool: return self._triage.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "admin" in attributes: # pragma no branch self._admin = self._makeBoolAttribute(attributes["admin"]) if "maintain" in attributes: # pragma no branch diff --git a/github/Plan.py b/github/Plan.py index e1f1a39d9b..9163a6d2da 100644 --- a/github/Plan.py +++ b/github/Plan.py @@ -36,8 +36,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -88,7 +89,7 @@ def seats(self) -> int: def space(self) -> int: return self._space.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "collaborators" in attributes: # pragma no branch self._collaborators = self._makeIntAttribute(attributes["collaborators"]) if "filled_seats" in attributes: # pragma no branch diff --git a/github/PullRequestMergeStatus.py b/github/PullRequestMergeStatus.py index 986985d31d..bf50476ac7 100644 --- a/github/PullRequestMergeStatus.py +++ b/github/PullRequestMergeStatus.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -72,7 +73,7 @@ def message(self) -> str: def sha(self) -> str: return self._sha.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "merged" in attributes: # pragma no branch self._merged = self._makeBoolAttribute(attributes["merged"]) if "message" in attributes: # pragma no branch diff --git a/github/Rate.py b/github/Rate.py index 6f634350bf..49bdc1e691 100644 --- a/github/Rate.py +++ b/github/Rate.py @@ -38,9 +38,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -88,7 +89,7 @@ def reset(self) -> datetime: def used(self) -> int: return self._used.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "limit" in attributes: # pragma no branch self._limit = self._makeIntAttribute(attributes["limit"]) if "remaining" in attributes: # pragma no branch diff --git a/github/Referrer.py b/github/Referrer.py index 268c32f3bb..747f379db9 100644 --- a/github/Referrer.py +++ b/github/Referrer.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -81,7 +82,7 @@ def referrer(self) -> str: def uniques(self) -> int: return self._uniques.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "count" in attributes: # pragma no branch self._count = self._makeIntAttribute(attributes["count"]) if "referrer" in attributes: # pragma no branch diff --git a/github/Requester.py b/github/Requester.py index b3e5eb554c..a76cf8532e 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -78,6 +78,7 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import io import json @@ -98,16 +99,10 @@ BinaryIO, Callable, Deque, - Dict, Generic, ItemsView, Iterator, - List, - Optional, - Tuple, - Type, TypeVar, - Union, ) import requests @@ -145,7 +140,7 @@ def getheaders(self) -> ItemsView[str, str]: def read(self) -> str: return self.response.text - def iter_content(self, chunk_size: Union[int, None] = 1) -> Iterator: + def iter_content(self, chunk_size: int | None = 1) -> Iterator: return self.response.iter_content(chunk_size=chunk_size) def raise_for_status(self) -> None: @@ -153,17 +148,17 @@ def raise_for_status(self) -> None: class HTTPSRequestsConnectionClass: - retry: Union[int, Retry] + retry: int | Retry # mimic the httplib connection object def __init__( self, host: str, - port: Optional[int] = None, + port: int | None = None, strict: bool = False, - timeout: Optional[int] = None, - retry: Optional[Union[int, Retry]] = None, - pool_size: Optional[int] = None, + timeout: int | None = None, + retry: int | Retry | None = None, + pool_size: int | None = None, **kwargs: Any, ) -> None: self.port = port if port else 443 @@ -198,8 +193,8 @@ def request( self, verb: str, url: str, - input: Optional[Union[str, io.BufferedReader]], - headers: Dict[str, str], + input: str | io.BufferedReader | None, + headers: dict[str, str], stream: bool = False, ) -> None: self.verb = verb @@ -230,11 +225,11 @@ class HTTPRequestsConnectionClass: def __init__( self, host: str, - port: Optional[int] = None, + port: int | None = None, strict: bool = False, - timeout: Optional[int] = None, - retry: Optional[Union[int, Retry]] = None, - pool_size: Optional[int] = None, + timeout: int | None = None, + retry: int | Retry | None = None, + pool_size: int | None = None, **kwargs: Any, ): self.port = port if port else 80 @@ -265,7 +260,7 @@ def __init__( ) self.session.mount("http://", self.adapter) - def request(self, verb: str, url: str, input: None, headers: Dict[str, str], stream: bool = False) -> None: + def request(self, verb: str, url: str, input: None, headers: dict[str, str], stream: bool = False) -> None: self.verb = verb self.url = url self.input = input @@ -290,15 +285,15 @@ def close(self) -> None: class Requester: - __installation_authorization: Optional["InstallationAuthorization"] - __app_auth: Optional["AppAuthentication"] + __installation_authorization: InstallationAuthorization | None + __app_auth: AppAuthentication | None __httpConnectionClass = HTTPRequestsConnectionClass __httpsConnectionClass = HTTPSRequestsConnectionClass __persist = True - __logger: Optional[logging.Logger] = None + __logger: logging.Logger | None = None - _frameBuffer: List[Any] + _frameBuffer: list[Any] @staticmethod def noopAuth(request: requests.models.PreparedRequest) -> requests.models.PreparedRequest: @@ -307,8 +302,8 @@ def noopAuth(request: requests.models.PreparedRequest) -> requests.models.Prepar @classmethod def injectConnectionClasses( cls, - httpConnectionClass: Type[HTTPRequestsConnectionClass], - httpsConnectionClass: Type[HTTPSRequestsConnectionClass], + httpConnectionClass: type[HTTPRequestsConnectionClass], + httpsConnectionClass: type[HTTPSRequestsConnectionClass], ) -> None: cls.__persist = False cls.__httpConnectionClass = httpConnectionClass @@ -344,9 +339,9 @@ def setOnCheckMe(cls, onCheckMe: Callable) -> None: DEBUG_HEADER_KEY = "DEBUG_FRAME" - ON_CHECK_ME: Optional[Callable] = None + ON_CHECK_ME: Callable | None = None - def NEW_DEBUG_FRAME(self, requestHeader: Dict[str, str]) -> None: + def NEW_DEBUG_FRAME(self, requestHeader: dict[str, str]) -> None: """ Initialize a debug frame with requestHeader Frame count is updated and will be attached to respond header @@ -362,7 +357,7 @@ def NEW_DEBUG_FRAME(self, requestHeader: Dict[str, str]) -> None: self._frameCount = len(self._frameBuffer) - 1 - def DEBUG_ON_RESPONSE(self, statusCode: int, responseHeader: Dict[str, Union[str, int]], data: str) -> None: + def DEBUG_ON_RESPONSE(self, statusCode: int, responseHeader: dict[str, str | int], data: str) -> None: """ Update current frame with response Current frame index will be attached to responseHeader. """ @@ -374,7 +369,7 @@ def DEBUG_ON_RESPONSE(self, statusCode: int, responseHeader: Dict[str, Union[str ] responseHeader[self.DEBUG_HEADER_KEY] = self._frameCount - def check_me(self, obj: "GithubObject") -> None: + def check_me(self, obj: GithubObject) -> None: if self.DEBUG_FLAG and self.ON_CHECK_ME is not None: # pragma no branch (Flag always set in tests) frame = None if self.DEBUG_HEADER_KEY in obj._headers: @@ -389,25 +384,25 @@ def _initializeDebugFeature(self) -> None: ############################################################# _frameCount: int - __connectionClass: Union[Type[HTTPRequestsConnectionClass], Type[HTTPSRequestsConnectionClass]] + __connectionClass: type[HTTPRequestsConnectionClass] | type[HTTPSRequestsConnectionClass] __hostname: str - __authorizationHeader: Optional[str] - __seconds_between_requests: Optional[float] - __seconds_between_writes: Optional[float] + __authorizationHeader: str | None + __seconds_between_requests: float | None + __seconds_between_writes: float | None # keep arguments in-sync with github.MainClass and GithubIntegration def __init__( self, - auth: Optional["Auth"], + auth: Auth | None, base_url: str, timeout: int, user_agent: str, per_page: int, - verify: Union[bool, str], - retry: Optional[Union[int, Retry]], - pool_size: Optional[int], - seconds_between_requests: Optional[float] = None, - seconds_between_writes: Optional[float] = None, + verify: bool | str, + retry: int | Retry | None, + pool_size: int | None, + seconds_between_requests: float | None = None, + seconds_between_writes: float | None = None, lazy: bool = False, ): self._initializeDebugFeature() @@ -426,7 +421,7 @@ def __init__( self.__pool_size = pool_size self.__seconds_between_requests = seconds_between_requests self.__seconds_between_writes = seconds_between_writes - self.__last_requests: Dict[str, float] = dict() + self.__last_requests: dict[str, float] = dict() self.__scheme = o.scheme if o.scheme == "https": self.__connectionClass = self.__httpsConnectionClass @@ -434,9 +429,9 @@ def __init__( self.__connectionClass = self.__httpConnectionClass else: assert False, "Unknown URL scheme" - self.__connection: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None + self.__connection: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None self.__connection_lock = threading.Lock() - self.__custom_connections: Deque[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = deque() + self.__custom_connections: Deque[HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass] = deque() self.rate_limiting = (-1, -1) self.rate_limiting_resettime = 0 self.FIX_REPO_GET_GIT_REF = True @@ -458,7 +453,7 @@ def __init__( if isinstance(self.__auth, WithRequester): self.__auth.withRequester(self) - def __getstate__(self) -> Dict[str, Any]: + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() # __connection_lock is not picklable del state["_Requester__connection_lock"] @@ -468,7 +463,7 @@ def __getstate__(self) -> Dict[str, Any]: del state["_Requester__custom_connections"] return state - def __setstate__(self, state: Dict[str, Any]) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) self.__connection_lock = threading.Lock() self.__connection = None @@ -482,7 +477,7 @@ def remove_suffix(string: str, suffix: str) -> str: return string @staticmethod - def get_graphql_prefix(path: Optional[str]) -> str: + def get_graphql_prefix(path: str | None) -> str: if path is None or path in ["", "/"]: path = "" if path.endswith(("/v3", "/v3/")): @@ -491,14 +486,14 @@ def get_graphql_prefix(path: Optional[str]) -> str: return path + "/graphql" @staticmethod - def get_parameters_of_url(url: str) -> Dict[str, list]: + def get_parameters_of_url(url: str) -> dict[str, list]: query = urllib.parse.urlparse(url)[4] return urllib.parse.parse_qs(query) @staticmethod def add_parameters_to_url( url: str, - parameters: Dict[str, Any], + parameters: dict[str, Any], ) -> str: scheme, netloc, url, params, query, fragment = urllib.parse.urlparse(url) url_params = urllib.parse.parse_qs(query) @@ -525,7 +520,7 @@ def close(self) -> None: self.__custom_connections.popleft().close() @property - def kwargs(self) -> Dict[str, Any]: + def kwargs(self) -> dict[str, Any]: """ Returns arguments required to recreate this Requester with Requester.__init__, as well as with MainClass.__init__ and GithubIntegration.__init__. @@ -567,10 +562,10 @@ def hostname_and_port(self) -> str: return f"{self.hostname}:{self.__port}" @property - def auth(self) -> Optional["Auth"]: + def auth(self) -> Auth | None: return self.__auth - def withAuth(self, auth: Optional["Auth"]) -> "Requester": + def withAuth(self, auth: Auth | None) -> Requester: """ Create a new requester instance with identical configuration but the given authentication method. @@ -590,7 +585,7 @@ def is_lazy(self) -> bool: def is_not_lazy(self) -> bool: return not self.__lazy - def withLazy(self, lazy: bool) -> "Requester": + def withLazy(self, lazy: bool) -> Requester: """ Create a new requester instance with identical configuration but the given lazy setting. @@ -607,11 +602,11 @@ def requestJsonAndCheck( self, verb: str, url: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - input: Optional[Any] = None, + parameters: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + input: Any | None = None, follow_302_redirect: bool = False, - ) -> Tuple[Dict[str, Any], Any]: + ) -> tuple[dict[str, Any], Any]: """ Send a request with JSON body. @@ -637,10 +632,10 @@ def requestMultipartAndCheck( self, verb: str, url: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - input: Optional[Dict[str, str]] = None, - ) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]: + parameters: dict[str, Any] | None = None, + headers: dict[str, Any] | None = None, + input: dict[str, str] | None = None, + ) -> tuple[dict[str, Any], dict[str, Any] | None]: """ Send a request with multi-part-encoded body. @@ -656,11 +651,11 @@ def requestBlobAndCheck( self, verb: str, url: str, - parameters: Optional[Dict[str, str]] = None, - headers: Optional[Dict[str, str]] = None, - input: Optional[str] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - ) -> Tuple[Dict[str, Any], Dict[str, Any]]: + parameters: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + input: str | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + ) -> tuple[dict[str, Any], dict[str, Any]]: """ Send a request with a file for the body. @@ -672,7 +667,7 @@ def requestBlobAndCheck( """ return self.__check(*self.requestBlob(verb, url, parameters, headers, input, self.__customConnection(url))) - def graphql_query(self, query: str, variables: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]: + def graphql_query(self, query: str, variables: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]: """ :calls: `POST /graphql `_ """ @@ -692,7 +687,7 @@ def paths_of_dict(cls, d: dict) -> dict: return {key: cls.paths_of_dict(val) if isinstance(val, dict) else None for key, val in d.items()} def data_as_class( - self, headers: Dict[str, Any], data: Dict[str, Any], data_path: List[str], klass: Type[T_gh] + self, headers: dict[str, Any], data: dict[str, Any], data_path: list[str], klass: type[T_gh] ) -> T_gh: for item in data_path: if item not in data: @@ -702,7 +697,7 @@ def data_as_class( data = as_rest_api_attributes(data) return klass(self, headers, data) - def graphql_node(self, node_id: str, graphql_schema: str, node_type: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: + def graphql_node(self, node_id: str, graphql_schema: str, node_type: str) -> tuple[dict[str, Any], dict[str, Any]]: """ :calls: `POST /graphql `_ """ @@ -732,7 +727,7 @@ def graphql_node(self, node_id: str, graphql_schema: str, node_type: str) -> Tup return headers, data def graphql_node_class( - self, node_id: str, graphql_schema: str, klass: Type[T_gh], node_type: Optional[str] = None + self, node_id: str, graphql_schema: str, klass: type[T_gh], node_type: str | None = None ) -> T_gh: """ :calls: `POST /graphql `_ @@ -744,7 +739,7 @@ def graphql_node_class( return self.data_as_class(headers, data, ["data", "node"], klass) def graphql_query_class( - self, query: str, variables: Dict[str, Any], data_path: List[str], klass: Type[T_gh] + self, query: str, variables: dict[str, Any], data_path: list[str], klass: type[T_gh] ) -> T_gh: """ :calls: `POST /graphql `_ @@ -753,8 +748,8 @@ def graphql_query_class( return self.data_as_class(headers, data, ["data"] + data_path, klass) def graphql_named_mutation( - self, mutation_name: str, mutation_input: Dict[str, Any], output_schema: str - ) -> Tuple[Dict[str, Any], Dict[str, Any]]: + self, mutation_name: str, mutation_input: dict[str, Any], output_schema: str + ) -> tuple[dict[str, Any], dict[str, Any]]: """ Create a mutation in the format: mutation Mutation($input: MutationNameInput!) { @@ -770,7 +765,7 @@ def graphql_named_mutation( return headers, data.get("data", {}).get(mutation_name, {}) def graphql_named_mutation_class( - self, mutation_name: str, mutation_input: Dict[str, Any], output_schema: str, item: str, klass: Type[T_gh] + self, mutation_name: str, mutation_input: dict[str, Any], output_schema: str, item: str, klass: type[T_gh] ) -> T_gh: """ Executes a mutation and returns the output object as the given GithubObject. @@ -784,18 +779,16 @@ def graphql_named_mutation_class( def __check( self, status: int, - responseHeaders: Dict[str, Any], + responseHeaders: dict[str, Any], output: str, - ) -> Tuple[Dict[str, Any], Any]: + ) -> tuple[dict[str, Any], Any]: data = self.__structuredFromJson(output) if status >= 400: raise self.createException(status, responseHeaders, data) return responseHeaders, data - def __customConnection( - self, url: str - ) -> Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]]: - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None + def __customConnection(self, url: str) -> HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None: + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None if not url.startswith("/"): o = urllib.parse.urlparse(url) if ( @@ -825,8 +818,8 @@ def __customConnection( def createException( cls, status: int, - headers: Dict[str, Any], - output: Dict[str, Any], + headers: dict[str, Any], + output: dict[str, Any], ) -> GithubException.GithubException: message = output.get("message") if output else None lc_message = message.lower() if message else "" @@ -890,10 +883,10 @@ def getFile( self, url: str, path: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - chunk_size: Optional[Union[int, None]] = 1, + parameters: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + chunk_size: int | None | None = 1, ) -> None: """ GET a file from the server and save it to the given path, which includes the filename. @@ -907,11 +900,11 @@ def getFile( def getStream( self, url: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - chunk_size: Optional[Union[int, None]] = 1, - ) -> Tuple[int, Dict[str, Any], Iterator]: + parameters: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + chunk_size: int | None | None = 1, + ) -> tuple[int, dict[str, Any], Iterator]: """ GET a stream from the server. @@ -922,7 +915,7 @@ def getStream( headers = {} headers["Accept"] = "application/octet-stream" - def encode(_: Any) -> Tuple[str, str]: + def encode(_: Any) -> tuple[str, str]: return "", "" status, responseHeaders, output = self.__requestEncode( @@ -939,12 +932,12 @@ def requestJson( self, verb: str, url: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - input: Optional[Any] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, + parameters: dict[str, Any] | None = None, + headers: dict[str, Any] | None = None, + input: Any | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, follow_302_redirect: bool = False, - ) -> Tuple[int, Dict[str, Any], str]: + ) -> tuple[int, dict[str, Any], str]: """ Send a request with JSON input. @@ -953,7 +946,7 @@ def requestJson( """ - def encode(input: Any) -> Tuple[str, str]: + def encode(input: Any) -> tuple[str, str]: return "application/json", json.dumps(input) status, responseHeaders, output = self.__requestEncode( @@ -967,11 +960,11 @@ def requestMultipart( self, verb: str, url: str, - parameters: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - input: Optional[Dict[str, str]] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - ) -> Tuple[int, Dict[str, Any], str]: + parameters: dict[str, Any] | None = None, + headers: dict[str, Any] | None = None, + input: dict[str, str] | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + ) -> tuple[int, dict[str, Any], str]: """ Send a request with multi-part encoding. @@ -980,7 +973,7 @@ def requestMultipart( """ - def encode(input: Dict[str, Any]) -> Tuple[str, str]: + def encode(input: dict[str, Any]) -> tuple[str, str]: boundary = "----------------------------3c3ba8b523b2" eol = "\r\n" @@ -1002,11 +995,11 @@ def requestBlob( self, verb: str, url: str, - parameters: Optional[Dict[str, str]] = None, - headers: Optional[Dict[str, str]] = None, - input: Optional[str] = None, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - ) -> Tuple[int, Dict[str, Any], str]: + parameters: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + input: str | None = None, + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + ) -> tuple[int, dict[str, Any], str]: """ Send a request with a file as request body. @@ -1017,7 +1010,7 @@ def requestBlob( if headers is None: headers = {} - def encode(local_path: str) -> Tuple[str, Any]: + def encode(local_path: str) -> tuple[str, Any]: if "Content-Type" in headers: # type: ignore mime_type = headers["Content-Type"] # type: ignore else: @@ -1039,10 +1032,10 @@ def requestMemoryBlobAndCheck( verb: str, url: str, parameters: Any, - headers: Dict[str, Any], + headers: dict[str, Any], file_like: BinaryIO, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None, - ) -> Tuple[Dict[str, Any], Any]: + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None = None, + ) -> tuple[dict[str, Any], Any]: """ Send a request with a binary file-like for the body. @@ -1053,7 +1046,7 @@ def requestMemoryBlobAndCheck( """ # The expected signature of encode means that the argument is ignored. - def encode(_: Any) -> Tuple[str, Any]: + def encode(_: Any) -> tuple[str, Any]: return headers["Content-Type"], file_like if not cnx: @@ -1066,16 +1059,16 @@ def encode(_: Any) -> Tuple[str, Any]: def __requestEncode( self, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]], + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None, verb: str, url: str, - parameters: Optional[Dict[str, str]], - requestHeaders: Optional[Dict[str, str]], - input: Optional[T], - encode: Callable[[T], Tuple[str, Any]], + parameters: dict[str, str] | None, + requestHeaders: dict[str, str] | None, + input: T | None, + encode: Callable[[T], tuple[str, Any]], stream: bool = False, follow_302_redirect: bool = False, - ) -> Tuple[int, Dict[str, Any], Union[str, object]]: + ) -> tuple[int, dict[str, Any], str | object]: assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] if parameters is None: parameters = {} @@ -1118,14 +1111,14 @@ def __requestEncode( def __requestRaw( self, - cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]], + cnx: HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass | None, verb: str, url: str, - requestHeaders: Dict[str, str], - input: Optional[Any], + requestHeaders: dict[str, str], + input: Any | None, stream: bool = False, follow_302_redirect: bool = False, - ) -> Tuple[int, Dict[str, Any], Union[str, object]]: + ) -> tuple[int, dict[str, Any], str | object]: self.__deferRequest(verb) try: @@ -1240,8 +1233,8 @@ def __makeAbsoluteUrl(self, url: str) -> str: return url def __createConnection( - self, hostname: Optional[str] = None - ) -> Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]: + self, hostname: str | None = None + ) -> HTTPRequestsConnectionClass | HTTPSRequestsConnectionClass: if self.__persist and self.__connection is not None and hostname is not None and hostname == self.__hostname: return self.__connection @@ -1273,11 +1266,11 @@ def __log( self, verb: str, url: str, - requestHeaders: Dict[str, str], - input: Optional[Any], - status: Optional[int], - responseHeaders: Dict[str, Any], - output: Optional[Union[str, object]], + requestHeaders: dict[str, str], + input: Any | None, + status: int | None, + responseHeaders: dict[str, Any], + output: str | object | None, ) -> None: if self._logger.isEnabledFor(logging.DEBUG): headersForRequest = requestHeaders.copy() @@ -1305,13 +1298,13 @@ class WithRequester(Generic[T]): __requester: Requester def __init__(self) -> None: - self.__requester: Optional[Requester] = None # type: ignore + self.__requester: Requester | None = None # type: ignore @property def requester(self) -> Requester: return self.__requester - def withRequester(self, requester: Requester) -> "WithRequester[T]": + def withRequester(self, requester: Requester) -> WithRequester[T]: assert isinstance(requester, Requester), requester self.__requester = requester return self diff --git a/github/Secret.py b/github/Secret.py index 82901aa4f4..8b732e61b0 100644 --- a/github/Secret.py +++ b/github/Secret.py @@ -37,9 +37,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet @@ -111,7 +112,7 @@ def delete(self) -> None: """ self._requester.requestJsonAndCheck("DELETE", self.url) - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "created_at" in attributes: self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) if "name" in attributes: diff --git a/github/SecurityAndAnalysisFeature.py b/github/SecurityAndAnalysisFeature.py index 6ca84191a3..b5e329f824 100644 --- a/github/SecurityAndAnalysisFeature.py +++ b/github/SecurityAndAnalysisFeature.py @@ -36,8 +36,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet @@ -65,6 +66,6 @@ def __repr__(self) -> str: def status(self) -> str: return self._status.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "status" in attributes: # pragma no branch self._status = self._makeStringAttribute(attributes["status"]) diff --git a/github/StatsCommitActivity.py b/github/StatsCommitActivity.py index 63ece53fb8..9e7d2541f9 100755 --- a/github/StatsCommitActivity.py +++ b/github/StatsCommitActivity.py @@ -36,9 +36,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any import github.GithubObject from github.GithubObject import Attribute @@ -73,7 +74,7 @@ def total(self) -> int: def week(self) -> datetime: return self._week.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "days" in attributes: # pragma no branch self._days = self._makeListOfIntsAttribute(attributes["days"]) if "total" in attributes: # pragma no branch diff --git a/github/StatsPunchCard.py b/github/StatsPunchCard.py index 97f0fe9069..928519650c 100755 --- a/github/StatsPunchCard.py +++ b/github/StatsPunchCard.py @@ -37,8 +37,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict, Tuple +from typing import Any import github.GithubObject import github.NamedUser # TODO remove unused @@ -56,7 +57,7 @@ class StatsPunchCard(github.GithubObject.NonCompletableGithubObject): """ - _dict: Dict[Tuple[int, int], int] + _dict: dict[tuple[int, int], int] def _initAttributes(self) -> None: self._dict = {} diff --git a/github/WorkflowStep.py b/github/WorkflowStep.py index 7bde2a9684..1f149c302a 100644 --- a/github/WorkflowStep.py +++ b/github/WorkflowStep.py @@ -37,9 +37,10 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations from datetime import datetime -from typing import Any, Dict +from typing import Any from github.GithubObject import Attribute, CompletableGithubObject, NotSet @@ -94,7 +95,7 @@ def status(self) -> str: self._completeIfNotSet(self._status) return self._status.value - def _useAttributes(self, attributes: Dict[str, Any]) -> None: + def _useAttributes(self, attributes: dict[str, Any]) -> None: if "completed_at" in attributes: # pragma no branch self._completed_at = self._makeDatetimeAttribute(attributes["completed_at"]) if "conclusion" in attributes: # pragma no branch diff --git a/tests/Framework.py b/tests/Framework.py index 4430f56d28..2925ac5e5a 100644 --- a/tests/Framework.py +++ b/tests/Framework.py @@ -54,6 +54,7 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations import base64 import contextlib @@ -64,7 +65,6 @@ import unittest import warnings from io import BytesIO -from typing import Optional import responses from requests.structures import CaseInsensitiveDict @@ -347,8 +347,8 @@ class BasicTestCase(unittest.TestCase): per_page = Consts.DEFAULT_PER_PAGE retry = None pool_size = None - seconds_between_requests: Optional[float] = None - seconds_between_writes: Optional[float] = None + seconds_between_requests: float | None = None + seconds_between_writes: float | None = None replayDataFolder = os.path.join(os.path.dirname(__file__), "ReplayData") def setUp(self): diff --git a/tests/GraphQl.py b/tests/GraphQl.py index e87eeb963e..efc6ae3272 100644 --- a/tests/GraphQl.py +++ b/tests/GraphQl.py @@ -20,8 +20,9 @@ # along with PyGithub. If not, see . # # # ################################################################################ +from __future__ import annotations -from typing import Any, Dict +from typing import Any import github import github.GithubException @@ -39,7 +40,7 @@ class GraphQl(Framework.TestCase): def setUp(self): super().setUp() - def expected(self, base_url: str = "https://github.com") -> Dict[Any, Any]: + def expected(self, base_url: str = "https://github.com") -> dict[Any, Any]: return { "actor": { "avatarUrl": "https://avatars.githubusercontent.com/u/14806300?u=786f9f8ef8782d45381b01580f7f7783cf9c7e37&v=4", From 8aa27d4bbeaf7dd7e8ce2928cca5716c0673b58d Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Mon, 24 Feb 2025 15:20:43 +0100 Subject: [PATCH 15/15] Fix return type of `PaginatedList[int]` (#3240) --- github/PaginatedList.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/github/PaginatedList.py b/github/PaginatedList.py index f1622eee85..2c918fee43 100644 --- a/github/PaginatedList.py +++ b/github/PaginatedList.py @@ -50,7 +50,7 @@ ################################################################################ from __future__ import annotations -from typing import Any, Callable, Generic, Iterator, TypeVar +from typing import Any, Callable, Generic, Iterator, TypeVar, overload from urllib.parse import parse_qs from github.GithubObject import GithubObject @@ -71,7 +71,15 @@ def _fetchNextPage(self) -> list[T]: def __init__(self, elements: list[T] | None = None) -> None: self.__elements = [] if elements is None else elements - def __getitem__(self, index: int | slice) -> Any: + @overload + def __getitem__(self, index: int) -> T: + ... + + @overload + def __getitem__(self, index: slice) -> _Slice: + ... + + def __getitem__(self, index: int | slice) -> T | _Slice: assert isinstance(index, (int, slice)) if isinstance(index, int): self.__fetchToIndex(index) @@ -108,7 +116,7 @@ def __iter__(self) -> Iterator[T]: index = self.__start while not self.__finished(index): if self.__list._isBiggerThan(index): - yield self.__list[index] + yield self.__list[index] # type: ignore index += self.__step else: return