pythonhere 0.2.0__py3-none-any.whl → 0.2.2__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.
@@ -0,0 +1,202 @@
1
+ ## Plyer helpers
2
+
3
+ Use this addon for Plyer-backed Android/device features:
4
+ notification, Android toast-style messages, vibration, audio recording, camera capture,
5
+ file chooser, GPS/location, battery, accelerometer, compass, text-to-speech,
6
+ and similar Plyer facades.
7
+
8
+ `plyer` is installed; do not need to check for import errors before normal use.
9
+
10
+ Rules:
11
+ - `plyer` is installed; do not need to check for import errors before normal use.
12
+ - Prefer the `plyer` package for the supported device facades listed here.
13
+ - Plyer does not have a separate `toast` facade. Do not write
14
+ `from plyer import toast`.
15
+ - For Android toast-style messages, use
16
+ `from plyer import notification` and call
17
+ `notification.notify(..., toast=True)`.
18
+ - Do not request permissions here unless the user explicitly asks; use the separate Android permissions prompt.
19
+ - Do not access camera, microphone, GPS/location, sensors, contacts, SMS, call logs, or private files unless the user requested that specific capability.
20
+ - Do not delete, overwrite, upload, or make network requests with selected files unless explicitly requested.
21
+ - For asynchronous Plyer callbacks, store results in globals. If a Kivy UI is
22
+ involved, update visible UI through the Kivy runtime pattern.
23
+ - For microphone recording, Android normally needs
24
+ `android.permission.RECORD_AUDIO` declared in the app manifest and granted at
25
+ runtime. Use the Android permissions prompt when the user asks to request or
26
+ check microphone permission.
27
+
28
+ Text-to-speech rules:
29
+ - For simple text-to-speech, use exactly this API shape:
30
+
31
+ from plyer import tts
32
+ tts.speak(message=text_to_read)
33
+
34
+ - Do not use low-level Android framework speech APIs through Pyjnius for
35
+ ordinary read-aloud, speech, voice output, poem reading, or narration
36
+ requests. Use Pyjnius speech only when the user explicitly asks for lower-level
37
+ Android speech controls that Plyer does not expose.
38
+ - Do not use legacy SL4A-style Android helper speech APIs.
39
+ - Do not use desktop speech packages or platform shell commands for
40
+ Android/PythonHere TTS snippets.
41
+ - Do not generate `TTS_AVAILABLE` fallback scaffolding or probe multiple TTS
42
+ backends unless the user explicitly asks for cross-platform desktop code.
43
+ - Do not run `tts.speak(...)` inside a background Python thread. Use the Plyer
44
+ call directly from the generated cell or from a short Kivy callback.
45
+ - For a Kivy UI button or delayed speech start, the callback should call
46
+ `tts.speak(message=text)` directly and update UI state through the Kivy
47
+ runtime pattern.
48
+
49
+ Plyer audio recording rules:
50
+ - Use `plyer.audio` for audio recording workflows.
51
+ - Do not use `plyer.audio` as a general local-file playback API.
52
+ - Never call `audio.play(path)` or `audio.play("file.wav")`.
53
+ - For Android Plyer recording, prefer `.3gp` output paths unless this runtime has
54
+ verified another format.
55
+ - Do not name Plyer Android recordings `.wav` unless the backend is known to
56
+ write real WAV PCM data.
57
+ - Replay audio recorded through Plyer with `audio.play()` and no arguments after
58
+ `audio.stop()`.
59
+ - Stop recording or Plyer-managed playback with `audio.stop()`.
60
+ - Do not use Kivy SoundLoader to replay audio just recorded through Plyer on
61
+ Android. Kivy SoundLoader is for normal existing local audio files and is
62
+ covered by the Kivy Runtime prompt.
63
+
64
+ Toast example:
65
+ from plyer import notification
66
+
67
+ notification.notify(
68
+ title="",
69
+ message="Hello",
70
+ app_name="PythonHere",
71
+ toast=True,
72
+ )
73
+
74
+ Notification example:
75
+ from plyer import notification
76
+
77
+ notification.notify(
78
+ title="PythonHere",
79
+ message="Done",
80
+ app_name="PythonHere",
81
+ timeout=5,
82
+ )
83
+
84
+ Toast plus notification example:
85
+ from plyer import notification
86
+
87
+ notification.notify(
88
+ title="",
89
+ message="Done",
90
+ app_name="PythonHere",
91
+ toast=True,
92
+ )
93
+ notification.notify(
94
+ title="PythonHere",
95
+ message="Done",
96
+ app_name="PythonHere",
97
+ timeout=5,
98
+ )
99
+
100
+ Vibration example:
101
+ from plyer import vibrator
102
+
103
+ vibrator.vibrate(0.2)
104
+
105
+ Text-to-speech example:
106
+ from plyer import tts
107
+
108
+ tts.speak(message="Hello from PythonHere.")
109
+
110
+ File chooser example:
111
+ from plyer import filechooser
112
+
113
+ def on_selection(paths):
114
+ plyer_filechooser_result = {
115
+ "paths": list(paths or []),
116
+ "cancelled_or_empty": not bool(paths),
117
+ }
118
+ globals()["plyer_filechooser_result"] = plyer_filechooser_result
119
+
120
+ filechooser.open_file(on_selection=on_selection)
121
+
122
+ Audio recording start example:
123
+ from pathlib import Path
124
+ from datetime import datetime
125
+
126
+ from plyer import audio
127
+
128
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
129
+ plyer_audio_recording_path = str(Path.cwd() / f"pythonhere-recording-{timestamp}.3gp")
130
+ audio.file_path = plyer_audio_recording_path
131
+ audio.start()
132
+ plyer_audio_recording_status = {
133
+ "recording": True,
134
+ "path": plyer_audio_recording_path,
135
+ }
136
+
137
+ Audio recording stop example:
138
+ from plyer import audio
139
+
140
+ audio.stop()
141
+ plyer_audio_recording_status = {
142
+ "recording": False,
143
+ "path": plyer_audio_recording_path,
144
+ }
145
+
146
+ Audio recording replay example:
147
+ from plyer import audio
148
+
149
+ audio.play()
150
+
151
+ Camera example:
152
+ from plyer import camera
153
+
154
+ camera.take_picture(
155
+ filename="photo.jpg",
156
+ on_complete=lambda path: globals().__setitem__(
157
+ "plyer_camera_result",
158
+ {"path": path, "cancelled_or_empty": not bool(path)},
159
+ ),
160
+ )
161
+
162
+ GPS example:
163
+ from plyer import gps
164
+
165
+ def on_location(**kwargs):
166
+ globals()["plyer_gps_last_location"] = dict(kwargs)
167
+
168
+ gps.configure(on_location=on_location)
169
+ gps.start()
170
+
171
+ GPS stop example:
172
+ from plyer import gps
173
+
174
+ gps.stop()
175
+
176
+ Battery example:
177
+ from plyer import battery
178
+
179
+ status = battery.status
180
+
181
+ Accelerometer example:
182
+ from plyer import accelerometer
183
+
184
+ accelerometer.enable()
185
+ acceleration = accelerometer.acceleration
186
+
187
+ Compass example:
188
+ from plyer import compass
189
+
190
+ compass.enable()
191
+ heading = compass.orientation
192
+
193
+ Plyer callback state pattern:
194
+ - For every asynchronous Plyer facade, store callback results in a named global
195
+ such as `plyer_filechooser_result`, `plyer_camera_result`, or
196
+ `plyer_gps_last_location`.
197
+ - If a Kivy UI is involved, update a visible status widget from the callback
198
+ using `Clock.schedule_once(...)` when needed.
199
+ - Do not treat a callback returning an empty selection or `None` as an
200
+ exception; report it as a cancelled/empty result.
201
+ - Keep file chooser behavior read-only unless the user explicitly asks to open,
202
+ process, copy, upload, delete, or overwrite selected files.
@@ -0,0 +1,37 @@
1
+ """PythonHere prompt sections for ``%%there ai``."""
2
+
3
+ from importlib.resources import files
4
+
5
+ from herethere.there.ai import register_ai_prompt, set_ai_prompts
6
+
7
+ PYTHONHERE_AI_ACTIVE_PROMPTS = (
8
+ "kivy-runtime",
9
+ "kivy-kv",
10
+ "android-runtime",
11
+ "jnius",
12
+ "android-permissions",
13
+ "android-packages",
14
+ "android-media",
15
+ "plyer",
16
+ )
17
+
18
+ PYTHONHERE_AI_PROMPTS = (
19
+ *PYTHONHERE_AI_ACTIVE_PROMPTS,
20
+ "able",
21
+ "midi",
22
+ )
23
+
24
+
25
+ def _read_prompt(name: str) -> str:
26
+ return (
27
+ files("pythonhere.magic_here")
28
+ .joinpath("prompts", f"{name}.md")
29
+ .read_text(encoding="utf-8")
30
+ )
31
+
32
+
33
+ def register_pythonhere_ai_prompts() -> None:
34
+ """Register PythonHere Android/Kivy prompt sections as the active AI stack."""
35
+ for name in PYTHONHERE_AI_PROMPTS:
36
+ register_ai_prompt(name, _read_prompt(name))
37
+ set_ai_prompts("default", *PYTHONHERE_AI_ACTIVE_PROMPTS)
pythonhere/main.py CHANGED
@@ -12,12 +12,14 @@ else:
12
12
  startup_script_exception = None # pylint: disable=invalid-name
13
13
 
14
14
  import asyncio
15
+ import logging
15
16
  import os
16
17
  import sys
17
18
  import threading
18
19
  from pathlib import Path
19
20
  from typing import Any
20
21
 
22
+ import asyncssh
21
23
  from enum_here import ScreenName, ServerState
22
24
  from exception_manager_here import install_exception_handler, show_exception_popup
23
25
  from kivy.app import App
@@ -31,6 +33,12 @@ from window_here import reset_window_environment
31
33
  monkeypatch_kivy()
32
34
 
33
35
 
36
+ def configure_logging():
37
+ """Configure logging for the app runtime."""
38
+ logging.getLogger().setLevel(logging.INFO)
39
+ asyncssh.set_log_level("WARNING")
40
+
41
+
34
42
  class PythonHereApp(App):
35
43
  """PythonHere main app."""
36
44
 
@@ -217,6 +225,7 @@ class PythonHereApp(App):
217
225
 
218
226
  async def main():
219
227
  """Run PythonHere."""
228
+ configure_logging()
220
229
  app = PythonHereApp()
221
230
  await app.run_app()
222
231
 
pythonhere/server_here.py CHANGED
@@ -30,7 +30,7 @@ async def run_ssh_server(app):
30
30
  try:
31
31
  config = ServerConfig(
32
32
  host="",
33
- chroot=app.upload_dir,
33
+ sftp_root=app.upload_dir,
34
34
  key_path=Path("./key.rsa").resolve(),
35
35
  **app.get_pythonhere_config(),
36
36
  )
@@ -1 +1 @@
1
- __version__ = "0.2.0"
1
+ __version__ = "0.2.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonhere
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Here is the Kivy based app to run code from the Jupyter magic %there
5
5
  Author-email: b3b <ash.b3b@gmail.com>
6
6
  License-Expression: MIT
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/x-rst
18
18
  License-File: LICENSE
19
- Requires-Dist: herethere[magic]>=0.1.0
19
+ Requires-Dist: herethere[magic]>=0.2.3
20
20
  Requires-Dist: ipython
21
21
  Requires-Dist: ipywidgets
22
22
  Requires-Dist: Pillow
@@ -40,7 +40,7 @@ PythonHere
40
40
  :alt: Supported Python versions
41
41
  .. image:: https://github.com/b3b/pythonhere/actions/workflows/tests.yml/badge.svg?branch=master
42
42
  :target: https://github.com/b3b/pythonhere/actions/workflows/tests.yml?query=branch%3Amaster
43
- :alt: CI Status
43
+ :alt: CI Status
44
44
  .. image:: https://codecov.io/github/b3b/pythonhere/coverage.svg?branch=master
45
45
  :target: https://codecov.io/github/b3b/pythonhere?branch=master
46
46
  :alt: Code coverage Status
@@ -68,6 +68,9 @@ Project documentation: https://herethere.me/pythonhere
68
68
  Install the Android app
69
69
  -----------------------
70
70
 
71
+ Install *PythonHere* with `Obtainium <https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://github.com/b3b/pythonhere>`_
72
+ to receive updates from GitHub Releases.
73
+
71
74
  Ready-to-use *PythonHere* APKs are available from the `GitHub Releases <https://github.com/b3b/pythonhere/releases>`_ page.
72
75
 
73
76
  For APK provenance and signing checks, see `Android APK verification <https://github.com/b3b/pythonhere/blob/master/docs/android-apk-verification.rst>`_.
@@ -120,6 +123,7 @@ To build with `Buildozer <https://github.com/kivy/buildozer>`_, run in the sourc
120
123
  Related resources
121
124
  -----------------
122
125
 
123
- * `Kivy Remote Shell <https://github.com/kivy/kivy-remote-shell>`_ : Remote SSH+Python interactive shell application
124
126
  * `herethere <https://github.com/b3b/herethere>`_ : Library for interactive code execution, based on AsyncSSH
125
127
  * `AsyncSSH <https://github.com/ronf/asyncssh>`_ : Asynchronous SSH for Python
128
+ * `Buildozer <https://github.com/kivy/buildozer>`_ : Tool used to build the Android APK.
129
+ * `python-for-android <https://github.com/kivy/python-for-android>`_ : Build toolchain used by Buildozer to package Python applications for Android.
@@ -1,21 +1,33 @@
1
- pythonhere/__init__.py,sha256=gkVrYFkF3klE1fripHpmTuZx8_BaVHWRRvvr6PK6XWE,212
2
- pythonhere/android_here.py,sha256=imQCNkhAJYgT3gMeX25kd9cQzH5ZbeF5PlgP8e9sbl0,3316
1
+ pythonhere/__init__.py,sha256=1GEGd8Y4FOPQOH1Mh6JC0GQDwV-0w83qV1KYCvGOxH0,466
2
+ pythonhere/android_here.py,sha256=wBZNDgglichTFDpGuKFwdNbPXS2kVw8XvssnGd_4Y9Q,3546
3
3
  pythonhere/enum_here.py,sha256=FPj3pBeXfpPwl4-LDdC-wmbaoiv4QdbKHkGoBfTXnTE,449
4
4
  pythonhere/exception_manager_here.kv,sha256=FZNhQgVojSLcVxZ0vjAxAF915xCBP-3nldPXVczRzCE,656
5
5
  pythonhere/exception_manager_here.py,sha256=-UYqGRbzkoyfF0-pLWbxaFGEAvAdaw6b8hkm_bMIKx8,1943
6
6
  pythonhere/launcher_here.py,sha256=I5zBpN0P3hmhsZFOJemxQ9Q2k2am5zkfS6Vg0Svnl98,1274
7
- pythonhere/main.py,sha256=7vAF6mznCr2JFxeXjm3-a6PN1abPt-loI8ZHncAAPp0,7237
7
+ pythonhere/main.py,sha256=lV4Wz_54khwQ9FE1u9YXkNay0x-3yc_-WmP3Loh7Z2o,7453
8
8
  pythonhere/network_here.py,sha256=e3FDlBcXPaTFlWaqW2_bcWctht-ifpFIvtOj0lO4S9w,1558
9
9
  pythonhere/patches_here.py,sha256=zB7Ohbk5PIXt0imnnrV5Mtl-Y7jpD_p5IBPlijQcCCQ,1727
10
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
11
+ pythonhere/server_here.py,sha256=Y3tOIQo0JBp50AUXOKQhxKMokvedV7QV_JSYPh4t99U,1857
12
+ pythonhere/version_here.py,sha256=m6kyaNpwBcP1XYcqrelX2oS3PJuOnElOcRdBa9pEb8c,22
13
13
  pythonhere/window_here.py,sha256=na47v7bgomAjb6VC_g35RwhzEtm5RCInQIuQK27zY8U,2023
14
14
  pythonhere/data/logo/logo-128.png,sha256=4sitDJcVBHOU5UPxicmrzc6YqFY7_xtYrc_ULyrw9ks,4161
15
15
  pythonhere/data/logo/logo-32.png,sha256=qanrIAt6vK_2BsCjCxth2B4vxBa0ONnpDlils7syUWE,1247
16
16
  pythonhere/data/logo/logo-splash.png,sha256=-edmwKnkkpGP4RZ0AJYto5_BOy8NoISt5ftOEI8BQOY,4663
17
17
  pythonhere/magic_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ pythonhere/magic_here/prompts.py,sha256=kiNSuHPEIgJdDYl84pLt_kBHtjdzWF_7sA9WS5wZCPU,899
18
19
  pythonhere/magic_here/shortcuts.py,sha256=pdB_ET6Snl4DxusbyZPI1_G8jGzA8d35er60qQb_2cY,2499
20
+ pythonhere/magic_here/prompts/README.md,sha256=5r56B8NOieyYPu9xzHarpRR9oU9KqYWM2Ds0wfjuWYQ,1604
21
+ pythonhere/magic_here/prompts/able.md,sha256=rh8f3muFdl2d50oOloWTc-mv9U7nuGBZtdsFXuiwzxQ,22836
22
+ pythonhere/magic_here/prompts/android-media.md,sha256=vM55sxJmvkLQmKAjHZYL_z0F0SR__PjOHSXtGIgP1BI,6631
23
+ pythonhere/magic_here/prompts/android-packages.md,sha256=naBfg68N7lXCd_d2fILgqTrcwLWK5NsGD_KDqlLs3S0,3834
24
+ pythonhere/magic_here/prompts/android-permissions.md,sha256=g85CsmQvMU6CTbpD82BH7NEByFhqpHs6OW613hxZtdQ,10903
25
+ pythonhere/magic_here/prompts/android-runtime.md,sha256=VCZkyw7nW5T-GtlF6R8vzYaQZ9BlMHWiTY4_X4DFwng,1674
26
+ pythonhere/magic_here/prompts/jnius.md,sha256=lyjdGUsZXw2lS-0eppS2-JNTVgEED30z1DglzT6H0D8,14999
27
+ pythonhere/magic_here/prompts/kivy-kv.md,sha256=ajIDSo6PG1_7gHcgfNrOARQOUlUEPUaeySQIxb6XjnM,8509
28
+ pythonhere/magic_here/prompts/kivy-runtime.md,sha256=cAgOF0uE3RKYdKQus7h4lklKtANaLPcOYilGShjNtgM,14621
29
+ pythonhere/magic_here/prompts/midi.md,sha256=2rKvgrwlmeA4MfdnRH9xnTSN0OFOm4mTxtgdYdMAd3Q,10611
30
+ pythonhere/magic_here/prompts/plyer.md,sha256=wplCHPXkN1vU_thGIxvALuYPB6QDdjCHFhoc93ppvAE,6708
19
31
  pythonhere/ui_here/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
32
  pythonhere/ui_here/actionbar_here.kv,sha256=cOjY4ByOyBDUbeS9kbI3JAhi_X3kJ3Pb5h4RL58urwU,214
21
33
  pythonhere/ui_here/common_here.kv,sha256=vnNyDwAKKNbRKvh0QKMbTvR0jNDKiY8DdGRnoEEexSc,324
@@ -26,8 +38,8 @@ pythonhere/ui_here/server_screen_here.kv,sha256=x0Se8hmgjS57gFIPa1r2lf6yJchs3lXO
26
38
  pythonhere/ui_here/server_screen_here.py,sha256=2zQv1YFmiKNxa__7UZDYbAAAwYFLoZPSZPhr7vjCAkU,925
27
39
  pythonhere/ui_here/settings_here.kv,sha256=stMcDq8dCYjay4idK30_7EUu5edkMDAQVPv0H0GuAcM,710
28
40
  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,,
41
+ pythonhere-0.2.2.dist-info/licenses/LICENSE,sha256=nW9_eVi3dSMOkr6xghsobAovBEJiffjl_5WjujJTa74,1071
42
+ pythonhere-0.2.2.dist-info/METADATA,sha256=tSi1b9FPaAZhPx6wdMJA7TTjhoxFqsUynO-UgCTFtS4,5079
43
+ pythonhere-0.2.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
44
+ pythonhere-0.2.2.dist-info/top_level.txt,sha256=dvkfRGF1tFbkjXzD9vwqXTge1Znkv7ga5fRJy5yhJsE,11
45
+ pythonhere-0.2.2.dist-info/RECORD,,