diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 71c4131f0..b347e34ee 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -6,6 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: pip install --user ruff==0.1.2 + # Version should be kept in sync with .pre-commit_config.yaml & also micropython + - run: pip install --user ruff==0.11.6 - run: ruff check --output-format=github . - run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 335c1c2fc..05f5d3df0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,8 @@ repos: verbose: true stages: [commit-msg] - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.1.2 + # Version should be kept in sync with .github/workflows/ruff.yml & also micropython + rev: v0.11.6 hooks: - id: ruff id: ruff-format 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 diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index 8f45dfac0..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) @@ -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 @@ -132,7 +134,7 @@ async def task(g=None, prompt="--> "): continue if curs: # move cursor to end of the line - sys.stdout.write("\x1B[{}C".format(curs)) + sys.stdout.write("\x1b[{}C".format(curs)) curs = 0 sys.stdout.write("\n") if cmd: @@ -153,15 +155,15 @@ async def task(g=None, prompt="--> "): if curs: cmd = "".join((cmd[: -curs - 1], cmd[-curs:])) sys.stdout.write( - "\x08\x1B[K" + "\x08\x1b[K" ) # move cursor back, erase to end of line sys.stdout.write(cmd[-curs:]) # redraw line - sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location + sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location else: cmd = cmd[:-1] sys.stdout.write("\x08 \x08") elif c == CHAR_CTRL_A: - await raw_repl(s, g) + raw_repl(sys.stdin, g) break elif c == CHAR_CTRL_B: continue @@ -207,21 +209,21 @@ async def task(g=None, prompt="--> "): elif key == "[D": # left if curs < len(cmd) - 1: curs += 1 - sys.stdout.write("\x1B") + sys.stdout.write("\x1b") sys.stdout.write(key) elif key == "[C": # right if curs: curs -= 1 - sys.stdout.write("\x1B") + sys.stdout.write("\x1b") sys.stdout.write(key) elif key == "[H": # home pcurs = curs curs = len(cmd) - sys.stdout.write("\x1B[{}D".format(curs - pcurs)) # move cursor left + sys.stdout.write("\x1b[{}D".format(curs - pcurs)) # move cursor left elif key == "[F": # end pcurs = curs curs = 0 - sys.stdout.write("\x1B[{}C".format(pcurs)) # move cursor right + sys.stdout.write("\x1b[{}C".format(pcurs)) # move cursor right else: # sys.stdout.write("\\x") # sys.stdout.write(hex(c)) @@ -231,7 +233,7 @@ async def task(g=None, prompt="--> "): # inserting into middle of line cmd = "".join((cmd[:-curs], b, cmd[-curs:])) sys.stdout.write(cmd[-curs - 1 :]) # redraw line to end - sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location + sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location else: sys.stdout.write(b) cmd += b @@ -239,7 +241,7 @@ async def task(g=None, prompt="--> "): micropython.kbd_intr(3) -async def raw_paste(s, g, window=512): +def raw_paste(s, window=512): sys.stdout.write("R\x01") # supported sys.stdout.write(bytearray([window & 0xFF, window >> 8, 0x01]).decode()) eof = False @@ -248,7 +250,7 @@ async def raw_paste(s, g, window=512): file = b"" while not eof: for idx in range(window): - b = await s.read(1) + b = s.read(1) c = ord(b) if c == CHAR_CTRL_C or c == CHAR_CTRL_D: # end of file @@ -267,7 +269,12 @@ async def raw_paste(s, g, window=512): return file -async def raw_repl(s: asyncio.StreamReader, g: dict): +def raw_repl(s, g: dict): + """ + This function is blocking to prevent other + async tasks from writing to the stdio stream and + breaking the raw repl session. + """ heading = "raw REPL; CTRL-B to exit\n" line = "" sys.stdout.write(heading) @@ -276,7 +283,7 @@ async def raw_repl(s: asyncio.StreamReader, g: dict): line = "" sys.stdout.write(">") while True: - b = await s.read(1) + b = s.read(1) c = ord(b) if c == CHAR_CTRL_A: rline = line @@ -284,7 +291,7 @@ async def raw_repl(s: asyncio.StreamReader, g: dict): if len(rline) == 2 and ord(rline[0]) == CHAR_CTRL_E: if rline[1] == "A": - line = await raw_paste(s, g) + line = raw_paste(s) break else: # reset raw REPL diff --git a/micropython/aiorepl/manifest.py b/micropython/aiorepl/manifest.py index 0fcc21849..83802e1c0 100644 --- a/micropython/aiorepl/manifest.py +++ b/micropython/aiorepl/manifest.py @@ -1,5 +1,5 @@ metadata( - version="0.2.0", + version="0.2.2", description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.", ) diff --git a/micropython/bluetooth/aioble-l2cap/manifest.py b/micropython/bluetooth/aioble-l2cap/manifest.py index 9150ad547..34a96344e 100644 --- a/micropython/bluetooth/aioble-l2cap/manifest.py +++ b/micropython/bluetooth/aioble-l2cap/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") require("aioble-core") 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) diff --git a/micropython/bluetooth/aioble/aioble/l2cap.py b/micropython/bluetooth/aioble/aioble/l2cap.py index e2d3bd9d4..397b7ceb6 100644 --- a/micropython/bluetooth/aioble/aioble/l2cap.py +++ b/micropython/bluetooth/aioble/aioble/l2cap.py @@ -133,7 +133,6 @@ def available(self): # Waits until the channel is free and then sends buf. # If the buffer is larger than the MTU it will be sent in chunks. async def send(self, buf, timeout_ms=None, chunk_size=None): - self._assert_connected() offset = 0 chunk_size = min(self.our_mtu * 2, self.peer_mtu, chunk_size or self.peer_mtu) mv = memoryview(buf) @@ -141,6 +140,7 @@ async def send(self, buf, timeout_ms=None, chunk_size=None): if self._stalled: await self.flush(timeout_ms) # l2cap_send returns True if you can send immediately. + self._assert_connected() self._stalled = not ble.l2cap_send( self._connection._conn_handle, self._cid, diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_client.py b/micropython/bluetooth/aioble/examples/l2cap_file_client.py index 9dce349a7..0817ca162 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_client.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_client.py @@ -88,7 +88,7 @@ async def download(self, path, dest): await self._command(_COMMAND_SEND, path.encode()) - with open(dest, "wb") as f: # noqa: ASYNC101 + with open(dest, "wb") as f: # noqa: ASYNC230 total = 0 buf = bytearray(self._channel.our_mtu) mv = memoryview(buf) diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_server.py b/micropython/bluetooth/aioble/examples/l2cap_file_server.py index fb806effc..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 @@ -83,7 +83,7 @@ async def l2cap_task(connection): if send_file: print("Sending:", send_file) - with open(send_file, "rb") as f: # noqa: ASYNC101 + with open(send_file, "rb") as f: # noqa: ASYNC230 buf = bytearray(channel.peer_mtu) mv = memoryview(buf) while n := f.readinto(buf): 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, diff --git a/micropython/bluetooth/aioble/manifest.py b/micropython/bluetooth/aioble/manifest.py index 832200570..1cf06c4d8 100644 --- a/micropython/bluetooth/aioble/manifest.py +++ b/micropython/bluetooth/aioble/manifest.py @@ -3,7 +3,7 @@ # code. This allows (for development purposes) all the files to live in the # one directory. -metadata(version="0.6.0") +metadata(version="0.6.1") # Default installation gives you everything. Install the individual # components (or a combination of them) if you want a more minimal install. diff --git a/micropython/drivers/codec/wm8960/wm8960.py b/micropython/drivers/codec/wm8960/wm8960.py index dc0dd655d..313649f36 100644 --- a/micropython/drivers/codec/wm8960/wm8960.py +++ b/micropython/drivers/codec/wm8960/wm8960.py @@ -331,8 +331,7 @@ def __init__( sysclk = 11289600 else: sysclk = 12288000 - if sysclk < sample_rate * 256: - sysclk = sample_rate * 256 + sysclk = max(sysclk, sample_rate * 256) if mclk_freq is None: mclk_freq = sysclk else: # sysclk_source == SYSCLK_MCLK @@ -691,10 +690,8 @@ def alc_mode(self, channel, mode=ALC_MODE): def alc_gain(self, target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78): def limit(value, minval, maxval): value = int(value) - if value < minval: - value = minval - if value > maxval: - value = maxval + value = max(value, minval) + value = min(value, maxval) return value target = limit((16 + (target * 2) // 3), 0, 15) @@ -718,8 +715,7 @@ def logb(value, limit): while value > 1: value >>= 1 lb += 1 - if lb > limit: - lb = limit + lb = min(lb, limit) return lb attack = logb(attack / 6, 7) diff --git a/micropython/drivers/display/lcd160cr/lcd160cr.py b/micropython/drivers/display/lcd160cr/lcd160cr.py index 42b5e215b..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 @@ -189,10 +189,8 @@ def clip_line(c, w, h): c[3] = h - 1 else: if c[0] == c[2]: - if c[1] < 0: - c[1] = 0 - if c[3] < 0: - c[3] = 0 + c[1] = max(c[1], 0) + c[3] = max(c[3], 0) else: if c[3] < c[1]: c[0], c[2] = c[2], c[0] diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index 64f819ec2..1e4a940c3 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) @@ -502,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 a4845c961..a56c6fe78 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 @@ -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 ca1397c66..ca7b77673 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 @@ -88,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 e3d46429d..5f1bfb6cc 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -27,22 +27,23 @@ 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 from micropython import const @@ -80,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. @@ -133,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/drivers/radio/nrf24l01/manifest.py b/micropython/drivers/radio/nrf24l01/manifest.py index 474d422f9..24276ee4b 100644 --- a/micropython/drivers/radio/nrf24l01/manifest.py +++ b/micropython/drivers/radio/nrf24l01/manifest.py @@ -1,3 +1,3 @@ -metadata(description="nrf24l01 2.4GHz radio driver.", version="0.1.0") +metadata(description="nrf24l01 2.4GHz radio driver.", version="0.2.0") module("nrf24l01.py", opt=3) diff --git a/micropython/drivers/radio/nrf24l01/nrf24l01.py b/micropython/drivers/radio/nrf24l01/nrf24l01.py index 76d55312f..9fbadcd60 100644 --- a/micropython/drivers/radio/nrf24l01/nrf24l01.py +++ b/micropython/drivers/radio/nrf24l01/nrf24l01.py @@ -1,5 +1,4 @@ -"""NRF24L01 driver for MicroPython -""" +"""NRF24L01 driver for MicroPython""" from micropython import const import utime @@ -130,6 +129,13 @@ def reg_write(self, reg, value): self.cs(1) return ret + def read_status(self): + self.cs(0) + # STATUS register is always shifted during command transmit + self.spi.readinto(self.buf, NOP) + self.cs(1) + return self.buf[0] + def flush_rx(self): self.cs(0) self.spi.readinto(self.buf, FLUSH_RX) @@ -220,6 +226,13 @@ def send(self, buf, timeout=500): result = None while result is None and utime.ticks_diff(utime.ticks_ms(), start) < timeout: result = self.send_done() # 1 == success, 2 == fail + + if result is None: + # timed out, cancel sending and power down the module + self.flush_tx() + self.reg_write(CONFIG, self.reg_read(CONFIG) & ~PWR_UP) + raise OSError("timed out") + if result == 2: raise OSError("send failed") @@ -227,7 +240,7 @@ def send(self, buf, timeout=500): def send_start(self, buf): # power up self.reg_write(CONFIG, (self.reg_read(CONFIG) | PWR_UP) & ~PRIM_RX) - utime.sleep_us(150) + utime.sleep_us(1500) # needs to be 1.5ms # send the data self.cs(0) self.spi.readinto(self.buf, W_TX_PAYLOAD) @@ -243,7 +256,8 @@ def send_start(self, buf): # returns None if send still in progress, 1 for success, 2 for fail def send_done(self): - if not (self.reg_read(STATUS) & (TX_DS | MAX_RT)): + status = self.read_status() + if not (status & (TX_DS | MAX_RT)): return None # tx not finished # either finished or failed: get and clear status flags, power down 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 fec52a738..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 @@ -52,7 +54,7 @@ def __init__(self, i2c, data_rate=1, address=0x5F): # Set configuration register # Humidity and temperature average configuration - self.bus.writeto_mem(self.slv_addr, 0x10, b"\x1B") + self.bus.writeto_mem(self.slv_addr, 0x10, b"\x1b") # Set control register # PD | BDU | ODR diff --git a/micropython/drivers/sensor/lps22h/lps22h.py b/micropython/drivers/sensor/lps22h/lps22h.py index 1e7f4ec3e..7dec72528 100644 --- a/micropython/drivers/sensor/lps22h/lps22h.py +++ b/micropython/drivers/sensor/lps22h/lps22h.py @@ -37,6 +37,7 @@ print("Pressure: %.2f hPa Temperature: %.2f C"%(lps.pressure(), lps.temperature())) time.sleep_ms(10) """ + import machine from micropython import const 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 diff --git a/micropython/espflash/espflash.py b/micropython/espflash/espflash.py index 74988777a..fbf4e1f7e 100644 --- a/micropython/espflash/espflash.py +++ b/micropython/espflash/espflash.py @@ -113,22 +113,22 @@ def _poll_reg(self, addr, flag, retry=10, delay=0.050): raise Exception(f"Register poll timeout. Addr: 0x{addr:02X} Flag: 0x{flag:02X}.") def _write_slip(self, pkt): - pkt = pkt.replace(b"\xDB", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc") - self.uart.write(b"\xC0" + pkt + b"\xC0") + pkt = pkt.replace(b"\xdb", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc") + self.uart.write(b"\xc0" + pkt + b"\xc0") self._log(pkt) def _read_slip(self): pkt = None # Find the packet start. - if self.uart.read(1) == b"\xC0": + if self.uart.read(1) == b"\xc0": pkt = bytearray() while True: b = self.uart.read(1) - if b is None or b == b"\xC0": + if b is None or b == b"\xc0": break pkt += b - pkt = pkt.replace(b"\xDB\xDD", b"\xDB").replace(b"\xDB\xDC", b"\xC0") - self._log(b"\xC0" + pkt + b"\xC0", False) + pkt = pkt.replace(b"\xdb\xdd", b"\xdb").replace(b"\xdb\xdc", b"\xc0") + self._log(b"\xc0" + pkt + b"\xc0", False) return pkt def _strerror(self, err): @@ -230,7 +230,7 @@ def flash_read_size(self): raise Exception(f"Unexpected flash size bits: 0x{flash_bits:02X}.") flash_size = 2**flash_bits - print(f"Flash size {flash_size/1024/1024} MBytes") + print(f"Flash size {flash_size / 1024 / 1024} MBytes") return flash_size def flash_attach(self): @@ -265,7 +265,7 @@ def flash_write_file(self, path, blksize=0x1000): self.md5sum.update(buf) # The last data block should be padded to the block size with 0xFF bytes. if len(buf) < blksize: - buf += b"\xFF" * (blksize - len(buf)) + buf += b"\xff" * (blksize - len(buf)) checksum = self._checksum(buf) if seq % erase_blocks == 0: # print(f"Erasing {seq} -> {seq+erase_blocks}...") diff --git a/micropython/lora/examples/reliable_delivery/sender.py b/micropython/lora/examples/reliable_delivery/sender.py index 2fba0d4d7..957e9d824 100644 --- a/micropython/lora/examples/reliable_delivery/sender.py +++ b/micropython/lora/examples/reliable_delivery/sender.py @@ -149,7 +149,7 @@ def send(self, sensor_data, adjust_output_power=True): delta = time.ticks_diff(maybe_ack.ticks_ms, sent_at) print( f"ACKed with RSSI {rssi}, {delta}ms after sent " - + f"(skew {delta-ACK_DELAY_MS-ack_packet_ms}ms)" + + f"(skew {delta - ACK_DELAY_MS - ack_packet_ms}ms)" ) if adjust_output_power: diff --git a/micropython/lora/examples/reliable_delivery/sender_async.py b/micropython/lora/examples/reliable_delivery/sender_async.py index a27420f6b..4c14d6f11 100644 --- a/micropython/lora/examples/reliable_delivery/sender_async.py +++ b/micropython/lora/examples/reliable_delivery/sender_async.py @@ -141,7 +141,7 @@ async def send(self, sensor_data, adjust_output_power=True): delta = time.ticks_diff(maybe_ack.ticks_ms, sent_at) print( f"ACKed with RSSI {rssi}, {delta}ms after sent " - + f"(skew {delta-ACK_DELAY_MS-ack_packet_ms}ms)" + + f"(skew {delta - ACK_DELAY_MS - ack_packet_ms}ms)" ) if adjust_output_power: diff --git a/micropython/lora/lora-sx126x/lora/sx126x.py b/micropython/lora/lora-sx126x/lora/sx126x.py index 77052c97c..446489431 100644 --- a/micropython/lora/lora-sx126x/lora/sx126x.py +++ b/micropython/lora/lora-sx126x/lora/sx126x.py @@ -363,11 +363,11 @@ def configure(self, lora_cfg): if "preamble_len" in lora_cfg: self._preamble_len = lora_cfg["preamble_len"] - self._invert_iq = ( + self._invert_iq = [ lora_cfg.get("invert_iq_rx", self._invert_iq[0]), lora_cfg.get("invert_iq_tx", self._invert_iq[1]), self._invert_iq[2], - ) + ] if "freq_khz" in lora_cfg: self._rf_freq_hz = int(lora_cfg["freq_khz"] * 1000) @@ -449,7 +449,7 @@ def configure(self, lora_cfg): def _invert_workaround(self, enable): # Apply workaround for DS 15.4 Optimizing the Inverted IQ Operation if self._invert_iq[2] != enable: - val = self._read_read(_REG_IQ_POLARITY_SETUP) + val = self._reg_read(_REG_IQ_POLARITY_SETUP) val = (val & ~4) | _flag(4, enable) self._reg_write(_REG_IQ_POLARITY_SETUP, val) self._invert_iq[2] = enable @@ -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) @@ -596,8 +596,9 @@ def _read_packet(self, rx_packet, flags): pkt_status = self._cmd("B", _CMD_GET_PACKET_STATUS, n_read=4) rx_packet.ticks_ms = ticks_ms - rx_packet.snr = pkt_status[2] # SNR, units: dB *4 - rx_packet.rssi = 0 - pkt_status[1] // 2 # RSSI, units: dBm + # SNR units are dB * 4 (signed) + rx_packet.rssi, rx_packet.snr = struct.unpack("xBbx", pkt_status) + rx_packet.rssi //= -2 # RSSI, units: dBm rx_packet.crc_error = (flags & _IRQ_CRC_ERR) != 0 return rx_packet diff --git a/micropython/lora/lora-sx126x/manifest.py b/micropython/lora/lora-sx126x/manifest.py index 785a975aa..76fa91d8d 100644 --- a/micropython/lora/lora-sx126x/manifest.py +++ b/micropython/lora/lora-sx126x/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.3") +metadata(version="0.1.5") require("lora") package("lora") 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 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/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:") 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/actuator.py b/micropython/senml/examples/actuator.py index 8e254349d..2fac474cd 100644 --- a/micropython/senml/examples/actuator.py +++ b/micropython/senml/examples/actuator.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * diff --git a/micropython/senml/examples/base.py b/micropython/senml/examples/base.py index 426cbbd0e..6a49cfdd2 100644 --- a/micropython/senml/examples/base.py +++ b/micropython/senml/examples/base.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic.py b/micropython/senml/examples/basic.py index 18a3a9a06..3f3ed6150 100644 --- a/micropython/senml/examples/basic.py +++ b/micropython/senml/examples/basic.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic2.py b/micropython/senml/examples/basic2.py index c2ea153bd..ca53b4a6e 100644 --- a/micropython/senml/examples/basic2.py +++ b/micropython/senml/examples/basic2.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic_cbor.py b/micropython/senml/examples/basic_cbor.py index 804a886fc..b9d9d620b 100644 --- a/micropython/senml/examples/basic_cbor.py +++ b/micropython/senml/examples/basic_cbor.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time import cbor2 diff --git a/micropython/senml/examples/custom_record.py b/micropython/senml/examples/custom_record.py index e68d05f5b..ece866791 100644 --- a/micropython/senml/examples/custom_record.py +++ b/micropython/senml/examples/custom_record.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time @@ -34,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/examples/gateway.py b/micropython/senml/examples/gateway.py index d28e4cffc..e1827ff2d 100644 --- a/micropython/senml/examples/gateway.py +++ b/micropython/senml/examples/gateway.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/gateway_actuators.py b/micropython/senml/examples/gateway_actuators.py index add5ed24c..a7e5b378c 100644 --- a/micropython/senml/examples/gateway_actuators.py +++ b/micropython/senml/examples/gateway_actuators.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * diff --git a/micropython/senml/examples/supported_data_types.py b/micropython/senml/examples/supported_data_types.py index 3149f49d2..94976bb66 100644 --- a/micropython/senml/examples/supported_data_types.py +++ b/micropython/senml/examples/supported_data_types.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/senml/__init__.py b/micropython/senml/senml/__init__.py index 93cbd7700..908375fdb 100644 --- a/micropython/senml/senml/__init__.py +++ b/micropython/senml/senml/__init__.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from .senml_base import SenmlBase from .senml_pack import SenmlPack from .senml_record import SenmlRecord diff --git a/micropython/senml/senml/senml_pack.py b/micropython/senml/senml/senml_pack.py index 4e106fd3e..cb5f89fa5 100644 --- a/micropython/senml/senml/senml_pack.py +++ b/micropython/senml/senml/senml_pack.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml.senml_record import SenmlRecord from senml.senml_base import SenmlBase import json @@ -170,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 9dfe22873..9cc260f5b 100644 --- a/micropython/senml/senml/senml_record.py +++ b/micropython/senml/senml/senml_record.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import binascii from senml.senml_base import SenmlBase @@ -37,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 @@ -107,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/umqtt.simple/manifest.py b/micropython/umqtt.simple/manifest.py index 45f9edfbd..709a27505 100644 --- a/micropython/umqtt.simple/manifest.py +++ b/micropython/umqtt.simple/manifest.py @@ -1,5 +1,7 @@ -metadata(description="Lightweight MQTT client for MicroPython.", version="1.5.0") +metadata(description="Lightweight MQTT client for MicroPython.", version="1.6.0") # Originally written by Paul Sokolovsky. +require("ssl") + package("umqtt") diff --git a/micropython/umqtt.simple/umqtt/simple.py b/micropython/umqtt.simple/umqtt/simple.py index 2a5b91655..d9cdffc47 100644 --- a/micropython/umqtt.simple/umqtt/simple.py +++ b/micropython/umqtt.simple/umqtt/simple.py @@ -17,6 +17,7 @@ def __init__( password=None, keepalive=0, ssl=None, + ssl_params={}, ): if port == 0: port = 8883 if ssl else 1883 @@ -25,6 +26,7 @@ def __init__( self.server = server self.port = port self.ssl = ssl + self.ssl_params = ssl_params self.pid = 0 self.cb = None self.user = user @@ -65,7 +67,12 @@ def connect(self, clean_session=True, timeout=None): self.sock.settimeout(timeout) addr = socket.getaddrinfo(self.server, self.port)[0][-1] self.sock.connect(addr) - if self.ssl: + if self.ssl is True: + # Legacy support for ssl=True and ssl_params arguments. + import ssl + + self.sock = ssl.wrap_socket(self.sock, **self.ssl_params) + elif self.ssl: self.sock = self.ssl.wrap_socket(self.sock, server_hostname=self.server) premsg = bytearray(b"\x10\0\0\0\0\0") msg = bytearray(b"\x04MQTT\x04\x02\0\0") diff --git a/micropython/urllib.urequest/manifest.py b/micropython/urllib.urequest/manifest.py index 2790208a7..033022711 100644 --- a/micropython/urllib.urequest/manifest.py +++ b/micropython/urllib.urequest/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.7.0") +metadata(version="0.8.0") # Originally written by Paul Sokolovsky. diff --git a/micropython/urllib.urequest/urllib/urequest.py b/micropython/urllib.urequest/urllib/urequest.py index f83cbaa94..a9622ea89 100644 --- a/micropython/urllib.urequest/urllib/urequest.py +++ b/micropython/urllib.urequest/urllib/urequest.py @@ -1,7 +1,7 @@ import socket -def urlopen(url, data=None, method="GET"): +def urlopen(url, data=None, method="GET", headers={}): if data is not None and method == "GET": method = "POST" try: @@ -40,6 +40,12 @@ def urlopen(url, data=None, method="GET"): s.write(host) s.write(b"\r\n") + for k in headers: + s.write(k) + s.write(b": ") + s.write(headers[k]) + s.write(b"\r\n") + if data: s.write(b"Content-Length: ") s.write(str(len(data))) diff --git a/micropython/usb/usb-device-cdc/manifest.py b/micropython/usb/usb-device-cdc/manifest.py index 4520325e3..e844b6f01 100644 --- a/micropython/usb/usb-device-cdc/manifest.py +++ b/micropython/usb/usb-device-cdc/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-cdc/usb/device/cdc.py b/micropython/usb/usb-device-cdc/usb/device/cdc.py index 28bfb0657..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 ) @@ -350,10 +350,9 @@ def _rd_cb(self, ep, res, num_bytes): ### def write(self, buf): - # use a memoryview to track how much of 'buf' we've written so far - # (unfortunately, this means a 1 block allocation for each write, but it's otherwise allocation free.) start = time.ticks_ms() - mv = memoryview(buf) + mv = buf + while True: # Keep pushing buf into _wb into it's all gone nbytes = self._wb.write(mv) @@ -362,6 +361,10 @@ def write(self, buf): if nbytes == len(mv): return len(buf) # Success + # if buf couldn't be fully written on the first attempt + # convert it to a memoryview to track partial writes + if mv is buf: + mv = memoryview(buf) mv = mv[nbytes:] # check for timeout 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 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/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..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 @@ -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. diff --git a/micropython/utop/README.md b/micropython/utop/README.md new file mode 100644 index 000000000..7b07e785d --- /dev/null +++ b/micropython/utop/README.md @@ -0,0 +1,12 @@ +# utop + +Provides a top-like live overview of the running system. + +On the `esp32` port this depends on the `esp32.idf_task_info()` function, which +can be enabled by adding the following lines to the board `sdkconfig`: + +```ini +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y +``` diff --git a/micropython/utop/manifest.py b/micropython/utop/manifest.py new file mode 100644 index 000000000..ebba07270 --- /dev/null +++ b/micropython/utop/manifest.py @@ -0,0 +1,6 @@ +metadata( + version="0.1.0", + description="Provides a top-like live overview of the running system.", +) + +module("utop.py") diff --git a/micropython/utop/utop.py b/micropython/utop/utop.py new file mode 100644 index 000000000..799ff4212 --- /dev/null +++ b/micropython/utop/utop.py @@ -0,0 +1,109 @@ +import micropython +import time + +try: + import esp32 + import _thread +except ImportError: + esp32 = None + + +def top(update_interval_ms=1000, timeout_ms=None, thread_names={}): + time_start = time.ticks_ms() + previous_total_runtime = None + previous_task_runtimes = {} + previous_line_count = 0 + esp32_task_state_names = dict( + enumerate(("running", "ready", "blocked", "suspended", "deleted", "invalid")) + ) + + while timeout_ms is None or abs(time.ticks_diff(time.ticks_ms(), time_start)) < timeout_ms: + if previous_line_count > 0: + print("\x1b[{}A".format(previous_line_count), end="") + line_count = 0 + + if esp32 is not None: + if not hasattr(esp32, "idf_task_info"): + print( + "INFO: esp32.idf_task_info() is not available, cannot list active tasks.\x1b[K" + ) + line_count += 1 + else: + print(" CPU% CORE PRIORITY STATE STACKWATERMARK NAME\x1b[K") + line_count += 1 + + total_runtime, tasks = esp32.idf_task_info() + current_thread_id = _thread.get_ident() + tasks.sort(key=lambda t: t[0]) + for ( + task_id, + task_name, + task_state, + task_priority, + task_runtime, + task_stackhighwatermark, + task_coreid, + ) in tasks: + task_runtime_percentage = "-" + if ( + total_runtime != previous_total_runtime + and task_id in previous_task_runtimes + ): + task_runtime_percentage = "{:.2f}%".format( + 100 + * ((task_runtime - previous_task_runtimes[task_id]) & (2**32 - 1)) + / ((total_runtime - previous_total_runtime) & (2**32 - 1)) + ) + print( + "{:>7} {:>4} {:>8d} {:<9} {:<14d} {}{}\x1b[K".format( + task_runtime_percentage, + "-" if task_coreid is None else task_coreid, + task_priority, + esp32_task_state_names.get(task_state, "unknown"), + task_stackhighwatermark, + thread_names.get(task_id, task_name), + " (*)" if task_id == current_thread_id else "", + ) + ) + line_count += 1 + + previous_task_runtimes[task_id] = task_runtime + previous_total_runtime = total_runtime + else: + print("INFO: Platform does not support listing active tasks.\x1b[K") + line_count += 1 + + print("\x1b[K") + line_count += 1 + print("MicroPython ", end="") + micropython.mem_info() + line_count += 3 + + if esp32 is not None: + print("\x1b[K") + line_count += 1 + for name, cap in (("data", esp32.HEAP_DATA), ("exec", esp32.HEAP_EXEC)): + heaps = esp32.idf_heap_info(cap) + print( + "IDF heap ({}): {} regions, {} total, {} free, {} largest contiguous, {} min free watermark\x1b[K".format( + name, + len(heaps), + sum((h[0] for h in heaps)), + sum((h[1] for h in heaps)), + max((h[2] for h in heaps)), + sum((h[3] for h in heaps)), + ) + ) + line_count += 1 + + if previous_line_count > line_count: + for _ in range(previous_line_count - line_count): + print("\x1b[K") + print("\x1b[{}A".format(previous_line_count - line_count), end="") + + previous_line_count = line_count + + try: + time.sleep_ms(update_interval_ms) + except KeyboardInterrupt: + break diff --git a/pyproject.toml b/pyproject.toml index 4776ddfe9..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,41 +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", - "PLR1701", + "PLR1704", # sometimes desirable to redefine an argument to save code size "PLR1714", "PLR5501", "PLW0602", "PLW0603", "PLW2901", - "RUF012", - "RUF100", - "W191", # tab-indent, redundant when using formatter + "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 ] -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 @@ -96,5 +99,3 @@ max-statements = 166 # ble multitests are evaluated with some names pre-defined "micropython/bluetooth/aioble/multitests/*" = ["F821"] - -[tool.ruff.format] diff --git a/python-ecosys/aiohttp/aiohttp/__init__.py b/python-ecosys/aiohttp/aiohttp/__init__.py index 1565163c4..1e6d89d05 100644 --- a/python-ecosys/aiohttp/aiohttp/__init__.py +++ b/python-ecosys/aiohttp/aiohttp/__init__.py @@ -18,8 +18,14 @@ class ClientResponse: def __init__(self, reader): self.content = reader + def _get_header(self, keyname, default): + for k in self.headers: + if k.lower() == keyname: + return self.headers[k] + return default + def _decode(self, data): - c_encoding = self.headers.get("Content-Encoding") + c_encoding = self._get_header("content-encoding", None) if c_encoding in ("gzip", "deflate", "gzip,deflate"): try: import deflate @@ -36,13 +42,15 @@ 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.headers.get("Content-Length", -1)))).decode(encoding) + return (await self.read(int(self._get_header("content-length", -1)))).decode(encoding) async def json(self): - return _json.loads(await self.read(int(self.headers.get("Content-Length", -1)))) + return _json.loads(await self.read(int(self._get_header("content-length", -1)))) def __repr__(self): return "" % (self.status, self.headers) @@ -60,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) @@ -263,7 +271,7 @@ def ws_connect(self, url, ssl=None): return _WSRequestContextManager(self, self._ws_connect(url, ssl=ssl)) async def _ws_connect(self, url, ssl=None): - ws_client = WebSocketClient(None) + ws_client = WebSocketClient(self._base_headers.copy()) await ws_client.connect(url, ssl=ssl, handshake_request=self.request_raw) self._reader = ws_client.reader return ClientWebSocketResponse(ws_client) diff --git a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py index 07d833730..53a640fe5 100644 --- a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py +++ b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py @@ -136,7 +136,7 @@ def _encode_websocket_frame(cls, opcode, payload): return frame + payload async def handshake(self, uri, ssl, req): - headers = {} + headers = self.params _http_proto = "http" if uri.protocol != "wss" else "https" url = f"{_http_proto}://{uri.hostname}:{uri.port}{uri.path or '/'}" key = binascii.b2a_base64(bytes(random.getrandbits(8) for _ in range(16)))[:-1] @@ -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 748970e5b..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.3", + version="0.0.6", pypi="aiohttp", ) diff --git a/python-ecosys/cbor2/cbor2/__init__.py b/python-ecosys/cbor2/cbor2/__init__.py index 7cd98734e..80790f0da 100644 --- a/python-ecosys/cbor2/cbor2/__init__.py +++ b/python-ecosys/cbor2/cbor2/__init__.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from ._decoder import CBORDecoder from ._decoder import load from ._decoder import loads diff --git a/python-ecosys/cbor2/cbor2/_decoder.py b/python-ecosys/cbor2/cbor2/_decoder.py index 5d509a535..0ddfc31d3 100644 --- a/python-ecosys/cbor2/cbor2/_decoder.py +++ b/python-ecosys/cbor2/cbor2/_decoder.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import io import struct @@ -35,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) diff --git a/python-ecosys/cbor2/cbor2/_encoder.py b/python-ecosys/cbor2/cbor2/_encoder.py index 80a4ac022..fe8715468 100644 --- a/python-ecosys/cbor2/cbor2/_encoder.py +++ b/python-ecosys/cbor2/cbor2/_encoder.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import io import struct diff --git a/python-ecosys/cbor2/examples/cbor_test.py b/python-ecosys/cbor2/examples/cbor_test.py index b4f351786..a1cd7e93e 100644 --- a/python-ecosys/cbor2/examples/cbor_test.py +++ b/python-ecosys/cbor2/examples/cbor_test.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import cbor2 input = [ 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) diff --git a/python-ecosys/requests/manifest.py b/python-ecosys/requests/manifest.py index f8343e2a1..85f159753 100644 --- a/python-ecosys/requests/manifest.py +++ b/python-ecosys/requests/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.10.1", pypi="requests") +metadata(version="0.10.2", pypi="requests") package("requests") diff --git a/python-ecosys/requests/requests/__init__.py b/python-ecosys/requests/requests/__init__.py index 2951035f7..4ca7489a4 100644 --- a/python-ecosys/requests/requests/__init__.py +++ b/python-ecosys/requests/requests/__init__.py @@ -182,6 +182,8 @@ def request( if redirect: s.close() + # Use the host specified in the redirect URL, as it may not be the same as the original URL. + headers.pop("Host", None) if status in [301, 302, 303]: return request("GET", redirect, None, None, headers, stream) else: diff --git a/python-stdlib/abc/abc.py b/python-stdlib/abc/abc.py index 941be4f5e..c2c707f62 100644 --- a/python-stdlib/abc/abc.py +++ b/python-stdlib/abc/abc.py @@ -1,2 +1,6 @@ +class ABC: + pass + + def abstractmethod(f): return f diff --git a/python-stdlib/abc/manifest.py b/python-stdlib/abc/manifest.py index 66495fd75..c76312960 100644 --- a/python-stdlib/abc/manifest.py +++ b/python-stdlib/abc/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.0.1") +metadata(version="0.1.0") module("abc.py") 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/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") diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 0f2a89105..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,15 +624,18 @@ def fromtimestamp(cls, ts, tz=None): else: us = 0 if tz is None: - raise NotImplementedError + 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): @@ -810,13 +804,45 @@ def astimezone(self, tz=None): return self _tz = self._tz if _tz is None: - raise NotImplementedError + ts = int(self._mktime()) + os = datetime(*_t.localtime(ts)[:6]) - datetime(*_t.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(*_t.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) @@ -828,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())) @@ -840,7 +866,7 @@ def toordinal(self): def timestamp(self): if self._tz is None: - raise NotImplementedError + return self._mktime() else: return (self - datetime.EPOCH).total_seconds() @@ -874,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) 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..999bfaca9 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,77 +2084,50 @@ 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) 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) + dt = datetime(2010, 3, 28, 2, 30) # doesn't exist + 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) + dt = datetime(2020, 3, 8, 2, 30) # doesn't exist + 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()) diff --git a/python-stdlib/errno/errno.py b/python-stdlib/errno/errno.py index 05441b69c..c513a7f14 100644 --- a/python-stdlib/errno/errno.py +++ b/python-stdlib/errno/errno.py @@ -34,5 +34,6 @@ ERANGE = 34 # Math result not representable EAFNOSUPPORT = 97 # Address family not supported by protocol ECONNRESET = 104 # Connection timed out +ENOTCONN = 107 # Not connected ETIMEDOUT = 110 # Connection timed out EINPROGRESS = 115 # Operation now in progress diff --git a/python-stdlib/errno/manifest.py b/python-stdlib/errno/manifest.py index a1e1e6c7c..075d3403d 100644 --- a/python-stdlib/errno/manifest.py +++ b/python-stdlib/errno/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.4") +metadata(version="0.2.0") module("errno.py") 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/inspect/inspect.py b/python-stdlib/inspect/inspect.py index 06aba8762..9074549bb 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -1,5 +1,7 @@ import sys +_g = lambda: (yield) + def getmembers(obj, pred=None): res = [] @@ -16,11 +18,16 @@ def isfunction(obj): def isgeneratorfunction(obj): - return isinstance(obj, type(lambda: (yield))) + return isinstance(obj, type(_g)) def isgenerator(obj): - return isinstance(obj, type(lambda: (yield)())) + return isinstance(obj, type((_g)())) + + +# In MicroPython there's currently no way to distinguish between generators and coroutines. +iscoroutinefunction = isgeneratorfunction +iscoroutine = isgenerator class _Class: @@ -73,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 a9d5a2381..119237c45 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.2") +metadata(version="0.2.0") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py new file mode 100644 index 000000000..f5110de70 --- /dev/null +++ b/python-stdlib/inspect/test_inspect.py @@ -0,0 +1,73 @@ +import collections +import inspect +import unittest + + +def fun(): + return 1 + + +def gen(): + yield 1 + + +class Class: + def meth(self): + pass + + +entities = ( + fun, + gen, + gen(), + Class, + Class.meth, + Class().meth, + inspect, +) + + +class TestInspect(unittest.TestCase): + def _test_is_helper(self, f, *entities_true): + for entity in entities: + result = f(entity) + if entity in entities_true: + self.assertTrue(result) + else: + self.assertFalse(result) + + def test_isfunction(self): + self._test_is_helper(inspect.isfunction, entities[0], entities[4]) + + def test_isgeneratorfunction(self): + self._test_is_helper(inspect.isgeneratorfunction, entities[1]) + + def test_isgenerator(self): + self._test_is_helper(inspect.isgenerator, entities[2]) + + def test_iscoroutinefunction(self): + self._test_is_helper(inspect.iscoroutinefunction, entities[1]) + + def test_iscoroutine(self): + self._test_is_helper(inspect.iscoroutine, entities[2]) + + def test_ismethod(self): + self._test_is_helper(inspect.ismethod, entities[5]) + + def test_isclass(self): + self._test_is_helper(inspect.isclass, entities[3]) + + 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) diff --git a/python-stdlib/logging/logging.py b/python-stdlib/logging/logging.py index f4874df7d..551bf7152 100644 --- a/python-stdlib/logging/logging.py +++ b/python-stdlib/logging/logging.py @@ -202,8 +202,8 @@ def critical(msg, *args): getLogger().critical(msg, *args) -def exception(msg, *args): - getLogger().exception(msg, *args) +def exception(msg, *args, exc_info=True): + getLogger().exception(msg, *args, exc_info=exc_info) def shutdown(): diff --git a/python-stdlib/logging/manifest.py b/python-stdlib/logging/manifest.py index d9f0ee886..c2614e9ea 100644 --- a/python-stdlib/logging/manifest.py +++ b/python-stdlib/logging/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.6.1") +metadata(version="0.6.2") module("logging.py") 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/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() 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/ci.sh b/tools/ci.sh index a5fcdf22e..6689e8aa4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -86,6 +86,7 @@ function ci_package_tests_run { python-stdlib/datetime \ python-stdlib/fnmatch \ python-stdlib/hashlib \ + python-stdlib/inspect \ python-stdlib/pathlib \ python-stdlib/quopri \ python-stdlib/shutil \ 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/tools/verifygitlog.py b/tools/verifygitlog.py index 20be794f8..46fec1e0c 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -49,7 +49,7 @@ def git_log(pretty_format, *args): def diagnose_subject_line(subject_line, subject_line_format, err): - err.error("Subject line: " + subject_line) + err.error('Subject line: "' + subject_line + '"') if not subject_line.endswith("."): err.error('* must end with "."') if not re.match(r"^[^!]+: ", subject_line): @@ -98,20 +98,47 @@ def verify_message_body(raw_body, err): if len(subject_line) >= 73: err.error("Subject line must be 72 or fewer characters: " + subject_line) + # Do additional checks on the prefix of the subject line. + verify_subject_line_prefix(subject_line.split(": ")[0], err) + # Second one divides subject and body. if len(raw_body) > 1 and raw_body[1]: err.error("Second message line must be empty: " + raw_body[1]) # Message body lines. for line in raw_body[2:]: - # Long lines with URLs are exempt from the line length rule. - if len(line) >= 76 and "://" not in line: + # Long lines with URLs or human names are exempt from the line length rule. + if len(line) >= 76 and not ( + "://" in line + or line.startswith("Co-authored-by: ") + or line.startswith("Signed-off-by: ") + ): err.error("Message lines should be 75 or less characters: " + line) if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: err.error('Message must be signed-off. Use "git commit -s".') +def verify_subject_line_prefix(prefix, err): + ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") + + if prefix.startswith((".", "/")): + err.error('Subject prefix cannot begin with "." or "/".') + + if prefix.endswith("/"): + err.error('Subject prefix cannot end with "/".') + + if prefix.startswith("ports/"): + err.error( + 'Subject prefix cannot begin with "ports/", start with the name of the port instead.' + ) + + if prefix.endswith(ext): + err.error( + "Subject prefix cannot end with a file extension, use the main part of the filename without the extension." + ) + + def run(args): verbose("run", *args) 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/json/json/__init__.py b/unix-ffi/json/json/__init__.py index 69a045563..954618f33 100644 --- a/unix-ffi/json/json/__init__.py +++ b/unix-ffi/json/json/__init__.py @@ -354,8 +354,8 @@ def loads( object_pairs_hook=None, **kw ): - """Deserialize ``s`` (a ``str`` instance containing a JSON - document) to a Python object. + """Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance + containing a JSON document) to a Python object. ``object_hook`` is an optional function that will be called with the result of any object literal decode (a ``dict``). The return value of @@ -413,4 +413,6 @@ def loads( kw["parse_int"] = parse_int if parse_constant is not None: kw["parse_constant"] = parse_constant + if isinstance(s, (bytes, bytearray)): + s = s.decode('utf-8') return cls(**kw).decode(s) diff --git a/unix-ffi/machine/machine/timer.py b/unix-ffi/machine/machine/timer.py index 3f371142c..be00cee33 100644 --- a/unix-ffi/machine/machine/timer.py +++ b/unix-ffi/machine/machine/timer.py @@ -5,7 +5,10 @@ from signal import * libc = ffilib.libc() -librt = ffilib.open("librt") +try: + librt = ffilib.open("librt") +except OSError as e: + librt = libc CLOCK_REALTIME = 0 CLOCK_MONOTONIC = 1 diff --git a/unix-ffi/machine/manifest.py b/unix-ffi/machine/manifest.py index c0e40764d..f7c11b81a 100644 --- a/unix-ffi/machine/manifest.py +++ b/unix-ffi/machine/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.2.1") +metadata(version="0.2.2") # Originally written by Paul Sokolovsky. 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")