Skip to content

Programs of several files

A program does not have to be one file. pb run follows the imports in your script, compiles what it finds, and sends the whole lot as one program:

robot/
  demo.py        import helpers, from robot.motors import spin
  helpers.py
  robot/
    motors.py
pb run demo.py

Nothing has to be listed anywhere. Anything your program imports that pb can find next to it goes along; anything it cannot find is assumed to be part of the firmware, which is what from pybricks.hubs import ThisHub is.

Where modules are looked for

One directory, and which one depends on how your project is laid out.

Your project Modules are looked for in
Scripts in a folder, no pyproject.toml The script's own folder
A project with a src/ directory src/
A project whose build backend names a directory That directory
Anything else The project's top directory

You do not configure this. If your project keeps its code in src/ — because uv, setuptools, hatch or poetry put it there — pb reads that from the same pyproject.toml your build backend does, so import robot.motors finds src/robot/motors.py and nothing has to be said twice.

When there are several projects in one repository, or when you are running from somewhere else entirely, -C says which project you mean.

There are no packages on the hub

A hub looks a module up by its whole name. robot.motors is simply a module whose name has a dot in it, which has two consequences worth knowing.

You do not need __init__.py. A folder of modules works as it stands. If you have __init__.py files because your project is also a Python package, they are ignored — importing robot on its own is importing a module called robot, which is robot.py, not the folder.

Relative imports do not work. from . import helpers is refused, and pb says which line it is on rather than letting the hub fail halfway through your program:

$ pb run demo.py
The hub cannot do relative imports (robot/motors.py:3).

Write from robot import helpers instead, or import helpers if it sits at the top.

One more oddity from the flat namespace: import robot.motors gives you the robot.motors module under the name robot, so robot.motors.spin() will not work. from robot.motors import spin and import robot.motors as motors both do what you expect, and one of those is what most people write anyway.

Modules that are already compiled

A .mpy file beside your script is sent as it is, without being compiled:

demo.py
fast.mpy       import fast

This is the only way to use a module built for a hub's native architecture, which cannot be produced from Python source. A .py wins over a .mpy of the same name, so a stale one cannot shadow the file you are editing, and pb checks the .mpy is one this hub's firmware will accept rather than letting the hub refuse the whole program without saying which file was wrong.

What the hub calls your program

The first module is the one that runs, and its name is what the hub shows — so pb run demo.py gives a program called demo. On firmware older than Pybricks profile v1.5.0 the entry module has to be called __main__ instead, and pb does that for you when it sees such a hub; you will only notice because the hub calls the program __main__.

pb run -c 'print(1)' has no file to be named after, so its one module is always __main__.