From 7df6b671d45ee8522d2fa1314149e21b90a0bd5c Mon Sep 17 00:00:00 2001 From: Chris Van Pelt Date: Thu, 10 Sep 2020 15:17:01 -0700 Subject: [PATCH 1/3] Basic support for public api artifact linking (#198) * Basic support for public api mods * PR feedback * Make sampledHistory return the results from the first spec * Clarfying comment Co-authored-by: Jeff Raubitschek --- tests/test_public_api.py | 33 +++++++++++++++++++++ tests/utils/mock_server.py | 2 +- wandb/apis/public.py | 54 +++++++++++++++++++++++++++++++++- wandb/internal/internal_api.py | 2 ++ wandb/internal/sender.py | 3 +- 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/tests/test_public_api.py b/tests/test_public_api.py index ad12779d..875ca4b3 100644 --- a/tests/test_public_api.py +++ b/tests/test_public_api.py @@ -96,6 +96,12 @@ def test_run_history(mock_server, api): assert run.history(pandas=False)[0] == {'acc': 10, 'loss': 90} +def test_run_history_keys(mock_server, api): + run = api.run("test/test/test") + assert run.history(keys=["acc", "loss"], pandas=False) == [ + {"loss": 0, "acc": 100}, {"loss": 1, "acc": 0}] + + def test_run_config(mock_server, api): run = api.run("test/test/test") assert run.config == {'epochs': 10} @@ -264,6 +270,33 @@ def test_artifact_run_logged(runner, mock_server, api): assert arts[0].name == "abc123" +def test_artifact_manual_use(runner, mock_server, api): + run = api.run("test/test/test") + art = api.artifact("entity/project/mnist:v0", type="dataset") + run.use_artifact(art) + assert True + + +def test_artifact_manual_log(runner, mock_server, api): + run = api.run("test/test/test") + art = api.artifact("entity/project/mnist:v0", type="dataset") + run.log_artifact(art) + assert True + + +def test_artifact_manual_error(runner, mock_server, api): + run = api.run("test/test/test") + art = wandb.Artifact("test", type="dataset") + with pytest.raises(wandb.CommError): + run.log_artifact(art) + with pytest.raises(wandb.CommError): + run.use_artifact(art) + with pytest.raises(wandb.CommError): + run.use_artifact("entity/project/mnist:v0") + with pytest.raises(wandb.CommError): + run.log_artifact("entity/project/mnist:v0") + + @pytest.mark.skipif(platform.system() == "Windows", reason="Verify is broken on Windows") def test_artifact_verify(runner, mock_server, api): diff --git a/tests/utils/mock_server.py b/tests/utils/mock_server.py index 5b6fab31..a483600f 100644 --- a/tests/utils/mock_server.py +++ b/tests/utils/mock_server.py @@ -100,7 +100,7 @@ def run(ctx): } ] }, - "sampledHistory": ['{"loss": 0, "acc": 100}'], + "sampledHistory": [[{"loss": 0, "acc": 100}, {"loss": 1, "acc": 0}]], "shouldStop": False, "failed": False, "stopped": stopped, diff --git a/wandb/apis/public.py b/wandb/apis/public.py index 6488fb43..a9ce7597 100644 --- a/wandb/apis/public.py +++ b/wandb/apis/public.py @@ -960,7 +960,8 @@ def _sampled_history(self, keys, x_axis="_step", samples=500): ''') response = self._exec(query, specs=[json.dumps(spec)]) - return [line for line in response['project']['run']['sampledHistory']] + # sampledHistory returns one list per spec, we only send one spec + return response['project']['run']['sampledHistory'][0] def _full_history(self, samples=500, stream="default"): node = "history" if stream == "default" else "events" @@ -1074,6 +1075,55 @@ def logged_artifacts(self, per_page=100): def used_artifacts(self, per_page=100): return RunArtifacts(self.client, self, mode="used", per_page=per_page) + @normalize_exceptions + def use_artifact(self, artifact): + """ Declare an artifact as an input to a run. + + Args: + artifact (:obj:`Artifact`): An artifact returned from + `wandb.Api().artifact(name)` + Returns: + A :obj:`Artifact` object. + """ + api = InternalApi(default_settings={ + "entity": self.entity, "project": self.project}) + api.set_current_run_id(self.id) + + if isinstance(artifact, Artifact): + api.use_artifact(artifact.id) + return artifact + elif isinstance(artifact, wandb.Artifact): + raise ValueError("Only existing artifacts are accepted by this api. " + "Manually create one with `wandb artifacts put`") + else: + raise ValueError('You must pass a wandb.Api().artifact() to use_artifact') + + @normalize_exceptions + def log_artifact(self, artifact, aliases=None): + """ Declare an artifact as output of a run. + + Args: + artifact (:obj:`Artifact`): An artifact returned from + `wandb.Api().artifact(name)` + aliases (list, optional): Aliases to apply to this artifact + Returns: + A :obj:`Artifact` object. + """ + api = InternalApi(default_settings={ + "entity": self.entity, "project": self.project}) + api.set_current_run_id(self.id) + + if isinstance(artifact, Artifact): + artifact_collection_name = artifact.name.split(':')[0] + api.create_artifact(artifact.type, artifact_collection_name, + artifact.digest, aliases=aliases) + return artifact + elif isinstance(artifact, wandb.Artifact): + raise ValueError("Only existing artifacts are accepted by this api. " + "Manually create one with `wandb artifacts put`") + else: + raise ValueError('You must pass a wandb.Api().artifact() to use_artifact') + @property def summary(self): if self._summary is None: @@ -1777,6 +1827,8 @@ def update_variables(self): self.variables.update({'cursor': self.cursor}) def convert_objects(self): + if self.last_response['project'] is None: + return [] return [ArtifactType(self.client, self.entity, self.project, r["node"]["name"], r["node"]) for r in self.last_response['project']['artifactTypes']['edges']] diff --git a/wandb/internal/internal_api.py b/wandb/internal/internal_api.py index 872e9bd0..23240614 100644 --- a/wandb/internal/internal_api.py +++ b/wandb/internal/internal_api.py @@ -1477,6 +1477,8 @@ def create_artifact(self, artifact_type_name, artifact_collection_name, digest, if not is_user_created: run_name = run_name or self.current_run_id + if aliases is None: + aliases = [] response = self.gql(mutation, variable_values={ 'entityName': entity_name, 'projectName': project_name, diff --git a/wandb/internal/sender.py b/wandb/internal/sender.py index eb782186..a49509c8 100644 --- a/wandb/internal/sender.py +++ b/wandb/internal/sender.py @@ -522,10 +522,11 @@ def send_artifact(self, data): is_user_created=artifact.user_created, ) + metadata = json.loads(artifact.metadata) if artifact.metadata else None saver.save( type=artifact.type, name=artifact.name, - metadata=json.loads(artifact.metadata), + metadata=metadata, description=artifact.description, aliases=artifact.aliases, use_after_commit=artifact.use_after_commit, From a48f04b43d2e67beb9f655336f4da4c867015bf0 Mon Sep 17 00:00:00 2001 From: Jeff Raubitschek Date: Thu, 10 Sep 2020 19:21:25 -0700 Subject: [PATCH 2/3] Fix up some login cases (#201) * another minimal fix * not so minimal * remove this debug * Make test_settings just be logged in * Revert "remove this debug" This reverts commit 08f47d6b1904aa4c402f64d48d241e908b9af5cf. * dont go offline if api is not configured * Test fixes * update test Co-authored-by: Chris Van Pelt --- tests/conftest.py | 2 ++ tests/test_public_api.py | 5 +++++ tests/wandb_test.py | 6 +++--- wandb/__init__.py | 2 +- wandb/cli/cli.py | 2 +- wandb/lib/apikey.py | 20 ++++++++++++++++++-- wandb/sdk/wandb_init.py | 4 ++++ wandb/sdk/wandb_login.py | 29 ++++++++++++++++++++--------- wandb/sdk_py27/wandb_init.py | 4 ++++ wandb/sdk_py27/wandb_login.py | 29 ++++++++++++++++++++--------- 10 files changed, 78 insertions(+), 25 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b17fc8b2..9a6c1f38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -145,6 +145,7 @@ def test_settings(test_dir, mocker): project="test", console="off", host="test", + api_key=DUMMY_API_KEY, run_id=wandb.util.generate_id(), _start_datetime=datetime.datetime.now()) settings.setdefaults() @@ -172,6 +173,7 @@ def runner(monkeypatch, mocker): 'project_name': 'test_model', 'files': ['weights.h5'], 'attach': False, 'team_name': 'Manual Entry'}) monkeypatch.setattr(webbrowser, 'open_new_tab', lambda x: True) + mocker.patch("wandb.lib.apikey.isatty", lambda stream: True) mocker.patch("wandb.lib.apikey.input", lambda x: 1) mocker.patch("wandb.lib.apikey.getpass.getpass", lambda x: DUMMY_API_KEY) return CliRunner() diff --git a/tests/test_public_api.py b/tests/test_public_api.py index 875ca4b3..ec57d749 100644 --- a/tests/test_public_api.py +++ b/tests/test_public_api.py @@ -19,6 +19,11 @@ def api(runner): return Api() +def test_api_auto_login_no_tty(mocker): + with pytest.raises(wandb.UsageError): + Api() + + def test_parse_project_path(api): e, p = api._parse_project_path("user/proj") assert e == "user" diff --git a/tests/wandb_test.py b/tests/wandb_test.py index b0465179..b690cf07 100644 --- a/tests/wandb_test.py +++ b/tests/wandb_test.py @@ -187,11 +187,11 @@ def test_login_key(capsys): assert wandb.api.api_key == "A" * 40 -def test_login_existing_key(): +def test_login_invalid_key(): os.environ["WANDB_API_KEY"] = "B" * 40 wandb.ensure_configured() - wandb.login() - assert wandb.api.api_key == "B" * 40 + with pytest.raises(wandb.UsageError): + wandb.login() del os.environ["WANDB_API_KEY"] diff --git a/wandb/__init__.py b/wandb/__init__.py index e7757c14..8498dbc0 100644 --- a/wandb/__init__.py +++ b/wandb/__init__.py @@ -50,7 +50,7 @@ Config = wandb_sdk.Config from wandb.apis import InternalApi, PublicApi -from wandb.errors.error import CommError +from wandb.errors.error import CommError, UsageError from wandb.lib import preinit as _preinit from wandb.lib import lazyloader as _lazyloader diff --git a/wandb/cli/cli.py b/wandb/cli/cli.py index c55d8f5a..5f3040d3 100644 --- a/wandb/cli/cli.py +++ b/wandb/cli/cli.py @@ -205,7 +205,7 @@ def login(key, host, cloud, relogin, anonymously, no_offline=False): api.set_setting("base_url", host.strip("/"), globally=True, persist=True) key = key[0] if len(key) > 0 else None - wandb.login(relogin=relogin, key=key, anonymous=anon_mode) + wandb.login(relogin=relogin, key=key, anonymous=anon_mode, force=True) @cli.command( diff --git a/wandb/lib/apikey.py b/wandb/lib/apikey.py index 27a02c14..3cf595fe 100644 --- a/wandb/lib/apikey.py +++ b/wandb/lib/apikey.py @@ -21,6 +21,7 @@ LOGIN_CHOICE_NEW = "Create a W&B account" LOGIN_CHOICE_EXISTS = "Use an existing W&B account" LOGIN_CHOICE_DRYRUN = "Don't visualize my results" +LOGIN_CHOICE_NOTTY = "Unconfigured" LOGIN_CHOICES = [ LOGIN_CHOICE_ANON, LOGIN_CHOICE_NEW, @@ -51,14 +52,22 @@ def _prompt_choice(): return -1 -def prompt_api_key( +def prompt_api_key( # noqa: C901 settings, api=None, input_callback=None, browser_callback=None, no_offline=False, + no_create=False, local=False, ): + """Prompt for api key. + + Returns: + str - if key is configured + None - if dryrun is selected + False - if unconfigured (notty) + """ input_callback = input_callback or getpass.getpass api = api or InternalApi() anon_mode = _fixup_anon_mode(settings.anonymous) @@ -71,6 +80,8 @@ def prompt_api_key( choices.remove(LOGIN_CHOICE_ANON) if jupyter or no_offline: choices.remove(LOGIN_CHOICE_DRYRUN) + if jupyter or no_create: + choices.remove(LOGIN_CHOICE_NEW) if jupyter and 'google.colab' in sys.modules: key = wandb.jupyter.attempt_colab_login(api.app_url) @@ -82,9 +93,11 @@ def prompt_api_key( result = LOGIN_CHOICE_ANON # If we're not in an interactive environment, default to dry-run. elif not jupyter and (not isatty(sys.stdout) or not isatty(sys.stdin)): - result = LOGIN_CHOICE_DRYRUN + result = LOGIN_CHOICE_NOTTY elif local: result = LOGIN_CHOICE_EXISTS + elif len(choices) == 1: + result = choices[0] else: for i, choice in enumerate(choices): wandb.termlog("(%i) %s" % (i + 1, choice)) @@ -131,6 +144,9 @@ def prompt_api_key( ).strip() write_key(settings, key) return key + elif result == LOGIN_CHOICE_NOTTY: + # TODO: Needs refactor as this needs to be handled by caller + return False else: # Jupyter environments don't have a tty, but we can still try logging in using # the browser callback if one is supplied. diff --git a/wandb/sdk/wandb_init.py b/wandb/sdk/wandb_init.py index 5952daf2..58e2ec15 100644 --- a/wandb/sdk/wandb_init.py +++ b/wandb/sdk/wandb_init.py @@ -148,6 +148,10 @@ def setup(self, kwargs): print("Ignored wandb.init() arg %s when running a sweep" % key) settings.apply_init(kwargs) + login_key = wandb_login._login(_disable_warning=True, _settings=settings) + if not login_key: + settings.mode = "offline" + # TODO(jhr): should this be moved? probably. d = dict(_start_time=time.time(), _start_datetime=datetime.datetime.now(),) settings.update(d) diff --git a/wandb/sdk/wandb_login.py b/wandb/sdk/wandb_login.py index fa28aa95..dd57e598 100644 --- a/wandb/sdk/wandb_login.py +++ b/wandb/sdk/wandb_login.py @@ -10,6 +10,7 @@ import click import wandb +from wandb.errors.error import UsageError from wandb.internal.internal_api import Api from wandb.lib import apikey @@ -20,14 +21,16 @@ def _validate_anonymous_setting(anon_str): return anon_str in ["must", "allow", "never"] -def login(anonymous=None, key=None, relogin=None): - return _login(anonymous=anonymous, key=key, relogin=relogin) +def login(anonymous=None, key=None, relogin=None, force=None): + configured = _login(anonymous=anonymous, key=key, relogin=relogin, force=force) + return True if configured else False def _login( anonymous=None, key=None, relogin=None, + force=None, _backend=None, _disable_warning=None, _settings=None, @@ -42,12 +45,15 @@ def _login( "allow" we'll only create an anonymous user if the user isn't already logged in. Returns: - None + bool: if key is configured + + Raises: + UsageError - if api_key can not configured and no tty """ if wandb.run is not None: if not _disable_warning: wandb.termwarn("Calling wandb.login() after wandb.init() is a no-op.") - return + return True settings = {} api = Api() @@ -59,7 +65,7 @@ def _login( "Invalid value passed for argument `anonymous` to " "wandb.login(). Can be 'must', 'allow', or 'never'." ) - return + return False settings.update({"anonymous": anonymous}) # Note: This won't actually do anything if called from a codepath where @@ -70,7 +76,7 @@ def _login( settings = _settings or wl.settings() if settings._offline: - return + return False active_entity = None logged_in = is_logged_in(settings=settings) @@ -90,7 +96,7 @@ def _login( ), repeat=False, ) - return + return True jupyter = settings._jupyter or False if key: @@ -105,12 +111,17 @@ def _login( ) apikey.write_key(settings, key) else: - apikey.prompt_api_key(settings, api=api) + key = apikey.prompt_api_key( + settings, api=api, no_offline=force, no_create=force + ) + if key is False: + raise UsageError("api_key not configured (no-tty). Run wandb login") + if _backend and not logged_in: # TODO: calling this twice is gross, this deserves a refactor # Make sure our backend picks up the new creds _ = _backend.interface.communicate_login(key, anonymous) - return + return key or False def is_logged_in(settings=None): diff --git a/wandb/sdk_py27/wandb_init.py b/wandb/sdk_py27/wandb_init.py index 0c71b296..493da27f 100644 --- a/wandb/sdk_py27/wandb_init.py +++ b/wandb/sdk_py27/wandb_init.py @@ -148,6 +148,10 @@ def setup(self, kwargs): print("Ignored wandb.init() arg %s when running a sweep" % key) settings.apply_init(kwargs) + login_key = wandb_login._login(_disable_warning=True, _settings=settings) + if not login_key: + settings.mode = "offline" + # TODO(jhr): should this be moved? probably. d = dict(_start_time=time.time(), _start_datetime=datetime.datetime.now(),) settings.update(d) diff --git a/wandb/sdk_py27/wandb_login.py b/wandb/sdk_py27/wandb_login.py index 7cf74280..541e03c8 100644 --- a/wandb/sdk_py27/wandb_login.py +++ b/wandb/sdk_py27/wandb_login.py @@ -10,6 +10,7 @@ import click import wandb +from wandb.errors.error import UsageError from wandb.internal.internal_api import Api from wandb.lib import apikey @@ -20,14 +21,16 @@ def _validate_anonymous_setting(anon_str): return anon_str in ["must", "allow", "never"] -def login(anonymous=None, key=None, relogin=None): - return _login(anonymous=anonymous, key=key, relogin=relogin) +def login(anonymous=None, key=None, relogin=None, force=None): + configured = _login(anonymous=anonymous, key=key, relogin=relogin, force=force) + return True if configured else False def _login( anonymous=None, key=None, relogin=None, + force=None, _backend=None, _disable_warning=None, _settings=None, @@ -42,12 +45,15 @@ def _login( "allow" we'll only create an anonymous user if the user isn't already logged in. Returns: - None + bool: if key is configured + + Raises: + UsageError - if api_key can not configured and no tty """ if wandb.run is not None: if not _disable_warning: wandb.termwarn("Calling wandb.login() after wandb.init() is a no-op.") - return + return True settings = {} api = Api() @@ -59,7 +65,7 @@ def _login( "Invalid value passed for argument `anonymous` to " "wandb.login(). Can be 'must', 'allow', or 'never'." ) - return + return False settings.update({"anonymous": anonymous}) # Note: This won't actually do anything if called from a codepath where @@ -70,7 +76,7 @@ def _login( settings = _settings or wl.settings() if settings._offline: - return + return False active_entity = None logged_in = is_logged_in(settings=settings) @@ -90,7 +96,7 @@ def _login( ), repeat=False, ) - return + return True jupyter = settings._jupyter or False if key: @@ -105,12 +111,17 @@ def _login( ) apikey.write_key(settings, key) else: - apikey.prompt_api_key(settings, api=api) + key = apikey.prompt_api_key( + settings, api=api, no_offline=force, no_create=force + ) + if key is False: + raise UsageError("api_key not configured (no-tty). Run wandb login") + if _backend and not logged_in: # TODO: calling this twice is gross, this deserves a refactor # Make sure our backend picks up the new creds _ = _backend.interface.communicate_login(key, anonymous) - return + return key or False def is_logged_in(settings=None): From dc8f20eb6e5be18ab9ec4708d91e6717bd5d931e Mon Sep 17 00:00:00 2001 From: Jeff Raubitschek Date: Fri, 11 Sep 2020 00:34:24 -0700 Subject: [PATCH 3/3] More login fixes (#202) * More login fixes * fix formatting on colab --- wandb/errors/term.py | 1 + wandb/lib/apikey.py | 17 +++++++---------- wandb/lib/server.py | 4 ++-- wandb/sdk/wandb_login.py | 17 +++++++++++++---- wandb/sdk/wandb_setup.py | 6 +++--- wandb/sdk_py27/wandb_login.py | 17 +++++++++++++---- wandb/sdk_py27/wandb_setup.py | 6 +++--- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/wandb/errors/term.py b/wandb/errors/term.py index 9d622b3d..e4199fbb 100644 --- a/wandb/errors/term.py +++ b/wandb/errors/term.py @@ -3,6 +3,7 @@ LOG_STRING = click.style('wandb', fg='blue', bold=True) +LOG_STRING_NOCOLOR = 'wandb' ERROR_STRING = click.style('ERROR', bg='red', fg='green') WARN_STRING = click.style('WARNING', fg='yellow') PRINTED_MESSAGES = set() diff --git a/wandb/lib/apikey.py b/wandb/lib/apikey.py index 3cf595fe..f3bd6164 100644 --- a/wandb/lib/apikey.py +++ b/wandb/lib/apikey.py @@ -13,7 +13,7 @@ from six.moves import input import wandb from wandb.apis import InternalApi -from wandb.errors.term import LOG_STRING +from wandb.errors import term from wandb.util import isatty @@ -43,7 +43,7 @@ def _prompt_choice(): return ( int( input( - "%s: Enter your choice: " % LOG_STRING + "%s: Enter your choice: " % term.LOG_STRING ) ) - 1 # noqa: W503 @@ -69,6 +69,7 @@ def prompt_api_key( # noqa: C901 False - if unconfigured (notty) """ input_callback = input_callback or getpass.getpass + log_string = term.LOG_STRING api = api or InternalApi() anon_mode = _fixup_anon_mode(settings.anonymous) jupyter = settings._jupyter or False @@ -84,6 +85,7 @@ def prompt_api_key( # noqa: C901 choices.remove(LOGIN_CHOICE_NEW) if jupyter and 'google.colab' in sys.modules: + log_string = term.LOG_STRING_NOCOLOR key = wandb.jupyter.attempt_colab_login(api.app_url) if key is not None: write_key(settings, key) @@ -110,6 +112,7 @@ def prompt_api_key( # noqa: C901 result = choices[idx] wandb.termlog("You chose '%s'" % result) + api_ask = "%s: Paste an API key from your profile and hit enter: " % log_string if result == LOGIN_CHOICE_ANON: key = api.create_anonymous_api_key() @@ -122,10 +125,7 @@ def prompt_api_key( # noqa: C901 wandb.termlog( "Create an account here: {}/authorize?signup=true".format(app_url) ) - key = input_callback( - "%s: Paste an API key from your profile and hit enter" - % LOG_STRING - ).strip() + key = input_callback(api_ask).strip() write_key(settings, key) return key @@ -138,10 +138,7 @@ def prompt_api_key( # noqa: C901 app_url ) ) - key = input_callback( - "%s: Paste an API key from your profile and hit enter" - % LOG_STRING - ).strip() + key = input_callback(api_ask).strip() write_key(settings, key) return key elif result == LOGIN_CHOICE_NOTTY: diff --git a/wandb/lib/server.py b/wandb/lib/server.py index 1320c69d..7cb6d35d 100644 --- a/wandb/lib/server.py +++ b/wandb/lib/server.py @@ -13,8 +13,8 @@ class ServerError(Exception): class Server(object): - def __init__(self, api=None): - self._api = api or InternalApi() + def __init__(self, api=None, settings=None): + self._api = api or InternalApi(default_settings=settings) self._error_network = None self._viewer = {} self._flags = {} diff --git a/wandb/sdk/wandb_login.py b/wandb/sdk/wandb_login.py index dd57e598..fca70833 100644 --- a/wandb/sdk/wandb_login.py +++ b/wandb/sdk/wandb_login.py @@ -16,6 +16,9 @@ logger = logging.getLogger("wandb") +if wandb.TYPE_CHECKING: # type: ignore + from typing import Dict # noqa: F401 pylint: disable=unused-import + def _validate_anonymous_setting(anon_str): return anon_str in ["must", "allow", "never"] @@ -55,7 +58,7 @@ def _login( wandb.termwarn("Calling wandb.login() after wandb.init() is a no-op.") return True - settings = {} + settings_dict: Dict = {} api = Api() if anonymous is not None: @@ -66,14 +69,20 @@ def _login( "wandb.login(). Can be 'must', 'allow', or 'never'." ) return False - settings.update({"anonymous": anonymous}) + settings_dict.update({"anonymous": anonymous}) + + if key: + settings_dict.update({"api_key": key}) # Note: This won't actually do anything if called from a codepath where # wandb.setup was previously called. If wandb.setup is called further up, # you must make sure the anonymous setting (and any other settings) are # already properly set up there. - wl = wandb.setup() - settings = _settings or wl.settings() + wl = wandb.setup(settings=wandb.Settings(**settings_dict)) + wl_settings = wl.settings() + if _settings: + wl_settings._apply_settings(settings=_settings) + settings = wl_settings if settings._offline: return False diff --git a/wandb/sdk/wandb_setup.py b/wandb/sdk/wandb_setup.py index 1bc34953..fba4edc0 100644 --- a/wandb/sdk/wandb_setup.py +++ b/wandb/sdk/wandb_setup.py @@ -89,7 +89,7 @@ def __init__(self, settings=None, environ=None): _set_logger(self._early_logger) # Have to load viewer before setting up settings. - self._load_viewer() + self._load_viewer(settings=settings) self._settings_setup(settings, self._early_logger) self._settings.freeze() @@ -150,8 +150,8 @@ def _get_entity(self): def _get_user_flags(self): return self._server._flags - def _load_viewer(self): - s = server.Server() + def _load_viewer(self, settings=None): + s = server.Server(settings=settings) s.query_with_timeout() self._server = s # if self.mode != "dryrun" and not self._api.disabled() and self._api.api_key: diff --git a/wandb/sdk_py27/wandb_login.py b/wandb/sdk_py27/wandb_login.py index 541e03c8..fcc0486d 100644 --- a/wandb/sdk_py27/wandb_login.py +++ b/wandb/sdk_py27/wandb_login.py @@ -16,6 +16,9 @@ logger = logging.getLogger("wandb") +if wandb.TYPE_CHECKING: # type: ignore + from typing import Dict # noqa: F401 pylint: disable=unused-import + def _validate_anonymous_setting(anon_str): return anon_str in ["must", "allow", "never"] @@ -55,7 +58,7 @@ def _login( wandb.termwarn("Calling wandb.login() after wandb.init() is a no-op.") return True - settings = {} + settings_dict = {} api = Api() if anonymous is not None: @@ -66,14 +69,20 @@ def _login( "wandb.login(). Can be 'must', 'allow', or 'never'." ) return False - settings.update({"anonymous": anonymous}) + settings_dict.update({"anonymous": anonymous}) + + if key: + settings_dict.update({"api_key": key}) # Note: This won't actually do anything if called from a codepath where # wandb.setup was previously called. If wandb.setup is called further up, # you must make sure the anonymous setting (and any other settings) are # already properly set up there. - wl = wandb.setup() - settings = _settings or wl.settings() + wl = wandb.setup(settings=wandb.Settings(**settings_dict)) + wl_settings = wl.settings() + if _settings: + wl_settings._apply_settings(settings=_settings) + settings = wl_settings if settings._offline: return False diff --git a/wandb/sdk_py27/wandb_setup.py b/wandb/sdk_py27/wandb_setup.py index 5e7456c6..52976ac4 100644 --- a/wandb/sdk_py27/wandb_setup.py +++ b/wandb/sdk_py27/wandb_setup.py @@ -89,7 +89,7 @@ def __init__(self, settings=None, environ=None): _set_logger(self._early_logger) # Have to load viewer before setting up settings. - self._load_viewer() + self._load_viewer(settings=settings) self._settings_setup(settings, self._early_logger) self._settings.freeze() @@ -150,8 +150,8 @@ def _get_entity(self): def _get_user_flags(self): return self._server._flags - def _load_viewer(self): - s = server.Server() + def _load_viewer(self, settings=None): + s = server.Server(settings=settings) s.query_with_timeout() self._server = s # if self.mode != "dryrun" and not self._api.disabled() and self._api.api_key: