Repository: wandb/wandb

Issue:
[CLI] init fails
`wandb --version && python --version && uname`
wandb, version 0.10.15
Python 3.8.7
Linux

**Description**
Just trying to simply run wandb.init().  
I created account and copied API_KEY.
I install wandb and login from shell:
```
pip install --upgrade wandb
wandb login <API_KEY>
```
Then run python script:
```
import wandb
wandb.init(project="test")
```
And I keep getting this error.  I've tried repeatedly deleting .netrc file.... does not help....
```
Traceback (most recent call last):
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/apis/normalize.py", line 24, in wrapper
    return func(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/sdk/internal/internal_api.py", line 382, in viewer
    res = self.gql(query)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/old/retry.py", line 96, in __call__
    result = self._call_fn(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/sdk/internal/internal_api.py", line 131, in execute
    return self.client.execute(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/client.py", line 52, in execute
    result = self._get_result(document, *args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/client.py", line 60, in _get_result
    return self.transport.execute(document, *args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/transport/requests.py", line 41, in execute
    result = request.json()
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/requests/models.py", line 900, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/cli/cli.py", line 94, in wrapper
    return func(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/cli/cli.py", line 331, in init
    viewer = api.viewer()
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/apis/internal.py", line 45, in viewer
    return self.api.viewer()
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/apis/normalize.py", line 62, in wrapper
    six.reraise(CommError, CommError(message, err), sys.exc_info()[2])
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/six.py", line 702, in reraise
    raise value.with_traceback(tb)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/apis/normalize.py", line 24, in wrapper
    return func(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/sdk/internal/internal_api.py", line 382, in viewer
    res = self.gql(query)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/old/retry.py", line 96, in __call__
    result = self._call_fn(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/sdk/internal/internal_api.py", line 131, in execute
    return self.client.execute(*args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/client.py", line 52, in execute
    result = self._get_result(document, *args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/client.py", line 60, in _get_result
    return self.transport.execute(document, *args, **kwargs)
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/wandb/vendor/gql-0.2.0/gql/transport/requests.py", line 41, in execute
    result = request.json()
  File "/home/david/code/repo/ai2/test/venv/lib/python3.8/site-packages/requests/models.py", line 900, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
wandb.errors.error.CommError: Expecting value: line 1 column 1 (char 0)
```



Discussion:
Can you share the tail of your `wandb/debug-internal.log`?  Have you ever logged that machine into a self-hosted instance of wandb?  If so, you could try `wandb login --cloud` to ensure it's communicating with our cloud servers instead of a potentially non-existent local server.
Thanks @vanpelt --  that solved it! 
In fact I _had_ logged into a self-hosted instance previously. 
`wandb login --cloud` resolved the error.
I also ran into this and I think we should reopen to give clearer guidance or ideally not force the user to call wandb login --cloud.

Resolving patch:
diff --git a/wandb/cli/cli.py b/wandb/cli/cli.py
--- a/wandb/cli/cli.py
+++ b/wandb/cli/cli.py
@@ -236,14 +236,19 @@ def login(key, host, cloud, relogin, anonymously, no_offline=False):
     if key:
         relogin = True
 
-    wandb.setup(
-        settings=wandb.Settings(
-            _cli_only_mode=True,
-            _disable_viewer=relogin,
-            anonymous=anon_mode,
-            base_url=host,
+    try:
+        wandb.setup(
+            settings=wandb.Settings(
+                _cli_only_mode=True,
+                _disable_viewer=relogin,
+                anonymous=anon_mode,
+                base_url=host,
+            )
         )
-    )
+    except TypeError as e:
+        wandb.termerror(str(e))
+        sys.exit(1)
+
     wandb.login(relogin=relogin, key=key, anonymous=anon_mode, host=host, force=True)
 
 
diff --git a/wandb/sdk/wandb_settings.py b/wandb/sdk/wandb_settings.py
--- a/wandb/sdk/wandb_settings.py
+++ b/wandb/sdk/wandb_settings.py
@@ -32,6 +32,7 @@
 import multiprocessing
 import os
 import platform
+import re
 import socket
 import sys
 import tempfile
@@ -645,6 +646,17 @@ def _validate_show_errors(self, value: str) -> Optional[str]:
             return "{} is not a boolean".format(value)
         return None
 
+    def _validate_base_url(self, value: Optional[str]) -> Optional[str]:
+        if value is not None:
+            if re.match(r".*wandb\.ai[^\.]*$", value) and "api." not in value:
+                # user might guess app.wandb.ai or wandb.ai is the default cloud server
+                return "{} is not a valid server address, did you mean https://api.wandb.ai?".format(
+                    value
+                )
+            elif re.match(r".*wandb\.ai[^\.]*$", value) and "http://" in value:
+                return "http is not secure, please use https://api.wandb.ai"
+        return None
+
     def _preprocess_base_url(self, value: Optional[str]) -> Optional[str]:
         if value is not None:
             value = value.rstrip("/")
diff --git a/wandb/sdk_py27/wandb_settings.py b/wandb/sdk_py27/wandb_settings.py
--- a/wandb/sdk_py27/wandb_settings.py
+++ b/wandb/sdk_py27/wandb_settings.py
@@ -32,6 +32,7 @@
 import multiprocessing
 import os
 import platform
+import re
 import socket
 import sys
 import tempfile
@@ -645,6 +646,17 @@ def _validate_show_errors(self, value):
             return "{} is not a boolean".format(value)
         return None
 
+    def _validate_base_url(self, value):
+        if value is not None:
+            if re.match(r".*wandb\.ai[^\.]*$", value) and "api." not in value:
+                # user might guess app.wandb.ai or wandb.ai is the default cloud server
+                return "{} is not a valid server address, did you mean https://api.wandb.ai?".format(
+                    value
+                )
+            elif re.match(r".*wandb\.ai[^\.]*$", value) and "http://" in value:
+                return "http is not secure, please use https://api.wandb.ai"
+        return None
+
     def _preprocess_base_url(self, value):
         if value is not None:
             value = value.rstrip("/")


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -210,6 +210,13 @@ def test_login_host_trailing_slash_fix_invalid(runner, empty_netrc, local_netrc)
         )
 
 
+def test_login_bad_host(runner, empty_netrc, local_netrc):
+    with runner.isolated_filesystem():
+        result = runner.invoke(cli.login, ["--host", "https://app.wandb.ai"])
+        assert "did you mean https://api.wandb.ai" in result.output
+        assert result.exit_code != 0
+
+
 def test_login_onprem_key_arg(runner, empty_netrc, local_netrc):
     onprem_key = "test-" + DUMMY_API_KEY
     with runner.isolated_filesystem():
diff --git a/tests/wandb_settings_test.py b/tests/wandb_settings_test.py
--- a/tests/wandb_settings_test.py
+++ b/tests/wandb_settings_test.py
@@ -227,6 +227,20 @@ def test_prio_context_over_ignore():
     assert s.project == "pizza"
 
 
+def test_validate_base_url():
+    s = Settings()
+    with pytest.raises(TypeError):
+        s.update(base_url="https://wandb.ai")
+    with pytest.raises(TypeError):
+        s.update(base_url="https://app.wandb.ai")
+    with pytest.raises(TypeError):
+        s.update(base_url="http://api.wandb.ai")
+    s.update(base_url="https://api.wandb.ai")
+    assert s.base_url == "https://api.wandb.ai"
+    s.update(base_url="https://wandb.ai.other.crazy.domain.com")
+    assert s.base_url == "https://wandb.ai.other.crazy.domain.com"
+
+
 def test_preprocess_base_url():
     s = Settings()
     s.update(base_url="http://host.com")