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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. wandb/__init__.py +5 -1
  2. wandb/__init__.pyi +15 -8
  3. wandb/_pydantic/__init__.py +30 -0
  4. wandb/_pydantic/base.py +148 -0
  5. wandb/_pydantic/utils.py +66 -0
  6. wandb/_pydantic/v1_compat.py +284 -0
  7. wandb/apis/paginator.py +82 -38
  8. wandb/apis/public/__init__.py +2 -2
  9. wandb/apis/public/api.py +111 -53
  10. wandb/apis/public/artifacts.py +387 -639
  11. wandb/apis/public/automations.py +69 -0
  12. wandb/apis/public/files.py +2 -2
  13. wandb/apis/public/integrations.py +168 -0
  14. wandb/apis/public/projects.py +32 -2
  15. wandb/apis/public/reports.py +2 -2
  16. wandb/apis/public/runs.py +19 -11
  17. wandb/apis/public/utils.py +107 -1
  18. wandb/automations/__init__.py +81 -0
  19. wandb/automations/_filters/__init__.py +40 -0
  20. wandb/automations/_filters/expressions.py +179 -0
  21. wandb/automations/_filters/operators.py +267 -0
  22. wandb/automations/_filters/run_metrics.py +183 -0
  23. wandb/automations/_generated/__init__.py +184 -0
  24. wandb/automations/_generated/create_filter_trigger.py +21 -0
  25. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  26. wandb/automations/_generated/delete_trigger.py +19 -0
  27. wandb/automations/_generated/enums.py +33 -0
  28. wandb/automations/_generated/fragments.py +343 -0
  29. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  30. wandb/automations/_generated/get_triggers.py +24 -0
  31. wandb/automations/_generated/get_triggers_by_entity.py +24 -0
  32. wandb/automations/_generated/input_types.py +104 -0
  33. wandb/automations/_generated/integrations_by_entity.py +22 -0
  34. wandb/automations/_generated/operations.py +710 -0
  35. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  36. wandb/automations/_generated/update_filter_trigger.py +21 -0
  37. wandb/automations/_utils.py +123 -0
  38. wandb/automations/_validators.py +73 -0
  39. wandb/automations/actions.py +205 -0
  40. wandb/automations/automations.py +109 -0
  41. wandb/automations/events.py +235 -0
  42. wandb/automations/integrations.py +26 -0
  43. wandb/automations/scopes.py +76 -0
  44. wandb/beta/workflows.py +9 -10
  45. wandb/bin/gpu_stats +0 -0
  46. wandb/cli/cli.py +3 -3
  47. wandb/integration/keras/keras.py +2 -1
  48. wandb/integration/langchain/wandb_tracer.py +2 -1
  49. wandb/integration/metaflow/metaflow.py +19 -17
  50. wandb/integration/sacred/__init__.py +1 -1
  51. wandb/jupyter.py +155 -133
  52. wandb/old/summary.py +0 -2
  53. wandb/proto/v3/wandb_internal_pb2.py +297 -292
  54. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  55. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  56. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  57. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  58. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  59. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  60. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  61. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  62. wandb/proto/v6/wandb_base_pb2.py +41 -0
  63. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  64. wandb/proto/v6/wandb_server_pb2.py +78 -0
  65. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  66. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  67. wandb/proto/wandb_base_pb2.py +2 -0
  68. wandb/proto/wandb_deprecated.py +10 -0
  69. wandb/proto/wandb_internal_pb2.py +3 -1
  70. wandb/proto/wandb_server_pb2.py +2 -0
  71. wandb/proto/wandb_settings_pb2.py +2 -0
  72. wandb/proto/wandb_telemetry_pb2.py +2 -0
  73. wandb/sdk/artifacts/_generated/__init__.py +248 -0
  74. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  75. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  76. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  77. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  78. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  79. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  80. wandb/sdk/artifacts/_generated/enums.py +17 -0
  81. wandb/sdk/artifacts/_generated/fragments.py +186 -0
  82. wandb/sdk/artifacts/_generated/input_types.py +16 -0
  83. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  84. wandb/sdk/artifacts/_generated/operations.py +510 -0
  85. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  86. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  87. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  88. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  89. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  90. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  91. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  92. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  93. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  94. wandb/sdk/artifacts/_graphql_fragments.py +56 -81
  95. wandb/sdk/artifacts/_validators.py +1 -0
  96. wandb/sdk/artifacts/artifact.py +110 -49
  97. wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
  98. wandb/sdk/artifacts/artifact_saver.py +16 -2
  99. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  100. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
  101. wandb/sdk/data_types/audio.py +1 -3
  102. wandb/sdk/data_types/base_types/media.py +13 -7
  103. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  104. wandb/sdk/data_types/html.py +36 -9
  105. wandb/sdk/data_types/image.py +56 -37
  106. wandb/sdk/data_types/molecule.py +1 -5
  107. wandb/sdk/data_types/object_3d.py +2 -1
  108. wandb/sdk/data_types/saved_model.py +7 -9
  109. wandb/sdk/data_types/table.py +5 -0
  110. wandb/sdk/data_types/trace_tree.py +2 -0
  111. wandb/sdk/data_types/utils.py +1 -1
  112. wandb/sdk/data_types/video.py +15 -30
  113. wandb/sdk/interface/interface.py +2 -0
  114. wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
  115. wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
  116. wandb/sdk/internal/internal_api.py +138 -47
  117. wandb/sdk/internal/profiler.py +6 -5
  118. wandb/sdk/internal/run.py +13 -6
  119. wandb/sdk/internal/sender.py +2 -0
  120. wandb/sdk/internal/sender_config.py +8 -11
  121. wandb/sdk/internal/settings_static.py +24 -2
  122. wandb/sdk/lib/apikey.py +40 -20
  123. wandb/sdk/lib/asyncio_compat.py +1 -1
  124. wandb/sdk/lib/deprecate.py +13 -22
  125. wandb/sdk/lib/disabled.py +2 -1
  126. wandb/sdk/lib/printer.py +37 -8
  127. wandb/sdk/lib/printer_asyncio.py +46 -0
  128. wandb/sdk/lib/redirect.py +10 -5
  129. wandb/sdk/lib/run_moment.py +4 -6
  130. wandb/sdk/lib/wb_logging.py +161 -0
  131. wandb/sdk/service/server_sock.py +19 -14
  132. wandb/sdk/service/service.py +9 -7
  133. wandb/sdk/service/streams.py +5 -0
  134. wandb/sdk/verify/verify.py +6 -3
  135. wandb/sdk/wandb_config.py +44 -43
  136. wandb/sdk/wandb_init.py +323 -141
  137. wandb/sdk/wandb_login.py +13 -4
  138. wandb/sdk/wandb_metadata.py +107 -91
  139. wandb/sdk/wandb_run.py +529 -325
  140. wandb/sdk/wandb_settings.py +422 -202
  141. wandb/sdk/wandb_setup.py +52 -1
  142. wandb/util.py +29 -29
  143. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/METADATA +7 -7
  144. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/RECORD +150 -93
  145. wandb/_globals.py +0 -19
  146. wandb/apis/public/_generated/base.py +0 -128
  147. wandb/apis/public/_generated/typing_compat.py +0 -14
  148. /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
  149. /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
  150. /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
  151. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
  152. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
  153. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
@@ -1,42 +1,33 @@
1
- __all__ = ["deprecate", "Deprecated"]
1
+ from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Optional, Tuple
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  import wandb
6
- from wandb.proto.wandb_deprecated import DEPRECATED_FEATURES, Deprecated
7
- from wandb.proto.wandb_telemetry_pb2 import Deprecated as TelemetryDeprecated
6
+ from wandb.proto.wandb_deprecated import DEPRECATED_FEATURES
7
+ from wandb.sdk.lib import telemetry
8
8
 
9
- # avoid cycle, use string type reference
9
+ # Necessary to break import cycle.
10
10
  if TYPE_CHECKING:
11
- from .. import wandb_run
12
-
13
-
14
- deprecated_field_names: Tuple[str, ...] = tuple(
15
- str(v) for k, v in Deprecated.__dict__.items() if not k.startswith("_")
16
- )
11
+ from wandb import wandb_run
17
12
 
18
13
 
19
14
  def deprecate(
20
15
  field_name: DEPRECATED_FEATURES,
21
16
  warning_message: str,
22
- run: Optional["wandb_run.Run"] = None,
17
+ run: wandb_run.Run | None = None,
23
18
  ) -> None:
24
19
  """Warn the user that a feature has been deprecated.
25
20
 
26
- Also stores the information about the event in telemetry.
21
+ If a run is provided, the given field on its telemetry is updated.
22
+ Otherwise, the global run is used.
27
23
 
28
24
  Args:
29
- field_name: The name of the feature that has been deprecated.
30
- Defined in wandb/proto/wandb_telemetry.proto::Deprecated
25
+ field_name: The field on the Deprecated proto for this deprecation.
31
26
  warning_message: The message to display to the user.
32
- run: The run to whose telemetry the event will be added.
27
+ run: The run whose telemetry to update.
33
28
  """
34
- known_fields = TelemetryDeprecated.DESCRIPTOR.fields_by_name.keys()
35
- if field_name not in known_fields:
36
- raise ValueError(
37
- f"Unknown field name: {field_name}. Known fields: {known_fields}"
38
- )
39
29
  _run = run or wandb.run
40
- with wandb.wandb_lib.telemetry.context(run=_run) as tel: # type: ignore[attr-defined]
30
+ with telemetry.context(run=_run) as tel:
41
31
  setattr(tel.deprecated, field_name, True)
32
+
42
33
  wandb.termwarn(warning_message, repeat=False)
wandb/sdk/lib/disabled.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import Any
2
2
 
3
+ from wandb.proto.wandb_deprecated import Deprecated
3
4
  from wandb.sdk.lib import deprecate
4
5
 
5
6
 
@@ -23,7 +24,7 @@ class RunDisabled:
23
24
 
24
25
  def __getattr__(self, name: str) -> Any:
25
26
  deprecate.deprecate(
26
- field_name=deprecate.Deprecated.run_disabled,
27
+ field_name=Deprecated.run_disabled,
27
28
  warning_message="RunDisabled is deprecated and is a no-op. "
28
29
  '`wandb.init(mode="disabled")` now returns and instance of `wandb.sdk.wandb_run.Run`.',
29
30
  )
wandb/sdk/lib/printer.py CHANGED
@@ -20,6 +20,7 @@ import click
20
20
 
21
21
  import wandb
22
22
  from wandb.errors import term
23
+ from wandb.sdk import wandb_setup
23
24
 
24
25
  from . import ipython, sparkline
25
26
 
@@ -98,12 +99,21 @@ _JUPYTER_PANEL_STYLES = """
98
99
  """
99
100
 
100
101
 
101
- def new_printer() -> Printer:
102
- """Returns a new printer based on the environment we're in."""
102
+ def new_printer(settings: wandb.Settings | None = None) -> Printer:
103
+ """Returns a printer appropriate for the environment we're in.
104
+
105
+ Args:
106
+ settings: The settings of a run. If not provided and `wandb.setup()`
107
+ has been called, then global settings are used. Otherwise,
108
+ settings (such as silent mode) are ignored.
109
+ """
110
+ if not settings and (singleton := wandb_setup.singleton()):
111
+ settings = singleton.settings
112
+
103
113
  if ipython.in_jupyter():
104
- return _PrinterJupyter()
114
+ return _PrinterJupyter(settings=settings)
105
115
  else:
106
- return _PrinterTerm()
116
+ return _PrinterTerm(settings=settings)
107
117
 
108
118
 
109
119
  class Printer(abc.ABC):
@@ -281,13 +291,18 @@ class DynamicText(abc.ABC):
281
291
 
282
292
 
283
293
  class _PrinterTerm(Printer):
284
- def __init__(self) -> None:
294
+ def __init__(self, *, settings: wandb.Settings | None) -> None:
285
295
  super().__init__()
296
+ self._settings = settings
286
297
  self._progress = itertools.cycle(["-", "\\", "|", "/"])
287
298
 
288
299
  @override
289
300
  @contextlib.contextmanager
290
301
  def dynamic_text(self) -> Iterator[DynamicText | None]:
302
+ if self._settings and self._settings.silent:
303
+ yield None
304
+ return
305
+
291
306
  with term.dynamic_text() as handle:
292
307
  if not handle:
293
308
  yield None
@@ -301,6 +316,9 @@ class _PrinterTerm(Printer):
301
316
  *,
302
317
  level: str | int | None = None,
303
318
  ) -> None:
319
+ if self._settings and self._settings.silent:
320
+ return
321
+
304
322
  text = "\n".join(text) if isinstance(text, (list, tuple)) else text
305
323
  self._display_fn_mapping(level)(text)
306
324
 
@@ -323,10 +341,16 @@ class _PrinterTerm(Printer):
323
341
 
324
342
  @override
325
343
  def progress_update(self, text: str, percent_done: float | None = None) -> None:
344
+ if self._settings and self._settings.silent:
345
+ return
346
+
326
347
  wandb.termlog(f"{next(self._progress)} {text}", newline=False)
327
348
 
328
349
  @override
329
350
  def progress_close(self, text: str | None = None) -> None:
351
+ if self._settings and self._settings.silent:
352
+ return
353
+
330
354
  text = text or " " * 79
331
355
  wandb.termlog(text)
332
356
 
@@ -422,8 +446,9 @@ class _DynamicTermText(DynamicText):
422
446
 
423
447
 
424
448
  class _PrinterJupyter(Printer):
425
- def __init__(self) -> None:
449
+ def __init__(self, *, settings: wandb.Settings | None) -> None:
426
450
  super().__init__()
451
+ self._settings = settings
427
452
  self._progress = ipython.jupyter_progress_bar()
428
453
 
429
454
  from IPython import display
@@ -433,6 +458,10 @@ class _PrinterJupyter(Printer):
433
458
  @override
434
459
  @contextlib.contextmanager
435
460
  def dynamic_text(self) -> Iterator[DynamicText | None]:
461
+ if self._settings and self._settings.silent:
462
+ yield None
463
+ return
464
+
436
465
  handle = self._ipython_display.display(
437
466
  self._ipython_display.HTML(""),
438
467
  display_id=True,
@@ -452,7 +481,7 @@ class _PrinterJupyter(Printer):
452
481
  *,
453
482
  level: str | int | None = None,
454
483
  ) -> None:
455
- if wandb.run and wandb.run._settings.silent:
484
+ if self._settings and self._settings.silent:
456
485
  return
457
486
 
458
487
  text = "<br>".join(text) if isinstance(text, (list, tuple)) else text
@@ -507,7 +536,7 @@ class _PrinterJupyter(Printer):
507
536
  text: str,
508
537
  percent_done: float | None = None,
509
538
  ) -> None:
510
- if not self._progress:
539
+ if (self._settings and self._settings.silent) or not self._progress:
511
540
  return
512
541
 
513
542
  if percent_done is None:
@@ -0,0 +1,46 @@
1
+ import asyncio
2
+ from typing import Callable, TypeVar
3
+
4
+ from wandb.sdk.lib import asyncio_compat, printer
5
+
6
+ _T = TypeVar("_T")
7
+
8
+
9
+ def run_async_with_spinner(
10
+ spinner_printer: printer.Printer,
11
+ text: str,
12
+ func: Callable[[], _T],
13
+ ) -> _T:
14
+ """Run a slow function while displaying a loading icon.
15
+
16
+ Args:
17
+ spinner_printer: The printer to use to display text.
18
+ text: The text to display next to the spinner while the function runs.
19
+ func: The function to run.
20
+
21
+ Returns:
22
+ The result of func.
23
+ """
24
+
25
+ async def _loop_run_with_spinner() -> _T:
26
+ func_running = asyncio.Event()
27
+
28
+ async def update_spinner() -> None:
29
+ tick = 0
30
+ with spinner_printer.dynamic_text() as text_area:
31
+ if text_area:
32
+ while not func_running.is_set():
33
+ spinner = spinner_printer.loading_symbol(tick)
34
+ text_area.set_text(f"{spinner} {text}")
35
+ tick += 1
36
+ await asyncio.sleep(0.1)
37
+ else:
38
+ spinner_printer.display(text)
39
+
40
+ async with asyncio_compat.open_task_group() as group:
41
+ group.start_soon(update_spinner())
42
+ res = await asyncio.get_running_loop().run_in_executor(None, func)
43
+ func_running.set()
44
+ return res
45
+
46
+ return asyncio_compat.run(_loop_run_with_spinner)
wandb/sdk/lib/redirect.py CHANGED
@@ -534,12 +534,15 @@ class StreamWrapper(RedirectBase):
534
534
  self,
535
535
  src: Literal["stdout", "stderr"],
536
536
  cbs: Iterable[Callable[[str], None]] = (),
537
+ *,
538
+ flush_periodically: bool,
537
539
  ) -> None:
538
540
  super().__init__(src=src, cbs=cbs)
539
541
  self._uninstall: Callable[[], None] | None = None
540
542
  self._emulator = TerminalEmulator()
541
543
  self._queue: queue.Queue[str] = queue.Queue()
542
544
  self._stopped = threading.Event()
545
+ self._flush_periodically = flush_periodically
543
546
 
544
547
  def _emulator_write(self) -> None:
545
548
  while True:
@@ -600,7 +603,7 @@ class StreamWrapper(RedirectBase):
600
603
  self._emulator_write_thread.daemon = True
601
604
  self._emulator_write_thread.start()
602
605
 
603
- if not wandb.run or wandb.run._settings.mode == "online":
606
+ if self._flush_periodically:
604
607
  self._callback_thread = threading.Thread(target=self._callback)
605
608
  self._callback_thread.daemon = True
606
609
  self._callback_thread.start()
@@ -732,10 +735,11 @@ _redirects: dict[str, Redirect | None] = {"stdout": None, "stderr": None}
732
735
  class Redirect(RedirectBase):
733
736
  """Redirect low level file descriptors."""
734
737
 
735
- def __init__(self, src, cbs=()):
738
+ def __init__(self, src, cbs=(), *, flush_periodically: bool):
736
739
  super().__init__(src=src, cbs=cbs)
737
740
  self._installed = False
738
741
  self._emulator = TerminalEmulator()
742
+ self._flush_periodically = flush_periodically
739
743
 
740
744
  def _pipe(self):
741
745
  if pty:
@@ -767,7 +771,7 @@ class Redirect(RedirectBase):
767
771
  self._emulator_write_thread = threading.Thread(target=self._emulator_write)
768
772
  self._emulator_write_thread.daemon = True
769
773
  self._emulator_write_thread.start()
770
- if not wandb.run or wandb.run._settings.mode == "online":
774
+ if self._flush_periodically:
771
775
  self._callback_thread = threading.Thread(target=self._callback)
772
776
  self._callback_thread.daemon = True
773
777
  self._callback_thread.start()
@@ -776,8 +780,9 @@ class Redirect(RedirectBase):
776
780
  if not self._installed:
777
781
  return
778
782
  self._installed = False
779
- # If the user printed a very long string (millions of chars) right before wandb.finish(),
780
- # it will take a while for it to reach pipe relay. 1 second is enough time for ~5 million chars.
783
+ # If the user printed a very long string (millions of chars) right
784
+ # before run.finish(), it will take a while for it to reach pipe relay.
785
+ # 1 second is enough time for ~5 million chars.
781
786
  time.sleep(1)
782
787
  self._stopped.set()
783
788
  os.dup2(self._orig_src_fd, self.src_fd)
@@ -1,11 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Literal, cast
4
+ from typing import Union
5
5
  from urllib import parse
6
6
 
7
- _STEP = Literal["_step"]
8
-
9
7
 
10
8
  @dataclass
11
9
  class RunMoment:
@@ -19,10 +17,10 @@ class RunMoment:
19
17
  run: str
20
18
  """run ID"""
21
19
 
22
- value: int | float
20
+ value: Union[int, float]
23
21
  """Value of the metric."""
24
22
 
25
- metric: _STEP = "_step"
23
+ metric: str = "_step"
26
24
  """Metric to use to determine the moment in the run.
27
25
 
28
26
  Currently, only the metric '_step' is supported.
@@ -81,4 +79,4 @@ class RunMoment:
81
79
  except ValueError as e:
82
80
  raise parse_err from e
83
81
 
84
- return cls(run=run, metric=cast(_STEP, metric), value=num_value)
82
+ return cls(run=run, metric=metric, value=num_value)
@@ -0,0 +1,161 @@
1
+ """Logging configuration for the "wandb" logger.
2
+
3
+ Most log statements in wandb are made in the context of a run and should be
4
+ redirected to that run's log file (usually named 'debug.log'). This module
5
+ provides a context manager to temporarily set the current run ID and registers
6
+ a global handler for the 'wandb' logger that sends log statements to the right
7
+ place.
8
+
9
+ All functions in this module are threadsafe.
10
+
11
+ NOTE: The pytest caplog fixture will fail to capture logs from the wandb logger
12
+ because they are not propagated to the root logger.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import contextlib
18
+ import contextvars
19
+ import logging
20
+ import pathlib
21
+ from typing import Iterator
22
+
23
+
24
+ class _NotRunSpecific:
25
+ """Sentinel for `not_run_specific()`."""
26
+
27
+
28
+ _NOT_RUN_SPECIFIC = _NotRunSpecific()
29
+
30
+
31
+ _run_id: contextvars.ContextVar[str | _NotRunSpecific | None] = contextvars.ContextVar(
32
+ "_run_id",
33
+ default=None,
34
+ )
35
+
36
+ _logger = logging.getLogger("wandb")
37
+
38
+
39
+ def configure_wandb_logger() -> None:
40
+ """Configures the global 'wandb' logger.
41
+
42
+ The wandb logger is not intended to be customized by users. Instead, it is
43
+ used as a mechanism to redirect log messages into wandb run-specific log
44
+ files.
45
+
46
+ This function is idempotent: calling it multiple times has the same effect.
47
+ """
48
+ # Send all DEBUG and above messages to registered handlers.
49
+ #
50
+ # Per-run handlers can set different levels.
51
+ _logger.setLevel(logging.DEBUG)
52
+
53
+ # Do not propagate wandb logs to the root logger, which the user may have
54
+ # configured to point elsewhere. All wandb log messages should go to a run's
55
+ # log file.
56
+ _logger.propagate = False
57
+
58
+ # If no handlers are configured for the 'wandb' logger, don't activate the
59
+ # "lastResort" handler which sends messages to stderr with a level of
60
+ # WARNING by default.
61
+ #
62
+ # This occurs in wandb code that runs outside the context of a Run and
63
+ # not as part of the CLI.
64
+ #
65
+ # Most such code uses the `termlog` / `termwarn` / `termerror` methods
66
+ # to communicate with the user. When that code executes while a run is
67
+ # active, its logger messages go to that run's log file.
68
+ if not _logger.handlers:
69
+ _logger.addHandler(logging.NullHandler())
70
+
71
+
72
+ @contextlib.contextmanager
73
+ def log_to_run(run_id: str | None) -> Iterator[None]:
74
+ """Direct all wandb log messages to the given run.
75
+
76
+ Args:
77
+ id: The current run ID, or None if actions in the context manager are
78
+ not associated to a specific run. In the latter case, log messages
79
+ will go to all runs.
80
+
81
+ Usage:
82
+
83
+ with wb_logging.run_id(...):
84
+ ... # Log messages here go to the specified run's logger.
85
+ """
86
+ token = _run_id.set(run_id)
87
+ try:
88
+ yield
89
+ finally:
90
+ _run_id.reset(token)
91
+
92
+
93
+ @contextlib.contextmanager
94
+ def log_to_all_runs() -> Iterator[None]:
95
+ """Direct wandb log messages to all runs.
96
+
97
+ Unlike `log_to_run(None)`, this indicates an intentional choice.
98
+ This is often convenient to use as a decorator:
99
+
100
+ @wb_logging.log_to_all_runs()
101
+ def my_func():
102
+ ... # Log messages here go to the specified run's logger.
103
+ """
104
+ token = _run_id.set(_NOT_RUN_SPECIFIC)
105
+ try:
106
+ yield
107
+ finally:
108
+ _run_id.reset(token)
109
+
110
+
111
+ def add_file_handler(run_id: str, filepath: pathlib.Path) -> logging.Handler:
112
+ """Direct log messages for a run to a file.
113
+
114
+ Args:
115
+ run_id: The run for which to create a log file.
116
+ filepath: The file to write log messages to.
117
+
118
+ Returns:
119
+ The added handler which can then be configured further or removed
120
+ from the 'wandb' logger directly.
121
+
122
+ The default logging level is INFO.
123
+ """
124
+ handler = logging.FileHandler(filepath)
125
+ handler.setLevel(logging.INFO)
126
+ handler.addFilter(_RunIDFilter(run_id))
127
+ handler.setFormatter(
128
+ logging.Formatter(
129
+ "%(asctime)s %(levelname)-7s %(threadName)-10s:%(process)d"
130
+ " [%(filename)s:%(funcName)s():%(lineno)s]%(run_id_tag)s"
131
+ " %(message)s"
132
+ )
133
+ )
134
+
135
+ _logger.addHandler(handler)
136
+ return handler
137
+
138
+
139
+ class _RunIDFilter(logging.Filter):
140
+ """Filters out messages logged for a different run."""
141
+
142
+ def __init__(self, run_id: str) -> None:
143
+ """Create a _RunIDFilter.
144
+
145
+ Args:
146
+ run_id: Allows messages when the run ID is this or None.
147
+ """
148
+ self._run_id = run_id
149
+
150
+ def filter(self, record: logging.LogRecord) -> bool:
151
+ run_id = _run_id.get()
152
+
153
+ if run_id is None:
154
+ record.run_id_tag = " [no run ID]"
155
+ return True
156
+ elif isinstance(run_id, _NotRunSpecific):
157
+ record.run_id_tag = " [all runs]"
158
+ return True
159
+ else:
160
+ record.run_id_tag = ""
161
+ return run_id == self._run_id
@@ -5,6 +5,7 @@ import time
5
5
  from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
6
6
 
7
7
  import wandb
8
+ from wandb.proto import wandb_internal_pb2 as pb
8
9
  from wandb.proto import wandb_server_pb2 as spb
9
10
  from wandb.sdk.internal.settings_static import SettingsStatic
10
11
 
@@ -156,27 +157,31 @@ class SockServerReadThread(threading.Thread):
156
157
  inform_attach_response=inform_attach_response,
157
158
  )
158
159
  self._sock_client.send_server_response(response)
159
- iface = self._mux.get_stream(stream_id).interface
160
-
161
- assert iface
162
160
 
163
161
  def server_record_communicate(self, sreq: "spb.ServerRequest") -> None:
164
- record = sreq.record_communicate
165
- # encode relay information so the right socket picks up the data
166
- record.control.relay_id = self._sock_client._sockid
167
- stream_id = record._info.stream_id
168
- iface = self._mux.get_stream(stream_id).interface
169
- assert iface.record_q
170
- iface.record_q.put(record)
162
+ self._put_record(sreq.record_communicate)
171
163
 
172
164
  def server_record_publish(self, sreq: "spb.ServerRequest") -> None:
173
- record = sreq.record_publish
165
+ self._put_record(sreq.record_publish)
166
+
167
+ def _put_record(self, record: "pb.Record") -> None:
174
168
  # encode relay information so the right socket picks up the data
175
169
  record.control.relay_id = self._sock_client._sockid
176
170
  stream_id = record._info.stream_id
177
- iface = self._mux.get_stream(stream_id).interface
178
- assert iface.record_q
179
- iface.record_q.put(record)
171
+
172
+ try:
173
+ iface = self._mux.get_stream(stream_id).interface
174
+
175
+ except KeyError:
176
+ # We should log the error but cannot because it may print to console
177
+ # due to how logging is set up. This error usually happens if
178
+ # a record is sent when no run is active, but during this time the
179
+ # logger prints to the console.
180
+ pass
181
+
182
+ else:
183
+ assert iface.record_q
184
+ iface.record_q.put(record)
180
185
 
181
186
  def server_inform_finish(self, sreq: "spb.ServerRequest") -> None:
182
187
  request = sreq.inform_finish
@@ -14,10 +14,10 @@ import tempfile
14
14
  import time
15
15
  from typing import TYPE_CHECKING, Any, Dict, Optional
16
16
 
17
- from wandb import _sentry, termlog
17
+ from wandb import _sentry
18
18
  from wandb.env import core_debug, error_reporting_enabled, is_require_legacy_service
19
19
  from wandb.errors import Error, WandbCoreNotAvailableError
20
- from wandb.errors.links import url_registry
20
+ from wandb.errors.term import termlog, termwarn
21
21
  from wandb.util import get_core_path, get_module
22
22
 
23
23
  from . import _startup_debug, port_file
@@ -163,13 +163,15 @@ class _Service:
163
163
  service_args.extend(["--log-level", "-4"])
164
164
 
165
165
  exec_cmd_list = []
166
- termlog(
167
- "Using wandb-core as the SDK backend. Please refer to "
168
- f"{url_registry.url('wandb-core')} for more information.",
169
- repeat=False,
170
- )
171
166
  else:
172
167
  service_args.extend(["wandb", "service", "--debug"])
168
+ termwarn(
169
+ "Using legacy-service, which is deprecated. If this is"
170
+ " unintentional, you can fix it by ensuring you do not call"
171
+ " `wandb.require('legacy-service')` and do not set the"
172
+ " WANDB_X_REQUIRE_LEGACY_SERVICE environment"
173
+ " variable."
174
+ )
173
175
 
174
176
  service_args += [
175
177
  "--port-filename",
@@ -198,6 +198,11 @@ class StreamMux:
198
198
  return stream_id in self._streams
199
199
 
200
200
  def get_stream(self, stream_id: str) -> StreamRecord:
201
+ """Returns the StreamRecord for the ID.
202
+
203
+ Raises:
204
+ KeyError: If a corresponding StreamRecord does not exist.
205
+ """
201
206
  with self._streams_lock:
202
207
  stream = self._streams[stream_id]
203
208
  return stream
@@ -132,7 +132,10 @@ def check_run(api: Api) -> bool:
132
132
  f.close()
133
133
 
134
134
  with wandb.init(
135
- id=nice_id("check_run"), reinit=True, config=config, project=PROJECT_NAME
135
+ id=nice_id("check_run"),
136
+ reinit=True,
137
+ config=config,
138
+ project=PROJECT_NAME,
136
139
  ) as run:
137
140
  run_id = run.id
138
141
  entity = run.entity
@@ -155,7 +158,7 @@ def check_run(api: Api) -> bool:
155
158
  "Failed to log to media. Contact W&B for support."
156
159
  )
157
160
 
158
- wandb.save(filepath)
161
+ run.save(filepath)
159
162
  public_api = wandb.Api()
160
163
  prev_run = public_api.run(f"{entity}/{PROJECT_NAME}/{run_id}")
161
164
  # raise Exception(prev_run.__dict__)
@@ -379,7 +382,7 @@ def check_graphql_put(api: Api, host: str) -> Tuple[bool, Optional[str]]:
379
382
  project=PROJECT_NAME,
380
383
  config={"test": "put to graphql"},
381
384
  ) as run:
382
- wandb.save(gql_fp)
385
+ run.save(gql_fp)
383
386
  public_api = wandb.Api()
384
387
  prev_run = public_api.run(f"{run.entity}/{PROJECT_NAME}/{run.id}")
385
388
  if prev_run is None: