|
1 | 1 | from collections.abc import Callable |
| 2 | +from concurrent.futures import ThreadPoolExecutor |
2 | 3 | from typing import Literal |
3 | 4 |
|
4 | 5 | from github.GithubException import UnknownObjectException |
|
17 | 18 |
|
18 | 19 |
|
19 | 20 | class GithubSynchronizer(AbstractSynchronizer): |
| 21 | + MAX_WORKERS = ( |
| 22 | + 5 # Maximum number of concurrent workers, 5 seems good but is not tested. |
| 23 | + ) |
| 24 | + |
20 | 25 | ADMIN_SUFFIX = " Admins" |
21 | 26 |
|
22 | 27 | # We can have all teams visible to all members of the organization. |
@@ -231,32 +236,42 @@ def sync_members_to_team( |
231 | 236 | github_team.name, |
232 | 237 | ) |
233 | 238 |
|
234 | | - # Check every member who are not invited to the team since |
235 | | - # we also need to check existing members for their roles. |
236 | | - # This does mean that invited members might not have the correct role, but |
237 | | - # it can be corrected during the sync after the invitation is accepted anyways. |
238 | | - for username in self.subtract_invited_members(members, github_team): |
239 | | - try: |
240 | | - current_role = github_team.get_team_membership(username).role |
241 | | - |
242 | | - # Skip syncing organization owners' role to member since their role |
243 | | - # will always remain as maintainer. |
244 | | - # https://github.com/orgs/community/discussions/140675#discussioncomment-10875640 |
245 | | - if username in self.org_owners and role == "member": |
246 | | - continue |
| 239 | + # Haven't find a way to batch retrieve membership roles, |
| 240 | + # so we use a thread pool to sync each member to optimize the performance. |
| 241 | + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: |
| 242 | + # Check every member who are not invited to the team since |
| 243 | + # we also need to check existing members for their roles. |
| 244 | + # This does mean that invited members might not have the correct role, but |
| 245 | + # it will be corrected in the sync after the invitation is accepted anyways. |
| 246 | + for username in self.subtract_invited_members(members, github_team): |
| 247 | + executor.submit(self.sync_member_to_team, github_team, username, role) |
| 248 | + |
| 249 | + def sync_member_to_team( |
| 250 | + self, |
| 251 | + github_team: GithubTeam, |
| 252 | + username: str, |
| 253 | + role: Literal["member", "maintainer"], |
| 254 | + ) -> None: |
| 255 | + """Sync the member to the Github team as the given role.""" |
| 256 | + try: |
| 257 | + current_role = github_team.get_team_membership(username).role |
247 | 258 |
|
248 | | - if current_role != role: |
249 | | - self.add_or_update_member_to_team(github_team, username, role) |
| 259 | + # Skip syncing organization owners' role to member since their role |
| 260 | + # will always remain as maintainer. |
| 261 | + # https://github.com/orgs/community/discussions/140675#discussioncomment-10875640 |
| 262 | + if username in self.org_owners and role == "member": |
| 263 | + return |
250 | 264 |
|
251 | | - except UnknownObjectException: |
| 265 | + if current_role != role: |
252 | 266 | self.add_or_update_member_to_team(github_team, username, role) |
253 | | - |
254 | | - except Exception: |
255 | | - self.logger.exception( |
256 | | - "Error syncing %s to %s GitHub team.", |
257 | | - username, |
258 | | - github_team.name, |
259 | | - ) |
| 267 | + except UnknownObjectException: |
| 268 | + self.add_or_update_member_to_team(github_team, username, role) |
| 269 | + except Exception: |
| 270 | + self.logger.exception( |
| 271 | + "Error syncing %s to %s GitHub team.", |
| 272 | + username, |
| 273 | + github_team.name, |
| 274 | + ) |
260 | 275 |
|
261 | 276 | def subtract_invited_members( |
262 | 277 | self, members: set[str], github_team: GithubTeam |
|
0 commit comments