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.
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:
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.
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.
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.
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.
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.