pythonhere 0.1.5.2__py3-none-any.whl → 0.2.0__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.
- pythonhere/__init__.py +1 -1
- pythonhere/android_here.py +4 -5
- pythonhere/data/logo/logo-128.png +0 -0
- pythonhere/data/logo/logo-32.png +0 -0
- pythonhere/data/logo/logo-splash.png +0 -0
- pythonhere/enum_here.py +1 -0
- pythonhere/exception_manager_here.kv +22 -0
- pythonhere/exception_manager_here.py +14 -30
- pythonhere/launcher_here.py +3 -3
- pythonhere/magic_here/shortcuts.py +6 -5
- pythonhere/main.py +90 -36
- pythonhere/network_here.py +4 -3
- pythonhere/patches_here.py +1 -2
- pythonhere/pythonhere.kv +49 -0
- pythonhere/server_here.py +4 -4
- pythonhere/ui_here/actionbar_here.kv +8 -0
- pythonhere/ui_here/common_here.kv +14 -0
- pythonhere/ui_here/connection_address_here.kv +32 -0
- pythonhere/ui_here/connection_address_here.py +2 -2
- pythonhere/ui_here/layout_here.py +1 -0
- pythonhere/ui_here/server_screen_here.kv +57 -0
- pythonhere/ui_here/server_screen_here.py +2 -2
- pythonhere/ui_here/settings_here.kv +31 -0
- pythonhere/ui_here/settings_here.py +3 -3
- pythonhere/version_here.py +1 -1
- pythonhere/window_here.py +3 -2
- pythonhere-0.2.0.dist-info/METADATA +125 -0
- pythonhere-0.2.0.dist-info/RECORD +33 -0
- {pythonhere-0.1.5.2.dist-info → pythonhere-0.2.0.dist-info}/WHEEL +1 -1
- pythonhere-0.1.5.2.dist-info/METADATA +0 -139
- pythonhere-0.1.5.2.dist-info/RECORD +0 -23
- {pythonhere-0.1.5.2.dist-info → pythonhere-0.2.0.dist-info/licenses}/LICENSE +0 -0
- {pythonhere-0.1.5.2.dist-info → pythonhere-0.2.0.dist-info}/top_level.txt +0 -0
pythonhere/__init__.py
CHANGED
pythonhere/android_here.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""Android specific functions."""
|
|
2
|
+
|
|
2
3
|
# pylint: disable=invalid-name,import-error,import-outside-toplevel
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
5
4
|
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from android import activity as android_activity
|
|
8
8
|
from jnius import autoclass, cast
|
|
9
9
|
from kivy.logger import Logger
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
Context = autoclass("android.content.Context")
|
|
13
12
|
Icon = autoclass("android.graphics.drawable.Icon")
|
|
14
13
|
Intent = autoclass("android.content.Intent")
|
|
@@ -23,7 +22,7 @@ def get_current_intent() -> Intent:
|
|
|
23
22
|
return PythonActivity.mActivity.getIntent()
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
def get_startup_script(intent: None = None) ->
|
|
25
|
+
def get_startup_script(intent: None = None) -> str | None:
|
|
27
26
|
"""Return script entrypoint that was passed to a given, or current, intent."""
|
|
28
27
|
if not intent:
|
|
29
28
|
intent = get_current_intent()
|
|
@@ -62,7 +61,7 @@ def bind_run_script_on_new_intent():
|
|
|
62
61
|
def create_shortcut_icon() -> Icon:
|
|
63
62
|
"""Create icon to use for a shurtcut."""
|
|
64
63
|
activity = PythonActivity.mActivity
|
|
65
|
-
Drawable = autoclass("{}.R$drawable"
|
|
64
|
+
Drawable = autoclass(f"{activity.getPackageName()}.R$drawable")
|
|
66
65
|
context = cast("android.content.Context", activity.getApplicationContext())
|
|
67
66
|
return Icon.createWithResource(context, Drawable.icon)
|
|
68
67
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
pythonhere/enum_here.py
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<-UnhandledExceptionPopupHere>:
|
|
2
|
+
title: "Unhandled Exception catched"
|
|
3
|
+
BoxLayout:
|
|
4
|
+
orientation: 'vertical'
|
|
5
|
+
padding: 10
|
|
6
|
+
spacing: 20
|
|
7
|
+
Label:
|
|
8
|
+
size_hint_y: None
|
|
9
|
+
font_size: '18sp'
|
|
10
|
+
height: '24sp'
|
|
11
|
+
text: 'Exception details: '
|
|
12
|
+
ScrollView:
|
|
13
|
+
CodeInput:
|
|
14
|
+
id: catched_exception_code_input_here
|
|
15
|
+
text: root.message
|
|
16
|
+
size_hint: 1, None
|
|
17
|
+
height: self.minimum_height
|
|
18
|
+
Button:
|
|
19
|
+
size_hint_y: None
|
|
20
|
+
height: '40sp'
|
|
21
|
+
text: 'OK, continue'
|
|
22
|
+
on_press: root.dismiss()
|
|
@@ -1,46 +1,26 @@
|
|
|
1
1
|
"""App exceptions manager."""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
import traceback
|
|
4
|
-
from
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from kivy.base import (
|
|
7
8
|
ExceptionHandler,
|
|
8
9
|
ExceptionManager,
|
|
9
10
|
)
|
|
10
11
|
from kivy.clock import Clock
|
|
11
|
-
from kivy.properties import StringProperty # pylint: disable=no-name-in-module
|
|
12
12
|
from kivy.lang import Builder
|
|
13
13
|
from kivy.logger import Logger
|
|
14
|
+
from kivy.properties import StringProperty # pylint: disable=no-name-in-module
|
|
14
15
|
from kivy.uix.popup import Popup
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def load_exception_popup_style():
|
|
18
19
|
"""Load KV rules for `UnhandledExceptionPopupHere`."""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
orientation: 'vertical'
|
|
24
|
-
padding: 10
|
|
25
|
-
spacing: 20
|
|
26
|
-
Label:
|
|
27
|
-
size_hint_y: None
|
|
28
|
-
font_size: '18sp'
|
|
29
|
-
height: '24sp'
|
|
30
|
-
text: 'Exception details: '
|
|
31
|
-
ScrollView:
|
|
32
|
-
CodeInput:
|
|
33
|
-
id: catched_exception_code_input_here
|
|
34
|
-
text: root.message
|
|
35
|
-
size_hint: 1, None
|
|
36
|
-
height: self.minimum_height
|
|
37
|
-
Button:
|
|
38
|
-
size_hint_y: None
|
|
39
|
-
height: '40sp'
|
|
40
|
-
text: 'OK, continue'
|
|
41
|
-
on_press: root.dismiss()
|
|
42
|
-
"""
|
|
43
|
-
)
|
|
20
|
+
kv_path = str(Path(__file__).with_suffix(".kv"))
|
|
21
|
+
|
|
22
|
+
if kv_path not in Builder.files:
|
|
23
|
+
Builder.load_file(kv_path)
|
|
44
24
|
|
|
45
25
|
|
|
46
26
|
class ErrorMessageOnException(ExceptionHandler):
|
|
@@ -48,9 +28,9 @@ class ErrorMessageOnException(ExceptionHandler):
|
|
|
48
28
|
|
|
49
29
|
def handle_exception(self, exception) -> int:
|
|
50
30
|
"""Handle a exception."""
|
|
51
|
-
Logger.exception("Unhandled Exception catched")
|
|
52
31
|
if isinstance(exception, (asyncio.CancelledError, KeyboardInterrupt)):
|
|
53
32
|
return ExceptionManager.RAISE
|
|
33
|
+
Logger.exception("Unhandled Exception catched")
|
|
54
34
|
show_exception_popup()
|
|
55
35
|
return ExceptionManager.PASS
|
|
56
36
|
|
|
@@ -63,10 +43,14 @@ class UnhandledExceptionPopupHere(Popup):
|
|
|
63
43
|
|
|
64
44
|
def install_exception_handler():
|
|
65
45
|
"""Install `ErrorMessageOnException` exception handler."""
|
|
66
|
-
|
|
46
|
+
if not any(
|
|
47
|
+
isinstance(handler, ErrorMessageOnException)
|
|
48
|
+
for handler in ExceptionManager.handlers
|
|
49
|
+
):
|
|
50
|
+
ExceptionManager.add_handler(ErrorMessageOnException())
|
|
67
51
|
|
|
68
52
|
|
|
69
|
-
def show_exception_popup(exc:
|
|
53
|
+
def show_exception_popup(exc: Exception | None = None):
|
|
70
54
|
"""Show exception popup."""
|
|
71
55
|
load_exception_popup_style()
|
|
72
56
|
if exc:
|
pythonhere/launcher_here.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Utilities for launching scripts."""
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
|
-
from pathlib import Path
|
|
4
4
|
import runpy
|
|
5
5
|
import sys
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
from kivy.logger import Logger
|
|
9
8
|
from kivy import platform
|
|
9
|
+
from kivy.logger import Logger
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def run_script(script: str):
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
from base64 import b64decode
|
|
5
5
|
from io import BytesIO, StringIO
|
|
6
6
|
|
|
7
|
-
from PIL import Image as PILImage
|
|
8
|
-
from IPython.display import display
|
|
9
7
|
import click
|
|
10
|
-
from herethere.there.commands import
|
|
11
|
-
|
|
8
|
+
from herethere.there.commands import there_code_shortcut, there_group
|
|
9
|
+
from IPython.display import display
|
|
10
|
+
from PIL import Image as PILImage
|
|
12
11
|
|
|
13
12
|
KV_COMMAND_TEMPLATE = r"""
|
|
14
13
|
from window_here import load_kv_string
|
|
@@ -67,7 +66,9 @@ def screenshot(ctx, width, output):
|
|
|
67
66
|
img = PILImage.open(BytesIO(data)).convert("RGB")
|
|
68
67
|
if width:
|
|
69
68
|
height = int(width * img.size[1] // img.size[0])
|
|
70
|
-
|
|
69
|
+
resampling = getattr(PILImage, "Resampling", PILImage)
|
|
70
|
+
resample_filter = getattr(resampling, "LANCZOS", PILImage.LANCZOS)
|
|
71
|
+
img = img.resize((width, height), resample_filter)
|
|
71
72
|
|
|
72
73
|
if output:
|
|
73
74
|
img.save(output)
|
pythonhere/main.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""PythonHere app."""
|
|
2
|
+
|
|
2
3
|
# pylint: disable=wrong-import-order,wrong-import-position
|
|
3
4
|
|
|
4
5
|
from launcher_here import try_startup_script
|
|
@@ -12,18 +13,17 @@ else:
|
|
|
12
13
|
|
|
13
14
|
import asyncio
|
|
14
15
|
import os
|
|
15
|
-
from pathlib import Path
|
|
16
16
|
import sys
|
|
17
17
|
import threading
|
|
18
|
-
from
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
19
20
|
|
|
21
|
+
from enum_here import ScreenName, ServerState
|
|
22
|
+
from exception_manager_here import install_exception_handler, show_exception_popup
|
|
20
23
|
from kivy.app import App
|
|
21
24
|
from kivy.clock import Clock
|
|
22
25
|
from kivy.config import Config, ConfigParser
|
|
23
26
|
from kivy.logger import Logger
|
|
24
|
-
|
|
25
|
-
from enum_here import ScreenName, ServerState
|
|
26
|
-
from exception_manager_here import install_exception_handler, show_exception_popup
|
|
27
27
|
from patches_here import monkeypatch_kivy
|
|
28
28
|
from server_here import run_ssh_server
|
|
29
29
|
from window_here import reset_window_environment
|
|
@@ -36,13 +36,25 @@ class PythonHereApp(App):
|
|
|
36
36
|
|
|
37
37
|
def __init__(self):
|
|
38
38
|
super().__init__()
|
|
39
|
-
self.server_task = None
|
|
39
|
+
self.server_task: asyncio.Task | None = None
|
|
40
|
+
self.app_task: asyncio.Task | None = None
|
|
41
|
+
self.asyncio_loop: asyncio.AbstractEventLoop | None = None
|
|
40
42
|
self.settings = None
|
|
43
|
+
|
|
44
|
+
# Created once a running asyncio loop exists.
|
|
45
|
+
self.ssh_server_config_ready: asyncio.Event | None = None
|
|
46
|
+
self.ssh_server_started: asyncio.Event | None = None
|
|
47
|
+
self.ssh_server_connected: asyncio.Event | None = None
|
|
48
|
+
|
|
49
|
+
self.ssh_server_namespace = {}
|
|
50
|
+
self.icon = "data/logo/logo-32.png"
|
|
51
|
+
|
|
52
|
+
def init_asyncio_state(self):
|
|
53
|
+
"""Initialize asyncio-owned state after the event loop is running."""
|
|
54
|
+
self.asyncio_loop = asyncio.get_running_loop()
|
|
41
55
|
self.ssh_server_config_ready = asyncio.Event()
|
|
42
56
|
self.ssh_server_started = asyncio.Event()
|
|
43
57
|
self.ssh_server_connected = asyncio.Event()
|
|
44
|
-
self.ssh_server_namespace = {}
|
|
45
|
-
self.icon = "data/logo/logo-32.png"
|
|
46
58
|
|
|
47
59
|
@property
|
|
48
60
|
def upload_dir(self) -> str:
|
|
@@ -67,9 +79,7 @@ class PythonHereApp(App):
|
|
|
67
79
|
"""Initialize application UI."""
|
|
68
80
|
super().build()
|
|
69
81
|
install_exception_handler()
|
|
70
|
-
|
|
71
82
|
self.settings = self.root.ids.settings
|
|
72
|
-
|
|
73
83
|
self.ssh_server_namespace.update(
|
|
74
84
|
{
|
|
75
85
|
"app": self,
|
|
@@ -82,47 +92,81 @@ class PythonHereApp(App):
|
|
|
82
92
|
lambda _: show_exception_popup(startup_script_exception), 0
|
|
83
93
|
)
|
|
84
94
|
|
|
85
|
-
def run_app(self):
|
|
86
|
-
"""Run application and SSH server
|
|
87
|
-
self.
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
async def run_app(self):
|
|
96
|
+
"""Run application and SSH server until either main task exits."""
|
|
97
|
+
self.init_asyncio_state()
|
|
98
|
+
|
|
99
|
+
self.server_task = asyncio.create_task(run_ssh_server(self))
|
|
100
|
+
self.app_task = asyncio.create_task(self.async_run_app())
|
|
101
|
+
|
|
102
|
+
tasks = (self.server_task, self.app_task)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
done, pending = await asyncio.wait(
|
|
106
|
+
tasks,
|
|
107
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
for task in pending:
|
|
111
|
+
task.cancel()
|
|
112
|
+
|
|
113
|
+
if pending:
|
|
114
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
115
|
+
|
|
116
|
+
for task in done:
|
|
117
|
+
if task.cancelled():
|
|
118
|
+
continue
|
|
119
|
+
exc = task.exception()
|
|
120
|
+
if exc is not None:
|
|
121
|
+
raise exc
|
|
122
|
+
finally:
|
|
123
|
+
await self.cancel_app_tasks()
|
|
90
124
|
|
|
91
125
|
async def async_run_app(self):
|
|
92
126
|
"""Run app asynchronously."""
|
|
93
127
|
try:
|
|
94
|
-
await self.async_run(
|
|
128
|
+
await self.async_run()
|
|
95
129
|
Logger.info("PythonHere: async run completed")
|
|
96
130
|
except asyncio.CancelledError:
|
|
97
131
|
Logger.info("PythonHere: app main task canceled")
|
|
132
|
+
raise
|
|
98
133
|
except Exception as exc:
|
|
99
134
|
Logger.exception(exc)
|
|
135
|
+
raise
|
|
136
|
+
finally:
|
|
137
|
+
if self.get_running_app():
|
|
138
|
+
self.stop()
|
|
100
139
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if self.get_running_app():
|
|
105
|
-
self.stop()
|
|
106
|
-
|
|
107
|
-
await self.cancel_asyncio_tasks()
|
|
108
|
-
|
|
109
|
-
async def cancel_asyncio_tasks(self):
|
|
110
|
-
"""Cancel all asyncio tasks."""
|
|
140
|
+
async def cancel_app_tasks(self):
|
|
141
|
+
"""Cancel tasks owned by this app."""
|
|
111
142
|
tasks = [
|
|
112
|
-
task
|
|
143
|
+
task
|
|
144
|
+
for task in (self.server_task, self.app_task)
|
|
145
|
+
if task is not None
|
|
146
|
+
and task is not asyncio.current_task()
|
|
147
|
+
and not task.done()
|
|
113
148
|
]
|
|
149
|
+
|
|
150
|
+
for task in tasks:
|
|
151
|
+
task.cancel()
|
|
152
|
+
|
|
114
153
|
if tasks:
|
|
115
|
-
|
|
116
|
-
task.cancel()
|
|
117
|
-
await asyncio.wait(tasks, timeout=1)
|
|
154
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
118
155
|
|
|
119
156
|
def update_server_config_status(self):
|
|
120
157
|
"""Check and update value of the `ssh_server_config_ready`, update screen."""
|
|
121
158
|
|
|
122
159
|
def update():
|
|
123
160
|
if all(self.get_pythonhere_config().values()):
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
if (
|
|
162
|
+
self.asyncio_loop is not None
|
|
163
|
+
and self.ssh_server_config_ready is not None
|
|
164
|
+
):
|
|
165
|
+
self.asyncio_loop.call_soon_threadsafe(
|
|
166
|
+
self.ssh_server_config_ready.set
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
Clock.schedule_once(lambda _: screen.update(), 0)
|
|
126
170
|
|
|
127
171
|
screen = self.root.ids.here_screen_manager
|
|
128
172
|
screen.current = ServerState.starting_server
|
|
@@ -133,7 +177,7 @@ class PythonHereApp(App):
|
|
|
133
177
|
"""Return user settings for SSH server."""
|
|
134
178
|
return self.settings.get_pythonhere_config()
|
|
135
179
|
|
|
136
|
-
def update_ssh_server_namespace(self, namespace:
|
|
180
|
+
def update_ssh_server_namespace(self, namespace: dict[str, Any]):
|
|
137
181
|
"""Update SSH server namespace."""
|
|
138
182
|
self.ssh_server_namespace.update(namespace)
|
|
139
183
|
|
|
@@ -152,8 +196,14 @@ class PythonHereApp(App):
|
|
|
152
196
|
def on_ssh_connection_made(self):
|
|
153
197
|
"""New authenticated SSH client connected handler."""
|
|
154
198
|
Logger.info("PythonHere: new SSH client connected")
|
|
199
|
+
|
|
200
|
+
if self.ssh_server_connected is None:
|
|
201
|
+
Logger.warning("PythonHere: SSH connected before asyncio state was ready")
|
|
202
|
+
return
|
|
203
|
+
|
|
155
204
|
if not self.ssh_server_connected.is_set():
|
|
156
205
|
self.ssh_server_connected.set()
|
|
206
|
+
|
|
157
207
|
Logger.info("PythonHere: reset window environment")
|
|
158
208
|
self.ssh_server_namespace["root"] = reset_window_environment()
|
|
159
209
|
self.chdir(self.upload_dir)
|
|
@@ -165,7 +215,11 @@ class PythonHereApp(App):
|
|
|
165
215
|
sys.path.insert(0, path)
|
|
166
216
|
|
|
167
217
|
|
|
218
|
+
async def main():
|
|
219
|
+
"""Run PythonHere."""
|
|
220
|
+
app = PythonHereApp()
|
|
221
|
+
await app.run_app()
|
|
222
|
+
|
|
223
|
+
|
|
168
224
|
if __name__ == "__main__":
|
|
169
|
-
|
|
170
|
-
loop.run_until_complete(PythonHereApp().run_app())
|
|
171
|
-
loop.close()
|
|
225
|
+
asyncio.run(main())
|
pythonhere/network_here.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Network addresses discovering."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
3
4
|
|
|
4
5
|
from kivy import platform
|
|
5
6
|
|
|
@@ -14,7 +15,7 @@ else:
|
|
|
14
15
|
|
|
15
16
|
def get_android_interface_addresses(
|
|
16
17
|
interface: "NetworkInterface",
|
|
17
|
-
) -> Iterator[
|
|
18
|
+
) -> Iterator[tuple[str, str]]:
|
|
18
19
|
"""Yields active IPv4 addresses for given network interface."""
|
|
19
20
|
if interface.isUp():
|
|
20
21
|
addresses = interface.getInetAddresses()
|
|
@@ -24,7 +25,7 @@ def get_android_interface_addresses(
|
|
|
24
25
|
yield interface.getDisplayName(), address.getHostAddress()
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
def get_all_available_ipv4_adrresses() -> Iterator[
|
|
28
|
+
def get_all_available_ipv4_adrresses() -> Iterator[tuple[str, str]]:
|
|
28
29
|
"""Yields available interfaces with IPv4 addresses
|
|
29
30
|
available for connections from there.
|
|
30
31
|
"""
|
pythonhere/patches_here.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"""Monkey patching Kivy @('_')@."""
|
|
2
2
|
|
|
3
|
+
import kivy.uix.widget
|
|
3
4
|
from kivy.factory import Factory
|
|
4
5
|
from kivy.lang.builder import BuilderBase
|
|
5
|
-
import kivy.uix.widget
|
|
6
|
-
|
|
7
6
|
|
|
8
7
|
_original_factory_register = Factory.register
|
|
9
8
|
_original_builderbase_match = BuilderBase.match
|
pythonhere/pythonhere.kv
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#:kivy 1.0
|
|
2
|
+
#:import RootLayout ui_here.layout_here.RootLayout
|
|
3
|
+
#:import ScreenName enum_here.ScreenName
|
|
4
|
+
#:import ServerState enum_here.ServerState
|
|
5
|
+
#:import ServerScreenManager ui_here.server_screen_here.ServerScreenManager
|
|
6
|
+
#:include ui_here/actionbar_here.kv
|
|
7
|
+
#:include ui_here/common_here.kv
|
|
8
|
+
#:include ui_here/connection_address_here.kv
|
|
9
|
+
#:include ui_here/server_screen_here.kv
|
|
10
|
+
#:include ui_here/settings_here.kv
|
|
11
|
+
|
|
12
|
+
RootLayout:
|
|
13
|
+
orientation: "vertical"
|
|
14
|
+
|
|
15
|
+
ActionBar:
|
|
16
|
+
pos_hint: {"top": 1}
|
|
17
|
+
|
|
18
|
+
ActionView:
|
|
19
|
+
ActionPrevious:
|
|
20
|
+
title: ""
|
|
21
|
+
with_previous: False
|
|
22
|
+
app_icon: "data/logo/logo-32.png"
|
|
23
|
+
|
|
24
|
+
ScreenActionButton:
|
|
25
|
+
id: open_here_action
|
|
26
|
+
text: f"%{tc('H', 3)}ere"
|
|
27
|
+
screen: ScreenName.here
|
|
28
|
+
ScreenActionButton:
|
|
29
|
+
id: open_settings_action
|
|
30
|
+
text: f"{tc('S', 3)}ettings"
|
|
31
|
+
screen: ScreenName.settings
|
|
32
|
+
|
|
33
|
+
ActionButton:
|
|
34
|
+
text: f"{tc('Q', 3)}uit"
|
|
35
|
+
on_release: app.get_running_app().stop()
|
|
36
|
+
|
|
37
|
+
ScreenManager:
|
|
38
|
+
id: screen_manager
|
|
39
|
+
Screen:
|
|
40
|
+
Screen:
|
|
41
|
+
id: here_screen
|
|
42
|
+
name: ScreenName.here
|
|
43
|
+
ServerScreenManager:
|
|
44
|
+
id: here_screen_manager
|
|
45
|
+
Screen:
|
|
46
|
+
id: settings_screen
|
|
47
|
+
name: ScreenName.settings
|
|
48
|
+
SettingsHere:
|
|
49
|
+
id: settings
|
pythonhere/server_here.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""SSH server."""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
6
|
+
from exception_manager_here import show_exception_popup
|
|
7
|
+
from herethere.here.server import ServerConfig, SSHServerHere, start_server
|
|
5
8
|
from kivy.app import App
|
|
6
9
|
from kivy.logger import Logger
|
|
7
|
-
from herethere.here.server import SSHServerHere, ServerConfig, start_server
|
|
8
|
-
|
|
9
|
-
from exception_manager_here import show_exception_popup
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class PythonHereServer(SSHServerHere):
|
|
@@ -53,7 +53,7 @@ async def run_ssh_server(app):
|
|
|
53
53
|
Logger.info("PythonHere: SSH server task canceled")
|
|
54
54
|
await server.stop()
|
|
55
55
|
except Exception as exc:
|
|
56
|
-
Logger.
|
|
56
|
+
Logger.error("PythonHere: SSH server stop by exception")
|
|
57
57
|
Logger.exception(exc)
|
|
58
58
|
show_exception_popup(exc)
|
|
59
59
|
Logger.info("PythonHere: SSH server closed")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#:kivy 1.0
|
|
2
|
+
# Common settings
|
|
3
|
+
|
|
4
|
+
#:import hex kivy.utils.get_color_from_hex
|
|
5
|
+
#:import sp kivy.metrics.sp
|
|
6
|
+
|
|
7
|
+
#:set bg_color "#EEEEEE"
|
|
8
|
+
#:set pallete ["#646464", "#306998", "#4B8BBE", "#FFD43B", "#FFE873"]
|
|
9
|
+
#:set tc lambda text, index: f"[color={pallete[index]}]{text}[/color]"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
<Label>:
|
|
13
|
+
font_name: "DejaVuSans"
|
|
14
|
+
markup: True
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#:kivy 1.0
|
|
2
|
+
#:import ConnectionAddressLabel ui_here.connection_address_here.ConnectionAddressLabel
|
|
3
|
+
#:import ConnectionAddressInfoBox ui_here.connection_address_here.ConnectionAddressInfoBox
|
|
4
|
+
|
|
5
|
+
<ConnectionAddressLabel>:
|
|
6
|
+
text: f"{tc('↳', 3)} {tc(self.address, 2)} ({self.interface})"
|
|
7
|
+
size_hint: 1, None
|
|
8
|
+
text_size: self.size
|
|
9
|
+
height: self.texture_size[1]
|
|
10
|
+
font_size: "20sp"
|
|
11
|
+
|
|
12
|
+
<ConnectionAddressInfoBox>:
|
|
13
|
+
cols: 1
|
|
14
|
+
size_hint: 1, 1
|
|
15
|
+
padding: '20sp', 0, 0, 0
|
|
16
|
+
|
|
17
|
+
Label:
|
|
18
|
+
text: f"Server is running.\nConnect {tc('here', 3)} via"
|
|
19
|
+
size_hint: 1, None
|
|
20
|
+
text_size: self.size
|
|
21
|
+
height: "80sp"
|
|
22
|
+
font_size: "30sp"
|
|
23
|
+
|
|
24
|
+
ScrollView:
|
|
25
|
+
do_scroll_x: False
|
|
26
|
+
size_hint: 1, 1
|
|
27
|
+
|
|
28
|
+
BoxLayout:
|
|
29
|
+
id: address_list
|
|
30
|
+
orientation: "vertical"
|
|
31
|
+
size_hint: 1, None
|
|
32
|
+
height: self.minimum_size[1]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Connection information widgets."""
|
|
2
|
+
|
|
2
3
|
from kivy.clock import Clock, mainthread
|
|
3
4
|
from kivy.logger import Logger
|
|
4
5
|
from kivy.properties import StringProperty # pylint: disable=no-name-in-module
|
|
5
|
-
from kivy.uix.label import Label
|
|
6
6
|
from kivy.uix.gridlayout import GridLayout
|
|
7
|
-
|
|
7
|
+
from kivy.uix.label import Label
|
|
8
8
|
from network_here import get_all_available_ipv4_adrresses
|
|
9
9
|
|
|
10
10
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
|
|
2
|
+
#:import App kivy.app.App
|
|
3
|
+
|
|
4
|
+
<LoadingImage@Image>:
|
|
5
|
+
source: "data/images/image-loading.zip"
|
|
6
|
+
allow_stretch: True
|
|
7
|
+
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
|
|
8
|
+
size_hint: None, None
|
|
9
|
+
size: sp(100), sp(100)
|
|
10
|
+
|
|
11
|
+
<StackLabel@Label>:
|
|
12
|
+
size_hint: None, None
|
|
13
|
+
font_size: "20sp"
|
|
14
|
+
size: self.texture_size
|
|
15
|
+
|
|
16
|
+
<ServerNotConfigured@Screen>:
|
|
17
|
+
StackLayout:
|
|
18
|
+
orientation: "lr-tb"
|
|
19
|
+
padding: sp(20), sp(20), 0, 0
|
|
20
|
+
StackLabel:
|
|
21
|
+
text: "To start "
|
|
22
|
+
StackLabel:
|
|
23
|
+
text: f"{tc('Python', 1)}{tc('here', 3)}, "
|
|
24
|
+
StackLabel:
|
|
25
|
+
text: "you "
|
|
26
|
+
StackLabel:
|
|
27
|
+
text: "need to "
|
|
28
|
+
StackLabel:
|
|
29
|
+
text: "edit the "
|
|
30
|
+
Button:
|
|
31
|
+
text: "Settings"
|
|
32
|
+
font_size: "20sp"
|
|
33
|
+
size_hint: None, None
|
|
34
|
+
width: self.texture_size[0] + sp(20)
|
|
35
|
+
height: sp(30)
|
|
36
|
+
on_release: App.get_running_app().root.switch_screen(ScreenName.settings)
|
|
37
|
+
StackLabel:
|
|
38
|
+
text: " section."
|
|
39
|
+
|
|
40
|
+
<StartingServerScreen@Screen>:
|
|
41
|
+
Label:
|
|
42
|
+
text: f"Waiting {tc('Python', 1)}{tc('here', 3)} to start"
|
|
43
|
+
font_size: "30sp"
|
|
44
|
+
LoadingImage:
|
|
45
|
+
|
|
46
|
+
<ServerScreenManager>:
|
|
47
|
+
transition: FallOutTransition()
|
|
48
|
+
Screen:
|
|
49
|
+
LoadingImage:
|
|
50
|
+
ServerNotConfigured:
|
|
51
|
+
name: ServerState.not_configured
|
|
52
|
+
Screen:
|
|
53
|
+
StartingServerScreen:
|
|
54
|
+
name: ServerState.starting_server
|
|
55
|
+
Screen:
|
|
56
|
+
name: ServerState.ready
|
|
57
|
+
ConnectionAddressInfoBox:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""%here server screen."""
|
|
2
|
+
|
|
3
|
+
from enum_here import ServerState
|
|
2
4
|
from kivy.app import App
|
|
3
5
|
from kivy.clock import Clock, mainthread
|
|
4
6
|
from kivy.uix.screenmanager import ScreenManager
|
|
5
7
|
|
|
6
|
-
from enum_here import ServerState
|
|
7
|
-
|
|
8
8
|
|
|
9
9
|
class ServerScreenManager(ScreenManager):
|
|
10
10
|
"""Screen manager for server %here section."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#:kivy 1.0
|
|
2
|
+
#:import App kivy.app.App
|
|
3
|
+
#:import SettingPassword ui_here.settings_here.SettingPassword
|
|
4
|
+
|
|
5
|
+
<SettingPassword>:
|
|
6
|
+
PasswordLabel:
|
|
7
|
+
pos: root.pos
|
|
8
|
+
font_size: "15sp"
|
|
9
|
+
text: '*' * len(root.value or '')
|
|
10
|
+
|
|
11
|
+
<SettingButton>:
|
|
12
|
+
size_hint: 1, None
|
|
13
|
+
height: sp(60)
|
|
14
|
+
opacity: 1 if self.active else 0
|
|
15
|
+
|
|
16
|
+
Button:
|
|
17
|
+
size_hint: None, None
|
|
18
|
+
width: self.texture_size[0] + sp(20)
|
|
19
|
+
height: sp(40)
|
|
20
|
+
halign: "center"
|
|
21
|
+
text: root.title
|
|
22
|
+
disabled: not root.active
|
|
23
|
+
on_release: root.on_release()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<StartServersettingButton>:
|
|
27
|
+
title: f"{tc('▶', 3)} Start the server"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
<ShowPolicySettingButton>:
|
|
31
|
+
title: "Open privacy\n policy in browser"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Settings panel widgets."""
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import webbrowser
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from kivy.app import App
|
|
6
7
|
from kivy.config import Config
|
|
@@ -14,7 +15,6 @@ from kivy.uix.label import Label
|
|
|
14
15
|
from kivy.uix.popup import Popup
|
|
15
16
|
from kivy.uix.settings import Settings, SettingString
|
|
16
17
|
|
|
17
|
-
|
|
18
18
|
SETTINGS_HERE = """
|
|
19
19
|
[
|
|
20
20
|
{
|
|
@@ -137,7 +137,7 @@ class SettingsHere(Settings):
|
|
|
137
137
|
self.add_kivy_panel()
|
|
138
138
|
self.add_json_panel("Privacy Policy", Config, data=SETTINGS_PRIVACY)
|
|
139
139
|
|
|
140
|
-
def get_pythonhere_config(self) ->
|
|
140
|
+
def get_pythonhere_config(self) -> dict[str, Any]:
|
|
141
141
|
"""Extract server parts of the config."""
|
|
142
142
|
return {
|
|
143
143
|
"username": Config.get("pythonhere", "username"),
|
pythonhere/version_here.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.2.0"
|
pythonhere/window_here.py
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythonhere
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Here is the Kivy based app to run code from the Jupyter magic %there
|
|
5
|
+
Author-email: b3b <ash.b3b@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/b3b/ipython-pythonhere
|
|
8
|
+
Project-URL: Changelog, https://github.com/b3b/pythonhere/blob/master/CHANGELOG.rst
|
|
9
|
+
Keywords: android,ipython,jupyter,magic,kivy
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/x-rst
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: herethere[magic]>=0.1.0
|
|
20
|
+
Requires-Dist: ipython
|
|
21
|
+
Requires-Dist: ipywidgets
|
|
22
|
+
Requires-Dist: Pillow
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
PythonHere
|
|
26
|
+
==========
|
|
27
|
+
|
|
28
|
+
.. start-badges
|
|
29
|
+
.. image:: https://img.shields.io/pypi/status/pythonhere
|
|
30
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
31
|
+
:alt: Status
|
|
32
|
+
.. image:: https://img.shields.io/pypi/v/pythonhere.svg
|
|
33
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
34
|
+
:alt: Latest version on PyPi
|
|
35
|
+
.. image:: https://img.shields.io/docker/v/herethere/pythonhere?color=%23FFD43B&label=Docker%20Image
|
|
36
|
+
:target: https://hub.docker.com/r/herethere/pythonhere
|
|
37
|
+
:alt: Docker Image Version (latest by date)
|
|
38
|
+
.. image:: https://img.shields.io/pypi/pyversions/pythonhere.svg
|
|
39
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
40
|
+
:alt: Supported Python versions
|
|
41
|
+
.. image:: https://github.com/b3b/pythonhere/actions/workflows/tests.yml/badge.svg?branch=master
|
|
42
|
+
:target: https://github.com/b3b/pythonhere/actions/workflows/tests.yml?query=branch%3Amaster
|
|
43
|
+
:alt: CI Status
|
|
44
|
+
.. image:: https://codecov.io/github/b3b/pythonhere/coverage.svg?branch=master
|
|
45
|
+
:target: https://codecov.io/github/b3b/pythonhere?branch=master
|
|
46
|
+
:alt: Code coverage Status
|
|
47
|
+
.. end-badges
|
|
48
|
+
|
|
49
|
+
*PythonHere* lets you run Python code from a local `Jupyter <https://jupyter.org/>`_
|
|
50
|
+
notebook inside a remote `Kivy <https://kivy.org>`_ app.
|
|
51
|
+
|
|
52
|
+
PythonHere has two parts:
|
|
53
|
+
|
|
54
|
+
* *Here* is the remote/server side. It runs a Python environment with a Kivy GUI,
|
|
55
|
+
for example on Android, Raspberry Pi, or another machine.
|
|
56
|
+
* *%there* is the local/client side. It is a Jupyter magic command for running
|
|
57
|
+
code interactively in the remote PythonHere environment.
|
|
58
|
+
|
|
59
|
+
This makes PythonHere useful as a live Python/Kivy playground, and as a way to
|
|
60
|
+
inspect or control a Python app running remotely.
|
|
61
|
+
|
|
62
|
+
Project documentation: https://herethere.me/pythonhere
|
|
63
|
+
|
|
64
|
+
.. image:: https://raw.githubusercontent.com/b3b/pythonhere/master/docs/description.png
|
|
65
|
+
:alt: Project description
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Install the Android app
|
|
69
|
+
-----------------------
|
|
70
|
+
|
|
71
|
+
Ready-to-use *PythonHere* APKs are available from the `GitHub Releases <https://github.com/b3b/pythonhere/releases>`_ page.
|
|
72
|
+
|
|
73
|
+
For APK provenance and signing checks, see `Android APK verification <https://github.com/b3b/pythonhere/blob/master/docs/android-apk-verification.rst>`_.
|
|
74
|
+
For a list of Python packages included in the Android build, see `buildozer.spec <https://github.com/b3b/pythonhere/blob/master/buildozer.spec>`_.
|
|
75
|
+
|
|
76
|
+
Start a local Jupyter environment with Docker
|
|
77
|
+
---------------------------------------------
|
|
78
|
+
|
|
79
|
+
The Docker image is based on `Jupyter Docker Stacks <https://jupyter-docker-stacks.readthedocs.io/en/latest/>`_
|
|
80
|
+
and includes *PythonHere* with usage examples.
|
|
81
|
+
|
|
82
|
+
Example command to start the Docker container::
|
|
83
|
+
|
|
84
|
+
docker run \
|
|
85
|
+
--rm \
|
|
86
|
+
-p 8888:8888 \
|
|
87
|
+
--user root \
|
|
88
|
+
-e CHOWN_EXTRA=/home/jovyan/work \
|
|
89
|
+
-e CHOWN_EXTRA_OPTS='-R' \
|
|
90
|
+
-v "$(pwd)/work":/home/jovyan/work \
|
|
91
|
+
herethere/pythonhere:latest
|
|
92
|
+
|
|
93
|
+
The command exposes the Jupyter server on host port ``8888``. Jupyter logs are
|
|
94
|
+
printed in the terminal and include a URL such as
|
|
95
|
+
``http://127.0.0.1:8888/?token=...``. Open this URL in a browser to use the
|
|
96
|
+
local Jupyter environment.
|
|
97
|
+
|
|
98
|
+
Files in ``/home/jovyan/work`` inside the container are stored in the local
|
|
99
|
+
``work`` directory.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
Run a local Jupyter environment without Docker
|
|
103
|
+
----------------------------------------------
|
|
104
|
+
|
|
105
|
+
Commands to run locally::
|
|
106
|
+
|
|
107
|
+
pip install pythonhere jupyter
|
|
108
|
+
jupyter notebook
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Build Android app
|
|
112
|
+
-----------------
|
|
113
|
+
|
|
114
|
+
To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the source directory::
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
buildozer android debug
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Related resources
|
|
121
|
+
-----------------
|
|
122
|
+
|
|
123
|
+
* `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
|
|
124
|
+
* `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
|
|
125
|
+
* `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
pythonhere/__init__.py,sha256=gkVrYFkF3klE1fripHpmTuZx8_BaVHWRRvvr6PK6XWE,212
|
|
2
|
+
pythonhere/android_here.py,sha256=imQCNkhAJYgT3gMeX25kd9cQzH5ZbeF5PlgP8e9sbl0,3316
|
|
3
|
+
pythonhere/enum_here.py,sha256=FPj3pBeXfpPwl4-LDdC-wmbaoiv4QdbKHkGoBfTXnTE,449
|
|
4
|
+
pythonhere/exception_manager_here.kv,sha256=FZNhQgVojSLcVxZ0vjAxAF915xCBP-3nldPXVczRzCE,656
|
|
5
|
+
pythonhere/exception_manager_here.py,sha256=-UYqGRbzkoyfF0-pLWbxaFGEAvAdaw6b8hkm_bMIKx8,1943
|
|
6
|
+
pythonhere/launcher_here.py,sha256=I5zBpN0P3hmhsZFOJemxQ9Q2k2am5zkfS6Vg0Svnl98,1274
|
|
7
|
+
pythonhere/main.py,sha256=7vAF6mznCr2JFxeXjm3-a6PN1abPt-loI8ZHncAAPp0,7237
|
|
8
|
+
pythonhere/network_here.py,sha256=e3FDlBcXPaTFlWaqW2_bcWctht-ifpFIvtOj0lO4S9w,1558
|
|
9
|
+
pythonhere/patches_here.py,sha256=zB7Ohbk5PIXt0imnnrV5Mtl-Y7jpD_p5IBPlijQcCCQ,1727
|
|
10
|
+
pythonhere/pythonhere.kv,sha256=x1xwiKvJAevqBhtXGLb_axIf-VRos4V_jM9Q2i44f2U,1453
|
|
11
|
+
pythonhere/server_here.py,sha256=ss4Alg4ySkI0aG58l8D2SQMXiGHJeK-hGUIIBrbQ6nE,1854
|
|
12
|
+
pythonhere/version_here.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
|
|
13
|
+
pythonhere/window_here.py,sha256=na47v7bgomAjb6VC_g35RwhzEtm5RCInQIuQK27zY8U,2023
|
|
14
|
+
pythonhere/data/logo/logo-128.png,sha256=4sitDJcVBHOU5UPxicmrzc6YqFY7_xtYrc_ULyrw9ks,4161
|
|
15
|
+
pythonhere/data/logo/logo-32.png,sha256=qanrIAt6vK_2BsCjCxth2B4vxBa0ONnpDlils7syUWE,1247
|
|
16
|
+
pythonhere/data/logo/logo-splash.png,sha256=-edmwKnkkpGP4RZ0AJYto5_BOy8NoISt5ftOEI8BQOY,4663
|
|
17
|
+
pythonhere/magic_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
pythonhere/magic_here/shortcuts.py,sha256=pdB_ET6Snl4DxusbyZPI1_G8jGzA8d35er60qQb_2cY,2499
|
|
19
|
+
pythonhere/ui_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
pythonhere/ui_here/actionbar_here.kv,sha256=cOjY4ByOyBDUbeS9kbI3JAhi_X3kJ3Pb5h4RL58urwU,214
|
|
21
|
+
pythonhere/ui_here/common_here.kv,sha256=vnNyDwAKKNbRKvh0QKMbTvR0jNDKiY8DdGRnoEEexSc,324
|
|
22
|
+
pythonhere/ui_here/connection_address_here.kv,sha256=T0E8WX6QVvFLB2QL4GP7g9uDwQt5ja7aAODUe8x1NUY,885
|
|
23
|
+
pythonhere/ui_here/connection_address_here.py,sha256=_NM2mJuXM2jgk9eXH8NEV6IxGvJX_fc3wuEq-n7BoLs,1160
|
|
24
|
+
pythonhere/ui_here/layout_here.py,sha256=4sOf4ELSeaJJQkBfgrkM0Cmf6A1bpYTYtZl5GDXGJZE,485
|
|
25
|
+
pythonhere/ui_here/server_screen_here.kv,sha256=x0Se8hmgjS57gFIPa1r2lf6yJchs3lXOrAAtWf6M5Io,1562
|
|
26
|
+
pythonhere/ui_here/server_screen_here.py,sha256=2zQv1YFmiKNxa__7UZDYbAAAwYFLoZPSZPhr7vjCAkU,925
|
|
27
|
+
pythonhere/ui_here/settings_here.kv,sha256=stMcDq8dCYjay4idK30_7EUu5edkMDAQVPv0H0GuAcM,710
|
|
28
|
+
pythonhere/ui_here/settings_here.py,sha256=7NflZBUqY2ANZaSEuubaX9wIKniqbekcNQkUbqhhmto,4093
|
|
29
|
+
pythonhere-0.2.0.dist-info/licenses/LICENSE,sha256=nW9_eVi3dSMOkr6xghsobAovBEJiffjl_5WjujJTa74,1071
|
|
30
|
+
pythonhere-0.2.0.dist-info/METADATA,sha256=s6EVWNzwY9n6PDwLpNX6kVrd55fGxGpba3j26LYz7cQ,4790
|
|
31
|
+
pythonhere-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
32
|
+
pythonhere-0.2.0.dist-info/top_level.txt,sha256=dvkfRGF1tFbkjXzD9vwqXTge1Znkv7ga5fRJy5yhJsE,11
|
|
33
|
+
pythonhere-0.2.0.dist-info/RECORD,,
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: pythonhere
|
|
3
|
-
Version: 0.1.5.2
|
|
4
|
-
Summary: Here is the Kivy based app to run code from the Jupyter magic %there
|
|
5
|
-
Home-page: https://github.com/b3b/ipython-pythonhere
|
|
6
|
-
Author: b3b
|
|
7
|
-
Author-email: ash.b3b@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Project-URL: Changelog, https://github.com/b3b/pythonhere/blob/master/CHANGELOG.rst
|
|
10
|
-
Keywords: android ipython jupyter magic kivy
|
|
11
|
-
Platform: UNKNOWN
|
|
12
|
-
Classifier: Development Status :: 3 - Alpha
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Description-Content-Type: text/x-rst
|
|
16
|
-
Requires-Dist: kivy (>=2.0.0)
|
|
17
|
-
Requires-Dist: herethere (<0.2.0,>=0.1.0)
|
|
18
|
-
Requires-Dist: ifaddr
|
|
19
|
-
Requires-Dist: ipython
|
|
20
|
-
Requires-Dist: ipywidgets
|
|
21
|
-
Requires-Dist: nest-asyncio
|
|
22
|
-
Requires-Dist: Pillow
|
|
23
|
-
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: black ; extra == 'dev'
|
|
25
|
-
Requires-Dist: codecov ; extra == 'dev'
|
|
26
|
-
Requires-Dist: docutils ; extra == 'dev'
|
|
27
|
-
Requires-Dist: flake8 ; extra == 'dev'
|
|
28
|
-
Requires-Dist: jupytext ; extra == 'dev'
|
|
29
|
-
Requires-Dist: pylint ; extra == 'dev'
|
|
30
|
-
Requires-Dist: pytest ; extra == 'dev'
|
|
31
|
-
Requires-Dist: pytest-asyncio ; extra == 'dev'
|
|
32
|
-
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
33
|
-
Requires-Dist: pytest-mock ; extra == 'dev'
|
|
34
|
-
Provides-Extra: docker
|
|
35
|
-
Requires-Dist: jupytext (==1.7.1) ; extra == 'docker'
|
|
36
|
-
|
|
37
|
-
PythonHere
|
|
38
|
-
==========
|
|
39
|
-
|
|
40
|
-
.. start-badges
|
|
41
|
-
.. image:: https://img.shields.io/pypi/status/pythonhere
|
|
42
|
-
:target: https://pypi.python.org/pypi/pythonhere
|
|
43
|
-
:alt: Status
|
|
44
|
-
.. image:: https://img.shields.io/pypi/v/pythonhere.svg
|
|
45
|
-
:target: https://pypi.python.org/pypi/pythonhere
|
|
46
|
-
:alt: Latest version on PyPi
|
|
47
|
-
.. image:: https://img.shields.io/docker/v/herethere/pythonhere?color=%23FFD43B&label=Docker%20Image
|
|
48
|
-
:target: https://hub.docker.com/r/herethere/pythonhere
|
|
49
|
-
:alt: Docker Image Version (latest by date)
|
|
50
|
-
.. image:: https://img.shields.io/pypi/pyversions/pythonhere.svg
|
|
51
|
-
:target: https://pypi.python.org/pypi/pythonhere
|
|
52
|
-
:alt: Supported Python versions
|
|
53
|
-
.. image:: https://github.com/b3b/pythonhere/workflows/ci/badge.svg?branch=master
|
|
54
|
-
:target: https://github.com/b3b/pythonhere/actions?workflow=CI
|
|
55
|
-
:alt: CI Status
|
|
56
|
-
.. image:: https://codecov.io/github/b3b/pythonhere/coverage.svg?branch=master
|
|
57
|
-
:target: https://codecov.io/github/b3b/pythonhere?branch=master
|
|
58
|
-
:alt: Code coverage Status
|
|
59
|
-
.. end-badges
|
|
60
|
-
|
|
61
|
-
*Here* is the `Kivy <https://kivy.org>`_ based app to run Python code from the `Jupyter <https://jupyter.org/>`_ magic %there.
|
|
62
|
-
|
|
63
|
-
- *Here* is a server part with the GUI interface. It could be Android, Raspberry Pi, some other remote device that being debugged.
|
|
64
|
-
- And *%there* is a client - Jupyter magic command to run code interactively on remote device.
|
|
65
|
-
|
|
66
|
-
This app could serve as a Python Kivy playground, for dynamic code execution from the PC.
|
|
67
|
-
|
|
68
|
-
Project documentation: https://herethere.me
|
|
69
|
-
|
|
70
|
-
.. image:: https://raw.githubusercontent.com/b3b/pythonhere/master/docs/description.png
|
|
71
|
-
:alt: Project description
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Install on Android
|
|
75
|
-
------------------
|
|
76
|
-
|
|
77
|
-
App is available on `Google Play <https://play.google.com/store/apps/details?id=me.herethere.pythonhere>`_.
|
|
78
|
-
|
|
79
|
-
Ready-to-use *PythonHere* APKs are available in the `Releases <https://github.com/b3b/pythonhere/releases>`_ section.
|
|
80
|
-
|
|
81
|
-
For a list of installed Python packages, see: `buildozer.spec <./buildozer.spec>`_.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Quick Start with Docker
|
|
85
|
-
-----------------------
|
|
86
|
-
|
|
87
|
-
Docker image is based on `Jupyter Docker Stacks <https://jupyter-docker-stacks.readthedocs.io/en/latest/>`_, and includes installed *PythonHere* with usage examples.
|
|
88
|
-
|
|
89
|
-
Example command to start the Docker container::
|
|
90
|
-
|
|
91
|
-
docker run \
|
|
92
|
-
--rm \
|
|
93
|
-
-p 8888:8888 \
|
|
94
|
-
-v "$(pwd)/work":/home/jovyan/work \
|
|
95
|
-
herethere/pythonhere:latest
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Command will expose the Jupyter Notebook server on host port 8888. Jupyter logs appear in the terminal and include an URL to the notebook server: http://127.0.0.1:8888/?token=... . Visiting this URL in a browser loads the Jupyter Notebook dashboard page.
|
|
99
|
-
|
|
100
|
-
Files from the directory **work** inside container, will be available in the host directory with the same name: **work**.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Run with Docker Compose
|
|
104
|
-
-----------------------
|
|
105
|
-
|
|
106
|
-
Commands to run with Docker Compose, in the source directory:::
|
|
107
|
-
|
|
108
|
-
cp docker-compose.yml.tmpl docker-compose.yml
|
|
109
|
-
docker-compose up
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Run locally
|
|
113
|
-
-----------
|
|
114
|
-
|
|
115
|
-
Commands to run locally::
|
|
116
|
-
|
|
117
|
-
pip install pythonhere jupyter
|
|
118
|
-
jupyter notebook
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Build Android app
|
|
122
|
-
-----------------
|
|
123
|
-
|
|
124
|
-
To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the source directory::
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
buildozer android debug
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Related resources
|
|
132
|
-
-----------------
|
|
133
|
-
|
|
134
|
-
* `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
|
|
135
|
-
* `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
|
|
136
|
-
* `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
|
|
137
|
-
* `Buildozer action <https://github.com/ArtemSBulgakov/buildozer-action>`_ : GitHub action that is used to build Android APK with Buildozer
|
|
138
|
-
|
|
139
|
-
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
pythonhere/__init__.py,sha256=Z8wJ5iv1FshCKvQpoRKY28rulC7HTWfy8rj9TdYJIJ8,212
|
|
2
|
-
pythonhere/android_here.py,sha256=niI9UJOMwBocblMg2bseZLtTRqDMz4-k-pUcY3PsmTg,3355
|
|
3
|
-
pythonhere/enum_here.py,sha256=RBXnu8ppF4idfVvBYbMQ1JO3cfEXfRDrm-uYlPXzhrw,448
|
|
4
|
-
pythonhere/exception_manager_here.py,sha256=rm8gHFU37cEJk10anyrIX_CyTqATBsnQVIJJLcgINhs,2399
|
|
5
|
-
pythonhere/launcher_here.py,sha256=sBgFv9-GJ5LbxfEiikKlejcA8mDi7zPBxEbKWStVAGc,1274
|
|
6
|
-
pythonhere/main.py,sha256=Crc76Py2rz7ERg9mEBD4KD1mOdCqvuy8N-hgMDPxMUY,5571
|
|
7
|
-
pythonhere/network_here.py,sha256=vbVjAnnEtnysj2SfblVG2Fr0HNqnAPVHZQCU22K9BnQ,1555
|
|
8
|
-
pythonhere/patches_here.py,sha256=bjUvdved7ZaL-9HTKJrtocCOEWEueSkGZs4dxr1L7rU,1728
|
|
9
|
-
pythonhere/server_here.py,sha256=IDH_ZYCg0z4F9LjkfY9Wzs21Z4z2L7slfc6xqfMYxH4,1855
|
|
10
|
-
pythonhere/version_here.py,sha256=tfw61Ig2RHTD_q6DG85fYMOUic2ygXuYT-eBNy9J8MY,24
|
|
11
|
-
pythonhere/window_here.py,sha256=2VU1AC_78wvKtwObezCRYiBdrEYpNCr9lJ1c3nnPgMs,2022
|
|
12
|
-
pythonhere/magic_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
pythonhere/magic_here/shortcuts.py,sha256=kk9JycYcBrmo3lbVdiXLAJz82Zle4Z6_SQLGuiW0SE4,2365
|
|
14
|
-
pythonhere/ui_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
pythonhere/ui_here/connection_address_here.py,sha256=VrMMFG9wUJVwmegysi8cl-GZeyNRdKWSjCFcmQtm9ps,1160
|
|
16
|
-
pythonhere/ui_here/layout_here.py,sha256=ofEpRTWhwvEmYF9MK8f_UKC6FuCTP9KEPBhOR_S7Z-Y,484
|
|
17
|
-
pythonhere/ui_here/server_screen_here.py,sha256=3sBLldgRWvo48_NBG16YjHvAzom-GWg6ngVpUUCu0Uw,925
|
|
18
|
-
pythonhere/ui_here/settings_here.py,sha256=P3KeR6TTX0ErGxXjNQ3LN8ujrJ9SQNEr4ZV-3UBw1M4,4099
|
|
19
|
-
pythonhere-0.1.5.2.dist-info/LICENSE,sha256=nW9_eVi3dSMOkr6xghsobAovBEJiffjl_5WjujJTa74,1071
|
|
20
|
-
pythonhere-0.1.5.2.dist-info/METADATA,sha256=n-NOX_4hz1SQEfC-h8cHYkew9M8bygBjj8CDCw491_M,5002
|
|
21
|
-
pythonhere-0.1.5.2.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
22
|
-
pythonhere-0.1.5.2.dist-info/top_level.txt,sha256=dvkfRGF1tFbkjXzD9vwqXTge1Znkv7ga5fRJy5yhJsE,11
|
|
23
|
-
pythonhere-0.1.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|