Skip to content

Commit 5d38ee9

Browse files
Merge commit '6d4b6d1419f6b290a3e105ddc04334a2f24e5468'
2 parents 36a04f4 + 6d4b6d1 commit 5d38ee9

13 files changed

+137
-53
lines changed

github/AccessToken.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# #
2121
################################################################################
2222

23-
from datetime import datetime, timedelta
23+
from datetime import datetime, timedelta, timezone
2424

2525
import github.GithubObject
2626

@@ -128,7 +128,7 @@ def _initAttributes(self):
128128
self._refresh_expires_in = github.GithubObject.NotSet
129129

130130
def _useAttributes(self, attributes):
131-
self._created = datetime.utcnow()
131+
self._created = datetime.now(timezone.utc)
132132
if "access_token" in attributes: # pragma no branch
133133
self._token = self._makeStringAttribute(attributes["access_token"])
134134
if "token_type" in attributes: # pragma no branch

github/Auth.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import abc
2424
import base64
2525
import time
26-
from datetime import datetime, timedelta
26+
from datetime import datetime, timedelta, timezone
2727
from typing import Dict, Optional, Union
2828

2929
import jwt
@@ -307,7 +307,7 @@ def _is_expired(self) -> bool:
307307
self.__installation_authorization.expires_at
308308
- TOKEN_REFRESH_THRESHOLD_TIMEDELTA
309309
)
310-
return token_expires_at < datetime.utcnow()
310+
return token_expires_at < datetime.now(timezone.utc)
311311

312312
def _get_installation_authorization(self) -> InstallationAuthorization:
313313
assert (
@@ -413,7 +413,9 @@ def withRequester(self, requester: Requester) -> "AppUserAuth":
413413

414414
@property
415415
def _is_expired(self) -> bool:
416-
return self._expires_at is not None and self._expires_at < datetime.utcnow()
416+
return self._expires_at is not None and self._expires_at < datetime.now(
417+
timezone.utc
418+
)
417419

418420
def _refresh(self):
419421
if self._refresh_token is None:
@@ -422,7 +424,7 @@ def _refresh(self):
422424
)
423425
if (
424426
self._refresh_expires_at is not None
425-
and self._refresh_expires_at < datetime.utcnow()
427+
and self._refresh_expires_at < datetime.now(timezone.utc)
426428
):
427429
raise RuntimeError(
428430
"Cannot refresh expired token because refresh token also expired"

github/AuthenticatedUser.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,9 @@ def has_in_watched(self, watched):
12291229
)
12301230
return status == 200
12311231

1232-
def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):
1232+
def mark_notifications_as_read(
1233+
self, last_read_at=datetime.datetime.now(datetime.timezone.utc)
1234+
):
12331235
"""
12341236
:calls: `PUT /notifications <https://docs.github.com/en/rest/reference/activity#notifications>`_
12351237
:param last_read_at: datetime

github/GithubIntegration.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from github import Consts
66
from github.Auth import AppAuth
7+
from github.GithubApp import GithubApp
78
from github.GithubException import GithubException
89
from github.Installation import Installation
910
from github.InstallationAuthorization import InstallationAuthorization
@@ -71,7 +72,10 @@ def __init__(
7172
jwt_algorithm=jwt_algorithm,
7273
)
7374

74-
assert auth is not None
75+
assert isinstance(
76+
auth, AppAuth
77+
), f"GithubIntegration requires github.Auth.AppAuth authentication, not {type(auth)}"
78+
7579
self.auth = auth
7680

7781
self.__requester = Requester(
@@ -213,3 +217,16 @@ def get_app_installation(self, installation_id):
213217
:rtype: :class:`github.Installation.Installation`
214218
"""
215219
return self._get_installed_app(url=f"/app/installations/{installation_id}")
220+
221+
def get_app(self):
222+
"""
223+
:calls: `GET /app <https://docs.github.com/en/rest/reference/apps#get-the-authenticated-app>`_
224+
:rtype: :class:`github.GithubApp.GithubApp`
225+
"""
226+
227+
headers, data = self.__requester.requestJsonAndCheck(
228+
"GET", "/app", headers=self._get_headers()
229+
)
230+
return GithubApp(
231+
requester=self.__requester, headers=headers, attributes=data, completed=True
232+
)

github/MainClass.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,8 +808,12 @@ def get_app(self, slug=github.GithubObject.NotSet):
808808
if slug is github.GithubObject.NotSet:
809809
# with no slug given, calling /app returns the authenticated app,
810810
# including the actual /apps/{slug}
811-
headers, data = self.__requester.requestJsonAndCheck("GET", "/app")
812-
return GithubApp.GithubApp(self.__requester, headers, data, completed=True)
811+
warnings.warn(
812+
"Argument slug is mandatory, calling this method without the slug argument is deprecated, please use "
813+
"github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
814+
category=DeprecationWarning,
815+
)
816+
return GithubIntegration(auth=self.__requester.auth).get_app()
813817
else:
814818
# with a slug given, we can lazily load the GithubApp
815819
return GithubApp.GithubApp(

github/Repository.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3724,7 +3724,9 @@ def get_notifications(
37243724
params,
37253725
)
37263726

3727-
def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):
3727+
def mark_notifications_as_read(
3728+
self, last_read_at=datetime.datetime.now(datetime.timezone.utc)
3729+
):
37283730
"""
37293731
:calls: `PUT /repos/{owner}/{repo}/notifications <https://docs.github.com/en/rest/reference/activity#notifications>`_
37303732
:param last_read_at: datetime

tests/ApplicationOAuth.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ def testGetAccessToken(self):
8383

8484
def testGetAccessTokenWithExpiry(self):
8585
with mock.patch("github.AccessToken.datetime") as dt:
86-
dt.utcnow = mock.Mock(
87-
return_value=datetime.datetime(2023, 6, 7, 12, 0, 0, 123)
86+
dt.now = mock.Mock(
87+
return_value=datetime.datetime(
88+
2023, 6, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc
89+
)
8890
)
8991
access_token = self.app.get_access_token(
9092
"oauth_code_removed", state="state_removed"
@@ -100,13 +102,14 @@ def testGetAccessTokenWithExpiry(self):
100102
self.assertEqual(access_token.scope, "")
101103
self.assertEqual(access_token.expires_in, 28800)
102104
self.assertEqual(
103-
access_token.expires_at, datetime.datetime(2023, 6, 7, 20, 0, 0, 123)
105+
access_token.expires_at,
106+
datetime.datetime(2023, 6, 7, 20, 0, 0, 123, tzinfo=datetime.timezone.utc),
104107
)
105108
self.assertEqual(access_token.refresh_token, "refresh_token_removed")
106109
self.assertEqual(access_token.refresh_expires_in, 15811200)
107110
self.assertEqual(
108111
access_token.refresh_expires_at,
109-
datetime.datetime(2023, 12, 7, 12, 0, 0, 123),
112+
datetime.datetime(2023, 12, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc),
110113
)
111114

112115
def testRefreshAccessToken(self):
@@ -115,8 +118,10 @@ def testRefreshAccessToken(self):
115118
)
116119

117120
with mock.patch("github.AccessToken.datetime") as dt:
118-
dt.utcnow = mock.Mock(
119-
return_value=datetime.datetime(2023, 6, 7, 12, 0, 0, 123)
121+
dt.now = mock.Mock(
122+
return_value=datetime.datetime(
123+
2023, 6, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc
124+
)
120125
)
121126
refreshed = self.app.refresh_access_token(access_token.refresh_token)
122127

@@ -133,17 +138,19 @@ def testRefreshAccessToken(self):
133138
self.assertEqual(refreshed.type, "bearer")
134139
self.assertEqual(refreshed.scope, "")
135140
self.assertEqual(
136-
refreshed.created, datetime.datetime(2023, 6, 7, 12, 0, 0, 123)
141+
refreshed.created,
142+
datetime.datetime(2023, 6, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc),
137143
)
138144
self.assertEqual(refreshed.expires_in, 28800)
139145
self.assertEqual(
140-
refreshed.expires_at, datetime.datetime(2023, 6, 7, 20, 0, 0, 123)
146+
refreshed.expires_at,
147+
datetime.datetime(2023, 6, 7, 20, 0, 0, 123, tzinfo=datetime.timezone.utc),
141148
)
142149
self.assertEqual(refreshed.refresh_token, "another_refresh_token_removed")
143150
self.assertEqual(refreshed.refresh_expires_in, 15811200)
144151
self.assertEqual(
145152
refreshed.refresh_expires_at,
146-
datetime.datetime(2023, 12, 7, 12, 0, 0, 123),
153+
datetime.datetime(2023, 12, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc),
147154
)
148155

149156
def testGetAccessTokenBadCode(self):

tests/Authentication.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
# #
2727
################################################################################
2828
import datetime
29-
import warnings
3029
from unittest import mock
3130

3231
import jwt
@@ -42,16 +41,6 @@ def testNoAuthentication(self):
4241
g = github.Github()
4342
self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
4443

45-
def assertWarning(self, warning, expected):
46-
self.assertWarnings(warning, expected)
47-
48-
def assertWarnings(self, warning, *expecteds):
49-
self.assertEqual(len(warning.warnings), len(expecteds))
50-
for message, expected in zip(warning.warnings, expecteds):
51-
self.assertIsInstance(message, warnings.WarningMessage)
52-
self.assertIsInstance(message.message, DeprecationWarning)
53-
self.assertEqual(message.message.args, (expected,))
54-
5544
def testBasicAuthentication(self):
5645
with self.assertWarns(DeprecationWarning) as warning:
5746
g = github.Github(self.login.login, self.login.password)
@@ -124,25 +113,33 @@ def testAppUserAuthentication(self):
124113
g = github.Github()
125114
app = g.get_oauth_application(client_id, client_secret)
126115
with mock.patch("github.AccessToken.datetime") as dt:
127-
dt.utcnow = mock.Mock(
128-
return_value=datetime.datetime(2023, 6, 7, 12, 0, 0, 123)
116+
dt.now = mock.Mock(
117+
return_value=datetime.datetime(
118+
2023, 6, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc
119+
)
129120
)
130121
token = app.refresh_access_token(refresh_token)
131122
self.assertEqual(token.token, "fresh access token")
132123
self.assertEqual(token.type, "bearer")
133124
self.assertEqual(token.scope, "")
134125
self.assertEqual(token.expires_in, 28800)
135-
self.assertEqual(token.expires_at, datetime.datetime(2023, 6, 7, 20, 0, 0, 123))
126+
self.assertEqual(
127+
token.expires_at,
128+
datetime.datetime(2023, 6, 7, 20, 0, 0, 123, tzinfo=datetime.timezone.utc),
129+
)
136130
self.assertEqual(token.refresh_token, "fresh refresh token")
137131
self.assertEqual(token.refresh_expires_in, 15811200)
138132
self.assertEqual(
139-
token.refresh_expires_at, datetime.datetime(2023, 12, 7, 12, 0, 0, 123)
133+
token.refresh_expires_at,
134+
datetime.datetime(2023, 12, 7, 12, 0, 0, 123, tzinfo=datetime.timezone.utc),
140135
)
141136

142137
auth = app.get_app_user_auth(token)
143138
with mock.patch("github.Auth.datetime") as dt:
144-
dt.utcnow = mock.Mock(
145-
return_value=datetime.datetime(2023, 6, 7, 20, 0, 0, 123)
139+
dt.now = mock.Mock(
140+
return_value=datetime.datetime(
141+
2023, 6, 7, 20, 0, 0, 123, tzinfo=datetime.timezone.utc
142+
)
146143
)
147144
self.assertEqual(auth._is_expired, False)
148145
self.assertEqual(auth.token, "fresh access token")
@@ -151,8 +148,10 @@ def testAppUserAuthentication(self):
151148

152149
# expire auth token
153150
with mock.patch("github.Auth.datetime") as dt:
154-
dt.utcnow = mock.Mock(
155-
return_value=datetime.datetime(2023, 6, 7, 20, 0, 1, 123)
151+
dt.now = mock.Mock(
152+
return_value=datetime.datetime(
153+
2023, 6, 7, 20, 0, 1, 123, tzinfo=datetime.timezone.utc
154+
)
156155
)
157156
self.assertEqual(auth._is_expired, True)
158157
self.assertEqual(auth.token, "another access token")

tests/Framework.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import os
4040
import traceback
4141
import unittest
42+
import warnings
4243

4344
import httpretty # type: ignore
4445
from requests.structures import CaseInsensitiveDict
@@ -328,6 +329,21 @@ def tearDown(self):
328329
self.__closeReplayFileIfNeeded()
329330
github.Requester.Requester.resetConnectionClasses()
330331

332+
def assertWarning(self, warning, expected):
333+
self.assertWarnings(warning, expected)
334+
335+
def assertWarnings(self, warning, *expecteds):
336+
self.assertEqual(len(warning.warnings), len(expecteds))
337+
actual = [
338+
(type(message), type(message.message), message.message.args)
339+
for message in warning.warnings
340+
]
341+
expected = [
342+
(warnings.WarningMessage, DeprecationWarning, (expected,))
343+
for expected in expecteds
344+
]
345+
self.assertSequenceEqual(actual, expected)
346+
331347
def __openFile(self, mode):
332348
for (_, _, functionName, _) in traceback.extract_stack():
333349
if (

tests/GithubApp.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222

2323
from datetime import datetime
2424

25+
import github
26+
2527
from . import Framework
28+
from .GithubIntegration import APP_ID, PRIVATE_KEY
2629

2730

2831
class GithubApp(Framework.TestCase):
@@ -99,8 +102,23 @@ def testGetPublicApp(self):
99102
self.assertEqual(app.url, "/apps/github-actions")
100103

101104
def testGetAuthenticatedApp(self):
102-
# For this to work correctly in record mode, this test must be run with --auth_with_jwt
103-
app = self.g.get_app()
105+
auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
106+
g = github.Github(auth=auth)
107+
108+
with self.assertWarns(DeprecationWarning) as warning:
109+
# we ignore warnings from httpretty dependency
110+
import warnings
111+
112+
warnings.filterwarnings("ignore", module="httpretty")
113+
114+
app = g.get_app()
115+
116+
self.assertWarning(
117+
warning,
118+
"Argument slug is mandatory, calling this method without the slug argument is deprecated, "
119+
"please use github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
120+
)
121+
104122
self.assertEqual(app.created_at, datetime(2020, 8, 1, 17, 23, 46))
105123
self.assertEqual(app.description, "Sample App to test PyGithub")
106124
self.assertListEqual(

tests/GithubIntegration.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import time # NOQA
2-
import warnings
32

43
import requests # NOQA
54

@@ -42,16 +41,6 @@ def setUp(self):
4241
self.repo_installation_id = 30614431
4342
self.user_installation_id = 30614431
4443

45-
def assertWarning(self, warning, expected):
46-
self.assertWarnings(warning, expected)
47-
48-
def assertWarnings(self, warning, *expecteds):
49-
self.assertEqual(len(warning.warnings), len(expecteds))
50-
for message, expected in zip(warning.warnings, expecteds):
51-
self.assertIsInstance(message, warnings.WarningMessage)
52-
self.assertIsInstance(message.message, DeprecationWarning)
53-
self.assertEqual(message.message.args, (expected,))
54-
5544
def testDeprecatedAppAuth(self):
5645
# Replay data copied from testGetInstallations to test authentication only
5746
with self.assertWarns(DeprecationWarning) as warning:
@@ -67,6 +56,16 @@ def testDeprecatedAppAuth(self):
6756
"instead",
6857
)
6958

59+
def testRequiredAppAuth(self):
60+
# GithubIntegration requires AppAuth authentication.
61+
for auth in [self.oauth_token, self.jwt, self.login]:
62+
with self.assertRaises(AssertionError) as r:
63+
github.GithubIntegration(auth=auth)
64+
self.assertEqual(
65+
str(r.exception),
66+
f"GithubIntegration requires github.Auth.AppAuth authentication, not {type(auth)}",
67+
)
68+
7069
def testAppAuth(self):
7170
# Replay data copied from testDeprecatedAppAuth to test parity
7271
auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
@@ -227,3 +226,11 @@ def testGetAccessTokenWithInvalidData(self):
227226
)
228227

229228
self.assertEqual(raisedexp.exception.status, 400)
229+
230+
def testGetApp(self):
231+
auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
232+
github_integration = github.GithubIntegration(auth=auth)
233+
app = github_integration.get_app()
234+
235+
self.assertEqual(app.name, "PyGithubTest")
236+
self.assertEqual(app.url, "/apps/pygithubtest")

0 commit comments

Comments
 (0)