plash-cli 0.0.2__tar.gz → 0.0.4__tar.gz
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.
- {plash_cli-0.0.2/plash_cli.egg-info → plash_cli-0.0.4}/PKG-INFO +79 -8
- {plash_cli-0.0.2 → plash_cli-0.0.4}/README.md +64 -6
- plash_cli-0.0.4/plash_cli/__init__.py +1 -0
- plash_cli-0.0.4/plash_cli/_modidx.py +21 -0
- plash_cli-0.0.4/plash_cli/core.py +204 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4/plash_cli.egg-info}/PKG-INFO +79 -8
- plash_cli-0.0.4/plash_cli.egg-info/entry_points.txt +12 -0
- plash_cli-0.0.4/pyproject.toml +11 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/settings.ini +9 -1
- plash_cli-0.0.2/plash_cli/__init__.py +0 -1
- plash_cli-0.0.2/plash_cli/_modidx.py +0 -12
- plash_cli-0.0.2/plash_cli/core.py +0 -90
- plash_cli-0.0.2/plash_cli.egg-info/entry_points.txt +0 -5
- plash_cli-0.0.2/pyproject.toml +0 -3
- {plash_cli-0.0.2 → plash_cli-0.0.4}/LICENSE +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/MANIFEST.in +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/plash_cli.egg-info/SOURCES.txt +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/plash_cli.egg-info/dependency_links.txt +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/plash_cli.egg-info/not-zip-safe +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/plash_cli.egg-info/requires.txt +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/plash_cli.egg-info/top_level.txt +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/setup.cfg +0 -0
- {plash_cli-0.0.2 → plash_cli-0.0.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: plash_cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: CLI for the Plash hosting service
|
|
5
5
|
Home-page: https://github.com/AnswerDotAI/plash_cli
|
|
6
6
|
Author: Jeremy Howard
|
|
@@ -23,6 +23,19 @@ License-File: LICENSE
|
|
|
23
23
|
Requires-Dist: fastcore
|
|
24
24
|
Requires-Dist: httpx
|
|
25
25
|
Provides-Extra: dev
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: provides-extra
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
26
39
|
|
|
27
40
|
# plash-cli
|
|
28
41
|
|
|
@@ -47,20 +60,20 @@ $ pip install git+https://github.com/AnswerDotAI/plash-cli.git
|
|
|
47
60
|
or from [pypi](https://pypi.org/project/plash-cli/)
|
|
48
61
|
|
|
49
62
|
``` sh
|
|
50
|
-
$ pip install
|
|
63
|
+
$ pip install plash-cli
|
|
51
64
|
```
|
|
52
65
|
|
|
53
66
|
## Deploy Your First FastHTML App
|
|
54
67
|
|
|
55
|
-
###
|
|
68
|
+
### Authentication
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
to use Plash. To obtain these, do the following:
|
|
70
|
+
To use Plash, you’ll need to authenticate:
|
|
59
71
|
|
|
60
72
|
1. Signup for an account at https://pla.sh/
|
|
61
73
|
2. Activate your Plash subscription
|
|
62
|
-
3.
|
|
63
|
-
|
|
74
|
+
3. Run `plash_login` in your terminal
|
|
75
|
+
4. A browser window will open for authentication
|
|
76
|
+
5. Once authenticated, your credentials will be saved locally
|
|
64
77
|
|
|
65
78
|
### Create a FastHTML App
|
|
66
79
|
|
|
@@ -95,6 +108,19 @@ Run `plash_deploy`. Your app will be live at
|
|
|
95
108
|
`https://<app-name>.pla.sh`. The URL will be shown in the deployment
|
|
96
109
|
output.
|
|
97
110
|
|
|
111
|
+
### Managing Your App
|
|
112
|
+
|
|
113
|
+
Plash CLI provides several commands to manage your apps:
|
|
114
|
+
|
|
115
|
+
- `plash_login` - Login to Plash
|
|
116
|
+
- `plash_deploy` - Deploy your app
|
|
117
|
+
- `plash_view` - Open your deployed app in a browser
|
|
118
|
+
- `plash_start` - Start your app if it’s stopped
|
|
119
|
+
- `plash_stop` - Stop your running app
|
|
120
|
+
- `plash_logs` - View your app’s logs
|
|
121
|
+
- `plash_download` - Download your deployed app files
|
|
122
|
+
- `plash_delete` - Delete your deployed app
|
|
123
|
+
|
|
98
124
|
### App Dependencies
|
|
99
125
|
|
|
100
126
|
If your app needs additional dependencies to run, we offer a number of
|
|
@@ -147,3 +173,48 @@ in your Plash container, to modify your apps behavior for local and
|
|
|
147
173
|
production development. You can download any deployed database names by
|
|
148
174
|
clicking the Download App button to get a compressed file of all files
|
|
149
175
|
in your /app folder in your deployed app.
|
|
176
|
+
|
|
177
|
+
## Deploy to Pla.sh via GitHub Actions
|
|
178
|
+
|
|
179
|
+
If you’d like to deploy your plash app every time you commit changes to
|
|
180
|
+
your GitHub repo, you can use the following workflow to your
|
|
181
|
+
`.github/workflows/` folder in the root of your repo:
|
|
182
|
+
|
|
183
|
+
``` yaml
|
|
184
|
+
name: Deploy to Plash
|
|
185
|
+
|
|
186
|
+
on:
|
|
187
|
+
push:
|
|
188
|
+
branches:
|
|
189
|
+
- main
|
|
190
|
+
workflow_dispatch:
|
|
191
|
+
|
|
192
|
+
jobs:
|
|
193
|
+
deploy:
|
|
194
|
+
runs-on: ubuntu-latest
|
|
195
|
+
steps:
|
|
196
|
+
- name: Checkout repository
|
|
197
|
+
uses: actions/checkout@v3
|
|
198
|
+
|
|
199
|
+
- name: Set up Python
|
|
200
|
+
uses: actions/setup-python@v4
|
|
201
|
+
with:
|
|
202
|
+
python-version: '3.x'
|
|
203
|
+
|
|
204
|
+
- name: Create Plash config
|
|
205
|
+
run: |
|
|
206
|
+
mkdir -p ~/.config
|
|
207
|
+
echo '${{ secrets.PLASH_CONFIG }}' > ~/.config/plash_config.json
|
|
208
|
+
|
|
209
|
+
- name: Install plash-cli with pip
|
|
210
|
+
run: pip install plash-cli
|
|
211
|
+
|
|
212
|
+
- name: Deploy to Plash
|
|
213
|
+
run: plash_deploy
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
It relies on storing your plash config as a secret named `PLASH_CONFIG`
|
|
217
|
+
in your GitHub repo. After running `plash_login`, you can find these in
|
|
218
|
+
`~/.config/plash_config.json` (unless you haved changed the
|
|
219
|
+
XDG_CONFIG_HOME environment variable). Learn more about GitHub secrets
|
|
220
|
+
[here](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
|
|
@@ -21,20 +21,20 @@ $ pip install git+https://github.com/AnswerDotAI/plash-cli.git
|
|
|
21
21
|
or from [pypi](https://pypi.org/project/plash-cli/)
|
|
22
22
|
|
|
23
23
|
``` sh
|
|
24
|
-
$ pip install
|
|
24
|
+
$ pip install plash-cli
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Deploy Your First FastHTML App
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### Authentication
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
to use Plash. To obtain these, do the following:
|
|
31
|
+
To use Plash, you’ll need to authenticate:
|
|
33
32
|
|
|
34
33
|
1. Signup for an account at https://pla.sh/
|
|
35
34
|
2. Activate your Plash subscription
|
|
36
|
-
3.
|
|
37
|
-
|
|
35
|
+
3. Run `plash_login` in your terminal
|
|
36
|
+
4. A browser window will open for authentication
|
|
37
|
+
5. Once authenticated, your credentials will be saved locally
|
|
38
38
|
|
|
39
39
|
### Create a FastHTML App
|
|
40
40
|
|
|
@@ -69,6 +69,19 @@ Run `plash_deploy`. Your app will be live at
|
|
|
69
69
|
`https://<app-name>.pla.sh`. The URL will be shown in the deployment
|
|
70
70
|
output.
|
|
71
71
|
|
|
72
|
+
### Managing Your App
|
|
73
|
+
|
|
74
|
+
Plash CLI provides several commands to manage your apps:
|
|
75
|
+
|
|
76
|
+
- `plash_login` - Login to Plash
|
|
77
|
+
- `plash_deploy` - Deploy your app
|
|
78
|
+
- `plash_view` - Open your deployed app in a browser
|
|
79
|
+
- `plash_start` - Start your app if it’s stopped
|
|
80
|
+
- `plash_stop` - Stop your running app
|
|
81
|
+
- `plash_logs` - View your app’s logs
|
|
82
|
+
- `plash_download` - Download your deployed app files
|
|
83
|
+
- `plash_delete` - Delete your deployed app
|
|
84
|
+
|
|
72
85
|
### App Dependencies
|
|
73
86
|
|
|
74
87
|
If your app needs additional dependencies to run, we offer a number of
|
|
@@ -121,3 +134,48 @@ in your Plash container, to modify your apps behavior for local and
|
|
|
121
134
|
production development. You can download any deployed database names by
|
|
122
135
|
clicking the Download App button to get a compressed file of all files
|
|
123
136
|
in your /app folder in your deployed app.
|
|
137
|
+
|
|
138
|
+
## Deploy to Pla.sh via GitHub Actions
|
|
139
|
+
|
|
140
|
+
If you’d like to deploy your plash app every time you commit changes to
|
|
141
|
+
your GitHub repo, you can use the following workflow to your
|
|
142
|
+
`.github/workflows/` folder in the root of your repo:
|
|
143
|
+
|
|
144
|
+
``` yaml
|
|
145
|
+
name: Deploy to Plash
|
|
146
|
+
|
|
147
|
+
on:
|
|
148
|
+
push:
|
|
149
|
+
branches:
|
|
150
|
+
- main
|
|
151
|
+
workflow_dispatch:
|
|
152
|
+
|
|
153
|
+
jobs:
|
|
154
|
+
deploy:
|
|
155
|
+
runs-on: ubuntu-latest
|
|
156
|
+
steps:
|
|
157
|
+
- name: Checkout repository
|
|
158
|
+
uses: actions/checkout@v3
|
|
159
|
+
|
|
160
|
+
- name: Set up Python
|
|
161
|
+
uses: actions/setup-python@v4
|
|
162
|
+
with:
|
|
163
|
+
python-version: '3.x'
|
|
164
|
+
|
|
165
|
+
- name: Create Plash config
|
|
166
|
+
run: |
|
|
167
|
+
mkdir -p ~/.config
|
|
168
|
+
echo '${{ secrets.PLASH_CONFIG }}' > ~/.config/plash_config.json
|
|
169
|
+
|
|
170
|
+
- name: Install plash-cli with pip
|
|
171
|
+
run: pip install plash-cli
|
|
172
|
+
|
|
173
|
+
- name: Deploy to Plash
|
|
174
|
+
run: plash_deploy
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
It relies on storing your plash config as a secret named `PLASH_CONFIG`
|
|
178
|
+
in your GitHub repo. After running `plash_login`, you can find these in
|
|
179
|
+
`~/.config/plash_config.json` (unless you haved changed the
|
|
180
|
+
XDG_CONFIG_HOME environment variable). Learn more about GitHub secrets
|
|
181
|
+
[here](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.4"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Autogenerated by nbdev
|
|
2
|
+
|
|
3
|
+
d = { 'settings': { 'branch': 'main',
|
|
4
|
+
'doc_baseurl': '/plash_cli',
|
|
5
|
+
'doc_host': 'https://AnswerDotAI.github.io',
|
|
6
|
+
'git_url': 'https://github.com/AnswerDotAI/plash_cli',
|
|
7
|
+
'lib_path': 'plash_cli'},
|
|
8
|
+
'syms': { 'plash_cli.core': { 'plash_cli.core.create_tar_archive': ('core.html#create_tar_archive', 'plash_cli/core.py'),
|
|
9
|
+
'plash_cli.core.delete': ('core.html#delete', 'plash_cli/core.py'),
|
|
10
|
+
'plash_cli.core.deploy': ('core.html#deploy', 'plash_cli/core.py'),
|
|
11
|
+
'plash_cli.core.download': ('core.html#download', 'plash_cli/core.py'),
|
|
12
|
+
'plash_cli.core.endpoint': ('core.html#endpoint', 'plash_cli/core.py'),
|
|
13
|
+
'plash_cli.core.endpoint_func': ('core.html#endpoint_func', 'plash_cli/core.py'),
|
|
14
|
+
'plash_cli.core.get_app_id': ('core.html#get_app_id', 'plash_cli/core.py'),
|
|
15
|
+
'plash_cli.core.get_client': ('core.html#get_client', 'plash_cli/core.py'),
|
|
16
|
+
'plash_cli.core.is_included': ('core.html#is_included', 'plash_cli/core.py'),
|
|
17
|
+
'plash_cli.core.login': ('core.html#login', 'plash_cli/core.py'),
|
|
18
|
+
'plash_cli.core.mk_auth_req': ('core.html#mk_auth_req', 'plash_cli/core.py'),
|
|
19
|
+
'plash_cli.core.poll_cookies': ('core.html#poll_cookies', 'plash_cli/core.py'),
|
|
20
|
+
'plash_cli.core.validate_app': ('core.html#validate_app', 'plash_cli/core.py'),
|
|
21
|
+
'plash_cli.core.view': ('core.html#view', 'plash_cli/core.py')}}}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""The Plash CLI tool"""
|
|
2
|
+
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
4
|
+
|
|
5
|
+
# %% auto 0
|
|
6
|
+
__all__ = ['PLASH_CONFIG_HOME', 'stop', 'start', 'logs', 'get_client', 'mk_auth_req', 'get_app_id', 'endpoint', 'is_included',
|
|
7
|
+
'create_tar_archive', 'validate_app', 'poll_cookies', 'login', 'deploy', 'view', 'delete', 'endpoint_func',
|
|
8
|
+
'download']
|
|
9
|
+
|
|
10
|
+
# %% ../nbs/00_core.ipynb 2
|
|
11
|
+
from fastcore.all import *
|
|
12
|
+
from fastcore.xdg import *
|
|
13
|
+
import secrets, webbrowser, json, httpx, io, tarfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from uuid import uuid4
|
|
16
|
+
from time import time, sleep
|
|
17
|
+
|
|
18
|
+
import io, sys, tarfile
|
|
19
|
+
|
|
20
|
+
# %% ../nbs/00_core.ipynb 4
|
|
21
|
+
PLASH_CONFIG_HOME = xdg_config_home() / 'plash_config.json'
|
|
22
|
+
|
|
23
|
+
# %% ../nbs/00_core.ipynb 5
|
|
24
|
+
def get_client(cookie_file):
|
|
25
|
+
client = httpx.Client()
|
|
26
|
+
if not cookie_file.exists():
|
|
27
|
+
raise FileNotFoundError("Plash config not found. Please run plash_login and try again.")
|
|
28
|
+
cookies = Path(cookie_file).read_json()
|
|
29
|
+
client.cookies.update(cookies)
|
|
30
|
+
return client
|
|
31
|
+
|
|
32
|
+
# %% ../nbs/00_core.ipynb 6
|
|
33
|
+
def mk_auth_req(url:str, method:str='get', **kwargs): return getattr(get_client(PLASH_CONFIG_HOME), method)(url, **kwargs)
|
|
34
|
+
|
|
35
|
+
# %% ../nbs/00_core.ipynb 7
|
|
36
|
+
def get_app_id(path:Path):
|
|
37
|
+
plash_app = Path(path) / '.plash'
|
|
38
|
+
if not plash_app.exists(): raise FileNotFoundError(f"File not found: {plash_app=}")
|
|
39
|
+
return parse_env(fn=plash_app)['PLASH_APP_ID']
|
|
40
|
+
|
|
41
|
+
# %% ../nbs/00_core.ipynb 8
|
|
42
|
+
def endpoint(path, local, port=None):
|
|
43
|
+
p = "http" if local else "https"
|
|
44
|
+
d = f"localhost:{port}" if local else "pla.sh"
|
|
45
|
+
return f"{p}://{d}{path}"
|
|
46
|
+
|
|
47
|
+
# %% ../nbs/00_core.ipynb 9
|
|
48
|
+
def is_included(path):
|
|
49
|
+
"Returns True if path should be included in deployment"
|
|
50
|
+
if path.name.startswith('.'): return False
|
|
51
|
+
if path.suffix == '.pyc': return False
|
|
52
|
+
excludes = {'.git', '__pycache__', '.gitignore', '.env',
|
|
53
|
+
'.pytest_cache', '.venv', 'venv', '.ipynb_checkpoints',
|
|
54
|
+
'.vscode', '.idea', '.sesskey'}
|
|
55
|
+
return not any(p in excludes for p in path.parts)
|
|
56
|
+
|
|
57
|
+
# %% ../nbs/00_core.ipynb 10
|
|
58
|
+
def create_tar_archive(path # Path to directory containing FastHTML app
|
|
59
|
+
)->io.BytesIO: # Buffer of tar directory
|
|
60
|
+
"Creates a tar archive of a directory, excluding files based on is_included"
|
|
61
|
+
buf = io.BytesIO()
|
|
62
|
+
files = L(Path(path).iterdir()).filter(is_included)
|
|
63
|
+
|
|
64
|
+
with tarfile.open(fileobj=buf, mode='w:gz') as tar:
|
|
65
|
+
for f in files: tar.add(f, arcname=f.name)
|
|
66
|
+
buf.seek(0)
|
|
67
|
+
return buf, len(files)
|
|
68
|
+
|
|
69
|
+
# %% ../nbs/00_core.ipynb 11
|
|
70
|
+
def validate_app(path):
|
|
71
|
+
"Validates that the app in the directory `path` is deployable as a FastHTML app"
|
|
72
|
+
print("Analyzing project structure...")
|
|
73
|
+
|
|
74
|
+
main_file = Path(path) / "main.py"
|
|
75
|
+
if not main_file.exists():
|
|
76
|
+
print('[red bold]ERROR: Your FastHTML app must have a main.py[/red bold]')
|
|
77
|
+
print(f'Your path is: [bold]{path}[/bold]')
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
# %% ../nbs/00_core.ipynb 13
|
|
81
|
+
def poll_cookies(paircode, local, port=None, interval=1, timeout=180):
|
|
82
|
+
"Poll server for token until received or timeout"
|
|
83
|
+
start = time()
|
|
84
|
+
client = httpx.Client()
|
|
85
|
+
url = endpoint(f"/cli_token?paircode={paircode}",local,port)
|
|
86
|
+
while time()-start < timeout:
|
|
87
|
+
resp = client.get(url).raise_for_status()
|
|
88
|
+
if resp.text.strip(): return dict(client.cookies)
|
|
89
|
+
sleep(interval)
|
|
90
|
+
|
|
91
|
+
@call_parse
|
|
92
|
+
def login(
|
|
93
|
+
local:bool=False, # Local dev
|
|
94
|
+
port:int=5002 # Port for local dev
|
|
95
|
+
):
|
|
96
|
+
"Authenticate CLI with server and save config"
|
|
97
|
+
paircode = secrets.token_urlsafe(16)
|
|
98
|
+
login_url = httpx.get(endpoint(f"/cli_login?paircode={paircode}",local,port)).text
|
|
99
|
+
print(f"Opening browser for authentication:\n{login_url}\n")
|
|
100
|
+
webbrowser.open(login_url)
|
|
101
|
+
|
|
102
|
+
cookies = poll_cookies(paircode, local, port)
|
|
103
|
+
if cookies:
|
|
104
|
+
Path(PLASH_CONFIG_HOME).write_text(json.dumps(cookies))
|
|
105
|
+
print(f"Authentication successful! Config saved to {PLASH_CONFIG_HOME}")
|
|
106
|
+
else: print("Authentication timed out.")
|
|
107
|
+
|
|
108
|
+
# %% ../nbs/00_core.ipynb 15
|
|
109
|
+
@call_parse
|
|
110
|
+
def deploy(
|
|
111
|
+
path:Path=Path('.'), # Path to project
|
|
112
|
+
local:bool=False, # Local dev
|
|
113
|
+
port:int=5002): # Port for local dev
|
|
114
|
+
"""🚀 Ship your app to production"""
|
|
115
|
+
print('Initializing deployment...')
|
|
116
|
+
validate_app(path)
|
|
117
|
+
tarz, filecount = create_tar_archive(path)
|
|
118
|
+
|
|
119
|
+
plash_app = Path(path) / '.plash'
|
|
120
|
+
if not plash_app.exists():
|
|
121
|
+
# Create the .plash file and write the app name
|
|
122
|
+
plash_app.write_text(f'export PLASH_APP_ID=fasthtml-app-{str(uuid4())[:8]}')
|
|
123
|
+
aid = parse_env(fn=plash_app)['PLASH_APP_ID']
|
|
124
|
+
|
|
125
|
+
resp = mk_auth_req(endpoint("/upload",local,port), "post", files={'file': tarz}, timeout=300.0, data={'aid': aid})
|
|
126
|
+
if resp.status_code == 200:
|
|
127
|
+
print('✅ Upload complete! Your app is currently being built.')
|
|
128
|
+
if local: print(f'It will be live at http://{aid}.localhost')
|
|
129
|
+
else: print(f'It will be live at https://{aid}.pla.sh')
|
|
130
|
+
else:
|
|
131
|
+
print(f'Failure {resp.status_code}')
|
|
132
|
+
print(f'Failure {resp.text}')
|
|
133
|
+
|
|
134
|
+
# %% ../nbs/00_core.ipynb 17
|
|
135
|
+
@call_parse
|
|
136
|
+
def view(
|
|
137
|
+
path:Path=Path('.'), # Path to project
|
|
138
|
+
local:bool=False, # Local dev
|
|
139
|
+
):
|
|
140
|
+
aid = get_app_id(path)
|
|
141
|
+
if local: url = f"http://{aid}.localhost"
|
|
142
|
+
else: url = f"https://{aid}.pla.sh"
|
|
143
|
+
print(f"Opening browser to view app :\n{url}\n")
|
|
144
|
+
webbrowser.open(url)
|
|
145
|
+
|
|
146
|
+
# %% ../nbs/00_core.ipynb 19
|
|
147
|
+
@call_parse
|
|
148
|
+
def delete(
|
|
149
|
+
path:Path=Path('.'), # Path to project
|
|
150
|
+
local:bool=False, # Local dev
|
|
151
|
+
port:int=5002, # Port for local dev
|
|
152
|
+
force:bool=False): # Skip confirmation prompt
|
|
153
|
+
'Delete your deployed app'
|
|
154
|
+
aid = get_app_id(path)
|
|
155
|
+
if not force:
|
|
156
|
+
confirm = input(f"Are you sure you want to delete app '{aid}'? This action cannot be undone. [y/N]: ")
|
|
157
|
+
if confirm.lower() not in ['y', 'yes']:
|
|
158
|
+
print("Deletion cancelled.")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
print(f"Deleting app '{aid}'...")
|
|
162
|
+
r = mk_auth_req(endpoint(f"/delete?aid={aid}",local,port), "delete")
|
|
163
|
+
return r.text
|
|
164
|
+
|
|
165
|
+
# %% ../nbs/00_core.ipynb 21
|
|
166
|
+
def endpoint_func(endpoint_name):
|
|
167
|
+
'Creates a function for a specific API endpoint'
|
|
168
|
+
@call_parse
|
|
169
|
+
def func(
|
|
170
|
+
path:Path=Path('.'), # Path to project
|
|
171
|
+
local:bool=False, # Local dev
|
|
172
|
+
port:int=5002): # Port for local dev
|
|
173
|
+
aid = get_app_id(path)
|
|
174
|
+
r = mk_auth_req(endpoint(f"{endpoint_name}?aid={aid}", local, port))
|
|
175
|
+
return r.text
|
|
176
|
+
|
|
177
|
+
# Set the function name and docstring
|
|
178
|
+
func.__name__ = endpoint_name
|
|
179
|
+
func.__doc__ = f"Access the '{endpoint_name}' endpoint for your app"
|
|
180
|
+
|
|
181
|
+
return func
|
|
182
|
+
|
|
183
|
+
# Create endpoint-specific functions
|
|
184
|
+
stop = endpoint_func('/stop')
|
|
185
|
+
start = endpoint_func('/start')
|
|
186
|
+
logs = endpoint_func('/logs')
|
|
187
|
+
|
|
188
|
+
# %% ../nbs/00_core.ipynb 23
|
|
189
|
+
@call_parse
|
|
190
|
+
def download(
|
|
191
|
+
path:Path=Path('.'), # Path to project
|
|
192
|
+
save_path:Path=Path("./download/"), # Save path (optional)
|
|
193
|
+
local:bool=False, # Local dev
|
|
194
|
+
port:int=5002, # Port for local dev
|
|
195
|
+
):
|
|
196
|
+
'Download your deployed app.'
|
|
197
|
+
aid = get_app_id(path)
|
|
198
|
+
try: save_path.mkdir(exist_ok=False)
|
|
199
|
+
except: print(f"ERROR: Save path ({save_path}) already exists. Please rename or delete this folder to avoid accidental overwrites.")
|
|
200
|
+
else:
|
|
201
|
+
response = mk_auth_req(endpoint(f'/download?aid={aid}', local, port)).raise_for_status()
|
|
202
|
+
file_bytes = io.BytesIO(response.content)
|
|
203
|
+
with tarfile.open(fileobj=file_bytes, mode="r:gz") as tar: tar.extractall(path=save_path)
|
|
204
|
+
print(f"Downloaded your app to: {save_path}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: plash_cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: CLI for the Plash hosting service
|
|
5
5
|
Home-page: https://github.com/AnswerDotAI/plash_cli
|
|
6
6
|
Author: Jeremy Howard
|
|
@@ -23,6 +23,19 @@ License-File: LICENSE
|
|
|
23
23
|
Requires-Dist: fastcore
|
|
24
24
|
Requires-Dist: httpx
|
|
25
25
|
Provides-Extra: dev
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: provides-extra
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
26
39
|
|
|
27
40
|
# plash-cli
|
|
28
41
|
|
|
@@ -47,20 +60,20 @@ $ pip install git+https://github.com/AnswerDotAI/plash-cli.git
|
|
|
47
60
|
or from [pypi](https://pypi.org/project/plash-cli/)
|
|
48
61
|
|
|
49
62
|
``` sh
|
|
50
|
-
$ pip install
|
|
63
|
+
$ pip install plash-cli
|
|
51
64
|
```
|
|
52
65
|
|
|
53
66
|
## Deploy Your First FastHTML App
|
|
54
67
|
|
|
55
|
-
###
|
|
68
|
+
### Authentication
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
to use Plash. To obtain these, do the following:
|
|
70
|
+
To use Plash, you’ll need to authenticate:
|
|
59
71
|
|
|
60
72
|
1. Signup for an account at https://pla.sh/
|
|
61
73
|
2. Activate your Plash subscription
|
|
62
|
-
3.
|
|
63
|
-
|
|
74
|
+
3. Run `plash_login` in your terminal
|
|
75
|
+
4. A browser window will open for authentication
|
|
76
|
+
5. Once authenticated, your credentials will be saved locally
|
|
64
77
|
|
|
65
78
|
### Create a FastHTML App
|
|
66
79
|
|
|
@@ -95,6 +108,19 @@ Run `plash_deploy`. Your app will be live at
|
|
|
95
108
|
`https://<app-name>.pla.sh`. The URL will be shown in the deployment
|
|
96
109
|
output.
|
|
97
110
|
|
|
111
|
+
### Managing Your App
|
|
112
|
+
|
|
113
|
+
Plash CLI provides several commands to manage your apps:
|
|
114
|
+
|
|
115
|
+
- `plash_login` - Login to Plash
|
|
116
|
+
- `plash_deploy` - Deploy your app
|
|
117
|
+
- `plash_view` - Open your deployed app in a browser
|
|
118
|
+
- `plash_start` - Start your app if it’s stopped
|
|
119
|
+
- `plash_stop` - Stop your running app
|
|
120
|
+
- `plash_logs` - View your app’s logs
|
|
121
|
+
- `plash_download` - Download your deployed app files
|
|
122
|
+
- `plash_delete` - Delete your deployed app
|
|
123
|
+
|
|
98
124
|
### App Dependencies
|
|
99
125
|
|
|
100
126
|
If your app needs additional dependencies to run, we offer a number of
|
|
@@ -147,3 +173,48 @@ in your Plash container, to modify your apps behavior for local and
|
|
|
147
173
|
production development. You can download any deployed database names by
|
|
148
174
|
clicking the Download App button to get a compressed file of all files
|
|
149
175
|
in your /app folder in your deployed app.
|
|
176
|
+
|
|
177
|
+
## Deploy to Pla.sh via GitHub Actions
|
|
178
|
+
|
|
179
|
+
If you’d like to deploy your plash app every time you commit changes to
|
|
180
|
+
your GitHub repo, you can use the following workflow to your
|
|
181
|
+
`.github/workflows/` folder in the root of your repo:
|
|
182
|
+
|
|
183
|
+
``` yaml
|
|
184
|
+
name: Deploy to Plash
|
|
185
|
+
|
|
186
|
+
on:
|
|
187
|
+
push:
|
|
188
|
+
branches:
|
|
189
|
+
- main
|
|
190
|
+
workflow_dispatch:
|
|
191
|
+
|
|
192
|
+
jobs:
|
|
193
|
+
deploy:
|
|
194
|
+
runs-on: ubuntu-latest
|
|
195
|
+
steps:
|
|
196
|
+
- name: Checkout repository
|
|
197
|
+
uses: actions/checkout@v3
|
|
198
|
+
|
|
199
|
+
- name: Set up Python
|
|
200
|
+
uses: actions/setup-python@v4
|
|
201
|
+
with:
|
|
202
|
+
python-version: '3.x'
|
|
203
|
+
|
|
204
|
+
- name: Create Plash config
|
|
205
|
+
run: |
|
|
206
|
+
mkdir -p ~/.config
|
|
207
|
+
echo '${{ secrets.PLASH_CONFIG }}' > ~/.config/plash_config.json
|
|
208
|
+
|
|
209
|
+
- name: Install plash-cli with pip
|
|
210
|
+
run: pip install plash-cli
|
|
211
|
+
|
|
212
|
+
- name: Deploy to Plash
|
|
213
|
+
run: plash_deploy
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
It relies on storing your plash config as a secret named `PLASH_CONFIG`
|
|
217
|
+
in your GitHub repo. After running `plash_login`, you can find these in
|
|
218
|
+
`~/.config/plash_config.json` (unless you haved changed the
|
|
219
|
+
XDG_CONFIG_HOME environment variable). Learn more about GitHub secrets
|
|
220
|
+
[here](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
plash_delete = plash_cli.core:delete
|
|
3
|
+
plash_deploy = plash_cli.core:deploy
|
|
4
|
+
plash_download = plash_cli.core:download
|
|
5
|
+
plash_login = plash_cli.core:login
|
|
6
|
+
plash_logs = plash_cli.core:logs
|
|
7
|
+
plash_start = plash_cli.core:start
|
|
8
|
+
plash_stop = plash_cli.core:stop
|
|
9
|
+
plash_view = plash_cli.core:view
|
|
10
|
+
|
|
11
|
+
[nbdev]
|
|
12
|
+
plash_cli = plash_cli._modidx:d
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name="plash_cli"
|
|
7
|
+
requires-python=">=3.7"
|
|
8
|
+
dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"]
|
|
9
|
+
|
|
10
|
+
[tool.uv]
|
|
11
|
+
cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = plash_cli
|
|
3
3
|
lib_name = plash_cli
|
|
4
|
-
version = 0.0.
|
|
4
|
+
version = 0.0.4
|
|
5
5
|
min_python = 3.7
|
|
6
6
|
license = apache2
|
|
7
7
|
black_formatting = False
|
|
@@ -28,6 +28,13 @@ status = 3
|
|
|
28
28
|
user = AnswerDotAI
|
|
29
29
|
requirements = fastcore httpx
|
|
30
30
|
console_scripts = plash_deploy=plash_cli.core:deploy
|
|
31
|
+
plash_login=plash_cli.core:login
|
|
32
|
+
plash_view=plash_cli.core:view
|
|
33
|
+
plash_logs=plash_cli.core:logs
|
|
34
|
+
plash_delete=plash_cli.core:delete
|
|
35
|
+
plash_stop=plash_cli.core:stop
|
|
36
|
+
plash_start=plash_cli.core:start
|
|
37
|
+
plash_download=plash_cli.core:download
|
|
31
38
|
readme_nb = index.ipynb
|
|
32
39
|
allowed_metadata_keys =
|
|
33
40
|
allowed_cell_metadata_keys =
|
|
@@ -36,4 +43,5 @@ clean_ids = True
|
|
|
36
43
|
clear_all = False
|
|
37
44
|
cell_number = True
|
|
38
45
|
skip_procs =
|
|
46
|
+
update_pyproject = True
|
|
39
47
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.2"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Autogenerated by nbdev
|
|
2
|
-
|
|
3
|
-
d = { 'settings': { 'branch': 'main',
|
|
4
|
-
'doc_baseurl': '/plash_cli',
|
|
5
|
-
'doc_host': 'https://AnswerDotAI.github.io',
|
|
6
|
-
'git_url': 'https://github.com/AnswerDotAI/plash_cli',
|
|
7
|
-
'lib_path': 'plash_cli'},
|
|
8
|
-
'syms': { 'plash_cli.core': { 'plash_cli.core.create_tar_archive': ('core.html#create_tar_archive', 'plash_cli/core.py'),
|
|
9
|
-
'plash_cli.core.deploy': ('core.html#deploy', 'plash_cli/core.py'),
|
|
10
|
-
'plash_cli.core.get_global_cfg': ('core.html#get_global_cfg', 'plash_cli/core.py'),
|
|
11
|
-
'plash_cli.core.is_included': ('core.html#is_included', 'plash_cli/core.py'),
|
|
12
|
-
'plash_cli.core.validate_app': ('core.html#validate_app', 'plash_cli/core.py')}}}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""The Plash CLI tool"""
|
|
2
|
-
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
4
|
-
|
|
5
|
-
# %% auto 0
|
|
6
|
-
__all__ = ['PLASH_CONFIG_HOME', 'get_global_cfg', 'is_included', 'create_tar_archive', 'validate_app', 'deploy']
|
|
7
|
-
|
|
8
|
-
# %% ../nbs/00_core.ipynb 2
|
|
9
|
-
from fastcore.all import *
|
|
10
|
-
from fastcore.xdg import *
|
|
11
|
-
from httpx import post as xpost
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from uuid import uuid4
|
|
14
|
-
|
|
15
|
-
import io, sys, tarfile
|
|
16
|
-
|
|
17
|
-
# %% ../nbs/00_core.ipynb 3
|
|
18
|
-
PLASH_CONFIG_HOME = xdg_config_home() / 'plash.env'
|
|
19
|
-
|
|
20
|
-
# %% ../nbs/00_core.ipynb 4
|
|
21
|
-
def get_global_cfg():
|
|
22
|
-
"""Works for all operating systems."""
|
|
23
|
-
try: return parse_env(fn=Path(PLASH_CONFIG_HOME))
|
|
24
|
-
except FileNotFoundError:
|
|
25
|
-
Path(PLASH_CONFIG_HOME).touch()
|
|
26
|
-
return parse_env(fn=Path(PLASH_CONFIG_HOME))
|
|
27
|
-
|
|
28
|
-
# %% ../nbs/00_core.ipynb 5
|
|
29
|
-
def is_included(path):
|
|
30
|
-
"Returns True if path should be included in deployment"
|
|
31
|
-
if path.name.startswith('.'): return False
|
|
32
|
-
if path.suffix == '.pyc': return False
|
|
33
|
-
excludes = {'.git', '__pycache__', '.gitignore', '.env',
|
|
34
|
-
'.pytest_cache', '.venv', 'venv', '.ipynb_checkpoints',
|
|
35
|
-
'.vscode', '.idea', '.sesskey'}
|
|
36
|
-
return not any(p in excludes for p in path.parts)
|
|
37
|
-
|
|
38
|
-
# %% ../nbs/00_core.ipynb 6
|
|
39
|
-
def create_tar_archive(path # Path to directory containing FastHTML app
|
|
40
|
-
)->io.BytesIO: # Buffer of tar directory
|
|
41
|
-
"Creates a tar archive of a directory, excluding files based on is_included"
|
|
42
|
-
buf = io.BytesIO()
|
|
43
|
-
files = L(Path(path).iterdir()).filter(is_included)
|
|
44
|
-
|
|
45
|
-
with tarfile.open(fileobj=buf, mode='w:gz') as tar:
|
|
46
|
-
for f in files: tar.add(f, arcname=f.name)
|
|
47
|
-
buf.seek(0)
|
|
48
|
-
return buf, len(files)
|
|
49
|
-
|
|
50
|
-
# %% ../nbs/00_core.ipynb 7
|
|
51
|
-
def validate_app(path):
|
|
52
|
-
"Validates that the app in the directory `path` is deployable as a FastHTML app"
|
|
53
|
-
print("Analyzing project structure...")
|
|
54
|
-
|
|
55
|
-
main_file = Path(path) / "main.py"
|
|
56
|
-
if not main_file.exists():
|
|
57
|
-
print('[red bold]ERROR: Your FastHTML app must have a main.py[/red bold]')
|
|
58
|
-
print(f'Your path is: [bold]{path}[/bold]')
|
|
59
|
-
sys.exit(1)
|
|
60
|
-
|
|
61
|
-
# %% ../nbs/00_core.ipynb 8
|
|
62
|
-
@call_parse
|
|
63
|
-
def deploy(
|
|
64
|
-
path:Path=Path('.'), # Path to project
|
|
65
|
-
local:bool=False, # local dev
|
|
66
|
-
port:int=5002):
|
|
67
|
-
"""🚀 Ship your app to production"""
|
|
68
|
-
print('Initializing deployment...')
|
|
69
|
-
validate_app(path)
|
|
70
|
-
tarz, filecount = create_tar_archive(path)
|
|
71
|
-
|
|
72
|
-
plash_app = Path(path) / '.plash'
|
|
73
|
-
if not plash_app.exists():
|
|
74
|
-
# Create the .plash file and write the app name
|
|
75
|
-
plash_app.write_text(f'export PLASH_APP_ID=fasthtml-app-{str(uuid4())[:8]}')
|
|
76
|
-
|
|
77
|
-
aid = parse_env(fn=plash_app)['PLASH_APP_ID']
|
|
78
|
-
cfg = get_global_cfg()
|
|
79
|
-
url = 'https://pla.sh/upload'
|
|
80
|
-
if local: url = f'http://localhost:{port}/upload'
|
|
81
|
-
headers = {'Authorization': f'Bearer {cfg["PLASH_TOKEN"]}'}
|
|
82
|
-
print(f'Uploading {filecount} files...')
|
|
83
|
-
resp = xpost(url, headers=headers, files={'file': tarz}, timeout=300.0,
|
|
84
|
-
data={'aid': aid, 'email': cfg['PLASH_EMAIL']})
|
|
85
|
-
if resp.status_code == 200:
|
|
86
|
-
print('✅ Upload complete! Your app is currently being built.')
|
|
87
|
-
print(f'It will be live at https://{aid}.pla.sh')
|
|
88
|
-
else:
|
|
89
|
-
print(f'Failure {resp.status_code}')
|
|
90
|
-
print(f'Failure {resp.text}')
|
plash_cli-0.0.2/pyproject.toml
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|