From f4d586b4325ec38e1b98b561129477a405a78086 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Tue, 18 Feb 2025 20:37:06 +0100 Subject: [PATCH 01/15] Add and apply missing schemas (#3209) Applies `api.github.com.2022-11-28.json` API spec. --- github/AuthenticatedUser.py | 11 +++++ github/CodeSecurityConfig.py | 27 ++++++++-- github/CopilotSeat.py | 77 ++++++++++++++++------------- github/DefaultCodeSecurityConfig.py | 3 ++ github/GitCommitVerification.py | 36 +++++++------- github/NamedUser.py | 1 + github/Organization.py | 1 + github/Permissions.py | 1 + github/RepoCodeSecurityConfig.py | 3 ++ github/Repository.py | 1 + github/Team.py | 1 + tests/GitCommitVerification.py | 11 +++++ 12 files changed, 120 insertions(+), 53 deletions(-) diff --git a/github/AuthenticatedUser.py b/github/AuthenticatedUser.py index e1334b61f4..84280a727f 100644 --- a/github/AuthenticatedUser.py +++ b/github/AuthenticatedUser.py @@ -126,6 +126,17 @@ class EmailData(NamedTuple): + """ + This class represents EmailData. + + The reference can be found here + http://docs.github.com/en/rest/reference/users#emails + + The OpenAPI schema can be found at + - /components/schemas/email + + """ + email: str primary: bool verified: bool diff --git a/github/CodeSecurityConfig.py b/github/CodeSecurityConfig.py index cefca6d264..fe28310c7e 100644 --- a/github/CodeSecurityConfig.py +++ b/github/CodeSecurityConfig.py @@ -36,11 +36,12 @@ class CodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-configuration + """ def _initAttributes(self) -> None: - self._id: Attribute[int] = NotSet - self._name: Attribute[str] = NotSet self._advanced_security: Attribute[str] = NotSet self._code_scanning_default_setup: Attribute[str] = NotSet self._created_at: Attribute[datetime] = NotSet @@ -48,18 +49,22 @@ def _initAttributes(self) -> None: self._dependabot_security_updates: Attribute[str] = NotSet self._dependency_graph: Attribute[str] = NotSet self._dependency_graph_autosubmit_action: Attribute[str] = NotSet + self._dependency_graph_autosubmit_action_options: Attribute[dict[str, Any]] = NotSet self._description: Attribute[str] = NotSet self._enforcement: Attribute[str] = NotSet self._html_url: Attribute[str] = NotSet + self._id: Attribute[int] = NotSet + self._name: Attribute[str] = NotSet self._private_vulnerability_reporting: Attribute[str] = NotSet self._secret_scanning: Attribute[str] = NotSet self._secret_scanning_delegated_bypass: Attribute[str] = NotSet + self._secret_scanning_delegated_bypass_options: Attribute[dict[str, Any]] = NotSet self._secret_scanning_non_provider_patterns: Attribute[str] = NotSet self._secret_scanning_push_protection: Attribute[str] = NotSet self._secret_scanning_validity_checks: Attribute[str] = NotSet self._target_type: Attribute[str] = NotSet - self._url: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet + self._url: Attribute[str] = NotSet def __repr__(self) -> str: return self.get__repr__( @@ -98,6 +103,10 @@ def dependency_graph(self) -> str: def dependency_graph_autosubmit_action(self) -> str: return self._dependency_graph_autosubmit_action.value + @property + def dependency_graph_autosubmit_action_options(self) -> dict[str, Any]: + return self._dependency_graph_autosubmit_action_options.value + @property def description(self) -> str: return self._description.value @@ -130,6 +139,10 @@ def secret_scanning(self) -> str: def secret_scanning_delegated_bypass(self) -> str: return self._secret_scanning_delegated_bypass.value + @property + def secret_scanning_delegated_bypass_options(self) -> dict[str, Any]: + return self._secret_scanning_delegated_bypass_options.value + @property def secret_scanning_non_provider_patterns(self) -> str: return self._secret_scanning_non_provider_patterns.value @@ -174,6 +187,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._dependency_graph_autosubmit_action = self._makeStringAttribute( attributes["dependency_graph_autosubmit_action"] ) + if "dependency_graph_autosubmit_action_options" in attributes: # pragma no branch + self._dependency_graph_autosubmit_action_options = self._makeDictAttribute( + attributes["dependency_graph_autosubmit_action_options"] + ) if "description" in attributes: # pragma no branch self._description = self._makeStringAttribute(attributes["description"]) if "enforcement" in attributes: # pragma no branch @@ -194,6 +211,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._secret_scanning_delegated_bypass = self._makeStringAttribute( attributes["secret_scanning_delegated_bypass"] ) + if "secret_scanning_delegated_bypass_options" in attributes: # pragma no branch + self._secret_scanning_delegated_bypass_options = self._makeDictAttribute( + attributes["secret_scanning_delegated_bypass_options"] + ) if "secret_scanning_non_provider_patterns" in attributes: # pragma no branch self._secret_scanning_non_provider_patterns = self._makeStringAttribute( attributes["secret_scanning_non_provider_patterns"] diff --git a/github/CopilotSeat.py b/github/CopilotSeat.py index ef2158c669..6cb93c2215 100644 --- a/github/CopilotSeat.py +++ b/github/CopilotSeat.py @@ -32,48 +32,41 @@ class CopilotSeat(NonCompletableGithubObject): + """ + This class represents CopilotSeat. + + The reference can be found here + https://docs.github.com/en/rest/copilot/copilot-user-management + + The OpenAPI schema can be found at + - /components/schemas/copilot-seat-details + + """ + def _initAttributes(self) -> None: + self._assignee: Attribute[github.NamedUser.NamedUser] | _NotSetType = NotSet + self._assigning_team: Attribute[github.Team.Team] | _NotSetType = NotSet self._created_at: Attribute[datetime] | _NotSetType = NotSet - self._updated_at: Attribute[datetime] | _NotSetType = NotSet - self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet self._last_activity_at: Attribute[datetime] | _NotSetType = NotSet self._last_activity_editor: Attribute[str] | _NotSetType = NotSet + self._pending_cancellation_date: Attribute[datetime] | _NotSetType = NotSet self._plan_type: Attribute[str] | _NotSetType = NotSet - self._assignee: Attribute[github.NamedUser.NamedUser] | _NotSetType = NotSet - self._assigning_team: Attribute[github.Team.Team] | _NotSetType = NotSet - - def _useAttributes(self, attributes: dict[str, Any]) -> None: - if "created_at" in attributes: - self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) - if "updated_at" in attributes: - self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) - if "pending_cancellation_date" in attributes: - self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) - if "last_activity_at" in attributes: - self._last_activity_at = self._makeDatetimeAttribute(attributes["last_activity_at"]) - if "last_activity_editor" in attributes: - self._last_activity_editor = self._makeStringAttribute(attributes["last_activity_editor"]) - if "plan_type" in attributes: - self._plan_type = self._makeStringAttribute(attributes["plan_type"]) - if "assignee" in attributes: - self._assignee = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["assignee"]) - if "assigning_team" in attributes: - self._assigning_team = self._makeClassAttribute(github.Team.Team, attributes["assigning_team"]) + self._updated_at: Attribute[datetime] | _NotSetType = NotSet def __repr__(self) -> str: return self.get__repr__({"assignee": self._assignee.value}) @property - def created_at(self) -> datetime: - return self._created_at.value + def assignee(self) -> github.NamedUser.NamedUser: + return self._assignee.value @property - def updated_at(self) -> datetime: - return self._updated_at.value + def assigning_team(self) -> github.Team.Team: + return self._assigning_team.value @property - def pending_cancellation_date(self) -> datetime: - return self._pending_cancellation_date.value + def created_at(self) -> datetime: + return self._created_at.value @property def last_activity_at(self) -> datetime: @@ -83,14 +76,32 @@ def last_activity_at(self) -> datetime: def last_activity_editor(self) -> str: return self._last_activity_editor.value + @property + def pending_cancellation_date(self) -> datetime: + return self._pending_cancellation_date.value + @property def plan_type(self) -> str: return self._plan_type.value @property - def assignee(self) -> github.NamedUser.NamedUser: - return self._assignee.value + def updated_at(self) -> datetime: + return self._updated_at.value - @property - def assigning_team(self) -> github.Team.Team: - return self._assigning_team.value + def _useAttributes(self, attributes: dict[str, Any]) -> None: + if "assignee" in attributes: + self._assignee = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["assignee"]) + if "assigning_team" in attributes: + self._assigning_team = self._makeClassAttribute(github.Team.Team, attributes["assigning_team"]) + if "created_at" in attributes: + self._created_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "last_activity_at" in attributes: + self._last_activity_at = self._makeDatetimeAttribute(attributes["last_activity_at"]) + if "last_activity_editor" in attributes: + self._last_activity_editor = self._makeStringAttribute(attributes["last_activity_editor"]) + if "pending_cancellation_date" in attributes: + self._pending_cancellation_date = self._makeDatetimeAttribute(attributes["pending_cancellation_date"]) + if "plan_type" in attributes: + self._plan_type = self._makeStringAttribute(attributes["plan_type"]) + if "updated_at" in attributes: + self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) diff --git a/github/DefaultCodeSecurityConfig.py b/github/DefaultCodeSecurityConfig.py index dabdb71335..e01e62b2fa 100644 --- a/github/DefaultCodeSecurityConfig.py +++ b/github/DefaultCodeSecurityConfig.py @@ -54,6 +54,9 @@ class DefaultCodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-default-configurations + """ def _initAttributes(self) -> None: diff --git a/github/GitCommitVerification.py b/github/GitCommitVerification.py index 24015bd813..8da952e2de 100644 --- a/github/GitCommitVerification.py +++ b/github/GitCommitVerification.py @@ -55,24 +55,22 @@ class GitCommitVerification(NonCompletableGithubObject): https://docs.github.com/en/rest/commits/commits The OpenAPI schema can be found at + - /components/schemas/file-commit/properties/commit/properties/verification - /components/schemas/git-commit/properties/verification + - /components/schemas/verification """ def _initAttributes(self) -> None: - self._verified: Attribute[bool] = NotSet - self._verified_at: Attribute[datetime] = NotSet + self._payload: Attribute[str] = NotSet self._reason: Attribute[str] = NotSet self._signature: Attribute[str] = NotSet - self._payload: Attribute[str] = NotSet - - @property - def verified(self) -> bool: - return self._verified.value + self._verified: Attribute[bool] = NotSet + self._verified_at: Attribute[datetime] = NotSet @property - def verified_at(self) -> datetime: - return self._verified_at.value + def payload(self) -> str: + return self._payload.value @property def reason(self) -> str: @@ -83,17 +81,21 @@ def signature(self) -> str: return self._signature.value @property - def payload(self) -> str: - return self._payload.value + def verified(self) -> bool: + return self._verified.value + + @property + def verified_at(self) -> datetime: + return self._verified_at.value def _useAttributes(self, attributes: dict[str, Any]) -> None: - if "verified" in attributes: # pragma no branch - self._verified = self._makeBoolAttribute(attributes["verified"]) - if "verified_at" in attributes: # pragma no branch - self._verified_at = self._makeDatetimeAttribute(attributes["verified_at"]) + if "payload" in attributes: # pragma no branch + self._payload = self._makeStringAttribute(attributes["payload"]) if "reason" in attributes: # pragma no branch self._reason = self._makeStringAttribute(attributes["reason"]) if "signature" in attributes: # pragma no branch self._signature = self._makeStringAttribute(attributes["signature"]) - if "payload" in attributes: # pragma no branch - self._payload = self._makeStringAttribute(attributes["payload"]) + if "verified" in attributes: # pragma no branch + self._verified = self._makeBoolAttribute(attributes["verified"]) + if "verified_at" in attributes: # pragma no branch + self._verified_at = self._makeDatetimeAttribute(attributes["verified_at"]) diff --git a/github/NamedUser.py b/github/NamedUser.py index b7df34f075..6a1dfaaeea 100644 --- a/github/NamedUser.py +++ b/github/NamedUser.py @@ -94,6 +94,7 @@ class NamedUser(github.GithubObject.CompletableGithubObject): - /components/schemas/actor - /components/schemas/collaborator - /components/schemas/contributor + - /components/schemas/empty-object - /components/schemas/nullable-simple-user - /components/schemas/public-user - /components/schemas/simple-user diff --git a/github/Organization.py b/github/Organization.py index 1bde04d269..3713f05ab3 100644 --- a/github/Organization.py +++ b/github/Organization.py @@ -152,6 +152,7 @@ class Organization(CompletableGithubObject): The OpenAPI schema can be found at - /components/schemas/actor + - /components/schemas/nullable-simple-user - /components/schemas/organization-full - /components/schemas/organization-simple - /components/schemas/team-organization diff --git a/github/Permissions.py b/github/Permissions.py index 4abba20551..2eceddf9d8 100644 --- a/github/Permissions.py +++ b/github/Permissions.py @@ -50,6 +50,7 @@ class Permissions(NonCompletableGithubObject): - /components/schemas/collaborator/properties/permissions - /components/schemas/full-repository/properties/permissions - /components/schemas/minimal-repository/properties/permissions + - /components/schemas/nullable-repository/properties/permissions - /components/schemas/repo-search-result-item/properties/permissions - /components/schemas/repository/properties/permissions - /components/schemas/team/properties/permissions diff --git a/github/RepoCodeSecurityConfig.py b/github/RepoCodeSecurityConfig.py index 29c1c7e1cb..99bd395ea7 100644 --- a/github/RepoCodeSecurityConfig.py +++ b/github/RepoCodeSecurityConfig.py @@ -57,6 +57,9 @@ class RepoCodeSecurityConfig(NonCompletableGithubObject): The reference can be found here https://docs.github.com/en/rest/code-security/configurations. + The OpenAPI schema can be found at + - /components/schemas/code-security-configuration-for-repository + """ def _initAttributes(self) -> None: diff --git a/github/Repository.py b/github/Repository.py index 5032fbf564..3c6846a6f7 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -333,6 +333,7 @@ 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 diff --git a/github/Team.py b/github/Team.py index b59a45b26b..1eda4f37c0 100644 --- a/github/Team.py +++ b/github/Team.py @@ -97,6 +97,7 @@ class Team(CompletableGithubObject): https://docs.github.com/en/rest/reference/teams The OpenAPI schema can be found at + - /components/schemas/enterprise-team - /components/schemas/nullable-team-simple - /components/schemas/team - /components/schemas/team-full diff --git a/tests/GitCommitVerification.py b/tests/GitCommitVerification.py index 54faaaead5..34572dbf99 100644 --- a/tests/GitCommitVerification.py +++ b/tests/GitCommitVerification.py @@ -22,6 +22,8 @@ # # ################################################################################ +from __future__ import annotations + from . import Framework @@ -34,6 +36,15 @@ def setUp(self): def testAttributes(self): verification = self.commit.commit.verification + self.assertEqual( + verification.payload, + "tree 4f502b4c4e5f0bdc7ee611f914bbdef51aef5efa\nparent 85087354078e426125dbbf88041bbaa6f35d8199\nauthor Tim Gates 1724830907 +1000\ncommitter Tim Gates 1724830915 +1000\n\nCommit verification support\n\nAdd support for verification component of Commit API response to see if\ncommit has been signed and the signature has been checked by Github\n", + ) + self.assertEqual(verification.reason, "valid") + self.assertEqual( + verification.signature, + "-----BEGIN PGP SIGNATURE-----\n\niMgEAAEKADIWIQRbWfY9TlELdM/5UaauO+DVOCPPBQUCZs7U0RQcdGltLmdhdGVz\nQGlyZXNzLmNvbQAKCRCuO+DVOCPPBQ2tBACAaRNONEWlDDNyYkAnIv8bZ55BuIuy\nTvbxVPjI8KLDqKLzgHO60HhQ3h/hCiug6g5fvVzyIrmayj3eEzaWAfa3+f37f3xK\nflZRMtNFUBYQoLTuyTkKvW85UA2AkUvKp3bHT5W6ZoCKqR5xw6pwKxuYQi7eJG9h\nNPrZTezkL6EDng==\n=Ng9G\n-----END PGP SIGNATURE-----", + ) self.assertEqual(verification.verified, True) self.assertIsNone(verification.verified_at) self.assertEqual(verification.reason, "valid") From 6b77787a832e5dba3c61008c961612bb81e8c314 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Tue, 18 Feb 2025 21:49:37 +0100 Subject: [PATCH 02/15] Sync `RepositoryAdvisory` tests with OpenAPI spec (#3215) --- tests/RepositoryAdvisory.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/RepositoryAdvisory.py b/tests/RepositoryAdvisory.py index 4e4fed0b8e..fd1972683e 100644 --- a/tests/RepositoryAdvisory.py +++ b/tests/RepositoryAdvisory.py @@ -46,6 +46,7 @@ def testAttributes(self): self.assertEqual(self.advisory.author.login, "JLLeitschuh") self.assertEqual(self.advisory.closed_at, None) self.assertIsNone(self.advisory.collaborating_teams) + self.assertIsNone(self.advisory.collaborating_users) self.assertEqual( self.advisory.created_at, datetime(2023, 3, 28, 21, 41, 40, tzinfo=timezone.utc), From e91c83795b80e677457c3fc944548661b880fd36 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Tue, 18 Feb 2025 22:13:58 +0100 Subject: [PATCH 03/15] Sync `ProjectColumn` and `ProjectCard` tests with OpenAPI spec (#3216) Turns these classes into `NonCompletableGithubObject`. --- github/MainClass.py | 2 +- github/Project.py | 2 +- github/ProjectCard.py | 26 ++++++++++++++++++++++++-- github/ProjectColumn.py | 6 +++--- tests/ProjectCard.py | 18 ++++++++++++------ tests/ProjectColumn.py | 12 +++++++----- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/github/MainClass.py b/github/MainClass.py index e5a5773368..52c6317354 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -522,7 +522,7 @@ def get_project_column(self, id: int) -> ProjectColumn: "/projects/columns/%d" % id, headers={"Accept": Consts.mediaTypeProjectsPreview}, ) - return github.ProjectColumn.ProjectColumn(self.__requester, headers, data, completed=True) + return github.ProjectColumn.ProjectColumn(self.__requester, headers, data) def get_gist(self, id: str) -> Gist: """ diff --git a/github/Project.py b/github/Project.py index 900e76a540..cd6dc6b10a 100644 --- a/github/Project.py +++ b/github/Project.py @@ -230,7 +230,7 @@ def create_column(self, name: str) -> github.ProjectColumn.ProjectColumn: headers, data = self._requester.requestJsonAndCheck( "POST", f"{self.url}/columns", headers=import_header, input=post_parameters ) - return github.ProjectColumn.ProjectColumn(self._requester, headers, data, completed=True) + return github.ProjectColumn.ProjectColumn(self._requester, headers, data) def _useAttributes(self, attributes: dict[str, Any]) -> None: if "body" in attributes: # pragma no branch diff --git a/github/ProjectCard.py b/github/ProjectCard.py index c88ef7bf2f..a9312b48f7 100644 --- a/github/ProjectCard.py +++ b/github/ProjectCard.py @@ -51,10 +51,11 @@ import github.Issue import github.NamedUser +import github.Organization import github.ProjectColumn import github.PullRequest from github import Consts -from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, Opt # NOTE: There is currently no way to get cards "in triage" for a project. # https://platform.github.community/t/moving-github-project-cards-that-are-in-triage/3784 @@ -63,7 +64,7 @@ # which may point the way to where the API is likely headed and what might come back to v3. E.g. ProjectCard.content member. -class ProjectCard(CompletableGithubObject): +class ProjectCard(NonCompletableGithubObject): """ This class represents Project Cards. @@ -77,6 +78,7 @@ class ProjectCard(CompletableGithubObject): def _initAttributes(self) -> None: self._archived: Attribute[bool] = NotSet + self._column_name: Attribute[str] = NotSet self._column_url: Attribute[str] = NotSet self._content_url: Attribute[str] = NotSet self._created_at: Attribute[datetime] = NotSet @@ -84,6 +86,8 @@ def _initAttributes(self) -> None: self._id: Attribute[int] = NotSet self._node_id: Attribute[str] = NotSet self._note: Attribute[str] = NotSet + self._project_id: Attribute[str] = NotSet + self._project_url: Attribute[str] = NotSet self._updated_at: Attribute[datetime] = NotSet self._url: Attribute[str] = NotSet @@ -94,6 +98,10 @@ def __repr__(self) -> str: def archived(self) -> bool: return self._archived.value + @property + def column_name(self) -> str: + return self._column_name.value + @property def column_url(self) -> str: return self._column_url.value @@ -122,6 +130,14 @@ def node_id(self) -> str: def note(self) -> str: return self._note.value + @property + def project_id(self) -> str: + return self._project_id.value + + @property + def project_url(self) -> str: + return self._project_url.value + @property def updated_at(self) -> datetime: return self._updated_at.value @@ -202,6 +218,8 @@ def edit(self, note: Opt[str] = NotSet, archived: Opt[bool] = NotSet) -> None: def _useAttributes(self, attributes: dict[str, Any]) -> None: if "archived" in attributes: # pragma no branch self._archived = self._makeBoolAttribute(attributes["archived"]) + if "column_name" in attributes: # pragma no branch + self._column_name = self._makeStringAttribute(attributes["column_name"]) if "column_url" in attributes: # pragma no branch self._column_url = self._makeStringAttribute(attributes["column_url"]) if "content_url" in attributes: # pragma no branch @@ -216,6 +234,10 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None: self._node_id = self._makeStringAttribute(attributes["node_id"]) if "note" in attributes: # pragma no branch self._note = self._makeStringAttribute(attributes["note"]) + if "project_id" in attributes: # pragma no branch + self._project_id = self._makeStringAttribute(attributes["project_id"]) + if "project_url" in attributes: # pragma no branch + self._project_url = self._makeStringAttribute(attributes["project_url"]) if "updated_at" in attributes: # pragma no branch self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"]) if "url" in attributes: # pragma no branch diff --git a/github/ProjectColumn.py b/github/ProjectColumn.py index ad4cfb4fb9..6e338ecb97 100644 --- a/github/ProjectColumn.py +++ b/github/ProjectColumn.py @@ -55,13 +55,13 @@ import github.GithubObject import github.Project import github.ProjectCard -from github.GithubObject import Attribute, CompletableGithubObject, NotSet, Opt +from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet, Opt from github.PaginatedList import PaginatedList from . import Consts -class ProjectColumn(CompletableGithubObject): +class ProjectColumn(NonCompletableGithubObject): """ This class represents Project Columns. @@ -159,7 +159,7 @@ def create_card( headers, data = self._requester.requestJsonAndCheck( "POST", f"{self.url}/cards", headers=import_header, input=post_parameters ) - return github.ProjectCard.ProjectCard(self._requester, headers, data, completed=True) + return github.ProjectCard.ProjectCard(self._requester, headers, data) def move(self, position: str) -> bool: """ diff --git a/tests/ProjectCard.py b/tests/ProjectCard.py index dc6dbbeee3..156584e9ad 100644 --- a/tests/ProjectCard.py +++ b/tests/ProjectCard.py @@ -22,6 +22,8 @@ from __future__ import annotations +from datetime import datetime, timezone + import github from . import Framework @@ -43,16 +45,20 @@ def setUp(self): # See https://developer.github.com/v3/projects/cards/#get-a-project-card def testAttributes(self): card = self.pull_card - self.assertEqual(card.url, "https://api.github.com/projects/columns/cards/11780055") + self.assertFalse(card.archived) + self.assertIsNone(card.column_name) self.assertEqual(card.column_url, "https://api.github.com/projects/columns/3138831") self.assertEqual(card.content_url, "https://api.github.com/repos/bbi-yggy/PyGithub/issues/1") - self.assertEqual(card.id, 11780055) - self.assertEqual(card.node_id, "MDExOlByb2plY3RDYXJkMTE3ODAwNTU=") - self.assertEqual(card.note, None) # No notes for cards with content. + self.assertEqual(card.created_at, datetime(2018, 8, 1, 4, 53, 59, tzinfo=timezone.utc)) self.assertEqual(card.creator, self.repo.owner) self.assertEqual(card.created_at.year, 2018) - self.assertTrue(card.updated_at >= card.created_at) - self.assertFalse(card.archived) + self.assertEqual(card.id, 11780055) + self.assertEqual(card.node_id, "MDExOlByb2plY3RDYXJkMTE3ODAwNTU=") + self.assertIsNone(card.note, None) + self.assertIsNone(card.project_id) + self.assertIsNone(card.project_url) + self.assertEqual(card.updated_at, datetime(2018, 8, 1, 4, 54, 16, tzinfo=timezone.utc)) + self.assertEqual(card.url, "https://api.github.com/projects/columns/cards/11780055") self.assertEqual(repr(card), "ProjectCard(id=11780055)") def testGetContent(self): diff --git a/tests/ProjectColumn.py b/tests/ProjectColumn.py index c02ae9473b..443af1a14a 100644 --- a/tests/ProjectColumn.py +++ b/tests/ProjectColumn.py @@ -24,6 +24,8 @@ # # ################################################################################ +from __future__ import annotations + from datetime import datetime, timezone from . import Framework @@ -40,14 +42,14 @@ def setUp(self): # See https://developer.github.com/v3/projects/columns/#get-a-project-column def testAttributes(self): col = self.col + self.assertEqual(col.cards_url, "https://api.github.com/projects/columns/3138830/cards") + self.assertEqual(col.created_at.year, 2018) self.assertEqual(col.id, 3138830) - self.assertEqual(col.node_id, "MDEzOlByb2plY3RDb2x1bW4zMTM4ODMw") self.assertEqual(col.name, "To Do") - self.assertEqual(col.url, "https://api.github.com/projects/columns/3138830") + self.assertEqual(col.node_id, "MDEzOlByb2plY3RDb2x1bW4zMTM4ODMw") self.assertEqual(col.project_url, "https://api.github.com/projects/1682941") - self.assertEqual(col.cards_url, "https://api.github.com/projects/columns/3138830/cards") - self.assertEqual(col.created_at.year, 2018) - self.assertTrue(col.updated_at >= col.created_at) + self.assertEqual(col.updated_at, datetime(2018, 8, 1, 4, 7, 35, tzinfo=timezone.utc)) + self.assertEqual(col.url, "https://api.github.com/projects/columns/3138830") self.assertEqual(repr(col), 'ProjectColumn(name="To Do")') def testCreate(self): From fae8f25d9c1bc2d47059d851985aff4da154ac6f Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:12:57 +0100 Subject: [PATCH 04/15] 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 41faf95480..481ea229f1 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -49,6 +49,8 @@ Breaking Changes 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 a1f328dfaf205e9ca9dde52e835e131ec52fb2c0 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:14:17 +0100 Subject: [PATCH 05/15] 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 aee3a35043b0e46efc7a76481c00e86a88c799ca Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 21 Feb 2025 11:14:51 +0100 Subject: [PATCH 06/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 07/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 08/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 09/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 10/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 11/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 12/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 13/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 14/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 15/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"}}