Skip to content

Commit 7063371

Browse files
helpful messages when no api token is present both inside and outside PythonAnywhere.
1 parent dda822e commit 7063371

File tree

3 files changed

+84
-44
lines changed

3 files changed

+84
-44
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pytest = "^7.0.0"
2727
pytest-mock = "^3.6.1"
2828
responses = "^0.13.3"
2929

30+
[tool.black]
31+
line-length = 120
32+
3033
[build-system]
3134
requires = ["poetry-core>=1.0.0"]
3235
build-backend = "poetry.core.masonry.api"

pythonanywhere_core/base.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,52 @@
66
from pythonanywhere_core.exceptions import AuthenticationError, NoTokenError
77

88
PYTHON_VERSIONS: Dict[str, str] = {
9-
"3.6": "python36", "3.7": "python37", "3.8": "python38", "3.9": "python39"
9+
"3.6": "python36",
10+
"3.7": "python37",
11+
"3.8": "python38",
12+
"3.9": "python39",
13+
"3.10": "python310",
1014
}
1115

1216

1317
def get_api_endpoint() -> str:
1418
hostname = os.environ.get(
1519
"PYTHONANYWHERE_SITE",
16-
"www." + os.environ.get(
17-
"PYTHONANYWHERE_DOMAIN",
18-
"pythonanywhere.com"
19-
)
20+
"www." + os.environ.get("PYTHONANYWHERE_DOMAIN", "pythonanywhere.com"),
2021
)
2122
return f"https://{hostname}/api/v0/user/{{username}}/{{flavor}}/"
2223

2324

24-
def call_api(url: str, method: str, **kwargs) -> requests.Response:
25-
token = os.environ.get("API_TOKEN")
26-
if token is None:
27-
raise NoTokenError(
25+
def helpful_token_error_message() -> str:
26+
if os.environ.get("PYTHONANYWHERE_SITE"):
27+
return (
2828
"Oops, you don't seem to have an API token. "
2929
"Please go to the 'Account' page on PythonAnywhere, then to the 'API Token' "
3030
"tab. Click the 'Create a new API token' button to create the token, then "
31-
"start a new console and try running this script again."
31+
"start a new console and try running me again."
32+
)
33+
else:
34+
return (
35+
"Oops, you don't seem to have an API_TOKEN environment variable set. "
36+
"Please go to the 'Account' page on PythonAnywhere, then to the 'API Token' "
37+
"tab. Click the 'Create a new API token' button to create the token, then "
38+
"use it to set API_TOKEN environmental variable and try running me again."
3239
)
40+
41+
42+
def call_api(url: str, method: str, **kwargs) -> requests.Response:
43+
token = os.environ.get("API_TOKEN")
44+
if token is None:
45+
raise NoTokenError(helpful_token_error_message())
3346
insecure = os.environ.get("PYTHONANYWHERE_INSECURE_API") == "true"
3447
response = requests.request(
3548
method=method,
3649
url=url,
3750
headers={"Authorization": f"Token {token}"},
3851
verify=not insecure,
39-
**kwargs
52+
**kwargs,
4053
)
4154
if response.status_code == 401:
4255
print(response, response.text)
43-
raise AuthenticationError(
44-
f"Authentication error {response.status_code} calling API: {response.text}"
45-
)
56+
raise AuthenticationError(f"Authentication error {response.status_code} calling API: {response.text}")
4657
return response

tests/test_base.py

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,75 @@
11
import pytest
22
import responses
33

4-
from pythonanywhere_core.base import call_api, get_api_endpoint
5-
from pythonanywhere_core.exceptions import AuthenticationError
4+
from pythonanywhere_core.base import (
5+
call_api,
6+
get_api_endpoint,
7+
helpful_token_error_message,
8+
)
9+
from pythonanywhere_core.exceptions import AuthenticationError, NoTokenError
10+
611

712
@pytest.fixture
813
def mock_requests(mocker):
914
return mocker.patch("pythonanywhere_core.base.requests")
1015

1116

12-
class TestGetAPIEndpoint:
17+
def test_api_endpoint_defaults_to_pythonanywhere_dot_com_if_no_environment_variables():
18+
assert get_api_endpoint() == "https://www.pythonanywhere.com/api/v0/user/{username}/{flavor}/"
19+
20+
21+
def test_gets_domain_from_pythonanywhere_site_and_ignores_pythonanywhere_domain_if_both_set(monkeypatch):
22+
monkeypatch.setenv("PYTHONANYWHERE_SITE", "www.foo.com")
23+
monkeypatch.setenv("PYTHONANYWHERE_DOMAIN", "wibble.com")
24+
25+
assert get_api_endpoint() == "https://www.foo.com/api/v0/user/{username}/{flavor}/"
26+
27+
28+
def test_gets_domain_from_pythonanywhere_domain_and_adds_on_www_if_set_but_no_pythonanywhere_site(monkeypatch):
29+
monkeypatch.setenv("PYTHONANYWHERE_DOMAIN", "foo.com")
30+
31+
assert get_api_endpoint() == "https://www.foo.com/api/v0/user/{username}/{flavor}/"
32+
33+
34+
def test_raises_on_401(api_token, api_responses):
35+
url = "https://foo.com/"
36+
api_responses.add(responses.POST, url, status=401, body="nope")
37+
with pytest.raises(AuthenticationError) as e:
38+
call_api(url, "post")
39+
assert str(e.value) == "Authentication error 401 calling API: nope"
40+
41+
42+
def test_passes_verify_from_environment(api_token, monkeypatch, mock_requests):
43+
monkeypatch.setenv("PYTHONANYWHERE_INSECURE_API", "true")
44+
45+
call_api("url", "post", foo="bar")
46+
47+
_, kwargs = mock_requests.request.call_args
48+
assert kwargs["verify"] is False
49+
50+
51+
def test_verify_is_true_if_env_not_set(api_token, mock_requests):
52+
call_api("url", "post", foo="bar")
1353

14-
def test_defaults_to_pythonanywhere_dot_com_if_no_environment_variables(self):
15-
assert get_api_endpoint() == "https://www.pythonanywhere.com/api/v0/user/{username}/{flavor}/"
54+
_, kwargs = mock_requests.request.call_args
55+
assert kwargs["verify"] is True
1656

17-
def test_gets_domain_from_pythonanywhere_site_and_ignores_pythonanywhere_domain_if_both_set(self, monkeypatch):
18-
monkeypatch.setenv("PYTHONANYWHERE_SITE", "www.foo.com")
19-
monkeypatch.setenv("PYTHONANYWHERE_DOMAIN", "wibble.com")
20-
assert get_api_endpoint() == "https://www.foo.com/api/v0/user/{username}/{flavor}/"
2157

22-
def test_gets_domain_from_pythonanywhere_domain_and_adds_on_www_if_set_but_no_pythonanywhere_site(
23-
self, monkeypatch
24-
):
25-
monkeypatch.setenv("PYTHONANYWHERE_DOMAIN", "foo.com")
26-
assert get_api_endpoint() == "https://www.foo.com/api/v0/user/{username}/{flavor}/"
58+
def test_raises_with_helpful_message_if_no_token_present(mocker):
59+
mock_helpful_message = mocker.patch("pythonanywhere_core.base.helpful_token_error_message")
60+
mock_helpful_message.return_value = "I'm so helpful"
2761

62+
with pytest.raises(NoTokenError) as exc:
63+
call_api("blah", "get")
2864

29-
class TestCallAPI:
30-
def test_raises_on_401(self, api_token, api_responses):
31-
url = "https://foo.com/"
32-
api_responses.add(responses.POST, url, status=401, body="nope")
33-
with pytest.raises(AuthenticationError) as e:
34-
call_api(url, "post")
35-
assert str(e.value) == "Authentication error 401 calling API: nope"
65+
assert str(exc.value) == "I'm so helpful"
3666

37-
def test_passes_verify_from_environment(self, api_token, monkeypatch, mock_requests):
38-
monkeypatch.setenv("PYTHONANYWHERE_INSECURE_API", "true")
3967

40-
call_api("url", "post", foo="bar")
68+
def test_helpful_message_inside_pythonanywhere(monkeypatch):
69+
monkeypatch.setenv("PYTHONANYWHERE_SITE", "www.foo.com")
4170

42-
args, kwargs = mock_requests.request.call_args
43-
assert kwargs["verify"] is False
71+
assert "Oops, you don't seem to have an API token." in helpful_token_error_message()
4472

45-
def test_verify_is_true_if_env_not_set(self, api_token, mock_requests):
46-
call_api("url", "post", foo="bar")
4773

48-
args, kwargs = mock_requests.request.call_args
49-
assert kwargs["verify"] is True
74+
def test_helpful_message_outside_pythonanywhere():
75+
assert "Oops, you don't seem to have an API_TOKEN environment variable set." in helpful_token_error_message()

0 commit comments

Comments
 (0)