plash-cli 0.3.12__tar.gz → 0.3.14__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.3.12/plash_cli.egg-info → plash_cli-0.3.14}/PKG-INFO +12 -26
- plash_cli-0.3.14/plash_cli/__init__.py +2 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli/auth.py +21 -15
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli/cli.py +58 -52
- {plash_cli-0.3.12 → plash_cli-0.3.14/plash_cli.egg-info}/PKG-INFO +12 -26
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli.egg-info/SOURCES.txt +0 -3
- plash_cli-0.3.14/plash_cli.egg-info/entry_points.txt +13 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli.egg-info/requires.txt +2 -2
- plash_cli-0.3.14/pyproject.toml +51 -0
- plash_cli-0.3.12/plash_cli/__init__.py +0 -2
- plash_cli-0.3.12/plash_cli.egg-info/entry_points.txt +0 -13
- plash_cli-0.3.12/plash_cli.egg-info/not-zip-safe +0 -1
- plash_cli-0.3.12/pyproject.toml +0 -11
- plash_cli-0.3.12/settings.ini +0 -49
- plash_cli-0.3.12/setup.py +0 -64
- {plash_cli-0.3.12 → plash_cli-0.3.14}/LICENSE +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/MANIFEST.in +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/README.md +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli/_bash_magic.py +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli/_modidx.py +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli/assets/es256_public_key.pem +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli.egg-info/dependency_links.txt +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/plash_cli.egg-info/top_level.txt +0 -0
- {plash_cli-0.3.12 → plash_cli-0.3.14}/setup.cfg +0 -0
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plash_cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.14
|
|
4
4
|
Summary: CLI for the Plash hosting service
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Keywords: nbdev
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
5
|
+
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/AnswerDotAI/plash_cli
|
|
8
|
+
Project-URL: Documentation, https://AnswerDotAI.github.io/plash_cli
|
|
9
|
+
Keywords: nbdev,jupyter,notebook,python
|
|
12
10
|
Classifier: Natural Language :: English
|
|
13
|
-
Classifier:
|
|
14
|
-
Classifier:
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier:
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
15
|
Requires-Python: >=3.10
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: fastcore
|
|
18
|
+
Requires-Dist: fastcore>=1.12.27
|
|
21
19
|
Requires-Dist: httpx>=0.28.1
|
|
22
20
|
Requires-Dist: python-dotenv
|
|
23
21
|
Requires-Dist: pyjwt
|
|
24
22
|
Requires-Dist: cryptography
|
|
25
23
|
Provides-Extra: dev
|
|
26
24
|
Requires-Dist: bash_kernel; extra == "dev"
|
|
27
|
-
Requires-Dist: nbdev; extra == "dev"
|
|
28
|
-
Dynamic: author
|
|
29
|
-
Dynamic: author-email
|
|
30
|
-
Dynamic: classifier
|
|
31
|
-
Dynamic: description
|
|
32
|
-
Dynamic: description-content-type
|
|
33
|
-
Dynamic: home-page
|
|
34
|
-
Dynamic: keywords
|
|
35
|
-
Dynamic: license
|
|
25
|
+
Requires-Dist: nbdev>=3; extra == "dev"
|
|
36
26
|
Dynamic: license-file
|
|
37
|
-
Dynamic: provides-extra
|
|
38
|
-
Dynamic: requires-dist
|
|
39
|
-
Dynamic: requires-python
|
|
40
|
-
Dynamic: summary
|
|
41
27
|
|
|
42
28
|
# Plash
|
|
43
29
|
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
"""Client side logic to add Plash Auth to your app
|
|
1
|
+
"""Client side logic to add Plash Auth to your app
|
|
2
|
+
|
|
3
|
+
Docs: https://AnswerDotAI.github.io/plash_cli/auth.html.md"""
|
|
2
4
|
|
|
3
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_auth.ipynb.
|
|
4
6
|
|
|
5
|
-
# %% auto 0
|
|
7
|
+
# %% auto #0
|
|
6
8
|
__all__ = ['signin_completed_rt', 'mk_signin_url', 'PlashAuthError', 'goog_id_from_signin_reply']
|
|
7
9
|
|
|
8
|
-
# %% ../nbs/01_auth.ipynb
|
|
10
|
+
# %% ../nbs/01_auth.ipynb #c6aa552a
|
|
9
11
|
import httpx,os,jwt
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
from warnings import warn
|
|
12
14
|
|
|
13
15
|
from . import __version__
|
|
14
16
|
|
|
15
|
-
# %% ../nbs/01_auth.ipynb
|
|
17
|
+
# %% ../nbs/01_auth.ipynb #d3504f51
|
|
16
18
|
_in_prod = os.getenv('PLASH_PRODUCTION', '') == '1'
|
|
17
19
|
|
|
18
|
-
# %% ../nbs/01_auth.ipynb
|
|
20
|
+
# %% ../nbs/01_auth.ipynb #e6590075
|
|
19
21
|
def _signin_url(email_re: str=None, hd_re: str=None):
|
|
20
22
|
res = httpx.post(os.environ['PLASH_AUTH_URL'], json=dict(email_re=email_re, hd_re=hd_re),
|
|
21
23
|
auth=(os.environ['PLASH_APP_ID'], os.environ['PLASH_APP_SECRET']),
|
|
@@ -23,20 +25,22 @@ def _signin_url(email_re: str=None, hd_re: str=None):
|
|
|
23
25
|
if "warning" in res: warn(res.pop('warning'))
|
|
24
26
|
return res
|
|
25
27
|
|
|
26
|
-
# %% ../nbs/01_auth.ipynb
|
|
28
|
+
# %% ../nbs/01_auth.ipynb #4b15411c
|
|
27
29
|
signin_completed_rt = "/signin_completed"
|
|
28
30
|
|
|
29
|
-
# %% ../nbs/01_auth.ipynb
|
|
30
|
-
def mk_signin_url(
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
# %% ../nbs/01_auth.ipynb #74a0a24d
|
|
32
|
+
def mk_signin_url(
|
|
33
|
+
session: dict, # Session dictionary
|
|
34
|
+
email_re: str=None, # Regex filter for allowed email addresses
|
|
35
|
+
hd_re: str=None # Regex filter for allowed Google hosted domains
|
|
36
|
+
):
|
|
33
37
|
"Generate a Google Sign-In URL for Plash authentication."
|
|
34
38
|
if not _in_prod: return f"{signin_completed_rt}?signin_reply=mock-sign-in-reply"
|
|
35
39
|
res = _signin_url(email_re, hd_re)
|
|
36
40
|
session['req_id'] = res['req_id']
|
|
37
41
|
return res['plash_signin_url']
|
|
38
42
|
|
|
39
|
-
# %% ../nbs/01_auth.ipynb
|
|
43
|
+
# %% ../nbs/01_auth.ipynb #8e66bb6e
|
|
40
44
|
def _parse_jwt(reply: str) -> dict:
|
|
41
45
|
"Parse JWT reply and return decoded claims or error info"
|
|
42
46
|
try: decoded = jwt.decode(reply, key=open(Path(__file__).parent / "assets" / "es256_public_key.pem","rb").read(), algorithms=["ES256"],
|
|
@@ -44,14 +48,16 @@ def _parse_jwt(reply: str) -> dict:
|
|
|
44
48
|
except Exception as e: return dict(req_id=None, sub=None, err=f'JWT validation failed: {e}')
|
|
45
49
|
return dict(req_id=decoded.get('req_id'), sub=decoded.get('sub'), err=decoded.get('err'))
|
|
46
50
|
|
|
47
|
-
# %% ../nbs/01_auth.ipynb
|
|
51
|
+
# %% ../nbs/01_auth.ipynb #0e61fbdd
|
|
48
52
|
class PlashAuthError(Exception):
|
|
49
53
|
"""Raised when Plash authentication fails"""
|
|
50
54
|
pass
|
|
51
55
|
|
|
52
|
-
# %% ../nbs/01_auth.ipynb
|
|
53
|
-
def goog_id_from_signin_reply(
|
|
54
|
-
|
|
56
|
+
# %% ../nbs/01_auth.ipynb #a311ce9a
|
|
57
|
+
def goog_id_from_signin_reply(
|
|
58
|
+
session: dict, # Session dictionary containing 'req_id'
|
|
59
|
+
reply: str # The JWT reply string from Plash after Google authentication
|
|
60
|
+
):
|
|
55
61
|
"Validate Google sign-in reply and returns Google user ID if valid."
|
|
56
62
|
if not _in_prod: return '424242424242424242424'
|
|
57
63
|
parsed = _parse_jwt(reply)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
"""The Plash CLI tool
|
|
1
|
+
"""The Plash CLI tool
|
|
2
|
+
|
|
3
|
+
Docs: https://AnswerDotAI.github.io/plash_cli/cli.html.md"""
|
|
2
4
|
|
|
3
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_cli.ipynb.
|
|
4
6
|
|
|
5
|
-
# %% auto 0
|
|
7
|
+
# %% auto #0
|
|
6
8
|
__all__ = ['PLASH_CONFIG_HOME', 'PLASH_DOMAIN', 'pat', 'log_modes', 'PlashError', 'login', 'create_tar_archive', 'deploy', 'view',
|
|
7
9
|
'delete_app', 'start_app', 'stop_app', 'logs', 'download_app', 'app_list', 'plash_tool_info']
|
|
8
10
|
|
|
9
|
-
# %% ../nbs/00_cli.ipynb
|
|
11
|
+
# %% ../nbs/00_cli.ipynb #702c219c
|
|
10
12
|
from fastcore.all import *
|
|
11
13
|
from fastcore.xdg import *
|
|
12
14
|
import secrets, webbrowser, httpx, io, tarfile, random, string
|
|
@@ -17,11 +19,11 @@ import io, os, re, tarfile
|
|
|
17
19
|
|
|
18
20
|
from . import __version__
|
|
19
21
|
|
|
20
|
-
# %% ../nbs/00_cli.ipynb
|
|
22
|
+
# %% ../nbs/00_cli.ipynb #2fc99aa4
|
|
21
23
|
PLASH_CONFIG_HOME = xdg_config_home() / 'plash_config.json'
|
|
22
24
|
PLASH_DOMAIN = os.getenv("PLASH_DOMAIN","pla.sh") # pla.sh plash-dev.answer.ai localhost:5002
|
|
23
25
|
|
|
24
|
-
# %% ../nbs/00_cli.ipynb
|
|
26
|
+
# %% ../nbs/00_cli.ipynb #1de1e2b3
|
|
25
27
|
def _get_client(cfg=PLASH_CONFIG_HOME):
|
|
26
28
|
client = httpx.Client()
|
|
27
29
|
if tok := os.getenv("PLASH_TOKEN"): cookies = {"session_": tok}
|
|
@@ -31,13 +33,14 @@ def _get_client(cfg=PLASH_CONFIG_HOME):
|
|
|
31
33
|
client.headers.update({'X-PLASH': 'true', 'User-Agent': f'plash_cli/{__version__}'})
|
|
32
34
|
return client
|
|
33
35
|
|
|
34
|
-
# %% ../nbs/00_cli.ipynb
|
|
36
|
+
# %% ../nbs/00_cli.ipynb #47d01e47
|
|
35
37
|
def _mk_auth_req(url:str, method:str='get', timeout=300., **kwargs):
|
|
36
38
|
r = getattr(_get_client(), method)(url, timeout=timeout, **kwargs)
|
|
37
39
|
if r.status_code == 200: return r
|
|
38
|
-
|
|
40
|
+
msg = r.headers.get('X-Plash-Error') or r.text.strip() or f'HTTP {r.status_code}'
|
|
41
|
+
raise PlashError(msg)
|
|
39
42
|
|
|
40
|
-
# %% ../nbs/00_cli.ipynb
|
|
43
|
+
# %% ../nbs/00_cli.ipynb #7edae5c8
|
|
41
44
|
def _get_app_name(path:Path):
|
|
42
45
|
plash_app = Path(path) / '.plash'
|
|
43
46
|
if not plash_app.exists(): raise FileNotFoundError(f"File not found: {plash_app=}")
|
|
@@ -49,15 +52,15 @@ def _get_app_name(path:Path):
|
|
|
49
52
|
raise RuntimeError(f"{plash_app=} did not have a PLASH_APP_NAME")
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
# %% ../nbs/00_cli.ipynb
|
|
55
|
+
# %% ../nbs/00_cli.ipynb #b607f7a9
|
|
53
56
|
def _prep(path,name): return name if name else _get_app_name(Path(path))
|
|
54
57
|
|
|
55
|
-
# %% ../nbs/00_cli.ipynb
|
|
58
|
+
# %% ../nbs/00_cli.ipynb #acaf42cf
|
|
56
59
|
def _endpoint(sub='', rt=''):
|
|
57
60
|
p = "http" if "localhost" in PLASH_DOMAIN else "https"
|
|
58
61
|
return f"{p}://{sub}{'.' if sub else ''}{PLASH_DOMAIN}{rt}"
|
|
59
62
|
|
|
60
|
-
# %% ../nbs/00_cli.ipynb
|
|
63
|
+
# %% ../nbs/00_cli.ipynb #d1638ced
|
|
61
64
|
def _is_included(path):
|
|
62
65
|
"Returns True if path should be included in deployment"
|
|
63
66
|
if path.name.startswith('.'): return False
|
|
@@ -67,10 +70,10 @@ def _is_included(path):
|
|
|
67
70
|
'.vscode', '.idea', '.sesskey'}
|
|
68
71
|
return not any(p in excludes for p in path.parts)
|
|
69
72
|
|
|
70
|
-
# %% ../nbs/00_cli.ipynb
|
|
73
|
+
# %% ../nbs/00_cli.ipynb #a20045ed
|
|
71
74
|
class PlashError(Exception): pass
|
|
72
75
|
|
|
73
|
-
# %% ../nbs/00_cli.ipynb
|
|
76
|
+
# %% ../nbs/00_cli.ipynb #b7d4028e
|
|
74
77
|
def _poll_cookies(paircode, interval=1, timeout=180):
|
|
75
78
|
"Poll server for token until received or timeout"
|
|
76
79
|
start = time()
|
|
@@ -81,7 +84,7 @@ def _poll_cookies(paircode, interval=1, timeout=180):
|
|
|
81
84
|
if resp.text.strip(): return dict(client.cookies)
|
|
82
85
|
sleep(interval)
|
|
83
86
|
|
|
84
|
-
# %% ../nbs/00_cli.ipynb
|
|
87
|
+
# %% ../nbs/00_cli.ipynb #eadaa5df
|
|
85
88
|
@call_parse
|
|
86
89
|
def login(
|
|
87
90
|
token:str=None, # Token to save directly to config
|
|
@@ -105,7 +108,7 @@ def login(
|
|
|
105
108
|
print(f"Authentication successful! Config saved to {PLASH_CONFIG_HOME}")
|
|
106
109
|
else: print("Authentication timed out.")
|
|
107
110
|
|
|
108
|
-
# %% ../nbs/00_cli.ipynb
|
|
111
|
+
# %% ../nbs/00_cli.ipynb #d52c060e
|
|
109
112
|
pat = r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$'
|
|
110
113
|
|
|
111
114
|
def _deps(script: bytes | str):
|
|
@@ -121,7 +124,7 @@ def _deps(script: bytes | str):
|
|
|
121
124
|
return '\n'.join(tomllib.loads(content)['dependencies'])
|
|
122
125
|
else: return None
|
|
123
126
|
|
|
124
|
-
# %% ../nbs/00_cli.ipynb
|
|
127
|
+
# %% ../nbs/00_cli.ipynb #3747912c
|
|
125
128
|
def _validate_app(path):
|
|
126
129
|
"Validates directory `path` is a deployable Plash app"
|
|
127
130
|
if not (path / 'main.py').exists():
|
|
@@ -130,7 +133,7 @@ def _validate_app(path):
|
|
|
130
133
|
if deps and (path/"requirements.txt").exists():
|
|
131
134
|
raise PlashError('A Plash app should not contain both a requirements.txt file and inline dependencies (see PEP723).')
|
|
132
135
|
|
|
133
|
-
# %% ../nbs/00_cli.ipynb
|
|
136
|
+
# %% ../nbs/00_cli.ipynb #ee0a7f14
|
|
134
137
|
def create_tar_archive(path:Path, force_data:bool=False) -> tuple[io.BytesIO, int]:
|
|
135
138
|
"Creates a tar archive of a directory, excluding files based on is_included"
|
|
136
139
|
tarz = io.BytesIO()
|
|
@@ -145,11 +148,12 @@ def create_tar_archive(path:Path, force_data:bool=False) -> tuple[io.BytesIO, in
|
|
|
145
148
|
tarz.seek(0)
|
|
146
149
|
return tarz, len(files)
|
|
147
150
|
|
|
148
|
-
# %% ../nbs/00_cli.ipynb
|
|
151
|
+
# %% ../nbs/00_cli.ipynb #2f2a6e00
|
|
149
152
|
def deploy(
|
|
150
153
|
path:Path='.', # Path to project
|
|
151
154
|
name:str=None, # Overrides the .plash file in project root if provided
|
|
152
|
-
_force_data:bool=False
|
|
155
|
+
_force_data:bool=False # Overwrite data/ directory during deployment
|
|
156
|
+
):
|
|
153
157
|
"""
|
|
154
158
|
Deploys app to production. By default, this command erases all files in your app which are not in data/.
|
|
155
159
|
Then uploads all files and folders, except paths starting with `.` and except the local data/ directory.
|
|
@@ -169,17 +173,16 @@ def deploy(
|
|
|
169
173
|
plash_app.write_text(f'export PLASH_APP_NAME={name}')
|
|
170
174
|
|
|
171
175
|
tarz, _ = create_tar_archive(path, _force_data)
|
|
172
|
-
|
|
173
|
-
data={'name': name, 'force_data': _force_data})
|
|
174
|
-
if not r: raise PlashError('Unknown failure')
|
|
176
|
+
_mk_auth_req(_endpoint(rt="/upload"), "post", files={'file': tarz}, data={'name': name, 'force_data': _force_data})
|
|
175
177
|
return name if "." in name else _endpoint(sub=name)
|
|
176
178
|
|
|
177
|
-
# %% ../nbs/00_cli.ipynb
|
|
179
|
+
# %% ../nbs/00_cli.ipynb #59d06b03
|
|
178
180
|
@call_parse
|
|
179
181
|
def _deploy(
|
|
180
182
|
path:Path='.', # Path to project
|
|
181
183
|
name:str=None, # Overrides the .plash file in project root if provided
|
|
182
|
-
force_data:bool=False
|
|
184
|
+
force_data:bool=False # Overwrite data/ directory during deployment
|
|
185
|
+
):
|
|
183
186
|
print('Initializing deployment...')
|
|
184
187
|
try: res = deploy(path=path, name=name, _force_data=force_data)
|
|
185
188
|
except PlashError as e: return str(e)
|
|
@@ -187,7 +190,7 @@ def _deploy(
|
|
|
187
190
|
f'It will be live at {res}')
|
|
188
191
|
_deploy.__doc__ = deploy.__doc__
|
|
189
192
|
|
|
190
|
-
# %% ../nbs/00_cli.ipynb
|
|
193
|
+
# %% ../nbs/00_cli.ipynb #75f7d1ab
|
|
191
194
|
@call_parse
|
|
192
195
|
def view(
|
|
193
196
|
path:Path='.', # Path to project directory
|
|
@@ -199,20 +202,22 @@ def view(
|
|
|
199
202
|
print(f"Opening browser to view app :\n{url}\n")
|
|
200
203
|
webbrowser.open(url)
|
|
201
204
|
|
|
202
|
-
# %% ../nbs/00_cli.ipynb
|
|
205
|
+
# %% ../nbs/00_cli.ipynb #43535aef
|
|
203
206
|
def delete_app(
|
|
204
207
|
path:Path='.', # Path to project
|
|
205
|
-
name:str=None
|
|
208
|
+
name:str=None # Overrides the .plash file in project root if provided
|
|
209
|
+
):
|
|
206
210
|
'Delete your deployed app'
|
|
207
211
|
name = _prep(path, name)
|
|
208
|
-
|
|
209
|
-
if not r: raise PlashError('Failed to delete app')
|
|
212
|
+
_mk_auth_req(_endpoint(rt=f"/delete?name={name}"), "delete")
|
|
210
213
|
return f"App '{name}' deleted successfully"
|
|
211
214
|
|
|
212
215
|
@call_parse
|
|
213
216
|
@delegates(delete_app)
|
|
214
|
-
def _delete(
|
|
215
|
-
|
|
217
|
+
def _delete(
|
|
218
|
+
force:bool=False, # Skip confirmation prompt
|
|
219
|
+
**kwargs
|
|
220
|
+
):
|
|
216
221
|
'Delete your deployed app'
|
|
217
222
|
if not force:
|
|
218
223
|
confirm = input("Are you sure you want to delete the app? [y/N]: ")
|
|
@@ -221,14 +226,14 @@ def _delete(force:bool=False, # Skip confirmation prompt
|
|
|
221
226
|
except PlashError as e: return str(e)
|
|
222
227
|
_delete.__doc__ = delete_app.__doc__
|
|
223
228
|
|
|
224
|
-
# %% ../nbs/00_cli.ipynb
|
|
229
|
+
# %% ../nbs/00_cli.ipynb #d72ccd37
|
|
225
230
|
def start_app(
|
|
226
231
|
path:Path='.', # Path to project
|
|
227
|
-
name:str=None
|
|
232
|
+
name:str=None # Overrides the .plash file in project root if provided
|
|
233
|
+
):
|
|
228
234
|
'Start your deployed app'
|
|
229
235
|
name = _prep(path, name)
|
|
230
|
-
|
|
231
|
-
if not r: raise PlashError('Failed to start app')
|
|
236
|
+
_mk_auth_req(_endpoint(rt=f"/start?name={name}"))
|
|
232
237
|
return f"App '{name}' started"
|
|
233
238
|
|
|
234
239
|
@call_parse
|
|
@@ -238,14 +243,14 @@ def _start(**kwargs):
|
|
|
238
243
|
except PlashError as e: return str(e)
|
|
239
244
|
_start.__doc__ = start_app.__doc__
|
|
240
245
|
|
|
241
|
-
# %% ../nbs/00_cli.ipynb
|
|
246
|
+
# %% ../nbs/00_cli.ipynb #ace91510
|
|
242
247
|
def stop_app(
|
|
243
248
|
path:Path='.', # Path to project
|
|
244
|
-
name:str=None
|
|
249
|
+
name:str=None # Overrides the .plash file in project root if provided
|
|
250
|
+
):
|
|
245
251
|
'Stop your deployed app'
|
|
246
252
|
name = _prep(path, name)
|
|
247
|
-
|
|
248
|
-
if not r: raise PlashError('Failed to stop app')
|
|
253
|
+
_mk_auth_req(_endpoint(rt=f"/stop?name={name}"))
|
|
249
254
|
return f"App '{name}' stopped"
|
|
250
255
|
|
|
251
256
|
@call_parse
|
|
@@ -255,24 +260,26 @@ def _stop(**kwargs):
|
|
|
255
260
|
except PlashError as e: return str(e)
|
|
256
261
|
_stop.__doc__ = stop_app.__doc__
|
|
257
262
|
|
|
258
|
-
# %% ../nbs/00_cli.ipynb
|
|
263
|
+
# %% ../nbs/00_cli.ipynb #24e62f1f
|
|
259
264
|
log_modes = str_enum('log_modes', 'build', 'app')
|
|
260
265
|
|
|
261
|
-
# %% ../nbs/00_cli.ipynb
|
|
266
|
+
# %% ../nbs/00_cli.ipynb #4c1597b9
|
|
262
267
|
def logs(
|
|
263
268
|
path:Path='.', # Path to project
|
|
264
269
|
name:str=None, # Overrides the .plash file in project root if provided
|
|
265
|
-
mode:log_modes='build'
|
|
270
|
+
mode:log_modes='build' # Choose between build or app logs
|
|
271
|
+
):
|
|
266
272
|
'Get logs for your deployed app'
|
|
267
273
|
name = _prep(path, name)
|
|
268
274
|
r = _mk_auth_req(_endpoint(rt=f"/logs?name={name}&mode={mode}"))
|
|
269
|
-
if not r: raise PlashError('Failed to retrieve logs')
|
|
270
275
|
return r.text
|
|
271
276
|
|
|
272
277
|
@call_parse
|
|
273
278
|
@delegates(logs)
|
|
274
|
-
def _logs(
|
|
275
|
-
|
|
279
|
+
def _logs(
|
|
280
|
+
tail:bool=False, # Tail the logs
|
|
281
|
+
**kwargs
|
|
282
|
+
):
|
|
276
283
|
try:
|
|
277
284
|
if not tail: return print(logs(**kwargs))
|
|
278
285
|
text = ''
|
|
@@ -287,22 +294,22 @@ def _logs(tail:bool=False, # Tail the logs
|
|
|
287
294
|
except PlashError as e: return str(e)
|
|
288
295
|
_logs.__doc__ = logs.__doc__
|
|
289
296
|
|
|
290
|
-
# %% ../nbs/00_cli.ipynb
|
|
297
|
+
# %% ../nbs/00_cli.ipynb #f8441125
|
|
291
298
|
@patch
|
|
292
299
|
def _is_dir_empty(self:Path): return next(self.iterdir(), None) is None
|
|
293
300
|
|
|
294
|
-
# %% ../nbs/00_cli.ipynb
|
|
301
|
+
# %% ../nbs/00_cli.ipynb #1c057f57
|
|
295
302
|
def download_app(
|
|
296
303
|
path:Path='.', # Path to project
|
|
297
304
|
name:str=None, # Overrides the .plash file in project root if provided
|
|
298
|
-
save_path:Path="./download/"
|
|
305
|
+
save_path:Path="./download/" # Save path (optional)
|
|
306
|
+
):
|
|
299
307
|
"Download deployed app to save_path."
|
|
300
308
|
name = _prep(path, name)
|
|
301
309
|
save_path = Path(save_path)
|
|
302
310
|
save_path.mkdir(exist_ok=True)
|
|
303
311
|
if not save_path._is_dir_empty(): raise PlashError(f'Save path ({save_path}) is not empty.')
|
|
304
312
|
r = _mk_auth_req(_endpoint(rt=f'/download?name={name}'))
|
|
305
|
-
if not r: raise PlashError('Download request failed')
|
|
306
313
|
with tarfile.open(fileobj=io.BytesIO(r.content), mode="r:gz") as tar:
|
|
307
314
|
tar.extractall(path=save_path, filter='data')
|
|
308
315
|
return save_path
|
|
@@ -314,11 +321,10 @@ def _download(**kwargs):
|
|
|
314
321
|
except PlashError as e: return str(e)
|
|
315
322
|
_download.__doc__ = download_app.__doc__
|
|
316
323
|
|
|
317
|
-
# %% ../nbs/00_cli.ipynb
|
|
324
|
+
# %% ../nbs/00_cli.ipynb #8378e31d
|
|
318
325
|
def app_list():
|
|
319
326
|
"List your deployed apps"
|
|
320
327
|
r = _mk_auth_req(_endpoint(rt="/user_apps"))
|
|
321
|
-
if not r: raise PlashError('Failed to retrieve')
|
|
322
328
|
return r.json()
|
|
323
329
|
|
|
324
330
|
@call_parse
|
|
@@ -329,7 +335,7 @@ def _app_list(verbose:bool=False): # Whether to show running status as well as n
|
|
|
329
335
|
for a in res: print(f"{a['running']} {a['name']}" if verbose else a['name'])
|
|
330
336
|
_app_list.__doc__ = app_list.__doc__
|
|
331
337
|
|
|
332
|
-
# %% ../nbs/00_cli.ipynb
|
|
338
|
+
# %% ../nbs/00_cli.ipynb #6ff07c8d
|
|
333
339
|
def plash_tool_info():
|
|
334
340
|
from dialoghelper import add_msg
|
|
335
341
|
add_msg('Plash tools: &`[login, deploy, start_app, stop_app, logs, download_app, app_list]`')
|
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plash_cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.14
|
|
4
4
|
Summary: CLI for the Plash hosting service
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Keywords: nbdev
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
5
|
+
Author-email: Jeremy Howard <info@fast.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/AnswerDotAI/plash_cli
|
|
8
|
+
Project-URL: Documentation, https://AnswerDotAI.github.io/plash_cli
|
|
9
|
+
Keywords: nbdev,jupyter,notebook,python
|
|
12
10
|
Classifier: Natural Language :: English
|
|
13
|
-
Classifier:
|
|
14
|
-
Classifier:
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier:
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
15
|
Requires-Python: >=3.10
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: fastcore
|
|
18
|
+
Requires-Dist: fastcore>=1.12.27
|
|
21
19
|
Requires-Dist: httpx>=0.28.1
|
|
22
20
|
Requires-Dist: python-dotenv
|
|
23
21
|
Requires-Dist: pyjwt
|
|
24
22
|
Requires-Dist: cryptography
|
|
25
23
|
Provides-Extra: dev
|
|
26
24
|
Requires-Dist: bash_kernel; extra == "dev"
|
|
27
|
-
Requires-Dist: nbdev; extra == "dev"
|
|
28
|
-
Dynamic: author
|
|
29
|
-
Dynamic: author-email
|
|
30
|
-
Dynamic: classifier
|
|
31
|
-
Dynamic: description
|
|
32
|
-
Dynamic: description-content-type
|
|
33
|
-
Dynamic: home-page
|
|
34
|
-
Dynamic: keywords
|
|
35
|
-
Dynamic: license
|
|
25
|
+
Requires-Dist: nbdev>=3; extra == "dev"
|
|
36
26
|
Dynamic: license-file
|
|
37
|
-
Dynamic: provides-extra
|
|
38
|
-
Dynamic: requires-dist
|
|
39
|
-
Dynamic: requires-python
|
|
40
|
-
Dynamic: summary
|
|
41
27
|
|
|
42
28
|
# Plash
|
|
43
29
|
|
|
@@ -2,8 +2,6 @@ LICENSE
|
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.md
|
|
4
4
|
pyproject.toml
|
|
5
|
-
settings.ini
|
|
6
|
-
setup.py
|
|
7
5
|
plash_cli/__init__.py
|
|
8
6
|
plash_cli/_bash_magic.py
|
|
9
7
|
plash_cli/_modidx.py
|
|
@@ -13,7 +11,6 @@ plash_cli.egg-info/PKG-INFO
|
|
|
13
11
|
plash_cli.egg-info/SOURCES.txt
|
|
14
12
|
plash_cli.egg-info/dependency_links.txt
|
|
15
13
|
plash_cli.egg-info/entry_points.txt
|
|
16
|
-
plash_cli.egg-info/not-zip-safe
|
|
17
14
|
plash_cli.egg-info/requires.txt
|
|
18
15
|
plash_cli.egg-info/top_level.txt
|
|
19
16
|
plash_cli/assets/es256_public_key.pem
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
plash-apps = plash_cli.cli:_app_list
|
|
3
|
+
plash-delete = plash_cli.cli:_delete
|
|
4
|
+
plash-deploy = plash_cli.cli:_deploy
|
|
5
|
+
plash-download = plash_cli.cli:_download
|
|
6
|
+
plash-login = plash_cli.cli:login
|
|
7
|
+
plash-logs = plash_cli.cli:_logs
|
|
8
|
+
plash-start = plash_cli.cli:_start
|
|
9
|
+
plash-stop = plash_cli.cli:_stop
|
|
10
|
+
plash-view = plash_cli.cli:view
|
|
11
|
+
|
|
12
|
+
[nbdev]
|
|
13
|
+
plash_cli = plash_cli._modidx:d
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plash_cli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "CLI for the Plash hosting service"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
authors = [{name = "Jeremy Howard", email = "info@fast.ai"}]
|
|
13
|
+
keywords = ['nbdev', 'jupyter', 'notebook', 'python']
|
|
14
|
+
classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
|
|
15
|
+
dependencies = ['fastcore>=1.12.27', 'httpx>=0.28.1', 'python-dotenv', 'pyjwt', 'cryptography']
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Repository = "https://github.com/AnswerDotAI/plash_cli"
|
|
19
|
+
Documentation = "https://AnswerDotAI.github.io/plash_cli"
|
|
20
|
+
|
|
21
|
+
[project.entry-points.nbdev]
|
|
22
|
+
plash_cli = "plash_cli._modidx:d"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = ['bash_kernel', 'nbdev>=3']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
plash-deploy = "plash_cli.cli:_deploy"
|
|
30
|
+
plash-login = "plash_cli.cli:login"
|
|
31
|
+
plash-view = "plash_cli.cli:view"
|
|
32
|
+
plash-logs = "plash_cli.cli:_logs"
|
|
33
|
+
plash-delete = "plash_cli.cli:_delete"
|
|
34
|
+
plash-stop = "plash_cli.cli:_stop"
|
|
35
|
+
plash-start = "plash_cli.cli:_start"
|
|
36
|
+
plash-download = "plash_cli.cli:_download"
|
|
37
|
+
plash-apps = "plash_cli.cli:_app_list"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = {attr = "plash_cli.__version__"}
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["plash_cli"]
|
|
44
|
+
|
|
45
|
+
[tool.nbdev]
|
|
46
|
+
allowed_metadata_keys = ['solveit']
|
|
47
|
+
allowed_cell_metadata_keys = ['solveit_ai']
|
|
48
|
+
custom_sidebar = false
|
|
49
|
+
|
|
50
|
+
[tool.chkstyle]
|
|
51
|
+
skip_paths = ["_modidx.py"]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
[console_scripts]
|
|
2
|
-
plash_apps = plash_cli.cli:_app_list
|
|
3
|
-
plash_delete = plash_cli.cli:_delete
|
|
4
|
-
plash_deploy = plash_cli.cli:_deploy
|
|
5
|
-
plash_download = plash_cli.cli:_download
|
|
6
|
-
plash_login = plash_cli.cli:login
|
|
7
|
-
plash_logs = plash_cli.cli:_logs
|
|
8
|
-
plash_start = plash_cli.cli:_start
|
|
9
|
-
plash_stop = plash_cli.cli:_stop
|
|
10
|
-
plash_view = plash_cli.cli:view
|
|
11
|
-
|
|
12
|
-
[nbdev]
|
|
13
|
-
plash_cli = plash_cli._modidx:d
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
plash_cli-0.3.12/pyproject.toml
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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.10"
|
|
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" }]
|
plash_cli-0.3.12/settings.ini
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
[DEFAULT]
|
|
2
|
-
jupyter_hooks = False
|
|
3
|
-
repo = plash_cli
|
|
4
|
-
lib_name = plash_cli
|
|
5
|
-
version = 0.3.12
|
|
6
|
-
min_python = 3.10
|
|
7
|
-
license = apache2
|
|
8
|
-
black_formatting = False
|
|
9
|
-
doc_path = _docs
|
|
10
|
-
lib_path = plash_cli
|
|
11
|
-
nbs_path = nbs
|
|
12
|
-
recursive = True
|
|
13
|
-
tst_flags = notest
|
|
14
|
-
put_version_in_init = True
|
|
15
|
-
branch = main
|
|
16
|
-
custom_sidebar = False
|
|
17
|
-
doc_host = https://AnswerDotAI.github.io
|
|
18
|
-
doc_baseurl = /plash_cli
|
|
19
|
-
git_url = https://github.com/AnswerDotAI/plash_cli
|
|
20
|
-
title = plash_cli
|
|
21
|
-
audience = Developers
|
|
22
|
-
author = Jeremy Howard
|
|
23
|
-
author_email = info@fast.ai
|
|
24
|
-
copyright = 2025 onwards, Jeremy Howard
|
|
25
|
-
description = CLI for the Plash hosting service
|
|
26
|
-
keywords = nbdev jupyter notebook python
|
|
27
|
-
language = English
|
|
28
|
-
status = 3
|
|
29
|
-
user = AnswerDotAI
|
|
30
|
-
requirements = fastcore httpx>=0.28.1 python-dotenv pyjwt cryptography
|
|
31
|
-
dev_requirements = bash_kernel nbdev
|
|
32
|
-
console_scripts = plash_deploy=plash_cli.cli:_deploy
|
|
33
|
-
plash_login=plash_cli.cli:login
|
|
34
|
-
plash_view=plash_cli.cli:view
|
|
35
|
-
plash_logs=plash_cli.cli:_logs
|
|
36
|
-
plash_delete=plash_cli.cli:_delete
|
|
37
|
-
plash_stop=plash_cli.cli:_stop
|
|
38
|
-
plash_start=plash_cli.cli:_start
|
|
39
|
-
plash_download=plash_cli.cli:_download
|
|
40
|
-
plash_apps=plash_cli.cli:_app_list
|
|
41
|
-
readme_nb = index.ipynb
|
|
42
|
-
allowed_metadata_keys =
|
|
43
|
-
allowed_cell_metadata_keys =
|
|
44
|
-
clean_ids = True
|
|
45
|
-
clear_all = False
|
|
46
|
-
cell_number = True
|
|
47
|
-
skip_procs =
|
|
48
|
-
update_pyproject = True
|
|
49
|
-
|
plash_cli-0.3.12/setup.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from pkg_resources import parse_version
|
|
2
|
-
from configparser import ConfigParser
|
|
3
|
-
import setuptools, shlex
|
|
4
|
-
assert parse_version(setuptools.__version__)>=parse_version('36.2')
|
|
5
|
-
|
|
6
|
-
# note: all settings are in settings.ini; edit there, not here
|
|
7
|
-
config = ConfigParser(delimiters=['='])
|
|
8
|
-
config.read('settings.ini', encoding='utf-8')
|
|
9
|
-
cfg = config['DEFAULT']
|
|
10
|
-
|
|
11
|
-
cfg_keys = 'version description keywords author author_email'.split()
|
|
12
|
-
expected = cfg_keys + "lib_name user branch license status min_python audience language".split()
|
|
13
|
-
for o in expected: assert o in cfg, "missing expected setting: {}".format(o)
|
|
14
|
-
setup_cfg = {o:cfg[o] for o in cfg_keys}
|
|
15
|
-
|
|
16
|
-
licenses = {
|
|
17
|
-
'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'),
|
|
18
|
-
'mit': ('MIT License', 'OSI Approved :: MIT License'),
|
|
19
|
-
'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'),
|
|
20
|
-
'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'),
|
|
21
|
-
'bsd3': ('BSD License', 'OSI Approved :: BSD License'),
|
|
22
|
-
}
|
|
23
|
-
statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha',
|
|
24
|
-
'4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ]
|
|
25
|
-
py_versions = '3.6 3.7 3.8 3.9 3.10 3.11 3.12'.split()
|
|
26
|
-
|
|
27
|
-
requirements = shlex.split(cfg.get('requirements', ''))
|
|
28
|
-
if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', ''))
|
|
29
|
-
min_python = cfg['min_python']
|
|
30
|
-
lic = licenses.get(cfg['license'].lower(), (cfg['license'], None))
|
|
31
|
-
dev_requirements = (cfg.get('dev_requirements') or '').split()
|
|
32
|
-
|
|
33
|
-
package_data = dict()
|
|
34
|
-
pkg_data = cfg.get('package_data', None)
|
|
35
|
-
if pkg_data:
|
|
36
|
-
package_data[cfg['lib_name']] = pkg_data.split() # split as multiple files might be listed
|
|
37
|
-
# Add package data to setup_cfg for setuptools.setup(..., **setup_cfg)
|
|
38
|
-
setup_cfg['package_data'] = package_data
|
|
39
|
-
|
|
40
|
-
setuptools.setup(
|
|
41
|
-
name = cfg['lib_name'],
|
|
42
|
-
license = lic[0],
|
|
43
|
-
classifiers = [
|
|
44
|
-
'Development Status :: ' + statuses[int(cfg['status'])],
|
|
45
|
-
'Intended Audience :: ' + cfg['audience'].title(),
|
|
46
|
-
'Natural Language :: ' + cfg['language'].title(),
|
|
47
|
-
] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []),
|
|
48
|
-
url = cfg['git_url'],
|
|
49
|
-
packages = setuptools.find_packages(),
|
|
50
|
-
include_package_data = True,
|
|
51
|
-
install_requires = requirements,
|
|
52
|
-
extras_require={ 'dev': dev_requirements },
|
|
53
|
-
dependency_links = cfg.get('dep_links','').split(),
|
|
54
|
-
python_requires = '>=' + cfg['min_python'],
|
|
55
|
-
long_description = open('README.md', encoding='utf-8').read(),
|
|
56
|
-
long_description_content_type = 'text/markdown',
|
|
57
|
-
zip_safe = False,
|
|
58
|
-
entry_points = {
|
|
59
|
-
'console_scripts': cfg.get('console_scripts','').split(),
|
|
60
|
-
'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d']
|
|
61
|
-
},
|
|
62
|
-
**setup_cfg)
|
|
63
|
-
|
|
64
|
-
|
|
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
|