wandb 0.19.8__py3-none-win_amd64.whl → 0.19.10__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. wandb/__init__.py +5 -1
  2. wandb/__init__.pyi +15 -8
  3. wandb/_pydantic/__init__.py +30 -0
  4. wandb/_pydantic/base.py +148 -0
  5. wandb/_pydantic/utils.py +66 -0
  6. wandb/_pydantic/v1_compat.py +284 -0
  7. wandb/apis/paginator.py +82 -38
  8. wandb/apis/public/__init__.py +2 -2
  9. wandb/apis/public/api.py +111 -53
  10. wandb/apis/public/artifacts.py +387 -639
  11. wandb/apis/public/automations.py +69 -0
  12. wandb/apis/public/files.py +2 -2
  13. wandb/apis/public/integrations.py +168 -0
  14. wandb/apis/public/projects.py +32 -2
  15. wandb/apis/public/reports.py +2 -2
  16. wandb/apis/public/runs.py +19 -11
  17. wandb/apis/public/utils.py +107 -1
  18. wandb/automations/__init__.py +81 -0
  19. wandb/automations/_filters/__init__.py +40 -0
  20. wandb/automations/_filters/expressions.py +179 -0
  21. wandb/automations/_filters/operators.py +267 -0
  22. wandb/automations/_filters/run_metrics.py +183 -0
  23. wandb/automations/_generated/__init__.py +184 -0
  24. wandb/automations/_generated/create_filter_trigger.py +21 -0
  25. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  26. wandb/automations/_generated/delete_trigger.py +19 -0
  27. wandb/automations/_generated/enums.py +33 -0
  28. wandb/automations/_generated/fragments.py +343 -0
  29. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  30. wandb/automations/_generated/get_triggers.py +24 -0
  31. wandb/automations/_generated/get_triggers_by_entity.py +24 -0
  32. wandb/automations/_generated/input_types.py +104 -0
  33. wandb/automations/_generated/integrations_by_entity.py +22 -0
  34. wandb/automations/_generated/operations.py +710 -0
  35. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  36. wandb/automations/_generated/update_filter_trigger.py +21 -0
  37. wandb/automations/_utils.py +123 -0
  38. wandb/automations/_validators.py +73 -0
  39. wandb/automations/actions.py +205 -0
  40. wandb/automations/automations.py +109 -0
  41. wandb/automations/events.py +235 -0
  42. wandb/automations/integrations.py +26 -0
  43. wandb/automations/scopes.py +76 -0
  44. wandb/beta/workflows.py +9 -10
  45. wandb/bin/gpu_stats.exe +0 -0
  46. wandb/bin/wandb-core +0 -0
  47. wandb/cli/cli.py +3 -3
  48. wandb/integration/keras/keras.py +2 -1
  49. wandb/integration/langchain/wandb_tracer.py +2 -1
  50. wandb/integration/metaflow/metaflow.py +19 -17
  51. wandb/integration/sacred/__init__.py +1 -1
  52. wandb/jupyter.py +155 -133
  53. wandb/old/summary.py +0 -2
  54. wandb/proto/v3/wandb_internal_pb2.py +297 -292
  55. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  56. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  57. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  58. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  59. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  60. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  61. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  62. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  63. wandb/proto/v6/wandb_base_pb2.py +41 -0
  64. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  65. wandb/proto/v6/wandb_server_pb2.py +78 -0
  66. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  67. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  68. wandb/proto/wandb_base_pb2.py +2 -0
  69. wandb/proto/wandb_deprecated.py +10 -0
  70. wandb/proto/wandb_internal_pb2.py +3 -1
  71. wandb/proto/wandb_server_pb2.py +2 -0
  72. wandb/proto/wandb_settings_pb2.py +2 -0
  73. wandb/proto/wandb_telemetry_pb2.py +2 -0
  74. wandb/sdk/artifacts/_generated/__init__.py +248 -0
  75. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  76. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  77. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  78. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  79. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  80. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  81. wandb/sdk/artifacts/_generated/enums.py +17 -0
  82. wandb/sdk/artifacts/_generated/fragments.py +186 -0
  83. wandb/sdk/artifacts/_generated/input_types.py +16 -0
  84. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  85. wandb/sdk/artifacts/_generated/operations.py +510 -0
  86. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  87. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  88. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  89. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  90. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  91. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  92. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  93. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  94. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  95. wandb/sdk/artifacts/_graphql_fragments.py +56 -81
  96. wandb/sdk/artifacts/_validators.py +1 -0
  97. wandb/sdk/artifacts/artifact.py +110 -49
  98. wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
  99. wandb/sdk/artifacts/artifact_saver.py +16 -2
  100. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  101. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
  102. wandb/sdk/data_types/audio.py +1 -3
  103. wandb/sdk/data_types/base_types/media.py +13 -7
  104. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  105. wandb/sdk/data_types/html.py +36 -9
  106. wandb/sdk/data_types/image.py +56 -37
  107. wandb/sdk/data_types/molecule.py +1 -5
  108. wandb/sdk/data_types/object_3d.py +2 -1
  109. wandb/sdk/data_types/saved_model.py +7 -9
  110. wandb/sdk/data_types/table.py +5 -0
  111. wandb/sdk/data_types/trace_tree.py +2 -0
  112. wandb/sdk/data_types/utils.py +1 -1
  113. wandb/sdk/data_types/video.py +15 -30
  114. wandb/sdk/interface/interface.py +2 -0
  115. wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
  116. wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
  117. wandb/sdk/internal/internal_api.py +138 -47
  118. wandb/sdk/internal/profiler.py +6 -5
  119. wandb/sdk/internal/run.py +13 -6
  120. wandb/sdk/internal/sender.py +2 -0
  121. wandb/sdk/internal/sender_config.py +8 -11
  122. wandb/sdk/internal/settings_static.py +24 -2
  123. wandb/sdk/lib/apikey.py +40 -20
  124. wandb/sdk/lib/asyncio_compat.py +1 -1
  125. wandb/sdk/lib/deprecate.py +13 -22
  126. wandb/sdk/lib/disabled.py +2 -1
  127. wandb/sdk/lib/printer.py +37 -8
  128. wandb/sdk/lib/printer_asyncio.py +46 -0
  129. wandb/sdk/lib/redirect.py +10 -5
  130. wandb/sdk/lib/run_moment.py +4 -6
  131. wandb/sdk/lib/wb_logging.py +161 -0
  132. wandb/sdk/service/server_sock.py +19 -14
  133. wandb/sdk/service/service.py +9 -7
  134. wandb/sdk/service/streams.py +5 -0
  135. wandb/sdk/verify/verify.py +6 -3
  136. wandb/sdk/wandb_config.py +44 -43
  137. wandb/sdk/wandb_init.py +323 -141
  138. wandb/sdk/wandb_login.py +13 -4
  139. wandb/sdk/wandb_metadata.py +107 -91
  140. wandb/sdk/wandb_run.py +529 -325
  141. wandb/sdk/wandb_settings.py +422 -202
  142. wandb/sdk/wandb_setup.py +52 -1
  143. wandb/util.py +29 -29
  144. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/METADATA +7 -7
  145. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/RECORD +151 -94
  146. wandb/_globals.py +0 -19
  147. wandb/apis/public/_generated/base.py +0 -128
  148. wandb/apis/public/_generated/typing_compat.py +0 -14
  149. /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
  150. /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
  151. /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
  152. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
  153. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
  154. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from itertools import chain
4
+ from typing import TYPE_CHECKING, Any, Iterable, Mapping
5
+
6
+ from pydantic import ValidationError
7
+ from typing_extensions import override
8
+ from wandb_graphql.language.ast import Document
9
+
10
+ from wandb.apis.paginator import Paginator, _Client
11
+
12
+ if TYPE_CHECKING:
13
+ from wandb.automations import Automation
14
+ from wandb.automations._generated import ProjectConnectionFields
15
+
16
+
17
+ class Automations(Paginator["Automation"]):
18
+ last_response: ProjectConnectionFields | None
19
+ _query: Document
20
+
21
+ def __init__(
22
+ self,
23
+ client: _Client,
24
+ variables: Mapping[str, Any],
25
+ per_page: int = 50,
26
+ _query: Document | None = None,
27
+ ):
28
+ super().__init__(client, variables, per_page=per_page)
29
+ if _query is None:
30
+ raise RuntimeError(f"Query required for {type(self).__qualname__}")
31
+ self._query = _query
32
+
33
+ @property
34
+ def more(self) -> bool:
35
+ """Whether there are more items to fetch."""
36
+ if self.last_response is None:
37
+ return True
38
+ return self.last_response.page_info.has_next_page
39
+
40
+ @property
41
+ def cursor(self) -> str | None:
42
+ """The start cursor to use for the next page."""
43
+ if self.last_response is None:
44
+ return None
45
+ return self.last_response.page_info.end_cursor
46
+
47
+ @override
48
+ def _update_response(self) -> None:
49
+ """Fetch the raw response data for the current page."""
50
+ from wandb.automations._generated import ProjectConnectionFields
51
+
52
+ data: dict[str, Any] = self.client.execute(
53
+ self._query, variable_values=self.variables
54
+ )
55
+ try:
56
+ page_data = data["searchScope"]["projects"]
57
+ self.last_response = ProjectConnectionFields.model_validate(page_data)
58
+ except (LookupError, AttributeError, ValidationError) as e:
59
+ raise ValueError("Unexpected response data") from e
60
+
61
+ def convert_objects(self) -> Iterable[Automation]:
62
+ """Parse the page data into a list of objects."""
63
+ from wandb.automations import Automation
64
+
65
+ page = self.last_response
66
+ return [
67
+ Automation.model_validate_json(obj.model_dump_json())
68
+ for obj in chain.from_iterable(edge.node.triggers for edge in page.edges)
69
+ ]
@@ -12,7 +12,7 @@ import wandb
12
12
  from wandb import util
13
13
  from wandb.apis.attrs import Attrs
14
14
  from wandb.apis.normalize import normalize_exceptions
15
- from wandb.apis.paginator import Paginator
15
+ from wandb.apis.paginator import SizedPaginator
16
16
  from wandb.apis.public import utils
17
17
  from wandb.apis.public.api import Api
18
18
  from wandb.apis.public.const import RETRY_TIMEDELTA
@@ -41,7 +41,7 @@ FILE_FRAGMENT = """fragment RunFilesFragment on Run {
41
41
  }"""
42
42
 
43
43
 
44
- class Files(Paginator):
44
+ class Files(SizedPaginator["File"]):
45
45
  """An iterable collection of `File` objects."""
46
46
 
47
47
  QUERY = gql(
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Iterable
4
+
5
+ from pydantic import ValidationError
6
+ from typing_extensions import override
7
+ from wandb_gql import gql
8
+ from wandb_graphql.language.ast import Document
9
+
10
+ from wandb.apis.paginator import Paginator
11
+
12
+ if TYPE_CHECKING:
13
+ from wandb.apis.paginator import _Client
14
+ from wandb.automations import Integration, SlackIntegration, WebhookIntegration
15
+ from wandb.automations._generated import (
16
+ GenericWebhookIntegrationConnectionFields,
17
+ IntegrationConnectionFields,
18
+ SlackIntegrationConnectionFields,
19
+ )
20
+
21
+
22
+ class Integrations(Paginator["Integration"]):
23
+ last_response: IntegrationConnectionFields | None
24
+ _query: Document
25
+
26
+ def __init__(self, client: _Client, variables: dict[str, Any], per_page: int = 50):
27
+ from wandb.automations._generated import INTEGRATIONS_BY_ENTITY_GQL
28
+
29
+ super().__init__(client, variables, per_page=per_page)
30
+ # All integrations for entity
31
+ self._query = gql(INTEGRATIONS_BY_ENTITY_GQL)
32
+
33
+ @property
34
+ def more(self) -> bool:
35
+ """Whether there are more Integrations to fetch."""
36
+ if self.last_response is None:
37
+ return True
38
+ return self.last_response.page_info.has_next_page
39
+
40
+ @property
41
+ def cursor(self) -> str | None:
42
+ """The start cursor to use for the next page."""
43
+ if self.last_response is None:
44
+ return None
45
+ return self.last_response.page_info.end_cursor
46
+
47
+ @override
48
+ def _update_response(self) -> None:
49
+ """Fetch and parse the response data for the current page."""
50
+ from wandb.automations._generated import IntegrationConnectionFields
51
+
52
+ data: dict[str, Any] = self.client.execute(
53
+ self._query, variable_values=self.variables
54
+ )
55
+ try:
56
+ page_data = data["entity"]["integrations"]
57
+ self.last_response = IntegrationConnectionFields.model_validate(page_data)
58
+ except (LookupError, AttributeError, ValidationError) as e:
59
+ raise ValueError("Unexpected response data") from e
60
+
61
+ def convert_objects(self) -> Iterable[Integration]:
62
+ """Parse the page data into a list of Integrations."""
63
+ from wandb.automations.integrations import _IntegrationEdge
64
+
65
+ page = self.last_response
66
+ return [_IntegrationEdge.model_validate(edge).node for edge in page.edges]
67
+
68
+
69
+ class WebhookIntegrations(Paginator["WebhookIntegration"]):
70
+ last_response: GenericWebhookIntegrationConnectionFields | None
71
+ _query: Document
72
+
73
+ def __init__(self, client: _Client, variables: dict[str, Any], per_page: int = 50):
74
+ from wandb.automations._generated import (
75
+ GENERIC_WEBHOOK_INTEGRATIONS_BY_ENTITY_GQL,
76
+ )
77
+
78
+ super().__init__(client, variables, per_page=per_page)
79
+ # Webhook integrations for entity
80
+ self._query = gql(GENERIC_WEBHOOK_INTEGRATIONS_BY_ENTITY_GQL)
81
+
82
+ @property
83
+ def more(self) -> bool:
84
+ """Whether there are more Integrations to fetch."""
85
+ if self.last_response is None:
86
+ return True
87
+ return self.last_response.page_info.has_next_page
88
+
89
+ @property
90
+ def cursor(self) -> str | None:
91
+ """The start cursor to use for the next page."""
92
+ if self.last_response is None:
93
+ return None
94
+ return self.last_response.page_info.end_cursor
95
+
96
+ @override
97
+ def _update_response(self) -> None:
98
+ """Fetch and parse the response data for the current page."""
99
+ from wandb.automations._generated import (
100
+ GenericWebhookIntegrationConnectionFields,
101
+ )
102
+
103
+ data: dict[str, Any] = self.client.execute(
104
+ self._query, variable_values=self.variables
105
+ )
106
+ try:
107
+ page_data = data["entity"]["integrations"]
108
+ self.last_response = (
109
+ GenericWebhookIntegrationConnectionFields.model_validate(page_data)
110
+ )
111
+ except (LookupError, AttributeError, ValidationError) as e:
112
+ raise ValueError("Unexpected response data") from e
113
+
114
+ def convert_objects(self) -> Iterable[WebhookIntegration]:
115
+ """Parse the page data into a list of Integrations."""
116
+ from wandb.automations import WebhookIntegration
117
+
118
+ page = self.last_response
119
+ return [WebhookIntegration.model_validate(edge.node) for edge in page.edges]
120
+
121
+
122
+ class SlackIntegrations(Paginator["SlackIntegration"]):
123
+ last_response: SlackIntegrationConnectionFields | None
124
+ _query: Document
125
+
126
+ def __init__(self, client: _Client, variables: dict[str, Any], per_page: int = 50):
127
+ from wandb.automations._generated import SLACK_INTEGRATIONS_BY_ENTITY_GQL
128
+
129
+ super().__init__(client, variables, per_page=per_page)
130
+ # Slack integrations for entity
131
+ self._query = gql(SLACK_INTEGRATIONS_BY_ENTITY_GQL)
132
+
133
+ @property
134
+ def more(self) -> bool:
135
+ """Whether there are more Integrations to fetch."""
136
+ if self.last_response is None:
137
+ return True
138
+ return self.last_response.page_info.has_next_page
139
+
140
+ @property
141
+ def cursor(self) -> str | None:
142
+ """The start cursor to use for the next page."""
143
+ if self.last_response is None:
144
+ return None
145
+ return self.last_response.page_info.end_cursor
146
+
147
+ @override
148
+ def _update_response(self) -> None:
149
+ """Fetch and parse the response data for the current page."""
150
+ from wandb.automations._generated import SlackIntegrationConnectionFields
151
+
152
+ data: dict[str, Any] = self.client.execute(
153
+ self._query, variable_values=self.variables
154
+ )
155
+ try:
156
+ page_data = data["entity"]["integrations"]
157
+ self.last_response = SlackIntegrationConnectionFields.model_validate(
158
+ page_data
159
+ )
160
+ except (LookupError, AttributeError, ValidationError) as e:
161
+ raise ValueError("Unexpected response data") from e
162
+
163
+ def convert_objects(self) -> list[SlackIntegration]:
164
+ """Parse the page data into a list of Integrations."""
165
+ from wandb.automations import SlackIntegration
166
+
167
+ page = self.last_response
168
+ return [SlackIntegration.model_validate(edge.node) for edge in page.edges]
@@ -1,5 +1,8 @@
1
1
  """Public API: projects."""
2
2
 
3
+ from contextlib import suppress
4
+
5
+ from requests import HTTPError
3
6
  from wandb_gql import gql
4
7
 
5
8
  from wandb.apis import public
@@ -17,7 +20,7 @@ PROJECT_FRAGMENT = """fragment ProjectFragment on Project {
17
20
  }"""
18
21
 
19
22
 
20
- class Projects(Paginator):
23
+ class Projects(Paginator["Project"]):
21
24
  """An iterable collection of `Project` objects."""
22
25
 
23
26
  QUERY = gql(
@@ -49,7 +52,8 @@ class Projects(Paginator):
49
52
  super().__init__(client, variables, per_page)
50
53
 
51
54
  @property
52
- def length(self):
55
+ def length(self) -> None:
56
+ # For backwards compatibility, even though this isn't a SizedPaginator
53
57
  return None
54
58
 
55
59
  @property
@@ -152,3 +156,29 @@ class Project(Attrs):
152
156
  )
153
157
  for e in ret["project"]["sweeps"]["edges"]
154
158
  ]
159
+
160
+ _PROJECT_ID = gql(
161
+ """
162
+ query ProjectID($projectName: String!, $entityName: String!) {
163
+ project(name: $projectName, entityName: $entityName) {
164
+ id
165
+ }
166
+ }
167
+ """
168
+ )
169
+
170
+ @property
171
+ def id(self) -> str:
172
+ # This is a workaround to ensure that the project ID can be retrieved
173
+ # on demand, as it generally is not set or fetched on instantiation.
174
+ # This is necessary if using this project as the scope of a new Automation.
175
+ with suppress(LookupError):
176
+ return self._attrs["id"]
177
+
178
+ variable_values = {"projectName": self.name, "entityName": self.entity}
179
+ try:
180
+ data = self.client.execute(self._PROJECT_ID, variable_values)
181
+ self._attrs["id"] = data["project"]["id"]
182
+ return self._attrs["id"]
183
+ except (HTTPError, LookupError, TypeError) as e:
184
+ raise ValueError(f"Unable to fetch project ID: {variable_values!r}") from e
@@ -9,11 +9,11 @@ from wandb_gql import gql
9
9
  import wandb
10
10
  from wandb.apis import public
11
11
  from wandb.apis.attrs import Attrs
12
- from wandb.apis.paginator import Paginator
12
+ from wandb.apis.paginator import SizedPaginator
13
13
  from wandb.sdk.lib import ipython
14
14
 
15
15
 
16
- class Reports(Paginator):
16
+ class Reports(SizedPaginator["BetaReport"]):
17
17
  """Reports is an iterable collection of `BetaReport` objects."""
18
18
 
19
19
  QUERY = gql(
wandb/apis/public/runs.py CHANGED
@@ -24,7 +24,7 @@ from wandb.apis import public
24
24
  from wandb.apis.attrs import Attrs
25
25
  from wandb.apis.internal import Api as InternalApi
26
26
  from wandb.apis.normalize import normalize_exceptions
27
- from wandb.apis.paginator import Paginator
27
+ from wandb.apis.paginator import SizedPaginator
28
28
  from wandb.apis.public.const import RETRY_TIMEDELTA
29
29
  from wandb.sdk.lib import ipython, json_util, runid
30
30
  from wandb.sdk.lib.paths import LogicalPath
@@ -61,7 +61,7 @@ RUN_FRAGMENT = """fragment RunFragment on Run {
61
61
  }"""
62
62
 
63
63
 
64
- class Runs(Paginator):
64
+ class Runs(SizedPaginator["Run"]):
65
65
  """An iterable collection of runs associated with a project and optional filter.
66
66
 
67
67
  This is generally used indirectly via the `Api`.runs method.
@@ -421,16 +421,15 @@ class Run(Attrs):
421
421
  """
422
422
  query Run($project: String!, $entity: String!, $name: String!) {{
423
423
  project(name: $project, entityName: $entity) {{
424
- {}
425
424
  run(name: $name) {{
425
+ {}
426
426
  ...RunFragment
427
427
  }}
428
428
  }}
429
429
  }}
430
430
  {}
431
431
  """.format(
432
- # Only query internalId if the server supports it
433
- "internalId" if self._server_provides_internal_id_for_project() else "",
432
+ "projectId" if self._server_provides_internal_id_for_project() else "",
434
433
  RUN_FRAGMENT,
435
434
  )
436
435
  )
@@ -444,7 +443,11 @@ class Run(Attrs):
444
443
  raise ValueError("Could not find run {}".format(self))
445
444
  self._attrs = response["project"]["run"]
446
445
  self._state = self._attrs["state"]
447
- self._project_internal_id = response["project"].get("internalId", None)
446
+
447
+ self._project_internal_id = (
448
+ int(self._attrs["projectId"]) if "projectId" in self._attrs else None
449
+ )
450
+
448
451
  if self._include_sweeps and self.sweep_name and not self.sweep:
449
452
  # There may be a lot of runs. Don't bother pulling them all
450
453
  # just for the sake of this one.
@@ -847,7 +850,12 @@ class Run(Attrs):
847
850
  api.set_current_run_id(self.id)
848
851
 
849
852
  if isinstance(artifact, wandb.Artifact) and not artifact.is_draft():
850
- api.use_artifact(artifact.id, use_as=use_as or artifact.name)
853
+ api.use_artifact(
854
+ artifact.id,
855
+ use_as=use_as or artifact.name,
856
+ artifact_entity_name=artifact.entity,
857
+ artifact_project_name=artifact.project,
858
+ )
851
859
  return artifact
852
860
  elif isinstance(artifact, wandb.Artifact) and artifact.is_draft():
853
861
  raise ValueError(
@@ -911,8 +919,8 @@ class Run(Attrs):
911
919
  This check is done by utilizing GraphQL introspection in the available fields on the Project type.
912
920
  """
913
921
  query_string = """
914
- query ProbeProjectInput {
915
- ProjectType: __type(name:"Project") {
922
+ query ProbeRunInput {
923
+ RunType: __type(name:"Run") {
916
924
  fields {
917
925
  name
918
926
  }
@@ -924,8 +932,8 @@ class Run(Attrs):
924
932
  if self.server_provides_internal_id_field is None:
925
933
  query = gql(query_string)
926
934
  res = self.client.execute(query)
927
- self.server_provides_internal_id_field = "internalId" in [
928
- x["name"] for x in (res.get("ProjectType", {}).get("fields", [{}]))
935
+ self.server_provides_internal_id_field = "projectId" in [
936
+ x["name"] for x in (res.get("RunType", {}).get("fields", [{}]))
929
937
  ]
930
938
 
931
939
  return self.server_provides_internal_id_field
@@ -1,8 +1,11 @@
1
1
  import re
2
2
  from enum import Enum
3
- from typing import Optional
3
+ from typing import Any, Dict, Iterable, Mapping, Optional, Set
4
4
  from urllib.parse import urlparse
5
5
 
6
+ from wandb_gql import gql
7
+ from wandb_graphql.language import ast, visitor
8
+
6
9
  from wandb._iterutils import one
7
10
  from wandb.sdk.artifacts._validators import is_artifact_registry_project
8
11
  from wandb.sdk.internal.internal_api import Api as InternalApi
@@ -102,3 +105,106 @@ def fetch_org_from_settings_or_entity(
102
105
  )
103
106
  organization = entity_org.display_name
104
107
  return organization
108
+
109
+
110
+ class _GQLCompatRewriter(visitor.Visitor):
111
+ """GraphQL AST visitor to rewrite queries/mutations to be compatible with older server versions."""
112
+
113
+ omit_variables: Set[str]
114
+ omit_fragments: Set[str]
115
+ omit_fields: Set[str]
116
+ rename_fields: Dict[str, str]
117
+
118
+ def __init__(
119
+ self,
120
+ omit_variables: Optional[Iterable[str]] = None,
121
+ omit_fragments: Optional[Iterable[str]] = None,
122
+ omit_fields: Optional[Iterable[str]] = None,
123
+ rename_fields: Optional[Mapping[str, str]] = None,
124
+ ):
125
+ self.omit_variables = set(omit_variables or ())
126
+ self.omit_fragments = set(omit_fragments or ())
127
+ self.omit_fields = set(omit_fields or ())
128
+ self.rename_fields = dict(rename_fields or {})
129
+
130
+ def enter_VariableDefinition(self, node: ast.VariableDefinition, *_, **__) -> Any: # noqa: N802
131
+ if node.variable.name.value in self.omit_variables:
132
+ return visitor.REMOVE
133
+ # return node
134
+
135
+ def enter_ObjectField(self, node: ast.ObjectField, *_, **__) -> Any: # noqa: N802
136
+ # For context, note that e.g.:
137
+ #
138
+ # {description: $description
139
+ # ...}
140
+ #
141
+ # Is parsed as:
142
+ #
143
+ # ObjectValue(fields=[
144
+ # ObjectField(name=Name(value='description'), value=Variable(name=Name(value='description'))),
145
+ # ...])
146
+ if (
147
+ isinstance(var := node.value, ast.Variable)
148
+ and var.name.value in self.omit_variables
149
+ ):
150
+ return visitor.REMOVE
151
+
152
+ def enter_Argument(self, node: ast.Argument, *_, **__) -> Any: # noqa: N802
153
+ if node.name.value in self.omit_variables:
154
+ return visitor.REMOVE
155
+
156
+ def enter_FragmentDefinition(self, node: ast.FragmentDefinition, *_, **__) -> Any: # noqa: N802
157
+ if node.name.value in self.omit_fragments:
158
+ return visitor.REMOVE
159
+
160
+ def enter_FragmentSpread(self, node: ast.FragmentSpread, *_, **__) -> Any: # noqa: N802
161
+ if node.name.value in self.omit_fragments:
162
+ return visitor.REMOVE
163
+
164
+ def enter_Field(self, node: ast.Field, *_, **__) -> Any: # noqa: N802
165
+ if node.name.value in self.omit_fields:
166
+ return visitor.REMOVE
167
+ if new_name := self.rename_fields.get(node.name.value):
168
+ node.name.value = new_name
169
+ return node
170
+
171
+ def leave_Field(self, node: ast.Field, *_, **__) -> Any: # noqa: N802
172
+ # If the field had a selection set, but now it's empty, remove the field entirely
173
+ if (node.selection_set is not None) and (not node.selection_set.selections):
174
+ return visitor.REMOVE
175
+
176
+
177
+ def gql_compat(
178
+ request_string: str,
179
+ omit_variables: Optional[Iterable[str]] = None,
180
+ omit_fragments: Optional[Iterable[str]] = None,
181
+ omit_fields: Optional[Iterable[str]] = None,
182
+ rename_fields: Optional[Mapping[str, str]] = None,
183
+ ) -> ast.Document:
184
+ """Rewrite a GraphQL request string to ensure compatibility with older server versions.
185
+
186
+ Args:
187
+ request_string (str): The GraphQL request string to rewrite.
188
+ omit_variables (Iterable[str] | None): Names of variables to remove from the request string.
189
+ omit_fragments (Iterable[str] | None): Names of fragments to remove from the request string.
190
+ omit_fields (Iterable[str] | None): Names of fields to remove from the request string.
191
+ rename_fields (Mapping[str, str] | None):
192
+ A mapping of fields to rename in the request string, given as `{old_name -> new_name}`.
193
+
194
+ Returns:
195
+ str: Modified GraphQL request string with fragments on omitted types removed.
196
+ """
197
+ # Parse the request into a GraphQL AST
198
+ doc = gql(request_string)
199
+
200
+ if not (omit_variables or omit_fragments or omit_fields or rename_fields):
201
+ return doc
202
+
203
+ # Visit the AST with our visitor to filter out unwanted fragments
204
+ rewriter = _GQLCompatRewriter(
205
+ omit_variables=omit_variables,
206
+ omit_fragments=omit_fragments,
207
+ omit_fields=omit_fields,
208
+ rename_fields=rename_fields,
209
+ )
210
+ return visitor.visit(doc, rewriter)
@@ -0,0 +1,81 @@
1
+ from wandb._pydantic import IS_PYDANTIC_V2
2
+
3
+ from . import _filters as filters
4
+ from . import actions, automations, events, scopes
5
+ from .actions import ActionType, DoNothing, DoNotification, DoWebhook
6
+ from .automations import Automation, NewAutomation, PreparedAutomation
7
+ from .events import (
8
+ ArtifactEvent,
9
+ EventType,
10
+ OnAddArtifactAlias,
11
+ OnCreateArtifact,
12
+ OnLinkArtifact,
13
+ OnRunMetric,
14
+ RunEvent,
15
+ )
16
+ from .integrations import Integration, SlackIntegration, WebhookIntegration
17
+ from .scopes import ArtifactCollectionScope, ProjectScope, ScopeType
18
+
19
+ # ----------------------------------------------------------------------------
20
+ # WARNINGS on import
21
+ if not IS_PYDANTIC_V2:
22
+ # Raises an error in Pydantic v1 environments, where the Automations API
23
+ # has not been tested and is unlikely to work as expected.
24
+ #
25
+ # Remove this when we either:
26
+ # - Drop support for Pydantic v1
27
+ # - Are able to implement (limited) Pydantic v1 support
28
+ raise ImportError(
29
+ "The W&B Automations API is not supported in Pydantic v1 at this time. "
30
+ "If at all possible, we currently recommend upgrading to Pydantic v2 to use this feature.",
31
+ )
32
+
33
+ else:
34
+ # If Pydantic v2 is available, we can use the full Automations API
35
+ # but communicate to users that the API is still experimental and
36
+ # may change rapidly.
37
+ import warnings
38
+
39
+ warnings.warn(
40
+ "The W&B Automations API is currently experimental. Although we'll communicate "
41
+ "breaking changes in release notes and attempt to minimize them in general, "
42
+ "please know that such changes may occur between release versions without notice. "
43
+ "We strongly recommend pinning your `wandb` version when using the Automations API "
44
+ "to avoid unexpected breakages.",
45
+ FutureWarning,
46
+ stacklevel=1,
47
+ )
48
+ # ----------------------------------------------------------------------------
49
+
50
+ __all__ = [
51
+ "filters",
52
+ "scopes",
53
+ "events",
54
+ "actions",
55
+ "automations",
56
+ # Scopes
57
+ "ScopeType",
58
+ "ArtifactCollectionScope",
59
+ "ProjectScope",
60
+ # Events
61
+ "EventType",
62
+ "OnAddArtifactAlias",
63
+ "OnCreateArtifact",
64
+ "OnLinkArtifact",
65
+ "OnRunMetric",
66
+ "ArtifactEvent",
67
+ "RunEvent",
68
+ # Actions
69
+ "ActionType",
70
+ "DoNotification",
71
+ "DoWebhook",
72
+ "DoNothing",
73
+ # Automations
74
+ "Automation",
75
+ "NewAutomation",
76
+ "PreparedAutomation",
77
+ # Integrations
78
+ "Integration",
79
+ "SlackIntegration",
80
+ "WebhookIntegration",
81
+ ]
@@ -0,0 +1,40 @@
1
+ from .expressions import FilterExpr, MongoLikeFilter
2
+ from .operators import (
3
+ And,
4
+ Contains,
5
+ Eq,
6
+ Exists,
7
+ Gt,
8
+ Gte,
9
+ In,
10
+ Lt,
11
+ Lte,
12
+ Ne,
13
+ Nor,
14
+ Not,
15
+ NotIn,
16
+ Op,
17
+ Or,
18
+ Regex,
19
+ )
20
+
21
+ __all__ = [
22
+ "And",
23
+ "Or",
24
+ "Nor",
25
+ "Not",
26
+ "Op",
27
+ "Gt",
28
+ "Lt",
29
+ "Gte",
30
+ "Lte",
31
+ "Eq",
32
+ "Ne",
33
+ "In",
34
+ "NotIn",
35
+ "Contains",
36
+ "Exists",
37
+ "Regex",
38
+ "FilterExpr",
39
+ "MongoLikeFilter",
40
+ ]