From e57cd9dca1210098563eef22125764618e26ba36 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:26:27 -0500 Subject: [PATCH] recipes: pydevices and pydevices-desktop 0.3.7 -> 0.5.2, pygraphics 0.0.39, palettes 0.0.14, pdwidgets 0.0.24 pydevices 0.5.2 carries the SDL display flicker fix (PyDevices/pydevices#65). Closes #11. --- p4a_app/utils/mip.py | 47 ++++++++++++++++++-- p4a_app/utils/path.py | 19 +++++--- p4a_recipes/pydevices-desktop/__init__.py | 2 +- p4a_recipes/pydevices-palettes/__init__.py | 2 +- p4a_recipes/pydevices-pdwidgets/__init__.py | 2 +- p4a_recipes/pydevices-pygraphics/__init__.py | 2 +- p4a_recipes/pydevices/__init__.py | 2 +- 7 files changed, 61 insertions(+), 15 deletions(-) diff --git a/p4a_app/utils/mip.py b/p4a_app/utils/mip.py index f5cab54..8d6eac5 100644 --- a/p4a_app/utils/mip.py +++ b/p4a_app/utils/mip.py @@ -27,14 +27,44 @@ ``mpy`` defaults to **False** on CPython, CircuitPython, and Pyodide (they use the index ``py`` channel or ``urls`` entries, not device ``.mpy`` bytecode). -""" -from __future__ import annotations +``install`` **does not raise when a package cannot be installed** -- it prints +``Package not found: `` followed by ``Package may be partially installed`` +and returns ``None``, exactly as MicroPython's on-device ``mip`` does. This is +deliberate parity, not an oversight: callers that need to know whether an +install succeeded must check for the files afterwards rather than relying on an +exception. Errors that are *not* a failed package -- notably having no usable +HTTP client at all -- still raise. +""" import json import os import sys +#: Marks this as the portable implementation rather than firmware ``mip``. +#: Importers that specifically need firmware ``mip`` check it to detect +#: search-path shadowing. +PORTABLE = True + +# MicroPython ships ``mip`` in firmware, so arriving here on MicroPython means +# ``import mip`` resolved to this file instead: the search path put this file's +# directory ahead of ``.frozen``. Say so now rather than failing later at the +# first request (there is no MicroPython transport below) or, as this file used +# to, on a CPython-only ``from __future__ import annotations`` whose +# "no module named '__future__'" said nothing about the real problem. +# +# Deliberately not a ``from __future__`` import: CPython requires future +# statements to be the first statement in the file, so one cannot be wrapped in +# try/except, and it would fail on MicroPython before this check could run. +if getattr(sys.implementation, "name", "") == "micropython": + raise ImportError( + "portable mip.py was imported on MicroPython, which ships `mip` in " + "firmware and should never reach this file. `.frozen` must come before " + "{!r} on sys.path -- the documented MICROPYPATH order is " + ".:.frozen:lib:utils:~/.micropython/lib:/usr/lib/micropython. " + "sys.path={!r}".format(globals().get("__file__", "utils/mip.py"), sys.path) + ) + _PACKAGE_INDEX = "https://micropython.org/pi/v2" # Final URL is "https://" + format(org, repo, branch, p=path). @@ -193,7 +223,14 @@ def _http_get(url): except ValueError: # CircuitPython unix: MicroPython-built .mpy of urequests/requests. continue - resp = mod.get(url) + try: + resp = mod.get(url) + except ImportError: + # Imports fine but cannot run: CircuitPython ships urequests while + # providing no socket module, so .get() raises "no module named + # 'socket'". That means this transport is unusable, not that the + # request failed -- keep looking (the curl branch below handles it). + continue try: if hasattr(resp, "content"): data = resp.content @@ -219,7 +256,9 @@ def _http_get(url): if hasattr(os, "system"): tmp = "/tmp/mip_http_" + str(int.from_bytes(os.urandom(4), "big")) + ".bin" # Quote URL for the shell; paths are ASCII package URLs from mip. - cmd = 'curl -fsSL "' + url + '" -o "' + tmp + '"' + # -S omitted on purpose: mip probes URLs that are expected to 404, and + # frozen mip stays quiet there. The caller prints "Package not found". + cmd = 'curl -fsL "' + url + '" -o "' + tmp + '"' try: rc = os.system(cmd) if rc == 0: diff --git a/p4a_app/utils/path.py b/p4a_app/utils/path.py index 3875be8..3305dfb 100644 --- a/p4a_app/utils/path.py +++ b/p4a_app/utils/path.py @@ -134,25 +134,32 @@ def apply(path=None): if key not in seen: seen.append(key) + # Walk the documented order, keeping an anchor just past the last + # documented entry seen in *path*. Missing entries go in at the anchor so + # they land in documented order *relative to the ones already there* -- + # inserting them all at the front would push, say, ``lib`` ahead of an + # already-present ``.frozen`` and shadow frozen modules with mounted ones. missing = [] + anchor = 0 for target in TARGETS: key = _norm(target) if key in seen: + for index, entry in enumerate(path): + if _norm(entry) == key: + anchor = max(anchor, index + 1) + break continue seen.append(key) missing.append(target) - - for offset, entry in enumerate(missing): - path.insert(offset, entry) + path.insert(anchor, target) + anchor += 1 return missing added = apply() if added: print( - "path.py: {} entries added: {}".format( - PATH_VAR, ":".join(entry or "." for entry in added) - ) + "path.py: {} entries added: {}".format(PATH_VAR, ":".join(entry or "." for entry in added)) ) else: print("path.py: {} already satisfied".format(PATH_VAR)) diff --git a/p4a_recipes/pydevices-desktop/__init__.py b/p4a_recipes/pydevices-desktop/__init__.py index 1deb251..794e169 100644 --- a/p4a_recipes/pydevices-desktop/__init__.py +++ b/p4a_recipes/pydevices-desktop/__init__.py @@ -5,7 +5,7 @@ class PydevicesDesktopRecipe(PyProjectRecipe): - version = "0.3.7" + version = "0.5.2" name = "pydevices-desktop" depends = ["sdl2", "pydevices"] call_hostpython_via_targetpython = False diff --git a/p4a_recipes/pydevices-palettes/__init__.py b/p4a_recipes/pydevices-palettes/__init__.py index e47b27f..c9af880 100644 --- a/p4a_recipes/pydevices-palettes/__init__.py +++ b/p4a_recipes/pydevices-palettes/__init__.py @@ -5,7 +5,7 @@ class PalettesRecipe(PyProjectRecipe): - version = "0.0.12" + version = "0.0.14" name = "pydevices-palettes" depends = [] call_hostpython_via_targetpython = False diff --git a/p4a_recipes/pydevices-pdwidgets/__init__.py b/p4a_recipes/pydevices-pdwidgets/__init__.py index 816f4af..1e52cca 100644 --- a/p4a_recipes/pydevices-pdwidgets/__init__.py +++ b/p4a_recipes/pydevices-pdwidgets/__init__.py @@ -6,7 +6,7 @@ class PdwidgetsRecipe(PyProjectRecipe): # Pin so hostpython pip does not reuse a stale wheel from cache. - version = "0.0.22" + version = "0.0.24" name = "pydevices-pdwidgets" depends = [ "pydevices", diff --git a/p4a_recipes/pydevices-pygraphics/__init__.py b/p4a_recipes/pydevices-pygraphics/__init__.py index 4fae23d..ac64881 100644 --- a/p4a_recipes/pydevices-pygraphics/__init__.py +++ b/p4a_recipes/pydevices-pygraphics/__init__.py @@ -6,7 +6,7 @@ class PygraphicsRecipe(PyProjectRecipe): # Pin so hostpython pip does not reuse a stale wheel from cache. - version = "0.0.37" + version = "0.0.39" name = "pydevices-pygraphics" depends = [] call_hostpython_via_targetpython = False diff --git a/p4a_recipes/pydevices/__init__.py b/p4a_recipes/pydevices/__init__.py index 44da0ee..5567614 100644 --- a/p4a_recipes/pydevices/__init__.py +++ b/p4a_recipes/pydevices/__init__.py @@ -15,7 +15,7 @@ class PydevicesRecipe(PyProjectRecipe): - version = "0.3.7" + version = "0.5.2" name = "pydevices" depends = [] call_hostpython_via_targetpython = False