wandb 0.19.9__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.
- wandb/__init__.py +1 -1
- wandb/__init__.pyi +4 -1
- wandb/_pydantic/__init__.py +14 -7
- wandb/_pydantic/base.py +44 -9
- wandb/_pydantic/utils.py +66 -0
- wandb/_pydantic/v1_compat.py +78 -56
- wandb/apis/public/__init__.py +2 -2
- wandb/apis/public/api.py +114 -2
- wandb/apis/public/artifacts.py +365 -673
- wandb/apis/public/automations.py +69 -0
- wandb/apis/public/integrations.py +168 -0
- wandb/apis/public/projects.py +29 -0
- 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.exe +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/jupyter.py +137 -118
- wandb/old/summary.py +0 -2
- wandb/proto/v3/wandb_internal_pb2.py +293 -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 +8 -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 -79
- wandb/sdk/artifacts/artifact.py +40 -13
- wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
- wandb/sdk/data_types/base_types/media.py +2 -3
- 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 +12 -12
- 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 +14 -26
- wandb/sdk/interface/interface.py +2 -0
- wandb/sdk/internal/profiler.py +6 -5
- wandb/sdk/internal/run.py +13 -6
- wandb/sdk/lib/apikey.py +25 -4
- 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/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_init.py +185 -65
- wandb/sdk/wandb_login.py +13 -4
- wandb/sdk/wandb_run.py +382 -286
- wandb/sdk/wandb_settings.py +21 -3
- wandb/sdk/wandb_setup.py +49 -0
- wandb/util.py +29 -29
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/METADATA +5 -5
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/RECORD +125 -72
- wandb/_globals.py +0 -19
- wandb/sdk/internal/_generated/base.py +0 -226
- wandb/sdk/internal/_generated/typing_compat.py +0 -14
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
- {wandb-0.19.9.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# Generated by ariadne-codegen
|
2
|
+
# Source: tools/graphql_codegen/automations/
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from pydantic import Field
|
9
|
+
|
10
|
+
from wandb._pydantic import GQLBase
|
11
|
+
|
12
|
+
from .fragments import UpdateFilterTriggerResult
|
13
|
+
|
14
|
+
|
15
|
+
class UpdateFilterTrigger(GQLBase):
|
16
|
+
update_filter_trigger: Optional[UpdateFilterTriggerResult] = Field(
|
17
|
+
alias="updateFilterTrigger"
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
UpdateFilterTrigger.model_rebuild()
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# ruff: noqa: UP007 # Avoid using `X | Y` for union fields, as this can cause issues with pydantic < 2.6
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Optional, Protocol, TypedDict
|
6
|
+
|
7
|
+
from wandb._pydantic import to_json
|
8
|
+
from wandb.automations.events import InputEvent
|
9
|
+
from wandb.automations.scopes import InputScope
|
10
|
+
|
11
|
+
from ._generated import (
|
12
|
+
CreateFilterTriggerInput,
|
13
|
+
TriggeredActionConfig,
|
14
|
+
UpdateFilterTriggerInput,
|
15
|
+
)
|
16
|
+
from .actions import (
|
17
|
+
ActionType,
|
18
|
+
DoNothing,
|
19
|
+
DoNotification,
|
20
|
+
DoWebhook,
|
21
|
+
InputAction,
|
22
|
+
SavedAction,
|
23
|
+
)
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from typing_extensions import Unpack
|
27
|
+
|
28
|
+
from .automations import Automation, NewAutomation
|
29
|
+
|
30
|
+
|
31
|
+
class HasId(Protocol):
|
32
|
+
id: str
|
33
|
+
|
34
|
+
|
35
|
+
def extract_id(obj: HasId | str) -> str:
|
36
|
+
return obj.id if hasattr(obj, "id") else obj
|
37
|
+
|
38
|
+
|
39
|
+
# NOTE: `QueueJobActionInput` for defining a Launch job is deprecated,
|
40
|
+
# so we deliberately don't currently expose it in the API for *creating* automations.
|
41
|
+
_ACTION_CONFIG_KEYS: dict[ActionType, str] = {
|
42
|
+
ActionType.NOTIFICATION: "notification_action_input",
|
43
|
+
ActionType.GENERIC_WEBHOOK: "generic_webhook_action_input",
|
44
|
+
ActionType.NO_OP: "no_op_action_input",
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
class InputActionConfig(TriggeredActionConfig):
|
49
|
+
"""A `TriggeredActionConfig` that prepares the action config for saving an automation."""
|
50
|
+
|
51
|
+
notification_action_input: Optional[DoNotification] = None
|
52
|
+
generic_webhook_action_input: Optional[DoWebhook] = None
|
53
|
+
no_op_action_input: Optional[DoNothing] = None
|
54
|
+
|
55
|
+
|
56
|
+
def prepare_action_input(obj: InputAction | SavedAction) -> InputActionConfig:
|
57
|
+
"""Return a `TriggeredActionConfig` as required in the input schema of CreateFilterTriggerInput."""
|
58
|
+
key = _ACTION_CONFIG_KEYS[obj.action_type]
|
59
|
+
# Delegate the validators for each ActionInput type to handle custom logic
|
60
|
+
# for converting from saved action types to an input action type.
|
61
|
+
return InputActionConfig.model_validate({key: obj})
|
62
|
+
|
63
|
+
|
64
|
+
class AutomationParams(TypedDict, total=False):
|
65
|
+
"""Keyword arguments that can be passed to create or update an automation."""
|
66
|
+
|
67
|
+
name: str
|
68
|
+
description: str
|
69
|
+
enabled: bool
|
70
|
+
|
71
|
+
scope: InputScope
|
72
|
+
event: InputEvent
|
73
|
+
action: InputAction
|
74
|
+
|
75
|
+
|
76
|
+
def prepare_create_input(
|
77
|
+
obj: NewAutomation, **updates: Unpack[AutomationParams]
|
78
|
+
) -> CreateFilterTriggerInput:
|
79
|
+
"""Prepares the payload to create an automation in a GraphQL request."""
|
80
|
+
from .automations import PreparedAutomation
|
81
|
+
|
82
|
+
# Apply any updates to the properties of the automation
|
83
|
+
updated = obj.model_copy(update=updates)
|
84
|
+
prepared = PreparedAutomation.model_validate(updated)
|
85
|
+
|
86
|
+
# Prepare the input as required for the GraphQL request
|
87
|
+
return CreateFilterTriggerInput(
|
88
|
+
name=prepared.name,
|
89
|
+
description=prepared.description,
|
90
|
+
enabled=prepared.enabled,
|
91
|
+
scope_type=prepared.scope.scope_type,
|
92
|
+
scope_id=prepared.scope.id,
|
93
|
+
triggering_event_type=prepared.event.event_type,
|
94
|
+
event_filter=to_json(prepared.event.filter),
|
95
|
+
triggered_action_type=prepared.action.action_type,
|
96
|
+
triggered_action_config=prepare_action_input(prepared.action).model_dump(),
|
97
|
+
)
|
98
|
+
|
99
|
+
|
100
|
+
def prepare_update_input(
|
101
|
+
obj: Automation, **updates: Unpack[AutomationParams]
|
102
|
+
) -> UpdateFilterTriggerInput:
|
103
|
+
"""Prepares the payload to update an automation in a GraphQL request."""
|
104
|
+
from .events import SavedEventFilter
|
105
|
+
|
106
|
+
updated = obj.model_copy(update=updates)
|
107
|
+
|
108
|
+
return UpdateFilterTriggerInput(
|
109
|
+
id=updated.id,
|
110
|
+
name=updated.name,
|
111
|
+
description=updated.description,
|
112
|
+
enabled=updated.enabled,
|
113
|
+
scope_type=updated.scope.scope_type,
|
114
|
+
scope_id=updated.scope.id,
|
115
|
+
triggering_event_type=updated.event.event_type,
|
116
|
+
event_filter=to_json(
|
117
|
+
updated.event.filter.filter
|
118
|
+
if isinstance(updated.event.filter, SavedEventFilter)
|
119
|
+
else updated.event.filter
|
120
|
+
),
|
121
|
+
triggered_action_type=updated.action.action_type,
|
122
|
+
triggered_action_config=prepare_action_input(updated.action).model_dump(),
|
123
|
+
)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from functools import singledispatch
|
4
|
+
from itertools import chain
|
5
|
+
from typing import Any, TypeVar
|
6
|
+
|
7
|
+
from ._filters import And, FilterExpr, In, Nor, Not, NotIn, Op, Or
|
8
|
+
|
9
|
+
T = TypeVar("T")
|
10
|
+
|
11
|
+
|
12
|
+
def validate_scope(v: Any) -> Any:
|
13
|
+
"""Convert a familiar wandb `Project` or `ArtifactCollection` object to an automation scope."""
|
14
|
+
from wandb.apis.public import ArtifactCollection, Project
|
15
|
+
|
16
|
+
from .scopes import ProjectScope, _ArtifactPortfolioScope, _ArtifactSequenceScope
|
17
|
+
|
18
|
+
if isinstance(v, Project):
|
19
|
+
return ProjectScope(id=v.id, name=v.name)
|
20
|
+
if isinstance(v, ArtifactCollection):
|
21
|
+
cls = _ArtifactSequenceScope if v.is_sequence() else _ArtifactPortfolioScope
|
22
|
+
return cls(id=v.id, name=v.name)
|
23
|
+
return v
|
24
|
+
|
25
|
+
|
26
|
+
@singledispatch
|
27
|
+
def simplify_op(op: Op | FilterExpr) -> Op | FilterExpr:
|
28
|
+
"""Simplify a MongoDB filter by removing and unnesting redundant operators."""
|
29
|
+
return op
|
30
|
+
|
31
|
+
|
32
|
+
@simplify_op.register
|
33
|
+
def _(op: And) -> Op:
|
34
|
+
# {"$and": []} -> {"$and": []}
|
35
|
+
if not (args := op.and_):
|
36
|
+
return op
|
37
|
+
|
38
|
+
# {"$and": [op]} -> op
|
39
|
+
if len(args) == 1:
|
40
|
+
return simplify_op(args[0])
|
41
|
+
|
42
|
+
# {"$and": [op, {"$and": [op2, ...]}]} -> {"$and": [op, op2, ...]}
|
43
|
+
flattened = chain.from_iterable(x.and_ if isinstance(x, And) else [x] for x in args)
|
44
|
+
return And(and_=map(simplify_op, flattened))
|
45
|
+
|
46
|
+
|
47
|
+
@simplify_op.register
|
48
|
+
def _(op: Or) -> Op:
|
49
|
+
# {"$or": []} -> {"$or": []}
|
50
|
+
if not (args := op.or_):
|
51
|
+
return op
|
52
|
+
|
53
|
+
# {"$or": [op]} -> op
|
54
|
+
if len(args) == 1:
|
55
|
+
return simplify_op(args[0])
|
56
|
+
|
57
|
+
# {"$or": [op, {"$or": [op2, ...]}]} -> {"$or": [op, op2, ...]}
|
58
|
+
flattened = chain.from_iterable(x.or_ if isinstance(x, Or) else [x] for x in args)
|
59
|
+
return Or(or_=map(simplify_op, flattened))
|
60
|
+
|
61
|
+
|
62
|
+
@simplify_op.register
|
63
|
+
def _(op: Not) -> Op:
|
64
|
+
inner = op.not_
|
65
|
+
|
66
|
+
# {"$not": {"$not": op}} -> op
|
67
|
+
# {"$not": {"$or": [op, ...]}} -> {"$nor": [op, ...]}
|
68
|
+
# {"$not": {"$nor": [op, ...]}} -> {"$or": [op, ...]}
|
69
|
+
# {"$not": {"$in": [op, ...]}} -> {"$nin": [op, ...]}
|
70
|
+
# {"$not": {"$nin": [op, ...]}} -> {"$in": [op, ...]}
|
71
|
+
if isinstance(inner, (Not, Or, Nor, In, NotIn)):
|
72
|
+
return simplify_op(~inner)
|
73
|
+
return Not(not_=simplify_op(inner))
|
@@ -0,0 +1,205 @@
|
|
1
|
+
"""Actions that are triggered by W&B Automations."""
|
2
|
+
|
3
|
+
# ruff: noqa: UP007 # Avoid using `X | Y` for union fields, as this can cause issues with pydantic < 2.6
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
8
|
+
|
9
|
+
from pydantic import Field
|
10
|
+
from typing_extensions import Self, TypeAlias, get_args
|
11
|
+
|
12
|
+
from wandb._pydantic import (
|
13
|
+
SerializedToJson,
|
14
|
+
field_validator,
|
15
|
+
model_validator,
|
16
|
+
pydantic_isinstance,
|
17
|
+
)
|
18
|
+
|
19
|
+
from ._generated import (
|
20
|
+
AlertSeverity,
|
21
|
+
GenericWebhookActionFields,
|
22
|
+
GenericWebhookActionInput,
|
23
|
+
NoOpActionFields,
|
24
|
+
NoOpTriggeredActionInput,
|
25
|
+
NotificationActionFields,
|
26
|
+
NotificationActionInput,
|
27
|
+
QueueJobActionFields,
|
28
|
+
TriggeredActionType,
|
29
|
+
)
|
30
|
+
from .integrations import SlackIntegration, WebhookIntegration
|
31
|
+
|
32
|
+
# Note: Pydantic doesn't like `list['JsonValue']` or `dict[str, 'JsonValue']`,
|
33
|
+
# which causes a RecursionError.
|
34
|
+
JsonValue: TypeAlias = Union[
|
35
|
+
List[Any],
|
36
|
+
Dict[str, Any],
|
37
|
+
# NOTE: For now, we're not expecting any doubly-serialized strings, as this makes validation logic easier, but revisit and revise if needed.
|
38
|
+
# str,
|
39
|
+
bool,
|
40
|
+
int,
|
41
|
+
float,
|
42
|
+
None,
|
43
|
+
]
|
44
|
+
|
45
|
+
# NOTE: Name shortened for readability and defined publicly for easier access
|
46
|
+
ActionType = TriggeredActionType
|
47
|
+
"""The type of action triggered by an automation."""
|
48
|
+
|
49
|
+
|
50
|
+
# ------------------------------------------------------------------------------
|
51
|
+
# Saved types: for parsing response data from saved automations
|
52
|
+
|
53
|
+
|
54
|
+
# NOTE: `QueueJobActionInput` for defining a Launch job is deprecated,
|
55
|
+
# so while we allow parsing it from previously saved Automations, we deliberately
|
56
|
+
# don't currently expose it in the API for creating automations.
|
57
|
+
class SavedLaunchJobAction(QueueJobActionFields):
|
58
|
+
action_type: Literal[ActionType.QUEUE_JOB] = ActionType.QUEUE_JOB
|
59
|
+
|
60
|
+
|
61
|
+
class SavedNotificationAction(NotificationActionFields):
|
62
|
+
action_type: Literal[ActionType.NOTIFICATION] = ActionType.NOTIFICATION
|
63
|
+
|
64
|
+
|
65
|
+
class SavedWebhookAction(GenericWebhookActionFields):
|
66
|
+
action_type: Literal[ActionType.GENERIC_WEBHOOK] = ActionType.GENERIC_WEBHOOK
|
67
|
+
|
68
|
+
# We override the type of the `requestPayload` field since the original GraphQL
|
69
|
+
# schema (and generated class) effectively defines it as a string, when we know
|
70
|
+
# and need to anticipate the expected structure of the JSON-serialized data.
|
71
|
+
request_payload: Optional[SerializedToJson[JsonValue]] = Field( # type: ignore[assignment]
|
72
|
+
default=None, alias="requestPayload"
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
class SavedNoOpAction(NoOpActionFields):
|
77
|
+
action_type: Literal[ActionType.NO_OP] = ActionType.NO_OP
|
78
|
+
|
79
|
+
|
80
|
+
# for type annotations
|
81
|
+
SavedAction = Union[
|
82
|
+
SavedLaunchJobAction,
|
83
|
+
SavedNotificationAction,
|
84
|
+
SavedWebhookAction,
|
85
|
+
SavedNoOpAction,
|
86
|
+
]
|
87
|
+
# for runtime type checks
|
88
|
+
SavedActionTypes: tuple[type, ...] = get_args(SavedAction)
|
89
|
+
|
90
|
+
|
91
|
+
# ------------------------------------------------------------------------------
|
92
|
+
# Input types: for creating or updating automations
|
93
|
+
class DoNotification(NotificationActionInput):
|
94
|
+
"""Schema for defining a triggered notification action."""
|
95
|
+
|
96
|
+
action_type: Literal[ActionType.NOTIFICATION] = ActionType.NOTIFICATION
|
97
|
+
|
98
|
+
# Note: Validation aliases match arg names from `wandb.alert()` to allow
|
99
|
+
# continuity with previous API.
|
100
|
+
title: str = Field(default="", validation_alias="title")
|
101
|
+
message: str = Field(default="", validation_alias="text")
|
102
|
+
severity: AlertSeverity = Field(
|
103
|
+
default=AlertSeverity.INFO, validation_alias="level"
|
104
|
+
)
|
105
|
+
|
106
|
+
@field_validator("severity", mode="before")
|
107
|
+
@classmethod
|
108
|
+
def _validate_severity(cls, v: Any) -> Any:
|
109
|
+
# Be helpful by accepting case-insensitive strings
|
110
|
+
return v.upper() if isinstance(v, str) else v
|
111
|
+
|
112
|
+
@model_validator(mode="before")
|
113
|
+
@classmethod
|
114
|
+
def _from_saved(cls, v: Any) -> Any:
|
115
|
+
"""Convert an action on a saved automation to a new/input action."""
|
116
|
+
if pydantic_isinstance(v, SavedNotificationAction):
|
117
|
+
return cls(
|
118
|
+
integration_id=v.integration.id,
|
119
|
+
title=v.title,
|
120
|
+
message=v.message,
|
121
|
+
severity=v.severity,
|
122
|
+
)
|
123
|
+
return v
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
def from_integration(
|
127
|
+
cls,
|
128
|
+
integration: SlackIntegration,
|
129
|
+
*,
|
130
|
+
title: str = "",
|
131
|
+
text: str = "",
|
132
|
+
level: AlertSeverity = AlertSeverity.INFO,
|
133
|
+
) -> Self:
|
134
|
+
"""Define a notification action that sends to the given (Slack) integration."""
|
135
|
+
integration = SlackIntegration.model_validate(integration)
|
136
|
+
return cls(
|
137
|
+
integration_id=integration.id,
|
138
|
+
title=title,
|
139
|
+
message=text,
|
140
|
+
severity=level,
|
141
|
+
)
|
142
|
+
|
143
|
+
|
144
|
+
class DoWebhook(GenericWebhookActionInput):
|
145
|
+
"""Schema for defining a triggered webhook action."""
|
146
|
+
|
147
|
+
action_type: Literal[ActionType.GENERIC_WEBHOOK] = ActionType.GENERIC_WEBHOOK
|
148
|
+
|
149
|
+
# overrides the generated field type to parse/serialize JSON strings
|
150
|
+
request_payload: Optional[SerializedToJson[JsonValue]] = Field( # type: ignore[assignment]
|
151
|
+
default=None, alias="requestPayload"
|
152
|
+
)
|
153
|
+
|
154
|
+
@model_validator(mode="before")
|
155
|
+
@classmethod
|
156
|
+
def _from_saved(cls, v: Any) -> Any:
|
157
|
+
"""Convert an action on a saved automation to a new/input action."""
|
158
|
+
if pydantic_isinstance(v, SavedWebhookAction):
|
159
|
+
return cls(
|
160
|
+
integration_id=v.integration.id,
|
161
|
+
request_payload=v.request_payload,
|
162
|
+
)
|
163
|
+
return v
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def from_integration(
|
167
|
+
cls,
|
168
|
+
integration: WebhookIntegration,
|
169
|
+
*,
|
170
|
+
request_payload: Optional[SerializedToJson[JsonValue]] = None,
|
171
|
+
) -> Self:
|
172
|
+
"""Define a webhook action that sends to the given (webhook) integration."""
|
173
|
+
integration = WebhookIntegration.model_validate(integration)
|
174
|
+
return cls(integration_id=integration.id, request_payload=request_payload)
|
175
|
+
|
176
|
+
|
177
|
+
class DoNothing(NoOpTriggeredActionInput):
|
178
|
+
"""Schema for defining a triggered no-op action."""
|
179
|
+
|
180
|
+
action_type: Literal[ActionType.NO_OP] = ActionType.NO_OP
|
181
|
+
|
182
|
+
no_op: bool = True # prevent exclusion on `.model_dump(exclude_none=True)`
|
183
|
+
|
184
|
+
@field_validator("no_op", mode="before")
|
185
|
+
@classmethod
|
186
|
+
def _ensure_nonnull(cls, v: Any) -> Any:
|
187
|
+
# Ensuring the value isn't None complies with validation and
|
188
|
+
# prevents the field (and action data) from getting excluded on
|
189
|
+
# `.model_dump(exclude_none=True)`
|
190
|
+
return True if (v is None) else v
|
191
|
+
|
192
|
+
|
193
|
+
DoNotification.model_rebuild()
|
194
|
+
DoWebhook.model_rebuild()
|
195
|
+
DoNothing.model_rebuild()
|
196
|
+
|
197
|
+
|
198
|
+
# for type annotations
|
199
|
+
InputAction = Union[
|
200
|
+
DoNotification,
|
201
|
+
DoWebhook,
|
202
|
+
DoNothing,
|
203
|
+
]
|
204
|
+
# for runtime type checks
|
205
|
+
InputActionTypes: tuple[type, ...] = get_args(InputAction)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# ruff: noqa: UP007 # Avoid using `X | Y` for union fields, as this can cause issues with pydantic < 2.6
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from typing import TYPE_CHECKING, Optional
|
7
|
+
|
8
|
+
from pydantic import Field
|
9
|
+
|
10
|
+
from wandb._pydantic import Base, GQLId
|
11
|
+
|
12
|
+
from ._generated import TriggerFields, UserFields
|
13
|
+
from .actions import InputAction, SavedAction
|
14
|
+
from .events import InputEvent, SavedEvent
|
15
|
+
from .scopes import InputScope, SavedScope
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
# ------------------------------------------------------------------------------
|
22
|
+
# Saved types: for parsing response data from saved automations
|
23
|
+
class Automation(TriggerFields):
|
24
|
+
"""A local instance of a saved W&B automation."""
|
25
|
+
|
26
|
+
id: GQLId
|
27
|
+
|
28
|
+
created_by: UserFields = Field(repr=False, frozen=True, alias="createdBy")
|
29
|
+
created_at: datetime = Field(repr=False, frozen=True, alias="createdAt")
|
30
|
+
updated_at: Optional[datetime] = Field(repr=False, frozen=True, alias="updatedAt")
|
31
|
+
|
32
|
+
name: str
|
33
|
+
description: Optional[str]
|
34
|
+
|
35
|
+
scope: SavedScope = Field(discriminator="typename__")
|
36
|
+
event: SavedEvent
|
37
|
+
action: SavedAction = Field(discriminator="typename__", alias="triggeredAction")
|
38
|
+
|
39
|
+
enabled: bool
|
40
|
+
|
41
|
+
# def save(
|
42
|
+
# self, api: Api | None = None, **updates: Unpack[AutomationParams]
|
43
|
+
# ) -> Automation:
|
44
|
+
# """Save this existing automation to the server, applying any local changes.
|
45
|
+
|
46
|
+
# Args:
|
47
|
+
# api: The API instance to use. If not provided, the default API instance is used.
|
48
|
+
# updates:
|
49
|
+
# Any final updates to apply to the automation before
|
50
|
+
# saving it. These override previously-set values, if any.
|
51
|
+
|
52
|
+
# Returns:
|
53
|
+
# The updated automation.
|
54
|
+
# """
|
55
|
+
# from wandb import Api
|
56
|
+
|
57
|
+
# return (api or Api()).update_automation(self, **updates)
|
58
|
+
|
59
|
+
# def delete(self, api: Api | None = None) -> DeleteTriggerResult:
|
60
|
+
# """Delete this automation from the server.
|
61
|
+
|
62
|
+
# Args:
|
63
|
+
# api: The API instance to use. If not provided, the default API instance is used.
|
64
|
+
# """
|
65
|
+
# from wandb import Api
|
66
|
+
|
67
|
+
# return (api or Api()).delete_automation(self)
|
68
|
+
|
69
|
+
|
70
|
+
class NewAutomation(Base):
|
71
|
+
"""An automation which can hold any of the fields of a NewAutomation, but may not be complete yet."""
|
72
|
+
|
73
|
+
name: Optional[str] = None
|
74
|
+
description: Optional[str] = None
|
75
|
+
enabled: bool = True
|
76
|
+
|
77
|
+
scope: Optional[InputScope] = Field(discriminator="typename__", default=None)
|
78
|
+
event: Optional[InputEvent] = Field(discriminator="event_type", default=None)
|
79
|
+
action: Optional[InputAction] = Field(discriminator="action_type", default=None)
|
80
|
+
|
81
|
+
# def save(
|
82
|
+
# self, api: Api | None = None, **updates: Unpack[AutomationParams]
|
83
|
+
# ) -> Automation:
|
84
|
+
# """Create this automation by saving it to the server.
|
85
|
+
|
86
|
+
# Args:
|
87
|
+
# api: The API instance to use. If not provided, the default API instance is used.
|
88
|
+
# updates:
|
89
|
+
# Any final updates to apply to the automation before
|
90
|
+
# saving it. These override previously-set values, if any.
|
91
|
+
|
92
|
+
# Returns:
|
93
|
+
# The created automation.
|
94
|
+
# """
|
95
|
+
# from wandb import Api
|
96
|
+
|
97
|
+
# return (api or Api()).create_automation(self, **updates)
|
98
|
+
|
99
|
+
|
100
|
+
class PreparedAutomation(NewAutomation):
|
101
|
+
"""A fully defined automation, ready to be sent to the server to create or update it."""
|
102
|
+
|
103
|
+
name: str
|
104
|
+
description: Optional[str] = None
|
105
|
+
enabled: bool = True
|
106
|
+
|
107
|
+
scope: InputScope
|
108
|
+
event: InputEvent
|
109
|
+
action: InputAction
|