pythonanywhere-core 0.2.7__py3-none-any.whl → 0.2.9__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.
@@ -1 +1 @@
1
- __version__ = "0.2.7"
1
+ __version__ = "0.2.9"
@@ -1,8 +1,10 @@
1
1
  import os
2
+ import platform
2
3
  from typing import Dict
3
4
 
4
5
  import requests
5
6
 
7
+ from pythonanywhere_core import __version__
6
8
  from pythonanywhere_core.exceptions import AuthenticationError, NoTokenError
7
9
 
8
10
  PYTHON_VERSIONS: Dict[str, str] = {
@@ -14,6 +16,7 @@ PYTHON_VERSIONS: Dict[str, str] = {
14
16
  "3.11": "python311",
15
17
  "3.12": "python312",
16
18
  "3.13": "python313",
19
+ "3.14": "python314",
17
20
  }
18
21
 
19
22
 
@@ -53,17 +56,35 @@ def call_api(url: str, method: str, **kwargs) -> requests.Response:
53
56
  :returns: requests.Response object
54
57
 
55
58
  :raises AuthenticationError: if API returns 401
56
- :raises NoTokenError: if API_TOKEN environment variable is not set"""
59
+ :raises NoTokenError: if API_TOKEN environment variable is not set
60
+
61
+ Client identification can be provided via PYTHONANYWHERE_CLIENT environment
62
+ variable (e.g., "pa/1.0.0" or "mcp-server/0.5.0") to help with usage analytics.
63
+ """
57
64
 
58
65
  token = os.environ.get("API_TOKEN")
59
66
  if token is None:
60
67
  raise NoTokenError(helpful_token_error_message())
61
- insecure = os.environ.get("PYTHONANYWHERE_INSECURE_API") == "true"
68
+
69
+ base_user_agent = f"pythonanywhere-core/{__version__}"
70
+ client_info = os.environ.get("PYTHONANYWHERE_CLIENT")
71
+
72
+ if client_info:
73
+ user_agent = f"{base_user_agent} ({client_info}; Python/{platform.python_version()})"
74
+ else:
75
+ user_agent = f"{base_user_agent} (Python/{platform.python_version()})"
76
+
77
+ headers = {
78
+ "Authorization": f"Token {token}",
79
+ "User-Agent": user_agent
80
+ }
81
+ if "headers" in kwargs:
82
+ headers.update(kwargs.pop("headers"))
83
+
62
84
  response = requests.request(
63
85
  method=method,
64
86
  url=url,
65
- headers={"Authorization": f"Token {token}"},
66
- verify=not insecure,
87
+ headers=headers,
67
88
  **kwargs,
68
89
  )
69
90
  if response.status_code == 401:
@@ -15,4 +15,14 @@ class NoTokenError(PythonAnywhereApiException):
15
15
 
16
16
 
17
17
  class DomainAlreadyExistsException(PythonAnywhereApiException):
18
- pass
18
+ pass
19
+
20
+
21
+ class MissingCNAMEException(PythonAnywhereApiException):
22
+ def __init__(self):
23
+ super().__init__(
24
+ "Could not find a CNAME for your website. If you're using an A record, "
25
+ "CloudFlare, or some other way of pointing your domain at PythonAnywhere "
26
+ "then that should not be a problem. If you're not, you should double-check "
27
+ "your DNS setup."
28
+ )
@@ -7,10 +7,9 @@ from textwrap import dedent
7
7
  from typing import Any
8
8
 
9
9
  from dateutil.parser import parse
10
- from snakesay import snakesay
11
10
 
12
11
  from pythonanywhere_core.base import call_api, get_api_endpoint, PYTHON_VERSIONS
13
- from pythonanywhere_core.exceptions import SanityException, PythonAnywhereApiException
12
+ from pythonanywhere_core.exceptions import SanityException, PythonAnywhereApiException, MissingCNAMEException
14
13
 
15
14
 
16
15
  class Webapp:
@@ -54,7 +53,6 @@ class Webapp:
54
53
 
55
54
  :raises SanityException: if API token is missing or webapp already exists
56
55
  """
57
- print(snakesay("Running API sanity checks"))
58
56
  token = os.environ.get("API_TOKEN")
59
57
  if not token:
60
58
  raise SanityException(
@@ -127,24 +125,13 @@ class Webapp:
127
125
  def reload(self) -> None:
128
126
  """Reload webapp
129
127
 
128
+ :raises MissingCNAMEException: if CNAME not found (reload succeeded)
130
129
  :raises PythonAnywhereApiException: if API call fails"""
131
130
  url = f"{self.domain_url}reload/"
132
131
  response = call_api(url, "post")
133
132
  if not response.ok:
134
133
  if response.status_code == 409 and response.json()["error"] == "cname_error":
135
- print(
136
- snakesay(
137
- dedent(
138
- """
139
- Could not find a CNAME for your website. If you're using an A record,
140
- CloudFlare, or some other way of pointing your domain at PythonAnywhere
141
- then that should not be a problem. If you're not, you should double-check
142
- your DNS setup.
143
- """
144
- )
145
- )
146
- )
147
- return
134
+ raise MissingCNAMEException()
148
135
  raise PythonAnywhereApiException(f"POST to reload webapp via API failed, got {response}:{response.text}")
149
136
 
150
137
  def set_ssl(self, certificate: str, private_key: str) -> None:
@@ -155,7 +142,6 @@ class Webapp:
155
142
 
156
143
  :raises PythonAnywhereApiException: if API call fails
157
144
  """
158
- print(snakesay(f"Setting up SSL for {self.domain} via API"))
159
145
  url = f"{self.domain_url}ssl/"
160
146
  response = call_api(url, "post", json={"cert": certificate, "private_key": private_key})
161
147
  if not response.ok:
@@ -193,12 +179,6 @@ class Webapp:
193
179
 
194
180
  :raises PythonAnywhereApiException: if API call fails
195
181
  """
196
- if index:
197
- message = f"Deleting old (archive number {index}) {log_type} log file for {self.domain} via API"
198
- else:
199
- message = f"Deleting current {log_type} log file for {self.domain} via API"
200
- print(snakesay(message))
201
-
202
182
  if index == 1:
203
183
  suffix = ".1"
204
184
  elif index > 1:
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pythonanywhere-core
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: API wrapper for programmatic management of PythonAnywhere services.
5
5
  License: MIT
6
+ License-File: LICENSE
6
7
  Keywords: pythonanywhere,api,cloud,web hosting
7
8
  Author: PythonAnywhere
8
9
  Author-email: developers@pythonanywhere.com
@@ -15,19 +16,30 @@ Classifier: Programming Language :: Python :: 3.10
15
16
  Classifier: Programming Language :: Python :: 3.11
16
17
  Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
18
20
  Classifier: Topic :: Software Development :: Libraries
19
21
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
20
22
  Requires-Dist: requests (>=2.30.0,<3.0.0)
21
- Requires-Dist: snakesay (>=0.10.3,<0.11.0)
22
23
  Requires-Dist: typing_extensions (>=4.5.0,<5.0.0)
24
+ Project-URL: Documentation, https://core.pythonanywhere.com/
25
+ Project-URL: Homepage, https://core.pythonanywhere.com/
26
+ Project-URL: Repository, https://github.com/pythonanywhere/pythonanywhere-core
23
27
  Description-Content-Type: text/x-rst
24
28
 
25
- API wrapper for programmatic management of PythonAnywhere services.
29
+ pythonanywhere-core
30
+ ===================
26
31
 
27
- It's a core code behind `PythonAnywhere cli tool`_.
32
+ Python SDK for PythonAnywhere API - programmatic management of PythonAnywhere
33
+ services including webapps, files, scheduled tasks, students, and websites.
34
+
35
+ Core library behind the `PythonAnywhere cli tool`_.
28
36
 
29
37
  .. _PythonAnywhere cli tool: https://pypi.org/project/pythonanywhere/
30
38
 
39
+ Documentation
40
+ =============
41
+
42
+ Full documentation is available at https://core.pythonanywhere.com/
31
43
 
32
44
  Development
33
45
  ===========
@@ -0,0 +1,13 @@
1
+ pythonanywhere_core/__init__.py,sha256=F8OVhAhMXSkvvXYgZtbPn2SG1AQC3joK4yu-FrHt81Y,22
2
+ pythonanywhere_core/base.py,sha256=fMdyaccJdpeB8ZN2QqqEmUPYSaAp3ZdvFxgDexR0Bfg,3195
3
+ pythonanywhere_core/exceptions.py,sha256=37aZy6txVFuiXfUdRJA5d7NQ1N5vbkdOveqv8rwnDbk,691
4
+ pythonanywhere_core/files.py,sha256=qIfbcg472TPeOejpif_i64PtWP_qROlxoz5JbePzX8Q,6491
5
+ pythonanywhere_core/resources.py,sha256=NzzL-eNyo26Lr5FVJSAaxTq0Ia0vP3tIew_N7yFiFlk,1172
6
+ pythonanywhere_core/schedule.py,sha256=giSO-Gssshkto9de2hSA3VeNKotXYu-lBG-iyqDolIw,3904
7
+ pythonanywhere_core/students.py,sha256=PeBLUP7AOCRwCDiaH71wURwFx3H3PLQW91f5AjOPQmU,1778
8
+ pythonanywhere_core/webapp.py,sha256=Fg-c_qnoXYK_VN3bMQjBPoVkRj49KZt_7OkWdqwW_Us,11277
9
+ pythonanywhere_core/website.py,sha256=6Q_37QDKkuKE3771V056aiPmrQ0v8ot6U59CIaZwQzk,4348
10
+ pythonanywhere_core-0.2.9.dist-info/METADATA,sha256=eYEs0j3fTbw5u1ayGidBnCzzFFKzECKpMWWQYPSnWl0,1880
11
+ pythonanywhere_core-0.2.9.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
12
+ pythonanywhere_core-0.2.9.dist-info/licenses/LICENSE,sha256=zOJT_E5Fv8crFEKS5ulv0btbjnUiIkdsEBPEBmLHRlQ,1080
13
+ pythonanywhere_core-0.2.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,13 +0,0 @@
1
- pythonanywhere_core/__init__.py,sha256=XHypfHSPdgXFKmOdoewn7czU670gt8InhHhzlP5j_aA,22
2
- pythonanywhere_core/base.py,sha256=ro1ByvW1Wlf8Qv2d3NFnHdmM7viujmDJmxUBATqvvHI,2572
3
- pythonanywhere_core/exceptions.py,sha256=NT6JBtd0VK3PbFc69qx4bnAygA94-pOTghp70xxz1Xw,281
4
- pythonanywhere_core/files.py,sha256=qIfbcg472TPeOejpif_i64PtWP_qROlxoz5JbePzX8Q,6491
5
- pythonanywhere_core/resources.py,sha256=NzzL-eNyo26Lr5FVJSAaxTq0Ia0vP3tIew_N7yFiFlk,1172
6
- pythonanywhere_core/schedule.py,sha256=giSO-Gssshkto9de2hSA3VeNKotXYu-lBG-iyqDolIw,3904
7
- pythonanywhere_core/students.py,sha256=PeBLUP7AOCRwCDiaH71wURwFx3H3PLQW91f5AjOPQmU,1778
8
- pythonanywhere_core/webapp.py,sha256=x_66jfGldtQrRhPPQgGFydJnvPreFyCBEYZGqeP0Ql4,12126
9
- pythonanywhere_core/website.py,sha256=6Q_37QDKkuKE3771V056aiPmrQ0v8ot6U59CIaZwQzk,4348
10
- pythonanywhere_core-0.2.7.dist-info/LICENSE,sha256=zOJT_E5Fv8crFEKS5ulv0btbjnUiIkdsEBPEBmLHRlQ,1080
11
- pythonanywhere_core-0.2.7.dist-info/METADATA,sha256=sf8yUXYmAt_0AZAv4jg1Ytr4AUe1r-A2rPlENC4bbP8,1430
12
- pythonanywhere_core-0.2.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
13
- pythonanywhere_core-0.2.7.dist-info/RECORD,,