wandb 0.21.3__py3-none-musllinux_1_2_aarch64.whl → 0.22.0__py3-none-musllinux_1_2_aarch64.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/__init__.py +42 -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/apis/public/runs.py +24 -6
- 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/integration/dspy/__init__.py +5 -0
- wandb/integration/dspy/dspy.py +422 -0
- wandb/integration/weave/weave.py +55 -0
- wandb/proto/v3/wandb_internal_pb2.py +234 -224
- 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_internal_pb2.py +226 -224
- 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_internal_pb2.py +226 -224
- 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_base_pb2.py +3 -3
- wandb/proto/v6/wandb_internal_pb2.py +229 -227
- wandb/proto/v6/wandb_server_pb2.py +41 -44
- wandb/proto/v6/wandb_settings_pb2.py +3 -3
- wandb/proto/v6/wandb_sync_pb2.py +49 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +15 -15
- 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 +7 -7
- wandb/sdk/artifacts/exceptions.py +2 -1
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/http_handler.py +1 -3
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +3 -2
- wandb/sdk/artifacts/storage_policies/_factories.py +63 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +59 -124
- wandb/sdk/interface/interface.py +10 -0
- wandb/sdk/interface/interface_shared.py +9 -0
- wandb/sdk/lib/asyncio_compat.py +88 -23
- wandb/sdk/lib/gql_request.py +18 -7
- 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 +9 -9
- wandb/sdk/wandb_run.py +13 -1
- wandb/sdk/wandb_settings.py +55 -0
- wandb/wandb_agent.py +35 -4
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/METADATA +1 -1
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/RECORD +818 -806
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/WHEEL +0 -0
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/entry_points.txt +0 -0
- {wandb-0.21.3.dist-info → wandb-0.22.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,
|
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional
|
|
3
3
|
from wandb_gql import gql
|
4
4
|
|
5
5
|
import wandb
|
6
|
+
from wandb._analytics import tracked
|
6
7
|
from wandb.proto.wandb_internal_pb2 import ServerFeature
|
7
8
|
from wandb.sdk.artifacts._validators import REGISTRY_PREFIX, validate_project_name
|
8
9
|
from wandb.sdk.internal.internal_api import Api as InternalApi
|
@@ -177,6 +178,7 @@ class Registry:
|
|
177
178
|
"""
|
178
179
|
self._visibility = value
|
179
180
|
|
181
|
+
@tracked
|
180
182
|
def collections(self, filter: Optional[Dict[str, Any]] = None) -> Collections:
|
181
183
|
"""Returns the collections belonging to the registry."""
|
182
184
|
registry_filter = {
|
@@ -184,6 +186,7 @@ class Registry:
|
|
184
186
|
}
|
185
187
|
return Collections(self.client, self.organization, registry_filter, filter)
|
186
188
|
|
189
|
+
@tracked
|
187
190
|
def versions(self, filter: Optional[Dict[str, Any]] = None) -> Versions:
|
188
191
|
"""Returns the versions belonging to the registry."""
|
189
192
|
registry_filter = {
|
@@ -192,6 +195,7 @@ class Registry:
|
|
192
195
|
return Versions(self.client, self.organization, registry_filter, None, filter)
|
193
196
|
|
194
197
|
@classmethod
|
198
|
+
@tracked
|
195
199
|
def create(
|
196
200
|
cls,
|
197
201
|
client: "Client",
|
@@ -256,6 +260,7 @@ class Registry:
|
|
256
260
|
response["upsertModel"]["project"],
|
257
261
|
)
|
258
262
|
|
263
|
+
@tracked
|
259
264
|
def delete(self) -> None:
|
260
265
|
"""Delete the registry. This is irreversible."""
|
261
266
|
try:
|
@@ -272,6 +277,7 @@ class Registry:
|
|
272
277
|
f"Failed to delete registry: {self.name!r} in organization: {self.organization!r}"
|
273
278
|
)
|
274
279
|
|
280
|
+
@tracked
|
275
281
|
def load(self) -> None:
|
276
282
|
"""Load the registry attributes from the backend to reflect the latest saved state."""
|
277
283
|
load_failure_message = (
|
@@ -295,6 +301,7 @@ class Registry:
|
|
295
301
|
raise ValueError(load_failure_message)
|
296
302
|
self._update_attributes(self.attrs)
|
297
303
|
|
304
|
+
@tracked
|
298
305
|
def save(self) -> None:
|
299
306
|
"""Save registry attributes to the backend."""
|
300
307
|
if not InternalApi()._server_supports(
|
wandb/apis/public/runs.py
CHANGED
@@ -88,10 +88,28 @@ RUN_FRAGMENT = """fragment RunFragment on Run {
|
|
88
88
|
|
89
89
|
@normalize_exceptions
|
90
90
|
def _server_provides_internal_id_for_project(client) -> bool:
|
91
|
-
"""Returns True if the server allows us to query the internalId field for a project.
|
92
|
-
|
93
|
-
|
91
|
+
"""Returns True if the server allows us to query the internalId field for a project."""
|
92
|
+
query_string = """
|
93
|
+
query ProbeProjectInput {
|
94
|
+
ProjectType: __type(name:"Project") {
|
95
|
+
fields {
|
96
|
+
name
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
94
100
|
"""
|
101
|
+
|
102
|
+
# Only perform the query once to avoid extra network calls
|
103
|
+
query = gql(query_string)
|
104
|
+
res = client.execute(query)
|
105
|
+
return "internalId" in [
|
106
|
+
x["name"] for x in (res.get("ProjectType", {}).get("fields", [{}]))
|
107
|
+
]
|
108
|
+
|
109
|
+
|
110
|
+
@normalize_exceptions
|
111
|
+
def _server_provides_project_id_for_run(client) -> bool:
|
112
|
+
"""Returns True if the server allows us to query the projectId field for a run."""
|
95
113
|
query_string = """
|
96
114
|
query ProbeRunInput {
|
97
115
|
RunType: __type(name:"Run") {
|
@@ -209,13 +227,13 @@ class Runs(SizedPaginator["Run"]):
|
|
209
227
|
f"""#graphql
|
210
228
|
query Runs($project: String!, $entity: String!, $cursor: String, $perPage: Int = 50, $order: String, $filters: JSONString) {{
|
211
229
|
project(name: $project, entityName: $entity) {{
|
212
|
-
internalId
|
230
|
+
{"internalId" if _server_provides_internal_id_for_project(client) else ""}
|
213
231
|
runCount(filters: $filters)
|
214
232
|
readOnly
|
215
233
|
runs(filters: $filters, after: $cursor, first: $perPage, order: $order) {{
|
216
234
|
edges {{
|
217
235
|
node {{
|
218
|
-
{"projectId" if
|
236
|
+
{"projectId" if _server_provides_project_id_for_run(client) else ""}
|
219
237
|
...RunFragment
|
220
238
|
}}
|
221
239
|
cursor
|
@@ -603,7 +621,7 @@ class Run(Attrs):
|
|
603
621
|
query Run($project: String!, $entity: String!, $name: String!) {{
|
604
622
|
project(name: $project, entityName: $entity) {{
|
605
623
|
run(name: $name) {{
|
606
|
-
{"projectId" if
|
624
|
+
{"projectId" if _server_provides_project_id_for_run(self.client) else ""}
|
607
625
|
...RunFragment
|
608
626
|
}}
|
609
627
|
}}
|
@@ -9,6 +9,7 @@ from pydantic import ConfigDict, model_serializer
|
|
9
9
|
from typing_extensions import Self, TypeAlias, get_args
|
10
10
|
|
11
11
|
from wandb._pydantic import CompatBaseModel, model_validator
|
12
|
+
from wandb._strutils import nameof
|
12
13
|
|
13
14
|
from .operators import (
|
14
15
|
Contains,
|
@@ -59,7 +60,7 @@ class FilterableField:
|
|
59
60
|
return self._name
|
60
61
|
|
61
62
|
def __repr__(self) -> str:
|
62
|
-
return f"{type(self)
|
63
|
+
return f"{nameof(type(self))}({self._name!r})"
|
63
64
|
|
64
65
|
# Methods to define filter expressions through chaining
|
65
66
|
def matches_regex(self, pattern: str) -> FilterExpr:
|
@@ -145,7 +146,7 @@ class FilterExpr(CompatBaseModel, SupportsLogicalOpSyntax):
|
|
145
146
|
op: Op
|
146
147
|
|
147
148
|
def __repr__(self) -> str:
|
148
|
-
return f"{type(self)
|
149
|
+
return f"{nameof(type(self))}({self.field!s}: {self.op!r})"
|
149
150
|
|
150
151
|
def __rich_repr__(self) -> RichReprResult:
|
151
152
|
# https://rich.readthedocs.io/en/stable/pretty.html
|
@@ -8,6 +8,7 @@ from pydantic import ConfigDict, Field, StrictBool, StrictFloat, StrictInt, Stri
|
|
8
8
|
from typing_extensions import TypeAlias, get_args
|
9
9
|
|
10
10
|
from wandb._pydantic import GQLBase
|
11
|
+
from wandb._strutils import nameof
|
11
12
|
|
12
13
|
# for type annotations
|
13
14
|
Scalar = Union[StrictStr, StrictInt, StrictFloat, StrictBool]
|
@@ -62,7 +63,7 @@ class BaseOp(GQLBase, SupportsLogicalOpSyntax):
|
|
62
63
|
def __repr__(self) -> str:
|
63
64
|
# Display operand as a positional arg
|
64
65
|
values_repr = ", ".join(map(repr, self.model_dump().values()))
|
65
|
-
return f"{type(self)
|
66
|
+
return f"{nameof(type(self))}({values_repr})"
|
66
67
|
|
67
68
|
def __rich_repr__(self) -> RichReprResult:
|
68
69
|
# Display field values as positional args:
|
wandb/automations/_validators.py
CHANGED
@@ -5,13 +5,33 @@ from functools import singledispatch
|
|
5
5
|
from itertools import chain
|
6
6
|
from typing import Any, TypeVar
|
7
7
|
|
8
|
+
from pydantic import BeforeValidator, Json, PlainSerializer
|
8
9
|
from pydantic_core import PydanticUseDefault
|
10
|
+
from typing_extensions import Annotated
|
11
|
+
|
12
|
+
from wandb._pydantic import to_json
|
9
13
|
|
10
14
|
from ._filters import And, FilterExpr, In, Nor, Not, NotIn, Op, Or
|
11
15
|
|
12
16
|
T = TypeVar("T")
|
13
17
|
|
14
18
|
|
19
|
+
def ensure_json(v: Any) -> Any:
|
20
|
+
"""In case the incoming value isn't serialized JSON, reserialize it.
|
21
|
+
|
22
|
+
This lets us use `Json[...]` fields with values that are already deserialized.
|
23
|
+
"""
|
24
|
+
# NOTE: Assumes that the deserialized type is not itself a string.
|
25
|
+
# Revisit this if we need to support deserialized types that are str/bytes.
|
26
|
+
return v if isinstance(v, (str, bytes)) else to_json(v)
|
27
|
+
|
28
|
+
|
29
|
+
# Allow lenient instantiation/validation: incoming data may already be deserialized.
|
30
|
+
SerializedToJson = Annotated[
|
31
|
+
Json[T], BeforeValidator(ensure_json), PlainSerializer(to_json)
|
32
|
+
]
|
33
|
+
|
34
|
+
|
15
35
|
class LenientStrEnum(str, Enum):
|
16
36
|
"""A string enum allowing for case-insensitive lookups by value.
|
17
37
|
|
wandb/automations/actions.py
CHANGED
@@ -7,7 +7,8 @@ from typing import Any, Literal, Optional, Union
|
|
7
7
|
from pydantic import BeforeValidator, Field
|
8
8
|
from typing_extensions import Annotated, Self, get_args
|
9
9
|
|
10
|
-
from wandb._pydantic import GQLBase, GQLId,
|
10
|
+
from wandb._pydantic import GQLBase, GQLId, Typename
|
11
|
+
from wandb._strutils import nameof
|
11
12
|
|
12
13
|
from ._generated import (
|
13
14
|
AlertSeverity,
|
@@ -21,6 +22,7 @@ from ._generated import (
|
|
21
22
|
)
|
22
23
|
from ._validators import (
|
23
24
|
LenientStrEnum,
|
25
|
+
SerializedToJson,
|
24
26
|
default_if_none,
|
25
27
|
to_input_action,
|
26
28
|
to_saved_action,
|
@@ -214,5 +216,5 @@ InputActionTypes: tuple[type, ...] = get_args(InputAction.__origin__) # type: i
|
|
214
216
|
|
215
217
|
__all__ = [
|
216
218
|
"ActionType",
|
217
|
-
*(cls
|
219
|
+
*(nameof(cls) for cls in InputActionTypes),
|
218
220
|
]
|
wandb/automations/events.py
CHANGED
@@ -9,18 +9,17 @@ from typing_extensions import Annotated, Self, get_args
|
|
9
9
|
|
10
10
|
from wandb._pydantic import (
|
11
11
|
GQLBase,
|
12
|
-
SerializedToJson,
|
13
|
-
ensure_json,
|
14
12
|
field_validator,
|
15
13
|
model_validator,
|
16
14
|
pydantic_isinstance,
|
17
15
|
)
|
16
|
+
from wandb._strutils import nameof
|
18
17
|
|
19
18
|
from ._filters import And, MongoLikeFilter, Or
|
20
19
|
from ._filters.expressions import FilterableField
|
21
20
|
from ._filters.run_metrics import MetricChangeFilter, MetricThresholdFilter, MetricVal
|
22
21
|
from ._generated import FilterEventFields
|
23
|
-
from ._validators import LenientStrEnum, simplify_op
|
22
|
+
from ._validators import LenientStrEnum, SerializedToJson, ensure_json, simplify_op
|
24
23
|
from .actions import InputAction, InputActionTypes, SavedActionTypes
|
25
24
|
from .scopes import ArtifactCollectionScope, AutomationScope, ProjectScope
|
26
25
|
|
@@ -156,7 +155,7 @@ class _BaseEventInput(GQLBase):
|
|
156
155
|
if isinstance(action, (InputActionTypes, SavedActionTypes)):
|
157
156
|
return NewAutomation(event=self, action=action)
|
158
157
|
|
159
|
-
raise TypeError(f"Expected a valid action, got: {type(action)
|
158
|
+
raise TypeError(f"Expected a valid action, got: {nameof(type(action))!r}")
|
160
159
|
|
161
160
|
def __rshift__(self, other: InputAction) -> NewAutomation:
|
162
161
|
"""Implements `event >> action` to define an Automation with this event and action."""
|
@@ -277,7 +276,7 @@ OnRunMetric.model_rebuild()
|
|
277
276
|
|
278
277
|
__all__ = [
|
279
278
|
"EventType",
|
280
|
-
*(cls
|
279
|
+
*(nameof(cls) for cls in InputEventTypes),
|
281
280
|
"RunEvent",
|
282
281
|
"ArtifactEvent",
|
283
282
|
"MetricThresholdFilter",
|
wandb/bin/gpu_stats
CHANGED
Binary file
|
wandb/bin/wandb-core
CHANGED
Binary file
|
wandb/cli/beta.py
CHANGED
@@ -6,13 +6,10 @@ These commands are experimental and may change or be removed in future versions.
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import pathlib
|
9
|
-
import sys
|
10
9
|
|
11
10
|
import click
|
12
11
|
|
13
|
-
import wandb
|
14
12
|
from wandb.errors import WandbCoreNotAvailableError
|
15
|
-
from wandb.sdk.wandb_sync import _sync
|
16
13
|
from wandb.util import get_core_path
|
17
14
|
|
18
15
|
|
@@ -35,141 +32,62 @@ def beta():
|
|
35
32
|
)
|
36
33
|
|
37
34
|
|
38
|
-
@beta.command(
|
39
|
-
|
40
|
-
context_settings={"default_map": {}},
|
41
|
-
help="Upload a training run to W&B",
|
42
|
-
)
|
43
|
-
@click.pass_context
|
44
|
-
@click.argument("wandb_dir", nargs=1, type=click.Path(exists=True))
|
45
|
-
@click.option("--id", "run_id", help="The run you want to upload to.")
|
46
|
-
@click.option("--project", "-p", help="The project you want to upload to.")
|
47
|
-
@click.option("--entity", "-e", help="The entity to scope to.")
|
48
|
-
@click.option("--skip-console", is_flag=True, default=False, help="Skip console logs")
|
49
|
-
@click.option("--append", is_flag=True, default=False, help="Append run")
|
50
|
-
@click.option(
|
51
|
-
"--include",
|
52
|
-
"-i",
|
53
|
-
help="Glob to include. Can be used multiple times.",
|
54
|
-
multiple=True,
|
55
|
-
)
|
35
|
+
@beta.command()
|
36
|
+
@click.argument("paths", type=click.Path(exists=True), nargs=-1)
|
56
37
|
@click.option(
|
57
|
-
"--
|
58
|
-
|
59
|
-
|
60
|
-
|
38
|
+
"--skip-synced/--no-skip-synced",
|
39
|
+
is_flag=True,
|
40
|
+
default=True,
|
41
|
+
help="Skip runs that have already been synced with this command.",
|
61
42
|
)
|
62
43
|
@click.option(
|
63
|
-
"--
|
44
|
+
"--dry-run",
|
64
45
|
is_flag=True,
|
65
|
-
default=
|
66
|
-
help="
|
46
|
+
default=False,
|
47
|
+
help="Print what would happen without uploading anything.",
|
67
48
|
)
|
68
49
|
@click.option(
|
69
|
-
"
|
50
|
+
"-v",
|
51
|
+
"--verbose",
|
70
52
|
is_flag=True,
|
71
|
-
default=
|
72
|
-
help="
|
53
|
+
default=False,
|
54
|
+
help="Print more information.",
|
73
55
|
)
|
74
56
|
@click.option(
|
75
|
-
"
|
57
|
+
"-n",
|
58
|
+
default=5,
|
59
|
+
help="Max number of runs to sync at a time.",
|
76
60
|
)
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
skip_console: bool = False,
|
84
|
-
append: bool = False,
|
85
|
-
include: str | None = None,
|
86
|
-
exclude: str | None = None,
|
87
|
-
skip_synced: bool = True,
|
88
|
-
mark_synced: bool = True,
|
89
|
-
dry_run: bool = False,
|
61
|
+
def sync(
|
62
|
+
paths: tuple[str, ...],
|
63
|
+
skip_synced: bool,
|
64
|
+
dry_run: bool,
|
65
|
+
verbose: bool,
|
66
|
+
n: int,
|
90
67
|
) -> None:
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
if not d.is_dir():
|
118
|
-
continue
|
119
|
-
if d in paths:
|
120
|
-
paths.remove(d)
|
121
|
-
|
122
|
-
# remove paths that are already synced, if requested
|
123
|
-
if skip_synced:
|
124
|
-
synced_paths = set()
|
125
|
-
for path in paths:
|
126
|
-
wandb_synced_files = [p for p in path.glob("*.wandb.synced") if p.is_file()]
|
127
|
-
if len(wandb_synced_files) > 1:
|
128
|
-
wandb.termwarn(
|
129
|
-
f"Multiple wandb.synced files found in directory {path}, skipping"
|
130
|
-
)
|
131
|
-
elif len(wandb_synced_files) == 1:
|
132
|
-
synced_paths.add(path)
|
133
|
-
paths -= synced_paths
|
134
|
-
|
135
|
-
if run_id and len(paths) > 1:
|
136
|
-
# TODO: handle this more gracefully
|
137
|
-
click.echo("id can only be set for a single run.", err=True)
|
138
|
-
sys.exit(1)
|
139
|
-
|
140
|
-
if not paths:
|
141
|
-
click.echo("No runs to sync.")
|
142
|
-
return
|
143
|
-
|
144
|
-
click.echo("Found runs:")
|
145
|
-
for path in paths:
|
146
|
-
click.echo(f" {path}")
|
147
|
-
|
148
|
-
if dry_run:
|
149
|
-
return
|
150
|
-
|
151
|
-
wandb.setup()
|
152
|
-
|
153
|
-
# TODO: make it thread-safe in the Rust code
|
154
|
-
with concurrent.futures.ProcessPoolExecutor(
|
155
|
-
max_workers=min(len(paths), cpu_count())
|
156
|
-
) as executor:
|
157
|
-
futures = []
|
158
|
-
for path in paths:
|
159
|
-
# we already know there is only one wandb file in the directory
|
160
|
-
wandb_file = [p for p in path.glob("*.wandb") if p.is_file()][0]
|
161
|
-
future = executor.submit(
|
162
|
-
_sync,
|
163
|
-
wandb_file,
|
164
|
-
run_id=run_id,
|
165
|
-
project=project,
|
166
|
-
entity=entity,
|
167
|
-
skip_console=skip_console,
|
168
|
-
append=append,
|
169
|
-
mark_synced=mark_synced,
|
170
|
-
)
|
171
|
-
futures.append(future)
|
172
|
-
|
173
|
-
# Wait for tasks to complete
|
174
|
-
for _ in concurrent.futures.as_completed(futures):
|
175
|
-
pass
|
68
|
+
"""Upload .wandb files specified by PATHS.
|
69
|
+
|
70
|
+
PATHS can include .wandb files, run directories containing .wandb files,
|
71
|
+
and "wandb" directories containing run directories.
|
72
|
+
|
73
|
+
For example, to sync all runs in a directory:
|
74
|
+
|
75
|
+
wandb beta sync ./wandb
|
76
|
+
|
77
|
+
To sync a specific run:
|
78
|
+
|
79
|
+
wandb beta sync ./wandb/run-20250813_124246-n67z9ude
|
80
|
+
|
81
|
+
Or equivalently:
|
82
|
+
|
83
|
+
wandb beta sync ./wandb/run-20250813_124246-n67z9ude/run-n67z9ude.wandb
|
84
|
+
"""
|
85
|
+
from . import beta_sync
|
86
|
+
|
87
|
+
beta_sync.sync(
|
88
|
+
[pathlib.Path(path) for path in paths],
|
89
|
+
dry_run=dry_run,
|
90
|
+
skip_synced=skip_synced,
|
91
|
+
verbose=verbose,
|
92
|
+
parallelism=n,
|
93
|
+
)
|