wandb 0.21.2__py3-none-macosx_12_0_arm64.whl → 0.21.4__py3-none-macosx_12_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 +1 -1
- wandb/__init__.pyi +1 -1
- wandb/_analytics.py +65 -0
- wandb/_iterutils.py +8 -0
- wandb/_pydantic/__init__.py +10 -11
- wandb/_pydantic/base.py +3 -53
- wandb/_pydantic/field_types.py +29 -0
- wandb/_pydantic/v1_compat.py +47 -30
- wandb/_strutils.py +40 -0
- wandb/apis/public/api.py +17 -4
- wandb/apis/public/artifacts.py +5 -4
- wandb/apis/public/automations.py +2 -1
- wandb/apis/public/registries/_freezable_list.py +6 -6
- wandb/apis/public/registries/_utils.py +2 -1
- wandb/apis/public/registries/registries_search.py +4 -0
- wandb/apis/public/registries/registry.py +7 -0
- wandb/automations/_filters/expressions.py +3 -2
- wandb/automations/_filters/operators.py +2 -1
- wandb/automations/_validators.py +20 -0
- wandb/automations/actions.py +4 -2
- wandb/automations/events.py +4 -5
- wandb/bin/gpu_stats +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/beta.py +48 -130
- wandb/cli/beta_sync.py +226 -0
- wandb/cli/cli.py +1 -1
- wandb/integration/dspy/__init__.py +5 -0
- wandb/integration/dspy/dspy.py +422 -0
- wandb/integration/weave/weave.py +55 -0
- wandb/proto/v3/wandb_server_pb2.py +38 -57
- wandb/proto/v3/wandb_sync_pb2.py +87 -0
- wandb/proto/v3/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v4/wandb_server_pb2.py +38 -41
- wandb/proto/v4/wandb_sync_pb2.py +38 -0
- wandb/proto/v4/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v5/wandb_server_pb2.py +38 -41
- wandb/proto/v5/wandb_sync_pb2.py +39 -0
- wandb/proto/v5/wandb_telemetry_pb2.py +12 -12
- wandb/proto/v6/wandb_server_pb2.py +38 -41
- wandb/proto/v6/wandb_sync_pb2.py +49 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +12 -12
- wandb/proto/wandb_generate_proto.py +1 -0
- wandb/proto/wandb_sync_pb2.py +12 -0
- wandb/sdk/artifacts/_validators.py +50 -49
- wandb/sdk/artifacts/artifact.py +11 -11
- wandb/sdk/artifacts/artifact_file_cache.py +1 -1
- wandb/sdk/artifacts/artifact_manifest_entry.py +6 -8
- wandb/sdk/artifacts/exceptions.py +2 -1
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +2 -1
- wandb/sdk/launch/inputs/internal.py +25 -24
- wandb/sdk/launch/inputs/schema.py +31 -1
- wandb/sdk/lib/asyncio_compat.py +88 -23
- wandb/sdk/lib/gql_request.py +18 -7
- wandb/sdk/lib/paths.py +23 -21
- wandb/sdk/lib/printer.py +9 -13
- wandb/sdk/lib/progress.py +8 -6
- wandb/sdk/lib/service/service_connection.py +42 -12
- wandb/sdk/mailbox/wait_with_progress.py +1 -1
- wandb/sdk/wandb_init.py +0 -8
- wandb/sdk/wandb_run.py +14 -2
- wandb/sdk/wandb_settings.py +55 -0
- wandb/sdk/wandb_setup.py +2 -2
- {wandb-0.21.2.dist-info → wandb-0.21.4.dist-info}/METADATA +2 -2
- {wandb-0.21.2.dist-info → wandb-0.21.4.dist-info}/RECORD +68 -57
- {wandb-0.21.2.dist-info → wandb-0.21.4.dist-info}/WHEEL +0 -0
- {wandb-0.21.2.dist-info → wandb-0.21.4.dist-info}/entry_points.txt +0 -0
- {wandb-0.21.2.dist-info → wandb-0.21.4.dist-info}/licenses/LICENSE +0 -0
wandb/__init__.py
CHANGED
wandb/__init__.pyi
CHANGED
wandb/_analytics.py
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from contextvars import ContextVar
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from functools import wraps
|
6
|
+
from typing import Callable, Final, TypeVar
|
7
|
+
from uuid import UUID, uuid4
|
8
|
+
|
9
|
+
from typing_extensions import ParamSpec
|
10
|
+
|
11
|
+
from wandb._strutils import nameof
|
12
|
+
|
13
|
+
P = ParamSpec("P")
|
14
|
+
R = TypeVar("R")
|
15
|
+
|
16
|
+
# Header keys for tracking the calling function
|
17
|
+
X_WANDB_PYTHON_FUNC: Final[str] = "X-Wandb-Python-Func"
|
18
|
+
X_WANDB_PYTHON_CALL_ID: Final[str] = "X-Wandb-Python-Call-Id"
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass(frozen=True)
|
22
|
+
class TrackedFuncInfo:
|
23
|
+
func: str
|
24
|
+
"""The fully qualified namespace of the tracked function."""
|
25
|
+
|
26
|
+
call_id: UUID = field(default_factory=uuid4)
|
27
|
+
"""A unique identifier assigned to each invocation."""
|
28
|
+
|
29
|
+
def to_headers(self) -> dict[str, str]:
|
30
|
+
return {
|
31
|
+
X_WANDB_PYTHON_FUNC: self.func,
|
32
|
+
X_WANDB_PYTHON_CALL_ID: str(self.call_id),
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
_current_func: ContextVar[TrackedFuncInfo] = ContextVar("_current_func")
|
37
|
+
"""An internal, threadsafe context variable to hold the current function being tracked."""
|
38
|
+
|
39
|
+
|
40
|
+
def tracked(func: Callable[P, R]) -> Callable[P, R]:
|
41
|
+
"""A decorator to inject the calling function name into any GraphQL request headers.
|
42
|
+
|
43
|
+
If a tracked function calls another tracked function, only the outermost function in
|
44
|
+
the call stack will be tracked.
|
45
|
+
"""
|
46
|
+
func_namespace = f"{func.__module__}.{nameof(func)}"
|
47
|
+
|
48
|
+
@wraps(func)
|
49
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
50
|
+
# Don't override the current tracked function if it's already set
|
51
|
+
if tracked_func():
|
52
|
+
return func(*args, **kwargs)
|
53
|
+
|
54
|
+
token = _current_func.set(TrackedFuncInfo(func=func_namespace))
|
55
|
+
try:
|
56
|
+
return func(*args, **kwargs)
|
57
|
+
finally:
|
58
|
+
_current_func.reset(token)
|
59
|
+
|
60
|
+
return wrapper
|
61
|
+
|
62
|
+
|
63
|
+
def tracked_func() -> TrackedFuncInfo | None:
|
64
|
+
"""Returns info on the current tracked function, if any, otherwise None."""
|
65
|
+
return _current_func.get(None)
|
wandb/_iterutils.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from collections.abc import Hashable
|
3
4
|
from typing import TYPE_CHECKING, Any, Iterable, TypeVar, Union, overload
|
4
5
|
|
5
6
|
if TYPE_CHECKING:
|
6
7
|
T = TypeVar("T")
|
8
|
+
HashableT = TypeVar("HashableT", bound=Hashable)
|
7
9
|
ClassInfo = Union[type[T], tuple[type[T], ...]]
|
8
10
|
|
9
11
|
|
@@ -22,6 +24,12 @@ def always_list(obj: Any, base_type: Any = (str, bytes)) -> list[T]:
|
|
22
24
|
return [obj] if isinstance(obj, base_type) else list(obj)
|
23
25
|
|
24
26
|
|
27
|
+
def unique_list(iterable: Iterable[HashableT]) -> list[HashableT]:
|
28
|
+
"""Return a deduplicated list of items from the given iterable, preserving order."""
|
29
|
+
# Trick for O(1) uniqueness check that maintains order
|
30
|
+
return list(dict.fromkeys(iterable))
|
31
|
+
|
32
|
+
|
25
33
|
def one(
|
26
34
|
iterable: Iterable[T],
|
27
35
|
too_short: type[Exception] | Exception | None = None,
|
wandb/_pydantic/__init__.py
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
"""Internal utilities for working with pydantic."""
|
2
2
|
|
3
|
-
from .base import
|
4
|
-
|
5
|
-
GQLBase,
|
6
|
-
GQLId,
|
7
|
-
SerializedToJson,
|
8
|
-
Typename,
|
9
|
-
ensure_json,
|
10
|
-
)
|
3
|
+
from .base import CompatBaseModel, GQLBase
|
4
|
+
from .field_types import GQLId, Typename
|
11
5
|
from .utils import IS_PYDANTIC_V2, from_json, gql_typename, pydantic_isinstance, to_json
|
12
|
-
from .v1_compat import
|
6
|
+
from .v1_compat import (
|
7
|
+
AliasChoices,
|
8
|
+
computed_field,
|
9
|
+
field_validator,
|
10
|
+
model_validator,
|
11
|
+
to_camel,
|
12
|
+
)
|
13
13
|
|
14
14
|
__all__ = [
|
15
15
|
"IS_PYDANTIC_V2",
|
@@ -17,14 +17,13 @@ __all__ = [
|
|
17
17
|
"GQLBase",
|
18
18
|
"Typename",
|
19
19
|
"GQLId",
|
20
|
-
"SerializedToJson",
|
21
20
|
"AliasChoices",
|
22
21
|
"computed_field",
|
23
22
|
"field_validator",
|
24
23
|
"model_validator",
|
25
24
|
"pydantic_isinstance",
|
25
|
+
"to_camel",
|
26
26
|
"to_json",
|
27
27
|
"from_json",
|
28
|
-
"ensure_json",
|
29
28
|
"gql_typename",
|
30
29
|
]
|
wandb/_pydantic/base.py
CHANGED
@@ -2,12 +2,11 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Callable, Literal
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Literal
|
6
6
|
|
7
|
-
from pydantic import BaseModel, ConfigDict
|
8
|
-
from typing_extensions import
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
8
|
+
from typing_extensions import TypedDict, Unpack, override
|
9
9
|
|
10
|
-
from .utils import IS_PYDANTIC_V2, to_json
|
11
10
|
from .v1_compat import PydanticCompatMixin
|
12
11
|
|
13
12
|
if TYPE_CHECKING:
|
@@ -77,52 +76,3 @@ class GQLBase(CompatBaseModel):
|
|
77
76
|
) -> str:
|
78
77
|
kwargs = {**MODEL_DUMP_DEFAULTS, **kwargs}
|
79
78
|
return super().model_dump_json(indent=indent, **kwargs)
|
80
|
-
|
81
|
-
|
82
|
-
# ------------------------------------------------------------------------------
|
83
|
-
# Reusable annotations for field types
|
84
|
-
T = TypeVar("T")
|
85
|
-
|
86
|
-
if IS_PYDANTIC_V2 or TYPE_CHECKING:
|
87
|
-
GQLId = Annotated[
|
88
|
-
StrictStr,
|
89
|
-
Field(repr=False, frozen=True),
|
90
|
-
]
|
91
|
-
else:
|
92
|
-
# FIXME: Find a way to fix this for pydantic v1, which doesn't like when
|
93
|
-
# `Field(...)` used in the field assignment AND `Annotated[...]`.
|
94
|
-
# This is a problem for codegen, which can currently outputs e.g.
|
95
|
-
#
|
96
|
-
# class MyModel(GQLBase):
|
97
|
-
# my_id: GQLId = Field(alias="myID")
|
98
|
-
#
|
99
|
-
GQLId = StrictStr # type: ignore[misc]
|
100
|
-
|
101
|
-
Typename = Annotated[
|
102
|
-
T,
|
103
|
-
Field(repr=False, frozen=True, alias="__typename"),
|
104
|
-
]
|
105
|
-
|
106
|
-
|
107
|
-
def ensure_json(v: Any) -> Any:
|
108
|
-
"""In case the incoming value isn't serialized JSON, reserialize it.
|
109
|
-
|
110
|
-
This lets us use `Json[...]` fields with values that are already deserialized.
|
111
|
-
"""
|
112
|
-
# NOTE: Assumes that the deserialized type is not itself a string.
|
113
|
-
# Revisit this if we need to support deserialized types that are str/bytes.
|
114
|
-
return v if isinstance(v, (str, bytes)) else to_json(v)
|
115
|
-
|
116
|
-
|
117
|
-
if IS_PYDANTIC_V2 or TYPE_CHECKING:
|
118
|
-
from pydantic import BeforeValidator, PlainSerializer
|
119
|
-
|
120
|
-
SerializedToJson = Annotated[
|
121
|
-
Json[T],
|
122
|
-
# Allow lenient instantiation/validation: incoming data may already be deserialized.
|
123
|
-
BeforeValidator(ensure_json),
|
124
|
-
PlainSerializer(to_json),
|
125
|
-
]
|
126
|
-
else:
|
127
|
-
# FIXME: Restore, modify, or replace this later after ensuring pydantic v1 compatibility.
|
128
|
-
SerializedToJson = Json[T] # type: ignore[misc]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""Reusable field types and annotations for pydantic fields."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, TypeVar
|
6
|
+
|
7
|
+
from pydantic import Field, StrictStr
|
8
|
+
from typing_extensions import Annotated
|
9
|
+
|
10
|
+
from .utils import IS_PYDANTIC_V2
|
11
|
+
|
12
|
+
T = TypeVar("T")
|
13
|
+
|
14
|
+
|
15
|
+
#: GraphQL `__typename` fields
|
16
|
+
Typename = Annotated[T, Field(repr=False, frozen=True, alias="__typename")]
|
17
|
+
|
18
|
+
|
19
|
+
if IS_PYDANTIC_V2 or TYPE_CHECKING:
|
20
|
+
GQLId = Annotated[StrictStr, Field(repr=False, frozen=True)]
|
21
|
+
|
22
|
+
else:
|
23
|
+
# FIXME: Find a way to fix this for pydantic v1, which doesn't like when
|
24
|
+
# `Field(...)` used in the field assignment AND `Annotated[...]`.
|
25
|
+
# This is a problem for codegen, which can currently output e.g.
|
26
|
+
#
|
27
|
+
# class MyModel(GQLBase):
|
28
|
+
# my_id: GQLId = Field(alias="myID")
|
29
|
+
GQLId = StrictStr # type: ignore[misc]
|
wandb/_pydantic/v1_compat.py
CHANGED
@@ -61,7 +61,12 @@ _V1_CONFIG_KEYS = {
|
|
61
61
|
|
62
62
|
def convert_v2_config(v2_config: dict[str, Any]) -> dict[str, Any]:
|
63
63
|
"""Internal helper: Return a copy of the v2 ConfigDict with renamed v1 keys."""
|
64
|
-
return {
|
64
|
+
return {
|
65
|
+
# Convert v2 config keys to v1 keys
|
66
|
+
**{_V1_CONFIG_KEYS.get(k, k): v for k, v in v2_config.items()},
|
67
|
+
# This is a v1-only config key. In v2, it no longer exists and is effectively always True.
|
68
|
+
"underscore_attrs_are_private": True,
|
69
|
+
}
|
65
70
|
|
66
71
|
|
67
72
|
@lru_cache(maxsize=None) # Reduce repeat introspection via `signature()`
|
@@ -87,12 +92,10 @@ class V1MixinMetaclass(PydanticModelMetaclass):
|
|
87
92
|
**kwargs: Any,
|
88
93
|
):
|
89
94
|
# In the class definition, convert the model config, if any:
|
90
|
-
# # BEFORE
|
91
|
-
# class MyModel(BaseModel): # v2 model with `ConfigDict`
|
95
|
+
# class MyModel(BaseModel): # BEFORE (v2)
|
92
96
|
# model_config = ConfigDict(populate_by_name=True)
|
93
97
|
#
|
94
|
-
# # AFTER
|
95
|
-
# class MyModel(BaseModel): # v1 model with inner `Config` class
|
98
|
+
# class MyModel(BaseModel): # AFTER (v1)
|
96
99
|
# class Config:
|
97
100
|
# allow_population_by_field_name = True
|
98
101
|
if config_dict := namespace.pop("model_config", None):
|
@@ -197,24 +200,41 @@ PydanticCompatMixin: type = V2Mixin if IS_PYDANTIC_V2 else V1Mixin
|
|
197
200
|
# Decorators and other pydantic helpers
|
198
201
|
# ----------------------------------------------------------------------------
|
199
202
|
if IS_PYDANTIC_V2:
|
203
|
+
from pydantic import alias_generators
|
204
|
+
|
205
|
+
# https://docs.pydantic.dev/latest/api/config/#pydantic.alias_generators.to_camel
|
206
|
+
to_camel = alias_generators.to_camel # e.g. "foo_bar" -> "fooBar"
|
207
|
+
|
208
|
+
# https://docs.pydantic.dev/latest/api/functional_validators/#pydantic.functional_validators.field_validator
|
200
209
|
field_validator = pydantic.field_validator
|
210
|
+
|
211
|
+
# https://docs.pydantic.dev/latest/api/functional_validators/#pydantic.functional_validators.model_validator
|
201
212
|
model_validator = pydantic.model_validator
|
202
|
-
|
213
|
+
|
214
|
+
# https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.computed_field
|
203
215
|
computed_field = pydantic.computed_field
|
204
216
|
|
217
|
+
# https://docs.pydantic.dev/latest/api/aliases/#pydantic.aliases.AliasChoices
|
218
|
+
AliasChoices = pydantic.AliasChoices
|
219
|
+
|
205
220
|
else:
|
206
|
-
|
207
|
-
|
221
|
+
from pydantic.utils import to_lower_camel
|
222
|
+
|
223
|
+
V2ValidatorMode = Literal["before", "after", "wrap", "plain"]
|
224
|
+
|
225
|
+
# NOTE:
|
226
|
+
# - `to_lower_camel` in v1 is the equivalent of `to_camel` in v2 (i.e. to lowerCamelCase)
|
227
|
+
# - `to_camel` in v1 is the equivalent of `to_pascal` in v2 (i.e. to UpperCamelCase)
|
228
|
+
to_camel = to_lower_camel
|
229
|
+
|
230
|
+
# Lets us use `@field_validator` from v2, while calling `@validator` from v1 if needed.
|
208
231
|
def field_validator(
|
209
|
-
field: str,
|
210
|
-
/,
|
211
232
|
*fields: str,
|
212
|
-
mode:
|
233
|
+
mode: V2ValidatorMode = "after",
|
213
234
|
check_fields: bool | None = None,
|
214
235
|
**_: Any,
|
215
236
|
) -> Callable:
|
216
237
|
return pydantic.validator( # type: ignore[deprecated]
|
217
|
-
field,
|
218
238
|
*fields,
|
219
239
|
pre=(mode == "before"),
|
220
240
|
always=True,
|
@@ -222,31 +242,28 @@ else:
|
|
222
242
|
allow_reuse=True,
|
223
243
|
)
|
224
244
|
|
225
|
-
#
|
226
|
-
|
227
|
-
def model_validator(
|
228
|
-
*,
|
229
|
-
mode: Literal["before", "after", "wrap", "plain"],
|
230
|
-
**_: Any,
|
231
|
-
) -> Callable:
|
245
|
+
# Lets us use `@model_validator` from v2, while calling `@root_validator` from v1 if needed.
|
246
|
+
def model_validator(*, mode: V2ValidatorMode, **_: Any) -> Callable:
|
232
247
|
if mode == "after":
|
233
|
-
|
234
|
-
# necessarily complicated because:
|
235
|
-
# - `@model_validator(mode="after")` decorates an instance method in pydantic v2
|
236
|
-
# - `@root_validator(pre=False)` always decorates a classmethod in pydantic v1
|
248
|
+
|
237
249
|
def _decorator(v2_method: Callable) -> Any:
|
250
|
+
# Patch the behavior for `@model_validator(mode="after")` in v1.
|
251
|
+
#
|
252
|
+
# This is necessarily complicated because:
|
253
|
+
# - `@model_validator(mode="after")` always decorates an instance method in v2,
|
254
|
+
# i.e. the decorated function has `self` as the first arg.
|
255
|
+
# - `@root_validator(pre=False)` always decorates a classmethod in v1,
|
256
|
+
# i.e. the decorated function has `cls` as the first arg.
|
257
|
+
|
238
258
|
def v1_method(
|
239
259
|
cls: type[V1Model], values: dict[str, Any]
|
240
260
|
) -> dict[str, Any]:
|
241
|
-
#
|
242
|
-
#
|
243
|
-
|
244
|
-
validated = v2_method(cls.construct(**values))
|
261
|
+
# values should already be validated in an "after" validator, so use `construct()`
|
262
|
+
# to instantiate without (re-)validating.
|
263
|
+
v_self = v2_method(cls.construct(**values))
|
245
264
|
|
246
265
|
# Pydantic v1 expects the validator to return a dict of {field_name -> value}
|
247
|
-
return {
|
248
|
-
name: getattr(validated, name) for name in validated.__fields__
|
249
|
-
}
|
266
|
+
return {f: getattr(v_self, f) for f in v_self.__fields__}
|
250
267
|
|
251
268
|
return pydantic.root_validator(pre=False, allow_reuse=True)( # type: ignore[call-overload]
|
252
269
|
classmethod(v1_method)
|
wandb/_strutils.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
|
6
|
+
def removeprefix(s: str, prefix: str) -> str:
|
7
|
+
"""Removes a prefix from a string.
|
8
|
+
|
9
|
+
This roughly backports the built-in `str.removeprefix` function from Python 3.9+.
|
10
|
+
Once Python 3.8 support is dropped, just replace this with `str.removeprefix`.
|
11
|
+
"""
|
12
|
+
return s[len(prefix) :] if s.startswith(prefix) else s
|
13
|
+
|
14
|
+
|
15
|
+
def removesuffix(s: str, suffix: str) -> str:
|
16
|
+
"""Removes a suffix from a string.
|
17
|
+
|
18
|
+
This roughly backports the built-in `str.removesuffix` function from Python 3.9+.
|
19
|
+
Once Python 3.8 support is dropped, just replace this with `str.removesuffix`.
|
20
|
+
"""
|
21
|
+
return s[: -len(suffix)] if s.endswith(suffix) else s
|
22
|
+
|
23
|
+
|
24
|
+
def ensureprefix(s: str, prefix: str) -> str:
|
25
|
+
"""Ensures the string has the given prefix prepended."""
|
26
|
+
return s if s.startswith(prefix) else f"{prefix}{s}"
|
27
|
+
|
28
|
+
|
29
|
+
def ensuresuffix(s: str, suffix: str) -> str:
|
30
|
+
"""Ensures the string has the given suffix appended."""
|
31
|
+
return s if s.endswith(suffix) else f"{s}{suffix}"
|
32
|
+
|
33
|
+
|
34
|
+
def nameof(obj: Any, full: bool = True) -> str:
|
35
|
+
"""Internal convenience helper that returns the object's `__name__` or `__qualname__`.
|
36
|
+
|
37
|
+
If `full` is True, attempt to return the object's `__qualname__` attribute,
|
38
|
+
falling back on the `__name__` attribute.
|
39
|
+
"""
|
40
|
+
return getattr(obj, "__qualname__", obj.__name__) if full else obj.__name__
|
wandb/apis/public/api.py
CHANGED
@@ -36,7 +36,9 @@ from wandb_gql.client import RetryError
|
|
36
36
|
|
37
37
|
import wandb
|
38
38
|
from wandb import env, util
|
39
|
+
from wandb._analytics import tracked
|
39
40
|
from wandb._iterutils import one
|
41
|
+
from wandb._strutils import nameof
|
40
42
|
from wandb.apis import public
|
41
43
|
from wandb.apis.normalize import normalize_exceptions
|
42
44
|
from wandb.apis.public.const import RETRY_TIMEDELTA
|
@@ -1730,6 +1732,7 @@ class Api:
|
|
1730
1732
|
|
1731
1733
|
return True
|
1732
1734
|
|
1735
|
+
@tracked
|
1733
1736
|
def registries(
|
1734
1737
|
self,
|
1735
1738
|
organization: Optional[str] = None,
|
@@ -1797,6 +1800,7 @@ class Api:
|
|
1797
1800
|
)
|
1798
1801
|
return Registries(self.client, organization, filter)
|
1799
1802
|
|
1803
|
+
@tracked
|
1800
1804
|
def registry(self, name: str, organization: Optional[str] = None) -> Registry:
|
1801
1805
|
"""Return a registry given a registry name.
|
1802
1806
|
|
@@ -1836,6 +1840,7 @@ class Api:
|
|
1836
1840
|
registry.load()
|
1837
1841
|
return registry
|
1838
1842
|
|
1843
|
+
@tracked
|
1839
1844
|
def create_registry(
|
1840
1845
|
self,
|
1841
1846
|
name: str,
|
@@ -1910,6 +1915,7 @@ class Api:
|
|
1910
1915
|
artifact_types,
|
1911
1916
|
)
|
1912
1917
|
|
1918
|
+
@tracked
|
1913
1919
|
def integrations(
|
1914
1920
|
self,
|
1915
1921
|
entity: Optional[str] = None,
|
@@ -1933,6 +1939,7 @@ class Api:
|
|
1933
1939
|
params = {"entityName": entity or self.default_entity}
|
1934
1940
|
return Integrations(client=self.client, variables=params, per_page=per_page)
|
1935
1941
|
|
1942
|
+
@tracked
|
1936
1943
|
def webhook_integrations(
|
1937
1944
|
self, entity: Optional[str] = None, *, per_page: int = 50
|
1938
1945
|
) -> Iterator["WebhookIntegration"]:
|
@@ -1976,6 +1983,7 @@ class Api:
|
|
1976
1983
|
client=self.client, variables=params, per_page=per_page
|
1977
1984
|
)
|
1978
1985
|
|
1986
|
+
@tracked
|
1979
1987
|
def slack_integrations(
|
1980
1988
|
self, *, entity: Optional[str] = None, per_page: int = 50
|
1981
1989
|
) -> Iterator["SlackIntegration"]:
|
@@ -2079,10 +2087,10 @@ class Api:
|
|
2079
2087
|
# Note: we can't currently define this as a constant outside the method
|
2080
2088
|
# and still keep it nearby in this module, because it relies on pydantic v2-only imports
|
2081
2089
|
fragment_names: dict[ActionType, str] = {
|
2082
|
-
ActionType.NO_OP: NoOpActionFields
|
2083
|
-
ActionType.QUEUE_JOB: QueueJobActionFields
|
2084
|
-
ActionType.NOTIFICATION: NotificationActionFields
|
2085
|
-
ActionType.GENERIC_WEBHOOK: GenericWebhookActionFields
|
2090
|
+
ActionType.NO_OP: nameof(NoOpActionFields),
|
2091
|
+
ActionType.QUEUE_JOB: nameof(QueueJobActionFields),
|
2092
|
+
ActionType.NOTIFICATION: nameof(NotificationActionFields),
|
2093
|
+
ActionType.GENERIC_WEBHOOK: nameof(GenericWebhookActionFields),
|
2086
2094
|
}
|
2087
2095
|
|
2088
2096
|
return set(
|
@@ -2092,6 +2100,7 @@ class Api:
|
|
2092
2100
|
and (name := fragment_names.get(action))
|
2093
2101
|
)
|
2094
2102
|
|
2103
|
+
@tracked
|
2095
2104
|
def automation(
|
2096
2105
|
self,
|
2097
2106
|
name: str,
|
@@ -2129,6 +2138,7 @@ class Api:
|
|
2129
2138
|
too_long=ValueError("Multiple automations found"),
|
2130
2139
|
)
|
2131
2140
|
|
2141
|
+
@tracked
|
2132
2142
|
def automations(
|
2133
2143
|
self,
|
2134
2144
|
entity: Optional[str] = None,
|
@@ -2186,6 +2196,7 @@ class Api:
|
|
2186
2196
|
yield from iterator
|
2187
2197
|
|
2188
2198
|
@normalize_exceptions
|
2199
|
+
@tracked
|
2189
2200
|
def create_automation(
|
2190
2201
|
self,
|
2191
2202
|
obj: "NewAutomation",
|
@@ -2293,6 +2304,7 @@ class Api:
|
|
2293
2304
|
return Automation.model_validate(result.trigger)
|
2294
2305
|
|
2295
2306
|
@normalize_exceptions
|
2307
|
+
@tracked
|
2296
2308
|
def update_automation(
|
2297
2309
|
self,
|
2298
2310
|
obj: "Automation",
|
@@ -2415,6 +2427,7 @@ class Api:
|
|
2415
2427
|
return Automation.model_validate(result.trigger)
|
2416
2428
|
|
2417
2429
|
@normalize_exceptions
|
2430
|
+
@tracked
|
2418
2431
|
def delete_automation(self, obj: Union["Automation", str]) -> Literal[True]:
|
2419
2432
|
"""Delete an automation.
|
2420
2433
|
|
wandb/apis/public/artifacts.py
CHANGED
@@ -15,6 +15,7 @@ from typing_extensions import override
|
|
15
15
|
from wandb_gql import Client, gql
|
16
16
|
|
17
17
|
import wandb
|
18
|
+
from wandb._strutils import nameof
|
18
19
|
from wandb.apis import public
|
19
20
|
from wandb.apis.normalize import normalize_exceptions
|
20
21
|
from wandb.apis.paginator import Paginator, SizedPaginator
|
@@ -104,7 +105,7 @@ class ArtifactTypes(Paginator["ArtifactType"]):
|
|
104
105
|
|
105
106
|
# Extract the inner `*Connection` result for faster/easier access.
|
106
107
|
if not ((proj := result.project) and (conn := proj.artifact_types)):
|
107
|
-
raise ValueError(f"Unable to parse {type(self)
|
108
|
+
raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
|
108
109
|
|
109
110
|
self.last_response = ArtifactTypesFragment.model_validate(conn)
|
110
111
|
|
@@ -305,7 +306,7 @@ class ArtifactCollections(SizedPaginator["ArtifactCollection"]):
|
|
305
306
|
and (type_ := proj.artifact_type)
|
306
307
|
and (conn := type_.artifact_collections)
|
307
308
|
):
|
308
|
-
raise ValueError(f"Unable to parse {type(self)
|
309
|
+
raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
|
309
310
|
|
310
311
|
self.last_response = ArtifactCollectionsFragment.model_validate(conn)
|
311
312
|
|
@@ -732,7 +733,7 @@ class Artifacts(SizedPaginator["Artifact"]):
|
|
732
733
|
and (collection := type_.artifact_collection)
|
733
734
|
and (conn := collection.artifacts)
|
734
735
|
):
|
735
|
-
raise ValueError(f"Unable to parse {type(self)
|
736
|
+
raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
|
736
737
|
|
737
738
|
self.last_response = ArtifactsFragment.model_validate(conn)
|
738
739
|
|
@@ -959,7 +960,7 @@ class ArtifactFiles(SizedPaginator["public.File"]):
|
|
959
960
|
conn = result.project.artifact_type.artifact.files
|
960
961
|
|
961
962
|
if conn is None:
|
962
|
-
raise ValueError(f"Unable to parse {type(self)
|
963
|
+
raise ValueError(f"Unable to parse {nameof(type(self))!r} response data")
|
963
964
|
|
964
965
|
self.last_response = FilesFragment.model_validate(conn)
|
965
966
|
|
wandb/apis/public/automations.py
CHANGED
@@ -9,6 +9,7 @@ from pydantic import ValidationError
|
|
9
9
|
from typing_extensions import override
|
10
10
|
from wandb_graphql.language.ast import Document
|
11
11
|
|
12
|
+
from wandb._strutils import nameof
|
12
13
|
from wandb.apis.paginator import Paginator, _Client
|
13
14
|
|
14
15
|
if TYPE_CHECKING:
|
@@ -34,7 +35,7 @@ class Automations(Paginator["Automation"]):
|
|
34
35
|
):
|
35
36
|
super().__init__(client, variables, per_page=per_page)
|
36
37
|
if _query is None:
|
37
|
-
raise RuntimeError(f"Query required for {type(self)
|
38
|
+
raise RuntimeError(f"Query required for {nameof(type(self))}")
|
38
39
|
self._query = _query
|
39
40
|
|
40
41
|
@property
|
@@ -13,6 +13,8 @@ from typing import (
|
|
13
13
|
overload,
|
14
14
|
)
|
15
15
|
|
16
|
+
from wandb._strutils import nameof
|
17
|
+
|
16
18
|
T = TypeVar("T")
|
17
19
|
|
18
20
|
|
@@ -84,9 +86,7 @@ class FreezableList(MutableSequence[T]):
|
|
84
86
|
) -> None:
|
85
87
|
if isinstance(index, slice):
|
86
88
|
# Setting slices might affect saved items, disallow for simplicity
|
87
|
-
raise TypeError(
|
88
|
-
f"{type(self).__name__!r} does not support slice assignment"
|
89
|
-
)
|
89
|
+
raise TypeError(f"{nameof(type(self))!r} does not support slice assignment")
|
90
90
|
else:
|
91
91
|
if value in self._frozen or value in self._draft:
|
92
92
|
return
|
@@ -111,7 +111,7 @@ class FreezableList(MutableSequence[T]):
|
|
111
111
|
|
112
112
|
def __delitem__(self, index: Union[int, slice]) -> None:
|
113
113
|
if isinstance(index, slice):
|
114
|
-
raise TypeError(f"{type(self)
|
114
|
+
raise TypeError(f"{nameof(type(self))!r} does not support slice deletion")
|
115
115
|
else:
|
116
116
|
# The frozen items are sequentially first and protected from changes
|
117
117
|
len_frozen = len(self._frozen)
|
@@ -158,7 +158,7 @@ class FreezableList(MutableSequence[T]):
|
|
158
158
|
return self._draft.insert(draft_index, value)
|
159
159
|
|
160
160
|
def __repr__(self) -> str:
|
161
|
-
return f"{type(self)
|
161
|
+
return f"{nameof(type(self))}(frozen={list(self._frozen)!r}, draft={list(self._draft)!r})"
|
162
162
|
|
163
163
|
@property
|
164
164
|
def draft(self) -> Tuple[T, ...]:
|
@@ -176,4 +176,4 @@ class AddOnlyArtifactTypesList(FreezableList[str]):
|
|
176
176
|
)
|
177
177
|
|
178
178
|
def __repr__(self) -> str:
|
179
|
-
return f"{type(self)
|
179
|
+
return f"{nameof(type(self))}(saved={list(self._frozen)!r}, draft={list(self._draft)!r})"
|
@@ -4,6 +4,7 @@ from enum import Enum
|
|
4
4
|
from functools import lru_cache
|
5
5
|
from typing import TYPE_CHECKING, Any, Literal, Mapping, Sequence
|
6
6
|
|
7
|
+
from wandb._strutils import ensureprefix
|
7
8
|
from wandb.sdk.artifacts._validators import (
|
8
9
|
REGISTRY_PREFIX,
|
9
10
|
validate_artifact_types_list,
|
@@ -81,7 +82,7 @@ def ensure_registry_prefix_on_names(query: Any, in_name: bool = False) -> Any:
|
|
81
82
|
"""
|
82
83
|
if isinstance((txt := query), str):
|
83
84
|
if in_name:
|
84
|
-
return txt
|
85
|
+
return ensureprefix(txt, REGISTRY_PREFIX)
|
85
86
|
return txt
|
86
87
|
if isinstance((dct := query), Mapping):
|
87
88
|
new_dict = {}
|
@@ -9,6 +9,7 @@ from pydantic import ValidationError
|
|
9
9
|
from typing_extensions import override
|
10
10
|
from wandb_gql import gql
|
11
11
|
|
12
|
+
from wandb._analytics import tracked
|
12
13
|
from wandb.apis.paginator import Paginator
|
13
14
|
from wandb.apis.public.utils import gql_compat
|
14
15
|
from wandb.sdk.artifacts._generated import (
|
@@ -69,6 +70,7 @@ class Registries(Paginator):
|
|
69
70
|
raise StopIteration
|
70
71
|
return self.objects[self.index]
|
71
72
|
|
73
|
+
@tracked
|
72
74
|
def collections(self, filter: dict[str, Any] | None = None) -> Collections:
|
73
75
|
return Collections(
|
74
76
|
client=self.client,
|
@@ -77,6 +79,7 @@ class Registries(Paginator):
|
|
77
79
|
collection_filter=filter,
|
78
80
|
)
|
79
81
|
|
82
|
+
@tracked
|
80
83
|
def versions(self, filter: dict[str, Any] | None = None) -> Versions:
|
81
84
|
return Versions(
|
82
85
|
client=self.client,
|
@@ -177,6 +180,7 @@ class Collections(Paginator["ArtifactCollection"]):
|
|
177
180
|
raise StopIteration
|
178
181
|
return self.objects[self.index]
|
179
182
|
|
183
|
+
@tracked
|
180
184
|
def versions(self, filter: dict[str, Any] | None = None) -> Versions:
|
181
185
|
return Versions(
|
182
186
|
client=self.client,
|