wandb 0.17.3__py3-none-macosx_11_0_arm64.whl → 0.17.4__py3-none-macosx_11_0_arm64.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,7 @@ import socket
11
11
  import sys
12
12
  import threading
13
13
  from copy import deepcopy
14
+ from pathlib import Path
14
15
  from typing import (
15
16
  IO,
16
17
  TYPE_CHECKING,
@@ -37,14 +38,14 @@ from wandb_gql.client import RetryError
37
38
  import wandb
38
39
  from wandb import env, util
39
40
  from wandb.apis.normalize import normalize_exceptions, parse_backend_error_messages
40
- from wandb.errors import CommError, UnsupportedError, UsageError
41
+ from wandb.errors import AuthenticationError, CommError, UnsupportedError, UsageError
41
42
  from wandb.integration.sagemaker import parse_sm_secrets
42
43
  from wandb.old.settings import Settings
43
44
  from wandb.sdk.internal.thread_local_settings import _thread_local_api_settings
44
45
  from wandb.sdk.lib.gql_request import GraphQLSession
45
46
  from wandb.sdk.lib.hashutil import B64MD5, md5_file_b64
46
47
 
47
- from ..lib import retry
48
+ from ..lib import credentials, retry
48
49
  from ..lib.filenames import DIFF_FNAME, METADATA_FNAME
49
50
  from ..lib.gitlib import GitRepo
50
51
  from . import context
@@ -234,14 +235,18 @@ class Api:
234
235
  extra_http_headers = self.settings("_extra_http_headers") or json.loads(
235
236
  self._environ.get("WANDB__EXTRA_HTTP_HEADERS", "{}")
236
237
  )
238
+ extra_http_headers.update(_thread_local_api_settings.headers or {})
239
+
240
+ auth = None
241
+ if self.access_token is not None:
242
+ extra_http_headers["Authorization"] = f"Bearer {self.access_token}"
243
+ elif _thread_local_api_settings.cookies is None:
244
+ auth = ("api", self.api_key or "")
245
+
237
246
  proxies = self.settings("_proxies") or json.loads(
238
247
  self._environ.get("WANDB__PROXIES", "{}")
239
248
  )
240
249
 
241
- auth = None
242
- if _thread_local_api_settings.cookies is None:
243
- auth = ("api", self.api_key or "")
244
- extra_http_headers.update(_thread_local_api_settings.headers or {})
245
250
  self.client = Client(
246
251
  transport=GraphQLSession(
247
252
  headers={
@@ -376,6 +381,35 @@ class Api:
376
381
  default_key: Optional[str] = self.default_settings.get("api_key")
377
382
  return env_key or key or sagemaker_key or default_key
378
383
 
384
+ @property
385
+ def access_token(self) -> Optional[str]:
386
+ """Retrieves an access token for authentication.
387
+
388
+ This function attempts to exchange an identity token for a temporary
389
+ access token from the server, and save it to the credentials file.
390
+ It uses the path to the identity token as defined in the environment
391
+ variables. If the environment variable is not set, it returns None.
392
+
393
+ Returns:
394
+ Optional[str]: The access token if available, otherwise None if
395
+ no identity token is supplied.
396
+ Raises:
397
+ AuthenticationError: If the path to the identity token is not found.
398
+ """
399
+ token_file_str = self._environ.get(env.IDENTITY_TOKEN_FILE)
400
+ if not token_file_str:
401
+ return None
402
+
403
+ token_file = Path(token_file_str)
404
+ if not token_file.exists():
405
+ raise AuthenticationError(f"Identity token file not found: {token_file}")
406
+
407
+ base_url = self.settings("base_url")
408
+ credentials_file = env.get_credentials_file(
409
+ str(credentials.DEFAULT_WANDB_CREDENTIALS_FILE), self._environ
410
+ )
411
+ return credentials.access_token(base_url, token_file, credentials_file)
412
+
379
413
  @property
380
414
  def api_url(self) -> str:
381
415
  return self.settings("base_url") # type: ignore
@@ -2674,14 +2708,20 @@ class Api:
2674
2708
  A tuple of the content length and the streaming response
2675
2709
  """
2676
2710
  check_httpclient_logger_handler()
2711
+
2712
+ http_headers = _thread_local_api_settings.headers or {}
2713
+
2677
2714
  auth = None
2678
- if _thread_local_api_settings.cookies is None:
2679
- auth = ("user", self.api_key or "")
2715
+ if self.access_token is not None:
2716
+ http_headers["Authorization"] = f"Bearer {self.access_token}"
2717
+ elif _thread_local_api_settings.cookies is None:
2718
+ auth = ("api", self.api_key or "")
2719
+
2680
2720
  response = requests.get(
2681
2721
  url,
2682
2722
  auth=auth,
2683
2723
  cookies=_thread_local_api_settings.cookies or {},
2684
- headers=_thread_local_api_settings.headers or {},
2724
+ headers=http_headers,
2685
2725
  stream=True,
2686
2726
  )
2687
2727
  response.raise_for_status()
@@ -3039,6 +3079,7 @@ class Api:
3039
3079
  entity: Optional[str] = None,
3040
3080
  state: Optional[str] = None,
3041
3081
  prior_runs: Optional[List[str]] = None,
3082
+ template_variable_values: Optional[Dict[str, Any]] = None,
3042
3083
  ) -> Tuple[str, List[str]]:
3043
3084
  """Upsert a sweep object.
3044
3085
 
@@ -3052,6 +3093,7 @@ class Api:
3052
3093
  entity (str): entity to use
3053
3094
  state (str): state
3054
3095
  prior_runs (list): IDs of existing runs to add to the sweep
3096
+ template_variable_values (dict): template variable values
3055
3097
  """
3056
3098
  project_query = """
3057
3099
  project {
@@ -3096,7 +3138,17 @@ class Api:
3096
3138
  """
3097
3139
  # TODO(jhr): we need protocol versioning to know schema is not supported
3098
3140
  # for now we will just try both new and old query
3099
-
3141
+ mutation_5 = gql(
3142
+ mutation_str.replace(
3143
+ "$controller: JSONString,",
3144
+ "$controller: JSONString,$launchScheduler: JSONString, $templateVariableValues: JSONString,",
3145
+ )
3146
+ .replace(
3147
+ "controller: $controller,",
3148
+ "controller: $controller,launchScheduler: $launchScheduler,templateVariableValues: $templateVariableValues,",
3149
+ )
3150
+ .replace("_PROJECT_QUERY_", project_query)
3151
+ )
3100
3152
  # launchScheduler was introduced in core v0.14.0
3101
3153
  mutation_4 = gql(
3102
3154
  mutation_str.replace(
@@ -3105,7 +3157,7 @@ class Api:
3105
3157
  )
3106
3158
  .replace(
3107
3159
  "controller: $controller,",
3108
- "controller: $controller,launchScheduler: $launchScheduler,",
3160
+ "controller: $controller,launchScheduler: $launchScheduler",
3109
3161
  )
3110
3162
  .replace("_PROJECT_QUERY_", project_query)
3111
3163
  )
@@ -3124,7 +3176,7 @@ class Api:
3124
3176
  )
3125
3177
 
3126
3178
  # TODO(dag): replace this with a query for protocol versioning
3127
- mutations = [mutation_4, mutation_3, mutation_2, mutation_1]
3179
+ mutations = [mutation_5, mutation_4, mutation_3, mutation_2, mutation_1]
3128
3180
 
3129
3181
  config = self._validate_config_and_fill_distribution(config)
3130
3182
 
@@ -3148,6 +3200,7 @@ class Api:
3148
3200
  "projectName": project or self.settings("project"),
3149
3201
  "controller": controller,
3150
3202
  "launchScheduler": launch_scheduler,
3203
+ "templateVariableValues": json.dumps(template_variable_values),
3151
3204
  "scheduler": scheduler,
3152
3205
  "priorRunsFilters": filters,
3153
3206
  }
@@ -308,7 +308,9 @@ class LaunchAgent:
308
308
  self._wandb_run = None
309
309
 
310
310
  if self.gorilla_supports_agents:
311
- settings = wandb.Settings(silent=True, disable_git=True)
311
+ settings = wandb.Settings(
312
+ silent=True, disable_git=True, disable_job_creation=True
313
+ )
312
314
  self._wandb_run = wandb.init(
313
315
  project=self._project,
314
316
  entity=self._entity,
@@ -259,10 +259,12 @@ class Scheduler(ABC):
259
259
 
260
260
  def _init_wandb_run(self) -> "SdkRun":
261
261
  """Controls resume or init logic for a scheduler wandb run."""
262
+ settings = wandb.Settings(disable_job_creation=True)
262
263
  run: SdkRun = wandb.init( # type: ignore
263
264
  name=f"Scheduler.{self._sweep_id}",
264
265
  resume="allow",
265
266
  config=self._kwargs, # when run as a job, this sets config
267
+ settings=settings,
266
268
  )
267
269
  return run
268
270
 
@@ -26,6 +26,7 @@ _Setting = Literal[
26
26
  "_disable_machine_info",
27
27
  "_executable",
28
28
  "_extra_http_headers",
29
+ "_file_stream_max_bytes",
29
30
  "_file_stream_retry_max",
30
31
  "_file_stream_retry_wait_min_seconds",
31
32
  "_file_stream_retry_wait_max_seconds",
@@ -91,6 +92,7 @@ _Setting = Literal[
91
92
  "config_paths",
92
93
  "console",
93
94
  "console_multipart",
95
+ "credentials_file",
94
96
  "deployment",
95
97
  "disable_code",
96
98
  "disable_git",
@@ -112,6 +114,7 @@ _Setting = Literal[
112
114
  "host",
113
115
  "http_proxy",
114
116
  "https_proxy",
117
+ "identity_token_file",
115
118
  "ignore_globs",
116
119
  "init_timeout",
117
120
  "is_local",
@@ -0,0 +1,141 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime, timedelta
4
+ from pathlib import Path
5
+
6
+ import requests.utils
7
+
8
+ from wandb.errors import AuthenticationError
9
+
10
+ DEFAULT_WANDB_CREDENTIALS_FILE = Path(
11
+ os.path.expanduser("~/.config/wandb/credentials.json")
12
+ )
13
+
14
+ _expires_at_fmt = "%Y-%m-%d %H:%M:%S"
15
+
16
+
17
+ def access_token(base_url: str, token_file: Path, credentials_file: Path) -> str:
18
+ """Retrieve an access token from the credentials file.
19
+
20
+ If no access token exists, create a new one by exchanging the identity
21
+ token from the token file, and save it to the credentials file.
22
+
23
+ Args:
24
+ base_url (str): The base URL of the server
25
+ token_file (pathlib.Path): The path to the file containing the
26
+ identity token
27
+ credentials_file (pathlib.Path): The path to file used to save
28
+ temporary access tokens
29
+
30
+ Returns:
31
+ str: The access token
32
+ """
33
+ if not credentials_file.exists():
34
+ _write_credentials_file(base_url, token_file, credentials_file)
35
+
36
+ data = _fetch_credentials(base_url, token_file, credentials_file)
37
+ return data["access_token"]
38
+
39
+
40
+ def _write_credentials_file(base_url: str, token_file: Path, credentials_file: Path):
41
+ """Obtain an access token from the server and write it to the credentials file.
42
+
43
+ Args:
44
+ base_url (str): The base URL of the server
45
+ token_file (pathlib.Path): The path to the file containing the
46
+ identity token
47
+ credentials_file (pathlib.Path): The path to file used to save
48
+ temporary access tokens
49
+ """
50
+ credentials = _create_access_token(base_url, token_file)
51
+ data = {"credentials": {base_url: credentials}}
52
+ with open(credentials_file, "w") as file:
53
+ json.dump(data, file, indent=4)
54
+
55
+ # Set file permissions to be read/write by the owner only
56
+ os.chmod(credentials_file, 0o600)
57
+
58
+
59
+ def _fetch_credentials(base_url: str, token_file: Path, credentials_file: Path) -> dict:
60
+ """Fetch the access token from the credentials file.
61
+
62
+ If the access token has expired, fetch a new one from the server and save it
63
+ to the credentials file.
64
+
65
+ Args:
66
+ base_url (str): The base URL of the server
67
+ token_file (pathlib.Path): The path to the file containing the
68
+ identity token
69
+ credentials_file (pathlib.Path): The path to file used to save
70
+ temporary access tokens
71
+
72
+ Returns:
73
+ dict: The credentials including the access token.
74
+ """
75
+ creds = {}
76
+ with open(credentials_file) as file:
77
+ data = json.load(file)
78
+ if "credentials" not in data:
79
+ data["credentials"] = {}
80
+ if base_url in data["credentials"]:
81
+ creds = data["credentials"][base_url]
82
+
83
+ expires_at = datetime.utcnow()
84
+ if "expires_at" in creds:
85
+ expires_at = datetime.strptime(creds["expires_at"], _expires_at_fmt)
86
+
87
+ if expires_at <= datetime.utcnow():
88
+ creds = _create_access_token(base_url, token_file)
89
+ with open(credentials_file, "w") as file:
90
+ data["credentials"][base_url] = creds
91
+ json.dump(data, file, indent=4)
92
+
93
+ return creds
94
+
95
+
96
+ def _create_access_token(base_url: str, token_file: Path) -> dict:
97
+ """Exchange an identity token for an access token from the server.
98
+
99
+ Args:
100
+ base_url (str): The base URL of the server.
101
+ token_file (pathlib.Path): The path to the file containing the
102
+ identity token
103
+
104
+ Returns:
105
+ dict: The access token and its expiration.
106
+
107
+ Raises:
108
+ FileNotFoundError: If the token file is not found.
109
+ OSError: If there is an issue reading the token file.
110
+ AuthenticationError: If the server fails to provide an access token.
111
+ """
112
+ try:
113
+ with open(token_file) as file:
114
+ token = file.read().strip()
115
+ except FileNotFoundError as e:
116
+ raise FileNotFoundError(f"Identity token file not found: {token_file}") from e
117
+ except OSError as e:
118
+ raise OSError(
119
+ f"Failed to read the identity token from file: {token_file}"
120
+ ) from e
121
+
122
+ url = f"{base_url}/oidc/token"
123
+ data = {
124
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
125
+ "assertion": token,
126
+ }
127
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
128
+
129
+ response = requests.post(url, data=data, headers=headers)
130
+
131
+ if response.status_code != 200:
132
+ raise AuthenticationError(
133
+ f"Failed to retrieve access token: {response.status_code}, {response.text}"
134
+ )
135
+
136
+ resp_json = response.json()
137
+ expires_at = datetime.utcnow() + timedelta(seconds=float(resp_json["expires_in"]))
138
+ resp_json["expires_at"] = expires_at.strftime(_expires_at_fmt)
139
+ del resp_json["expires_in"]
140
+
141
+ return resp_json
wandb/sdk/wandb_init.py CHANGED
@@ -323,6 +323,15 @@ class _WandbInit:
323
323
  if save_code_pre_user_settings is False:
324
324
  settings.update({"save_code": False}, source=Source.INIT)
325
325
 
326
+ # TODO: remove this once we refactor the client. This is a temporary
327
+ # fix to make sure that we use the same project name for wandb-core.
328
+ # The reason this is not going throught the settings object is to
329
+ # avoid failure cases in other parts of the code that will be
330
+ # removed with the switch to wandb-core.
331
+ if settings.project is None:
332
+ project = wandb.util.auto_project_name(settings.program)
333
+ settings.update({"project": project}, source=Source.INIT)
334
+
326
335
  # TODO(jhr): should this be moved? probably.
327
336
  settings._set_run_start_time(source=Source.INIT)
328
337
 
@@ -989,8 +998,9 @@ def init(
989
998
 
990
999
  Arguments:
991
1000
  project: (str, optional) The name of the project where you're sending
992
- the new run. If the project is not specified, the run is put in an
993
- "Uncategorized" project.
1001
+ the new run. If the project is not specified, we will try to infer
1002
+ the project name from git root or the current program file. If we
1003
+ can't infer the project name, we will default to `"uncategorized"`.
994
1004
  entity: (str, optional) An entity is a username or team name where
995
1005
  you're sending runs. This entity must exist before you can send runs
996
1006
  there, so make sure to create your account or team in the UI before
wandb/sdk/wandb_login.py CHANGED
@@ -156,6 +156,9 @@ class _WandbLogin:
156
156
  """Returns whether an API key is set or can be inferred."""
157
157
  return apikey.api_key(settings=self._settings) is not None
158
158
 
159
+ def should_use_identity_token(self):
160
+ return self._settings.identity_token_file is not None
161
+
159
162
  def set_backend(self, backend):
160
163
  self._backend = backend
161
164
 
@@ -327,6 +330,9 @@ def _login(
327
330
  )
328
331
  return False
329
332
 
333
+ if wlogin.should_use_identity_token():
334
+ return True
335
+
330
336
  # perform a login
331
337
  logged_in = wlogin.login()
332
338
 
@@ -127,6 +127,7 @@ class _Manager:
127
127
  raise ManagerConnectionError(f"Connection to wandb service failed: {e}")
128
128
 
129
129
  def __init__(self, settings: "Settings") -> None:
130
+ """Connects to the internal service, starting it if necessary."""
130
131
  from wandb.sdk.service import service
131
132
 
132
133
  self._settings = settings
@@ -134,6 +135,7 @@ class _Manager:
134
135
  self._hooks = None
135
136
 
136
137
  self._service = service._Service(settings=self._settings)
138
+
137
139
  token = _ManagerToken.from_environment()
138
140
  if not token:
139
141
  self._service.start()
@@ -144,7 +146,6 @@ class _Manager:
144
146
  token = _ManagerToken.from_params(transport=transport, host=host, port=port)
145
147
  token.set_environment()
146
148
  self._atexit_setup()
147
-
148
149
  self._token = token
149
150
 
150
151
  try:
@@ -152,6 +153,24 @@ class _Manager:
152
153
  except ManagerConnectionError as e:
153
154
  wandb._sentry.reraise(e)
154
155
 
156
+ def _teardown(self, exit_code: int) -> int:
157
+ """Shuts down the internal process and returns its exit code.
158
+
159
+ This sends a teardown record to the process. An exception is raised if
160
+ the process has already been shut down.
161
+ """
162
+ unregister_all_post_import_hooks()
163
+
164
+ if self._atexit_lambda:
165
+ atexit.unregister(self._atexit_lambda)
166
+ self._atexit_lambda = None
167
+
168
+ try:
169
+ self._inform_teardown(exit_code)
170
+ return self._service.join()
171
+ finally:
172
+ self._token.reset_environment()
173
+
155
174
  def _atexit_setup(self) -> None:
156
175
  self._atexit_lambda = lambda: self._atexit_teardown()
157
176
 
@@ -161,28 +180,18 @@ class _Manager:
161
180
 
162
181
  def _atexit_teardown(self) -> None:
163
182
  trigger.call("on_finished")
164
- exit_code = self._hooks.exit_code if self._hooks else 0
165
- self._teardown(exit_code)
166
-
167
- def _teardown(self, exit_code: int) -> None:
168
- unregister_all_post_import_hooks()
169
183
 
170
- if self._atexit_lambda:
171
- atexit.unregister(self._atexit_lambda)
172
- self._atexit_lambda = None
184
+ # Clear the atexit hook---we're executing it now, after which the
185
+ # process will exit.
186
+ self._atexit_lambda = None
173
187
 
174
188
  try:
175
- self._inform_teardown(exit_code)
176
- result = self._service.join()
177
- if result and not self._settings._notebook:
178
- os._exit(result)
189
+ self._teardown(self._hooks.exit_code if self._hooks else 0)
179
190
  except Exception as e:
180
191
  wandb.termlog(
181
- f"While tearing down the service manager. The following error has occurred: {e}",
192
+ f"Encountered an error while tearing down the service manager: {e}",
182
193
  repeat=False,
183
194
  )
184
- finally:
185
- self._token.reset_environment()
186
195
 
187
196
  def _get_service(self) -> "service._Service":
188
197
  return self._service
@@ -43,7 +43,7 @@ from wandb.apis.internal import Api
43
43
  from wandb.errors import UsageError
44
44
  from wandb.proto import wandb_settings_pb2
45
45
  from wandb.sdk.internal.system.env_probe_helpers import is_aws_lambda
46
- from wandb.sdk.lib import filesystem
46
+ from wandb.sdk.lib import credentials, filesystem
47
47
  from wandb.sdk.lib._settings_toposort_generated import SETTINGS_TOPOLOGICALLY_SORTED
48
48
  from wandb.sdk.lib.run_moment import RunMoment
49
49
  from wandb.sdk.wandb_setup import _EarlyLogger
@@ -314,6 +314,7 @@ class SettingsData:
314
314
  _disable_machine_info: bool # Disable automatic machine info collection
315
315
  _executable: str
316
316
  _extra_http_headers: Mapping[str, str]
317
+ _file_stream_max_bytes: int # max size for filestream requests in core
317
318
  # file stream retry client configuration
318
319
  _file_stream_retry_max: int # max number of retries
319
320
  _file_stream_retry_wait_min_seconds: float # min wait time between retries
@@ -391,6 +392,7 @@ class SettingsData:
391
392
  config_paths: Sequence[str]
392
393
  console: str
393
394
  console_multipart: bool # whether to produce multipart console log files
395
+ credentials_file: str # file path to write access tokens
394
396
  deployment: str
395
397
  disable_code: bool
396
398
  disable_git: bool
@@ -412,6 +414,7 @@ class SettingsData:
412
414
  host: str
413
415
  http_proxy: str # proxy server for the http requests to W&B
414
416
  https_proxy: str # proxy server for the https requests to W&B
417
+ identity_token_file: str # file path to supply a jwt for authentication
415
418
  ignore_globs: Tuple[str]
416
419
  init_timeout: float
417
420
  is_local: bool
@@ -659,6 +662,7 @@ class Settings(SettingsData):
659
662
  _disable_update_check={"preprocessor": _str_as_bool},
660
663
  _disable_viewer={"preprocessor": _str_as_bool},
661
664
  _extra_http_headers={"preprocessor": _str_as_json},
665
+ _file_stream_max_bytes={"preprocessor": int},
662
666
  _file_stream_retry_max={"preprocessor": int},
663
667
  _file_stream_retry_wait_min_seconds={"preprocessor": float},
664
668
  _file_stream_retry_wait_max_seconds={"preprocessor": float},
@@ -783,6 +787,10 @@ class Settings(SettingsData):
783
787
  "auto_hook": True,
784
788
  },
785
789
  console_multipart={"value": False, "preprocessor": _str_as_bool},
790
+ credentials_file={
791
+ "value": str(credentials.DEFAULT_WANDB_CREDENTIALS_FILE),
792
+ "preprocessor": str,
793
+ },
786
794
  deployment={
787
795
  "hook": lambda _: "local" if self.is_local else "cloud",
788
796
  "auto_hook": True,
@@ -829,6 +837,7 @@ class Settings(SettingsData):
829
837
  "hook": lambda x: self._proxies and self._proxies.get("https") or x,
830
838
  "auto_hook": True,
831
839
  },
840
+ identity_token_file={"value": None, "preprocessor": str},
832
841
  ignore_globs={
833
842
  "value": tuple(),
834
843
  "preprocessor": lambda x: tuple(x) if not isinstance(x, tuple) else x,
@@ -873,7 +882,9 @@ class Settings(SettingsData):
873
882
  program={
874
883
  "hook": lambda x: self._get_program(x),
875
884
  },
876
- project={"validator": self._validate_project},
885
+ project={
886
+ "validator": self._validate_project,
887
+ },
877
888
  project_url={"hook": lambda _: self._project_url(), "auto_hook": True},
878
889
  quiet={"preprocessor": _str_as_bool},
879
890
  reinit={"preprocessor": _str_as_bool},
wandb/sdk/wandb_setup.py CHANGED
@@ -268,20 +268,20 @@ class _WandbSetup__WandbSetup: # noqa: N801
268
268
  self._config = config_dict
269
269
 
270
270
  def _teardown(self, exit_code: Optional[int] = None) -> None:
271
- exit_code = exit_code or 0
272
- self._teardown_manager(exit_code=exit_code)
271
+ if not self._manager:
272
+ return
273
+
274
+ internal_exit_code = self._manager._teardown(exit_code or 0)
275
+ self._manager = None
276
+
277
+ if internal_exit_code != 0:
278
+ sys.exit(internal_exit_code)
273
279
 
274
280
  def _setup_manager(self) -> None:
275
281
  if self._settings._disable_service:
276
282
  return
277
283
  self._manager = wandb_manager._Manager(settings=self._settings)
278
284
 
279
- def _teardown_manager(self, exit_code: int) -> None:
280
- if not self._manager:
281
- return
282
- self._manager._teardown(exit_code)
283
- self._manager = None
284
-
285
285
  def _get_manager(self) -> Optional[wandb_manager._Manager]:
286
286
  return self._manager
287
287
 
@@ -312,11 +312,9 @@ def _setup(
312
312
  ) -> Optional["_WandbSetup"]:
313
313
  """Set up library context."""
314
314
  if _reset:
315
- setup_instance = _WandbSetup._instance
316
- if setup_instance:
317
- setup_instance._teardown()
318
- _WandbSetup._instance = None
315
+ teardown()
319
316
  return None
317
+
320
318
  wl = _WandbSetup(settings=settings)
321
319
  return wl
322
320
 
@@ -330,6 +328,7 @@ def setup(
330
328
 
331
329
  def teardown(exit_code: Optional[int] = None) -> None:
332
330
  setup_instance = _WandbSetup._instance
331
+ _WandbSetup._instance = None
332
+
333
333
  if setup_instance:
334
334
  setup_instance._teardown(exit_code=exit_code)
335
- _WandbSetup._instance = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wandb
3
- Version: 0.17.3
3
+ Version: 0.17.4
4
4
  Summary: A CLI and library for interacting with the Weights & Biases API.
5
5
  Project-URL: Source, https://github.com/wandb/wandb
6
6
  Project-URL: Bug Reports, https://github.com/wandb/wandb/issues