Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

MicroPython for Microcontrollers  Module 9 – Packages, .mpy files and custom firmware

Libraries with mip and micropython-lib

How mip installs micropython-lib packages on a board, from mpremote and on the unix port: where the files go, pinning versions and writing a package.json.

  • Beginner
  • 15 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Install a micropython-lib package with mip on a board, with mpremote mip or on the unix port
  • Predict which files mip fetches for a package and the folder it writes them to
  • Specify the version mip installs for a package and for each of its dependencies
  • Write a package.json with urls, deps and a version for a package of your own

Before you start

What you’ll learn · Before you start

What you will learn

  • Install a micropython-lib package with mip on a board, with mpremote mip or on the unix port
  • Predict which files mip fetches for a package and the folder it writes them to
  • Specify the version mip installs for a package and for each of its dependencies
  • Write a package.json with urls, deps and a version for a package of your own

Before you start

On this page

pip install umqtt.simple puts nothing on a Pico W: pip installs into the Python on your computer, and the board has neither pip nor PyPI. MicroPython has an installer of its own, mip: MicroPython's package installer. It runs on boards with networking, inside mpremote on a computer and on the unix port, fetches a package from micropython-lib or from an address, and writes it into the first /lib folder of sys.path., and a collection of its own, micropython-lib: The MicroPython project's collection of packages for its builds, from logging and datetime to device drivers and MQTT clients. Its index at micropython.org serves each package as .mpy files and as source, and mip installs from it by default., whose index lists more than a hundred packages written for small chips, from logging to display drivers and MQTT clients.

This lesson follows one install request by request, on the board and from your computer: which files mip fetches, the folder it writes them to, what it installs when you name no version, and how a package of your own describes itself to mip in a package.json: The file that describes a package to mip: urls lists its files as [destination, source] pairs, deps its dependencies as [name, version] pairs, and version the package's own version. mip reads it to install a package from an address..

mip copies a package from micropython-lib into /lib

On a Pico W with Wi-Fi connected, two lines install the MQTT client of micropython-lib: import mip, then mip.install("umqtt.simple"). The same installer exists in two more places, and the package documentation gives all three the same features:

Where you type it The command Who downloads the files
On the board, at its prompt or in a program mip.install("umqtt.simple") The board, over its own network
On your computer, with the board on USB mpremote mip install umqtt.simple Your computer, which copies them to the board
On your computer, for the unix port: MicroPython built as an ordinary program for Linux, macOS and other Unix systems, with its REPL in a terminal. Its standard variant builds at the same feature level as the rp2 and esp32 ports, so it runs most board code for tests. More in “Imports, modules and packages on a device” micropython -m mip install umqtt.simple Your computer, into the unix port’s folder

Only the first needs a network on the board. mip ships in the firmware of boards with network support, so a Pico without Wi-Fi gets its packages through mpremote: MicroPython's command-line tool for your computer: it connects to a board over USB serial and runs code, copies, lists and hashes files, mounts a local folder and opens the REPL, all through the raw REPL. More in “mpremote: run, copy, mount and sync”, which downloads on your computer and copies over USB. Step through one install on a Pico W, from the folder mip chooses to the last file it writes:

Interactive · message by message One mip install, request by request Step through mip.install("umqtt.simple", version="1.8.0") on a Pico W, from choosing the folder to the last file it writes.
One mip install, request by request, step 7 of 7. Messages so far: mip on the board to The index: GET package/6/…/1.8.0; The index to mip on the board: ssl.mpy, umqtt/simple.mpy; mip on the board to The index: GET file/d5/d53a3211; The index to mip on the board: bytes of ssl.mpy; mip on the board to The index: GET file/92/9240e244; The index to mip on the board: bytes of simple.mpy.mip on the boardDoneThe indexmicropython.org/pi/v2GET package/6/…/1.8.0ssl.mpy, umqtt/simple.mpyGET file/d5/d53a3211bytes of ssl.mpyGET file/92/9240e244bytes of simple.mpy

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Choose the folder. mip reads sys.path, which is ['', '.frozen', '/lib'] on a Pico W, and takes the first entry that ends in /lib and does not start with /rom. Every file of this install goes under /lib.
  2. Ask for the package file. The low byte of sys.implementation._mpy, 4870, is 6, the .mpy version of the firmware. mip asks for the package file of umqtt.simple 1.8.0 built for that version.
  3. The list of files. The reply names two files, each with the first 8 hex digits of its SHA-256 hash. ssl comes along because the umqtt.simple package requires it.
  4. Fetch the first file. Files are stored on the index under their hash, so mip asks for file d53a3211, in the folder named by its first two digits.
  5. Write it. mip prints Copying: /lib/ssl.mpy, creates the folders it needs and writes the bytes as they arrive, 128 at a time.
  6. The second file. The same again for umqtt/simple.mpy, hash 9240e244. The path in the reply keeps its folder, so the file lands in /lib/umqtt/.
  7. Done. With both files written mip prints Done. import umqtt.simple now finds /lib/umqtt/simple.mpy, already compiled, so the board compiles nothing.

Two things decide every install. The first is the index, https://micropython.org/pi/v2 unless you pass index=: for a package name it answers with a package file that lists the files to copy, each with the first 8 hex digits of its SHA-256 hash, and mip then fetches each file by that hash. A package the requested one depends on is part of that list: umqtt.simple requires ssl in its manifest, so its package file names ssl.mpy too. If a file is already on the board with the same hash, mip prints Exists: and skips it.

The second is the target folder. mip takes the first entry of sys.path that ends in /lib and does not start with /rom, which is /lib on a Pico W. target="/data/lib" on the board, or --target=/data/lib with mpremote, puts the files elsewhere, but then that folder must be on sys.path too, or nothing can import them. This program follows those rules for umqtt.simple 1.8.0 on three builds, with the package files that the index serves for that version written into it, so it needs no network:

What mip asks for, and where it writes, on three builds Python · install/install_plan.py
"""What mip.install() asks the index for, and where it writes each file.

This follows the rules of mip in micropython-lib (the copy that MicroPython 1.29.0 ships) and makes no
network request: the replies below are the package files the index serves for umqtt.simple 1.8.0.
"""

INDEX = "https://micropython.org/pi/v2"

REPLIES = {
    INDEX + "/package/6/umqtt.simple/1.8.0.json": {
        "v": 1,
        "version": "1.8.0",
        "hashes": [["ssl.mpy", "d53a3211"], ["umqtt/simple.mpy", "9240e244"]],
    },
    INDEX + "/package/py/umqtt.simple/1.8.0.json": {
        "v": 1,
        "version": "1.8.0",
        "hashes": [["ssl.py", "89a6e5ef"], ["umqtt/simple.py", "de2fbae7"]],
    },
}

BUILDS = {
    "Pico W": (4870, ["", ".frozen", "/lib"]),
    "Pico W with a ROMFS image": (4870, ["", ".frozen", "/rom", "/rom/lib", "/lib"]),
    "unix port": (774, ["", ".frozen", "~/.micropython/lib", "/usr/lib/micropython"]),
}


def default_target(path):
    """The first sys.path entry that ends in /lib, skipping the read-only /rom ones."""
    for entry in path:
        if entry.endswith("/lib") and not entry.startswith("/rom"):
            return entry
    return None


def install(build, name, version=None, mpy=True):
    mpy_number, path = BUILDS[build]
    target = default_target(path)
    version = version or "latest"
    kind = mpy_number & 0xFF if mpy else "py"
    url = f"{INDEX}/package/{kind}/{name}/{version}.json"
    print(f"{build}, sys.path {path}")
    print(f"  mip.install({name!r}, version={version!r}, mpy={mpy})")
    print(f"  Installing {name} ({version}) from {INDEX} to {target}")
    print(f"  GET {url}")
    reply = REPLIES.get(url)
    if reply is None:
        print("  Package not found:", url)
        print("  Package may be partially installed")
        return
    for dest, short_hash in reply["hashes"]:
        print(f"  GET {INDEX}/file/{short_hash[:2]}/{short_hash}")
        print(f"    Copying: {target}/{dest}")
    print("  Done")


install("Pico W", "umqtt.simple", "1.8.0")
install("Pico W with a ROMFS image", "umqtt.simple", "1.8.0")
install("unix port", "umqtt.simple", "1.8.0", mpy=False)
install("Pico W", "umqtt.simple", "1.9.0")

Output

Pico W, sys.path ['', '.frozen', '/lib']
  mip.install('umqtt.simple', version='1.8.0', mpy=True)
  Installing umqtt.simple (1.8.0) from https://micropython.org/pi/v2 to /lib
  GET https://micropython.org/pi/v2/package/6/umqtt.simple/1.8.0.json
  GET https://micropython.org/pi/v2/file/d5/d53a3211
    Copying: /lib/ssl.mpy
  GET https://micropython.org/pi/v2/file/92/9240e244
    Copying: /lib/umqtt/simple.mpy
  Done
Pico W with a ROMFS image, sys.path ['', '.frozen', '/rom', '/rom/lib', '/lib']
  mip.install('umqtt.simple', version='1.8.0', mpy=True)
  Installing umqtt.simple (1.8.0) from https://micropython.org/pi/v2 to /lib
  GET https://micropython.org/pi/v2/package/6/umqtt.simple/1.8.0.json
  GET https://micropython.org/pi/v2/file/d5/d53a3211
    Copying: /lib/ssl.mpy
  GET https://micropython.org/pi/v2/file/92/9240e244
    Copying: /lib/umqtt/simple.mpy
  Done
unix port, sys.path ['', '.frozen', '~/.micropython/lib', '/usr/lib/micropython']
  mip.install('umqtt.simple', version='1.8.0', mpy=False)
  Installing umqtt.simple (1.8.0) from https://micropython.org/pi/v2 to ~/.micropython/lib
  GET https://micropython.org/pi/v2/package/py/umqtt.simple/1.8.0.json
  GET https://micropython.org/pi/v2/file/89/89a6e5ef
    Copying: ~/.micropython/lib/ssl.py
  GET https://micropython.org/pi/v2/file/de/de2fbae7
    Copying: ~/.micropython/lib/umqtt/simple.py
  Done
Pico W, sys.path ['', '.frozen', '/lib']
  mip.install('umqtt.simple', version='1.9.0', mpy=True)
  Installing umqtt.simple (1.9.0) from https://micropython.org/pi/v2 to /lib
  GET https://micropython.org/pi/v2/package/6/umqtt.simple/1.9.0.json
  Package not found: https://micropython.org/pi/v2/package/6/umqtt.simple/1.9.0.json
  Package may be partially installed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 install_plan.py

The first two runs write to the same place. A Pico W built with a ROMFS: MicroPython's read-only filesystem in a flash partition, mounted at /rom on boards whose firmware has one. Its .mpy files run in place from flash, like frozen code, but a new image is written with mpremote, without new firmware. More in “Frozen modules, manifests and ROMFS” image has /rom/lib on its path before /lib, and mip passes over it, because that image is read-only. The unix port writes to ~/.micropython/lib in your home folder, the first /lib folder of its default path; its sys.path shows that folder with the ~ replaced by the full path of your home folder. The last two runs, the source files and the version that does not exist, come back in the next section.

Typing import mip on a plain Pico

import mip on a Pico without Wi-Fi fails with ImportError, because only firmware for boards with networking includes mip. Install from your computer instead, mpremote mip install logging: mpremote reads the board’s sys.path over USB, downloads the files and copies them into /lib.

Checkpoint

Check: Where mip installs from, and to

  1. Question 1 of 2 A Pico W built with a ROMFS image has sys.path ['', '.frozen', '/rom', '/rom/lib', '/lib']. Where does mip.install('logging') write logging.mpy?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: /lib/logging.mpy

    The rule is the first sys.path entry that ends in /lib and does not start with /rom. On this board that is the last entry, /lib, so the file is /lib/logging.mpy.

  2. Question 2 of 2 A Pico without Wi-Fi needs the logging package. What works?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: mpremote mip install logging, on the computer the Pico is plugged into

    mip on the board needs the board's own network, and only firmware for network boards ships it. mpremote mip follows the same rules from your computer: it reads the board's sys.path, downloads and copies.

Without a version, mip installs the newest .mpy build

mip.install("umqtt.simple") with nothing else asks the index for latest.json, the newest version of the package, and for files compiled to .mpy file: A file of precompiled MicroPython bytecode, made with mpy-cross on a computer, that a board imports like a .py module without compiling it. It may also hold machine code for one architecture. More in “What MicroPython is and where it runs” for the board’s .mpy version. That version is the low byte of sys.implementation._mpy: 4870 & 0xFF is 6 on a Pico, and 6 on every release from 1.19 on. Two arguments change what you get, and the package documentation names the command-line forms that do the same:

You write mip asks the index for You get
mip.install("umqtt.simple") package/6/umqtt.simple/latest.json The newest version, as .mpy files
mip.install("umqtt.simple", version="1.8.0") or mpremote mip install umqtt.simple@1.8.0 package/6/umqtt.simple/1.8.0.json Exactly 1.8.0
mip.install("umqtt.simple", mpy=False) or --no-mpy package/py/umqtt.simple/latest.json The .py source files

The source build is useful when you want to read the code on the board or step through it; the .mpy build saves the board the work of compiling at every import. Look back at the third run of the program: with mpy=False the same package file lists ssl.py and umqtt/simple.py, with hashes of their own.

A version the index does not have stops the install at its first request. The last run asked for 1.9.0 and got Package not found, followed by mip’s general warning, Package may be partially installed. Here nothing was written, because the package file itself was missing; the warning matters when a later file fails, after earlier ones were already copied.

Name a version for anything a fleet of boards depends on. latest means whatever is newest on the day of the install, and the index keeps every version it has published, so a pin keeps working while new versions arrive. For a package fetched from a Git repository instead of the index, the version names a branch or a tag, and without one mip takes the repository’s default branch.

Key idea

An install without a version takes the newest .mpy build of the day, so pin the version of every package that boards in the field depend on: the same name and version give every board the same files.

Checkpoint

Check: Versions and .mpy builds

  1. Question 1 of 2 What does mip.install('umqtt.simple', mpy=False) install?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The .py source files of the newest version

    The index holds two builds of each version, the .mpy files for an .mpy version and the source, and mpy=False picks the source: the URL has py where it would have 6.

  2. Question 2 of 2 Boards installed with mip.install('umqtt.simple') three months apart behave differently on reconnect. What explains it, and what prevents it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Each install took the newest version of its day; pin version= in the install

    latest names the newest version on the day of the request. A fleet that must match installs a named version, such as version='1.8.0' or umqtt.simple@1.8.0 with mpremote.

A package.json lists the files and dependencies of your own package

Your own driver is not in micropython-lib, but mip installs it the same way from a Git repository or any web server, as long as the package has a package.json beside its files. This one describes the station package of this module, three modules that read a Modbus sensor and send the readings by MQTT, with two dependencies, each pinned. The program checks the file the way mip will read it, then prints what one install would fetch and write:

A package.json checked, and the install it describes

Python · package/package_check.py

"""Checks a package.json before mip reads it, then lists what one install would fetch and write.

mip itself checks nothing first: a bad entry stops it halfway, with part of the package copied. The URL rules
are mip's (micropython-lib, the copy MicroPython 1.29.0 ships); nothing here touches the network.
"""
import json

INDEX = "https://micropython.org/pi/v2"
PREFIXES = ("http://", "https://", "github:", "gitlab:", "codeberg:")
HOSTS = {
    "github:": "https://raw.githubusercontent.com/{org}/{repo}/{ref}/{path}",
    "gitlab:": "https://gitlab.com/{org}/{repo}/-/raw/{ref}/{path}",
    "codeberg:": "https://codeberg.org/api/v1/repos/{org}/{repo}/raw/{path}?ref={ref}",
}


def rewrite(url, version):
    """github:org/repo/path and the like become HTTPS addresses; the version names a branch or tag."""
    for prefix, form in HOSTS.items():
        if url.startswith(prefix):
            org, repo, *path = url[len(prefix):].split("/")
            return form.format(org=org, repo=repo, ref=version or "HEAD", path="/".join(path))
    return url


def problems(package):
    found = []
    if not isinstance(package.get("version"), str) or not package["version"]:
        found.append("version: missing; mip ignores it, but people and tools read it")
    for i, entry in enumerate(package.get("urls", [])):
        if not (isinstance(entry, list) and len(entry) == 2 and all(isinstance(x, str) for x in entry)):
            found.append(f"urls[{i}]: must be a pair [destination, source]")
            continue
        dest, source = entry
        if "\\" in dest or "\\" in source:
            found.append(f"urls[{i}]: {entry} uses backslashes; write forward slashes")
        if dest.startswith("/") or ".." in dest.split("/"):
            found.append(f"urls[{i}]: destination {dest!r} must be relative to the target folder")
    for i, dep in enumerate(package.get("deps", [])):
        if not (isinstance(dep, list) and len(dep) == 2 and all(isinstance(x, str) for x in dep)):
            found.append(f"deps[{i}]: {dep} must be a pair [name, version]")
    return found


def plan(package, source, version, target="/lib", mpy_number=4870):
    json_url = source + "/package.json"
    print(f"mip.install({source!r}, version={version!r}), target {target}")
    print("  GET", rewrite(json_url, version))
    base = json_url.rpartition("/")[0]
    for dest, url in package["urls"]:
        if not url.startswith(PREFIXES):
            url = base + "/" + url
        print("  GET", rewrite(url, version))
        print(f"    Copying: {target}/{dest}")
    for name, dep_version in package["deps"]:
        print(f"  then {name} {dep_version}: GET {INDEX}/package/{mpy_number & 0xFF}/{name}/{dep_version}.json")


with open("package.json") as f:
    station = json.load(f)
print("package.json:", problems(station) or "no problems found")
plan(station, "github:your-name/station", "v0.3.0")

broken = {
    "urls": [["station\\modbus.py", "station\\modbus.py"], ["/lib/station/upload.py", "station/upload.py"]],
    "deps": [["umqtt.simple"]],
}
print("broken package:")
for problem in problems(broken):
    print("  " + problem)

Output

package.json: no problems found
mip.install('github:your-name/station', version='v0.3.0'), target /lib
  GET https://raw.githubusercontent.com/your-name/station/v0.3.0/package.json
  GET https://raw.githubusercontent.com/your-name/station/v0.3.0/station/__init__.py
    Copying: /lib/station/__init__.py
  GET https://raw.githubusercontent.com/your-name/station/v0.3.0/station/modbus.py
    Copying: /lib/station/modbus.py
  GET https://raw.githubusercontent.com/your-name/station/v0.3.0/station/upload.py
    Copying: /lib/station/upload.py
  then umqtt.simple 1.8.0: GET https://micropython.org/pi/v2/package/6/umqtt.simple/1.8.0.json
  then logging 0.6.2: GET https://micropython.org/pi/v2/package/6/logging/0.6.2.json
broken package:
  version: missing; mip ignores it, but people and tools read it
  urls[0]: ['station\\modbus.py', 'station\\modbus.py'] uses backslashes; write forward slashes
  urls[1]: destination '/lib/station/upload.py' must be relative to the target folder
  deps[0]: ['umqtt.simple'] must be a pair [name, version]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 package_check.py

package/package.json

{
  "version": "0.3.0",
  "urls": [
    ["station/__init__.py", "station/__init__.py"],
    ["station/modbus.py", "station/modbus.py"],
    ["station/upload.py", "station/upload.py"]
  ],
  "deps": [
    ["umqtt.simple", "1.8.0"],
    ["logging", "0.6.2"]
  ]
}

The format that the package documentation gives has three keys. urls lists each file as a pair, the destination under the target folder first and the source second. A relative source is read from the folder that holds package.json; a full address, or a github:, gitlab: or codeberg: one, is not. deps lists [name, version] pairs, which mip installs after the files, each with its own version: from the index for a plain name, from that repository for a github: address. version is the package’s own version, for people and tools: mip itself does not read it.

The output shows the rewriting. github:your-name/station with the version v0.3.0 becomes https://raw.githubusercontent.com/your-name/station/v0.3.0/ followed by the path, so every file comes from the same tag. Notice also what arrives: your own files come as the .py files you published, because mip copies what each address holds and compiles nothing, while the dependencies from the index arrive as .mpy files.

Writing Windows paths in package.json

["station\\modbus.py", "station\\modbus.py"] works nowhere: the board’s file names and web addresses both use /. The documentation asks for forward slashes in package.json even on Windows, so that the same file works from a local folder and from a web server.

To test a package before you publish it, install it from the folder on your computer: mpremote mip install ./station/package.json reads the file and its relative sources from your disk and copies them to the board. The broken package at the end of the output shows what the check catches before mip ever runs: no version, a backslash, a destination that starts with /, and a dependency without its version.

JSON Validator Check that a package.json is valid JSON before a board tries to read it.

Checkpoint

Check: A package.json of your own

  1. Question 1 of 2 mip.install('github:your-name/station', version='v0.3.0') reads a package.json whose urls has ['station/modbus.py', 'station/modbus.py']. Where does that file come from?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: https://raw.githubusercontent.com/your-name/station/v0.3.0/station/modbus.py

    A relative source is joined to the folder of package.json and rewritten like it: github:your-name/station becomes raw.githubusercontent.com/your-name/station/v0.3.0/, then the path.

  2. Question 2 of 2 A package.json lists three .py files in urls and umqtt.simple in deps. What lands in /lib?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The three .py files as published, and umqtt.simple as .mpy files

    Your files arrive as the bytes you published, so as source here, and the board compiles them at import. The dependency comes from the index, as .mpy files for the board's .mpy version.

Practise: package a soil sensor

Write the package.json of a three-module sensor package that depends on umqtt.simple. The sample tests read your file as JSON and check each rule of this lesson, the forward slashes included:

Exercise · Easy · Python

Write the package.json of a sensor package

A soil-moisture package lives in a Git repository with its package.json at the top and three modules beside it: soil/__init__.py, soil/probe.py and soil/calibrate.py. It sends its readings with umqtt.simple from micropython-lib.

Fill in package.json so that mip.install("github:your-name/soil") installs the package on a board:

  • urls lists each of the three modules as a [destination, source] pair. On the board each one must land in a soil folder inside the target, and each source is the module's path in the repository, relative to package.json.
  • deps pins umqtt.simple to version 1.8.0.
  • version is 1.0.0.

The sample tests read the file as JSON and check each of these rules, the forward slashes included.

Starter code · package.json

{
  "version": "",
  "urls": [],
  "deps": []
}
The sample tests · test_package.py
import json
from pathlib import Path

MODULES = {"soil/__init__.py", "soil/probe.py", "soil/calibrate.py"}


def package():
    return json.loads(Path("package.json").read_text())


def test_is_an_object():
    """package.json is a JSON object with only urls, deps and version"""
    data = package()
    assert isinstance(data, dict), "the file must hold one JSON object"
    assert set(data) <= {"urls", "deps", "version"}, f"unexpected keys: {sorted(set(data) - {'urls', 'deps', 'version'})}"


def test_version():
    """the package's version is 1.0.0"""
    assert package().get("version") == "1.0.0"


def test_three_modules():
    """urls installs the three modules into a soil folder"""
    urls = package().get("urls", [])
    assert all(isinstance(u, list) and len(u) == 2 for u in urls), "each entry is a [destination, source] pair"
    assert {u[0] for u in urls} == MODULES, "the destinations are soil/__init__.py, soil/probe.py and soil/calibrate.py"
    assert len(urls) == 3, "list each module once"


def test_sources_are_relative():
    """each source is the module's own path in the repository, with forward slashes"""
    for dest, source in package().get("urls", []):
        assert "\\" not in dest + source, "write forward slashes, even on Windows"
        assert not source.startswith(("/", "http://", "https://", "github:")), "give the path relative to package.json"
        assert source == dest, f"{dest} comes from the file of the same path, not {source}"


def test_pinned_dependency():
    """deps pins umqtt.simple to 1.8.0"""
    deps = package().get("deps", [])
    assert all(isinstance(d, list) and len(d) == 2 for d in deps), "each dependency is a [name, version] pair"
    assert ["umqtt.simple", "1.8.0"] in deps
A hint

Each entry of urls is a list of two strings, and here both strings are the same relative path, for example ["soil/probe.py", "soil/probe.py"]. Each entry of deps is a list of a package name and a version.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then check yourself on mip’s install path, its versions and package.json. Feedback follows every answer, and the questions start with the idea you know least:

Practice that adapts to you

Practise this lesson

9 questions about 3 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Which folder does mip write to when no target is given?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: The first sys.path entry that ends in /lib and is not under /rom

    mip scans sys.path in order and takes the first entry that ends in /lib, skipping any that starts with /rom. On a Pico that is /lib.

  2. Question 2 of 9 The unix port's default path is '', then .frozen, ~/.micropython/lib and /usr/lib/micropython, with ~ standing for your home folder. Where does micropython -m mip install logging write?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: ~/.micropython/lib, in your home folder

    The same rule as on a board: the first entry ending in /lib, here ~/.micropython/lib in your home folder.

  3. Question 3 of 9 mpremote mip install --target=/data/lib logging succeeds, but import logging then fails on the board. What is missing?

    Choose one answer.

    A hint

    Where does an import look?

    Show the answer: Question 3 of 9

    Answer: /data/lib on sys.path

    target puts the files in another folder, and the documentation warns that the folder must then be on sys.path. sys.path.append('/data/lib') in main.py before the import fixes it.

  4. Question 4 of 9 An ESP32 reports sys.implementation._mpy = 11014. What number does mip put in the index address, package/?/umqtt.simple/latest.json?

    Type a number.

    A hint

    mip keeps only the low byte, _mpy & 0xFF.

    Show the answer: Question 4 of 9

    Answer: 6

    11014 is 0x2B06, and its low byte 0x06 is 6, the .mpy version. The other bits name the sub-version and the architecture, which a bytecode build does not depend on.

  5. Question 5 of 9 Which of these install exactly version 1.8.0 of umqtt.simple?

    Choose every answer that is right.

    Show the answer: Question 5 of 9

    Answer:

    • mip.install('umqtt.simple', version='1.8.0')
    • mpremote mip install umqtt.simple@1.8.0

    mip takes the version as its own argument, or after @ on the command line. Pip's == notation means nothing to it.

  6. Question 6 of 9 An install of version 1.9.0 prints Package not found, then Package may be partially installed. What is on the board?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Nothing new; the first request, for the package file, failed

    mip prints the same warning after any failure. When the package file itself is missing, nothing was written; it means something only when a later file fails after others were copied.

  7. Question 7 of 9 How does a package.json pin its dependency on logging 0.6.2?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: "deps": [["logging", "0.6.2"]]

    mip reads deps as [name, version] pairs and installs each from the index with its own version, after the package's own files.

  8. Question 8 of 9 A urls entry is ['/lib/station/upload.py', 'station/upload.py']. What is wrong with it?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: The destination must be relative to the target folder

    The destination is a path under the target folder, such as station/upload.py, so the same package installs anywhere target= points.

  9. Question 9 of 9 Boards that install your package from Git compile its .py files at every import. How could they get .mpy files instead?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: Publish .mpy files made with mpy-cross and list them in urls

    The index compiles micropython-lib for each .mpy version; your own package is copied as published. Compiling it with mpy-cross, the subject of the next lesson, and listing the .mpy files does the same for it.

Hash Generator Compute the SHA-256 of a file and compare its first 8 digits with an index entry.

Interview questions

The first question is answered here; the next two are longer, and their first steps are on the module’s page of questions.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher · Warm-up

    Why can't you pip install a package onto a Pico, and what do you use instead?

    pip installs packages into the Python on your computer, from PyPI, and a board has neither pip nor PyPI. MicroPython has its own installer, mip, which fetches packages from micropython-lib, an index of packages written for MicroPython's builds, and copies them as precompiled .mpy files into the first sys.path folder that ends in /lib, which is /lib on a Pico. On a board with networking, such as a Pico W, mip runs on the board itself: import mip, then mip.install("logging"). On a board without Wi-Fi, run mpremote mip install logging on the computer it is plugged into: mpremote downloads the files and copies them over USB. For a package of your own, mip reads a package.json that lists its files and dependencies.

  2. Mid to senior

    Forty Pico W boards installed umqtt.simple with mip.install("umqtt.simple") over three months, and some now behave differently when the broker drops the connection. How do you tell what each board runs, and how do you make the next installs the same everywhere?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    A teammate's package installs with mpremote mip install github:team/sensor on their computer. On yours it prints Package may be partially installed and the import fails. How do you find the cause, and what do you change so it cannot happen again?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are the three ways to install a micropython-lib package, and which needs a network on the board?

    mip.install("umqtt.simple") on the board, which needs its own network; mpremote mip install umqtt.simple over USB; micropython -m mip install umqtt.simple for the unix port.

  2. Why does import mip fail on a plain Pico?

    Only the firmware of boards with networking includes mip. Install from your computer with mpremote mip install, which downloads the files and copies them over USB.

  3. Into which folder does mip write a package?

    The first sys.path entry that ends in /lib and does not start with /rom, which is /lib on a Pico W. target= can name another folder, which must then be on sys.path too.

  4. What does mip install when no version is named?

    The newest version, as .mpy files for the board's .mpy version, which is the low byte of sys.implementation._mpy: 6 on every release from 1.19 on.

  5. How do you pin a package's version, or install its .py source, with mip?

    Add version="1.8.0" to mip.install(), or write umqtt.simple@1.8.0 after mpremote mip install; mpy=False, or --no-mpy, fetches the .py source instead.

  6. Which keys does a package.json for mip have?

    urls, a list of [destination, source] pairs written with forward slashes; deps, a list of [name, version] pairs; and version, for people and tools, which mip itself does not read.

  7. When does mip skip a file instead of writing it?

    When the board already has a file with that hash, mip prints Exists and moves on. The index names files by a short prefix of their SHA-256 hash, 8 hex digits.

  8. How do you try your own package with mip before publishing it?

    mpremote mip install ./station/package.json reads the file and its relative sources from your disk and copies them to the board.

Key takeaways

  • mip installs micropython-lib packages from the board, from mpremote over USB or on the unix port, by the same rules, and only the first needs a network on the board.
  • mip writes into the first sys.path folder that ends in /lib and is not under /rom, which is /lib on a Pico, unless target= names another folder that is also on the path.
  • Without a version mip installs the newest .mpy build for the board’s .mpy version; version= or @1.8.0 pins it, and mpy=False or --no-mpy installs the source.
  • A package of your own needs a package.json: urls as [destination, source] pairs with forward slashes, deps as [name, version] pairs, and a version for people and tools.

Terms in this lesson

micropython-lib
The MicroPython project's collection of packages for its builds, from logging and datetime to device drivers and MQTT clients. Its index at micropython.org serves each package as .mpy files and as source, and mip installs from it by default.
mip
MicroPython's package installer. It runs on boards with networking, inside mpremote on a computer and on the unix port, fetches a package from micropython-lib or from an address, and writes it into the first /lib folder of sys.path.
package.json
The file that describes a package to mip: urls lists its files as [destination, source] pairs, deps its dependencies as [name, version] pairs, and version the package's own version. mip reads it to install a package from an address.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.