pythonhere 0.1.4__tar.gz → 0.2.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.
- pythonhere-0.2.0/LICENSE +22 -0
- pythonhere-0.2.0/PKG-INFO +125 -0
- pythonhere-0.2.0/README.rst +101 -0
- pythonhere-0.2.0/pyproject.toml +151 -0
- pythonhere-0.2.0/pythonhere/android_here.py +100 -0
- pythonhere-0.2.0/pythonhere/data/logo/logo-128.png +0 -0
- pythonhere-0.2.0/pythonhere/data/logo/logo-32.png +0 -0
- pythonhere-0.2.0/pythonhere/data/logo/logo-splash.png +0 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/enum_here.py +1 -0
- pythonhere-0.2.0/pythonhere/exception_manager_here.kv +22 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/exception_manager_here.py +14 -30
- pythonhere-0.2.0/pythonhere/launcher_here.py +46 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/magic_here/shortcuts.py +21 -5
- pythonhere-0.2.0/pythonhere/main.py +225 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/network_here.py +4 -3
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/patches_here.py +1 -2
- pythonhere-0.2.0/pythonhere/pythonhere.kv +49 -0
- pythonhere-0.2.0/pythonhere/server_here.py +59 -0
- pythonhere-0.2.0/pythonhere/ui_here/actionbar_here.kv +8 -0
- pythonhere-0.2.0/pythonhere/ui_here/common_here.kv +14 -0
- pythonhere-0.2.0/pythonhere/ui_here/connection_address_here.kv +32 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/ui_here/layout_here.py +1 -0
- pythonhere-0.2.0/pythonhere/ui_here/server_screen_here.kv +57 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/ui_here/server_screen_here.py +4 -3
- pythonhere-0.2.0/pythonhere/ui_here/settings_here.kv +31 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/ui_here/settings_here.py +15 -6
- pythonhere-0.2.0/pythonhere/version_here.py +1 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/window_here.py +10 -3
- pythonhere-0.2.0/pythonhere.egg-info/PKG-INFO +125 -0
- pythonhere-0.2.0/pythonhere.egg-info/SOURCES.txt +47 -0
- pythonhere-0.2.0/pythonhere.egg-info/requires.txt +4 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/setup.cfg +0 -3
- pythonhere-0.2.0/setup.py +3 -0
- pythonhere-0.2.0/tests/test_android_here.py +32 -0
- pythonhere-0.2.0/tests/test_exception_manager_here.py +20 -0
- pythonhere-0.2.0/tests/test_launcher_here.py +50 -0
- pythonhere-0.2.0/tests/test_magic.py +73 -0
- pythonhere-0.2.0/tests/test_main.py +271 -0
- pythonhere-0.2.0/tests/test_network.py +8 -0
- pythonhere-0.2.0/tests/test_patches.py +37 -0
- pythonhere-0.2.0/tests/test_server_here.py +86 -0
- pythonhere-0.2.0/tests/test_settings.py +33 -0
- pythonhere-0.2.0/tests/test_window_here.py +14 -0
- pythonhere-0.1.4/PKG-INFO +0 -119
- pythonhere-0.1.4/README.rst +0 -101
- pythonhere-0.1.4/pythonhere/main.py +0 -128
- pythonhere-0.1.4/pythonhere/server_here.py +0 -56
- pythonhere-0.1.4/pythonhere/version_here.py +0 -1
- pythonhere-0.1.4/pythonhere.egg-info/PKG-INFO +0 -119
- pythonhere-0.1.4/pythonhere.egg-info/SOURCES.txt +0 -24
- pythonhere-0.1.4/pythonhere.egg-info/requires.txt +0 -22
- pythonhere-0.1.4/setup.py +0 -63
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/__init__.py +1 -1
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/magic_here/__init__.py +0 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/ui_here/__init__.py +0 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere/ui_here/connection_address_here.py +2 -2
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere.egg-info/dependency_links.txt +0 -0
- {pythonhere-0.1.4 → pythonhere-0.2.0}/pythonhere.egg-info/top_level.txt +0 -0
pythonhere-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 b3b
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -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,101 @@
|
|
|
1
|
+
PythonHere
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
.. start-badges
|
|
5
|
+
.. image:: https://img.shields.io/pypi/status/pythonhere
|
|
6
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
7
|
+
:alt: Status
|
|
8
|
+
.. image:: https://img.shields.io/pypi/v/pythonhere.svg
|
|
9
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
10
|
+
:alt: Latest version on PyPi
|
|
11
|
+
.. image:: https://img.shields.io/docker/v/herethere/pythonhere?color=%23FFD43B&label=Docker%20Image
|
|
12
|
+
:target: https://hub.docker.com/r/herethere/pythonhere
|
|
13
|
+
:alt: Docker Image Version (latest by date)
|
|
14
|
+
.. image:: https://img.shields.io/pypi/pyversions/pythonhere.svg
|
|
15
|
+
:target: https://pypi.python.org/pypi/pythonhere
|
|
16
|
+
:alt: Supported Python versions
|
|
17
|
+
.. image:: https://github.com/b3b/pythonhere/actions/workflows/tests.yml/badge.svg?branch=master
|
|
18
|
+
:target: https://github.com/b3b/pythonhere/actions/workflows/tests.yml?query=branch%3Amaster
|
|
19
|
+
:alt: CI Status
|
|
20
|
+
.. image:: https://codecov.io/github/b3b/pythonhere/coverage.svg?branch=master
|
|
21
|
+
:target: https://codecov.io/github/b3b/pythonhere?branch=master
|
|
22
|
+
:alt: Code coverage Status
|
|
23
|
+
.. end-badges
|
|
24
|
+
|
|
25
|
+
*PythonHere* lets you run Python code from a local `Jupyter <https://jupyter.org/>`_
|
|
26
|
+
notebook inside a remote `Kivy <https://kivy.org>`_ app.
|
|
27
|
+
|
|
28
|
+
PythonHere has two parts:
|
|
29
|
+
|
|
30
|
+
* *Here* is the remote/server side. It runs a Python environment with a Kivy GUI,
|
|
31
|
+
for example on Android, Raspberry Pi, or another machine.
|
|
32
|
+
* *%there* is the local/client side. It is a Jupyter magic command for running
|
|
33
|
+
code interactively in the remote PythonHere environment.
|
|
34
|
+
|
|
35
|
+
This makes PythonHere useful as a live Python/Kivy playground, and as a way to
|
|
36
|
+
inspect or control a Python app running remotely.
|
|
37
|
+
|
|
38
|
+
Project documentation: https://herethere.me/pythonhere
|
|
39
|
+
|
|
40
|
+
.. image:: https://raw.githubusercontent.com/b3b/pythonhere/master/docs/description.png
|
|
41
|
+
:alt: Project description
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Install the Android app
|
|
45
|
+
-----------------------
|
|
46
|
+
|
|
47
|
+
Ready-to-use *PythonHere* APKs are available from the `GitHub Releases <https://github.com/b3b/pythonhere/releases>`_ page.
|
|
48
|
+
|
|
49
|
+
For APK provenance and signing checks, see `Android APK verification <https://github.com/b3b/pythonhere/blob/master/docs/android-apk-verification.rst>`_.
|
|
50
|
+
For a list of Python packages included in the Android build, see `buildozer.spec <https://github.com/b3b/pythonhere/blob/master/buildozer.spec>`_.
|
|
51
|
+
|
|
52
|
+
Start a local Jupyter environment with Docker
|
|
53
|
+
---------------------------------------------
|
|
54
|
+
|
|
55
|
+
The Docker image is based on `Jupyter Docker Stacks <https://jupyter-docker-stacks.readthedocs.io/en/latest/>`_
|
|
56
|
+
and includes *PythonHere* with usage examples.
|
|
57
|
+
|
|
58
|
+
Example command to start the Docker container::
|
|
59
|
+
|
|
60
|
+
docker run \
|
|
61
|
+
--rm \
|
|
62
|
+
-p 8888:8888 \
|
|
63
|
+
--user root \
|
|
64
|
+
-e CHOWN_EXTRA=/home/jovyan/work \
|
|
65
|
+
-e CHOWN_EXTRA_OPTS='-R' \
|
|
66
|
+
-v "$(pwd)/work":/home/jovyan/work \
|
|
67
|
+
herethere/pythonhere:latest
|
|
68
|
+
|
|
69
|
+
The command exposes the Jupyter server on host port ``8888``. Jupyter logs are
|
|
70
|
+
printed in the terminal and include a URL such as
|
|
71
|
+
``http://127.0.0.1:8888/?token=...``. Open this URL in a browser to use the
|
|
72
|
+
local Jupyter environment.
|
|
73
|
+
|
|
74
|
+
Files in ``/home/jovyan/work`` inside the container are stored in the local
|
|
75
|
+
``work`` directory.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Run a local Jupyter environment without Docker
|
|
79
|
+
----------------------------------------------
|
|
80
|
+
|
|
81
|
+
Commands to run locally::
|
|
82
|
+
|
|
83
|
+
pip install pythonhere jupyter
|
|
84
|
+
jupyter notebook
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Build Android app
|
|
88
|
+
-----------------
|
|
89
|
+
|
|
90
|
+
To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the source directory::
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
buildozer android debug
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
Related resources
|
|
97
|
+
-----------------
|
|
98
|
+
|
|
99
|
+
* `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
|
|
100
|
+
* `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
|
|
101
|
+
* `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=70", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pythonhere"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Here is the Kivy based app to run code from the Jupyter magic %there"
|
|
9
|
+
readme = { file = "README.rst", content-type = "text/x-rst" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "b3b", email = "ash.b3b@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["android", "ipython", "jupyter", "magic", "kivy"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"herethere[magic]>=0.1.0",
|
|
26
|
+
"ipython",
|
|
27
|
+
"ipywidgets",
|
|
28
|
+
"Pillow",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
{ include-group = "docker" },
|
|
34
|
+
"build",
|
|
35
|
+
"codecov",
|
|
36
|
+
"docutils",
|
|
37
|
+
"ifaddr",
|
|
38
|
+
"kivy==2.3.1",
|
|
39
|
+
"nest-asyncio2==1.7.2",
|
|
40
|
+
"pylint",
|
|
41
|
+
"pytest",
|
|
42
|
+
"pytest-asyncio",
|
|
43
|
+
"pytest-cov",
|
|
44
|
+
"pytest-mock",
|
|
45
|
+
"ruff",
|
|
46
|
+
"twine",
|
|
47
|
+
]
|
|
48
|
+
docker = [
|
|
49
|
+
"jupytext==1.19.3",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/b3b/ipython-pythonhere"
|
|
54
|
+
Changelog = "https://github.com/b3b/pythonhere/blob/master/CHANGELOG.rst"
|
|
55
|
+
|
|
56
|
+
[tool.uv]
|
|
57
|
+
environments = [
|
|
58
|
+
"sys_platform == 'linux'",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
asyncio_mode = "auto"
|
|
63
|
+
|
|
64
|
+
[tool.setuptools]
|
|
65
|
+
packages = [
|
|
66
|
+
"pythonhere",
|
|
67
|
+
"pythonhere.magic_here",
|
|
68
|
+
"pythonhere.ui_here",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.dynamic]
|
|
72
|
+
version = { attr = "pythonhere.version_here.__version__" }
|
|
73
|
+
|
|
74
|
+
[tool.setuptools.package-data]
|
|
75
|
+
pythonhere = [
|
|
76
|
+
"*.kv",
|
|
77
|
+
"data/logo/*.png",
|
|
78
|
+
"ui_here/*.kv",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 88
|
|
83
|
+
target-version = "py310"
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint]
|
|
86
|
+
select = [
|
|
87
|
+
"E", # pycodestyle errors
|
|
88
|
+
"F", # pyflakes
|
|
89
|
+
"W", # pycodestyle warnings
|
|
90
|
+
"I", # import sorting
|
|
91
|
+
"UP", # pyupgrade
|
|
92
|
+
"B", # flake8-bugbear
|
|
93
|
+
"TRY", # exception handling checks
|
|
94
|
+
"PL", # pylint-style checks supported by Ruff
|
|
95
|
+
]
|
|
96
|
+
ignore = [
|
|
97
|
+
"PLC0207", # keep simple last-part string splits when they are clearer
|
|
98
|
+
"PLC0415", # allow delayed Kivy/Android imports that control side effects
|
|
99
|
+
"TRY002", # keep existing broad app-facing exceptions for now
|
|
100
|
+
"TRY003", # allow direct exception messages in this small app
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.ruff.lint.per-file-ignores]
|
|
104
|
+
"tests/**/*.py" = [
|
|
105
|
+
"PLR0913", # parametrized tests naturally pass several fixtures/values
|
|
106
|
+
"PLR2004", # literal expected values are readable in tests
|
|
107
|
+
"PLW0603", # tests may intentionally exercise global namespace behavior
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff.format]
|
|
111
|
+
quote-style = "double"
|
|
112
|
+
indent-style = "space"
|
|
113
|
+
|
|
114
|
+
[tool.pylint.main]
|
|
115
|
+
fail-under = 10.0
|
|
116
|
+
ignore = ["migrations"]
|
|
117
|
+
py-version = "3.10"
|
|
118
|
+
|
|
119
|
+
[tool.pylint."messages control"]
|
|
120
|
+
disable = [
|
|
121
|
+
"attribute-defined-outside-init",
|
|
122
|
+
"bad-inline-option",
|
|
123
|
+
"broad-exception-caught",
|
|
124
|
+
"broad-exception-raised",
|
|
125
|
+
"deprecated-pragma",
|
|
126
|
+
"duplicate-code",
|
|
127
|
+
"file-ignored",
|
|
128
|
+
"import-outside-toplevel",
|
|
129
|
+
"import-error",
|
|
130
|
+
"inconsistent-return-statements",
|
|
131
|
+
"invalid-name",
|
|
132
|
+
"locally-disabled",
|
|
133
|
+
"logging-too-few-args",
|
|
134
|
+
"missing-function-docstring",
|
|
135
|
+
"missing-module-docstring",
|
|
136
|
+
"no-member",
|
|
137
|
+
"possibly-used-before-assignment",
|
|
138
|
+
"protected-access",
|
|
139
|
+
"raw-checker-failed",
|
|
140
|
+
"suppressed-message",
|
|
141
|
+
"too-few-public-methods",
|
|
142
|
+
"too-many-instance-attributes",
|
|
143
|
+
"unnecessary-ellipsis",
|
|
144
|
+
"unused-argument",
|
|
145
|
+
"unused-wildcard-import",
|
|
146
|
+
"use-symbolic-message-instead",
|
|
147
|
+
"useless-suppression",
|
|
148
|
+
"wildcard-import",
|
|
149
|
+
"wrong-import-order",
|
|
150
|
+
]
|
|
151
|
+
enable = ["c-extension-no-member"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Android specific functions."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=invalid-name,import-error,import-outside-toplevel
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from android import activity as android_activity
|
|
8
|
+
from jnius import autoclass, cast
|
|
9
|
+
from kivy.logger import Logger
|
|
10
|
+
|
|
11
|
+
Context = autoclass("android.content.Context")
|
|
12
|
+
Icon = autoclass("android.graphics.drawable.Icon")
|
|
13
|
+
Intent = autoclass("android.content.Intent")
|
|
14
|
+
PythonActivity = autoclass("org.kivy.android.PythonActivity")
|
|
15
|
+
ShortcutInfoBuilder = autoclass("android.content.pm.ShortcutInfo$Builder")
|
|
16
|
+
System = autoclass("java.lang.System")
|
|
17
|
+
Uri = autoclass("android.net.Uri")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_current_intent() -> Intent:
|
|
21
|
+
"""Return the intent that started Python activity."""
|
|
22
|
+
return PythonActivity.mActivity.getIntent()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_startup_script(intent: None = None) -> str | None:
|
|
26
|
+
"""Return script entrypoint that was passed to a given, or current, intent."""
|
|
27
|
+
if not intent:
|
|
28
|
+
intent = get_current_intent()
|
|
29
|
+
data = intent.getData()
|
|
30
|
+
return data and data.toString()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def restart_app(script: str = None):
|
|
34
|
+
"""Restart app, with a script as a starting point if provided."""
|
|
35
|
+
Logger.info("PythonHere: restart requested with a script: %s", script)
|
|
36
|
+
activity = PythonActivity.mActivity
|
|
37
|
+
intent = Intent(activity.getApplicationContext(), PythonActivity)
|
|
38
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
39
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
40
|
+
|
|
41
|
+
if script:
|
|
42
|
+
intent.setData(Uri.parse(script))
|
|
43
|
+
|
|
44
|
+
activity.startActivity(intent)
|
|
45
|
+
System.exit(0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def bind_run_script_on_new_intent():
|
|
49
|
+
"""Add handler for new intent event:
|
|
50
|
+
restart app with entrypoint of a new intent.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def on_new_intent(intent):
|
|
54
|
+
Logger.info("PythonHere: on_new_intent")
|
|
55
|
+
restart_app(get_startup_script(intent))
|
|
56
|
+
Logger.error("PythonHere: app was not restarted")
|
|
57
|
+
|
|
58
|
+
android_activity.bind(on_new_intent=on_new_intent)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def create_shortcut_icon() -> Icon:
|
|
62
|
+
"""Create icon to use for a shurtcut."""
|
|
63
|
+
activity = PythonActivity.mActivity
|
|
64
|
+
Drawable = autoclass(f"{activity.getPackageName()}.R$drawable")
|
|
65
|
+
context = cast("android.content.Context", activity.getApplicationContext())
|
|
66
|
+
return Icon.createWithResource(context, Drawable.icon)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_script_path(script: str) -> str:
|
|
70
|
+
"""Resolve path against upload directory."""
|
|
71
|
+
from kivy.app import App
|
|
72
|
+
|
|
73
|
+
if script.startswith("/"):
|
|
74
|
+
path = Path(script)
|
|
75
|
+
else:
|
|
76
|
+
app = App.get_running_app()
|
|
77
|
+
path = Path(app.upload_dir) / script
|
|
78
|
+
return str(path.resolve(strict=True))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def pin_shortcut(script: str, label: str):
|
|
82
|
+
"""Request a pinned shortcut creation to run a Python script."""
|
|
83
|
+
activity = PythonActivity.mActivity
|
|
84
|
+
context = cast("android.content.Context", activity.getApplicationContext())
|
|
85
|
+
|
|
86
|
+
intent = Intent(activity.getApplicationContext(), PythonActivity)
|
|
87
|
+
intent.setAction(Intent.ACTION_MAIN)
|
|
88
|
+
intent.setData(Uri.parse(resolve_script_path(script)))
|
|
89
|
+
|
|
90
|
+
shortcut = (
|
|
91
|
+
ShortcutInfoBuilder(context, f"pythonhere-{uuid.uuid4().hex}")
|
|
92
|
+
.setShortLabel(label)
|
|
93
|
+
.setLongLabel(label)
|
|
94
|
+
.setIntent(intent)
|
|
95
|
+
.setIcon(create_shortcut_icon())
|
|
96
|
+
.build()
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
manager = activity.getSystemService(Context.SHORTCUT_SERVICE)
|
|
100
|
+
manager.requestPinShortcut(shortcut, None)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Utilities for launching scripts."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import runpy
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from kivy import platform
|
|
9
|
+
from kivy.logger import Logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_script(script: str):
|
|
13
|
+
"""Execute given script."""
|
|
14
|
+
Logger.info("PythonHere: Run script %s", script)
|
|
15
|
+
try:
|
|
16
|
+
path = Path(script).resolve(strict=True)
|
|
17
|
+
except FileNotFoundError:
|
|
18
|
+
Logger.error("Script not found: %s", script)
|
|
19
|
+
raise Exception(f"Script not found: {script}") from None
|
|
20
|
+
|
|
21
|
+
original_cwd = str(Path.cwd())
|
|
22
|
+
original_sys_path = sys.path[:]
|
|
23
|
+
try:
|
|
24
|
+
script_dir = path.parent
|
|
25
|
+
os.chdir(str(script_dir))
|
|
26
|
+
sys.path.insert(0, str(script_dir))
|
|
27
|
+
runpy.run_path(str(path), run_name="__main__")
|
|
28
|
+
finally:
|
|
29
|
+
os.chdir(original_cwd)
|
|
30
|
+
sys.path = original_sys_path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def try_startup_script():
|
|
34
|
+
"""Execute startup script, if it was passed to app."""
|
|
35
|
+
if platform != "android":
|
|
36
|
+
return
|
|
37
|
+
import android_here # pylint: disable=import-outside-toplevel
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
android_here.bind_run_script_on_new_intent()
|
|
41
|
+
script = android_here.get_startup_script()
|
|
42
|
+
if script:
|
|
43
|
+
run_script(script)
|
|
44
|
+
except Exception:
|
|
45
|
+
Logger.exception("PythonHere: Error while starting script")
|
|
46
|
+
raise
|