diff --git a/.github/workflows/build_packages.yml b/.github/workflows/build_packages.yml index 8854a7307..a89658e2f 100644 --- a/.github/workflows/build_packages.yml +++ b/.github/workflows/build_packages.yml @@ -23,7 +23,7 @@ jobs: if: vars.MICROPY_PUBLISH_MIP_INDEX && github.event_name == 'push' && ! github.event.deleted run: source tools/ci.sh && ci_push_package_index - name: Upload packages as artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-${{ github.sha }} path: ${{ env.PACKAGE_INDEX_PATH }} 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/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index 8f45dfac0..fbe513b7c 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -114,6 +114,8 @@ async def task(g=None, prompt="--> "): curs = 0 # cursor offset from end of cmd buffer while True: b = await s.read(1) + if not b: # Handle EOF/empty read + break pc = c # save previous character c = ord(b) pt = t # save previous time @@ -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/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..3c3b3b44d 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_server.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_server.py @@ -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/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..177c6fea3 100644 --- a/micropython/drivers/display/lcd160cr/lcd160cr.py +++ b/micropython/drivers/display/lcd160cr/lcd160cr.py @@ -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/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index e3d46429d..e5a96ad5c 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -43,6 +43,7 @@ print("") time.sleep_ms(100) """ + import array from micropython import const 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/hts221/hts221.py b/micropython/drivers/sensor/hts221/hts221.py index fec52a738..c6cd51f48 100644 --- a/micropython/drivers/sensor/hts221/hts221.py +++ b/micropython/drivers/sensor/hts221/hts221.py @@ -52,7 +52,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/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..7fa4896ae 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 @@ -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/mip/manifest.py b/micropython/mip/manifest.py index 88fb08da1..9fb94ebcb 100644 --- a/micropython/mip/manifest.py +++ b/micropython/mip/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.3.0", description="On-device package installer for network-capable boards") +metadata(version="0.4.1", 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 0c3c6f204..7c0fb4d3a 100644 --- a/micropython/mip/mip/__init__.py +++ b/micropython/mip/mip/__init__.py @@ -9,6 +9,8 @@ _PACKAGE_INDEX = const("https://micropython.org/pi/v2") _CHUNK_SIZE = 128 +allowed_mip_url_prefixes = ("http://", "https://", "github:", "gitlab:") + # This implements os.makedirs(os.dirname(path)) def _ensure_path_exists(path): @@ -124,8 +126,12 @@ def _install_json(package_json_url, index, target, version, mpy): if not _download_file(file_url, fs_target_path): print("File not found: {} {}".format(target_path, short_hash)) return False + base_url = package_json_url.rpartition("/")[0] for target_path, url in package_json.get("urls", ()): fs_target_path = target + "/" + target_path + is_full_url = any(url.startswith(p) for p in allowed_mip_url_prefixes) + if base_url and not is_full_url: + url = f"{base_url}/{url}" # Relative URLs if not _download_file(_rewrite_url(https://github.com/url,%20version), fs_target_path): print("File not found: {} {}".format(target_path, url)) return False @@ -136,12 +142,7 @@ def _install_json(package_json_url, index, target, version, mpy): def _install_package(package, index, target, version, mpy): - if ( - package.startswith("http://") - or package.startswith("https://") - or package.startswith("github:") - or package.startswith("gitlab:") - ): + if any(package.startswith(p) for p in allowed_mip_url_prefixes): if package.endswith(".py") or package.endswith(".mpy"): print("Downloading {} to {}".format(package, target)) return _download_file( @@ -170,7 +171,7 @@ def _install_package(package, index, target, version, mpy): def install(package, index=None, target=None, version=None, mpy=True): if not target: for p in sys.path: - if p.endswith("/lib"): + if not p.startswith("/rom") and p.endswith("/lib"): target = p break else: 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..1e83ea06b 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 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..5a0554467 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 diff --git a/micropython/senml/senml/senml_record.py b/micropython/senml/senml/senml_record.py index 9dfe22873..ae40f0f70 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 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..0acea184f 100644 --- a/micropython/usb/usb-device-cdc/usb/device/cdc.py +++ b/micropython/usb/usb-device-cdc/usb/device/cdc.py @@ -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/manifest.py b/micropython/usb/usb-device/manifest.py index 025e67547..fa0d8a3f5 100644 --- a/micropython/usb/usb-device/manifest.py +++ b/micropython/usb/usb-device/manifest.py @@ -1,2 +1,2 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") package("usb") diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index 7be09ee46..b0d91d8ff 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -600,7 +600,8 @@ def submit_xfer(self, ep_addr, data, done_cb=None): # function has returned to the caller. if not self._open: raise RuntimeError("Not open") - _dev._submit_xfer(ep_addr, data, done_cb) + if not _dev._submit_xfer(ep_addr, data, done_cb): + raise RuntimeError("DCD error") def stall(self, ep_addr, *args): # Set or get the endpoint STALL state. 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..83d29405d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ ignore = [ "ISC003", # micropython does not support implicit concatenation of f-strings "PIE810", # micropython does not support passing tuples to .startswith or .endswith "PLC1901", - "PLR1701", + "PLR1704", # sometimes desirable to redefine an argument to save code size "PLR1714", "PLR5501", "PLW0602", @@ -72,6 +72,7 @@ ignore = [ "PLW2901", "RUF012", "RUF100", + "SIM101", "W191", # tab-indent, redundant when using formatter ] line-length = 99 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..965dbfd46 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 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/manifest.py b/python-ecosys/requests/manifest.py index eb7bb2d42..85f159753 100644 --- a/python-ecosys/requests/manifest.py +++ b/python-ecosys/requests/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.10.0", 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/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..56411b96e 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -68,14 +68,6 @@ import unittest -# See localtz.patch -try: - datetime.fromtimestamp(0) - LOCALTZ = True -except NotImplementedError: - LOCALTZ = False - - if hasattr(datetime, "EPOCH"): EPOCH = datetime.EPOCH else: @@ -1619,11 +1611,8 @@ def test___init__24(self): def test_fromtimestamp00(self): with LocalTz("Europe/Rome"): ts = 1012499103.001234 - if LOCALTZ: - dt = datetime.fromtimestamp(ts) - self.assertEqual(dt, d1t1) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + dt = datetime.fromtimestamp(ts) + self.assertEqual(dt, d1t1) def test_fromtimestamp01(self): ts = 1012506303.001234 @@ -1642,48 +1631,35 @@ def test_fromtimestamp04(self): dt = datetime(2010, 10, 31, 0, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 2 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertFalse(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertFalse(ds.fold) def test_fromtimestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 1, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 1 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertTrue(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertTrue(ds.fold) def test_fromtimestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 5, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 4 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) def test_fromtimestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 7, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 5 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) - @unittest.skipIf(not LOCALTZ, "naive datetime not supported") def test_now00(self): tm = datetime(*mod_time.localtime()[:6]) dt = datetime.now() @@ -2004,46 +1980,31 @@ def test_astimezone04(self): with LocalTz("Europe/Rome"): dt1 = dt27tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone05(self): with LocalTz("Europe/Rome"): dt1 = dt28tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone06(self): with LocalTz("Europe/Rome"): dt1 = dt30tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone07(self): with LocalTz("Europe/Rome"): dt1 = dt31tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone08(self): with LocalTz("Europe/Rome"): dt1 = dt3 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_utcoffset00(self): self.assertEqual(dt1.utcoffset(), None) @@ -2123,10 +2084,7 @@ def test_weekday00(self): def test_timestamp00(self): with LocalTz("Europe/Rome"): - if LOCALTZ: - self.assertEqual(d1t1.timestamp(), 1012499103.001234) - else: - self.assertRaises(NotImplementedError, d1t1.timestamp) + self.assertEqual(d1t1.timestamp(), 1012499103.001234) def test_timestamp01(self): self.assertEqual(d1t1z.timestamp(), 1012506303.001234) @@ -2134,66 +2092,42 @@ def test_timestamp01(self): def test_timestamp02(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 3, 28, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1269739800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1269739800.0) def test_timestamp03(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1281400200.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1281400200.0) def test_timestamp04(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288485000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288485000.0) def test_timestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288488600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288488600.0) def test_timestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 3, 8, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1583652600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1583652600.0) def test_timestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1597041000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1597041000.0) def test_timestamp08(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_timestamp09(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_isoweekday00(self): self.assertEqual(dt1.isoweekday(), d1.isoweekday()) 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/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/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/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/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.