wandb 0.15.9__py3-none-any.whl → 0.15.11__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. wandb/__init__.py +5 -1
  2. wandb/apis/public.py +137 -17
  3. wandb/apis/reports/_panels.py +1 -1
  4. wandb/apis/reports/blocks.py +1 -0
  5. wandb/apis/reports/report.py +27 -5
  6. wandb/cli/cli.py +52 -41
  7. wandb/docker/__init__.py +17 -0
  8. wandb/docker/auth.py +1 -1
  9. wandb/env.py +24 -4
  10. wandb/filesync/step_checksum.py +3 -3
  11. wandb/integration/openai/openai.py +3 -0
  12. wandb/integration/ultralytics/__init__.py +9 -0
  13. wandb/integration/ultralytics/bbox_utils.py +196 -0
  14. wandb/integration/ultralytics/callback.py +458 -0
  15. wandb/integration/ultralytics/classification_utils.py +66 -0
  16. wandb/integration/ultralytics/mask_utils.py +141 -0
  17. wandb/integration/ultralytics/pose_utils.py +92 -0
  18. wandb/integration/xgboost/xgboost.py +3 -3
  19. wandb/integration/yolov8/__init__.py +0 -7
  20. wandb/integration/yolov8/yolov8.py +22 -3
  21. wandb/old/settings.py +7 -0
  22. wandb/plot/line_series.py +0 -1
  23. wandb/proto/v3/wandb_internal_pb2.py +353 -300
  24. wandb/proto/v3/wandb_server_pb2.py +37 -41
  25. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  26. wandb/proto/v3/wandb_telemetry_pb2.py +16 -16
  27. wandb/proto/v4/wandb_internal_pb2.py +272 -260
  28. wandb/proto/v4/wandb_server_pb2.py +37 -40
  29. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  30. wandb/proto/v4/wandb_telemetry_pb2.py +16 -16
  31. wandb/proto/wandb_internal_codegen.py +7 -31
  32. wandb/sdk/artifacts/artifact.py +321 -189
  33. wandb/sdk/artifacts/artifact_cache.py +14 -0
  34. wandb/sdk/artifacts/artifact_manifest.py +5 -4
  35. wandb/sdk/artifacts/artifact_manifest_entry.py +37 -9
  36. wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +1 -9
  37. wandb/sdk/artifacts/artifact_saver.py +13 -50
  38. wandb/sdk/artifacts/artifact_ttl.py +6 -0
  39. wandb/sdk/artifacts/artifacts_cache.py +119 -93
  40. wandb/sdk/artifacts/staging.py +25 -0
  41. wandb/sdk/artifacts/storage_handlers/s3_handler.py +12 -7
  42. wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +2 -3
  43. wandb/sdk/artifacts/storage_policies/__init__.py +4 -0
  44. wandb/sdk/artifacts/storage_policies/register.py +1 -0
  45. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +4 -3
  46. wandb/sdk/artifacts/storage_policy.py +4 -2
  47. wandb/sdk/backend/backend.py +0 -16
  48. wandb/sdk/data_types/image.py +3 -1
  49. wandb/sdk/integration_utils/auto_logging.py +38 -13
  50. wandb/sdk/interface/interface.py +16 -135
  51. wandb/sdk/interface/interface_shared.py +9 -147
  52. wandb/sdk/interface/interface_sock.py +0 -26
  53. wandb/sdk/internal/file_pusher.py +20 -3
  54. wandb/sdk/internal/file_stream.py +3 -1
  55. wandb/sdk/internal/handler.py +53 -70
  56. wandb/sdk/internal/internal_api.py +220 -130
  57. wandb/sdk/internal/job_builder.py +41 -37
  58. wandb/sdk/internal/sender.py +7 -25
  59. wandb/sdk/internal/system/assets/disk.py +144 -11
  60. wandb/sdk/internal/system/system_info.py +6 -2
  61. wandb/sdk/launch/__init__.py +5 -0
  62. wandb/sdk/launch/{launch.py → _launch.py} +53 -54
  63. wandb/sdk/launch/{launch_add.py → _launch_add.py} +34 -31
  64. wandb/sdk/launch/_project_spec.py +13 -2
  65. wandb/sdk/launch/agent/agent.py +103 -59
  66. wandb/sdk/launch/agent/run_queue_item_file_saver.py +6 -4
  67. wandb/sdk/launch/builder/build.py +19 -1
  68. wandb/sdk/launch/builder/docker_builder.py +5 -1
  69. wandb/sdk/launch/builder/kaniko_builder.py +5 -1
  70. wandb/sdk/launch/create_job.py +20 -5
  71. wandb/sdk/launch/loader.py +14 -5
  72. wandb/sdk/launch/runner/abstract.py +0 -2
  73. wandb/sdk/launch/runner/kubernetes_monitor.py +329 -0
  74. wandb/sdk/launch/runner/kubernetes_runner.py +66 -209
  75. wandb/sdk/launch/runner/local_container.py +5 -2
  76. wandb/sdk/launch/runner/local_process.py +4 -1
  77. wandb/sdk/launch/sweeps/scheduler.py +43 -25
  78. wandb/sdk/launch/sweeps/utils.py +5 -3
  79. wandb/sdk/launch/utils.py +3 -1
  80. wandb/sdk/lib/_settings_toposort_generate.py +3 -9
  81. wandb/sdk/lib/_settings_toposort_generated.py +27 -3
  82. wandb/sdk/lib/_wburls_generated.py +1 -0
  83. wandb/sdk/lib/filenames.py +27 -6
  84. wandb/sdk/lib/filesystem.py +181 -7
  85. wandb/sdk/lib/fsm.py +5 -3
  86. wandb/sdk/lib/gql_request.py +3 -0
  87. wandb/sdk/lib/ipython.py +7 -0
  88. wandb/sdk/lib/wburls.py +1 -0
  89. wandb/sdk/service/port_file.py +2 -15
  90. wandb/sdk/service/server.py +7 -55
  91. wandb/sdk/service/service.py +56 -26
  92. wandb/sdk/service/service_base.py +1 -1
  93. wandb/sdk/service/streams.py +11 -5
  94. wandb/sdk/verify/verify.py +2 -2
  95. wandb/sdk/wandb_init.py +8 -2
  96. wandb/sdk/wandb_manager.py +4 -14
  97. wandb/sdk/wandb_run.py +143 -53
  98. wandb/sdk/wandb_settings.py +148 -35
  99. wandb/testing/relay.py +85 -38
  100. wandb/util.py +87 -4
  101. wandb/wandb_torch.py +24 -38
  102. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/METADATA +48 -23
  103. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/RECORD +107 -103
  104. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/WHEEL +1 -1
  105. wandb/proto/v3/wandb_server_pb2_grpc.py +0 -1422
  106. wandb/proto/v4/wandb_server_pb2_grpc.py +0 -1422
  107. wandb/proto/wandb_server_pb2_grpc.py +0 -8
  108. wandb/sdk/artifacts/storage_policies/s3_bucket_policy.py +0 -61
  109. wandb/sdk/interface/interface_grpc.py +0 -460
  110. wandb/sdk/service/server_grpc.py +0 -444
  111. wandb/sdk/service/service_grpc.py +0 -73
  112. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/LICENSE +0 -0
  113. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/entry_points.txt +0 -0
  114. {wandb-0.15.9.dist-info → wandb-0.15.11.dist-info}/top_level.txt +0 -0
@@ -32,7 +32,7 @@ from typing import (
32
32
  Union,
33
33
  no_type_check,
34
34
  )
35
- from urllib.parse import quote, urlencode, urlparse, urlsplit
35
+ from urllib.parse import quote, unquote, urlencode, urlparse, urlsplit
36
36
 
37
37
  from google.protobuf.wrappers_pb2 import BoolValue, DoubleValue, Int32Value, StringValue
38
38
 
@@ -170,13 +170,18 @@ def _get_program() -> Optional[str]:
170
170
  return None
171
171
 
172
172
 
173
- def _get_program_relpath_from_gitrepo(
174
- program: str, _logger: Optional[_EarlyLogger] = None
173
+ def _get_program_relpath(
174
+ program: str, root: Optional[str] = None, _logger: Optional[_EarlyLogger] = None
175
175
  ) -> Optional[str]:
176
- repo = GitRepo()
177
- root = repo.root
176
+ if not program:
177
+ if _logger is not None:
178
+ _logger.warning("Empty program passed to get_program_relpath")
179
+ return None
180
+
181
+ root = root or os.getcwd()
178
182
  if not root:
179
- root = os.getcwd()
183
+ return None
184
+
180
185
  full_path_to_program = os.path.join(
181
186
  root, os.path.relpath(os.getcwd(), root), program
182
187
  )
@@ -304,12 +309,27 @@ class SettingsData:
304
309
  _disable_setproctitle: bool # Do not use setproctitle on internal process
305
310
  _disable_stats: bool # Do not collect system metrics
306
311
  _disable_viewer: bool # Prevent early viewer query
312
+ _disable_machine_info: bool # Disable automatic machine info collection
307
313
  _except_exit: bool
308
314
  _executable: str
309
315
  _extra_http_headers: Mapping[str, str]
310
- _file_stream_timeout_seconds: float
316
+ # file stream retry client configuration
317
+ _file_stream_retry_max: int # max number of retries
318
+ _file_stream_retry_wait_min_seconds: int # min wait time between retries
319
+ _file_stream_retry_wait_max_seconds: int # max wait time between retries
320
+ _file_stream_timeout_seconds: int # timeout for individual HTTP requests
321
+ # file uploader retry client configuration
322
+ _file_uploader_retry_max: int
323
+ _file_uploader_retry_wait_min_seconds: int
324
+ _file_uploader_retry_wait_max_seconds: int
325
+ _file_uploader_timeout_seconds: int
311
326
  _flow_control_custom: bool
312
327
  _flow_control_disabled: bool
328
+ # graphql retry client configuration
329
+ _graphql_retry_max: int
330
+ _graphql_retry_wait_min_seconds: int
331
+ _graphql_retry_wait_max_seconds: int
332
+ _graphql_timeout_seconds: int
313
333
  _internal_check_process: float
314
334
  _internal_queue_timeout: float
315
335
  _ipython: bool
@@ -328,6 +348,7 @@ class SettingsData:
328
348
  _sync: bool
329
349
  _os: str
330
350
  _platform: str
351
+ _proxies: Mapping[str, str] # dedicated global proxy servers [scheme -> url]
331
352
  _python: str
332
353
  _runqueue_item_id: str
333
354
  _require_nexus: bool
@@ -346,6 +367,7 @@ class SettingsData:
346
367
  # - {"metric regex pattern, including endpoint name as prefix": {"label": "label value regex pattern"}}
347
368
  # - ("metric regex pattern 1", "metric regex pattern 2", ...)
348
369
  _stats_open_metrics_filters: Union[Sequence[str], Mapping[str, Mapping[str, str]]]
370
+ _stats_disk_paths: Sequence[str] # paths to monitor disk usage
349
371
  _tmp_code_dir: str
350
372
  _tracelog: str
351
373
  _unsaved_keys: Sequence[str]
@@ -356,6 +378,7 @@ class SettingsData:
356
378
  azure_account_url_to_access_key: Dict[str, str]
357
379
  base_url: str # The base url for the wandb api
358
380
  code_dir: str
381
+ colab_url: str
359
382
  config_paths: Sequence[str]
360
383
  console: str
361
384
  deployment: str
@@ -394,7 +417,8 @@ class SettingsData:
394
417
  notebook_name: str
395
418
  problem: str
396
419
  program: str
397
- program_relpath: Optional[str]
420
+ program_abspath: str
421
+ program_relpath: str
398
422
  project: str
399
423
  project_url: str
400
424
  quiet: bool
@@ -587,29 +611,65 @@ class Settings(SettingsData):
587
611
  Note that key names must be the same as the class attribute names.
588
612
  """
589
613
  props: Dict[str, Dict[str, Any]] = dict(
614
+ _async_upload_concurrency_limit={
615
+ "preprocessor": int,
616
+ "validator": self._validate__async_upload_concurrency_limit,
617
+ },
590
618
  _aws_lambda={
591
619
  "hook": lambda _: is_aws_lambda(),
592
620
  "auto_hook": True,
593
621
  },
594
- _async_upload_concurrency_limit={
595
- "preprocessor": int,
596
- "validator": self._validate__async_upload_concurrency_limit,
622
+ _colab={
623
+ "hook": lambda _: "google.colab" in sys.modules,
624
+ "auto_hook": True,
625
+ },
626
+ _disable_machine_info={
627
+ "value": False,
628
+ "preprocessor": _str_as_bool,
629
+ },
630
+ _disable_meta={
631
+ "value": False,
632
+ "preprocessor": _str_as_bool,
633
+ "hook": lambda x: self._disable_machine_info or x,
597
634
  },
598
- _disable_meta={"preprocessor": _str_as_bool},
599
635
  _disable_service={
600
636
  "value": False,
601
637
  "preprocessor": _str_as_bool,
602
638
  "is_policy": True,
603
639
  },
604
640
  _disable_setproctitle={"value": False, "preprocessor": _str_as_bool},
605
- _disable_stats={"preprocessor": _str_as_bool},
641
+ _disable_stats={
642
+ "value": False,
643
+ "preprocessor": _str_as_bool,
644
+ "hook": lambda x: self._disable_machine_info or x,
645
+ },
606
646
  _disable_viewer={"preprocessor": _str_as_bool},
607
647
  _extra_http_headers={"preprocessor": _str_as_json},
608
- _network_buffer={"preprocessor": int},
609
- _colab={
610
- "hook": lambda _: "google.colab" in sys.modules,
648
+ # Retry filestream requests for 2 hours before dropping chunk (how do we recover?)
649
+ # retry_count = seconds_in_2_hours / max_retry_time + num_retries_until_max_60_sec
650
+ # = 7200 / 60 + ceil(log2(60/2))
651
+ # = 120 + 5
652
+ _file_stream_retry_max={"value": 125, "preprocessor": int},
653
+ _file_stream_retry_wait_min_seconds={"value": 2, "preprocessor": int},
654
+ _file_stream_retry_wait_max_seconds={"value": 60, "preprocessor": int},
655
+ # A 3 minute timeout for all filestream post requests
656
+ _file_stream_timeout_seconds={"value": 180, "preprocessor": int},
657
+ _file_uploader_retry_max={"value": 10, "preprocessor": int},
658
+ _file_uploader_retry_wait_min_seconds={"value": 2, "preprocessor": int},
659
+ _file_uploader_retry_wait_max_seconds={"value": 60, "preprocessor": int},
660
+ _file_uploader_timeout_seconds={"value": 0, "preprocessor": int},
661
+ _flow_control_disabled={
662
+ "hook": lambda _: self._network_buffer == 0,
663
+ "auto_hook": True,
664
+ },
665
+ _flow_control_custom={
666
+ "hook": lambda _: bool(self._network_buffer),
611
667
  "auto_hook": True,
612
668
  },
669
+ _graphql_retry_max={"value": 10, "preprocessor": int},
670
+ _graphql_retry_wait_min_seconds={"value": 2, "preprocessor": int},
671
+ _graphql_retry_wait_max_seconds={"value": 60, "preprocessor": int},
672
+ _graphql_timeout_seconds={"value": 30.0, "preprocessor": int},
613
673
  _internal_check_process={"value": 8, "preprocessor": float},
614
674
  _internal_queue_timeout={"value": 2, "preprocessor": float},
615
675
  _ipython={
@@ -622,6 +682,7 @@ class Settings(SettingsData):
622
682
  },
623
683
  _kaggle={"hook": lambda _: util._is_likely_kaggle(), "auto_hook": True},
624
684
  _log_level={"value": logging.DEBUG},
685
+ _network_buffer={"preprocessor": int},
625
686
  _noop={"hook": lambda _: self.mode == "disabled", "auto_hook": True},
626
687
  _notebook={
627
688
  "hook": lambda _: self._ipython
@@ -638,20 +699,10 @@ class Settings(SettingsData):
638
699
  ),
639
700
  "auto_hook": True,
640
701
  },
641
- _file_stream_timeout_seconds={
642
- "value": 60,
643
- "preprocessor": float,
644
- },
645
- _flow_control_disabled={
646
- "hook": lambda _: self._network_buffer == 0,
647
- "auto_hook": True,
648
- },
649
- _flow_control_custom={
650
- "hook": lambda _: bool(self._network_buffer),
651
- "auto_hook": True,
652
- },
653
- _sync={"value": False},
654
702
  _platform={"value": util.get_platform_name()},
703
+ _proxies={
704
+ "preprocessor": _str_as_json,
705
+ },
655
706
  _require_nexus={"value": False, "preprocessor": _str_as_bool},
656
707
  _save_requirements={"value": True, "preprocessor": _str_as_bool},
657
708
  _service_wait={
@@ -682,6 +733,11 @@ class Settings(SettingsData):
682
733
  "value": (".*",),
683
734
  "preprocessor": _str_as_json,
684
735
  },
736
+ _stats_disk_paths={
737
+ "value": ("/",),
738
+ "preprocessor": _str_as_json,
739
+ },
740
+ _sync={"value": False},
685
741
  _tmp_code_dir={
686
742
  "value": "code",
687
743
  "hook": lambda x: self._path_convert(self.tmp_dir, x),
@@ -697,6 +753,10 @@ class Settings(SettingsData):
697
753
  "preprocessor": lambda x: str(x).strip().rstrip("/"),
698
754
  "validator": self._validate_base_url,
699
755
  },
756
+ colab_url={
757
+ "hook": lambda _: self._get_colab_url(),
758
+ "auto_hook": True,
759
+ },
700
760
  config_paths={"preprocessor": _str_as_tuple},
701
761
  console={
702
762
  "value": "auto",
@@ -708,10 +768,22 @@ class Settings(SettingsData):
708
768
  "hook": lambda _: "local" if self.is_local else "cloud",
709
769
  "auto_hook": True,
710
770
  },
711
- disable_code={"preprocessor": _str_as_bool},
771
+ disable_code={
772
+ "value": False,
773
+ "preprocessor": _str_as_bool,
774
+ "hook": lambda x: self._disable_machine_info or x,
775
+ },
712
776
  disable_hints={"preprocessor": _str_as_bool},
713
- disable_git={"preprocessor": _str_as_bool},
714
- disable_job_creation={"value": False, "preprocessor": _str_as_bool},
777
+ disable_git={
778
+ "value": False,
779
+ "preprocessor": _str_as_bool,
780
+ "hook": lambda x: self._disable_machine_info or x,
781
+ },
782
+ disable_job_creation={
783
+ "value": False,
784
+ "preprocessor": _str_as_bool,
785
+ "hook": lambda x: self._disable_machine_info or x,
786
+ },
715
787
  disabled={"value": False, "preprocessor": _str_as_bool},
716
788
  files_dir={
717
789
  "value": "files",
@@ -726,7 +798,7 @@ class Settings(SettingsData):
726
798
  "value": tuple(),
727
799
  "preprocessor": lambda x: tuple(x) if not isinstance(x, tuple) else x,
728
800
  },
729
- init_timeout={"value": 60, "preprocessor": lambda x: float(x)},
801
+ init_timeout={"value": 90, "preprocessor": lambda x: float(x)},
730
802
  is_local={
731
803
  "hook": (
732
804
  lambda _: self.base_url != "https://api.wandb.ai"
@@ -764,6 +836,9 @@ class Settings(SettingsData):
764
836
  login_timeout={"preprocessor": lambda x: float(x)},
765
837
  mode={"value": "online", "validator": self._validate_mode},
766
838
  problem={"value": "fatal", "validator": self._validate_problem},
839
+ program={
840
+ "hook": lambda x: self._get_program(x),
841
+ },
767
842
  project={"validator": self._validate_project},
768
843
  project_url={"hook": lambda _: self._project_url(), "auto_hook": True},
769
844
  quiet={"preprocessor": _str_as_bool},
@@ -1134,6 +1209,32 @@ class Settings(SettingsData):
1134
1209
  console = "redirect"
1135
1210
  return console
1136
1211
 
1212
+ def _get_colab_url(self) -> Optional[str]:
1213
+ if not self._colab:
1214
+ return None
1215
+ if self._jupyter_path and self._jupyter_path.startswith("fileId="):
1216
+ unescaped = unquote(self._jupyter_path)
1217
+ return "https://colab.research.google.com/notebook#" + unescaped
1218
+ return None
1219
+
1220
+ def _get_program(self, program: Optional[str]) -> Optional[str]:
1221
+ if program is not None and program != "<python with no main file>":
1222
+ return program
1223
+
1224
+ if not self._jupyter:
1225
+ return program
1226
+
1227
+ if self.notebook_name:
1228
+ return self.notebook_name
1229
+
1230
+ if not self._jupyter_path:
1231
+ return program
1232
+
1233
+ if self._jupyter_path.startswith("fileId="):
1234
+ return self._jupyter_name
1235
+ else:
1236
+ return self._jupyter_path
1237
+
1137
1238
  def _get_url_query_string(self) -> str:
1138
1239
  # TODO(settings) use `wandb_setting` (if self.anonymous != "true":)
1139
1240
  if Api().settings().get("anonymous") != "true":
@@ -1545,6 +1646,9 @@ class Settings(SettingsData):
1545
1646
  "WANDB_NOTES": "run_notes",
1546
1647
  "WANDB_TAGS": "run_tags",
1547
1648
  "WANDB_JOB_TYPE": "run_job_type",
1649
+ "WANDB_HTTP_TIMEOUT": "_graphql_timeout_seconds",
1650
+ "WANDB_FILE_PUSHER_TIMEOUT": "_file_uploader_timeout_seconds",
1651
+ "WANDB_USER_EMAIL": "email",
1548
1652
  }
1549
1653
  env = dict()
1550
1654
  for setting, value in environ.items():
@@ -1665,10 +1769,18 @@ class Settings(SettingsData):
1665
1769
  settings: Dict[str, Union[bool, str, None]] = dict()
1666
1770
  program = self.program or _get_program()
1667
1771
  if program is not None:
1668
- program_relpath = self.program_relpath or _get_program_relpath_from_gitrepo(
1669
- program, _logger=_logger
1772
+ repo = GitRepo()
1773
+ root = repo.root or os.getcwd()
1774
+
1775
+ program_relpath = self.program_relpath or _get_program_relpath(
1776
+ program, repo.root, _logger=_logger
1670
1777
  )
1671
1778
  settings["program_relpath"] = program_relpath
1779
+ program_abspath = os.path.abspath(
1780
+ os.path.join(root, os.path.relpath(os.getcwd(), root), program)
1781
+ )
1782
+ if os.path.exists(program_abspath):
1783
+ settings["program_abspath"] = program_abspath
1672
1784
  else:
1673
1785
  program = "<python with no main file>"
1674
1786
 
@@ -1731,6 +1843,7 @@ class Settings(SettingsData):
1731
1843
  job_type="run_job_type",
1732
1844
  notes="run_notes",
1733
1845
  dir="root_dir",
1846
+ sweep_id="sweep_id",
1734
1847
  )
1735
1848
  init_settings = {
1736
1849
  param_map.get(k, k): v for k, v in init_settings.items() if v is not None
wandb/testing/relay.py CHANGED
@@ -74,6 +74,18 @@ class DeliberateHTTPError(Exception):
74
74
  return f"DeliberateHTTPError({self.message!r}, {self.status_code!r})"
75
75
 
76
76
 
77
+ @dataclasses.dataclass
78
+ class RunAttrs:
79
+ """Simple data class for run attributes."""
80
+
81
+ name: str
82
+ display_name: str
83
+ description: str
84
+ sweep_name: str
85
+ project: Dict[str, Any]
86
+ config: Dict[str, Any]
87
+
88
+
77
89
  class Context:
78
90
  """A container used to store the snooped state/data of a test.
79
91
 
@@ -223,6 +235,20 @@ class Context:
223
235
  run_stats = self.events[mask_run]
224
236
  return run_stats
225
237
 
238
+ def get_run_attrs(self, run_id: str) -> Optional[RunAttrs]:
239
+ run_entry = self._entries.get(run_id)
240
+ if not run_entry:
241
+ return None
242
+
243
+ return RunAttrs(
244
+ name=run_entry["name"],
245
+ display_name=run_entry["displayName"],
246
+ description=run_entry["description"],
247
+ sweep_name=run_entry["sweepName"],
248
+ project=run_entry["project"],
249
+ config=run_entry["config"],
250
+ )
251
+
226
252
  # todo: add getter (by run_id) utilities for other properties
227
253
 
228
254
 
@@ -317,8 +343,8 @@ class QueryResolver:
317
343
  query = "CreateRunFiles" in request_data.get("query", "")
318
344
  if query:
319
345
  run_name = request_data["variables"]["run"]
320
- files = (
321
- response_data.get("data", {}).get("createRunFiles", {}).get("files", {})
346
+ files = ((response_data.get("data") or {}).get("createRunFiles") or {}).get(
347
+ "files", {}
322
348
  )
323
349
  post_processed_data = {
324
350
  "name": run_name,
@@ -526,6 +552,7 @@ class RelayServer:
526
552
  base_url: str,
527
553
  inject: Optional[List[InjectedResponse]] = None,
528
554
  control: Optional[RelayControlProtocol] = None,
555
+ verbose: bool = False,
529
556
  ) -> None:
530
557
  # todo for the future:
531
558
  # - consider switching from Flask to Quart
@@ -581,6 +608,7 @@ class RelayServer:
581
608
 
582
609
  # useful when debugging:
583
610
  # self.after_request_fn = self.app.after_request(self.after_request_fn)
611
+ self.verbose = verbose
584
612
 
585
613
  @staticmethod
586
614
  def handle_http_exception(e):
@@ -632,11 +660,25 @@ class RelayServer:
632
660
  json=request.get_json(),
633
661
  ).prepare()
634
662
 
663
+ if self.verbose:
664
+ print("*****************")
665
+ print("RELAY REQUEST:")
666
+ print(prepared_relayed_request.url)
667
+ print(prepared_relayed_request.method)
668
+ print(prepared_relayed_request.headers)
669
+ print(prepared_relayed_request.body)
670
+ print("*****************")
671
+
635
672
  for injected_response in self.inject:
636
673
  # where are we in the application pattern?
637
674
  should_apply = injected_response.application_pattern.should_apply()
638
675
  # check if an injected response matches the request
639
676
  if injected_response == prepared_relayed_request:
677
+ if self.verbose:
678
+ print("*****************")
679
+ print("INJECTING RESPONSE:")
680
+ print(injected_response.to_dict())
681
+ print("*****************")
640
682
  # rotate the injection pattern
641
683
  injected_response.application_pattern.next()
642
684
  if should_apply:
@@ -693,19 +735,21 @@ class RelayServer:
693
735
  request = flask.request
694
736
  with Timer() as timer:
695
737
  relayed_response = self.relay(request)
696
- # print("*****************")
697
- # print("GRAPHQL REQUEST:")
698
- # print(request.get_json())
699
- # print("GRAPHQL RESPONSE:")
700
- # print(relayed_response.status_code, relayed_response.json())
701
- # print("*****************")
738
+ if self.verbose:
739
+ print("*****************")
740
+ print("GRAPHQL REQUEST:")
741
+ print(request.get_json())
742
+ print("GRAPHQL RESPONSE:")
743
+ print(relayed_response.status_code, relayed_response.json())
744
+ print("*****************")
702
745
  # snoop work to extract the context
703
746
  self.snoop_context(request, relayed_response, timer.elapsed)
704
- # print("*****************")
705
- # print("SNOOPED CONTEXT:")
706
- # print(self.context.entries)
707
- # print(len(self.context.raw_data))
708
- # print("*****************")
747
+ if self.verbose:
748
+ print("*****************")
749
+ print("SNOOPED CONTEXT:")
750
+ print(self.context.entries)
751
+ print(len(self.context.raw_data))
752
+ print("*****************")
709
753
 
710
754
  return relayed_response.json()
711
755
 
@@ -713,16 +757,17 @@ class RelayServer:
713
757
  request = flask.request
714
758
  with Timer() as timer:
715
759
  relayed_response = self.relay(request)
716
- # print("*****************")
717
- # print("FILE STREAM REQUEST:")
718
- # print("********PATH*********")
719
- # print(path)
720
- # print("********ENDPATH*********")
721
- # print(request.get_json())
722
- # print("FILE STREAM RESPONSE:")
723
- # print(relayed_response)
724
- # print(relayed_response.status_code, relayed_response.json())
725
- # print("*****************")
760
+ if self.verbose:
761
+ print("*****************")
762
+ print("FILE STREAM REQUEST:")
763
+ print("********PATH*********")
764
+ print(path)
765
+ print("********ENDPATH*********")
766
+ print(request.get_json())
767
+ print("FILE STREAM RESPONSE:")
768
+ print(relayed_response)
769
+ print(relayed_response.status_code, relayed_response.json())
770
+ print("*****************")
726
771
 
727
772
  self.snoop_context(request, relayed_response, timer.elapsed, path=path)
728
773
 
@@ -732,12 +777,13 @@ class RelayServer:
732
777
  request = flask.request
733
778
  with Timer() as timer:
734
779
  relayed_response = self.relay(request)
735
- # print("*****************")
736
- # print("STORAGE REQUEST:")
737
- # print(request.get_json())
738
- # print("STORAGE RESPONSE:")
739
- # print(relayed_response.status_code, relayed_response.json())
740
- # print("*****************")
780
+ if self.verbose:
781
+ print("*****************")
782
+ print("STORAGE REQUEST:")
783
+ print(request.get_json())
784
+ print("STORAGE RESPONSE:")
785
+ print(relayed_response.status_code, relayed_response.json())
786
+ print("*****************")
741
787
 
742
788
  self.snoop_context(request, relayed_response, timer.elapsed)
743
789
 
@@ -747,15 +793,16 @@ class RelayServer:
747
793
  request = flask.request
748
794
  with Timer() as timer:
749
795
  relayed_response = self.relay(request)
750
- # print("*****************")
751
- # print("STORAGE FILE REQUEST:")
752
- # print("********PATH*********")
753
- # print(path)
754
- # print("********ENDPATH*********")
755
- # print(request.get_json())
756
- # print("STORAGE FILE RESPONSE:")
757
- # print(relayed_response.json())
758
- # print("*****************")
796
+ if self.verbose:
797
+ print("*****************")
798
+ print("STORAGE FILE REQUEST:")
799
+ print("********PATH*********")
800
+ print(path)
801
+ print("********ENDPATH*********")
802
+ print(request.get_json())
803
+ print("STORAGE FILE RESPONSE:")
804
+ print(relayed_response.json())
805
+ print("*****************")
759
806
 
760
807
  self.snoop_context(request, relayed_response, timer.elapsed, path=path)
761
808