Skip to content

Commit b2a21fe

Browse files
Refactors api to separate base from webapps
1 parent 30534ab commit b2a21fe

File tree

8 files changed

+90
-71
lines changed

8 files changed

+90
-71
lines changed

pythonanywhere/api/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import warnings
2+
3+
from pythonanywhere.api.base import AuthenticationError, PYTHON_VERSIONS, call_api, get_api_endpoint
4+
from pythonanywhere.api.webapp import Webapp
5+
6+
warnings.warn(
7+
"""
8+
Importing from pythonanywhere.api is deprecated in favor of
9+
pythonanywhere.api.base for call_api and get_api_endpoint
10+
and pythonanywhere.api.webapp for Webapp
11+
""",
12+
DeprecationWarning
13+
)

pythonanywhere/api/base.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os
2+
3+
import requests
4+
5+
PYTHON_VERSIONS = {
6+
"2.7": "python27", "3.4": "python34", "3.5": "python35", "3.6": "python36", "3.7": "python37", "3.8": "python38",
7+
}
8+
9+
10+
class AuthenticationError(Exception):
11+
pass
12+
13+
14+
class NoTokenError(Exception):
15+
pass
16+
17+
18+
def get_api_endpoint():
19+
hostname = os.environ.get(
20+
"PYTHONANYWHERE_SITE",
21+
"www." + os.environ.get(
22+
"PYTHONANYWHERE_DOMAIN",
23+
"pythonanywhere.com"
24+
)
25+
)
26+
return "https://{hostname}/api/v0/user/{{username}}/{{flavor}}/".format(hostname=hostname)
27+
28+
29+
def call_api(url, method, **kwargs):
30+
token = os.environ.get("API_TOKEN")
31+
if token is None:
32+
raise NoTokenError(
33+
"Oops, you don't seem to have an API token. "
34+
"Please go to the 'Account' page on PythonAnywhere, then to the 'API Token' "
35+
"tab. Click the 'Create a new API token' button to create the token, then "
36+
"start a new console and try running this script again."
37+
)
38+
insecure = os.environ.get("PYTHONANYWHERE_INSECURE_API") == "true"
39+
response = requests.request(
40+
method=method,
41+
url=url,
42+
headers={"Authorization": "Token {token}".format(token=token)},
43+
verify=not insecure,
44+
**kwargs
45+
)
46+
if response.status_code == 401:
47+
print(response, response.text)
48+
raise AuthenticationError(
49+
"Authentication error {status_code} calling API: {response_text}".format(
50+
status_code=response.status_code, response_text=response.text
51+
)
52+
)
53+
return response
54+
55+

pythonanywhere/api/base.pyi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Dict
2+
3+
from requests import Response
4+
5+
PYTHON_VERSIONS: Dict[str, str] = ...
6+
7+
class AuthenticationError(Exception): ...
8+
class NoTokenError(Exception): ...
9+
10+
def get_api_endpoint() -> str: ...
11+
def call_api(url: str, method: str, **kwargs) -> Response: ...
12+
13+
Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,11 @@
44
from textwrap import dedent
55

66
from dateutil.parser import parse
7-
import requests
87

8+
from pythonanywhere.api.base import PYTHON_VERSIONS, call_api, get_api_endpoint
99
from pythonanywhere.exceptions import SanityException
1010
from pythonanywhere.snakesay import snakesay
1111

12-
PYTHON_VERSIONS = {
13-
"2.7": "python27", "3.4": "python34", "3.5": "python35", "3.6": "python36", "3.7": "python37", "3.8": "python38",
14-
}
15-
16-
17-
class AuthenticationError(Exception):
18-
pass
19-
20-
21-
class NoTokenError(Exception):
22-
pass
23-
24-
25-
def get_api_endpoint():
26-
hostname = os.environ.get(
27-
"PYTHONANYWHERE_SITE",
28-
"www." + os.environ.get(
29-
"PYTHONANYWHERE_DOMAIN",
30-
"pythonanywhere.com"
31-
)
32-
)
33-
return "https://{hostname}/api/v0/user/{{username}}/{{flavor}}/".format(hostname=hostname)
34-
35-
36-
def call_api(url, method, **kwargs):
37-
token = os.environ.get("API_TOKEN")
38-
if token is None:
39-
raise NoTokenError(
40-
"Oops, you don't seem to have an API token. "
41-
"Please go to the 'Account' page on PythonAnywhere, then to the 'API Token' "
42-
"tab. Click the 'Create a new API token' button to create the token, then "
43-
"start a new console and try running this script again."
44-
)
45-
insecure = os.environ.get("PYTHONANYWHERE_INSECURE_API") == "true"
46-
response = requests.request(
47-
method=method,
48-
url=url,
49-
headers={"Authorization": "Token {token}".format(token=token)},
50-
verify=not insecure,
51-
**kwargs
52-
)
53-
if response.status_code == 401:
54-
print(response, response.text)
55-
raise AuthenticationError(
56-
"Authentication error {status_code} calling API: {response_text}".format(
57-
status_code=response.status_code, response_text=response.text
58-
)
59-
)
60-
return response
61-
6212

6313
class Webapp:
6414
def __init__(self, domain):
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
from pathlib import Path
22
from typing import Dict, List, Any
33

4-
from requests import Response
5-
6-
PYTHON_VERSIONS: Dict[str, str] = ...
7-
8-
class AuthenticationError(Exception): ...
9-
class NoTokenError(Exception): ...
10-
11-
def get_api_endpoint() -> str: ...
12-
def call_api(url: str, method: str, **kwargs) -> Response: ...
13-
144
class Webapp:
155
domain: str = ...
166
def __init__(self, domain: str) -> None: ...
@@ -22,4 +12,4 @@ class Webapp:
2212
def set_ssl(self, certificate: str, private_key: str) -> None: ...
2313
def get_ssl_info(self) -> Dict[str, Any]: ...
2414
def delete_log(self, log_type: str, index: int = 0) -> None: ...
25-
def get_log_info(self) -> Dict[str, List[int]]: ...
15+
def get_log_info(self) -> Dict[str, List[int]]: ...

tests/test_api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from dateutil.tz import tzutc
88
import pytest
99
import responses
10-
from pythonanywhere.api import PYTHON_VERSIONS, AuthenticationError, Webapp, call_api, get_api_endpoint
10+
from pythonanywhere.api.base import PYTHON_VERSIONS, AuthenticationError, call_api, get_api_endpoint
11+
from pythonanywhere.api.webapp import Webapp
1112
from pythonanywhere.exceptions import SanityException
1213

1314

@@ -38,13 +39,13 @@ def test_raises_on_401(self, api_token, api_responses):
3839

3940
def test_passes_verify_from_environment(self, api_token, monkeypatch):
4041
monkeypatch.setenv("PYTHONANYWHERE_INSECURE_API", "true")
41-
with patch("pythonanywhere.api.requests") as mock_requests:
42+
with patch("pythonanywhere.api.base.requests") as mock_requests:
4243
call_api("url", "post", foo="bar")
4344
args, kwargs = mock_requests.request.call_args
4445
assert kwargs["verify"] is False
4546

4647
def test_verify_is_true_if_env_not_set(self, api_token):
47-
with patch("pythonanywhere.api.requests") as mock_requests:
48+
with patch("pythonanywhere.api.base.requests") as mock_requests:
4849
call_api("url", "post", foo="bar")
4950
args, kwargs = mock_requests.request.call_args
5051
assert kwargs["verify"] is True

tests/test_pa_autoconfigure_django.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from unittest.mock import call, patch
2-
import getpass
32
import os
43
import pytest
54
import subprocess
@@ -9,7 +8,6 @@
98
from scripts.pa_autoconfigure_django import main
109

1110

12-
1311
class TestMain:
1412

1513
def test_calls_all_stuff_in_right_order(self):
@@ -39,7 +37,7 @@ def test_actually_works_against_example_repo(
3937
domain = 'mydomain.com'
4038
with patch('scripts.pa_autoconfigure_django.DjangoProject.update_wsgi_file'):
4139
with patch('scripts.pa_autoconfigure_django.DjangoProject.start_bash'):
42-
with patch('pythonanywhere.api.call_api'):
40+
with patch('pythonanywhere.api.webapp.call_api'):
4341
main(repo, domain, '2.7', nuke=False)
4442

4543
expected_django_version = '1.11.1'

tests/test_pa_start_django_webapp_with_virtualenv.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import getpass
21
import os
32
import subprocess
43
from platform import python_version
@@ -32,7 +31,7 @@ def test_actually_creates_django_project_in_virtualenv_with_hacked_settings_and_
3231
):
3332

3433
with patch("scripts.pa_start_django_webapp_with_virtualenv.DjangoProject.update_wsgi_file"):
35-
with patch("pythonanywhere.api.call_api"):
34+
with patch("pythonanywhere.api.webapp.call_api"):
3635
main("mydomain.com", "1.9.2", "2.7", nuke=False)
3736

3837
django_version = (
@@ -58,7 +57,7 @@ def test_actually_creates_django_project_in_virtualenv_with_hacked_settings_and_
5857
def test_nuke_option_lets_you_run_twice(self, fake_home, virtualenvs_folder, api_token):
5958

6059
with patch("scripts.pa_start_django_webapp_with_virtualenv.DjangoProject.update_wsgi_file"):
61-
with patch("pythonanywhere.api.call_api"):
60+
with patch("pythonanywhere.api.webapp.call_api"):
6261
running_python_version = ".".join(python_version().split(".")[:2])
6362
if running_python_version[0] == 2:
6463
old_django_version = "1.9.2"

0 commit comments

Comments
 (0)