From f5dc1c762ff2fa7d643a62d6358983da72f66ee4 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:12:57 +0100 Subject: [PATCH 1/7] Mention removal of `AppAuth.private_key` in changelog (#3212) Fixes #3210. --- doc/changes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changes.rst b/doc/changes.rst index 406456db11..f1dd182196 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -29,6 +29,8 @@ should be replaced with repo.get_views_traffic().views.timestamp repo.get_clones_traffic().clones.timestamp +* Property ``AppAuth.private_key`` has been removed (`#3065 `_) (`36697b22 `_) + * Fix typos (`#3086 `_) (`a50ae51b `_): Property ``OrganizationCustomProperty.respository_id`` renamed to ``OrganizationCustomProperty.repository_id``. From f975552acd0a745b717523a52730214647d3d696 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:14:17 +0100 Subject: [PATCH 2/7] Fix broken pickle support for `Auth` classes (#3211) With #3065 we broke pickling as it introduced a lambda into an `Auth` instance. This adds tests for all `Auth` implementations to work with pickle. Fixes #3208. --- github/Auth.py | 22 ++++++++++++++-------- tests/Pickle.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/github/Auth.py b/github/Auth.py index c80f7aacfc..7a71ec8ba1 100644 --- a/github/Auth.py +++ b/github/Auth.py @@ -186,6 +186,19 @@ def token_type(self) -> str: return "Bearer" +class JwtSigner: + def __init__(self, private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str): + self._private_key_or_func = private_key_or_func + self._jwt_algorithm = jwt_algorithm + + def jwt_sign(self, payload: dict) -> Union[str, bytes]: + if callable(self._private_key_or_func): + private_key = self._private_key_or_func() + else: + private_key = self._private_key_or_func + return jwt.encode(payload, key=private_key, algorithm=self._jwt_algorithm) + + class AppAuth(JWT): """ This class is used to authenticate as a GitHub App. @@ -196,14 +209,7 @@ class AppAuth(JWT): @staticmethod def create_jwt_sign(private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str) -> DictSignFunction: - def jwt_sign(payload: dict) -> Union[str, bytes]: - if callable(private_key_or_func): - private_key = private_key_or_func() - else: - private_key = private_key_or_func - return jwt.encode(payload, key=private_key, algorithm=jwt_algorithm) - - return jwt_sign + return JwtSigner(private_key_or_func, jwt_algorithm).jwt_sign # v3: move * above private_key def __init__( diff --git a/tests/Pickle.py b/tests/Pickle.py index 613516f1cd..2d28f98ffa 100644 --- a/tests/Pickle.py +++ b/tests/Pickle.py @@ -24,9 +24,13 @@ # # ################################################################################ +import inspect import pickle +import sys +from abc import ABC import github +from github.Auth import AppAuth, AppAuthToken, AppInstallationAuth, AppUserAuth, Auth, Login, NetrcAuth, Token from github.PaginatedList import PaginatedList from github.Repository import Repository @@ -59,3 +63,30 @@ def testPicklePaginatedList(self): branches = repo.get_branches() branches2 = pickle.loads(pickle.dumps(branches)) self.assertIsInstance(branches2, PaginatedList) + + auths = [ + Login("login", "password"), + Token("token"), + AppAuth("id", "key"), + AppAuthToken("token"), + AppInstallationAuth(AppAuth("id", "key"), 123), + AppUserAuth("client_id", "client_secret", "access_token"), + NetrcAuth(), + ] + + def testPickleAuthSetup(self): + # check we are testing *all* exiting auth classes + auth_module = sys.modules[github.Auth.__name__] + existing_auths = [ + clazz_type.__name__ + for clazz, clazz_type in inspect.getmembers(auth_module, inspect.isclass) + if Auth in clazz_type.mro() and ABC not in clazz_type.__bases__ + ] + tested_auths = [type(auth).__name__ for auth in self.auths] + self.assertSequenceEqual(sorted(existing_auths), sorted(tested_auths)) + + def testPickleAuth(self): + for auth in self.auths: + with self.subTest(auth=type(auth).__name__): + auth2 = pickle.loads(pickle.dumps(auth)) + self.assertIsInstance(auth2, type(auth)) From d12e7d4cb42b7e55812dbedaabb0642a9baf6e50 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:16:46 +0100 Subject: [PATCH 3/7] 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 93297440ce7911b8b32203287efb223c56384faa Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:17:45 +0100 Subject: [PATCH 4/7] 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 048a1a3837e8ff4936ee547cd516ebf91613aa73 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:57:19 +0100 Subject: [PATCH 5/7] 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 f1dd182196..c5b7b5dcc6 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -29,6 +29,24 @@ should be replaced with 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 f997a2f65308fb720503c7bda24a8859dad81e03 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 14:03:56 +0100 Subject: [PATCH 6/7] 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 fbbca673ac..105597548c 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 @@ -1820,7 +1822,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 `_ """ @@ -1830,7 +1834,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 da30d6e793ffb4fbe70383b59d2eb7026fe2d8c7 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 14:44:15 +0100 Subject: [PATCH 7/7] Releasing v2.6.1 (#3230) --- doc/changes.rst | 15 +++++++++++++++ github/CodeSecurityConfigRepository.py | 4 ++++ tests/GitCommitVerification.py | 1 + 3 files changed, 20 insertions(+) diff --git a/doc/changes.rst b/doc/changes.rst index c5b7b5dcc6..561fc33952 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -4,6 +4,21 @@ Change log Stable versions ~~~~~~~~~~~~~~~ +Version 2.6.1 (February 21, 2025) +--------------------------------- + +Bug Fixes +^^^^^^^^^ +* Fix broken pickle support for ``Auth`` classes (`#3211 `_) (`f975552a `_) +* Remove schema from ``Deployment``, remove ``message`` attribute (`#3223 `_) (`d12e7d4c `_) +* Fix incorrect deprecated import (`#3225 `_) (`93297440 `_) +* Add ``CodeSecurityConfigRepository`` returned by ``get_repos_for_code_security_config`` (`#3219 `_) (`f997a2f6 `_) +* Make ``GitTag.verification`` return ``GitCommitVerification`` (`#3226 `_) (`048a1a38 `_) + +Maintenance +^^^^^^^^^^^ +* Mention removal of ``AppAuth.private_key`` in changelog (`#3212 `_) (`f5dc1c76 `_) + Version 2.6.0 (February 15, 2025) --------------------------------- diff --git a/github/CodeSecurityConfigRepository.py b/github/CodeSecurityConfigRepository.py index 4f69431a2f..e799b1d0fb 100644 --- a/github/CodeSecurityConfigRepository.py +++ b/github/CodeSecurityConfigRepository.py @@ -1,5 +1,9 @@ ############################ Copyrights and license ############################ # # +# Copyright 2024 Enrico Minack # +# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> # +# Copyright 2024 Thomas Cooper # +# Copyright 2025 Enrico Minack # # # # This file is part of PyGithub. # # http://pygithub.readthedocs.io/ # diff --git a/tests/GitCommitVerification.py b/tests/GitCommitVerification.py index 54faaaead5..8eadd0e9db 100644 --- a/tests/GitCommitVerification.py +++ b/tests/GitCommitVerification.py @@ -2,6 +2,7 @@ # # # Copyright 2021 Claire Johns <42869556+johnsc1@users.noreply.github.com> # # Copyright 2023 Enrico Minack # +# Copyright 2025 Enrico Minack # # Copyright 2025 Tim Gates # # # # This file is part of PyGithub. #