Making Python scripts work both as imports and executables - is there a clean solution?

Hi everyone!

Over at Mesa (a Python library for agent-based modeling) we’ve run into an annoying problem with our example models. The issue comes up when we try to make our code work both as importable modules and as directly runnable scripts.

Here’s what we’re dealing with:

Our project looks like this:

mesa/
├── mesa/
│   ├── __init__.py
│   └── examples/
│       ├── __init__.py
│       └── basic/
│           ├── __init__.py
│           └── boid_flockers/
│               ├── __init__.py
│               ├── agents.py
│               ├── model.py
│               ├── app.py
...

The problem shows up in files like model.py, where we’re using relative imports:

from .agents import Boid

This make the models importable with:

from mesa.examples import BoidFlockers

However, when we try to run our app.py visualisation or run it from the model.py directly, we get stuck on a relative import error:

ImportError: attempted relative import with no known parent package

We’ve tried a few approaches to fix this:

  1. We could use absolute imports like from mesa.examples.advanced.epstein_civil_violence.agents import Citizen, Cop. This works, but it’s pretty verbose and requires the package to be installed or in PYTHONPATH.

  2. We tried the try-except approach:

    try:
        from agents import Citizen, Cop
    except ImportError:
        from .agents import Citizen, Cop
    

    But this feels hacky and could hide real import errors.

  3. We also considered messing with sys.path, but that feels like asking for trouble down the road.

What I’m really looking for is a clean solution that:

  • Keeps the nice relative imports
  • Lets users run example files directly (like solara run app.py)
  • Doesn’t require sys.path tricks or try-except blocks
  • Follows Python best practices

Has anyone solved this elegantly? I’d love to hear your thoughts! For context, you can check out our examples here if you’re curious, and our own discussion on this here.

1 Like

Pretty sure if you run it with the -m option, it should work, i.e. python -m mesa.examples.basic.boid_flockers.app.

Otherwise you can also provide a thin __main__.py file so that python -m mesa boid_flockers works by dynamically importing and executing the cirrect example.

Note that e.g. the first solution you listed doesn’t quite work if the file being executed also gets imported by others: if that happens, there are now two versions of that module in memory with duplicate, non-related classes.

In general you should avoid executing python code in such a way that there is an entry in sys.path (e.g. the current directory) that points within a package. That will almost always cause issues at some point.

1 Like

Make the library installable, add a pyproject.toml with a project.scripts table that points to all your entrypoints.

It works great with relative imports, as the launcher pip builds for the user just imports your library itself. But if it’s installed you can then also refactor, and replace the import paths with those based on your root_package name.

Mesa even already has one:


[project.scripts]
mesa = "mesa.main:cli"
1 Like

You could set __package__ manually to the expected package (see PEP 366 – Main module explicit relative imports | peps.python.org for more details).

Before the first relative import, add


if __name__ == "__main__" and __package__ is None:
    __package__ = "mesa.examples.basic.boid_flockers"

and make sure mesa is a top-level package when you execute your script.

Like MegaIng says, you can use python -m mesa.examples.whatever and things will work.

To make python mesa/examples/whatever.py to also work, you could add:

if __name__ == "__main__":
   import sys
   import mesa.examples.whatever
   sys.exit()

to the top of whatever.py

You’ll need the root package to be on pythonpath though, e.g. via an editable install (or to do a sys.path trick)