pythonanywhere-core 0.2.4__py3-none-any.whl → 0.2.6__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/webapp.py +110 -16
- {pythonanywhere_core-0.2.4.dist-info → pythonanywhere_core-0.2.6.dist-info}/METADATA +3 -5
- {pythonanywhere_core-0.2.4.dist-info → pythonanywhere_core-0.2.6.dist-info}/RECORD +6 -6
- {pythonanywhere_core-0.2.4.dist-info → pythonanywhere_core-0.2.6.dist-info}/WHEEL +1 -1
- {pythonanywhere_core-0.2.4.dist-info → pythonanywhere_core-0.2.6.dist-info}/LICENSE +0 -0
pythonanywhere_core/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
1
|
+
__version__ = "0.2.6"
|
pythonanywhere_core/webapp.py
CHANGED
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import getpass
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from textwrap import dedent
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
8
9
|
from dateutil.parser import parse
|
|
9
10
|
from snakesay import snakesay
|
|
@@ -21,16 +22,25 @@ class Webapp:
|
|
|
21
22
|
|
|
22
23
|
Methods:
|
|
23
24
|
- :meth:`Webapp.create`: Create a new webapp.
|
|
25
|
+
- :meth:`Webapp.create_static_file_mapping`: Create a static file mapping.
|
|
26
|
+
- :meth:`Webapp.add_default_static_files_mappings`: Add default static files mappings.
|
|
24
27
|
- :meth:`Webapp.reload`: Reload the webapp.
|
|
25
28
|
- :meth:`Webapp.set_ssl`: Set the SSL certificate and private key.
|
|
26
29
|
- :meth:`Webapp.get_ssl_info`: Retrieve SSL certificate information.
|
|
27
30
|
- :meth:`Webapp.delete_log`: Delete a log file.
|
|
28
31
|
- :meth:`Webapp.get_log_info`: Retrieve log file information.
|
|
32
|
+
- :meth:`Webapp.get`: Retrieve webapp information.
|
|
33
|
+
- :meth:`Webapp.delete`: Delete webapp.
|
|
34
|
+
- :meth:`Webapp.patch`: Patch webapp.
|
|
35
|
+
|
|
36
|
+
Class Methods:
|
|
37
|
+
- :meth:`Webapp.list_webapps`: List all webapps for the current user.
|
|
29
38
|
"""
|
|
39
|
+
username = getpass.getuser()
|
|
40
|
+
files_url = get_api_endpoint(username=username, flavor="files")
|
|
41
|
+
webapps_url = get_api_endpoint(username=username, flavor="webapps")
|
|
42
|
+
|
|
30
43
|
def __init__(self, domain: str) -> None:
|
|
31
|
-
self.username = getpass.getuser()
|
|
32
|
-
self.files_url = get_api_endpoint(username=self.username, flavor="files")
|
|
33
|
-
self.webapps_url = get_api_endpoint(username=self.username, flavor="webapps")
|
|
34
44
|
self.domain = domain
|
|
35
45
|
self.domain_url = f"{self.webapps_url}{self.domain}/"
|
|
36
46
|
|
|
@@ -38,7 +48,12 @@ class Webapp:
|
|
|
38
48
|
return self.domain == other.domain
|
|
39
49
|
|
|
40
50
|
def sanity_checks(self, nuke: bool) -> None:
|
|
41
|
-
"""Check that we have a token, and that we don't already have a webapp for this domain
|
|
51
|
+
"""Check that we have a token, and that we don't already have a webapp for this domain.
|
|
52
|
+
|
|
53
|
+
:param nuke: if True, skip the check for existing webapp
|
|
54
|
+
|
|
55
|
+
:raises SanityException: if API token is missing or webapp already exists
|
|
56
|
+
"""
|
|
42
57
|
print(snakesay("Running API sanity checks"))
|
|
43
58
|
token = os.environ.get("API_TOKEN")
|
|
44
59
|
if not token:
|
|
@@ -71,7 +86,6 @@ class Webapp:
|
|
|
71
86
|
|
|
72
87
|
:raises PythonAnywhereApiException: if API call fails
|
|
73
88
|
"""
|
|
74
|
-
print(snakesay("Creating web app via API"))
|
|
75
89
|
if nuke:
|
|
76
90
|
call_api(self.domain_url, "delete")
|
|
77
91
|
response = call_api(
|
|
@@ -89,21 +103,31 @@ class Webapp:
|
|
|
89
103
|
"PATCH to set virtualenv path and source directory via API failed," f"got {response}:{response.text}"
|
|
90
104
|
)
|
|
91
105
|
|
|
106
|
+
def create_static_file_mapping(self, url_path: str, directory_path: Path) -> None:
|
|
107
|
+
"""Create a static file mapping via the API.
|
|
108
|
+
|
|
109
|
+
:param url_path: URL path (e.g., '/static/')
|
|
110
|
+
:param directory_path: Filesystem path to serve (as Path)
|
|
111
|
+
|
|
112
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
113
|
+
"""
|
|
114
|
+
url = f"{self.domain_url}static_files/"
|
|
115
|
+
call_api(url, "post", json=dict(url=url_path, path=str(directory_path)))
|
|
116
|
+
|
|
92
117
|
def add_default_static_files_mappings(self, project_path: Path) -> None:
|
|
93
|
-
"""Add default static files mappings for /static/ and /media
|
|
118
|
+
"""Add default static files mappings for /static/ and /media/.
|
|
94
119
|
|
|
95
120
|
:param project_path: path to the project
|
|
121
|
+
|
|
122
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
96
123
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
call_api(url, "post", json=dict(url="/static/", path=str(Path(project_path) / "static")))
|
|
100
|
-
call_api(url, "post", json=dict(url="/media/", path=str(Path(project_path) / "media")))
|
|
124
|
+
self.create_static_file_mapping("/static/", Path(project_path) / "static")
|
|
125
|
+
self.create_static_file_mapping("/media/", Path(project_path) / "media")
|
|
101
126
|
|
|
102
127
|
def reload(self) -> None:
|
|
103
128
|
"""Reload webapp
|
|
104
129
|
|
|
105
130
|
:raises PythonAnywhereApiException: if API call fails"""
|
|
106
|
-
print(snakesay(f"Reloading {self.domain} via API"))
|
|
107
131
|
url = f"{self.domain_url}reload/"
|
|
108
132
|
response = call_api(url, "post")
|
|
109
133
|
if not response.ok:
|
|
@@ -124,10 +148,12 @@ class Webapp:
|
|
|
124
148
|
raise PythonAnywhereApiException(f"POST to reload webapp via API failed, got {response}:{response.text}")
|
|
125
149
|
|
|
126
150
|
def set_ssl(self, certificate: str, private_key: str) -> None:
|
|
127
|
-
"""Set SSL certificate and private key for webapp
|
|
151
|
+
"""Set SSL certificate and private key for webapp.
|
|
128
152
|
|
|
129
153
|
:param certificate: SSL certificate
|
|
130
154
|
:param private_key: SSL private key
|
|
155
|
+
|
|
156
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
131
157
|
"""
|
|
132
158
|
print(snakesay(f"Setting up SSL for {self.domain} via API"))
|
|
133
159
|
url = f"{self.domain_url}ssl/"
|
|
@@ -144,7 +170,12 @@ class Webapp:
|
|
|
144
170
|
)
|
|
145
171
|
|
|
146
172
|
def get_ssl_info(self) -> dict[str, Any]:
|
|
147
|
-
"""Get SSL certificate info
|
|
173
|
+
"""Get SSL certificate info.
|
|
174
|
+
|
|
175
|
+
:returns: dictionary with SSL certificate information including parsed expiration date
|
|
176
|
+
|
|
177
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
178
|
+
"""
|
|
148
179
|
url = f"{self.domain_url}ssl/"
|
|
149
180
|
response = call_api(url, "get")
|
|
150
181
|
if not response.ok:
|
|
@@ -181,10 +212,11 @@ class Webapp:
|
|
|
181
212
|
if not response.ok:
|
|
182
213
|
raise PythonAnywhereApiException(f"DELETE log file via API failed, got {response}:{response.text}")
|
|
183
214
|
|
|
184
|
-
def get_log_info(self) -> dict:
|
|
185
|
-
"""Get log files info
|
|
215
|
+
def get_log_info(self) -> dict[str, list[int]]:
|
|
216
|
+
"""Get log files info.
|
|
186
217
|
|
|
187
|
-
:returns: dictionary with log files info
|
|
218
|
+
:returns: dictionary with log files info, keys are log types ('access', 'error', 'server'),
|
|
219
|
+
values are lists of log file indices
|
|
188
220
|
|
|
189
221
|
:raises PythonAnywhereApiException: if API call fails"""
|
|
190
222
|
url = f"{self.files_url}tree/?path=/var/log/"
|
|
@@ -210,3 +242,65 @@ class Webapp:
|
|
|
210
242
|
continue
|
|
211
243
|
logs[log_type].append(log_index)
|
|
212
244
|
return logs
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def list_webapps(cls) -> list[dict[str, Any]]:
|
|
248
|
+
"""List all webapps for the current user.
|
|
249
|
+
|
|
250
|
+
:returns: list of webapps info as dictionaries
|
|
251
|
+
|
|
252
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
253
|
+
"""
|
|
254
|
+
response = call_api(cls.webapps_url, "get")
|
|
255
|
+
if not response.ok:
|
|
256
|
+
raise PythonAnywhereApiException(
|
|
257
|
+
f"GET webapps via API failed, "
|
|
258
|
+
f"got {response}:{response.text}"
|
|
259
|
+
)
|
|
260
|
+
return response.json()
|
|
261
|
+
|
|
262
|
+
def get(self) -> dict[str, Any]:
|
|
263
|
+
"""Retrieve webapp information.
|
|
264
|
+
|
|
265
|
+
:returns: dictionary with webapp information
|
|
266
|
+
|
|
267
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
268
|
+
"""
|
|
269
|
+
response = call_api(self.domain_url, "get")
|
|
270
|
+
|
|
271
|
+
if not response.ok:
|
|
272
|
+
raise PythonAnywhereApiException(
|
|
273
|
+
f"GET webapp for {self.domain} via API failed, got {response}:{response.text}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return response.json()
|
|
277
|
+
|
|
278
|
+
def delete(self) -> None:
|
|
279
|
+
"""Delete webapp.
|
|
280
|
+
|
|
281
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
282
|
+
"""
|
|
283
|
+
response = call_api(self.domain_url, "delete")
|
|
284
|
+
|
|
285
|
+
if response.status_code != 204:
|
|
286
|
+
raise PythonAnywhereApiException(
|
|
287
|
+
f"DELETE webapp for {self.domain} via API failed, got {response}:{response.text}"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def patch(self, data: dict) -> dict[str, Any]:
|
|
291
|
+
"""Patch webapp with provided data.
|
|
292
|
+
|
|
293
|
+
:param data: dictionary with data to update
|
|
294
|
+
:returns: dictionary with updated webapp information
|
|
295
|
+
|
|
296
|
+
:raises PythonAnywhereApiException: if API call fails
|
|
297
|
+
"""
|
|
298
|
+
response = call_api(self.domain_url, "patch", data=data)
|
|
299
|
+
|
|
300
|
+
if not response.ok:
|
|
301
|
+
raise PythonAnywhereApiException(
|
|
302
|
+
f"PATCH webapp for {self.domain} via API failed, "
|
|
303
|
+
f"got {response}:{response.text}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return response.json()
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: pythonanywhere-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: API wrapper for programmatic management of PythonAnywhere services.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: pythonanywhere,api,cloud,web hosting
|
|
7
7
|
Author: PythonAnywhere
|
|
8
8
|
Author-email: developers@pythonanywhere.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
pythonanywhere_core/__init__.py,sha256=
|
|
1
|
+
pythonanywhere_core/__init__.py,sha256=Oz5HbwHMyE87nmwV80AZzpkJPf-wBg7eDuJr_BXZkhU,22
|
|
2
2
|
pythonanywhere_core/base.py,sha256=ro1ByvW1Wlf8Qv2d3NFnHdmM7viujmDJmxUBATqvvHI,2572
|
|
3
3
|
pythonanywhere_core/exceptions.py,sha256=NT6JBtd0VK3PbFc69qx4bnAygA94-pOTghp70xxz1Xw,281
|
|
4
4
|
pythonanywhere_core/files.py,sha256=qIfbcg472TPeOejpif_i64PtWP_qROlxoz5JbePzX8Q,6491
|
|
5
5
|
pythonanywhere_core/schedule.py,sha256=giSO-Gssshkto9de2hSA3VeNKotXYu-lBG-iyqDolIw,3904
|
|
6
6
|
pythonanywhere_core/students.py,sha256=PeBLUP7AOCRwCDiaH71wURwFx3H3PLQW91f5AjOPQmU,1778
|
|
7
|
-
pythonanywhere_core/webapp.py,sha256=
|
|
7
|
+
pythonanywhere_core/webapp.py,sha256=x_66jfGldtQrRhPPQgGFydJnvPreFyCBEYZGqeP0Ql4,12126
|
|
8
8
|
pythonanywhere_core/website.py,sha256=PZlheuqfD0_qYtcjb7fSyqq9KxphtTAXQwU0xrBKxOs,4415
|
|
9
|
-
pythonanywhere_core-0.2.
|
|
10
|
-
pythonanywhere_core-0.2.
|
|
11
|
-
pythonanywhere_core-0.2.
|
|
12
|
-
pythonanywhere_core-0.2.
|
|
9
|
+
pythonanywhere_core-0.2.6.dist-info/LICENSE,sha256=zOJT_E5Fv8crFEKS5ulv0btbjnUiIkdsEBPEBmLHRlQ,1080
|
|
10
|
+
pythonanywhere_core-0.2.6.dist-info/METADATA,sha256=wieLxN1lVBw5JMMeYTJQslxgy5-Gh1imY80s3SfZouM,1430
|
|
11
|
+
pythonanywhere_core-0.2.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
12
|
+
pythonanywhere_core-0.2.6.dist-info/RECORD,,
|
|
File without changes
|