wandb 0.19.8__py3-none-macosx_11_0_arm64.whl → 0.19.10__py3-none-macosx_11_0_arm64.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.
- wandb/__init__.py +5 -1
- wandb/__init__.pyi +15 -8
- wandb/_pydantic/__init__.py +30 -0
- wandb/_pydantic/base.py +148 -0
- wandb/_pydantic/utils.py +66 -0
- wandb/_pydantic/v1_compat.py +284 -0
- wandb/apis/paginator.py +82 -38
- wandb/apis/public/__init__.py +2 -2
- wandb/apis/public/api.py +111 -53
- wandb/apis/public/artifacts.py +387 -639
- wandb/apis/public/automations.py +69 -0
- wandb/apis/public/files.py +2 -2
- wandb/apis/public/integrations.py +168 -0
- wandb/apis/public/projects.py +32 -2
- wandb/apis/public/reports.py +2 -2
- wandb/apis/public/runs.py +19 -11
- wandb/apis/public/utils.py +107 -1
- wandb/automations/__init__.py +81 -0
- wandb/automations/_filters/__init__.py +40 -0
- wandb/automations/_filters/expressions.py +179 -0
- wandb/automations/_filters/operators.py +267 -0
- wandb/automations/_filters/run_metrics.py +183 -0
- wandb/automations/_generated/__init__.py +184 -0
- wandb/automations/_generated/create_filter_trigger.py +21 -0
- wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
- wandb/automations/_generated/delete_trigger.py +19 -0
- wandb/automations/_generated/enums.py +33 -0
- wandb/automations/_generated/fragments.py +343 -0
- wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
- wandb/automations/_generated/get_triggers.py +24 -0
- wandb/automations/_generated/get_triggers_by_entity.py +24 -0
- wandb/automations/_generated/input_types.py +104 -0
- wandb/automations/_generated/integrations_by_entity.py +22 -0
- wandb/automations/_generated/operations.py +710 -0
- wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
- wandb/automations/_generated/update_filter_trigger.py +21 -0
- wandb/automations/_utils.py +123 -0
- wandb/automations/_validators.py +73 -0
- wandb/automations/actions.py +205 -0
- wandb/automations/automations.py +109 -0
- wandb/automations/events.py +235 -0
- wandb/automations/integrations.py +26 -0
- wandb/automations/scopes.py +76 -0
- wandb/beta/workflows.py +9 -10
- wandb/bin/gpu_stats +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/cli.py +3 -3
- wandb/integration/keras/keras.py +2 -1
- wandb/integration/langchain/wandb_tracer.py +2 -1
- wandb/integration/metaflow/metaflow.py +19 -17
- wandb/integration/sacred/__init__.py +1 -1
- wandb/jupyter.py +155 -133
- wandb/old/summary.py +0 -2
- wandb/proto/v3/wandb_internal_pb2.py +297 -292
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v4/wandb_internal_pb2.py +292 -292
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v5/wandb_internal_pb2.py +292 -292
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v6/wandb_base_pb2.py +41 -0
- wandb/proto/v6/wandb_internal_pb2.py +393 -0
- wandb/proto/v6/wandb_server_pb2.py +78 -0
- wandb/proto/v6/wandb_settings_pb2.py +58 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
- wandb/proto/wandb_base_pb2.py +2 -0
- wandb/proto/wandb_deprecated.py +10 -0
- wandb/proto/wandb_internal_pb2.py +3 -1
- wandb/proto/wandb_server_pb2.py +2 -0
- wandb/proto/wandb_settings_pb2.py +2 -0
- wandb/proto/wandb_telemetry_pb2.py +2 -0
- wandb/sdk/artifacts/_generated/__init__.py +248 -0
- wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
- wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
- wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
- wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
- wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_generated/enums.py +17 -0
- wandb/sdk/artifacts/_generated/fragments.py +186 -0
- wandb/sdk/artifacts/_generated/input_types.py +16 -0
- wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
- wandb/sdk/artifacts/_generated/operations.py +510 -0
- wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
- wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
- wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
- wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_graphql_fragments.py +56 -81
- wandb/sdk/artifacts/_validators.py +1 -0
- wandb/sdk/artifacts/artifact.py +110 -49
- wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
- wandb/sdk/artifacts/artifact_saver.py +16 -2
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
- wandb/sdk/data_types/audio.py +1 -3
- wandb/sdk/data_types/base_types/media.py +13 -7
- wandb/sdk/data_types/base_types/wb_value.py +34 -11
- wandb/sdk/data_types/html.py +36 -9
- wandb/sdk/data_types/image.py +56 -37
- wandb/sdk/data_types/molecule.py +1 -5
- wandb/sdk/data_types/object_3d.py +2 -1
- wandb/sdk/data_types/saved_model.py +7 -9
- wandb/sdk/data_types/table.py +5 -0
- wandb/sdk/data_types/trace_tree.py +2 -0
- wandb/sdk/data_types/utils.py +1 -1
- wandb/sdk/data_types/video.py +15 -30
- wandb/sdk/interface/interface.py +2 -0
- wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
- wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
- wandb/sdk/internal/internal_api.py +138 -47
- wandb/sdk/internal/profiler.py +6 -5
- wandb/sdk/internal/run.py +13 -6
- wandb/sdk/internal/sender.py +2 -0
- wandb/sdk/internal/sender_config.py +8 -11
- wandb/sdk/internal/settings_static.py +24 -2
- wandb/sdk/lib/apikey.py +40 -20
- wandb/sdk/lib/asyncio_compat.py +1 -1
- wandb/sdk/lib/deprecate.py +13 -22
- wandb/sdk/lib/disabled.py +2 -1
- wandb/sdk/lib/printer.py +37 -8
- wandb/sdk/lib/printer_asyncio.py +46 -0
- wandb/sdk/lib/redirect.py +10 -5
- wandb/sdk/lib/run_moment.py +4 -6
- wandb/sdk/lib/wb_logging.py +161 -0
- wandb/sdk/service/server_sock.py +19 -14
- wandb/sdk/service/service.py +9 -7
- wandb/sdk/service/streams.py +5 -0
- wandb/sdk/verify/verify.py +6 -3
- wandb/sdk/wandb_config.py +44 -43
- wandb/sdk/wandb_init.py +323 -141
- wandb/sdk/wandb_login.py +13 -4
- wandb/sdk/wandb_metadata.py +107 -91
- wandb/sdk/wandb_run.py +529 -325
- wandb/sdk/wandb_settings.py +422 -202
- wandb/sdk/wandb_setup.py +52 -1
- wandb/util.py +29 -29
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/METADATA +7 -7
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/RECORD +151 -94
- wandb/_globals.py +0 -19
- wandb/apis/public/_generated/base.py +0 -128
- wandb/apis/public/_generated/typing_compat.py +0 -14
- /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
- /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
- /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
- {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
|
+
]
|
wandb/apis/public/files.py
CHANGED
@@ -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
|
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(
|
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]
|
wandb/apis/public/projects.py
CHANGED
@@ -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
|
wandb/apis/public/reports.py
CHANGED
@@ -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
|
12
|
+
from wandb.apis.paginator import SizedPaginator
|
13
13
|
from wandb.sdk.lib import ipython
|
14
14
|
|
15
15
|
|
16
|
-
class Reports(
|
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
|
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(
|
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
|
-
|
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
|
-
|
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(
|
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
|
915
|
-
|
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 = "
|
928
|
-
x["name"] for x in (res.get("
|
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
|
wandb/apis/public/utils.py
CHANGED
@@ -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
|
+
]
|