From b95ca2491aa405199a429fc24e227a32cb6564f8 Mon Sep 17 00:00:00 2001 From: FuNK3Y Date: Sat, 19 Jul 2025 12:14:17 +0000 Subject: [PATCH 01/34] aiohttp: Fix partial reads by using readexactly. Fixes issue #1012. Signed-off-by: FuNK3Y --- python-ecosys/aiohttp/aiohttp/__init__.py | 10 ++++++---- python-ecosys/aiohttp/aiohttp/aiohttp_ws.py | 10 +++++----- python-ecosys/aiohttp/manifest.py | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/python-ecosys/aiohttp/aiohttp/__init__.py b/python-ecosys/aiohttp/aiohttp/__init__.py index 8c5493f30..1e6d89d05 100644 --- a/python-ecosys/aiohttp/aiohttp/__init__.py +++ b/python-ecosys/aiohttp/aiohttp/__init__.py @@ -42,7 +42,9 @@ def _decode(self, data): return data async def read(self, sz=-1): - return self._decode(await self.content.read(sz)) + return self._decode( + await (self.content.read(sz) if sz == -1 else self.content.readexactly(sz)) + ) async def text(self, encoding="utf-8"): return (await self.read(int(self._get_header("content-length", -1)))).decode(encoding) @@ -66,13 +68,13 @@ async def read(self, sz=4 * 1024 * 1024): self.chunk_size = int(l, 16) if self.chunk_size == 0: # End of message - sep = await self.content.read(2) + sep = await self.content.readexactly(2) assert sep == b"\r\n" return b"" - data = await self.content.read(min(sz, self.chunk_size)) + data = await self.content.readexactly(min(sz, self.chunk_size)) self.chunk_size -= len(data) if self.chunk_size == 0: - sep = await self.content.read(2) + sep = await self.content.readexactly(2) assert sep == b"\r\n" return self._decode(data) diff --git a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py index 6e0818c92..53a640fe5 100644 --- a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py +++ b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py @@ -189,7 +189,7 @@ async def close(self): await self.send(b"", self.CLOSE) async def _read_frame(self): - header = await self.reader.read(2) + header = await self.reader.readexactly(2) if len(header) != 2: # pragma: no cover # raise OSError(32, "Websocket connection closed") opcode = self.CLOSE @@ -197,13 +197,13 @@ async def _read_frame(self): return opcode, payload fin, opcode, has_mask, length = self._parse_frame_header(header) if length == 126: # Magic number, length header is 2 bytes - (length,) = struct.unpack("!H", await self.reader.read(2)) + (length,) = struct.unpack("!H", await self.reader.readexactly(2)) elif length == 127: # Magic number, length header is 8 bytes - (length,) = struct.unpack("!Q", await self.reader.read(8)) + (length,) = struct.unpack("!Q", await self.reader.readexactly(8)) if has_mask: # pragma: no cover - mask = await self.reader.read(4) - payload = await self.reader.read(length) + mask = await self.reader.readexactly(4) + payload = await self.reader.readexactly(length) if has_mask: # pragma: no cover payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload)) return opcode, payload diff --git a/python-ecosys/aiohttp/manifest.py b/python-ecosys/aiohttp/manifest.py index d22a6ce11..bbf22bb29 100644 --- a/python-ecosys/aiohttp/manifest.py +++ b/python-ecosys/aiohttp/manifest.py @@ -1,6 +1,6 @@ metadata( description="HTTP client module for MicroPython asyncio module", - version="0.0.5", + version="0.0.6", pypi="aiohttp", ) From 34c4ee1647ac4b177ae40adf0ec514660e433dc0 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 17 Jun 2025 15:04:39 +1000 Subject: [PATCH 02/34] aiorepl: Handle stream shutdown. Signed-off-by: Andrew Leech --- micropython/aiorepl/aiorepl.py | 2 ++ micropython/aiorepl/manifest.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index a640efcd1..fbe513b7c 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -114,6 +114,8 @@ async def task(g=None, prompt="--> "): curs = 0 # cursor offset from end of cmd buffer while True: b = await s.read(1) + if not b: # Handle EOF/empty read + break pc = c # save previous character c = ord(b) pt = t # save previous time diff --git a/micropython/aiorepl/manifest.py b/micropython/aiorepl/manifest.py index ca2fa1513..83802e1c0 100644 --- a/micropython/aiorepl/manifest.py +++ b/micropython/aiorepl/manifest.py @@ -1,5 +1,5 @@ metadata( - version="0.2.1", + version="0.2.2", description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.", ) From ea763cad8d5ee095361da75bed945af5ab858449 Mon Sep 17 00:00:00 2001 From: Hyx Date: Fri, 1 Aug 2025 12:47:43 +0800 Subject: [PATCH 03/34] usb-device: Raise RuntimeError when DCD error occurs. So the behavior matches the comment. Signed-off-by: Hyx --- micropython/usb/usb-device/manifest.py | 2 +- micropython/usb/usb-device/usb/device/core.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/micropython/usb/usb-device/manifest.py b/micropython/usb/usb-device/manifest.py index 025e67547..fa0d8a3f5 100644 --- a/micropython/usb/usb-device/manifest.py +++ b/micropython/usb/usb-device/manifest.py @@ -1,2 +1,2 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") package("usb") diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index 7be09ee46..b0d91d8ff 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -600,7 +600,8 @@ def submit_xfer(self, ep_addr, data, done_cb=None): # function has returned to the caller. if not self._open: raise RuntimeError("Not open") - _dev._submit_xfer(ep_addr, data, done_cb) + if not _dev._submit_xfer(ep_addr, data, done_cb): + raise RuntimeError("DCD error") def stall(self, ep_addr, *args): # Set or get the endpoint STALL state. From bdc4706cc700ae1c0a4520e252897bb0e03c327b Mon Sep 17 00:00:00 2001 From: Hyx Date: Fri, 1 Aug 2025 12:53:01 +0800 Subject: [PATCH 04/34] usb-device-hid: Return True after submit_xfer. This tells the caller that no error has occurred. The child classes can use this state to do double buffering correctly. If any fundamental error occurs in submit_xfer, the underlying code will raise an exception. Signed-off-by: Hyx --- micropython/usb/usb-device-hid/manifest.py | 2 +- micropython/usb/usb-device-hid/usb/device/hid.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/micropython/usb/usb-device-hid/manifest.py b/micropython/usb/usb-device-hid/manifest.py index af9b8cb84..4520325e3 100644 --- a/micropython/usb/usb-device-hid/manifest.py +++ b/micropython/usb/usb-device-hid/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.0") +metadata(version="0.1.1") require("usb-device") package("usb") diff --git a/micropython/usb/usb-device-hid/usb/device/hid.py b/micropython/usb/usb-device-hid/usb/device/hid.py index 9e4c70dde..1c1c9ac6b 100644 --- a/micropython/usb/usb-device-hid/usb/device/hid.py +++ b/micropython/usb/usb-device-hid/usb/device/hid.py @@ -123,6 +123,7 @@ def send_report(self, report_data, timeout_ms=100): if not self.is_open(): return False self.submit_xfer(self._int_ep, report_data) + return True def desc_cfg(self, desc, itf_num, ep_num, strs): # Add the standard interface descriptor From b4565b41eab684722d9fcb2deee322869e47c359 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 19 Sep 2025 13:38:26 +1000 Subject: [PATCH 05/34] inspect: Implement a very basic signature function. This implements a very basic `inspect.signature()` function. At the moment it returns only a simple `Signature` instance with a `parameters` attribute that holds an `OrderedDict` whose length matches the arity of the input function (the number of arguments it takes). So, the following code works and is compatible with CPython: def f(a, b, *, c): pass print(len(inspect.signature(f).parameters)) That should print 3. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 57 +++++++++++++++++++++++++++ python-stdlib/inspect/manifest.py | 2 +- python-stdlib/inspect/test_inspect.py | 13 ++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index c16c6b3e3..9074549bb 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -80,3 +80,60 @@ def currentframe(): def getframeinfo(frame, context=1): return ("", -1, "", [""], 0) + + +class Signature: + pass + + +# This `signature()` function is very limited. It's main purpose is to work out +# the arity of the given function, ie the number of arguments it takes. +# +# The return value is an instance of `Signature` with a `parameters` member which +# is an OrderedDict whose length is the number of arguments of `f`. +def signature(f): + import collections + import uctypes + + s = Signature() + s.parameters = collections.OrderedDict() + + t = type(f) + if t is type(globals): + # A zero-parameter built-in. + num_args = 0 + elif t is type(abs): + # A one-parameter built-in. + num_args = 1 + elif t is type(hasattr): + # A two-parameter built-in. + num_args = 2 + elif t is type(setattr): + # A three-parameter built-in. + num_args = 3 + elif t is type(signature): + # A bytecode function, work out the number of arguments by inspecting the bytecode data. + fun_obj = uctypes.struct(id(f), (uctypes.ARRAY | 0, uctypes.LONG | 4)) + bytecode = uctypes.bytearray_at(fun_obj[3], 8) + # See py/bc.h:MP_BC_PRELUDE_SIG_DECODE_INTO macro. + i = 0 + z = bytecode[i] + i += 1 + A = z & 0x3 + K = 0 + n = 0 + while z & 0x80: + z = bytecode[i] + i += 1 + A |= (z & 0x4) << n + K |= ((z & 0x08) >> 3) << n + num_args = A + K + else: + raise NotImplementedError("unsupported function type") + + # Add dummy arguments to the OrderedDict. + for i in range(num_args): + a = "x{}".format(i) + s.parameters[a] = a + + return s diff --git a/python-stdlib/inspect/manifest.py b/python-stdlib/inspect/manifest.py index e99e659f2..119237c45 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.3") +metadata(version="0.2.0") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py index 29ed80f11..f5110de70 100644 --- a/python-stdlib/inspect/test_inspect.py +++ b/python-stdlib/inspect/test_inspect.py @@ -1,3 +1,4 @@ +import collections import inspect import unittest @@ -58,3 +59,15 @@ def test_isclass(self): def test_ismodule(self): self._test_is_helper(inspect.ismodule, entities[6]) + + def test_signature(self): + self.assertEqual(inspect.signature(globals).parameters, collections.OrderedDict()) + self.assertEqual(len(inspect.signature(abs).parameters), 1) + self.assertEqual(len(inspect.signature(hasattr).parameters), 2) + self.assertEqual(len(inspect.signature(setattr).parameters), 3) + self.assertEqual(len(inspect.signature(lambda: 0).parameters), 0) + self.assertEqual(len(inspect.signature(lambda x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda *, x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda x, y: 0).parameters), 2) + self.assertEqual(len(inspect.signature(lambda x, y, z: 0).parameters), 3) + self.assertEqual(len(inspect.signature(lambda x, y, *, z: 0).parameters), 3) From 656f42071e8fa819e2b63e75cadba7ba639b32b5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 25 Sep 2025 00:34:12 +1000 Subject: [PATCH 06/34] datetime: Apply localtz patch to include naive date/time support. This commit applies the existing `localtz.patch` patch to add support for naive datetime objects. That is, objects that don't have any info about the current timezone. This allows `datetime.datetime.now()` to work; prior to this patch it would raise NotImplementedError. Although we don't really have support for localtime vs gmtime on bare-metal, ports such as the unix port and webassembly port do have this distinction, and for them being able to do `datetime.datetime.now()` is quite important (at least, that's what users expect to be able to do). The associated unittest test has been updated. This patch changes the size of datetime.mpy: 8466 -> 8897, so +431 bytes. Signed-off-by: Damien George --- python-stdlib/datetime/datetime.py | 41 +++++++- python-stdlib/datetime/localtz.patch | 84 ----------------- python-stdlib/datetime/manifest.py | 2 +- python-stdlib/datetime/test_datetime.py | 118 ++++++------------------ 4 files changed, 65 insertions(+), 180 deletions(-) delete mode 100644 python-stdlib/datetime/localtz.patch diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 0f2a89105..4005fcd79 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -633,7 +633,10 @@ def fromtimestamp(cls, ts, tz=None): else: us = 0 if tz is None: - raise NotImplementedError + dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) + s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 + if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): + dt._fd = 1 else: dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) dt = tz.fromutc(dt) @@ -810,13 +813,45 @@ def astimezone(self, tz=None): return self _tz = self._tz if _tz is None: - raise NotImplementedError + ts = int(self._mktime()) + os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) else: os = _tz.utcoffset(self) utc = self - os utc = utc.replace(tzinfo=tz) return tz.fromutc(utc) + def _mktime(self): + def local(u): + return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 + + epoch = datetime.EPOCH.replace(tzinfo=None) + t, us = divmod((self - epoch)._us, 1_000_000) + ts = None + + a = local(t) - t + u1 = t - a + t1 = local(u1) + if t1 == t: + u2 = u1 + (86400 if self.fold else -86400) + b = local(u2) - u2 + if a == b: + ts = u1 + else: + b = t1 - u1 + if ts is None: + u2 = t - b + t2 = local(u2) + if t2 == t: + ts = u2 + elif t1 == t: + ts = u1 + elif self.fold: + ts = min(u1, u2) + else: + ts = max(u1, u2) + return ts + us / 1_000_000 + def utcoffset(self): return None if self._tz is None else self._tz.utcoffset(self) @@ -840,7 +875,7 @@ def toordinal(self): def timestamp(self): if self._tz is None: - raise NotImplementedError + return self._mktime() else: return (self - datetime.EPOCH).total_seconds() diff --git a/python-stdlib/datetime/localtz.patch b/python-stdlib/datetime/localtz.patch deleted file mode 100644 index 7a2449d5d..000000000 --- a/python-stdlib/datetime/localtz.patch +++ /dev/null @@ -1,84 +0,0 @@ -localtz.patch - -The CPython's implementation of `datetime.fromtimestamp()`, -`datetime.astimezone()` and `datetime.timestamp()` for naive datetime objects -relay on proper management of DST (daylight saving time) by `time.localtime()` -for the timezone of interest. In the Unix port of MicroPython, this is -accomplished by properly setting the TZ environment variable, e.g. -`os.putenv("TZ", "Europe/Rome")`. - -Because real boards often lack a supportive `time.localtime()`, the source code -in `datetime.py` has been removed as to save precious resources. If your board -provide a proper implementation, you can restore the support to naive datetime -objects by applying this patch, e.g. `patch -p1 < localtz.patch`. - ---- a/datetime.py -+++ b/datetime.py -@@ -635,7 +635,10 @@ class datetime: - else: - us = 0 - if tz is None: -- raise NotImplementedError -+ dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) -+ s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 -+ if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): -+ dt._fd = 1 - else: - dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) - dt = tz.fromutc(dt) -@@ -812,13 +815,45 @@ class datetime: - return self - _tz = self._tz - if _tz is None: -- raise NotImplementedError -+ ts = int(self._mktime()) -+ os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) - else: - os = _tz.utcoffset(self) - utc = self - os - utc = utc.replace(tzinfo=tz) - return tz.fromutc(utc) - -+ def _mktime(self): -+ def local(u): -+ return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 -+ -+ epoch = datetime.EPOCH.replace(tzinfo=None) -+ t, us = divmod((self - epoch)._us, 1_000_000) -+ ts = None -+ -+ a = local(t) - t -+ u1 = t - a -+ t1 = local(u1) -+ if t1 == t: -+ u2 = u1 + (86400 if self.fold else -86400) -+ b = local(u2) - u2 -+ if a == b: -+ ts = u1 -+ else: -+ b = t1 - u1 -+ if ts is None: -+ u2 = t - b -+ t2 = local(u2) -+ if t2 == t: -+ ts = u2 -+ elif t1 == t: -+ ts = u1 -+ elif self.fold: -+ ts = min(u1, u2) -+ else: -+ ts = max(u1, u2) -+ return ts + us / 1_000_000 -+ - def utcoffset(self): - return None if self._tz is None else self._tz.utcoffset(self) - -@@ -842,7 +877,7 @@ class datetime: - - def timestamp(self): - if self._tz is None: -- raise NotImplementedError -+ return self._mktime() - else: - return (self - datetime.EPOCH).total_seconds() - diff --git a/python-stdlib/datetime/manifest.py b/python-stdlib/datetime/manifest.py index 017189cec..d4adce9cd 100644 --- a/python-stdlib/datetime/manifest.py +++ b/python-stdlib/datetime/manifest.py @@ -1,4 +1,4 @@ -metadata(version="4.0.0") +metadata(version="4.1.0") # Originally written by Lorenzo Cappelletti. diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 98da458f9..56411b96e 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -68,14 +68,6 @@ import unittest -# See localtz.patch -try: - datetime.fromtimestamp(0) - LOCALTZ = True -except NotImplementedError: - LOCALTZ = False - - if hasattr(datetime, "EPOCH"): EPOCH = datetime.EPOCH else: @@ -1619,11 +1611,8 @@ def test___init__24(self): def test_fromtimestamp00(self): with LocalTz("Europe/Rome"): ts = 1012499103.001234 - if LOCALTZ: - dt = datetime.fromtimestamp(ts) - self.assertEqual(dt, d1t1) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + dt = datetime.fromtimestamp(ts) + self.assertEqual(dt, d1t1) def test_fromtimestamp01(self): ts = 1012506303.001234 @@ -1642,48 +1631,35 @@ def test_fromtimestamp04(self): dt = datetime(2010, 10, 31, 0, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 2 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertFalse(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertFalse(ds.fold) def test_fromtimestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 1, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 1 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertTrue(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertTrue(ds.fold) def test_fromtimestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 5, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 4 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) def test_fromtimestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 7, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 5 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) - @unittest.skipIf(not LOCALTZ, "naive datetime not supported") def test_now00(self): tm = datetime(*mod_time.localtime()[:6]) dt = datetime.now() @@ -2004,46 +1980,31 @@ def test_astimezone04(self): with LocalTz("Europe/Rome"): dt1 = dt27tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone05(self): with LocalTz("Europe/Rome"): dt1 = dt28tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone06(self): with LocalTz("Europe/Rome"): dt1 = dt30tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone07(self): with LocalTz("Europe/Rome"): dt1 = dt31tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone08(self): with LocalTz("Europe/Rome"): dt1 = dt3 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_utcoffset00(self): self.assertEqual(dt1.utcoffset(), None) @@ -2123,10 +2084,7 @@ def test_weekday00(self): def test_timestamp00(self): with LocalTz("Europe/Rome"): - if LOCALTZ: - self.assertEqual(d1t1.timestamp(), 1012499103.001234) - else: - self.assertRaises(NotImplementedError, d1t1.timestamp) + self.assertEqual(d1t1.timestamp(), 1012499103.001234) def test_timestamp01(self): self.assertEqual(d1t1z.timestamp(), 1012506303.001234) @@ -2134,66 +2092,42 @@ def test_timestamp01(self): def test_timestamp02(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 3, 28, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1269739800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1269739800.0) def test_timestamp03(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1281400200.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1281400200.0) def test_timestamp04(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288485000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288485000.0) def test_timestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288488600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288488600.0) def test_timestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 3, 8, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1583652600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1583652600.0) def test_timestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1597041000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1597041000.0) def test_timestamp08(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_timestamp09(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_isoweekday00(self): self.assertEqual(dt1.isoweekday(), d1.isoweekday()) From 3eaf0279f350855f0ee3a8a65774a49afdbf4e3d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 25 Sep 2025 00:50:31 +1000 Subject: [PATCH 07/34] datetime: Optimize for code size. Optimizations applied here are: - writing once-used helper functions inline in their place of use - writing once-used constant tuples inline in their place of use (I would have used `from micropython import const` but that renders the code not runnable under CPython for testing, and also increases code size itself for the import) - renamed _tmod to _t - renamed _format to _fmt - optimised timedelta._tuple() slightly Reduces datetime.mpy by: 8897 -> 8728, so saves 169 bytes. Signed-off-by: Damien George --- python-stdlib/datetime/datetime.py | 71 +++++++++++++----------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 4005fcd79..b18edf1c2 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -1,37 +1,22 @@ # datetime.py -import time as _tmod - -_DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) -_DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) -_TIME_SPEC = ("auto", "hours", "minutes", "seconds", "milliseconds", "microseconds") +import time as _t def _leap(y): return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0) -def _dby(y): - # year -> number of days before January 1st of year. - Y = y - 1 - return Y * 365 + Y // 4 - Y // 100 + Y // 400 - - def _dim(y, m): # year, month -> number of days in that month in that year. if m == 2 and _leap(y): return 29 - return _DIM[m] + return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[m] def _dbm(y, m): # year, month -> number of days in year preceding first day of month. - return _DBM[m] + (m > 2 and _leap(y)) - - -def _ymd2o(y, m, d): - # y, month, day -> ordinal, considering 01-Jan-0001 as day 1. - return _dby(y) + _dbm(y, m) + d + return (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[m] + (m > 2 and _leap(y)) def _o2ymd(n): @@ -73,7 +58,7 @@ def total_seconds(self): @property def days(self): - return self._tuple(2)[0] + return self._tuple(1) @property def seconds(self): @@ -145,7 +130,7 @@ def __bool__(self): return self._us != 0 def __str__(self): - return self._format(0x40) + return self._fmt(0x40) def __hash__(self): if not hasattr(self, "_hash"): @@ -153,9 +138,9 @@ def __hash__(self): return self._hash def isoformat(self): - return self._format(0) + return self._fmt(0) - def _format(self, spec=0): + def _fmt(self, spec=0): if self._us >= 0: td = self g = "" @@ -201,8 +186,8 @@ def tuple(self): def _tuple(self, n): d, us = divmod(self._us, 86_400_000_000) - if n == 2: - return d, us + if n == 1: + return d s, us = divmod(us, 1_000_000) if n == 3: return d, s, us @@ -241,7 +226,7 @@ def fromutc(self, dt): return dt + dtdst def isoformat(self, dt): - return self.utcoffset(dt)._format(0x12) + return self.utcoffset(dt)._fmt(0x12) class timezone(tzinfo): @@ -276,7 +261,7 @@ def dst(self, dt): def tzname(self, dt): if self._name: return self._name - return self._offset._format(0x22) + return self._offset._fmt(0x22) def fromutc(self, dt): return dt + self._offset @@ -287,7 +272,11 @@ def fromutc(self, dt): def _date(y, m, d): if MINYEAR <= y <= MAXYEAR and 1 <= m <= 12 and 1 <= d <= _dim(y, m): - return _ymd2o(y, m, d) + # year -> number of days before January 1st of year. + Y = y - 1 + _dby = Y * 365 + Y // 4 - Y // 100 + Y // 400 + # y, month, day -> ordinal, considering 01-Jan-0001 as day 1. + return _dby + _dbm(y, m) + d elif y == 0 and m == 0 and 1 <= d <= 3_652_059: return d else: @@ -310,11 +299,11 @@ def __init__(self, year, month, day): @classmethod def fromtimestamp(cls, ts): - return cls(*_tmod.localtime(ts)[:3]) + return cls(*_t.localtime(ts)[:3]) @classmethod def today(cls): - return cls(*_tmod.localtime()[:3]) + return cls(*_t.localtime()[:3]) @classmethod def fromordinal(cls, n): @@ -490,7 +479,9 @@ def _iso2t(s): def _t2iso(td, timespec, dt, tz): - s = td._format(_TIME_SPEC.index(timespec)) + s = td._fmt( + ("auto", "hours", "minutes", "seconds", "milliseconds", "microseconds").index(timespec) + ) if tz is not None: s += tz.isoformat(dt) return s @@ -633,18 +624,18 @@ def fromtimestamp(cls, ts, tz=None): else: us = 0 if tz is None: - dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) - s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 - if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): + dt = cls(*_t.localtime(ts)[:6], microsecond=us, tzinfo=tz) + s = (dt - datetime(*_t.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 + if s < 0 and dt == datetime(*_t.localtime(ts + s)[:6]): dt._fd = 1 else: - dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) + dt = cls(*_t.gmtime(ts)[:6], microsecond=us, tzinfo=tz) dt = tz.fromutc(dt) return dt @classmethod def now(cls, tz=None): - return cls.fromtimestamp(_tmod.time(), tz) + return cls.fromtimestamp(_t.time(), tz) @classmethod def fromordinal(cls, n): @@ -814,7 +805,7 @@ def astimezone(self, tz=None): _tz = self._tz if _tz is None: ts = int(self._mktime()) - os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) + os = datetime(*_t.localtime(ts)[:6]) - datetime(*_t.gmtime(ts)[:6]) else: os = _tz.utcoffset(self) utc = self - os @@ -823,7 +814,7 @@ def astimezone(self, tz=None): def _mktime(self): def local(u): - return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 + return (datetime(*_t.localtime(u)[:6]) - epoch)._us // 1_000_000 epoch = datetime.EPOCH.replace(tzinfo=None) t, us = divmod((self - epoch)._us, 1_000_000) @@ -863,10 +854,10 @@ def tzname(self): def timetuple(self): if self._tz is None: - conv = _tmod.gmtime + conv = _t.gmtime epoch = datetime.EPOCH.replace(tzinfo=None) else: - conv = _tmod.localtime + conv = _t.localtime epoch = datetime.EPOCH return conv(round((self - epoch).total_seconds())) @@ -909,4 +900,4 @@ def tuple(self): return d + t + (self._tz, self._fd) -datetime.EPOCH = datetime(*_tmod.gmtime(0)[:6], tzinfo=timezone.utc) +datetime.EPOCH = datetime(*_t.gmtime(0)[:6], tzinfo=timezone.utc) From 200e8d13c9e186ff5d1d54cc08ec5078309f43cd Mon Sep 17 00:00:00 2001 From: Ben Wynn Date: Wed, 29 Oct 2025 17:07:03 -0400 Subject: [PATCH 08/34] sdcard: Updating sector calculation for SDXC. The current code only calculates sectors for SDHC, with a max of 32G The new code will calculate sectors for SDXC, with a max of 2T C_SIZE is 22 bits[69:48] and the high 2 bits of csd[7] are required to be 0, currently, so i'm not masking them off. The max value for C_SIZE is 0x3FFEFF, so C_SIZE+1*1024 is a 32-bit number. bumped version in manifest.py Signed-off-by: Ben Wynn --- micropython/drivers/storage/sdcard/manifest.py | 2 +- micropython/drivers/storage/sdcard/sdcard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/drivers/storage/sdcard/manifest.py b/micropython/drivers/storage/sdcard/manifest.py index cb4647eeb..ea30a7a39 100644 --- a/micropython/drivers/storage/sdcard/manifest.py +++ b/micropython/drivers/storage/sdcard/manifest.py @@ -1,3 +1,3 @@ -metadata(description="SDCard block device driver.", version="0.1.0") +metadata(description="SDCard block device driver.", version="0.1.1") module("sdcard.py", opt=3) diff --git a/micropython/drivers/storage/sdcard/sdcard.py b/micropython/drivers/storage/sdcard/sdcard.py index c9c991f59..3df4788a2 100644 --- a/micropython/drivers/storage/sdcard/sdcard.py +++ b/micropython/drivers/storage/sdcard/sdcard.py @@ -97,7 +97,7 @@ def init_card(self, baudrate): csd = bytearray(16) self.readinto(csd) if csd[0] & 0xC0 == 0x40: # CSD version 2.0 - self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024 + self.sectors = ((csd[7] << 16 | csd[8] << 8 | csd[9]) + 1) * 1024 elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB) c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6 c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7 From a7c805cc3750a2b37d35a50c36acd98b19ec98e2 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:42:14 +0100 Subject: [PATCH 09/34] mip: Optimize _CHUNK_SIZE const for code size. 17 bytes reduced. Signed-off-by: Jos Verlinde --- micropython/mip/manifest.py | 2 +- micropython/mip/mip/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/micropython/mip/manifest.py b/micropython/mip/manifest.py index 9fb94ebcb..a1b340670 100644 --- a/micropython/mip/manifest.py +++ b/micropython/mip/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.4.1", description="On-device package installer for network-capable boards") +metadata(version="0.4.2", description="On-device package installer for network-capable boards") require("requests") diff --git a/micropython/mip/mip/__init__.py b/micropython/mip/mip/__init__.py index 7c0fb4d3a..ab48393d6 100644 --- a/micropython/mip/mip/__init__.py +++ b/micropython/mip/mip/__init__.py @@ -1,13 +1,14 @@ # MicroPython package installer # MIT license; Copyright (c) 2022 Jim Mussared -from micropython import const -import requests import sys +import requests + +from micropython import const _PACKAGE_INDEX = const("https://micropython.org/pi/v2") -_CHUNK_SIZE = 128 +_CHUNK_SIZE = const(128) allowed_mip_url_prefixes = ("http://", "https://", "github:", "gitlab:") From 28136d837ab936324d46f2cbe50e686475f5c38c Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 01:20:18 +0100 Subject: [PATCH 10/34] aioble/examples: Change variable name to _ADV_INTERVAL_US. Fixes issue #1055. Signed-off-by: Jos Verlinde --- micropython/bluetooth/aioble/examples/temp_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/bluetooth/aioble/examples/temp_sensor.py b/micropython/bluetooth/aioble/examples/temp_sensor.py index 54580f595..9a4ec26de 100644 --- a/micropython/bluetooth/aioble/examples/temp_sensor.py +++ b/micropython/bluetooth/aioble/examples/temp_sensor.py @@ -20,7 +20,7 @@ _ADV_APPEARANCE_GENERIC_THERMOMETER = const(768) # How frequently to send advertising beacons. -_ADV_INTERVAL_MS = 250_000 +_ADV_INTERVAL_US = 250_000 # Register GATT server. @@ -50,7 +50,7 @@ async def sensor_task(): async def peripheral_task(): while True: async with await aioble.advertise( - _ADV_INTERVAL_MS, + _ADV_INTERVAL_US, name="mpy-temp", services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER, From 7b01b50bfbc9342b65004abf187190e144ca854f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 01:45:23 +0100 Subject: [PATCH 11/34] aioble: Fix typo in README in aioble.ADDR_PUBLIC. Fixes issue #699. Signed-off-by: Jos Verlinde --- micropython/bluetooth/aioble/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/bluetooth/aioble/README.md b/micropython/bluetooth/aioble/README.md index 83ae00209..559594850 100644 --- a/micropython/bluetooth/aioble/README.md +++ b/micropython/bluetooth/aioble/README.md @@ -93,7 +93,7 @@ async with aioble.scan(duration_ms=5000, interval_us=30000, window_us=30000, act # Either from scan result device = result.device # Or with known address -device = aioble.Device(aioble.PUBLIC, "aa:bb:cc:dd:ee:ff") +device = aioble.Device(aioble.ADDR_PUBLIC, "aa:bb:cc:dd:ee:ff") try: connection = await device.connect(timeout_ms=2000) From 0b78a15fb90d1bbd7637d6500cbbd9b2988a97fe Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:04:34 +0100 Subject: [PATCH 12/34] copy: Fix typo in _deepcopy_dispatch. Fixes issue #952. Signed-off-by: Jos Verlinde --- python-stdlib/copy/copy.py | 2 +- python-stdlib/copy/manifest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-stdlib/copy/copy.py b/python-stdlib/copy/copy.py index 0a9283777..9d56d6cbd 100644 --- a/python-stdlib/copy/copy.py +++ b/python-stdlib/copy/copy.py @@ -191,7 +191,7 @@ def deepcopy(x, memo=None, _nil=[]): if copier: y = copier(memo) else: - reductor = dispatch_table.get(cls) + reductor = _deepcopy_dispatch.get(cls) if reductor: rv = reductor(x) else: diff --git a/python-stdlib/copy/manifest.py b/python-stdlib/copy/manifest.py index b22ebeb90..f0849295f 100644 --- a/python-stdlib/copy/manifest.py +++ b/python-stdlib/copy/manifest.py @@ -1,4 +1,4 @@ -metadata(version="3.3.4") +metadata(version="3.3.5") require("types") From 886f136662232e2532bb089ce6f85be8d71d7529 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:33:58 +0100 Subject: [PATCH 13/34] requests: Update example for fetching using requests. This is a working alternative to PR #919. Signed-off-by: Jos Verlinde --- python-ecosys/requests/{example_xively.py => example_quote.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename python-ecosys/requests/{example_xively.py => example_quote.py} (85%) diff --git a/python-ecosys/requests/example_xively.py b/python-ecosys/requests/example_quote.py similarity index 85% rename from python-ecosys/requests/example_xively.py rename to python-ecosys/requests/example_quote.py index 60e139b98..cfbe8ac0e 100644 --- a/python-ecosys/requests/example_xively.py +++ b/python-ecosys/requests/example_quote.py @@ -1,6 +1,6 @@ import requests -r = requests.get("http://api.xively.com/") +r = requests.get("https://dummyjson.com/quotes/1") print(r) print(r.content) print(r.text) From 2992784136be0f4281b5941fe6ae92a46a171b5a Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 17 Nov 2025 13:25:59 +0100 Subject: [PATCH 14/34] all: Fix formatting errors in docstrings. No changes to versions as only comments and docstrings changed Signed-off-by: Jos Verlinde --- micropython/drivers/imu/bmi270/bmi270.py | 38 ++++++++++--------- micropython/drivers/imu/bmm150/bmm150.py | 24 ++++++------ micropython/drivers/imu/lsm6dsox/lsm6dsox.py | 27 ++++++------- micropython/drivers/imu/lsm9ds1/lsm9ds1.py | 24 ++++++------ micropython/drivers/sensor/ds18x20/ds18x20.py | 7 +++- micropython/drivers/sensor/hs3003/hs3003.py | 22 ++++++----- micropython/drivers/sensor/hts221/hts221.py | 22 ++++++----- python-stdlib/cmd/cmd.py | 29 +++++++------- python-stdlib/contextlib/contextlib.py | 8 ++-- python-stdlib/heapq/heapq.py | 25 ++++++------ python-stdlib/textwrap/textwrap.py | 6 ++- 11 files changed, 123 insertions(+), 109 deletions(-) diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index 64f819ec2..c76962f7e 100644 --- a/micropython/drivers/imu/bmi270/bmi270.py +++ b/micropython/drivers/imu/bmi270/bmi270.py @@ -21,29 +21,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: + + import time + from bmi270 import BMI270 + from machine import Pin, SPI, I2C + + # Init in I2C mode. + imu = BMI270(I2C(1, scl=Pin(15), sda=Pin(14))) + + # Or init in SPI mode. + # TODO: Not supported yet. + # imu = BMI270(SPI(5), cs=Pin(10)) + + while (True): + print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) + print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) + print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + print("") + time.sleep_ms(100) -import time -from bmi270 import BMI270 -from machine import Pin, SPI, I2C - -# Init in I2C mode. -imu = BMI270(I2C(1, scl=Pin(15), sda=Pin(14))) - -# Or init in SPI mode. -# TODO: Not supported yet. -# imu = BMI270(SPI(5), cs=Pin(10)) - -while (True): - print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) - print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) - print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - print("") - time.sleep_ms(100) """ import array import time + from micropython import const _DEFAULT_ADDR = const(0x68) diff --git a/micropython/drivers/imu/bmm150/bmm150.py b/micropython/drivers/imu/bmm150/bmm150.py index a4845c961..aea4348b5 100644 --- a/micropython/drivers/imu/bmm150/bmm150.py +++ b/micropython/drivers/imu/bmm150/bmm150.py @@ -21,22 +21,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: -import time -from bmm150 import BMM150 -from machine import Pin, SPI, I2C + import time + from bmm150 import BMM150 + from machine import Pin, SPI, I2C -# Init in I2C mode. -imu = BMM150(I2C(1, scl=Pin(15), sda=Pin(14))) + # Init in I2C mode. + imu = BMM150(I2C(1, scl=Pin(15), sda=Pin(14))) -# Or init in SPI mode. -# TODO: Not supported yet. -# imu = BMM150(SPI(5), cs=Pin(10)) + # Or init in SPI mode. + # TODO: Not supported yet. + # imu = BMM150(SPI(5), cs=Pin(10)) -while (True): - print('magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - time.sleep_ms(100) + while (True): + print('magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + time.sleep_ms(100) """ import array diff --git a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py index ca1397c66..b932ff006 100644 --- a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py +++ b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py @@ -25,23 +25,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: -import time -from lsm6dsox import LSM6DSOX + import time + from lsm6dsox import LSM6DSOX + + from machine import Pin, SPI, I2C + # Init in I2C mode. + lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12))) -from machine import Pin, SPI, I2C -# Init in I2C mode. -lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12))) + # Or init in SPI mode. + #lsm = LSM6DSOX(SPI(5), cs=Pin(10)) -# Or init in SPI mode. -#lsm = LSM6DSOX(SPI(5), cs=Pin(10)) + while (True): + print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.accel())) + print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.gyro())) + print("") + time.sleep_ms(100) -while (True): - print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.accel())) - print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.gyro())) - print("") - time.sleep_ms(100) """ import array diff --git a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index e5a96ad5c..a45e73039 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -27,21 +27,21 @@ The sensor contains an accelerometer / gyroscope / magnetometer Uses the internal FIFO to store up to 16 gyro/accel data, use the iter_accel_gyro generator to access it. -Example usage: +Example usage:: -import time -from lsm9ds1 import LSM9DS1 -from machine import Pin, I2C + import time + from lsm9ds1 import LSM9DS1 + from machine import Pin, I2C -imu = LSM9DS1(I2C(1, scl=Pin(15), sda=Pin(14))) + imu = LSM9DS1(I2C(1, scl=Pin(15), sda=Pin(14))) -while (True): - #for g,a in imu.iter_accel_gyro(): print(g,a) # using fifo - print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.accel())) - print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.gyro())) - print("") - time.sleep_ms(100) + while (True): + #for g,a in imu.iter_accel_gyro(): print(g,a) # using fifo + print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.accel())) + print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.gyro())) + print("") + time.sleep_ms(100) """ import array diff --git a/micropython/drivers/sensor/ds18x20/ds18x20.py b/micropython/drivers/sensor/ds18x20/ds18x20.py index ad2d9f52c..066a05fb9 100644 --- a/micropython/drivers/sensor/ds18x20/ds18x20.py +++ b/micropython/drivers/sensor/ds18x20/ds18x20.py @@ -1,5 +1,8 @@ -# DS18x20 temperature sensor driver for MicroPython. -# MIT license; Copyright (c) 2016 Damien P. George +""" +DS18x20 temperature sensor driver for MicroPython. + +MIT license; Copyright (c) 2016 Damien P. George +""" from micropython import const diff --git a/micropython/drivers/sensor/hs3003/hs3003.py b/micropython/drivers/sensor/hs3003/hs3003.py index 003501649..332df8de7 100644 --- a/micropython/drivers/sensor/hs3003/hs3003.py +++ b/micropython/drivers/sensor/hs3003/hs3003.py @@ -22,21 +22,23 @@ THE SOFTWARE. HS3003 driver for MicroPython. +------------------------------ Example usage: -import time -from hs3003 import HS3003 -from machine import Pin, I2C + import time + from hs3003 import HS3003 + from machine import Pin, I2C -bus = I2C(1, scl=Pin(15), sda=Pin(14)) -hts = HS3003(bus) + bus = I2C(1, scl=Pin(15), sda=Pin(14)) + hts = HS3003(bus) + + while True: + rH = hts.humidity() + temp = hts.temperature() + print ("rH: %.2f%% T: %.2fC" %(rH, temp)) + time.sleep_ms(100) -while True: - rH = hts.humidity() - temp = hts.temperature() - print ("rH: %.2f%% T: %.2fC" %(rH, temp)) - time.sleep_ms(100) """ import struct diff --git a/micropython/drivers/sensor/hts221/hts221.py b/micropython/drivers/sensor/hts221/hts221.py index c6cd51f48..5be268d40 100644 --- a/micropython/drivers/sensor/hts221/hts221.py +++ b/micropython/drivers/sensor/hts221/hts221.py @@ -23,22 +23,24 @@ THE SOFTWARE. HTS221 driver driver for MicroPython. +------------------------------------- + Original source: https://github.com/ControlEverythingCommunity/HTS221/blob/master/Python/HTS221.py Example usage: -import time -import hts221 -from machine import Pin, I2C + import time + import hts221 + from machine import Pin, I2C -bus = I2C(1, scl=Pin(15), sda=Pin(14)) -hts = hts221.HTS221(bus) + bus = I2C(1, scl=Pin(15), sda=Pin(14)) + hts = hts221.HTS221(bus) -while (True): - rH = hts.humidity() - temp = hts.temperature() - print ("rH: %.2f%% T: %.2fC" %(rH, temp)) - time.sleep_ms(100) + while (True): + rH = hts.humidity() + temp = hts.temperature() + print ("rH: %.2f%% T: %.2fC" %(rH, temp)) + time.sleep_ms(100) """ import struct diff --git a/python-stdlib/cmd/cmd.py b/python-stdlib/cmd/cmd.py index 447ea1489..b05fc245d 100644 --- a/python-stdlib/cmd/cmd.py +++ b/python-stdlib/cmd/cmd.py @@ -1,20 +1,21 @@ -"""A generic class to build line-oriented command interpreters. +""" +A generic class to build line-oriented command interpreters. Interpreters constructed with this class obey the following conventions: 1. End of file on input is processed as the command 'EOF'. 2. A command is parsed out of each line by collecting the prefix composed of characters in the identchars member. -3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method +3. A command 'foo' is dispatched to a method 'do_foo()'; the do_ method is passed a single argument consisting of the remainder of the line. 4. Typing an empty line repeats the last command. (Actually, it calls the - method `emptyline', which may be overridden in a subclass.) -5. There is a predefined `help' method. Given an argument `topic', it - calls the command `help_topic'. With no arguments, it lists all topics + method 'emptyline', which may be overridden in a subclass.) +5. There is a predefined 'help' method. Given an argument 'topic', it + calls the command 'help_topic'. With no arguments, it lists all topics with defined help_ functions, broken into up to three topics; documented commands, miscellaneous help topics, and undocumented commands. -6. The command '?' is a synonym for `help'. The command '!' is a synonym - for `shell', if a do_shell method exists. +6. The command '?' is a synonym for 'help'. The command '!' is a synonym + for 'shell', if a do_shell method exists. 7. If completion is enabled, completing commands will be done automatically, and completing of commands args is done by calling complete_foo() with arguments text, line, begidx, endidx. text is string we are matching @@ -23,21 +24,21 @@ indexes of the text being matched, which could be used to provide different completion depending upon which position the argument is in. -The `default' method may be overridden to intercept commands for which there +The 'default' method may be overridden to intercept commands for which there is no do_ method. -The `completedefault' method may be overridden to intercept completions for +The 'completedefault' method may be overridden to intercept completions for commands that have no complete_ method. -The data member `self.ruler' sets the character used to draw separator lines +The data member 'self.ruler' sets the character used to draw separator lines in the help messages. If empty, no ruler line is drawn. It defaults to "=". -If the value of `self.intro' is nonempty when the cmdloop method is called, +If the value of 'self.intro' is nonempty when the cmdloop method is called, it is printed out on interpreter startup. This value may be overridden via an optional argument to the cmdloop() method. -The data members `self.doc_header', `self.misc_header', and -`self.undoc_header' set the headers used for the help function's +The data members 'self.doc_header', 'self.misc_header', and +'self.undoc_header' set the headers used for the help function's listings of documented functions, miscellaneous topics, and undocumented functions respectively. @@ -48,7 +49,7 @@ One of the notable deviations is that since MicroPython strips doc strings, this means that that help by doc string feature doesn't work. -completions have also been stripped out. +Completions have also been stripped out. """ import sys diff --git a/python-stdlib/contextlib/contextlib.py b/python-stdlib/contextlib/contextlib.py index 3e598b4b6..2f6d4a928 100644 --- a/python-stdlib/contextlib/contextlib.py +++ b/python-stdlib/contextlib/contextlib.py @@ -1,10 +1,10 @@ -"""Utilities for with-statement contexts. See PEP 343. +""" +Utilities for with-statement contexts. See PEP 343. Original source code: https://hg.python.org/cpython/file/3.4/Lib/contextlib.py Not implemented: - redirect_stdout; - """ import sys @@ -15,12 +15,12 @@ class closing(object): """Context to automatically close something at the end of a block. - Code like this: + Code like this:: with closing(.open()) as f: - is equivalent to this: + is equivalent to this:: f = .open() try: diff --git a/python-stdlib/heapq/heapq.py b/python-stdlib/heapq/heapq.py index b11853b8d..792497e68 100644 --- a/python-stdlib/heapq/heapq.py +++ b/python-stdlib/heapq/heapq.py @@ -1,4 +1,5 @@ -"""Heap queue algorithm (a.k.a. priority queue). +""" +Heap queue algorithm (a.k.a. priority queue). Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for all k, counting elements from 0. For the sake of comparison, @@ -7,13 +8,13 @@ Usage: -heap = [] # creates an empty heap -heappush(heap, item) # pushes a new item on the heap -item = heappop(heap) # pops the smallest item from the heap -item = heap[0] # smallest item on the heap without popping it -heapify(x) # transforms list into a heap, in-place, in linear time -item = heapreplace(heap, item) # pops and returns smallest item, and adds - # new item; the heap size is unchanged + heap = [] # creates an empty heap + heappush(heap, item) # pushes a new item on the heap + item = heappop(heap) # pops the smallest item from the heap + item = heap[0] # smallest item on the heap without popping it + heapify(x) # transforms list into a heap, in-place, in linear time + item = heapreplace(heap, item) # pops and returns smallest item, and adds + # new item; the heap size is unchanged Our API differs from textbook heap algorithms as follows: @@ -40,7 +41,7 @@ property of a heap is that a[0] is always its smallest element. The strange invariant above is meant to be an efficient memory -representation for a tournament. The numbers below are `k', not a[k]: +representation for a tournament. The numbers below are 'k', not a[k]:: 0 @@ -53,7 +54,7 @@ 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -In the tree above, each cell `k' is topping `2*k+1' and `2*k+2'. In +In the tree above, each cell 'k' is topping '2*k+1' and '2*k+2'. In an usual binary tournament we see in sports, each cell is the winner over the two cells it tops, and we can trace the winner down the tree to see all opponents s/he had. However, in many computer applications @@ -108,7 +109,7 @@ effective! In a word, heaps are useful memory structures to know. I use them in -a few applications, and I think it is good to keep a `heap' module +a few applications, and I think it is good to keep a `heap` module around. :-) -------------------- @@ -377,7 +378,7 @@ def _siftup_max(heap, pos): def merge(*iterables): """Merge multiple sorted inputs into a single sorted output. - Similar to sorted(itertools.chain(*iterables)) but returns a generator, + Similar to `sorted(itertools.chain(*iterables))` but returns a generator, does not pull the data into memory all at once, and assumes that each of the input streams is already sorted (smallest to largest). diff --git a/python-stdlib/textwrap/textwrap.py b/python-stdlib/textwrap/textwrap.py index 4e9f35069..c5771bec6 100644 --- a/python-stdlib/textwrap/textwrap.py +++ b/python-stdlib/textwrap/textwrap.py @@ -1,4 +1,5 @@ -"""Text wrapping and filling. +""" +Text wrapping and filling. """ # Copyright (C) 1999-2001 Gregory P. Ward. @@ -169,7 +170,8 @@ def _split(self, text): return chunks def _fix_sentence_endings(self, chunks): - """_fix_sentence_endings(chunks : [string]) + """ + _fix_sentence_endings(chunks : [string]) Correct for sentence endings buried in 'chunks'. Eg. when the original text contains "... foo.\nBar ...", munge_whitespace() From bfd33d581ae0a02fe51d0c43ad8e2a538e02d830 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 17 Nov 2025 13:30:39 +0100 Subject: [PATCH 15/34] CONTRIBUTING: Add guidelines for module documentation and versioning. Signed-off-by: Jos Verlinde --- CONTRIBUTING.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61a49101e..815d7373c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,6 +87,40 @@ specific conventions and guidelines for micropython-lib: packages](README.md#installing-packages-from-forks) in your Pull Request description. +### Module documentation + +Each package in micropython-lib is encouraged to include documentation in +the form of docstrings in the code itself. The top-level docstring in the main +module or package should provide an overview of the package's functionality, +usage examples, and any important notes or caveats. + +Note that the docstrings should be concise and relevant, even though they will +not be included in the cross-compiled bytecode and will not impact the size of +the module when installed via `mip`. + +When writing docstrings, please follow these guidelines: +* Use triple double quotes (`"""`) for docstrings. +* Multi-line docstrings should place the starting and ending triple quotes on + their own lines. +* Start with a brief summary of the module's purpose. +* Include usage examples where appropriate (indent examplecode blocks with 4 + spaces). +* Use proper indentation for multi-line docstrings to align with the code block. + +### Module versioning + +Each package in micropython-lib should include a `manifest.py` file that +specifies metadata about the package, including its version. The version +management is intentionally manual to ensure package stability and prevent +accidental version bumps. When making changes to a package that affect its +functionality or API, please update the version in `manifest.py`. The +`tools/build.py` script will detect new versions and add them to the index, but +won't increment versions automatically + +> [!NOTE] +> Changes to docstrings or comments that do not change the generated bytecode +> should not change the version. + ### Publishing packages from forks You can easily publish the packages from your micropython-lib From 9ff562640dabca05f07e27835ebad97a0428578c Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 18 Nov 2025 09:04:40 +0000 Subject: [PATCH 16/34] cbor2: Silence missing `__eq__` warning. Signed-off-by: Jos Verlinde --- python-ecosys/cbor2/cbor2/_decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-ecosys/cbor2/cbor2/_decoder.py b/python-ecosys/cbor2/cbor2/_decoder.py index 965dbfd46..0ddfc31d3 100644 --- a/python-ecosys/cbor2/cbor2/_decoder.py +++ b/python-ecosys/cbor2/cbor2/_decoder.py @@ -34,7 +34,7 @@ class CBORDecodeError(Exception): break_marker = object() -class CBORSimpleValue(object): +class CBORSimpleValue(object): # noqa: PLW1641 """ Represents a CBOR "simple value". :param int value: the value (0-255) From 95fd713b8a0331dfa6730a6fa89f461f5f6c1192 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 03:00:18 +0100 Subject: [PATCH 17/34] pyproject.toml: Reorganize ruff lint settings for newer ruff. Changes are: - Reorganize lint settings to new sections. - Align ignore rules with micropython/micropython repo. - Update to python38 syntax to enable `:=` operator. Signed-off-by: Jos Verlinde --- pyproject.toml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83d29405d..a309df1f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,14 @@ [tool.ruff] -exclude = [ +extend-exclude = [ "python-stdlib", "unix-ffi", ] +line-length = 99 +target-version = "py38" # enable use of walrus operator + +[tool.ruff.lint] select = [ - "ASYNC", # flake8-comprehensions + "ASYNC", # flake8-async "C4", # flake8-comprehensions "C90", # McCabe cyclomatic complexity "DTZ", # flake8-datetimez @@ -53,42 +57,40 @@ select = [ # "TRY", # tryceratops # "UP", # pyupgrade ] -ignore = [ +extend-ignore = [ "E722", - "E741", # 'l' is currently widely used + "E741", # 'l' is currently widely used "F401", "F403", "F405", - "E501", # line length, recommended to disable + "E501", # line length, recommended to disable "ISC001", - "ISC003", # micropython does not support implicit concatenation of f-strings - "PIE810", # micropython does not support passing tuples to .startswith or .endswith + "ISC003", # MicroPython does not support implicit concatenation of f-strings + "PIE810", # MicroPython does not support passing tuples to .startswith or .endswith + "PLC0415", # conditional imports are common in MicroPython "PLC1901", - "PLR1704", # sometimes desirable to redefine an argument to save code size + "PLR1704", # sometimes desirable to redefine an argument to save code size "PLR1714", "PLR5501", "PLW0602", "PLW0603", "PLW2901", - "RUF012", - "RUF100", + "RUF012", # mutable default values in class attributes. + "RUF059", # Unpacked variable `foo` is never used + "RUF100", # duplicate noqa directives. "SIM101", - "W191", # tab-indent, redundant when using formatter + "W191", # tab-indent, redundant when using formatter ] -line-length = 99 -target-version = "py37" +mccabe.max-complexity = 61 -[tool.ruff.mccabe] -max-complexity = 61 - -[tool.ruff.pylint] +[tool.ruff.lint.pylint] allow-magic-value-types = ["bytes", "int", "str"] max-args = 14 max-branches = 58 max-returns = 13 max-statements = 166 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "micropython/aiorepl/aiorepl.py" = ["PGH001"] # manifest.py files are evaluated with some global names pre-defined @@ -97,5 +99,3 @@ max-statements = 166 # ble multitests are evaluated with some names pre-defined "micropython/bluetooth/aioble/multitests/*" = ["F821"] - -[tool.ruff.format] From b3125247bfd0fa4d03662c49eb416aa3397a36de Mon Sep 17 00:00:00 2001 From: Marcel Petrick Date: Mon, 17 Nov 2025 13:25:15 +0100 Subject: [PATCH 18/34] all: Correct various typos in comments and docs. Non-functional changes only: - Fixed minor spelling mistakes in comments. - Corrected typos in user-facing strings. - No variables, logic, or functional code was modified. Signed-off-by: Marcel Petrick --- micropython/aiorepl/aiorepl.py | 2 +- .../bluetooth/aioble/examples/l2cap_file_server.py | 2 +- micropython/drivers/display/lcd160cr/lcd160cr.py | 2 +- micropython/drivers/imu/bmi270/bmi270.py | 2 +- micropython/drivers/imu/bmm150/bmm150.py | 2 +- micropython/drivers/imu/lsm6dsox/lsm6dsox.py | 2 +- micropython/drivers/imu/lsm9ds1/lsm9ds1.py | 6 +++--- micropython/lora/lora-sx126x/lora/sx126x.py | 2 +- micropython/lora/lora/lora/modem.py | 2 +- micropython/senml/docs/index.md | 2 +- micropython/senml/docs/senml_record.md | 4 ++-- micropython/senml/examples/custom_record.py | 2 +- micropython/senml/senml/senml_pack.py | 2 +- micropython/senml/senml/senml_record.py | 6 +++--- micropython/ucontextlib/ucontextlib.py | 2 +- micropython/umqtt.simple/README.rst | 2 +- micropython/usb/usb-device-cdc/usb/device/cdc.py | 2 +- micropython/usb/usb-device-midi/usb/device/midi.py | 2 +- .../usb/usb-device-mouse/usb/device/mouse.py | 2 +- micropython/usb/usb-device/usb/device/core.py | 2 +- python-stdlib/datetime/test_datetime.py | 4 ++-- python-stdlib/pathlib/tests/test_pathlib.py | 2 +- python-stdlib/time/README.md | 2 +- python-stdlib/unittest/examples/example_subtest.py | 4 ++-- tools/uncrustify.cfg | 14 +++++++------- unix-ffi/cgi/cgi.py | 2 +- unix-ffi/ucurses/ucurses/__init__.py | 2 +- 27 files changed, 40 insertions(+), 40 deletions(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index fbe513b7c..15026e435 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -74,7 +74,7 @@ async def kbd_intr_task(exec_task, s): except asyncio.CancelledError: pass else: - # Excute code snippet directly. + # Execute code snippet directly. try: try: micropython.kbd_intr(3) diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_server.py b/micropython/bluetooth/aioble/examples/l2cap_file_server.py index 3c3b3b44d..95aad0b38 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_server.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_server.py @@ -1,7 +1,7 @@ # MIT license; Copyright (c) 2021 Jim Mussared # This is a BLE file server, based very loosely on the Object Transfer Service -# specification. It demonstrated transfering data over an L2CAP channel, as +# specification. It demonstrated transferring data over an L2CAP channel, as # well as using notifications and GATT writes on a characteristic. # The server supports downloading and uploading files, as well as querying diff --git a/micropython/drivers/display/lcd160cr/lcd160cr.py b/micropython/drivers/display/lcd160cr/lcd160cr.py index 177c6fea3..1d5da2293 100644 --- a/micropython/drivers/display/lcd160cr/lcd160cr.py +++ b/micropython/drivers/display/lcd160cr/lcd160cr.py @@ -56,7 +56,7 @@ def __init__(self, connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98): pwr(1) sleep_ms(10) # else: - # alread have power + # already have power # lets be optimistic... # set connections diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index c76962f7e..1e4a940c3 100644 --- a/micropython/drivers/imu/bmi270/bmi270.py +++ b/micropython/drivers/imu/bmi270/bmi270.py @@ -504,7 +504,7 @@ def __init__( accel_scale=4, bmm_magnet=None, ): - """Initalizes Gyro and Accelerometer. + """Initializes Gyro and Accelerometer. bus: IMU bus address: I2C address (in I2C mode). cs: SPI CS pin (in SPI mode). diff --git a/micropython/drivers/imu/bmm150/bmm150.py b/micropython/drivers/imu/bmm150/bmm150.py index aea4348b5..a56c6fe78 100644 --- a/micropython/drivers/imu/bmm150/bmm150.py +++ b/micropython/drivers/imu/bmm150/bmm150.py @@ -66,7 +66,7 @@ def __init__( address=_DEFAULT_ADDR, magnet_odr=30, ): - """Initalizes the Magnetometer. + """Initializes the Magnetometer. bus: IMU bus address: I2C address (in I2C mode). cs: SPI CS pin (in SPI mode). diff --git a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py index b932ff006..ca7b77673 100644 --- a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py +++ b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py @@ -89,7 +89,7 @@ def __init__( accel_scale=4, ucf=None, ): - """Initalizes Gyro and Accelerator. + """Initializes Gyro and Accelerator. accel_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz) gyro_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz) gyro_scale: (245dps, 500dps, 1000dps, 2000dps) diff --git a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index a45e73039..5f1bfb6cc 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -81,7 +81,7 @@ def __init__( magnet_odr=80, magnet_scale=4, ): - """Initalizes Gyro, Accelerometer and Magnetometer. + """Initializes Gyro, Accelerometer and Magnetometer. bus: IMU bus address_imu: IMU I2C address. address_magnet: Magnetometer I2C address. @@ -134,14 +134,14 @@ def __init__( mv[5] = 0x2 # ctrl9 - FIFO enabled self.bus.writeto_mem(self.address_imu, _CTRL_REG4_G, mv) - # fifo: use continous mode (overwrite old data if overflow) + # fifo: use continuous mode (overwrite old data if overflow) self.bus.writeto_mem(self.address_imu, _FIFO_CTRL_REG, b"\x00") self.bus.writeto_mem(self.address_imu, _FIFO_CTRL_REG, b"\xc0") # Configure Magnetometer mv[0] = 0x40 | (magnet_odr << 2) # ctrl1: high performance mode mv[1] = _MAGNET_SCALE.index(magnet_scale) << 5 # ctrl2: scale, normal mode, no reset - mv[2] = 0x00 # ctrl3: continous conversion, no low power, I2C + mv[2] = 0x00 # ctrl3: continuous conversion, no low power, I2C mv[3] = 0x08 # ctrl4: high performance z-axis mv[4] = 0x00 # ctr5: no fast read, no block update self.bus.writeto_mem(self.address_magnet, _CTRL_REG1_M, mv[:5]) diff --git a/micropython/lora/lora-sx126x/lora/sx126x.py b/micropython/lora/lora-sx126x/lora/sx126x.py index 7fa4896ae..446489431 100644 --- a/micropython/lora/lora-sx126x/lora/sx126x.py +++ b/micropython/lora/lora-sx126x/lora/sx126x.py @@ -501,7 +501,7 @@ def calibrate_image(self): self._cmd(">BH", _CMD_CALIBRATE_IMAGE, args) - # Can't find anythign in Datasheet about how long image calibration + # Can't find anything in Datasheet about how long image calibration # takes or exactly how it signals completion. Assuming it will be # similar to _CMD_CALIBRATE. self._wait_not_busy(_CALIBRATE_TIMEOUT_US) diff --git a/micropython/lora/lora/lora/modem.py b/micropython/lora/lora/lora/modem.py index 499712acf..73fd4db20 100644 --- a/micropython/lora/lora/lora/modem.py +++ b/micropython/lora/lora/lora/modem.py @@ -47,7 +47,7 @@ def __init__(self, ant_sw): self._bw_hz = 125000 # Reset value self._coding_rate = 5 self._crc_en = True # use packet CRCs - self._implicit_header = False # implict vs explicit header mode + self._implicit_header = False # implicit vs explicit header mode self._preamble_len = 12 self._coding_rate = 5 diff --git a/micropython/senml/docs/index.md b/micropython/senml/docs/index.md index 91ed7fe99..e849f2ca7 100644 --- a/micropython/senml/docs/index.md +++ b/micropython/senml/docs/index.md @@ -1,4 +1,4 @@ -Welcome to the API documet site for the micro-python SenML library. +Welcome to the API document site for the micro-python SenML library. The following api sections are available: diff --git a/micropython/senml/docs/senml_record.md b/micropython/senml/docs/senml_record.md index 6bac549a5..e7c8d0cd0 100644 --- a/micropython/senml/docs/senml_record.md +++ b/micropython/senml/docs/senml_record.md @@ -42,11 +42,11 @@ _parameters:_ - `kwargs:` optional parameters: - value: the value to store in the record - time: the timestamp to use (when was the value measured) - - name: the name of hte record + - name: the name of the record - unit: unit value - sum: sum value - update_time: max time before sensor will provide an updated reading - - callback: a callback function taht will be called when actuator data has been found. Expects no params + - callback: a callback function that will be called when actuator data has been found. Expects no params ### do_actuate diff --git a/micropython/senml/examples/custom_record.py b/micropython/senml/examples/custom_record.py index 1e83ea06b..ece866791 100644 --- a/micropython/senml/examples/custom_record.py +++ b/micropython/senml/examples/custom_record.py @@ -33,7 +33,7 @@ def __init__(self, name, **kwargs): """overriding the init function so we can initiate the 3 senml records that will represent lat,lon, alt""" self._lat = SenmlRecord( "lattitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LATITUDE - ) # create these befor calling base constructor so that all can be init correctly from constructor + ) # create these before calling base constructor so that all can be init correctly from constructor self._lon = SenmlRecord("longitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LONGITUDE) self._alt = SenmlRecord("altitude", unit=SenmlUnits.SENML_UNIT_METER) super(Coordinates, self).__init__( diff --git a/micropython/senml/senml/senml_pack.py b/micropython/senml/senml/senml_pack.py index 5a0554467..cb5f89fa5 100644 --- a/micropython/senml/senml/senml_pack.py +++ b/micropython/senml/senml/senml_pack.py @@ -169,7 +169,7 @@ def from_json(self, data): def _process_incomming_data(self, records, naming_map): """ - generic processor for incomming data (actuators. + generic processor for incoming data (actuators. :param records: the list of raw senml data, parsed from a json or cbor structure :param naming_map: translates cbor to json field names (when needed). :return: None diff --git a/micropython/senml/senml/senml_record.py b/micropython/senml/senml/senml_record.py index ae40f0f70..9cc260f5b 100644 --- a/micropython/senml/senml/senml_record.py +++ b/micropython/senml/senml/senml_record.py @@ -36,11 +36,11 @@ def __init__(self, name, **kwargs): :param kwargs: optional parameters: - value: the value to store in the record - time: the timestamp to use (when was the value measured) - - name: the name of hte record + - name: the name of the record - unit: unit value - sum: sum value - update_time: max time before sensor will provide an updated reading - - callback: a callback function taht will be called when actuator data has been found. Expects no params + - callback: a callback function that will be called when actuator data has been found. Expects no params """ self.__parent = None # using double __ cause it's a field for an internal property self._unit = None # declare and init internal fields @@ -106,7 +106,7 @@ def value(self): @value.setter def value(self, value): - """set the current value. Will not automatically update the time stamp. This has to be done seperatly for more + """set the current value. Will not automatically update the time stamp. This has to be done separately for more finegrained control Note: when the value is a float, you can control rounding in the rendered output by using the function round() while assigning the value. ex: record.value = round(12.2 / 1.5423, 2) diff --git a/micropython/ucontextlib/ucontextlib.py b/micropython/ucontextlib/ucontextlib.py index d259f9b8f..d7d984a95 100644 --- a/micropython/ucontextlib/ucontextlib.py +++ b/micropython/ucontextlib/ucontextlib.py @@ -6,7 +6,7 @@ - redirect_stdout; - ExitStack. - closing - - supress + - suppress """ diff --git a/micropython/umqtt.simple/README.rst b/micropython/umqtt.simple/README.rst index d9d09b970..ee360ad61 100644 --- a/micropython/umqtt.simple/README.rst +++ b/micropython/umqtt.simple/README.rst @@ -47,7 +47,7 @@ Taking into account API traits described above, umqtt pretty closely follows MQTT control operations, and maps them to class methods: * ``connect(...)`` - Connect to a server. Returns True if this connection - uses persisten session stored on a server (this will be always False if + uses persistent session stored on a server (this will be always False if clean_session=True argument is used (default)). * ``disconnect()`` - Disconnect from a server, release resources. * ``ping()`` - Ping server (response is processed automatically by wait_msg()). diff --git a/micropython/usb/usb-device-cdc/usb/device/cdc.py b/micropython/usb/usb-device-cdc/usb/device/cdc.py index 0acea184f..4ec012bc5 100644 --- a/micropython/usb/usb-device-cdc/usb/device/cdc.py +++ b/micropython/usb/usb-device-cdc/usb/device/cdc.py @@ -228,7 +228,7 @@ def desc_cfg(self, desc, itf_num, ep_num, strs): 5, # bFunctionLength _CS_DESC_TYPE, # bDescriptorType _CDC_FUNC_DESC_CALL_MANAGEMENT, # bDescriptorSubtype - 0, # bmCapabilities - XXX no call managment so far + 0, # bmCapabilities - XXX no call management so far itf_num + 1, # bDataInterface - interface 1 ) diff --git a/micropython/usb/usb-device-midi/usb/device/midi.py b/micropython/usb/usb-device-midi/usb/device/midi.py index ecb178ea4..55bfbd08b 100644 --- a/micropython/usb/usb-device-midi/usb/device/midi.py +++ b/micropython/usb/usb-device-midi/usb/device/midi.py @@ -61,7 +61,7 @@ class MIDIInterface(Interface): # Base class to implement a USB MIDI device in Python. # - # To be compliant this also regisers a dummy USB Audio interface, but that + # To be compliant this also registers a dummy USB Audio interface, but that # interface isn't otherwise used. def __init__(self, rxlen=16, txlen=16): diff --git a/micropython/usb/usb-device-mouse/usb/device/mouse.py b/micropython/usb/usb-device-mouse/usb/device/mouse.py index d3dea9c61..61345276b 100644 --- a/micropython/usb/usb-device-mouse/usb/device/mouse.py +++ b/micropython/usb/usb-device-mouse/usb/device/mouse.py @@ -77,7 +77,7 @@ def move_by(self, dx, dy): b'\xA1\x00' # Collection (Physical) b'\x05\x09' # Usage Page (Buttons) b'\x19\x01' # Usage Minimum (01), - b'\x29\x03' # Usage Maximun (03), + b'\x29\x03' # Usage Maximum (03), b'\x15\x00' # Logical Minimum (0), b'\x25\x01' # Logical Maximum (1), b'\x95\x03' # Report Count (3), diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index b0d91d8ff..926c662bd 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -446,7 +446,7 @@ def num_itfs(self): # Return the number of actual USB Interfaces represented by this object # (as set in desc_cfg().) # - # Only needs to be overriden if implementing a Interface class that + # Only needs to be overridden if implementing a Interface class that # represents more than one USB Interface descriptor (i.e. MIDI), or an # Interface Association Descriptor (i.e. USB-CDC). return 1 diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 56411b96e..999bfaca9 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -2091,7 +2091,7 @@ def test_timestamp01(self): def test_timestamp02(self): with LocalTz("Europe/Rome"): - dt = datetime(2010, 3, 28, 2, 30) # doens't exist + dt = datetime(2010, 3, 28, 2, 30) # doesn't exist self.assertEqual(dt.timestamp(), 1269739800.0) def test_timestamp03(self): @@ -2111,7 +2111,7 @@ def test_timestamp05(self): def test_timestamp06(self): with LocalTz("US/Eastern"): - dt = datetime(2020, 3, 8, 2, 30) # doens't exist + dt = datetime(2020, 3, 8, 2, 30) # doesn't exist self.assertEqual(dt.timestamp(), 1583652600.0) def test_timestamp07(self): diff --git a/python-stdlib/pathlib/tests/test_pathlib.py b/python-stdlib/pathlib/tests/test_pathlib.py index e632e1242..31e762eea 100644 --- a/python-stdlib/pathlib/tests/test_pathlib.py +++ b/python-stdlib/pathlib/tests/test_pathlib.py @@ -234,7 +234,7 @@ def test_touch(self): self.assertExists(target) # Technically should be FileExistsError, - # but thats not builtin to micropython + # but that's not builtin to micropython with self.assertRaises(OSError): path.touch(exist_ok=False) diff --git a/python-stdlib/time/README.md b/python-stdlib/time/README.md index f07517305..f3d880ad7 100644 --- a/python-stdlib/time/README.md +++ b/python-stdlib/time/README.md @@ -19,7 +19,7 @@ See [Package management](https://docs.micropython.org/en/latest/reference/packag ## Common uses -`strftime()` is used when using a loggging [Formatter +`strftime()` is used when using a logging [Formatter Object](https://docs.python.org/3/library/logging.html#formatter-objects) that employs [`asctime`](https://docs.python.org/3/library/logging.html#formatter-objects). diff --git a/python-stdlib/unittest/examples/example_subtest.py b/python-stdlib/unittest/examples/example_subtest.py index 558af0b26..40b87d45b 100644 --- a/python-stdlib/unittest/examples/example_subtest.py +++ b/python-stdlib/unittest/examples/example_subtest.py @@ -99,7 +99,7 @@ def test_sorted(self) -> None: self.assertEqual(sorted(test["unsorted"]), test["sorted"]) def test_factorial(self) -> None: - """Test that the factorial fuction correctly calculates factorials + """Test that the factorial function correctly calculates factorials Makes use of `msg` argument in subtest method to clarify which subtests had an error in the results @@ -190,7 +190,7 @@ def test_person(self) -> None: self.assertFalse(bob.has_friend(alice)) # Friendship is not always commutative, so Bob is not implicitly friends with Alice - with self.subTest("Alice and Bob should not both be friends with eachother"): + with self.subTest("Alice and Bob should not both be friends with each other"): with self.assertRaises(AssertionError): self.assertTrue(bob.has_friend(alice) and alice.has_friend(bob)) diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg index 80542b903..7a0ff0d46 100644 --- a/tools/uncrustify.cfg +++ b/tools/uncrustify.cfg @@ -1323,7 +1323,7 @@ indent_using_block = true # true/false # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number -# Whether to indent the statments inside ternary operator. +# Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false # If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. @@ -1779,7 +1779,7 @@ nl_func_call_args_multi_line = false # true/false # different lines. nl_func_call_end_multi_line = false # true/false -# Whether to respect nl_func_call_XXX option incase of closure args. +# Whether to respect nl_func_call_XXX option in case of closure args. nl_func_call_args_multi_line_ignore_closures = false # true/false # Whether to add a newline after '<' of a template parameter list. @@ -2570,7 +2570,7 @@ align_oc_decl_colon = false # true/false # (OC) Whether to not align parameters in an Objectve-C message call if first # colon is not on next line of the message call (the same way Xcode does -# aligment) +# alignment) align_oc_msg_colon_xcode_like = false # true/false # @@ -2916,28 +2916,28 @@ pp_define_at_level = false # true/false pp_ignore_define_body = false # true/false # Whether to indent case statements between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the case statements +# Only applies to the indent of the preprocessor that the case statements # directly inside of. # # Default: true pp_indent_case = true # true/false # Whether to indent whole function definitions between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the function definition +# Only applies to the indent of the preprocessor that the function definition # is directly inside of. # # Default: true pp_indent_func_def = true # true/false # Whether to indent extern C blocks between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the extern block is +# Only applies to the indent of the preprocessor that the extern block is # directly inside of. # # Default: true pp_indent_extern = true # true/false # Whether to indent braces directly inside #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the braces are directly +# Only applies to the indent of the preprocessor that the braces are directly # inside of. # # Default: true diff --git a/unix-ffi/cgi/cgi.py b/unix-ffi/cgi/cgi.py index 550f70713..e8e91bfe4 100644 --- a/unix-ffi/cgi/cgi.py +++ b/unix-ffi/cgi/cgi.py @@ -197,7 +197,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): # parse query string function called from urlparse, -# this is done in order to maintain backward compatiblity. +# this is done in order to maintain backward compatibility. def parse_qs(qs, keep_blank_values=0, strict_parsing=0): diff --git a/unix-ffi/ucurses/ucurses/__init__.py b/unix-ffi/ucurses/ucurses/__init__.py index 688df745c..2e3af32d6 100644 --- a/unix-ffi/ucurses/ucurses/__init__.py +++ b/unix-ffi/ucurses/ucurses/__init__.py @@ -311,7 +311,7 @@ def meta(yes): def mousemask(mask): - # Mouse reporting - X10 compatbility mode + # Mouse reporting - X10 compatibility mode _wr(b"\x1b[?9h") From 7d0a1f86beb7ab035f9cbf60f4b4204603eb150d Mon Sep 17 00:00:00 2001 From: Breno RdV Date: Sat, 15 Nov 2025 02:18:40 -0500 Subject: [PATCH 19/34] lora: Fix import error detection for missing drivers. Signed-off-by: Breno RdV --- micropython/lora/lora/lora/__init__.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/micropython/lora/lora/lora/__init__.py b/micropython/lora/lora/lora/__init__.py index 7f8930b8c..9030c91bd 100644 --- a/micropython/lora/lora/lora/__init__.py +++ b/micropython/lora/lora/lora/__init__.py @@ -5,6 +5,12 @@ ok = False # Flag if at least one modem driver package is installed + +def _can_ignore_error(e): + """Check if ImportError can be ignored due to missing module.""" + return all(x in str(e) for x in ["no module named", "lora"]) + + # Various lora "sub-packages" try: @@ -12,7 +18,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise try: @@ -20,7 +26,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise try: @@ -28,7 +34,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise From 852f2cceff9f35ecf59db303400b8cd8e63dbc1d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 6 Oct 2025 13:10:44 +1100 Subject: [PATCH 20/34] inspect: Support closures/generators/async-funcs in inspect.signature. This is a follow-up to b4565b41eab684722d9fcb2deee322869e47c359 (PR #1043), to support closures, async functions and generators in `inspect.signature`. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 16 +++++++++++++--- python-stdlib/inspect/manifest.py | 2 +- python-stdlib/inspect/test_inspect.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index 9074549bb..f6b7d3c89 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -1,7 +1,10 @@ import sys +# Generator function. _g = lambda: (yield) +# Closure type. +_ct = type((lambda x: (lambda: x))(None)) def getmembers(obj, pred=None): res = [] @@ -111,9 +114,16 @@ def signature(f): elif t is type(setattr): # A three-parameter built-in. num_args = 3 - elif t is type(signature): + elif t is type(signature) or t is type(_g) or t is _ct: # A bytecode function, work out the number of arguments by inspecting the bytecode data. - fun_obj = uctypes.struct(id(f), (uctypes.ARRAY | 0, uctypes.LONG | 4)) + fun_ptr = id(f) + num_closed_over = 0 + if t is _ct: + # A closure, the function is the second word. + clo_ptr = uctypes.struct(fun_ptr, (uctypes.ARRAY | 0, uctypes.LONG | 3)) + fun_ptr = clo_ptr[1] + num_closed_over = clo_ptr[2] + fun_obj = uctypes.struct(fun_ptr, (uctypes.ARRAY | 0, uctypes.LONG | 4)) bytecode = uctypes.bytearray_at(fun_obj[3], 8) # See py/bc.h:MP_BC_PRELUDE_SIG_DECODE_INTO macro. i = 0 @@ -127,7 +137,7 @@ def signature(f): i += 1 A |= (z & 0x4) << n K |= ((z & 0x08) >> 3) << n - num_args = A + K + num_args = A + K - num_closed_over else: raise NotImplementedError("unsupported function type") diff --git a/python-stdlib/inspect/manifest.py b/python-stdlib/inspect/manifest.py index 119237c45..03ede9f0a 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py index f5110de70..31650040b 100644 --- a/python-stdlib/inspect/test_inspect.py +++ b/python-stdlib/inspect/test_inspect.py @@ -11,6 +11,22 @@ def gen(): yield 1 +def make_closure(): + a = 1 + b = 2 + def closure(x): + return a + b + x + return closure + + +def make_gen_closure(): + a = 1 + b = 2 + def gen_closure(x): + yield a + b + x + return gen_closure + + class Class: def meth(self): pass @@ -71,3 +87,6 @@ def test_signature(self): self.assertEqual(len(inspect.signature(lambda x, y: 0).parameters), 2) self.assertEqual(len(inspect.signature(lambda x, y, z: 0).parameters), 3) self.assertEqual(len(inspect.signature(lambda x, y, *, z: 0).parameters), 3) + self.assertEqual(len(inspect.signature(gen).parameters), 0) + self.assertEqual(len(inspect.signature(make_closure()).parameters), 1) + self.assertEqual(len(inspect.signature(make_gen_closure()).parameters), 1) From 9031aa3120f3931ebcd24a689316a84af03527e7 Mon Sep 17 00:00:00 2001 From: Hyx Date: Wed, 30 Jul 2025 21:15:54 +0800 Subject: [PATCH 21/34] usb-device-hid: Fix descriptor protocol config and set correct default. Subclass in HID interface descriptor is a flag of boot protocol support. Set it according to the interface protocol settings. HID devices should come up in non-boot mode according to Device Class Definition for Human Interface Devices (HID) v1.11 Appendix F.5. Set the initial state of interface protocol to report protocol. Signed-off-by: Hyx --- micropython/usb/usb-device-hid/usb/device/hid.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/micropython/usb/usb-device-hid/usb/device/hid.py b/micropython/usb/usb-device-hid/usb/device/hid.py index 1c1c9ac6b..6c3cdc88e 100644 --- a/micropython/usb/usb-device-hid/usb/device/hid.py +++ b/micropython/usb/usb-device-hid/usb/device/hid.py @@ -34,6 +34,8 @@ _INTERFACE_SUBCLASS_NONE = const(0x00) _INTERFACE_SUBCLASS_BOOT = const(0x01) +# These values will only make sense when interface subclass +# is 0x01, which indicates boot protocol support. _INTERFACE_PROTOCOL_NONE = const(0x00) _INTERFACE_PROTOCOL_KEYBOARD = const(0x01) _INTERFACE_PROTOCOL_MOUSE = const(0x02) @@ -131,7 +133,9 @@ def desc_cfg(self, desc, itf_num, ep_num, strs): itf_num, 1, _INTERFACE_CLASS, - _INTERFACE_SUBCLASS_NONE, + _INTERFACE_SUBCLASS_NONE + if self.protocol == _INTERFACE_PROTOCOL_NONE + else _INTERFACE_SUBCLASS_BOOT, self.protocol, len(strs) if self.interface_str else 0, ) @@ -149,7 +153,12 @@ def desc_cfg(self, desc, itf_num, ep_num, strs): desc.endpoint(self._int_ep, "interrupt", 8, 8) self.idle_rate = 0 - self.protocol = 0 + + # This variable is reused to track boot protocol status. + # 0 for boot protocol, 1 for report protocol + # According to Device Class Definition for Human Interface Devices (HID) v1.11 + # Appendix F.5, the device comes up in non-boot mode by default. + self.protocol = 1 def num_eps(self): return 1 From 8a230a6ebc3f44e68d621af346b2d21c592855a7 Mon Sep 17 00:00:00 2001 From: Hyx Date: Wed, 30 Jul 2025 22:39:05 +0800 Subject: [PATCH 22/34] usb-device-hid: Use report protocol after report descriptor requested. So HID device will exit boot protocol mode when entering normal OS. Signed-off-by: Hyx --- micropython/usb/usb-device-hid/manifest.py | 2 +- micropython/usb/usb-device-hid/usb/device/hid.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/micropython/usb/usb-device-hid/manifest.py b/micropython/usb/usb-device-hid/manifest.py index 4520325e3..e844b6f01 100644 --- a/micropython/usb/usb-device-hid/manifest.py +++ b/micropython/usb/usb-device-hid/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.1") +metadata(version="0.1.2") require("usb-device") package("usb") diff --git a/micropython/usb/usb-device-hid/usb/device/hid.py b/micropython/usb/usb-device-hid/usb/device/hid.py index 6c3cdc88e..07664fb02 100644 --- a/micropython/usb/usb-device-hid/usb/device/hid.py +++ b/micropython/usb/usb-device-hid/usb/device/hid.py @@ -207,6 +207,8 @@ def on_interface_control_xfer(self, stage, request): if desc_type == _DESC_HID_TYPE: return self.get_hid_descriptor() if desc_type == _DESC_REPORT_TYPE: + # Reset to report protocol when report descriptor is requested + self.protocol = 1 return self.report_descriptor elif req_type == _REQ_TYPE_CLASS: # HID Spec p50: 7.2 Class-Specific Requests From 0d838c3a866138ab044435688fa9e587009e4c25 Mon Sep 17 00:00:00 2001 From: Pedro Araoz Date: Sun, 24 Aug 2025 17:31:12 -0300 Subject: [PATCH 23/34] umqtt.simple: Add unsubscribe method. Addresses issues #197 and #512. Signed-off-by: Pedro Araoz --- micropython/umqtt.simple/README.rst | 1 + micropython/umqtt.simple/manifest.py | 2 +- micropython/umqtt.simple/umqtt/simple.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/micropython/umqtt.simple/README.rst b/micropython/umqtt.simple/README.rst index ee360ad61..6578a1a89 100644 --- a/micropython/umqtt.simple/README.rst +++ b/micropython/umqtt.simple/README.rst @@ -53,6 +53,7 @@ follows MQTT control operations, and maps them to class methods: * ``ping()`` - Ping server (response is processed automatically by wait_msg()). * ``publish()`` - Publish a message. * ``subscribe()`` - Subscribe to a topic. +* ``unsubscribe()`` - Unsubscribe to a topic. * ``set_callback()`` - Set callback for received subscription messages. * ``set_last_will()`` - Set MQTT "last will" message. Should be called *before* connect(). diff --git a/micropython/umqtt.simple/manifest.py b/micropython/umqtt.simple/manifest.py index 709a27505..ce639bbad 100644 --- a/micropython/umqtt.simple/manifest.py +++ b/micropython/umqtt.simple/manifest.py @@ -1,4 +1,4 @@ -metadata(description="Lightweight MQTT client for MicroPython.", version="1.6.0") +metadata(description="Lightweight MQTT client for MicroPython.", version="1.7.0") # Originally written by Paul Sokolovsky. diff --git a/micropython/umqtt.simple/umqtt/simple.py b/micropython/umqtt.simple/umqtt/simple.py index d9cdffc47..5d52ee305 100644 --- a/micropython/umqtt.simple/umqtt/simple.py +++ b/micropython/umqtt.simple/umqtt/simple.py @@ -175,6 +175,19 @@ def subscribe(self, topic, qos=0): raise MQTTException(resp[3]) return + def unsubscribe(self, topic): + pkt = bytearray(b"\xa2\0\0\0") + self.pid += 1 + struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic), self.pid) + self.sock.write(pkt) + self._send_str(topic) + while 1: + op = self.wait_msg() + if op == 0xB0: + resp = self.sock.read(3) + assert resp[1] == pkt[2] and resp[2] == pkt[3] + return + # Wait for a single incoming MQTT message and process it. # Subscribed messages are delivered to a callback previously # set by .set_callback() method. Other (internal) MQTT From da3909775653dfcf9627c4126c456426ae5773b9 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike <111072251+ThomasFarstrike@users.noreply.github.com> Date: Tue, 11 Nov 2025 23:14:52 +0100 Subject: [PATCH 24/34] aiohttp: Correctly handle WebSocket message fragmentation. Handle WebSocket fragmentation by properly by taking into account the "fin" flag to know if a frame is "final" or whether there will be continuations before it's final. Signed-off-by: Thomas Farstrike <111072251+ThomasFarstrike@users.noreply.github.com> --- python-ecosys/aiohttp/aiohttp/aiohttp_ws.py | 8 ++++++-- python-ecosys/aiohttp/manifest.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py index 53a640fe5..30fa9ab77 100644 --- a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py +++ b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py @@ -166,7 +166,11 @@ async def handshake(self, uri, ssl, req): async def receive(self): while True: - opcode, payload = await self._read_frame() + opcode, payload, final = await self._read_frame() + while not final: + # original opcode must be preserved + _, morepayload, final = await self._read_frame() + payload += morepayload send_opcode, data = self._process_websocket_frame(opcode, payload) if send_opcode: # pragma: no cover await self.send(data, send_opcode) @@ -206,7 +210,7 @@ async def _read_frame(self): payload = await self.reader.readexactly(length) if has_mask: # pragma: no cover payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload)) - return opcode, payload + return opcode, payload, fin class ClientWebSocketResponse: diff --git a/python-ecosys/aiohttp/manifest.py b/python-ecosys/aiohttp/manifest.py index bbf22bb29..cee6f0c10 100644 --- a/python-ecosys/aiohttp/manifest.py +++ b/python-ecosys/aiohttp/manifest.py @@ -1,6 +1,6 @@ metadata( description="HTTP client module for MicroPython asyncio module", - version="0.0.6", + version="0.0.7", pypi="aiohttp", ) From 80a5e925e9a0840cd8fd4b8ab9b986fdfd27d697 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Nov 2025 11:37:46 +1100 Subject: [PATCH 25/34] iperf3: Fix use as a CLI on the unix port of MicroPython. The unix port of MicroPython has pollable objects (not fd's) and supports `ticks_us`/`ticks_diff` natively. Signed-off-by: Damien George --- python-ecosys/iperf3/iperf3.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python-ecosys/iperf3/iperf3.py b/python-ecosys/iperf3/iperf3.py index 363d10d59..169a8ed07 100644 --- a/python-ecosys/iperf3/iperf3.py +++ b/python-ecosys/iperf3/iperf3.py @@ -534,7 +534,7 @@ def main(): client(opt_host, opt_udp, opt_reverse) -if sys.platform == "linux": +if sys.implementation.name != "micropython": def pollable_is_sock(pollable, sock): return sock is not None and pollable[0] == sock.fileno() @@ -544,12 +544,12 @@ def ticks_us(): def ticks_diff(a, b): return a - b - - if __name__ == "__main__": - main() else: def pollable_is_sock(pollable, sock): return pollable[0] == sock from time import ticks_us, ticks_diff + +if sys.platform == "linux" and __name__ == "__main__": + main() From 241e5ffb460098aee2d0c1a755ca7964ba4cbb68 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Nov 2025 11:38:35 +1100 Subject: [PATCH 26/34] iperf3: Factor out data transfer logic to separate function. So it can be reused by the server code. Signed-off-by: Damien George --- python-ecosys/iperf3/iperf3.py | 66 ++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/python-ecosys/iperf3/iperf3.py b/python-ecosys/iperf3/iperf3.py index 169a8ed07..0da460d1f 100644 --- a/python-ecosys/iperf3/iperf3.py +++ b/python-ecosys/iperf3/iperf3.py @@ -198,6 +198,33 @@ def make_cookie(): return cookie +def _transfer(udp, reverse, addr, s_data, buf, udp_last_send, udp_packet_id, udp_interval, stats): + if udp: + if reverse: + recvninto(s_data, buf) + udp_in_sec, udp_in_usec, udp_in_id = struct.unpack_from(">III", buf, 0) + if udp_in_id != udp_packet_id + 1: + stats.add_lost_packets(udp_in_id - (udp_packet_id + 1)) + udp_packet_id = udp_in_id + stats.add_bytes(len(buf)) + else: + t = ticks_us() + if t - udp_last_send > udp_interval: + udp_last_send += udp_interval + udp_packet_id += 1 + struct.pack_into(">III", buf, 0, t // 1000000, t % 1000000, udp_packet_id) + n = s_data.sendto(buf, addr) + stats.add_bytes(n) + else: + if reverse: + recvninto(s_data, buf) + n = len(buf) + else: + n = s_data.send(buf) + stats.add_bytes(n) + return udp_last_send, udp_packet_id + + def server_once(): # Listen for a connection ai = socket.getaddrinfo("0.0.0.0", 5201) @@ -360,6 +387,7 @@ def client(host, udp=False, reverse=False, bandwidth=10 * 1024 * 1024): else: param["tcp"] = True param["len"] = 3000 + udp_interval = None if reverse: param["reverse"] = True @@ -403,33 +431,17 @@ def client(host, udp=False, reverse=False, bandwidth=10 * 1024 * 1024): stats.stop() else: # Send/receiver data - if udp: - if reverse: - recvninto(s_data, buf) - udp_in_sec, udp_in_usec, udp_in_id = struct.unpack_from(">III", buf, 0) - # print(udp_in_sec, udp_in_usec, udp_in_id) - if udp_in_id != udp_packet_id + 1: - stats.add_lost_packets(udp_in_id - (udp_packet_id + 1)) - udp_packet_id = udp_in_id - stats.add_bytes(len(buf)) - else: - # print('UDP send', udp_last_send, t, udp_interval) - if t - udp_last_send > udp_interval: - udp_last_send += udp_interval - udp_packet_id += 1 - struct.pack_into( - ">III", buf, 0, t // 1000000, t % 1000000, udp_packet_id - ) - n = s_data.sendto(buf, ai[-1]) - stats.add_bytes(n) - else: - if reverse: - recvninto(s_data, buf) - n = len(buf) - else: - # print('TCP send', len(buf)) - n = s_data.send(buf) - stats.add_bytes(n) + udp_last_send, udp_packet_id = _transfer( + udp, + reverse, + ai[-1], + s_data, + buf, + udp_last_send, + udp_packet_id, + udp_interval, + stats, + ) elif pollable_is_sock(pollable, s_ctrl): # Receive command From 6ca6f5b337df1b33ef0b02c3710a056cd1428af5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Nov 2025 11:39:09 +1100 Subject: [PATCH 27/34] iperf3: Fix server UDP mode. The main issues were: - incorrect magic response value - should be using sendto instead of send for UDP Signed-off-by: Damien George --- python-ecosys/iperf3/iperf3.py | 29 +++++++++++++++++++++-------- python-ecosys/iperf3/manifest.py | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/python-ecosys/iperf3/iperf3.py b/python-ecosys/iperf3/iperf3.py index 0da460d1f..05d69f774 100644 --- a/python-ecosys/iperf3/iperf3.py +++ b/python-ecosys/iperf3/iperf3.py @@ -260,13 +260,21 @@ def server_once(): s_data, addr = s_listen.accept() print("Accepted connection:", addr) recvn(s_data, COOKIE_SIZE) + udp = False + udp_packet_id = 0 + udp_interval = None + udp_last_send = None elif param.get("udp", False): # Close TCP connection and open UDP "connection" s_listen.close() s_data = socket.socket(ai[0], socket.SOCK_DGRAM) s_data.bind(ai[-1]) data, addr = s_data.recvfrom(4) - s_data.sendto(b"\x12\x34\x56\x78", addr) + s_data.sendto(struct.pack(" Date: Sat, 22 Nov 2025 09:41:43 -0800 Subject: [PATCH 28/34] unix-ffi/socket: Remove ip add/drop membership from socket. Remove the IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP since they are now defined in the builtin socket module, because they vary across systems. Depends on: https://github.com/micropython/micropython/pull/18459 Signed-off-by: Alex Tran --- unix-ffi/socket/manifest.py | 2 +- unix-ffi/socket/socket.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/unix-ffi/socket/manifest.py b/unix-ffi/socket/manifest.py index a0a384d37..9cb582a84 100644 --- a/unix-ffi/socket/manifest.py +++ b/unix-ffi/socket/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.5.2") +metadata(version="0.5.3") # Originally written by Paul Sokolovsky. diff --git a/unix-ffi/socket/socket.py b/unix-ffi/socket/socket.py index 1b47f9840..16d661b0c 100644 --- a/unix-ffi/socket/socket.py +++ b/unix-ffi/socket/socket.py @@ -4,8 +4,6 @@ _GLOBAL_DEFAULT_TIMEOUT = 30 IPPROTO_IP = 0 -IP_ADD_MEMBERSHIP = 35 -IP_DROP_MEMBERSHIP = 36 INADDR_ANY = 0 error = OSError From 1390722783013fb9bbef87d680cf7338a1421ae5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Dec 2025 13:07:34 +1100 Subject: [PATCH 29/34] tarfile: Add basic unittest for tarfile.TarFile. Tests TarFile iteration and extracting file information. Signed-off-by: Damien George --- python-stdlib/tarfile/test.tar | Bin 0 -> 20480 bytes python-stdlib/tarfile/test_tarfile.py | 37 ++++++++++++++++++++++++++ tools/ci.sh | 1 + 3 files changed, 38 insertions(+) create mode 100644 python-stdlib/tarfile/test.tar create mode 100644 python-stdlib/tarfile/test_tarfile.py diff --git a/python-stdlib/tarfile/test.tar b/python-stdlib/tarfile/test.tar new file mode 100644 index 0000000000000000000000000000000000000000..7fa0604d7f389d2647224b46dc320b920ca2b400 GIT binary patch literal 20480 zcmeI%O=`n15QgDd_Y^)st6%#(PVAI!n&LL}`W-oK=%ykDOW5=^1TO^A@C=`1lwQg| zMxQnW(UK}GmIAyOQa9CxQ{3I#i_vhcw`EL#R&#SI8dLaMnK<;#w;bnR%U3$uYrw6*e{y6{5 z?fh@Uod3?W(!|n*rTPE)`JeiKEbN^6fA9X^`cC(MYl8Nl@4v#0(dm!u|K*W4yh6(V zP`JKD`JeZH&3_>Oh1)VuKal?#%$r?9FFzjjSG2x-{$0@VpYJ~(&3yg`=gEK3rhHF6 zR#+t8|1^5L|MMYU@Bdo9|0;AwWMu!Z;=I*GP8AAUmVf=9K=1!M$NpcTHF6^VHJDig z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** S5I_I{1Q0*~0R#|OEbs-AIgsK2 literal 0 HcmV?d00001 diff --git a/python-stdlib/tarfile/test_tarfile.py b/python-stdlib/tarfile/test_tarfile.py new file mode 100644 index 000000000..d3eb34318 --- /dev/null +++ b/python-stdlib/tarfile/test_tarfile.py @@ -0,0 +1,37 @@ +import tarfile +import unittest + + +test_tar_contents = ( + ("a", "file", 2), + ("b", "file", 2), + ("dir/", "dir", 0), + ("dir/c", "file", 2), + ("dir/d", "file", 2), + ("tar.tar", "file", 10240), +) + +test_sub_tar_contents = ( + ("e", "file", 2), + ("f", "file", 2), +) + + +class TestTarFile(unittest.TestCase): + def check_contents(self, expected, tf): + for i, file in enumerate(tf): + name, type, size = expected[i] + self.assertEqual(file.name, name) + self.assertEqual(file.type, type) + self.assertEqual(file.size, size) + + def test_iter(self): + tf = tarfile.TarFile("test.tar") + for _ in range(6): + self.assertIsInstance(next(tf), tarfile.TarInfo) + with self.assertRaises(StopIteration): + next(tf) + + def test_contents(self): + tf = tarfile.TarFile("test.tar") + self.check_contents(test_tar_contents, tf) diff --git a/tools/ci.sh b/tools/ci.sh index 6689e8aa4..abe83b563 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -90,6 +90,7 @@ function ci_package_tests_run { python-stdlib/pathlib \ python-stdlib/quopri \ python-stdlib/shutil \ + python-stdlib/tarfile \ python-stdlib/tempfile \ python-stdlib/time \ python-stdlib/unittest/tests \ From abf316c536eb1495207123f5772c5617753c31bb Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Dec 2025 13:08:13 +1100 Subject: [PATCH 30/34] tarfile: Fix FileSection.skip to not rely on extended readinto args. Commit 2ca1527321d7e6b65bcab45d304be82d65f3c4f4 optimized `FileSection.skip()` for memory use. But that introduced a dependency on the MicroPython-extension to stream read methods for an additional argument specifying a maximum read size. This optimization meant that all file-like objects passed into TarFile must support the extended 2-argument `readinto` form. This is problematic for at least two use cases: 1. Nested tar files, because `FileSetion` itself doesn't support 2-argument `readinto`. 2. Using `mpremote mount` and reading a tar file from the remote mount, which also doesn't support 2-argument `readinto`. Instead of requiring all file-like objects to implement this extended form of `readinto`, this commit changes `FileSection.skip()` so that it doesn't use this form. A test is added for this case which fails without the fix here. Signed-off-by: Damien George --- python-stdlib/tarfile/manifest.py | 2 +- python-stdlib/tarfile/tarfile/__init__.py | 9 ++++++--- python-stdlib/tarfile/test_tarfile.py | 8 ++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/python-stdlib/tarfile/manifest.py b/python-stdlib/tarfile/manifest.py index 9940bb051..c379837c7 100644 --- a/python-stdlib/tarfile/manifest.py +++ b/python-stdlib/tarfile/manifest.py @@ -1,4 +1,4 @@ -metadata(description="Read-only implementation of Python's tarfile.", version="0.4.1") +metadata(description="Read-only implementation of Python's tarfile.", version="0.4.2") # Originally written by Paul Sokolovsky. diff --git a/python-stdlib/tarfile/tarfile/__init__.py b/python-stdlib/tarfile/tarfile/__init__.py index 4bb95af30..00b271137 100644 --- a/python-stdlib/tarfile/tarfile/__init__.py +++ b/python-stdlib/tarfile/tarfile/__init__.py @@ -55,9 +55,12 @@ def skip(self): if sz: buf = bytearray(16) while sz: - s = min(sz, 16) - self.f.readinto(buf, s) - sz -= s + if sz >= 16: + self.f.readinto(buf) + sz -= 16 + else: + self.f.read(sz) + sz = 0 class TarInfo: diff --git a/python-stdlib/tarfile/test_tarfile.py b/python-stdlib/tarfile/test_tarfile.py index d3eb34318..7acd61af7 100644 --- a/python-stdlib/tarfile/test_tarfile.py +++ b/python-stdlib/tarfile/test_tarfile.py @@ -35,3 +35,11 @@ def test_iter(self): def test_contents(self): tf = tarfile.TarFile("test.tar") self.check_contents(test_tar_contents, tf) + + def test_nested_tar(self): + tf = tarfile.TarFile("test.tar") + for file in tf: + if file.name == "tar.tar": + subf = tf.extractfile(file) + subtf = tarfile.TarFile(fileobj=subf) + self.check_contents(test_sub_tar_contents, subtf) From 7473d1d6e07fa0df5ba260628b13b04b20ebdc1b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 18 Nov 2025 06:49:41 +0100 Subject: [PATCH 31/34] argparse: Add support for custom argument types. This commit adds support for optional custom argument type validation to argparse.ArgumentParser, allowing for shorter argument validation code for both simple builtins and complex types. For example, assuming that a particular command line argument must be an integer, using "parser.add_argument('-a', type=int)" will make sure that any value passed to that argument that cannot be converted into an integer will trigger an argument validation error. Signed-off-by: Alessandro Gatti --- python-stdlib/argparse/argparse.py | 33 ++++++++++++++----- python-stdlib/argparse/manifest.py | 2 +- python-stdlib/argparse/test_argparse.py | 44 +++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/python-stdlib/argparse/argparse.py b/python-stdlib/argparse/argparse.py index 5c92887f9..828c6e0bc 100644 --- a/python-stdlib/argparse/argparse.py +++ b/python-stdlib/argparse/argparse.py @@ -10,8 +10,12 @@ class _ArgError(BaseException): pass +class ArgumentTypeError(BaseException): + pass + + class _Arg: - def __init__(self, names, dest, action, nargs, const, default, help): + def __init__(self, names, dest, action, nargs, const, default, help, type): self.names = names self.dest = dest self.action = action @@ -19,20 +23,26 @@ def __init__(self, names, dest, action, nargs, const, default, help): self.const = const self.default = default self.help = help + self.type = type + + def _apply(self, optname, arg): + if self.type: + try: + return self.type(arg) + except (ArgumentTypeError, TypeError, ValueError) as e: + raise _ArgError("invalid value for %s: %s (%s)" % (optname, arg, str(e))) + return arg def parse(self, optname, args): # parse args for this arg if self.action == "store": if self.nargs is None: if args: - return args.pop(0) + return self._apply(optname, args.pop(0)) else: raise _ArgError("expecting value for %s" % optname) elif self.nargs == "?": - if args: - return args.pop(0) - else: - return self.default + return self._apply(optname, args.pop(0) if args else self.default) else: if self.nargs == "*": n = -1 @@ -52,7 +62,7 @@ def parse(self, optname, args): else: break else: - ret.append(args.pop(0)) + ret.append(self._apply(optname, args.pop(0))) n -= 1 if n > 0: raise _ArgError("expecting value for %s" % optname) @@ -103,6 +113,10 @@ def add_argument(self, *args, **kwargs): dest = args[0] if not args: args = [dest] + arg_type = kwargs.get("type", None) + if arg_type is not None: + if not callable(arg_type): + raise ValueError("type is not callable") list.append( _Arg( args, @@ -112,6 +126,7 @@ def add_argument(self, *args, **kwargs): const, default, kwargs.get("help", ""), + arg_type, ) ) @@ -176,7 +191,9 @@ def _parse_args(self, args, return_unknown): arg_vals = [] for opt in self.opt: arg_dest.append(opt.dest) - arg_vals.append(opt.default) + arg_vals.append( + opt._apply(opt.dest, opt.default) if isinstance(opt.default, str) else opt.default + ) # deal with unknown arguments, if needed unknown = [] diff --git a/python-stdlib/argparse/manifest.py b/python-stdlib/argparse/manifest.py index 02bf1a22c..fcfe50a35 100644 --- a/python-stdlib/argparse/manifest.py +++ b/python-stdlib/argparse/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.4.0") +metadata(version="0.5.0") # Originally written by Damien George. diff --git a/python-stdlib/argparse/test_argparse.py b/python-stdlib/argparse/test_argparse.py index d86e53211..7548d4a92 100644 --- a/python-stdlib/argparse/test_argparse.py +++ b/python-stdlib/argparse/test_argparse.py @@ -66,3 +66,47 @@ args, rest = parser.parse_known_args(["a", "b", "c", "-b", "2", "--x", "5", "1"]) assert args.a == ["a", "b"] and args.b == "2" assert rest == ["c", "--x", "5", "1"] + + +class CustomArgType: + def __init__(self, add): + self.add = add + + def __call__(self, value): + return int(value) + self.add + + +parser = argparse.ArgumentParser() +parser.add_argument("-a", type=int) +args = parser.parse_args(["-a", "123"]) +assert args.a == 123 +parser.add_argument("-b", type=str) +args = parser.parse_args(["-b", "string"]) +assert args.b == "string" +parser.add_argument("-c", type=CustomArgType(1)) +args = parser.parse_args(["-c", "123"]) +assert args.c == 124 +try: + parser.add_argument("-d", type=()) + assert False +except ValueError as e: + assert "not callable" in str(e) +parser.add_argument("-d", type=int, nargs="+") +args = parser.parse_args(["-d", "123", "124", "125"]) +assert args.d == [123, 124, 125] +parser.add_argument("-e", type=CustomArgType(1), nargs="+") +args = parser.parse_args(["-e", "123", "124", "125"]) +assert args.e == [124, 125, 126] +parser.add_argument("-f", type=CustomArgType(1), nargs="?") +args = parser.parse_args(["-f", "123"]) +assert args.f == 124 +parser.add_argument("-g", type=CustomArgType(1), default=1) +parser.add_argument("-i", type=CustomArgType(1), default="1") +args = parser.parse_args([]) +assert args.g == 1 +assert args.i == 2 +parser.add_argument("-j", type=CustomArgType(1), default=1) +args = parser.parse_args(["-j", "3"]) +assert args.g == 1 +assert args.i == 2 +assert args.j == 4 From 6b2945cd08178c8ac1cea7f9f8774cbe09e5d0db Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 18 Nov 2025 17:21:26 +0000 Subject: [PATCH 32/34] pyproject.toml: Add codespell configuration, CI and precommit. Add the codespell configuration based on the micropython/micropython repo. Configuration added: - pyproject.toml: codespell configuration with ignore-words stored in tools/ignore_words.txt and enabling of codespell ignore blocks. - .github/workflows/codespell.yml: GitHub action runs on Push and PR. - .pre-commit-config.yaml: runs codespell on commit. Signed-off-by: Jos Verlinde --- .github/workflows/codespell.yml | 13 +++++++++++++ .pre-commit-config.yaml | 8 ++++++++ pyproject.toml | 10 ++++++++++ tools/ignore_words.txt | 12 ++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 .github/workflows/codespell.yml create mode 100644 tools/ignore_words.txt diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 000000000..688134b42 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,13 @@ +name: Check spelling with codespell + +on: [push, pull_request] + +jobs: + codespell: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + # codespell version should be kept in sync with .pre-commit-config.yml + - run: pip install --user codespell==2.4.1 tomli + - run: codespell + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05f5d3df0..b12b351c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,3 +13,11 @@ repos: hooks: - id: ruff id: ruff-format + + - repo: https://github.com/codespell-project/codespell + # version should be kept in sync with .github/workflows/codespell.yml & micropython repo + rev: v2.4.1 + hooks: + - id: codespell + additional_dependencies: + - tomli diff --git a/pyproject.toml b/pyproject.toml index a309df1f6..736fd3a62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,13 @@ +[tool.codespell] +count = "" +ignore-regex = '\b[A-Z]{3}\b' +ignore-multiline-regex = "# codespell:ignore-begin *\n.*# codespell:ignore-end *\n" +ignore-words = "tools/ignore_words.txt" +quiet-level = 3 +skip = """ +./.git,\ +""" + [tool.ruff] extend-exclude = [ "python-stdlib", diff --git a/tools/ignore_words.txt b/tools/ignore_words.txt new file mode 100644 index 000000000..5286cbed5 --- /dev/null +++ b/tools/ignore_words.txt @@ -0,0 +1,12 @@ +# non-words to be ignored by codespell +# shared with micropython/micropython +asend +ure + +# Specific to micropython/micropython-lib +bu +curch +ist +clen +shttp +ody From c266d3023e133434a367496b064438c8e78639f4 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 18 Nov 2025 17:38:59 +0000 Subject: [PATCH 33/34] all: Fix spelling and typos in comments and docstring. Spelling preferences based on codespell configuration. Signed-off-by: Jos Verlinde --- micropython/bluetooth/aioble/aioble/central.py | 2 +- micropython/bluetooth/aioble/aioble/client.py | 2 +- micropython/espflash/example.py | 2 +- micropython/lora/tests/test_time_on_air.py | 2 +- micropython/senml/examples/custom_record.py | 2 +- micropython/senml/senml/senml_record.py | 4 ++-- micropython/usb/usb-device/usb/device/core.py | 2 +- python-ecosys/requests/requests/__init__.py | 6 +++--- python-stdlib/fnmatch/test_fnmatch.py | 4 ++-- python-stdlib/unittest/tests/test_assertions.py | 2 +- unix-ffi/email.charset/email/charset.py | 4 ++-- unix-ffi/email.parser/email/parser.py | 4 ++-- unix-ffi/html.entities/html/entities.py | 5 ++++- unix-ffi/html.parser/html/parser.py | 2 +- unix-ffi/http.client/http/client.py | 6 +++--- 15 files changed, 26 insertions(+), 23 deletions(-) diff --git a/micropython/bluetooth/aioble/aioble/central.py b/micropython/bluetooth/aioble/aioble/central.py index 131b1e0db..15ef8aa12 100644 --- a/micropython/bluetooth/aioble/aioble/central.py +++ b/micropython/bluetooth/aioble/aioble/central.py @@ -119,7 +119,7 @@ async def _connect( _connecting.add(device) # Event will be set in the connected IRQ, and then later - # re-used to notify disconnection. + # reused to notify disconnection. connection._event = connection._event or asyncio.ThreadSafeFlag() try: diff --git a/micropython/bluetooth/aioble/aioble/client.py b/micropython/bluetooth/aioble/aioble/client.py index 859c6e937..a3b0efb6e 100644 --- a/micropython/bluetooth/aioble/aioble/client.py +++ b/micropython/bluetooth/aioble/aioble/client.py @@ -244,7 +244,7 @@ async def read(self, timeout_ms=1000): self._register_with_connection() # This will be set by the done IRQ. self._read_status = None - # This will be set by the result and done IRQs. Re-use if possible. + # This will be set by the result and done IRQs. Reuse if possible. self._read_event = self._read_event or asyncio.ThreadSafeFlag() # Issue the read. diff --git a/micropython/espflash/example.py b/micropython/espflash/example.py index 76e8eb84e..ea4a8af95 100644 --- a/micropython/espflash/example.py +++ b/micropython/espflash/example.py @@ -13,7 +13,7 @@ esp = espflash.ESPFlash(reset, gpio0, uart) # Enter bootloader download mode, at 115200 esp.bootloader() - # Can now chage to higher/lower baudrate + # Can now change to higher/lower baudrate esp.set_baudrate(921600) # Must call this first before any flash functions. esp.flash_attach() diff --git a/micropython/lora/tests/test_time_on_air.py b/micropython/lora/tests/test_time_on_air.py index 56fa1ad81..7a26e5954 100644 --- a/micropython/lora/tests/test_time_on_air.py +++ b/micropython/lora/tests/test_time_on_air.py @@ -5,7 +5,7 @@ # # ## What is this? # -# Host tests for the BaseModem.get_time_on_air_us() function. Theses against +# Host tests for the BaseModem.get_time_on_air_us() function. These test against # dummy test values produced by the Semtech "SX1261 LoRa Calculator" software, # as downloaded from # https://lora-developers.semtech.com/documentation/product-documents/ diff --git a/micropython/senml/examples/custom_record.py b/micropython/senml/examples/custom_record.py index ece866791..90f1ddfdb 100644 --- a/micropython/senml/examples/custom_record.py +++ b/micropython/senml/examples/custom_record.py @@ -32,7 +32,7 @@ class Coordinates(SenmlRecord): def __init__(self, name, **kwargs): """overriding the init function so we can initiate the 3 senml records that will represent lat,lon, alt""" self._lat = SenmlRecord( - "lattitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LATITUDE + "latitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LATITUDE ) # create these before calling base constructor so that all can be init correctly from constructor self._lon = SenmlRecord("longitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LONGITUDE) self._alt = SenmlRecord("altitude", unit=SenmlUnits.SENML_UNIT_METER) diff --git a/micropython/senml/senml/senml_record.py b/micropython/senml/senml/senml_record.py index 9cc260f5b..282f3e5e3 100644 --- a/micropython/senml/senml/senml_record.py +++ b/micropython/senml/senml/senml_record.py @@ -188,7 +188,7 @@ def _build_rec_dict(self, naming_map, appendTo): elif isinstance(self._value, bytearray): if ( naming_map["vd"] == "vd" - ): # neeed to make a distinction between json (needs base64) and cbor (needs binary) + ): # need to make a distinction between json (needs base64) and cbor (needs binary) result[naming_map["vd"]] = binascii.b2a_base64(self._value, newline=False).decode( "utf8" ) @@ -216,7 +216,7 @@ def _build_rec_dict(self, naming_map, appendTo): def _from_raw(self, raw, naming_map): """ - extracts te data from the raw record. Used during parsing of incoming data. + extracts the data from the raw record. Used during parsing of incoming data. :param raw: a raw senml record which still contains the original field names :param naming_map: used to map cbor names to json field names :return: diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index 926c662bd..20cda94ca 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -611,7 +611,7 @@ def stall(self, ep_addr, *args): # argument to set or clear. # # Generally endpoint STALL is handled automatically, but there are some - # device classes that need to explicitly stall or unstall an endpoint + # device classes that need to explicitly stall or un-stall an endpoint # under certain conditions. if not self._open or ep_addr not in self._eps: raise RuntimeError diff --git a/python-ecosys/requests/requests/__init__.py b/python-ecosys/requests/requests/__init__.py index 4ca7489a4..68b4b18cb 100644 --- a/python-ecosys/requests/requests/__init__.py +++ b/python-ecosys/requests/requests/__init__.py @@ -56,9 +56,9 @@ def request( import binascii username, password = auth - formated = b"{}:{}".format(username, password) - formated = str(binascii.b2a_base64(formated)[:-1], "ascii") - headers["Authorization"] = "Basic {}".format(formated) + formatted = b"{}:{}".format(username, password) + formatted = str(binascii.b2a_base64(formatted)[:-1], "ascii") + headers["Authorization"] = "Basic {}".format(formatted) try: proto, dummy, host, path = url.split("/", 3) diff --git a/python-stdlib/fnmatch/test_fnmatch.py b/python-stdlib/fnmatch/test_fnmatch.py index 97ef8fff7..d7f543336 100644 --- a/python-stdlib/fnmatch/test_fnmatch.py +++ b/python-stdlib/fnmatch/test_fnmatch.py @@ -58,8 +58,8 @@ def test_fnmatchcase(self): @unittest.skip("unsupported on MicroPython") def test_bytes(self): - self.check_match(b"test", b"te*") - self.check_match(b"test\xff", b"te*\xff") + self.check_match(b"test", b"te*") # codespell:ignore + self.check_match(b"test\xff", b"te*\xff") # codespell:ignore self.check_match(b"foo\nbar", b"foo*") diff --git a/python-stdlib/unittest/tests/test_assertions.py b/python-stdlib/unittest/tests/test_assertions.py index b191220e6..3b9904086 100644 --- a/python-stdlib/unittest/tests/test_assertions.py +++ b/python-stdlib/unittest/tests/test_assertions.py @@ -92,7 +92,7 @@ def testFalse(self): with self.assertRaises(AssertionError): self.assertFalse(True) - def testIn(self): + def testIn(self): # codespell:ignore self.assertIn("t", "cat") with self.assertRaises(AssertionError): self.assertIn("x", "cat") diff --git a/unix-ffi/email.charset/email/charset.py b/unix-ffi/email.charset/email/charset.py index 304cc8fef..9278766c2 100644 --- a/unix-ffi/email.charset/email/charset.py +++ b/unix-ffi/email.charset/email/charset.py @@ -222,11 +222,11 @@ def __init__(self, input_charset=DEFAULT_CHARSET): # We can try to guess which encoding and conversion to use by the # charset_map dictionary. Try that first, but let the user override # it. - henc, benc, conv = CHARSETS.get(self.input_charset, (SHORTEST, BASE64, None)) + henc, benc, conv = CHARSETS.get(self.input_charset, (SHORTEST, BASE64, None)) # codespell:ignore if not conv: conv = self.input_charset # Set the attributes, allowing the arguments to override the default. - self.header_encoding = henc + self.header_encoding = henc # codespell:ignore self.body_encoding = benc self.output_charset = ALIASES.get(conv, conv) # Now set the codecs. If one isn't defined for input_charset, diff --git a/unix-ffi/email.parser/email/parser.py b/unix-ffi/email.parser/email/parser.py index 760adeff5..7053d0734 100644 --- a/unix-ffi/email.parser/email/parser.py +++ b/unix-ffi/email.parser/email/parser.py @@ -23,7 +23,7 @@ def __init__(self, _class=Message, policy=compat32): textual representation of the message. The string must be formatted as a block of RFC 2822 headers and header - continuation lines, optionally preceeded by a `Unix-from' header. The + continuation lines, optionally preceded by a `Unix-from' header. The header block is terminated either by the end of the string or by a blank line. @@ -85,7 +85,7 @@ def __init__(self, *args, **kw): textual representation of the message. The input must be formatted as a block of RFC 2822 headers and header - continuation lines, optionally preceeded by a `Unix-from' header. The + continuation lines, optionally preceded by a `Unix-from' header. The header block is terminated either by the end of the input or by a blank line. diff --git a/unix-ffi/html.entities/html/entities.py b/unix-ffi/html.entities/html/entities.py index 223af74aa..b5f8717c8 100644 --- a/unix-ffi/html.entities/html/entities.py +++ b/unix-ffi/html.entities/html/entities.py @@ -1,6 +1,7 @@ """HTML character entity references.""" # maps the HTML entity name to the Unicode codepoint +# codespell:ignore-begin name2codepoint = { "AElig": 0x00C6, # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 "Aacute": 0x00C1, # latin capital letter A with acute, U+00C1 ISOlat1 @@ -255,9 +256,10 @@ "zwj": 0x200D, # zero width joiner, U+200D NEW RFC 2070 "zwnj": 0x200C, # zero width non-joiner, U+200C NEW RFC 2070 } - +# codespell:ignore-end # maps the HTML5 named character references to the equivalent Unicode character(s) +# codespell:ignore-begin html5 = { "Aacute": "\xc1", "aacute": "\xe1", @@ -2491,6 +2493,7 @@ "zwj;": "\u200d", "zwnj;": "\u200c", } +# codespell:ignore-end # maps the Unicode codepoint to the HTML entity name codepoint2name = {} diff --git a/unix-ffi/html.parser/html/parser.py b/unix-ffi/html.parser/html/parser.py index 74b39d49a..571b42a34 100644 --- a/unix-ffi/html.parser/html/parser.py +++ b/unix-ffi/html.parser/html/parser.py @@ -445,7 +445,7 @@ def parse_endtag(self, i): tagname = namematch.group().lower() # consume and ignore other stuff between the name and the > # Note: this is not 100% correct, since we might have things like - # , but looking for > after tha name should cover + # , but looking for > after the name should cover # most of the cases and is much simpler gtpos = rawdata.find(">", namematch.end()) self.handle_endtag(tagname) diff --git a/unix-ffi/http.client/http/client.py b/unix-ffi/http.client/http/client.py index 856848283..3556f1ca8 100644 --- a/unix-ffi/http.client/http/client.py +++ b/unix-ffi/http.client/http/client.py @@ -1038,10 +1038,10 @@ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): self.putheader("Accept-Encoding", "identity") # we can accept "chunked" Transfer-Encodings, but no others - # NOTE: no TE header implies *only* "chunked" + # NOTE: no 'TE' header implies *only* "chunked" # self.putheader('TE', 'chunked') - # if TE is supplied in the header, then it must appear in a + # if 'TE' is supplied in the header, then it must appear in a # Connection header. # self.putheader('Connection', 'TE') @@ -1093,7 +1093,7 @@ def _set_content_length(self, body): thelen = None try: thelen = str(len(body)) - except TypeError as te: + except TypeError as te: # codespell:ignore # If this is a file-like object, try to # fstat its file descriptor try: From 6ae440a8a144233e6e703f6759b7e7a0afaa37a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 8 Dec 2025 11:17:46 +1100 Subject: [PATCH 34/34] aioble/examples: Change variable name to _ADV_INTERVAL_US. Following 28136d837ab936324d46f2cbe50e686475f5c38c Signed-off-by: Damien George --- micropython/bluetooth/aioble/examples/l2cap_file_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_server.py b/micropython/bluetooth/aioble/examples/l2cap_file_server.py index 95aad0b38..04afc12e5 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_server.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_server.py @@ -33,7 +33,7 @@ _CONTROL_CHARACTERISTIC_UUID = bluetooth.UUID("0492fcec-7194-11eb-9439-0242ac130003") # How frequently to send advertising beacons. -_ADV_INTERVAL_MS = 250_000 +_ADV_INTERVAL_US = 250_000 _COMMAND_SEND = const(0) @@ -162,7 +162,7 @@ async def peripheral_task(): while True: print("Waiting for connection") connection = await aioble.advertise( - _ADV_INTERVAL_MS, + _ADV_INTERVAL_US, name="mpy-file", services=[_FILE_SERVICE_UUID], )