teams.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """W&B Public API for managing teams and team members.
  2. This module provides classes for managing W&B teams and their members.
  3. Note:
  4. This module is part of the W&B Public API and provides methods to manage
  5. teams and their members. Team management operations require appropriate
  6. permissions.
  7. """
  8. from __future__ import annotations
  9. from collections.abc import Mapping
  10. from typing import TYPE_CHECKING, Any
  11. from typing_extensions import Self
  12. from wandb_gql import gql
  13. from wandb.apis.attrs import Attrs
  14. if TYPE_CHECKING:
  15. from .api import Api, RetryingClient
  16. class Member(Attrs):
  17. """A member of a team.
  18. Args:
  19. client (`wandb.apis.internal.Api`): The client instance to use
  20. team (str): The name of the team this member belongs to
  21. attrs (dict): The member attributes
  22. """
  23. def __init__(self, client: RetryingClient, team: str, attrs: Mapping[str, Any]):
  24. super().__init__(attrs)
  25. self._client = client
  26. self.team = team
  27. def delete(self):
  28. """Remove a member from a team.
  29. Returns:
  30. Boolean indicating success
  31. """
  32. from requests import HTTPError
  33. from wandb.apis._generated import DELETE_INVITE_GQL, DeleteInvite
  34. try:
  35. data = self._client.execute(
  36. gql(DELETE_INVITE_GQL), {"id": self.id, "entity": self.team}
  37. )
  38. except HTTPError:
  39. return False
  40. else:
  41. result = DeleteInvite.model_validate(data).result
  42. return (result is not None) and result.success
  43. def __repr__(self):
  44. return f"<Member {self.name} ({self.account_type})>"
  45. class Team(Attrs):
  46. """A class that represents a W&B team.
  47. This class provides methods to manage W&B teams, including creating teams,
  48. inviting members, and managing service accounts. It inherits from Attrs
  49. to handle team attributes.
  50. Args:
  51. client (`wandb.apis.public.Api`): The api instance to use
  52. name (str): The name of the team
  53. attrs (dict): Optional dictionary of team attributes
  54. Note:
  55. Team management requires appropriate permissions.
  56. """
  57. def __init__(
  58. self,
  59. client: RetryingClient,
  60. name: str,
  61. attrs: Mapping[str, Any] | None = None,
  62. ):
  63. super().__init__(attrs or {})
  64. self._client = client
  65. self.name = name
  66. self.load()
  67. @classmethod
  68. def create(cls, api: Api, team: str, admin_username: str | None = None) -> Self:
  69. """Create a new team.
  70. Args:
  71. api: (`Api`) The api instance to use
  72. team: (str) The name of the team
  73. admin_username: (str) optional username of the admin user of the team, defaults to the current user.
  74. Returns:
  75. A `Team` object
  76. """
  77. from requests import HTTPError
  78. from wandb.apis._generated import CREATE_TEAM_GQL
  79. try:
  80. api.client.execute(
  81. gql(CREATE_TEAM_GQL),
  82. {"teamName": team, "teamAdminUserName": admin_username},
  83. )
  84. except HTTPError:
  85. pass
  86. return cls(api.client, team)
  87. def invite(self, username_or_email: str, admin: bool = False) -> bool:
  88. """Invite a user to a team.
  89. Args:
  90. username_or_email: (str) The username or email address of the user
  91. you want to invite.
  92. admin: (bool) Whether to make this user a team admin.
  93. Defaults to `False`.
  94. Returns:
  95. `True` on success, `False` if user was already invited or didn't exist.
  96. """
  97. from requests import HTTPError
  98. from wandb.apis._generated import CREATE_INVITE_GQL
  99. variables = {
  100. "entity": self.name,
  101. "admin": admin,
  102. ("email" if ("@" in username_or_email) else "username"): username_or_email,
  103. }
  104. try:
  105. self._client.execute(gql(CREATE_INVITE_GQL), variables)
  106. except HTTPError:
  107. return False
  108. return True
  109. def create_service_account(self, description: str) -> Member | None:
  110. """Create a service account for the team.
  111. Args:
  112. description: (str) A description for this service account
  113. Returns:
  114. The service account `Member` object, or None on failure
  115. """
  116. from requests import HTTPError
  117. from wandb.apis._generated import CREATE_SERVICE_ACCOUNT_GQL
  118. try:
  119. self._client.execute(
  120. gql(CREATE_SERVICE_ACCOUNT_GQL),
  121. {"entity": self.name, "description": description},
  122. )
  123. self.load(True)
  124. return self.members[-1]
  125. except HTTPError:
  126. return None
  127. def load(self, force: bool = False) -> dict[str, Any]:
  128. """Return members that belong to a team.
  129. <!-- lazydoc-ignore: internal -->
  130. """
  131. from wandb.apis._generated import GET_TEAM_ENTITY_GQL, GetTeamEntity
  132. if force or not self._attrs:
  133. data = self._client.execute(gql(GET_TEAM_ENTITY_GQL), {"name": self.name})
  134. result = GetTeamEntity.model_validate(data)
  135. self._attrs = entity.model_dump() if (entity := result.entity) else {}
  136. self._attrs["members"] = [
  137. Member(self._client, self.name, member)
  138. for member in self._attrs["members"]
  139. ]
  140. return self._attrs
  141. def __repr__(self) -> str:
  142. return f"<Team {self.name}>"