Allow mpremote mip to download libraries as local files
Description
Background
Currently the easiest way to install micropython-lib packages is with mpremote mip install.
However, when using such packages, there's no syntax highlighting and pylance checking.
Adding such packages to import paths for pylance is not trivial, especially since micropython mip has a custom manifest format.
Suggestion
I suggest adding a new subcommand to mpremote mip -- "download", inspired by pip download command.
This command will work just like mip install, but:
- would download files to local fs instead of connected MicroPython board
- would not require a MicroPython board to be connected to work
Then this mpremote mip download command can be used to download packages into a subdirectory added to import paths for IDE/Pylance/linter, and proper tooling can be utilised while using upstream micropython-lib packages.
Code Size
No response
Implementation
I intend to implement this feature and would submit a Pull Request if desirable
Code of Conduct
Yes, I agree
`mpremote mip install` hangs when uploading files
Port, board and/or hardware
STM32 port on a NUCLEO-144--F767ZI
MicroPython version
MicroPython v1.27.0-preview.440.ga6864109db on 2025-11-23; NUCLEO-F767ZI with STM32F767
Reproduction
- (Optional) Perform a mass erase on all flash blocks.
- Build main firmware and mboot, then upload image to the board with
deploy-stlink - Reboot the board
- Run
mpremote mip install unittest - Wait until it hangs
Expected behaviour
mpremote mip should install the required package
Observed behaviour
The upload process hangs at random intervals, and when stopping it via CTRL+C the traceback is as follows (the process was stopped after a few minutes of being stuck at 13%):
Traceback (most recent call last):
File "/home/agatti/src/micropython/ports/stm32/../../tools/mpremote/mpremote.py", line 6, in <module>
sys.exit(main.main())
~~~~~~~~~^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/main.py", line 614, in main
handler_func(state, args)
~~~~~~~~~~~~^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 206, in do_mip
_install_package(
~~~~~~~~~~~~~~~~^
state.transport,
^^^^^^^^^^^^^^^^
...<4 lines>...
args.mpy,
^^^^^^^^^
)
^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 169, in _install_package
_install_json(transport, package, index, target, version, mpy)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 131, in _install_json
_download_file(transport, file_url, fs_target_path)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/mip.py", line 99, in _download_file
transport.fs_writefile(dest, data, progress_callback=show_progress_bar)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport.py", line 163, in fs_writefile
self.exec("w(" + repr(chunk) + ")")
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 309, in exec
ret, ret_err = self.exec_raw(command, data_consumer=data_consumer)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 295, in exec_raw
self.exec_raw_no_follow(command)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 273, in exec_raw_no_follow
return self.raw_paste_write(command_bytes)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/home/agatti/src/micropython/tools/mpremote/mpremote/transport_serial.py", line 228, in raw_paste_write
data = self.serial.read(1)
File "/usr/lib/python3.13/site-packages/serial/serialposix.py", line 565, in read
ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
The firmware is still waiting for data to arrive on the serial port when mpremote seems to be stuck:
^C
Program received signal SIGINT, Interrupt.
mp_hal_stdin_rx_chr () at mphalport.c:53
53 for (;;) {
08:14:08 mp_hal_stdin_rx_chr > bt
#0 mp_hal_stdin_rx_chr () at mphalport.c:53
#1 0x08068aec in mp_reader_stdin_readbyte (data=0x2007ff74)
at ../../shared/runtime/pyexec.c:255
#2 0x08023164 in next_char (lex=lex@entry=0x20037b50) at ../../py/lexer.c:174
#3 0x08023456 in parse_string_literal (lex=0x20037b50, is_raw=false,
is_fstring=false) at ../../py/lexer.c:413
#4 mp_lexer_to_next (lex=lex@entry=0x20037b50) at ../../py/lexer.c:696
#5 0x0802443c in mp_parse (lex=lex@entry=0x20037b50,
input_kind=input_kind@entry=MP_PARSE_FILE_INPUT) at ../../py/parse.c:1167
#6 0x08068be8 in parse_compile_execute (source=source@entry=0x2007ff7c,
input_kind=input_kind@entry=MP_PARSE_FILE_INPUT,
exec_flags=exec_flags@entry=65) at ../../shared/runtime/pyexec.c:111
#7 0x08068d90 in do_reader_stdin (c=65) at ../../shared/runtime/pyexec.c:324
#8 pyexec_raw_repl () at ../../shared/runtime/pyexec.c:567
#9 0x080730d4 in stm32_main (reset_mode=<optimized out>) at main.c:718
#10 <signal handler called>
in fact, reconnecting to the serial port will find the interpreter still waiting for data to arrive.
Additional Information
No, I've provided everything above.
Code of Conduct
Yes, I agree