plash-cli 0.2.1__tar.gz → 0.3.0__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.
@@ -3,3 +3,4 @@ include LICENSE
3
3
  include CONTRIBUTING.md
4
4
  include README.md
5
5
  recursive-exclude * __pycache__
6
+ include plash_cli/assets/es256_public_key.pem
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plash_cli
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: CLI for the Plash hosting service
5
5
  Home-page: https://github.com/AnswerDotAI/plash_cli
6
6
  Author: Jeremy Howard
@@ -18,6 +18,9 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: fastcore
20
20
  Requires-Dist: httpx>=0.28.1
21
+ Requires-Dist: python-dotenv
22
+ Requires-Dist: pyjwt
23
+ Requires-Dist: cryptography
21
24
  Provides-Extra: dev
22
25
  Requires-Dist: bash_kernel; extra == "dev"
23
26
  Requires-Dist: nbdev; extra == "dev"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -5,7 +5,12 @@ d = { 'settings': { 'branch': 'main',
5
5
  'doc_host': 'https://AnswerDotAI.github.io',
6
6
  'git_url': 'https://github.com/AnswerDotAI/plash_cli',
7
7
  'lib_path': 'plash_cli'},
8
- 'syms': { 'plash_cli.core': { 'plash_cli.core.PlashError': ('core.html#plasherror', 'plash_cli/core.py'),
8
+ 'syms': { 'plash_cli.auth': { 'plash_cli.auth.PlashAuthError': ('auth.html#plashautherror', 'plash_cli/auth.py'),
9
+ 'plash_cli.auth._parse_jwt': ('auth.html#_parse_jwt', 'plash_cli/auth.py'),
10
+ 'plash_cli.auth._signin_url': ('auth.html#_signin_url', 'plash_cli/auth.py'),
11
+ 'plash_cli.auth.goog_id_from_signin_reply': ('auth.html#goog_id_from_signin_reply', 'plash_cli/auth.py'),
12
+ 'plash_cli.auth.mk_signin_url': ('auth.html#mk_signin_url', 'plash_cli/auth.py')},
13
+ 'plash_cli.core': { 'plash_cli.core.PlashError': ('core.html#plasherror', 'plash_cli/core.py'),
9
14
  'plash_cli.core._deps': ('core.html#_deps', 'plash_cli/core.py'),
10
15
  'plash_cli.core._gen_app_name': ('core.html#_gen_app_name', 'plash_cli/core.py'),
11
16
  'plash_cli.core.apps': ('core.html#apps', 'plash_cli/core.py'),
@@ -0,0 +1,4 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmAlaJd3pPsLNDxMf+gG1e+0DfSnS
3
+ mfrJiP5rgj8GL/xwhALJl9DOrw0gBh1H3Q2/XQvs+Df0rWXJ5bryn2ZPmg==
4
+ -----END PUBLIC KEY-----
@@ -0,0 +1,60 @@
1
+ """Client side logic to add Plash Auth to your app"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_auth.ipynb.
4
+
5
+ # %% auto 0
6
+ __all__ = ['signin_completed_rt', 'mk_signin_url', 'PlashAuthError', 'goog_id_from_signin_reply']
7
+
8
+ # %% ../nbs/01_auth.ipynb 3
9
+ import httpx,os,jwt
10
+ from pathlib import Path
11
+ from warnings import warn
12
+
13
+ from . import __version__
14
+
15
+ # %% ../nbs/01_auth.ipynb 5
16
+ signin_completed_rt = "/signin_completed"
17
+
18
+ # %% ../nbs/01_auth.ipynb 7
19
+ _in_prod = os.getenv('PLASH_PRODUCTION', '') == '1'
20
+
21
+ # %% ../nbs/01_auth.ipynb 9
22
+ def _signin_url(email_re: str=None, hd_re: str=None):
23
+ res = httpx.post(os.environ['PLASH_AUTH_URL'], json=dict(email_re=email_re, hd_re=hd_re),
24
+ auth=(os.environ['PLASH_APP_ID'], os.environ['PLASH_APP_SECRET']),
25
+ headers={'X-PLASH-AUTH-VERSION': __version__}).raise_for_status().json()
26
+ if "warning" in res: warn(res.pop('warning'))
27
+ return res
28
+
29
+ # %% ../nbs/01_auth.ipynb 11
30
+ def mk_signin_url(session: dict, # Session dictionary
31
+ email_re: str=None, # Regex filter for allowed email addresses
32
+ hd_re: str=None): # Regex filter for allowed Google hosted domains
33
+ "Generate a Google Sign-In URL for Plash authentication."
34
+ if not _in_prod: return f"{signin_completed_rt}?signin_reply=mock-sign-in-reply"
35
+ res = _signin_url(email_re, hd_re)
36
+ session['req_id'] = res['req_id']
37
+ return res['plash_signin_url']
38
+
39
+ # %% ../nbs/01_auth.ipynb 13
40
+ def _parse_jwt(reply: str) -> dict:
41
+ "Parse JWT reply and return decoded claims or error info"
42
+ try: decoded = jwt.decode(reply, key=open(Path(__file__).parent / "assets" / "es256_public_key.pem","rb").read(), algorithms=["ES256"],
43
+ options=dict(verify_aud=False, verify_iss=False))
44
+ except Exception as e: return dict(req_id=None, sub=None, err=f'JWT validation failed: {e}')
45
+ return dict(req_id=decoded.get('req_id'), sub=decoded.get('sub'), err=decoded.get('err'))
46
+
47
+ # %% ../nbs/01_auth.ipynb 15
48
+ class PlashAuthError(Exception):
49
+ """Raised when Plash authentication fails"""
50
+ pass
51
+
52
+ # %% ../nbs/01_auth.ipynb 17
53
+ def goog_id_from_signin_reply(session: dict, # Session dictionary containing 'req_id'
54
+ reply: str): # The JWT reply string from Plash after Google authentication
55
+ "Validate Google sign-in reply and returns Google user ID if valid."
56
+ if not _in_prod: return '424242424242424242424'
57
+ parsed = _parse_jwt(reply)
58
+ if session.get('req_id') != parsed['req_id']: raise PlashAuthError("Request originated from a different browser than the one receiving the reply")
59
+ if parsed['err']: raise PlashAuthError(f"Authentication failed: {parsed['err']}")
60
+ return parsed['sub']
@@ -110,7 +110,7 @@ def validate_app(path):
110
110
  "Validates directory `path` is a deployable Plash app"
111
111
  if not (path / 'main.py').exists():
112
112
  raise PlashError('A Plash app requires a main.py file.')
113
- deps = _deps((path / 'main.py').read_text())
113
+ deps = _deps((path / 'main.py').read_text(encoding='utf-8'))
114
114
  if deps and (path/"requirements.txt").exists():
115
115
  raise PlashError('A Plash app should not contain both a requirements.txt file and inline dependencies (see PEP723).')
116
116
 
@@ -137,7 +137,7 @@ def _gen_app_name():
137
137
  suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=3))
138
138
  return f"{random.choice(adjectives)}-{random.choice(nouns)}-{random.choice(verbs)}-{suffix}"
139
139
 
140
- # %% ../nbs/00_core.ipynb 27
140
+ # %% ../nbs/00_core.ipynb 26
141
141
  @call_parse
142
142
  def deploy(
143
143
  path:Path=Path('.'), # Path to project
@@ -170,7 +170,7 @@ def deploy(
170
170
  print(f'It will be live at {name if "." in name else endpoint(sub=name)}')
171
171
  else: print(f'Failure: {resp.status_code}\n{resp.text}')
172
172
 
173
- # %% ../nbs/00_core.ipynb 29
173
+ # %% ../nbs/00_core.ipynb 28
174
174
  @call_parse
175
175
  def view(
176
176
  path:Path=Path('.'), # Path to project directory
@@ -182,7 +182,7 @@ def view(
182
182
  print(f"Opening browser to view app :\n{url}\n")
183
183
  webbrowser.open(url)
184
184
 
185
- # %% ../nbs/00_core.ipynb 31
185
+ # %% ../nbs/00_core.ipynb 30
186
186
  @call_parse
187
187
  def delete(
188
188
  path:Path=Path('.'), # Path to project
@@ -200,7 +200,7 @@ def delete(
200
200
  r = mk_auth_req(endpoint(rt=f"/delete?name={name}"), "delete")
201
201
  return r.text
202
202
 
203
- # %% ../nbs/00_core.ipynb 33
203
+ # %% ../nbs/00_core.ipynb 32
204
204
  def endpoint_func(endpoint_name):
205
205
  'Creates a function for a specific API endpoint'
206
206
  def func(
@@ -221,10 +221,10 @@ def endpoint_func(endpoint_name):
221
221
  stop = endpoint_func('/stop')
222
222
  start = endpoint_func('/start')
223
223
 
224
- # %% ../nbs/00_core.ipynb 35
224
+ # %% ../nbs/00_core.ipynb 34
225
225
  log_modes = str_enum('log_modes', 'build', 'app')
226
226
 
227
- # %% ../nbs/00_core.ipynb 36
227
+ # %% ../nbs/00_core.ipynb 35
228
228
  @call_parse
229
229
  def logs(
230
230
  path:Path=Path('.'), # Path to project
@@ -250,7 +250,7 @@ def logs(
250
250
  r = mk_auth_req(endpoint(rt=f"/logs?name={name}&mode={mode}"))
251
251
  return r.text
252
252
 
253
- # %% ../nbs/00_core.ipynb 38
253
+ # %% ../nbs/00_core.ipynb 37
254
254
  @call_parse
255
255
  def download(
256
256
  path:Path=Path('.'), # Path to project
@@ -266,7 +266,7 @@ def download(
266
266
  with tarfile.open(fileobj=file_bytes, mode="r:gz") as tar: tar.extractall(path=save_path)
267
267
  print(f"Downloaded your app to: {save_path}")
268
268
 
269
- # %% ../nbs/00_core.ipynb 40
269
+ # %% ../nbs/00_core.ipynb 39
270
270
  @call_parse
271
271
  def apps(verbose:bool=False):
272
272
  "List your deployed apps (verbose shows status table: 1=running, 0=stopped)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plash_cli
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: CLI for the Plash hosting service
5
5
  Home-page: https://github.com/AnswerDotAI/plash_cli
6
6
  Author: Jeremy Howard
@@ -18,6 +18,9 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: fastcore
20
20
  Requires-Dist: httpx>=0.28.1
21
+ Requires-Dist: python-dotenv
22
+ Requires-Dist: pyjwt
23
+ Requires-Dist: cryptography
21
24
  Provides-Extra: dev
22
25
  Requires-Dist: bash_kernel; extra == "dev"
23
26
  Requires-Dist: nbdev; extra == "dev"
@@ -7,6 +7,7 @@ setup.py
7
7
  plash_cli/__init__.py
8
8
  plash_cli/_bash_magic.py
9
9
  plash_cli/_modidx.py
10
+ plash_cli/auth.py
10
11
  plash_cli/core.py
11
12
  plash_cli.egg-info/PKG-INFO
12
13
  plash_cli.egg-info/SOURCES.txt
@@ -14,4 +15,5 @@ plash_cli.egg-info/dependency_links.txt
14
15
  plash_cli.egg-info/entry_points.txt
15
16
  plash_cli.egg-info/not-zip-safe
16
17
  plash_cli.egg-info/requires.txt
17
- plash_cli.egg-info/top_level.txt
18
+ plash_cli.egg-info/top_level.txt
19
+ plash_cli/assets/es256_public_key.pem
@@ -1,5 +1,8 @@
1
1
  fastcore
2
2
  httpx>=0.28.1
3
+ python-dotenv
4
+ pyjwt
5
+ cryptography
3
6
 
4
7
  [dev]
5
8
  bash_kernel
@@ -2,7 +2,7 @@
2
2
  jupyter_hooks = False
3
3
  repo = plash_cli
4
4
  lib_name = plash_cli
5
- version = 0.2.1
5
+ version = 0.3.0
6
6
  min_python = 3.11
7
7
  license = apache2
8
8
  black_formatting = False
@@ -27,7 +27,7 @@ keywords = nbdev jupyter notebook python
27
27
  language = English
28
28
  status = 3
29
29
  user = AnswerDotAI
30
- requirements = fastcore httpx>=0.28.1
30
+ requirements = fastcore httpx>=0.28.1 python-dotenv pyjwt cryptography
31
31
  dev_requirements = bash_kernel nbdev
32
32
  console_scripts = plash_deploy=plash_cli.core:deploy
33
33
  plash_login=plash_cli.core:login
@@ -1 +0,0 @@
1
- __version__ = "0.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes