Skip to content

Commit f0562fe

Browse files
committed
Reduce the number of API calls for getting all members
Reduced from `2 x members` (which was >600 for us) to 3 (best case; worst case `1 x members`)
1 parent fa135fa commit f0562fe

File tree

1 file changed

+35
-65
lines changed

1 file changed

+35
-65
lines changed

api/src/multiaccessy/accessy.py

Lines changed: 35 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,42 @@ def get_member_id_from_accessy_id(self, accessy_id: UUID) -> Optional[int]:
240240
def get_all_members(self) -> list[AccessyMember]:
241241
"""Get a list of all Accessy members in the ORG with GROUPS (lab and special)"""
242242

243-
org_member_ids = set(item["id"] for item in self._get_users_org())
243+
def fill_in_group_affiliation_and_membership_id(members: list[AccessyMember], group: UUID) -> None:
244+
group_members = self._get_users_in_access_group(group)
245+
userId_to_group_member = {m.userId: m for m in group_members}
246+
for member in members:
247+
assert member.user_id is not None
248+
membership = userId_to_group_member.get(member.user_id, None)
249+
250+
if not membership:
251+
continue
252+
253+
member.groups.add(group)
254+
if member.membership_id is None:
255+
member.membership_id = membership.id
256+
elif member.membership_id != membership.id:
257+
raise AccessyError(
258+
f"User {member.name!r} {member.user_id=} has different membership_id for different groups"
259+
f" ({member.membership_id} != {membership.id}). This is a bug"
260+
)
261+
262+
def fill_in_membership_id_if_missing(member: AccessyMember) -> None:
263+
if member.membership_id is None:
264+
member.membership_id = self._get(
265+
f"/asset/admin/user/{member.user_id}/organization/{self.organization_id()}/membership"
266+
)["id"]
267+
268+
org_members = [AccessyUser.from_dict(item) for item in self._get_users_org()]
269+
members = [
270+
AccessyMember(user_id=item.id, phone=item.msisdn, name=f"{item.firstName} {item.lastName}")
271+
for item in org_members
272+
]
273+
274+
for group in [ACCESSY_LABACCESS_GROUP, ACCESSY_SPECIAL_LABACCESS_GROUP]:
275+
fill_in_group_affiliation_and_membership_id(members, group)
244276

245-
members = self._user_ids_to_accessy_members(org_member_ids)
246-
self._populate_user_groups(members)
277+
for m in members:
278+
fill_in_membership_id_if_missing(m)
247279

248280
return members
249281

@@ -510,68 +542,6 @@ def _get_group_description(self, group_id: UUID) -> str:
510542
assert isinstance(name, str)
511543
return name
512544

513-
def _user_ids_to_accessy_members(self, user_ids: Iterable[UUID]) -> list[AccessyMember]:
514-
"""Convert a list of User ID:s to AccessyMembers"""
515-
516-
APPLICATION_PHONE_NUMBER = object() # Sentinel phone number for applications
517-
518-
def fill_user_details(user: AccessyMember) -> None:
519-
assert user.user_id is not None
520-
data = self.get_user_details(user.user_id)
521-
522-
# API keys do not have phone numbers, set it to sentinel object so we can filter out API keys further down
523-
if data.application:
524-
user.phone = APPLICATION_PHONE_NUMBER
525-
return
526-
527-
if data.msisdn is not None:
528-
user.phone = data.msisdn
529-
else:
530-
logger.warning(f"User {user.user_id} does not have a phone number in accessy. {data=}")
531-
user.name = f"{data.firstName} {data.lastName}"
532-
533-
def fill_membership_id(user: AccessyMember) -> None:
534-
data = self._get(f"/asset/admin/user/{user.user_id}/organization/{self.organization_id()}/membership")
535-
user.membership_id = data["id"]
536-
537-
exception_during_fetch = None
538-
threads = []
539-
user_ids = list(user_ids)
540-
thread_count = min(4, len(user_ids))
541-
accessy_members: List[AccessyMember] = []
542-
for i in range(thread_count):
543-
# Chunk the user_ids into equal parts for each thread
544-
slice = user_ids[i::thread_count]
545-
member_slice = [AccessyMember(user_id=uid) for uid in slice]
546-
accessy_members.extend(member_slice)
547-
548-
# Start a thread for each chunk
549-
def worker(member_slice: list[AccessyMember]) -> None:
550-
nonlocal exception_during_fetch
551-
try:
552-
for member in member_slice:
553-
fill_user_details(member)
554-
fill_membership_id(member)
555-
except AccessyError as e:
556-
exception_during_fetch = e
557-
558-
t = threading.Thread(target=worker, args=(member_slice,))
559-
threads.append(t)
560-
t.start()
561-
562-
for t in threads:
563-
t.join()
564-
565-
# Filter out API keys
566-
accessy_members = [m for m in accessy_members if m.phone is not APPLICATION_PHONE_NUMBER]
567-
568-
if exception_during_fetch:
569-
raise RuntimeError(
570-
f"One or more threads failed to fetch user details. The last exception as: {exception_during_fetch}"
571-
)
572-
573-
return accessy_members
574-
575545
def _user_id_to_accessy_member(self, user_id: UUID) -> AccessyMember | None:
576546
"""Convert an Accessy User ID to an AccessyMember object"""
577547

0 commit comments

Comments
 (0)