11"""This is the module that contains functions related to authenticating to GitHub with a personal access token."""
22
3- import github3
4- import requests
5- from requests .adapters import HTTPAdapter
6- from urllib3 .util .retry import Retry
7-
8- # Retry strategy: 5 retries with exponential backoff for transient errors
9- RETRY_STRATEGY = Retry (
10- total = 5 ,
11- backoff_factor = 1 , # 1s, 2s, 4s, 8s, 16s
12- status_forcelist = [429 , 500 , 502 , 503 , 504 ],
13- raise_on_status = False ,
14- )
15- REQUEST_TIMEOUT = 30 # seconds
16-
17-
18- def _configure_session (github_connection : github3 .GitHub ) -> None :
19- """Mount retry adapter and set timeout on the github3 session."""
20- session = getattr (github_connection , "session" , None )
21- if session is None :
22- return
23- adapter = HTTPAdapter (max_retries = RETRY_STRATEGY )
24- session .mount ("https://" , adapter )
25- session .mount ("http://" , adapter )
26- session .request = _timeout_wrapper (session .request , REQUEST_TIMEOUT ) # type: ignore[method-assign]
27-
28-
29- def _timeout_wrapper (original_request , default_timeout ):
30- """Wrap a session.request method to inject a default timeout."""
31-
32- def wrapper (* args , ** kwargs ):
33- kwargs .setdefault ("timeout" , default_timeout )
34- return original_request (* args , ** kwargs )
35-
36- return wrapper
3+ from github import Auth , Github , GithubIntegration
374
385
396def auth_to_github (
@@ -43,7 +10,7 @@ def auth_to_github(
4310 gh_app_private_key_bytes : bytes ,
4411 ghe : str ,
4512 gh_app_enterprise_only : bool ,
46- ) -> github3 . GitHub :
13+ ) -> Github :
4714 """
4815 Connect to GitHub.com or GitHub Enterprise, depending on env variables.
4916
@@ -56,30 +23,34 @@ def auth_to_github(
5623 gh_app_enterprise_only (bool): Set this to true if the GH APP is created on GHE and needs to communicate with GHE api only
5724
5825 Returns:
59- github3.GitHub : the GitHub connection object
26+ Github : the GitHub connection object
6027 """
28+ ghe = ghe .rstrip ("/" )
29+
6130 if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id :
31+ app_auth = Auth .AppAuth (int (gh_app_id ), gh_app_private_key_bytes .decode ())
32+ installation_auth = app_auth .get_installation_auth (int (gh_app_installation_id ))
6233 if ghe and gh_app_enterprise_only :
63- gh = github3 .github .GitHubEnterprise (url = ghe )
34+ github_connection = Github (
35+ base_url = f"{ ghe } /api/v3" ,
36+ auth = installation_auth ,
37+ )
6438 else :
65- gh = github3 .github .GitHub ()
66- gh .login_as_app_installation (
67- gh_app_private_key_bytes , str (gh_app_id ), gh_app_installation_id
68- )
69- github_connection = gh
39+ github_connection = Github (auth = installation_auth )
7040 elif ghe and token :
71- github_connection = github3 .github .GitHubEnterprise (url = ghe , token = token )
41+ github_connection = Github (
42+ base_url = f"{ ghe } /api/v3" ,
43+ auth = Auth .Token (token ),
44+ )
7245 elif token :
73- github_connection = github3 . login (token = token )
46+ github_connection = Github ( auth = Auth . Token (token ) )
7447 else :
7548 raise ValueError (
76- "GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set"
49+ "GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, "
50+ "GH_APP_PRIVATE_KEY] environment variables are not set"
7751 )
7852
79- if not github_connection :
80- raise ValueError ("Unable to authenticate to GitHub" )
81- _configure_session (github_connection )
82- return github_connection # type: ignore
53+ return github_connection
8354
8455
8556def get_github_app_installation_token (
@@ -99,31 +70,32 @@ def get_github_app_installation_token(
9970 gh_app_installation_id (str): the GitHub App Installation ID
10071
10172 Returns:
102- str: the GitHub App token
73+ str | None : the GitHub App token, or None if the request fails
10374 """
104- jwt_headers = github3 .apps .create_jwt_headers (gh_app_private_key_bytes , gh_app_id )
105- api_endpoint = f"{ ghe } /api/v3" if ghe else "https://api.github.com"
106- url = f"{ api_endpoint } /app/installations/{ gh_app_installation_id } /access_tokens"
107-
10875 try :
109- response = requests .post (url , headers = jwt_headers , json = None , timeout = 5 )
110- response .raise_for_status ()
111- except requests .exceptions .RequestException as e :
76+ ghe = ghe .rstrip ("/" )
77+ app_auth = Auth .AppAuth (int (gh_app_id ), gh_app_private_key_bytes .decode ())
78+ if ghe :
79+ gi = GithubIntegration (auth = app_auth , base_url = f"{ ghe } /api/v3" )
80+ else :
81+ gi = GithubIntegration (auth = app_auth )
82+ installation_token = gi .get_access_token (int (gh_app_installation_id ))
83+ return installation_token .token
84+ except Exception as e : # pylint: disable=broad-exception-caught
11285 print (f"Request failed: { e } " )
11386 return None
114- return response .json ().get ("token" )
11587
11688
11789def get_team_members (
118- github_connection : github3 . GitHub ,
90+ github_connection : Github ,
11991 org : str ,
12092 team_slug : str ,
12193) -> list [str ]:
12294 """
12395 Fetch the members of a GitHub team by slug.
12496
12597 Args:
126- github_connection: Authenticated github3 connection.
98+ github_connection: Authenticated GitHub connection.
12799 org: The organization that owns the team.
128100 team_slug: The team slug (e.g., "nux-reviewers").
129101
@@ -132,21 +104,20 @@ def get_team_members(
132104 Returns an empty list if the team is not found or an error occurs.
133105 """
134106 try :
135- organization = github_connection .organization (org )
107+ organization = github_connection .get_organization (org )
136108 if not organization :
137109 print (f" ⚠️ Organization '{ org } ' not found, skipping team '{ team_slug } '" )
138110 return []
139111
140- # team_by_name accepts a slug despite its name (hits /orgs/{org}/teams/{slug})
141- team = organization .team_by_name (team_slug )
112+ team = organization .get_team_by_slug (team_slug )
142113 if not team :
143114 print (
144115 f" ⚠️ Team '{ team_slug } ' not found in '{ org } ', "
145116 "skipping (check token permissions: read:org)"
146117 )
147118 return []
148119
149- members = [m .login for m in team .members ()]
120+ members = [m .login for m in team .get_members ()]
150121 print (f" Resolved team { org } /{ team_slug } : { len (members )} member(s)" )
151122 return members
152123 except Exception as e : # pylint: disable=broad-except
0 commit comments