Skip to content

Commit fa885f0

Browse files
iarspiderEnricoMi
andauthored
Make Commit.files return PaginatedList (#2939)
[`/commits/{ref}` endpoint](https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit) is paginated, meaning you can get up to 3000 files per commit: > Note: If there are more than 300 files in the commit diff and the default JSON media type is requested, the response will include pagination link headers for the remaining files, up to a limit of 3000 files. Each page contains the static commit information, and the only changes are to the file listing. As suggested [here](#1745 (comment)). Fixes #1745. --------- Co-authored-by: Enrico Minack <[email protected]>
1 parent 6013610 commit fa885f0

File tree

6 files changed

+50
-9
lines changed

6 files changed

+50
-9
lines changed

doc/changes.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ Change log
44
Stable versions
55
~~~~~~~~~~~~~~~
66

7+
Version 2.4.0 (???)
8+
-------------------
9+
10+
Breaking Changes
11+
^^^^^^^^^^^^^^^^
12+
13+
* The ``github.Commit.Commit`` class provides a ``files`` property that used to return a ``list[github.File.File]``,
14+
which has now been changed to ``PaginatedList[github.File.File]``. This breaks user code that assumes a ``list``:
15+
16+
.. code-block:: python
17+
18+
files = repo.get_commit("7266e812ed2976ea36a4303edecfe5d75522343f").files
19+
no_of_files = len(files)
20+
21+
This will raise a ``TypeError: object of type 'PaginatedList' has no len()``, as the returned ``PaginatedList``
22+
does not support the ``len()`` method. Use the ``totalCount`` property instead:
23+
24+
.. code-block:: python
25+
26+
files = repo.get_commit("7266e812ed2976ea36a4303edecfe5d75522343f").files
27+
no_of_files = files.totalCount
28+
729
Version 2.3.0 (March 21, 2024)
830
------------------------------
931

github/Commit.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ def _initAttributes(self) -> None:
8585
self._comments_url: Attribute[str] = NotSet
8686
self._commit: Attribute[GitCommit] = NotSet
8787
self._committer: Attribute[NamedUser] = NotSet
88-
self._files: Attribute[list[File]] = NotSet
8988
self._html_url: Attribute[str] = NotSet
9089
self._parents: Attribute[list[Commit]] = NotSet
9190
self._sha: Attribute[str] = NotSet
@@ -115,10 +114,21 @@ def committer(self) -> NamedUser:
115114
self._completeIfNotSet(self._committer)
116115
return self._committer.value
117116

117+
# This should be a method, but this used to be a property and cannot be changed without breaking user code
118+
# TODO: remove @property on version 3
118119
@property
119-
def files(self) -> list[File]:
120-
self._completeIfNotSet(self._files)
121-
return self._files.value
120+
def files(self) -> PaginatedList[File]:
121+
return PaginatedList(
122+
github.File.File,
123+
self._requester,
124+
self.url,
125+
{},
126+
None,
127+
"files",
128+
"total_files",
129+
self.raw_data,
130+
self.raw_headers,
131+
)
122132

123133
@property
124134
def html_url(self) -> str:
@@ -289,8 +299,6 @@ def _useAttributes(self, attributes: dict[str, Any]) -> None:
289299
self._commit = self._makeClassAttribute(github.GitCommit.GitCommit, attributes["commit"])
290300
if "committer" in attributes: # pragma no branch
291301
self._committer = self._makeClassAttribute(github.NamedUser.NamedUser, attributes["committer"])
292-
if "files" in attributes: # pragma no branch
293-
self._files = self._makeListOfClassesAttribute(github.File.File, attributes["files"])
294302
if "html_url" in attributes: # pragma no branch
295303
self._html_url = self._makeStringAttribute(attributes["html_url"])
296304
if "parents" in attributes: # pragma no branch

tests/BadAttributes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def testBadTransformedAttributeInList(self):
101101
commit = self.g.get_repo("klmitch/turnstile", lazy=True).get_commit("38d9082a898d0822b5ccdfd78f3a536e2efa6c26")
102102

103103
with self.assertRaises(github.BadAttributeException) as raisedexp:
104-
commit.files
104+
commit.parents
105105
self.assertEqual(raisedexp.exception.actual_value, [42])
106106
self.assertEqual(raisedexp.exception.expected_type, [dict])
107107
self.assertEqual(raisedexp.exception.transformation_exception, None)

tests/Commit.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def testAttributes(self):
5050
"https://api.github.com/repos/jacquev6/PyGithub/git/commits/1292bf0e22c796e91cc3d6e24b544aece8c21f2a",
5151
)
5252
self.assertEqual(self.commit.committer.login, "jacquev6")
53-
self.assertEqual(len(self.commit.files), 1)
53+
self.assertEqual(len(list(self.commit.files)), 1)
54+
self.assertEqual(self.commit.files.totalCount, 1)
5455
self.assertEqual(self.commit.files[0].additions, 0)
5556
self.assertEqual(
5657
self.commit.files[0].blob_url,

tests/ReplayData/BadAttributes.testBadTransformedAttributeInList.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ None
77
None
88
200
99
[('status', '200 OK'), ('x-ratelimit-remaining', '4998'), ('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', '205eb9ed-a173-47a2-b670-16ec266adef5'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '6111'), ('server', 'GitHub.com'), ('last-modified', 'Wed, 01 May 2013 19:03:50 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"50a14a79f7095a8d4fed16d05a4b1412"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 12 Sep 2013 09:09:25 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378980564')]
10-
{"sha":"38d9082a898d0822b5ccdfd78f3a536e2efa6c26","commit":{"author":{"name":"Kevin L. Mitchell","email":"[email protected]","date":"2013-05-01T19:03:50Z"},"committer":{"name":"Kevin L. Mitchell","email":"[email protected]","date":"2013-05-01T19:03:50Z"},"message":"Ignore empty configuration values for extra database arguments\n\nTurnstile's functionality to allow the control daemon and the\ncompactor daemon to use different Redis configuration provides\none problem: values that are set in the \"[redis]\" section are\ninherited by all sections, even when that is not desired. To\ncombat this, we now allow an empty value to completely delete\nthe key from the redis configuration in Config.get_database().","tree":{"sha":"83b8ab73bedb67846b47533d1bac7767ac325dc8","url":"https://api.github.com/repos/klmitch/turnstile/git/trees/83b8ab73bedb67846b47533d1bac7767ac325dc8"},"url":"https://api.github.com/repos/klmitch/turnstile/git/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","comment_count":0},"url":"https://api.github.com/repos/klmitch/turnstile/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","html_url":"https://github.com/klmitch/turnstile/commit/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","comments_url":"https://api.github.com/repos/klmitch/turnstile/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26/comments","author":{"login":"klmitch","id":686398,"avatar_url":"https://1.gravatar.com/avatar/3c505225c6f28a7702b318a991141495?d=https%3A%2F%2Fidenticons.github.com%2Ffffa0f2e30bad5753edbb60f250b7cbe.png","gravatar_id":"3c505225c6f28a7702b318a991141495","url":"https://api.github.com/users/klmitch","html_url":"https://github.com/klmitch","followers_url":"https://api.github.com/users/klmitch/followers","following_url":"https://api.github.com/users/klmitch/following{/other_user}","gists_url":"https://api.github.com/users/klmitch/gists{/gist_id}","starred_url":"https://api.github.com/users/klmitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klmitch/subscriptions","organizations_url":"https://api.github.com/users/klmitch/orgs","repos_url":"https://api.github.com/users/klmitch/repos","events_url":"https://api.github.com/users/klmitch/events{/privacy}","received_events_url":"https://api.github.com/users/klmitch/received_events","type":"User"},"committer":{"login":"klmitch","id":686398,"avatar_url":"https://1.gravatar.com/avatar/3c505225c6f28a7702b318a991141495?d=https%3A%2F%2Fidenticons.github.com%2Ffffa0f2e30bad5753edbb60f250b7cbe.png","gravatar_id":"3c505225c6f28a7702b318a991141495","url":"https://api.github.com/users/klmitch","html_url":"https://github.com/klmitch","followers_url":"https://api.github.com/users/klmitch/followers","following_url":"https://api.github.com/users/klmitch/following{/other_user}","gists_url":"https://api.github.com/users/klmitch/gists{/gist_id}","starred_url":"https://api.github.com/users/klmitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klmitch/subscriptions","organizations_url":"https://api.github.com/users/klmitch/orgs","repos_url":"https://api.github.com/users/klmitch/repos","events_url":"https://api.github.com/users/klmitch/events{/privacy}","received_events_url":"https://api.github.com/users/klmitch/received_events","type":"User"},"parents":[{"sha":"e649bcaa580248de40ef6c126fe446a3da514312","url":"https://api.github.com/repos/klmitch/turnstile/commits/e649bcaa580248de40ef6c126fe446a3da514312","html_url":"https://github.com/klmitch/turnstile/commit/e649bcaa580248de40ef6c126fe446a3da514312"}],"stats":{"total":9,"additions":7,"deletions":2},"files":[42]}
10+
{"sha":"38d9082a898d0822b5ccdfd78f3a536e2efa6c26","commit":{"author":{"name":"Kevin L. Mitchell","email":"[email protected]","date":"2013-05-01T19:03:50Z"},"committer":{"name":"Kevin L. Mitchell","email":"[email protected]","date":"2013-05-01T19:03:50Z"},"message":"Ignore empty configuration values for extra database arguments\n\nTurnstile's functionality to allow the control daemon and the\ncompactor daemon to use different Redis configuration provides\none problem: values that are set in the \"[redis]\" section are\ninherited by all sections, even when that is not desired. To\ncombat this, we now allow an empty value to completely delete\nthe key from the redis configuration in Config.get_database().","tree":{"sha":"83b8ab73bedb67846b47533d1bac7767ac325dc8","url":"https://api.github.com/repos/klmitch/turnstile/git/trees/83b8ab73bedb67846b47533d1bac7767ac325dc8"},"url":"https://api.github.com/repos/klmitch/turnstile/git/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","comment_count":0},"url":"https://api.github.com/repos/klmitch/turnstile/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","html_url":"https://github.com/klmitch/turnstile/commit/38d9082a898d0822b5ccdfd78f3a536e2efa6c26","comments_url":"https://api.github.com/repos/klmitch/turnstile/commits/38d9082a898d0822b5ccdfd78f3a536e2efa6c26/comments","author":{"login":"klmitch","id":686398,"avatar_url":"https://1.gravatar.com/avatar/3c505225c6f28a7702b318a991141495?d=https%3A%2F%2Fidenticons.github.com%2Ffffa0f2e30bad5753edbb60f250b7cbe.png","gravatar_id":"3c505225c6f28a7702b318a991141495","url":"https://api.github.com/users/klmitch","html_url":"https://github.com/klmitch","followers_url":"https://api.github.com/users/klmitch/followers","following_url":"https://api.github.com/users/klmitch/following{/other_user}","gists_url":"https://api.github.com/users/klmitch/gists{/gist_id}","starred_url":"https://api.github.com/users/klmitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klmitch/subscriptions","organizations_url":"https://api.github.com/users/klmitch/orgs","repos_url":"https://api.github.com/users/klmitch/repos","events_url":"https://api.github.com/users/klmitch/events{/privacy}","received_events_url":"https://api.github.com/users/klmitch/received_events","type":"User"},"committer":{"login":"klmitch","id":686398,"avatar_url":"https://1.gravatar.com/avatar/3c505225c6f28a7702b318a991141495?d=https%3A%2F%2Fidenticons.github.com%2Ffffa0f2e30bad5753edbb60f250b7cbe.png","gravatar_id":"3c505225c6f28a7702b318a991141495","url":"https://api.github.com/users/klmitch","html_url":"https://github.com/klmitch","followers_url":"https://api.github.com/users/klmitch/followers","following_url":"https://api.github.com/users/klmitch/following{/other_user}","gists_url":"https://api.github.com/users/klmitch/gists{/gist_id}","starred_url":"https://api.github.com/users/klmitch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/klmitch/subscriptions","organizations_url":"https://api.github.com/users/klmitch/orgs","repos_url":"https://api.github.com/users/klmitch/repos","events_url":"https://api.github.com/users/klmitch/events{/privacy}","received_events_url":"https://api.github.com/users/klmitch/received_events","type":"User"},"parents":[42],"stats":{"total":9,"additions":7,"deletions":2},"files":[]}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
https
2+
GET
3+
api.github.com
4+
None
5+
/repos/jacquev6/PyGithub/commits/1292bf0e22c796e91cc3d6e24b544aece8c21f2a?per_page=1
6+
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
7+
None
8+
200
9+
[('Server', 'GitHub.com'), ('Date', 'Fri, 05 Apr 2024 12:24:56 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"da87e7c05666ad0fc39ed4ca3c27dcde63a172cf5d6e1fa42a85947a9277b320"'), ('Last-Modified', 'Wed, 09 May 2012 16:22:33 GMT'), ('X-OAuth-Scopes', 'admin:org, project, repo, workflow, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('x-github-api-version-selected', '2022-11-28'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4995'), ('X-RateLimit-Reset', '1712323495'), ('X-RateLimit-Used', '5'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', '8482:23E254:4A48AF2:4A968B7:660FED98')]
10+
{"sha":"1292bf0e22c796e91cc3d6e24b544aece8c21f2a","node_id":"MDY6Q29tbWl0MzU0NDQ5MDoxMjkyYmYwZTIyYzc5NmU5MWNjM2Q2ZTI0YjU0NGFlY2U4YzIxZjJh","commit":{"author":{"name":"Vincent Jacques","email":"[email protected]","date":"2012-05-09T16:22:33Z"},"committer":{"name":"Vincent Jacques","email":"[email protected]","date":"2012-05-09T16:22:33Z"},"message":"Remove completion functions from GitAuthor","tree":{"sha":"4c6bd50994f0f9823f898b1c6c964ad7d4fa11ab","url":"https://api.github.com/repos/PyGithub/PyGithub/git/trees/4c6bd50994f0f9823f898b1c6c964ad7d4fa11ab"},"url":"https://api.github.com/repos/PyGithub/PyGithub/git/commits/1292bf0e22c796e91cc3d6e24b544aece8c21f2a","comment_count":9,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/PyGithub/PyGithub/commits/1292bf0e22c796e91cc3d6e24b544aece8c21f2a","html_url":"https://github.com/PyGithub/PyGithub/commit/1292bf0e22c796e91cc3d6e24b544aece8c21f2a","comments_url":"https://api.github.com/repos/PyGithub/PyGithub/commits/1292bf0e22c796e91cc3d6e24b544aece8c21f2a/comments","author":{"login":"jacquev6","id":327146,"node_id":"MDQ6VXNlcjMyNzE0Ng==","avatar_url":"https://avatars.githubusercontent.com/u/327146?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"committer":{"login":"jacquev6","id":327146,"node_id":"MDQ6VXNlcjMyNzE0Ng==","avatar_url":"https://avatars.githubusercontent.com/u/327146?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"parents":[{"sha":"b46ed0dfde5ad02d3b91eb54a41c5ed960710eae","url":"https://api.github.com/repos/PyGithub/PyGithub/commits/b46ed0dfde5ad02d3b91eb54a41c5ed960710eae","html_url":"https://github.com/PyGithub/PyGithub/commit/b46ed0dfde5ad02d3b91eb54a41c5ed960710eae"}],"stats":{"total":20,"additions":0,"deletions":20},"files":[{"sha":"ca6a3c616fc1367b6d01d04a7cf6ee27cf216f26","filename":"github/GithubObjects/GitAuthor.py","status":"modified","additions":0,"deletions":20,"changes":20,"blob_url":"https://github.com/PyGithub/PyGithub/blob/1292bf0e22c796e91cc3d6e24b544aece8c21f2a/github%2FGithubObjects%2FGitAuthor.py","raw_url":"https://github.com/PyGithub/PyGithub/raw/1292bf0e22c796e91cc3d6e24b544aece8c21f2a/github%2FGithubObjects%2FGitAuthor.py","contents_url":"https://api.github.com/repos/PyGithub/PyGithub/contents/github%2FGithubObjects%2FGitAuthor.py?ref=1292bf0e22c796e91cc3d6e24b544aece8c21f2a","patch":"@@ -14,44 +14,24 @@ def __init__( self, requester, attributes, lazy ):\n self.__completed = False\n self.__initAttributes()\n self.__useAttributes( attributes )\n- if not lazy:\n- self.__complete()\n \n @property\n def date( self ):\n- self.__completeIfNeeded( self.__date )\n return self.__date\n \n @property\n def email( self ):\n- self.__completeIfNeeded( self.__email )\n return self.__email\n \n @property\n def name( self ):\n- self.__completeIfNeeded( self.__name )\n return self.__name\n \n def __initAttributes( self ):\n self.__date = None\n self.__email = None\n self.__name = None\n \n- def __completeIfNeeded( self, testedAttribute ):\n- if not self.__completed and testedAttribute is None:\n- self.__complete()\n-\n- # @todo Do not generate __complete if type has no url attribute\n- def __complete( self ):\n- status, headers, data = self.__requester.request(\n- \"GET\",\n- self.__url,\n- None,\n- None\n- )\n- self.__useAttributes( data )\n- self.__completed = True\n-\n def __useAttributes( self, attributes ):\n #@todo No need to check if attribute is in attributes when attribute is mandatory\n if \"date\" in attributes and attributes[ \"date\" ] is not None:"}]}

0 commit comments

Comments
 (0)