Skip to content

Commit 9b6aeba

Browse files
Tutorial and target directory config.
1 parent 74573d4 commit 9b6aeba

9 files changed

Lines changed: 261 additions & 5 deletions

File tree

docs/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pip install git+https://github.com/beeware/briefcase@main
1010
```
1111
///
1212

13+
There is a [tutorial](tutorial.md) that builds on top of the [BeeWare Tutorial](https://tutorial.beeware.org/) and walks you through deploying your first app to PythonAnywhere.
14+
1315
## Prerequisites
1416

1517
* A PythonAnywhere account
@@ -75,6 +77,15 @@ The domain name for your PythonAnywhere webapp. If not specified, defaults to `<
7577
domain = "www.mycustomdomain.com"
7678
```
7779

80+
### `directory`
81+
82+
The remote directory on PythonAnywhere where the app files will be uploaded. If not specified, defaults to `/home/<username>/<app_name>`.
83+
84+
```toml
85+
[tool.briefcase.app.myapp.pythonanywhere]
86+
directory = "/home/mypauser/www"
87+
```
88+
7889
## Environment variables
7990

8091
The following environment variables are used by the plugin:

docs/tutorial.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Tutorial - Deploy to PythonAnywhere
2+
3+
This tutorial picks up where [Tutorial 6 - Put it on the web!](https://tutorial.beeware.org/en/latest/tutorial/tutorial-6/) of the BeeWare Tutorial leaves off. In that tutorial, you built a static web app and ran it locally with `briefcase run web`. Now we'll publish it to PythonAnywhere so it's accessible on the internet.
4+
5+
/// admonition | Prerequisites
6+
type: info
7+
8+
Before starting, make sure you have:
9+
10+
* Completed the BeeWare tutorial through [Tutorial 6](https://tutorial.beeware.org/en/latest/tutorial/tutorial-6/) (or at least run `briefcase create web static` and `briefcase build web static`)
11+
* A [PythonAnywhere](https://www.pythonanywhere.com/) account
12+
* An API token from your [Account page](https://www.pythonanywhere.com/account/#api_token)
13+
14+
///
15+
16+
## Install the plugin
17+
18+
With your BeeWare tutorial virtual environment active, install the plugin:
19+
20+
/// tab | macOS
21+
22+
```console
23+
(beeware-venv) $ pip install pythonanywhere-briefcase-plugin
24+
```
25+
26+
///
27+
28+
/// tab | Linux
29+
30+
```console
31+
(beeware-venv) $ pip install pythonanywhere-briefcase-plugin
32+
```
33+
34+
///
35+
36+
/// tab | Windows
37+
38+
```doscon
39+
(beeware-venv) C:\...>pip install pythonanywhere-briefcase-plugin
40+
```
41+
42+
///
43+
44+
## Package the app
45+
46+
In Tutorial 6, you ran the app locally with `briefcase run web`. To publish it, we first need to package the app into a distributable artifact:
47+
48+
/// tab | macOS
49+
50+
```console
51+
(beeware-venv) $ briefcase package web static
52+
53+
[helloworld] Packaging Hello World...
54+
...
55+
56+
[helloworld] Packaged dist/Hello World-0.0.1.web.zip
57+
```
58+
59+
///
60+
61+
/// tab | Linux
62+
63+
```console
64+
(beeware-venv) $ briefcase package web static
65+
66+
[helloworld] Packaging Hello World...
67+
...
68+
69+
[helloworld] Packaged dist/Hello World-0.0.1.web.zip
70+
```
71+
72+
///
73+
74+
/// tab | Windows
75+
76+
```doscon
77+
(beeware-venv) C:\...>briefcase package web static
78+
79+
[helloworld] Packaging Hello World...
80+
...
81+
82+
[helloworld] Packaged dist\Hello World-0.0.1.web.zip
83+
```
84+
85+
///
86+
87+
This creates a ZIP file containing all the static files needed to serve your app.
88+
89+
## Set your API token
90+
91+
The plugin needs your PythonAnywhere API token to upload files and configure the webapp. Set it as an environment variable:
92+
93+
/// tab | macOS
94+
95+
```console
96+
(beeware-venv) $ export API_TOKEN=your-api-token-here
97+
```
98+
99+
///
100+
101+
/// tab | Linux
102+
103+
```console
104+
(beeware-venv) $ export API_TOKEN=your-api-token-here
105+
```
106+
107+
///
108+
109+
/// tab | Windows
110+
111+
```doscon
112+
(beeware-venv) C:\...>set API_TOKEN=your-api-token-here
113+
```
114+
115+
///
116+
117+
Replace `your-api-token-here` with the token from your [Account page](https://www.pythonanywhere.com/account/#api_token).
118+
119+
//// admonition | Username
120+
type: note
121+
122+
The plugin needs to know your PythonAnywhere username to upload files to the right place. By default, it uses the `PYTHONANYWHERE_USERNAME` environment variable, falling back to your local system username.
123+
124+
If you're running this from a PythonAnywhere console, the username is detected automatically -- no extra configuration needed.
125+
126+
If you're running locally and your PythonAnywhere username is different from your local system username, either set the environment variable:
127+
128+
/// tab | macOS
129+
130+
```console
131+
(beeware-venv) $ export PYTHONANYWHERE_USERNAME=your-pa-username
132+
```
133+
134+
///
135+
136+
/// tab | Linux
137+
138+
```console
139+
(beeware-venv) $ export PYTHONANYWHERE_USERNAME=your-pa-username
140+
```
141+
142+
///
143+
144+
/// tab | Windows
145+
146+
```doscon
147+
(beeware-venv) C:\...>set PYTHONANYWHERE_USERNAME=your-pa-username
148+
```
149+
150+
///
151+
152+
or add it to the `pyproject.toml` of your app:
153+
154+
```toml
155+
[tool.briefcase.app.helloworld.pythonanywhere]
156+
username = "your-pa-username"
157+
```
158+
159+
////
160+
161+
## Publish
162+
163+
Now, publish the app to PythonAnywhere:
164+
165+
/// tab | macOS
166+
167+
```console
168+
(beeware-venv) $ briefcase publish web static
169+
```
170+
171+
///
172+
173+
/// tab | Linux
174+
175+
```console
176+
(beeware-venv) $ briefcase publish web static
177+
```
178+
179+
///
180+
181+
/// tab | Windows
182+
183+
```doscon
184+
(beeware-venv) C:\...>briefcase publish web static
185+
```
186+
187+
///
188+
189+
The plugin will:
190+
191+
1. Extract the packaged ZIP file.
192+
2. Upload the contents to `/home/<your-username>/helloworld/` on PythonAnywhere.
193+
3. Create a webapp (or update the existing one) with a static file mapping pointing `/` to that directory.
194+
4. Reload the webapp.
195+
196+
Once it's done, your app is live at `https://<your-username>.pythonanywhere.com/`!
197+
198+
## How does this work?
199+
200+
In Tutorial 6, Briefcase started a local web server to serve your static web app. The `publish` command takes the same static files and deploys them to PythonAnywhere's infrastructure instead.
201+
202+
PythonAnywhere serves the files as a static website -- no Python process runs on the server. The Python code in your app runs in the *browser*, using [PyScript](https://pyscript.net), just as it did when you ran it locally.
203+
204+
The plugin uses the [PythonAnywhere API](https://help.pythonanywhere.com/pages/API/) to upload files and configure the webapp, which is why the API token is required.
205+
206+
## Next steps
207+
208+
Your app is now live on the internet. From here you might want to:
209+
210+
* **Use a custom domain** -- configure a `domain` in your `pyproject.toml` to serve the app from your own domain name. See the [Configuration](index.md#configuration) section.
211+
* **Continue the BeeWare tutorial** -- [Tutorial 7](https://tutorial.beeware.org/en/latest/tutorial/tutorial-7/) covers adding third-party libraries to your app.

mkdocs.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ repo_name: pythonanywhere/pythonanywhere-briefcase-plugin
66

77
docs_dir: docs
88

9+
nav:
10+
- Home: index.md
11+
- Tutorial: tutorial.md
12+
913
theme:
1014
name: material
1115
language: en
@@ -36,6 +40,8 @@ markdown_extensions:
3640
pymdownx.highlight: {}
3741
pymdownx.superfences: {}
3842
pymdownx.blocks.admonition: {}
43+
pymdownx.blocks.tab:
44+
alternate_style: true
3945
pymdownx.details: {}
4046
toc:
4147
permalink: true

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[project]
22
name = "pythonanywhere-briefcase-plugin"
3-
version = "0.0.1a3"
3+
version = "0.0.1a4"
44
description = "A Briefcase publication channel plugin for deploying static web apps to PythonAnywhere."
55
requires-python = ">= 3.10"
66
readme = "README.md"
77
license = "MIT"
88
license-files = ["LICENSE"]
99
authors = [
10-
{name = "Filip Lajszczak", email = "filip@lajszczak.dev"},
10+
{name = "PythonAnywhere", email = "developers@pythonanywhere.com"},
1111
]
1212
keywords = ["briefcase", "pythonanywhere", "deployment", "web", "static"]
1313
classifiers = [
@@ -26,6 +26,10 @@ dependencies = [
2626
"pythonanywhere-core >= 0.3.0",
2727
]
2828

29+
[project.urls]
30+
Documentation = "https://briefcase.pythonanywhere.com/"
31+
Repository = "https://github.com/pythonanywhere/pythonanywhere-briefcase-plugin"
32+
2933
[build-system]
3034
requires = ["uv_build >= 0.7.2, < 0.8"]
3135
build-backend = "uv_build"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from pythonanywhere_briefcase_plugin.channel import PythonAnywherePublicationChannel
22

3-
__version__ = "0.0.1a3"
3+
__version__ = "0.0.1a4"
44
__all__ = ["PythonAnywherePublicationChannel"]

src/pythonanywhere_briefcase_plugin/channel.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ def publish_app(self, app: AppConfig, command: PublishCommandAPI, **options):
5858

5959
username = self._resolve_username(app)
6060
domain = self._resolve_domain(app)
61-
remote_path = f"/home/{username}/{app.app_name}"
61+
remote_path = getattr(app, "pythonanywhere_directory", None)
62+
if not remote_path:
63+
remote_path = f"/home/{username}/{app.app_name}"
6264

6365
try:
6466
with tempfile.TemporaryDirectory() as tmpdir:

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def mock_app(mocker):
2424
# No pythonanywhere-specific config by default
2525
del app.pythonanywhere_username
2626
del app.pythonanywhere_domain
27+
del app.pythonanywhere_directory
2728
return app
2829

2930

tests/test_channel.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,24 @@ def test_publish_updates_existing_webapp(mock_app, mock_command, dist_zip, mocke
187187
"/", Path("/home/testuser/myapp")
188188
)
189189
mock_webapp.reload.assert_called_once()
190+
191+
192+
def test_publish_custom_directory(mock_app, mock_command, dist_zip, mocker):
193+
mock_app.pythonanywhere_username = "testuser"
194+
mock_app.pythonanywhere_directory = "/home/testuser/custom-dir"
195+
196+
mock_files_cls = mocker.patch("pythonanywhere_briefcase_plugin.channel.Files")
197+
mock_webapp_cls = mocker.patch("pythonanywhere_briefcase_plugin.channel.Webapp")
198+
mock_webapp_cls.return_value.get.return_value = {"id": 1}
199+
200+
channel = PythonAnywherePublicationChannel()
201+
channel.publish_app(mock_app, mock_command)
202+
203+
# Files uploaded to custom remote path
204+
_, remote_path = mock_files_cls.return_value.tree_post.call_args[0]
205+
assert remote_path == "/home/testuser/custom-dir"
206+
207+
# Static file mapping uses custom path
208+
mock_webapp_cls.return_value.create_static_file_mapping.assert_called_once_with(
209+
"/", Path("/home/testuser/custom-dir")
210+
)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)