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,432 @@
1
+ ## Pyjnius
2
+
3
+ `jnius` is installed.
4
+
5
+ Rules:
6
+
7
+ - Import Java classes with `from jnius import autoclass`.
8
+ - For Java callbacks/listeners implemented in Python, import and use
9
+ `PythonJavaClass` and `java_method`. A plain Python class with a matching
10
+ method name is not a Java interface implementation.
11
+ - Import `cast` only when the generated code actually uses it.
12
+ - Do not import Android app classes directly from `jnius`. For example, do not
13
+ write `from jnius import PythonService`; use
14
+ `autoclass("org.kivy.android.PythonService")` inside the fallback path.
15
+ - Import optional Android classes, such as `org.kivy.android.PythonService`,
16
+ only inside the fallback path where they are needed.
17
+ - Use `cast` only when a Java API requires a specific declared type.
18
+ - Keep Java object references local unless the user needs to inspect them later.
19
+ - Convert Java strings to Python strings with `str(...)` before storing results.
20
+ - Treat Java arrays and lists defensively: they may be `None`; iterate only after
21
+ checking.
22
+ - Java arrays are Python-indexable in Pyjnius. Use `len(java_array)` and
23
+ `java_array[index]`. Do not call `.size()` or `.get()` unless the object is a
24
+ Java `List`.
25
+ - For Java long bitmasks, Python `int` values are acceptable.
26
+ - Catch narrow exceptions only around optional Android fields or deprecated APIs.
27
+ Do not hide collection-wide failures.
28
+ - If catching an exception for one package/item, store a readable error string in
29
+ that item and continue.
30
+ - Do not use Pyjnius to launch external processes.
31
+ - Do not reference Android class constants from a variable that has not been
32
+ assigned with `autoclass`.
33
+
34
+ Pyjnius arrays:
35
+ - Use Python lists, bytes, or bytearray for Java array arguments.
36
+ - Do not invent imports like `jarray`.
37
+ - If a Java API needs a writable output buffer, use a mutable Python list or bytearray and check the method’s return value.
38
+
39
+ Pyjnius object conversion rules:
40
+
41
+ - Do not assume Python string conversion of Java objects produces valid Android
42
+ values.
43
+ - Convert Java `String` objects to Python strings with `str(...)` before storing
44
+ ordinary text results, but do not use Python `str(...)` for Android object
45
+ identifiers that have their own Java string form, such as `Uri`.
46
+ - For Java objects with Android-specific string forms, call the Java method
47
+ `toString()`.
48
+ - For `android.net.Uri`, prefer keeping the Java `Uri` object and passing it
49
+ directly to Android APIs such as `ContentResolver.openInputStream(uri)`.
50
+ - Do not convert Java `android.net.Uri` objects with Python `str(uri)`.
51
+ In Pyjnius, `str(uri)` may produce a Python object representation like
52
+ `<android.net.Uri at 0x... jclass=android/net/Uri ...>` instead of a valid
53
+ `content://...` URI string.
54
+ - If a Java `Uri` must be stored as text, use `uri.toString()`, not `str(uri)`.
55
+ - Never parse a Pyjnius object representation as a URI.
56
+ - Any generated code that logs or displays a URI should log both the media ID
57
+ and `uri.toString()`, not the Python object representation.
58
+
59
+ Wrong:
60
+
61
+ ```python
62
+ content_uri = ContentUris.withAppendedId(ImagesMedia.EXTERNAL_CONTENT_URI, image_id)
63
+ photos.append({"id": image_id, "uri": str(content_uri)})
64
+
65
+ uri = Uri.parse(photo["uri"])
66
+ stream = resolver.openInputStream(uri)
67
+ ```
68
+
69
+ Correct, preferred:
70
+
71
+ ```python
72
+ content_uri = ContentUris.withAppendedId(ImagesMedia.EXTERNAL_CONTENT_URI, image_id)
73
+ photos.append({"id": image_id, "uri": content_uri})
74
+
75
+ stream = resolver.openInputStream(photo["uri"])
76
+ ```
77
+
78
+ Correct, if serialization is needed:
79
+
80
+ ```python
81
+ content_uri = ContentUris.withAppendedId(ImagesMedia.EXTERNAL_CONTENT_URI, image_id)
82
+ photos.append({"id": image_id, "uri": content_uri.toString()})
83
+
84
+ Uri = autoclass("android.net.Uri")
85
+ uri = Uri.parse(photo["uri"])
86
+ stream = resolver.openInputStream(uri)
87
+ ```
88
+
89
+ Pyjnius Java class access rules:
90
+
91
+ - Do not call Java classes through undefined Python package names.
92
+ - Every Java class used must be bound with `autoclass(...)`.
93
+ - Do not assume Java package names are available as Python modules.
94
+
95
+ Wrong:
96
+
97
+ ```python
98
+ android.graphics.Bitmap.createScaledBitmap(bitmap, new_w, new_h, True)
99
+ ```
100
+
101
+ Correct:
102
+
103
+ ```python
104
+ Bitmap = autoclass("android.graphics.Bitmap")
105
+ scaled_bitmap = Bitmap.createScaledBitmap(bitmap, new_w, new_h, True)
106
+ ```
107
+
108
+ Nested Java classes:
109
+
110
+ - In Pyjnius, nested Java classes must be imported with `$` using `autoclass`.
111
+ Do not access nested Java classes as Python attributes of the parent class.
112
+
113
+ Correct:
114
+
115
+ ```python
116
+ VERSION = autoclass("android.os.Build$VERSION")
117
+ BitmapFactoryOptions = autoclass("android.graphics.BitmapFactory$Options")
118
+ CompressFormat = autoclass("android.graphics.Bitmap$CompressFormat")
119
+ PackageInfoFlags = autoclass("android.content.pm.PackageManager$PackageInfoFlags")
120
+ ImagesMedia = autoclass("android.provider.MediaStore$Images$Media")
121
+ ```
122
+
123
+ Incorrect:
124
+
125
+ ```python
126
+ Build.VERSION.SDK_INT
127
+ BitmapFactory.Options()
128
+ Bitmap.CompressFormat
129
+ PackageManager.PackageInfoFlags
130
+ MediaStore.Images.Media
131
+ ```
132
+
133
+ Android version checks:
134
+
135
+ - Use:
136
+
137
+ ```python
138
+ VERSION = autoclass("android.os.Build$VERSION")
139
+ SDK_INT = VERSION.SDK_INT
140
+ ```
141
+
142
+ - Do not use:
143
+
144
+ ```python
145
+ Build.VERSION.SDK_INT
146
+ ```
147
+
148
+ because Pyjnius does not reliably expose nested classes as Python attributes.
149
+
150
+ Nullable Java constants:
151
+
152
+ - Treat Android Java class constants returned through Pyjnius as nullable.
153
+ Some constants may be `None` even when the Android API normally defines them.
154
+ - Never pass unchecked Pyjnius constants into Android APIs that expect strings,
155
+ arrays of strings, column names, permissions, selection clauses, sort orders,
156
+ file modes, or intent actions.
157
+ - Before using a Java string constant in `projection`, `selection`, `sortOrder`,
158
+ `getColumnIndex(...)`, `Intent(...)`, or permission checks, resolve it to a
159
+ non-null Python string.
160
+
161
+ Correct:
162
+
163
+ ```python
164
+ ImagesMedia = autoclass("android.provider.MediaStore$Images$Media")
165
+
166
+ COL_ID = ImagesMedia._ID or "_id"
167
+ COL_DATE_ADDED = ImagesMedia.DATE_ADDED or "date_added"
168
+ COL_DATE_TAKEN = ImagesMedia.DATE_TAKEN or "datetaken"
169
+ COL_DISPLAY_NAME = ImagesMedia.DISPLAY_NAME or "_display_name"
170
+ COL_BUCKET = ImagesMedia.BUCKET_DISPLAY_NAME or "bucket_display_name"
171
+
172
+ projection = [COL_ID, COL_DATE_ADDED]
173
+ sort_order = COL_DATE_ADDED + " DESC"
174
+ ```
175
+
176
+ Incorrect:
177
+
178
+ ```python
179
+ projection = [ImagesMedia._ID, ImagesMedia.DATE_TAKEN]
180
+ sort_order = f"{ImagesMedia.DATE_TAKEN} DESC"
181
+ selection = ImagesMedia.BUCKET_DISPLAY_NAME + " = ?"
182
+ ```
183
+
184
+ - Never allow `None` inside Java `String[]` arguments or string parameters.
185
+ This includes MediaStore projections, selection args, sort order strings, and
186
+ cursor column names.
187
+ - If an Android API throws a Java `NullPointerException` mentioning
188
+ `String.toLowerCase()` during a query, suspect a `None` column name or string
189
+ argument passed from Pyjnius.
190
+ - Prefer explicit fallback strings for well-known Android column names rather
191
+ than raw constants when generating resilient code.
192
+
193
+ Bitmap-related Pyjnius rules:
194
+
195
+ - For bitmap decode options, use:
196
+
197
+ ```python
198
+ BitmapFactoryOptions = autoclass("android.graphics.BitmapFactory$Options")
199
+ opts = BitmapFactoryOptions()
200
+ ```
201
+
202
+ - Never use:
203
+
204
+ ```python
205
+ opts = BitmapFactory.Options()
206
+ ```
207
+
208
+ - For bitmap compression format, use:
209
+
210
+ ```python
211
+ CompressFormat = autoclass("android.graphics.Bitmap$CompressFormat")
212
+ bitmap.compress(CompressFormat.JPEG, 85, output_stream)
213
+ ```
214
+
215
+ - Never use:
216
+
217
+ ```python
218
+ Bitmap.CompressFormat.JPEG
219
+ ```
220
+
221
+
222
+ Android MediaStore and shared-media Pyjnius rules:
223
+
224
+ - Prefer MediaStore content URIs over raw filesystem paths.
225
+ - Do not depend on the `_data` column for gallery/media access.
226
+ - Query `_id`, build a content URI, then decode through `ContentResolver`.
227
+ - Keep the Java `Uri` object directly or store URI text with `uri.toString()`.
228
+ Do not store `str(uri)`.
229
+ - Do not filter only by `bucket_display_name = "Camera"` unless the user
230
+ specifically asked for the Camera folder only. For a general gallery, query
231
+ all images first, then add filters only after the broad query is confirmed
232
+ working.
233
+ - Always handle `cursor is None` and `cursor.getCount() == 0`.
234
+ - Always close the cursor in `finally`.
235
+ - If a thumbnail decode fails for one item, store/log a readable item-level
236
+ error and continue with other items.
237
+
238
+ Preferred MediaStore URI pattern:
239
+
240
+ ```python
241
+ ContentUris = autoclass("android.content.ContentUris")
242
+ ImagesMedia = autoclass("android.provider.MediaStore$Images$Media")
243
+ BitmapFactory = autoclass("android.graphics.BitmapFactory")
244
+ BitmapFactoryOptions = autoclass("android.graphics.BitmapFactory$Options")
245
+
246
+ content_uri = ContentUris.withAppendedId(
247
+ ImagesMedia.EXTERNAL_CONTENT_URI,
248
+ image_id,
249
+ )
250
+
251
+ opts = BitmapFactoryOptions()
252
+ stream = resolver.openInputStream(content_uri)
253
+ try:
254
+ bitmap = BitmapFactory.decodeStream(stream, None, opts)
255
+ finally:
256
+ if stream is not None:
257
+ stream.close()
258
+ ```
259
+
260
+ Avoid as the default:
261
+
262
+ ```python
263
+ path = cursor.getString(cursor.getColumnIndex("_data"))
264
+ bitmap = BitmapFactory.decodeFile(path, opts)
265
+ ```
266
+
267
+ Android media permission and special-access rules:
268
+
269
+ - Declaring a permission in `buildozer.spec` or `AndroidManifest.xml` does not
270
+ prove it is granted at runtime.
271
+ - For Android 13+ image access, check/request
272
+ `android.permission.READ_MEDIA_IMAGES`.
273
+ - For Android 13+ video access, check/request
274
+ `android.permission.READ_MEDIA_VIDEO`.
275
+ - For Android 13+ audio access, check/request
276
+ `android.permission.READ_MEDIA_AUDIO`.
277
+ - For Android 12 and below, check/request
278
+ `android.permission.READ_EXTERNAL_STORAGE`.
279
+ - Do not rely on `WRITE_EXTERNAL_STORAGE` for reading shared photos on modern
280
+ Android.
281
+ - Do not check `MANAGE_EXTERNAL_STORAGE` with `check_permission(...)`.
282
+ It is special Android settings access, not a normal runtime permission.
283
+ - For arbitrary `/sdcard`, `/sdcard/DCIM`, `/sdcard/Download`, or full
284
+ shared-storage browsing on Android 11+, check
285
+ `Environment.isExternalStorageManager()`.
286
+ - If all-files access is needed and not enabled, open
287
+ `Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION` with
288
+ `Uri.parse("package:" + activity.getPackageName())`. If that fails, fall back
289
+ to `Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION`.
290
+
291
+ Thumbnail and cache rules:
292
+
293
+ - Write generated thumbnails into the app cache directory from
294
+ `context.getCacheDir().getAbsolutePath()`.
295
+ - Do not use `Path.cwd() / "gallery_cache"` for Android cache files.
296
+ - Kivy `Image.source` should point to a real cache file path.
297
+ - Do not assume Kivy supports `data:image/...;base64,...` sources.
298
+ - Use separate constants for UI size and bitmap decode size, for example
299
+ `THUMB_UI_DP = dp(120)` and `THUMB_PX = 240`.
300
+ - Do not pass `dp(...)` floats into Android bitmap APIs.
301
+ - Android bitmap dimensions and sample sizes must be plain Python `int` values.
302
+ - Recycle Android `Bitmap` objects after thumbnail compression when possible.
303
+
304
+ Media/gallery error-reporting rules:
305
+
306
+ - Do not hide media/gallery failures behind generic messages like
307
+ `Error loading photos. Check logs.`
308
+ - Generated code should show the real failure stage and exception message in the
309
+ UI during development.
310
+ - Background workers should return structured results such as
311
+ `{ "ok": False, "stage": "decode_thumbnail", "error": "AttributeError: ..." }`.
312
+ - Logs may include full stack traces, but logs must not be the only place where
313
+ the real problem appears.
314
+
315
+ General forbidden Pyjnius patterns:
316
+
317
+ Never generate:
318
+
319
+ ```python
320
+ BitmapFactory.Options()
321
+ Bitmap.CompressFormat
322
+ Build.VERSION
323
+ MediaStore.Images.Media
324
+ PackageManager.PackageInfoFlags
325
+ projection = [SomeJavaClass.SOME_COLUMN]
326
+ sort_order = f"{SomeJavaClass.SOME_COLUMN} DESC"
327
+ ```
328
+
329
+ Generate:
330
+
331
+ ```python
332
+ BitmapFactoryOptions = autoclass("android.graphics.BitmapFactory$Options")
333
+ CompressFormat = autoclass("android.graphics.Bitmap$CompressFormat")
334
+ VERSION = autoclass("android.os.Build$VERSION")
335
+ ImagesMedia = autoclass("android.provider.MediaStore$Images$Media")
336
+ PackageInfoFlags = autoclass("android.content.pm.PackageManager$PackageInfoFlags")
337
+
338
+ COL = SomeJavaClass.SOME_COLUMN or "known_fallback_name"
339
+ projection = [COL]
340
+ sort_order = COL + " DESC"
341
+ ```
342
+
343
+ For Android package APIs:
344
+
345
+ - `PackageManager.GET_PERMISSIONS` requests permission metadata.
346
+ - Android API 33 and newer require
347
+ `android.content.pm.PackageManager$PackageInfoFlags.of(flags)`.
348
+ - In Pyjnius, define:
349
+
350
+ ```python
351
+ PackageInfoFlags = autoclass("android.content.pm.PackageManager$PackageInfoFlags")
352
+ ```
353
+
354
+ and call:
355
+
356
+ ```python
357
+ PackageInfoFlags.of(flags)
358
+ ```
359
+
360
+ - Do not access it as:
361
+
362
+ ```python
363
+ PackageManager.PackageInfoFlags
364
+ ```
365
+
366
+ - Older APIs accept integer flags.
367
+ - System-app flags come from `android.content.pm.ApplicationInfo`.
368
+ Use `ApplicationInfo.FLAG_SYSTEM` and `ApplicationInfo.FLAG_UPDATED_SYSTEM_APP`.
369
+ Do not use `android.content.pm.ActivityInfo` for this.
370
+ - Permission grant status comes from
371
+ `android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED`, not from
372
+ `PackageManager.PERMISSION_GRANTED` and not from `ActivityInfo`.
373
+ - If checking requested permission grant status, define:
374
+
375
+ ```python
376
+ PackageInfo = autoclass("android.content.pm.PackageInfo")
377
+ ```
378
+
379
+ before using that constant.
380
+ - Version code should use `longVersionCode` when present and fall back to
381
+ `versionCode`.
382
+
383
+ Additional forbidden Pyjnius media patterns:
384
+
385
+ Never generate:
386
+
387
+ ```python
388
+ str(content_uri)
389
+ Uri.parse(str(content_uri))
390
+ photos.append({"uri": str(content_uri)})
391
+ android.graphics.Bitmap.createScaledBitmap(bitmap, new_w, new_h, True)
392
+ Path.cwd() / "gallery_cache"
393
+ show_error("Error loading photos. Check logs.")
394
+ return None # after catching a media/gallery exception
395
+ cursor.getString(cursor.getColumnIndex("_data")) # as the primary media access path
396
+ ```
397
+
398
+ Generate:
399
+
400
+ ```python
401
+ photos.append({"uri": content_uri})
402
+ # or, only if text serialization is required:
403
+ photos.append({"uri": content_uri.toString()})
404
+
405
+ Bitmap = autoclass("android.graphics.Bitmap")
406
+ scaled_bitmap = Bitmap.createScaledBitmap(bitmap, new_w, new_h, True)
407
+
408
+ cache_dir = context.getCacheDir().getAbsolutePath()
409
+
410
+ return {
411
+ "ok": False,
412
+ "stage": stage,
413
+ "error": f"{type(e).__name__}: {e}",
414
+ }
415
+ ```
416
+
417
+ Pyjnius overload and Android intent safety:
418
+ - When Java APIs have overloaded constructors or methods, prefer the least
419
+ ambiguous call pattern through Pyjnius. For Android `Intent`, create
420
+ `intent = Intent(action)` and then call setters such as `setData(...)`,
421
+ `setType(...)`, or `putExtra(...)` rather than relying on overloaded
422
+ constructors.
423
+ - Do not pass Python `None` where Android expects a Java `String`, `String[]`,
424
+ `Uri`, `Intent`, `Context`, or callback/listener.
425
+ - When an API expects a Java primitive array or Java collection, prefer normal
426
+ Python lists only when Pyjnius is known to convert them for that method.
427
+ Otherwise use the Android/Python-for-Android helper API if one exists.
428
+ - For nullable Android constants, resolve to non-null Python strings before
429
+ passing them into Android APIs.
430
+ - Keep Settings/Intent launch results in a named global and report whether
431
+ `startActivity(...)` was attempted; do not claim the requested setting changed
432
+ just because the Settings screen opened.
@@ -0,0 +1,239 @@
1
+ ## Kv design
2
+
3
+ Use KV for layout only:
4
+ - widget tree
5
+ - ids
6
+ - simple widget properties
7
+ - simple canvas instructions
8
+
9
+ Critical generated-KV constraints:
10
+ - If KV uses `dp(...)` or `sp(...)`, the KV string must include the matching
11
+ `#:import` line once at the top, before widget rules.
12
+ - If KV uses `sin(...)`, `cos(...)`, `abs(...)`, `min(...)`, or any other helper
13
+ function in an expression, the name must be defined in KV parser scope with
14
+ `#:import` or the calculation must be moved into Python.
15
+ - Do not use KV dynamic class/template syntax such as `<Name@BaseWidget>:` in
16
+ generated PythonHere snippets.
17
+ - Do not call `Builder.template(...)`; use `ui = Builder.load_string(KV)`.
18
+ - Do not put callbacks in KV. Bind callbacks in Python after
19
+ `Builder.load_string(KV)`.
20
+ - Do not use `app.some_method()` or `root.some_method()` in KV callbacks.
21
+ - Do not use `#:set` to inject Python globals, callback functions, generated
22
+ text, or state into KV. Set widget properties and bind callbacks from Python
23
+ after loading.
24
+
25
+ For generated PythonHere cells, do not put Python callbacks in KV.
26
+ Do not put generated dynamic Python logic in KV.
27
+
28
+ Avoid:
29
+ `on_release: something()`
30
+ `on_press: something()`
31
+ `on_text: something(self.text)`
32
+ `on_value: something(self.value)`
33
+ `values: [f"{num}: {name}" for num, name in sorted(items.items())]`
34
+ `text: some_python_variable`
35
+ `source: compute_path()`
36
+ `angle: app.feature_angle`
37
+
38
+ Also avoid generated proxy/global calls in KV:
39
+ `on_release: actions.handle(...)`
40
+ `on_release: app_actions["handle"]()`
41
+ `on_release: app.stop_everything()`
42
+ `on_release: root.stop_everything()`
43
+ `on_release: start_cb()`
44
+ `text: app_poem_text`
45
+ `#:set start_cb some_python_function`
46
+ `#:set app_poem_text some_python_text`
47
+
48
+ Avoid referencing notebook/global variables from KV. Kivy Builder may not have
49
+ the expected globals in parser scope, and KV parser errors are hard to recover
50
+ from in a live app. Put dynamic values into widgets from Python after the widget
51
+ tree exists.
52
+ Do not use `app.some_feature_state` in generated KV. In PythonHere, `app` is the
53
+ real PythonHere Kivy App instance, not a generated feature controller. Store
54
+ feature state on the generated root/widget class with Kivy properties, or update
55
+ widget/canvas instructions from Python.
56
+
57
+ Preferred pattern:
58
+ - Put ids on interactive widgets in KV.
59
+ - Load the UI.
60
+ - Set dynamic properties from Python after `Builder.load_string(KV)`.
61
+ - Bind callbacks in Python after `Builder.load_string(KV)`.
62
+
63
+ Example:
64
+
65
+ `ui = Builder.load_string(KV)`
66
+ `ui.ids.primary_button.bind(on_release=handle_primary_action)`
67
+ `ui.ids.value_slider.bind(value=handle_value_change)`
68
+
69
+ Dynamic values example:
70
+
71
+ ```
72
+ KV = """
73
+ BoxLayout:
74
+ Spinner:
75
+ id: instrument_spinner
76
+ text: "Choose instrument"
77
+ values: []
78
+ Slider:
79
+ id: volume_slider
80
+ """
81
+
82
+ ui = Builder.load_string(KV)
83
+ ui.ids.instrument_spinner.values = [
84
+ f"{num}: {name}" for num, name in sorted(midi_instruments.items())
85
+ ]
86
+ ui.ids.instrument_spinner.bind(text=handle_instrument)
87
+ ui.ids.volume_slider.bind(value=handle_volume)
88
+ ```
89
+
90
+ Widget-owned property example:
91
+
92
+ ```
93
+ from kivy.properties import NumericProperty
94
+ from kivy.uix.floatlayout import FloatLayout
95
+
96
+ class DynamicRoot(FloatLayout):
97
+ feature_angle = NumericProperty(0)
98
+
99
+ KV = """
100
+ #:import dp kivy.metrics.dp
101
+
102
+ <DynamicRoot>:
103
+ canvas.before:
104
+ PushMatrix:
105
+ Rotate:
106
+ angle: root.feature_angle
107
+ origin: self.center
108
+ Ellipse:
109
+ size: dp(120), dp(120)
110
+ pos: self.center_x - dp(60), self.center_y - dp(60)
111
+ PopMatrix:
112
+
113
+ DynamicRoot:
114
+ """
115
+
116
+ ui = Builder.load_string(KV)
117
+ Clock.schedule_interval(lambda dt: setattr(ui, "feature_angle", ui.feature_angle + 3), 1 / 30)
118
+ ```
119
+
120
+ KV root rule:
121
+ - In PythonHere generated cells, `KV` must end with a concrete root widget instance.
122
+ - Prefer a direct root widget such as `BoxLayout:`, `FloatLayout:`, `GridLayout:`, or a custom class instance such as `DesktopUI:`.
123
+ - Do not make `KV` contain only class/rule definitions.
124
+ - Do not use KV dynamic class/template syntax such as `<RootWidget@BoxLayout>:`
125
+ for generated PythonHere snippets.
126
+ - Do not call `Builder.template(...)`. Use `ui = Builder.load_string(KV)` with a
127
+ KV string that ends in a concrete root widget instance.
128
+ - Do not load rule-only KV and then instantiate a Python class manually, such as
129
+ `Builder.load_string(KV); ui = MyWidget()`. The generated KV should return the
130
+ actual root widget from `Builder.load_string(KV)`.
131
+ - Do not call `Builder.unload_file(...)` or pass a fake `filename=...` for
132
+ generated inline KV snippets.
133
+
134
+ Good:
135
+
136
+ ```
137
+ KV = """
138
+ BoxLayout:
139
+ Label:
140
+ text: "Hello"
141
+ """
142
+ ```
143
+
144
+ Good when using custom classes:
145
+
146
+ ```
147
+ KV = """
148
+ <DesktopUI>:
149
+ ...
150
+
151
+ DesktopUI:
152
+ """
153
+ ```
154
+
155
+ Bad:
156
+
157
+ ```
158
+ KV = """
159
+ <DesktopUI>:
160
+ ...
161
+ """
162
+ ```
163
+
164
+ because `Builder.load_string(KV)` returns `None` for rule-only KV.
165
+
166
+ Required self-check:
167
+ - If the code does `ui = Builder.load_string(KV)`, then `ui` must be a widget.
168
+ - If `KV` contains `<SomeClass>:` rules, it must also contain a final concrete instance like `SomeClass:`.
169
+ - Never call `root.add_widget(ui)` unless `ui is not None`.
170
+ - Never call `Builder.template(...)` for generated PythonHere UI snippets.
171
+ - Never define `<SomeName@BaseWidget>:` dynamic classes in generated KV.
172
+ - Never use `#:set` to expose Python functions or generated text to KV.
173
+ - Never call `Builder.unload_file(...)` for generated inline KV snippets.
174
+ - If code defines a `KV = """..."""` string for the UI, it must actually load
175
+ that string with `Builder.load_string(KV)` and add the loaded widget, or omit
176
+ the KV string entirely. Do not define KV and then instantiate a bare Python
177
+ widget class such as `ui = MyWidget()`; that ignores the KV tree and usually
178
+ shows an empty UI.
179
+
180
+ Kivy property compatibility:
181
+ - Generated Kivy code must use only valid property option values in both KV and Python-created widgets. For `Label.shorten_from`, use only `"left"`, `"center"`, or `"right"`.
182
+
183
+ KV imports:
184
+ - Any Python name used inside KV expressions must either be a KV local such as
185
+ `self`, `root`, or an explicitly imported name declared with `#:import` at
186
+ the top of the KV string.
187
+ - Do not assume Python imports outside the KV string are visible to the KV
188
+ parser. `from math import sin` in Python does not make `sin(...)` valid inside
189
+ KV; use `#:import sin math.sin` in the KV string or move the calculation into
190
+ Python.
191
+ - If KV uses `dp(...)` or `sp(...)`, include exactly one matching import at the
192
+ top of the KV string:
193
+ `#:import dp kivy.metrics.dp`
194
+ `#:import sp kivy.metrics.sp`
195
+ - If KV expressions use math functions, include explicit imports such as:
196
+ `#:import sin math.sin`
197
+ `#:import cos math.cos`
198
+ - Do not generate duplicate `#:import` lines for the same name.
199
+ - Prefer moving nontrivial calculations into Python properties or Python-side
200
+ canvas updates instead of putting complex formulas in KV. This is especially
201
+ important for animated positions, trigonometry, paths, query results, and
202
+ generated lists.
203
+ - If a KV canvas expression still uses trigonometry, the KV string must include
204
+ the math imports once, before any widget rules:
205
+
206
+ ```
207
+ KV = """
208
+ #:import sin math.sin
209
+ #:import cos math.cos
210
+ #:import dp kivy.metrics.dp
211
+
212
+ FloatLayout:
213
+ canvas:
214
+ Ellipse:
215
+ size: dp(24), dp(24)
216
+ pos: self.x + self.width * sin(root.phase), self.y
217
+ """
218
+ ```
219
+
220
+ - Before returning code, scan the KV string for function calls such as `sin(`,
221
+ `cos(`, `dp(`, `sp(`, `rgba(`, or helper names and ensure each helper is
222
+ defined in KV parser scope or removed.
223
+
224
+ KV safety additions:
225
+ - `ids` exist on the loaded root widget, not as global variables. Access them as
226
+ `ui.ids.some_id` after `Builder.load_string(KV)` returns a concrete root
227
+ widget.
228
+ - Avoid assigning duplicate ids in generated KV.
229
+ - Do not create dynamic styling aliases such as `<PoemLabel@Label>:`. For
230
+ generated snippets, repeat simple properties, use a real Python class, or set
231
+ properties from Python after loading the widget tree.
232
+ - Keep KV expressions literal and simple. Use Python to compute all lists,
233
+ paths, formatted labels, colors derived from runtime state, and callback
234
+ decisions after the widget tree is loaded.
235
+ - Do not use `root` in KV to refer to the PythonHere global `root`. In KV,
236
+ `root` means the current KV rule/root widget.
237
+ - When a custom root class owns Kivy properties, define the class in Python
238
+ before `Builder.load_string(KV)` and end KV with a concrete instance of that
239
+ class.