projects.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. """W&B Public API for Project objects.
  2. This module provides classes for interacting with W&B projects and their
  3. associated data.
  4. Example:
  5. ```python
  6. from wandb.apis.public import Api
  7. # Get all projects for an entity
  8. projects = Api().projects("entity")
  9. # Access project data
  10. for project in projects:
  11. print(f"Project: {project.name}")
  12. print(f"URL: {project.url}")
  13. # Get artifact types
  14. for artifact_type in project.artifacts_types():
  15. print(f"Artifact Type: {artifact_type.name}")
  16. # Get sweeps
  17. for sweep in project.sweeps():
  18. print(f"Sweep ID: {sweep.id}")
  19. print(f"State: {sweep.state}")
  20. ```
  21. Note:
  22. This module is part of the W&B Public API and provides methods to access
  23. and manage projects. For creating new projects, use wandb.init()
  24. with a new project name.
  25. """
  26. from __future__ import annotations
  27. from collections.abc import Mapping
  28. from typing import TYPE_CHECKING, Any, ClassVar
  29. from typing_extensions import override
  30. from wandb_gql import gql
  31. from wandb._strutils import nameof
  32. from wandb.apis import public
  33. from wandb.apis.attrs import Attrs
  34. from wandb.apis.normalize import normalize_exceptions
  35. from wandb.apis.paginator import RelayPaginator
  36. from wandb.apis.public.api import RetryingClient
  37. from wandb.apis.public.sweeps import Sweeps
  38. from wandb.sdk.lib import ipython
  39. if TYPE_CHECKING:
  40. from wandb_graphql.language.ast import Document
  41. from wandb._pydantic import Connection
  42. from wandb.apis._generated import ProjectFragment
  43. class Projects(RelayPaginator["ProjectFragment", "Project"]):
  44. """An lazy iterator of `Project` objects.
  45. An iterable interface to access projects created and saved by the entity.
  46. Args:
  47. client (`wandb.apis.internal.Api`): The API client instance to use.
  48. entity (str): The entity name (username or team) to fetch projects for.
  49. per_page (int): Number of projects to fetch per request (default is 50).
  50. Example:
  51. ```python
  52. from wandb.apis.public.api import Api
  53. # Find projects that belong to this entity
  54. projects = Api().projects(entity="entity")
  55. # Iterate over files
  56. for project in projects:
  57. print(f"Project: {project.name}")
  58. print(f"- URL: {project.url}")
  59. print(f"- Created at: {project.created_at}")
  60. print(f"- Is benchmark: {project.is_benchmark}")
  61. ```
  62. """
  63. QUERY: ClassVar[Document | None] = None
  64. last_response: Connection[ProjectFragment] | None
  65. def __init__(
  66. self,
  67. client: RetryingClient,
  68. entity: str,
  69. per_page: int = 50,
  70. ) -> Projects:
  71. """An iterable collection of `Project` objects.
  72. Args:
  73. client: The API client used to query W&B.
  74. entity: The entity which owns the projects.
  75. per_page: The number of projects to fetch per request to the API.
  76. """
  77. if self.QUERY is None:
  78. from wandb.apis._generated import GET_PROJECTS_GQL
  79. type(self).QUERY = gql(GET_PROJECTS_GQL)
  80. self.entity = entity
  81. super().__init__(client, variables={"entity": entity}, per_page=per_page)
  82. @override
  83. def _update_response(self) -> None:
  84. """Fetch and validate the response data for the current page."""
  85. from wandb._pydantic import Connection
  86. from wandb.apis._generated import GetProjects, ProjectFragment
  87. data = self.client.execute(self.QUERY, variable_values=self.variables)
  88. result = GetProjects.model_validate(data)
  89. if not (conn := result.models):
  90. raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
  91. self.last_response = Connection[ProjectFragment].model_validate(conn)
  92. @property
  93. def length(self) -> None:
  94. """Returns the total number of projects.
  95. Note: This property is not available for projects.
  96. <!-- lazydoc-ignore: internal -->
  97. """
  98. # For backwards compatibility, even though this isn't a SizedPaginator
  99. return None
  100. def _convert(self, node: ProjectFragment) -> Project:
  101. return Project(self.client, self.entity, node.name, node.model_dump())
  102. def __repr__(self):
  103. return f"<Projects {self.entity}>"
  104. class Project(Attrs):
  105. """A project is a namespace for runs.
  106. Args:
  107. client: W&B API client instance.
  108. name (str): The name of the project.
  109. entity (str): The entity name that owns the project.
  110. """
  111. def __init__(
  112. self,
  113. client: RetryingClient,
  114. entity: str,
  115. project: str,
  116. attrs: Mapping[str, Any],
  117. ) -> Project:
  118. """A single project associated with an entity.
  119. Args:
  120. client: The API client used to query W&B.
  121. entity: The entity which owns the project.
  122. project: The name of the project to query.
  123. attrs: The attributes of the project.
  124. """
  125. super().__init__(attrs)
  126. self._is_loaded = bool(attrs)
  127. self.client = client
  128. self.name = project
  129. self.entity = entity
  130. def _load(self) -> None:
  131. from requests import HTTPError
  132. from wandb.apis._generated import GET_PROJECT_GQL, GetProject
  133. gql_vars = {"name": self.name, "entity": self.entity}
  134. try:
  135. data = self.client.execute(gql(GET_PROJECT_GQL), gql_vars)
  136. except HTTPError as e:
  137. raise ValueError(f"Unable to fetch project ID: {gql_vars!r}") from e
  138. project = GetProject.model_validate(data).project
  139. self._attrs = project.model_dump() if project else {}
  140. self._is_loaded = True
  141. @property
  142. def owner(self) -> public.User:
  143. """Returns the project owner as a User object.
  144. Raises:
  145. ValueError: when no user information is found for the project.
  146. """
  147. if not self._is_loaded:
  148. self._load()
  149. if "user" not in self._attrs:
  150. raise ValueError(f"No user found for project {self.name}")
  151. return public.User(self.client, self._attrs["user"])
  152. @property
  153. def path(self) -> list[str]:
  154. """Returns the path of the project. The path is a list containing the
  155. entity and project name."""
  156. return [self.entity, self.name]
  157. @property
  158. def url(self) -> str:
  159. """Returns the URL of the project."""
  160. return self.client.app_url + "/".join(self.path + ["workspace"])
  161. def to_html(self, height: int = 420, hidden: bool = False) -> str:
  162. """Generate HTML containing an iframe displaying this project.
  163. <!-- lazydoc-ignore: internal -->
  164. """
  165. url = self.url + "?jupyter=true"
  166. style = f"border:none;width:100%;height:{height}px;"
  167. prefix = ""
  168. if hidden:
  169. style += "display:none;"
  170. prefix = ipython.toggle_button("project")
  171. return prefix + f"<iframe src={url!r} style={style!r}></iframe>"
  172. def _repr_html_(self) -> str:
  173. return self.to_html()
  174. def __repr__(self):
  175. return "<Project {}>".format("/".join(self.path))
  176. @normalize_exceptions
  177. def artifacts_types(self, per_page: int = 50) -> public.ArtifactTypes:
  178. """Returns all artifact types associated with this project."""
  179. return public.ArtifactTypes(self.client, self.entity, self.name)
  180. @normalize_exceptions
  181. def collections(
  182. self,
  183. filters: Mapping[str, Any] | None = None,
  184. order: str | None = None,
  185. per_page: int = 50,
  186. ) -> public.ProjectArtifactCollections:
  187. """Returns all artifact collections associated with this project.
  188. Args:
  189. filters: Optional mapping of filters to apply to the query.
  190. order: Optional string to specify the order of the results.
  191. If you prepend order with a + order is ascending (default).
  192. If you prepend order with a - order is descending.
  193. per_page: The number of artifact collections to fetch per page.
  194. Default is 50.
  195. """
  196. return public.ProjectArtifactCollections(
  197. self.client,
  198. self.entity,
  199. self.name,
  200. filters=filters,
  201. order=order,
  202. per_page=per_page,
  203. )
  204. @normalize_exceptions
  205. def sweeps(self, per_page: int = 50) -> Sweeps:
  206. """Return a paginated collection of sweeps in this project.
  207. Args:
  208. per_page: The number of sweeps to fetch per request to the API.
  209. Returns:
  210. A `Sweeps` object, which is an iterable collection of `Sweep` objects.
  211. """
  212. return Sweeps(self.client, self.entity, self.name, per_page=per_page)
  213. @property
  214. def id(self) -> str:
  215. if not self._is_loaded:
  216. self._load()
  217. if "id" not in self._attrs:
  218. raise ValueError(f"Project {self.name} not found")
  219. return self._attrs["id"]
  220. @override
  221. def __getattr__(self, name: str) -> Any:
  222. if not self._is_loaded:
  223. self._load()
  224. return super().__getattr__(name)