pythonanywhere-core 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonanywhere_core/__init__.py +1 -1
- pythonanywhere_core/base.py +0 -1
- pythonanywhere_core/files.py +11 -6
- pythonanywhere_core/schedule.py +20 -14
- pythonanywhere_core/webapp.py +19 -4
- pythonanywhere_core/website.py +25 -5
- {pythonanywhere_core-0.3.1.dist-info → pythonanywhere_core-0.3.3.dist-info}/METADATA +1 -1
- pythonanywhere_core-0.3.3.dist-info/RECORD +13 -0
- pythonanywhere_core-0.3.1.dist-info/RECORD +0 -13
- {pythonanywhere_core-0.3.1.dist-info → pythonanywhere_core-0.3.3.dist-info}/WHEEL +0 -0
- {pythonanywhere_core-0.3.1.dist-info → pythonanywhere_core-0.3.3.dist-info}/licenses/LICENSE +0 -0
pythonanywhere_core/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.3"
|
pythonanywhere_core/base.py
CHANGED
|
@@ -96,6 +96,5 @@ def call_api(url: str, method: str, **kwargs) -> requests.Response:
|
|
|
96
96
|
**kwargs,
|
|
97
97
|
)
|
|
98
98
|
if response.status_code == 401:
|
|
99
|
-
print(response, response.text)
|
|
100
99
|
raise AuthenticationError(f"Authentication error {response.status_code} calling API: {response.text}")
|
|
101
100
|
return response
|
pythonanywhere_core/files.py
CHANGED
|
@@ -117,15 +117,16 @@ class Files:
|
|
|
117
117
|
"""Starts sharing a file at `path`.
|
|
118
118
|
|
|
119
119
|
Returns a tuple with a message and sharing link on
|
|
120
|
-
|
|
121
|
-
"was already shared"
|
|
120
|
+
HTTP 200/201, raises otherwise. Message is "successfully shared" on 201,
|
|
121
|
+
"was already shared" on 200."""
|
|
122
122
|
|
|
123
123
|
url = self.sharing_endpoint
|
|
124
124
|
|
|
125
125
|
result = call_api(url, "POST", json={"path": path})
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
messages = {200: "was already shared", 201: "successfully shared"}
|
|
128
|
+
if result.status_code in messages:
|
|
129
|
+
msg = messages[result.status_code]
|
|
129
130
|
sharing_url_suffix = result.json()["url"]
|
|
130
131
|
return msg, self._make_sharing_url(sharing_url_suffix)
|
|
131
132
|
|
|
@@ -137,7 +138,7 @@ class Files:
|
|
|
137
138
|
"""Checks sharing status for a `path`.
|
|
138
139
|
|
|
139
140
|
Returns url with sharing link if file is shared or an empty
|
|
140
|
-
string
|
|
141
|
+
string on HTTP 404 (not shared). Raises on other API errors."""
|
|
141
142
|
|
|
142
143
|
url = f"{self.sharing_endpoint}?path={path}"
|
|
143
144
|
|
|
@@ -145,9 +146,13 @@ class Files:
|
|
|
145
146
|
if result.ok:
|
|
146
147
|
sharing_url_suffix = result.json()["url"]
|
|
147
148
|
return self._make_sharing_url(sharing_url_suffix)
|
|
148
|
-
|
|
149
|
+
if result.status_code == 404:
|
|
149
150
|
return ""
|
|
150
151
|
|
|
152
|
+
raise PythonAnywhereApiException(
|
|
153
|
+
f"GET sharing status for {url} failed, got {result}{self._error_msg(result)}"
|
|
154
|
+
)
|
|
155
|
+
|
|
151
156
|
def sharing_delete(self, path: str) -> int:
|
|
152
157
|
"""Stops sharing file at `path`.
|
|
153
158
|
|
pythonanywhere_core/schedule.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List
|
|
2
2
|
|
|
3
3
|
from typing_extensions import Literal
|
|
4
4
|
|
|
@@ -29,30 +29,31 @@ class Schedule:
|
|
|
29
29
|
|
|
30
30
|
base_url: str = get_api_endpoint(username=get_username(), flavor="schedule")
|
|
31
31
|
|
|
32
|
-
def create(self, params: dict) ->
|
|
32
|
+
def create(self, params: dict) -> dict:
|
|
33
33
|
"""Creates new scheduled task using `params`.
|
|
34
34
|
|
|
35
35
|
Params should be: command, enabled (True or False), interval (daily or
|
|
36
36
|
hourly), hour (24h format) and minute.
|
|
37
37
|
|
|
38
38
|
:param params: dictionary with required scheduled task specs
|
|
39
|
-
:returns: dictionary with created task specs
|
|
39
|
+
:returns: dictionary with created task specs on HTTP 201
|
|
40
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 201"""
|
|
40
41
|
|
|
41
42
|
result = call_api(self.base_url, "POST", json=params)
|
|
42
43
|
|
|
43
44
|
if result.status_code == 201:
|
|
44
45
|
return result.json()
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
47
|
+
raise PythonAnywhereApiException(
|
|
48
|
+
f"POST to set new task via API failed, got {result}: {result.text}"
|
|
49
|
+
)
|
|
50
50
|
|
|
51
51
|
def delete(self, task_id: int) -> Literal[True]:
|
|
52
52
|
"""Deletes scheduled task by id.
|
|
53
53
|
|
|
54
54
|
:param task_id: scheduled task to be deleted id number
|
|
55
|
-
:returns: True when API response is 204
|
|
55
|
+
:returns: True when API response is 204
|
|
56
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 204"""
|
|
56
57
|
|
|
57
58
|
result = call_api(
|
|
58
59
|
f"{self.base_url}{task_id}/", "DELETE"
|
|
@@ -61,17 +62,22 @@ class Schedule:
|
|
|
61
62
|
if result.status_code == 204:
|
|
62
63
|
return True
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
65
|
+
raise PythonAnywhereApiException(
|
|
66
|
+
f"DELETE via API on task {task_id} failed, got {result}: {result.text}"
|
|
67
|
+
)
|
|
68
68
|
|
|
69
69
|
def get_list(self) -> List[dict]:
|
|
70
70
|
"""Gets list of existing scheduled tasks.
|
|
71
71
|
|
|
72
|
-
:returns: list of existing scheduled tasks specs
|
|
72
|
+
:returns: list of existing scheduled tasks specs on HTTP 200
|
|
73
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 200"""
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
result = call_api(self.base_url, "GET")
|
|
76
|
+
if result.status_code != 200:
|
|
77
|
+
raise PythonAnywhereApiException(
|
|
78
|
+
f"GET scheduled tasks via API failed, got {result}: {result.text}"
|
|
79
|
+
)
|
|
80
|
+
return result.json()
|
|
75
81
|
|
|
76
82
|
def get_specs(self, task_id: int) -> dict:
|
|
77
83
|
"""Get task specs by id.
|
pythonanywhere_core/webapp.py
CHANGED
|
@@ -51,6 +51,7 @@ class Webapp:
|
|
|
51
51
|
:param nuke: if True, skip the check for existing webapp
|
|
52
52
|
|
|
53
53
|
:raises SanityException: if API token is missing or webapp already exists
|
|
54
|
+
:raises PythonAnywhereApiException: if the existence check returns neither 200 nor 404
|
|
54
55
|
"""
|
|
55
56
|
token = os.environ.get("API_TOKEN")
|
|
56
57
|
if not token:
|
|
@@ -73,18 +74,28 @@ class Webapp:
|
|
|
73
74
|
f"You already have a webapp for {self.domain}.\n\nUse the --nuke option if you want to replace it."
|
|
74
75
|
)
|
|
75
76
|
|
|
77
|
+
if response.status_code != 404:
|
|
78
|
+
raise PythonAnywhereApiException(
|
|
79
|
+
f"GET to check webapp existence via API failed, got {response}:{response.text}"
|
|
80
|
+
)
|
|
81
|
+
|
|
76
82
|
def create(self, python_version: str, virtualenv_path: Path | None, project_path: Path, nuke: bool) -> None:
|
|
77
83
|
"""Create a webapp for the given domain, using the given python version and virtualenv path
|
|
78
84
|
|
|
79
85
|
:param python_version: python version to use
|
|
80
86
|
:param virtualenv_path: path to the virtualenv, or None to skip setting it
|
|
81
87
|
:param project_path: path to the project
|
|
82
|
-
:param nuke: if True, delete any existing webapp for this domain
|
|
88
|
+
:param nuke: if True, delete any existing webapp for this domain;
|
|
89
|
+
continue only on HTTP 204 (deleted) or 404 (absent)
|
|
83
90
|
|
|
84
91
|
:raises PythonAnywhereApiException: if API call fails
|
|
85
92
|
"""
|
|
86
93
|
if nuke:
|
|
87
|
-
call_api(self.domain_url, "delete")
|
|
94
|
+
response = call_api(self.domain_url, "delete")
|
|
95
|
+
if response.status_code not in (204, 404):
|
|
96
|
+
raise PythonAnywhereApiException(
|
|
97
|
+
f"DELETE before replacing webapp via API failed, got {response}:{response.text}"
|
|
98
|
+
)
|
|
88
99
|
response = call_api(
|
|
89
100
|
self.webapps_url,
|
|
90
101
|
"post",
|
|
@@ -106,10 +117,14 @@ class Webapp:
|
|
|
106
117
|
:param url_path: URL path (e.g., '/static/')
|
|
107
118
|
:param directory_path: Filesystem path to serve (as Path)
|
|
108
119
|
|
|
109
|
-
:raises PythonAnywhereApiException: if
|
|
120
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 201
|
|
110
121
|
"""
|
|
111
122
|
url = f"{self.domain_url}static_files/"
|
|
112
|
-
call_api(url, "post", json=dict(url=url_path, path=str(directory_path)))
|
|
123
|
+
response = call_api(url, "post", json=dict(url=url_path, path=str(directory_path)))
|
|
124
|
+
if response.status_code != 201:
|
|
125
|
+
raise PythonAnywhereApiException(
|
|
126
|
+
f"POST to create static file mapping via API failed, got {response}:{response.text}"
|
|
127
|
+
)
|
|
113
128
|
|
|
114
129
|
def add_default_static_files_mappings(self, project_path: Path) -> None:
|
|
115
130
|
"""Add default static files mappings for /static/ and /media/.
|
pythonanywhere_core/website.py
CHANGED
|
@@ -52,33 +52,48 @@ class Website:
|
|
|
52
52
|
def get(self, domain_name: str) -> dict:
|
|
53
53
|
"""Returns dictionary with website info for ``domain_name``.
|
|
54
54
|
:param domain_name:
|
|
55
|
-
:return: dictionary with website info
|
|
55
|
+
:return: dictionary with website info on HTTP 200
|
|
56
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 200"""
|
|
56
57
|
|
|
57
58
|
response = call_api(
|
|
58
59
|
f"{self.websites_base_url}{domain_name}/",
|
|
59
60
|
"get",
|
|
60
61
|
)
|
|
62
|
+
if response.status_code != 200:
|
|
63
|
+
raise PythonAnywhereApiException(
|
|
64
|
+
f"GET website via API failed, got {response}:{response.text}"
|
|
65
|
+
)
|
|
61
66
|
return response.json()
|
|
62
67
|
|
|
63
68
|
def list(self) -> list:
|
|
64
69
|
"""Returns list of dictionaries with all websites info.
|
|
65
|
-
:return: list of dictionaries with websites info
|
|
70
|
+
:return: list of dictionaries with websites info on HTTP 200
|
|
71
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 200"""
|
|
66
72
|
|
|
67
73
|
response = call_api(
|
|
68
74
|
self.websites_base_url,
|
|
69
75
|
"get",
|
|
70
76
|
)
|
|
77
|
+
if response.status_code != 200:
|
|
78
|
+
raise PythonAnywhereApiException(
|
|
79
|
+
f"GET websites via API failed, got {response}:{response.text}"
|
|
80
|
+
)
|
|
71
81
|
return response.json()
|
|
72
82
|
|
|
73
83
|
def reload(self, domain_name: str) -> dict:
|
|
74
84
|
"""Reloads website with ``domain_name``.
|
|
75
85
|
:param domain_name: domain name for website to reload
|
|
76
|
-
:return: dictionary with response
|
|
86
|
+
:return: dictionary with response on HTTP 200
|
|
87
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 200"""
|
|
77
88
|
|
|
78
89
|
response = call_api(
|
|
79
90
|
f"{self.websites_base_url}{domain_name}/reload/",
|
|
80
91
|
"post",
|
|
81
92
|
)
|
|
93
|
+
if response.status_code != 200:
|
|
94
|
+
raise PythonAnywhereApiException(
|
|
95
|
+
f"POST to reload website via API failed, got {response}:{response.text}"
|
|
96
|
+
)
|
|
82
97
|
return response.json()
|
|
83
98
|
|
|
84
99
|
def auto_ssl(self, domain_name: str) -> dict:
|
|
@@ -110,10 +125,15 @@ class Website:
|
|
|
110
125
|
def delete(self, domain_name: str) -> dict:
|
|
111
126
|
"""Deletes website with ``domain_name``.
|
|
112
127
|
:param domain_name: domain name for website to delete
|
|
113
|
-
:return: empty dictionary
|
|
128
|
+
:return: empty dictionary on HTTP 204
|
|
129
|
+
:raises PythonAnywhereApiException: if the response is not HTTP 204"""
|
|
114
130
|
|
|
115
|
-
call_api(
|
|
131
|
+
response = call_api(
|
|
116
132
|
f"{self.websites_base_url}{domain_name}/",
|
|
117
133
|
"delete",
|
|
118
134
|
)
|
|
135
|
+
if response.status_code != 204:
|
|
136
|
+
raise PythonAnywhereApiException(
|
|
137
|
+
f"DELETE website via API failed, got {response}:{response.text}"
|
|
138
|
+
)
|
|
119
139
|
return {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
pythonanywhere_core/__init__.py,sha256=8KcCYTXH99C2-gCLuPILJvtT9YftRWJsartIx6TQ2ZY,22
|
|
2
|
+
pythonanywhere_core/base.py,sha256=nyyX53D0O5o_1a9rHhJDHUM24wlCr0SS544ZZbXbOYw,3415
|
|
3
|
+
pythonanywhere_core/exceptions.py,sha256=37aZy6txVFuiXfUdRJA5d7NQ1N5vbkdOveqv8rwnDbk,691
|
|
4
|
+
pythonanywhere_core/files.py,sha256=vksw1ZEPvCn9Hv1YrRhc1fea72cuzUzzeyr67u9CvKM,7765
|
|
5
|
+
pythonanywhere_core/resources.py,sha256=VBXspr1sGhfAVSFoTQtwpeN5CDG9-wHZwXJ3-5vy7dY,1167
|
|
6
|
+
pythonanywhere_core/schedule.py,sha256=9YHKvSql9p6VtLGLn7IqXzmdtg2U9ZG287hAcv8Je5g,4261
|
|
7
|
+
pythonanywhere_core/students.py,sha256=VfdftRrwPFEb-m2LW7pbiS9LiffwFNTfHpVeLXMQLLc,1774
|
|
8
|
+
pythonanywhere_core/webapp.py,sha256=dniwSCxogsKuqHI25TyVpk5vTxusrqgJjR4coaq9CYE,12126
|
|
9
|
+
pythonanywhere_core/website.py,sha256=yAosA61uQ7dEIsqX5qUFzNZflWDevMiS25FzNfXeArc,5625
|
|
10
|
+
pythonanywhere_core-0.3.3.dist-info/METADATA,sha256=Wd3dJ_-lZlurXDit_oMnvIr2xUM-nXM-DO_4hWaqNeM,1931
|
|
11
|
+
pythonanywhere_core-0.3.3.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
|
|
12
|
+
pythonanywhere_core-0.3.3.dist-info/licenses/LICENSE,sha256=zOJT_E5Fv8crFEKS5ulv0btbjnUiIkdsEBPEBmLHRlQ,1080
|
|
13
|
+
pythonanywhere_core-0.3.3.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
pythonanywhere_core/__init__.py,sha256=r4xAFihOf72W9TD-lpMi6ntWSTKTP2SlzKP1ytkjRbI,22
|
|
2
|
-
pythonanywhere_core/base.py,sha256=l4GiEa-oe-EyAc_knX9kqmzQsPEIAH3ec1CivkWeK3Q,3454
|
|
3
|
-
pythonanywhere_core/exceptions.py,sha256=37aZy6txVFuiXfUdRJA5d7NQ1N5vbkdOveqv8rwnDbk,691
|
|
4
|
-
pythonanywhere_core/files.py,sha256=XjOsxei_tDgrubUVMmH4iYgc4sTmoFNk5EtZMJR3-0Q,7531
|
|
5
|
-
pythonanywhere_core/resources.py,sha256=VBXspr1sGhfAVSFoTQtwpeN5CDG9-wHZwXJ3-5vy7dY,1167
|
|
6
|
-
pythonanywhere_core/schedule.py,sha256=payqSYJf92gQzsZwVBtccXH4m_C--AAbdbf5DOtImhs,3900
|
|
7
|
-
pythonanywhere_core/students.py,sha256=VfdftRrwPFEb-m2LW7pbiS9LiffwFNTfHpVeLXMQLLc,1774
|
|
8
|
-
pythonanywhere_core/webapp.py,sha256=inwCDOHY_7IVQXJTeAxucn5vYPpJNdn-JLM3ji5gHwQ,11308
|
|
9
|
-
pythonanywhere_core/website.py,sha256=RnMf97CjfLnDF2aFiH_EJcuChBrGhZ7DMErWkpmXqHM,4535
|
|
10
|
-
pythonanywhere_core-0.3.1.dist-info/METADATA,sha256=wsbVvuF57oHtgGdHNuQM01HgX9GCgfM3B4i0Dvt0c2Y,1931
|
|
11
|
-
pythonanywhere_core-0.3.1.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
|
|
12
|
-
pythonanywhere_core-0.3.1.dist-info/licenses/LICENSE,sha256=zOJT_E5Fv8crFEKS5ulv0btbjnUiIkdsEBPEBmLHRlQ,1080
|
|
13
|
-
pythonanywhere_core-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
{pythonanywhere_core-0.3.1.dist-info → pythonanywhere_core-0.3.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|