DEV Community

John Wick
John Wick

Posted on

Fixing "Attempted Relative Import" Errors with StayPresent's bot_module

How to fix the python attempted relative import error when deploying a packaged bot, using StayPresent's bot_module parameter instead of bot_file.

Fixing "Attempted Relative Import" Errors with StayPresent's bot_module

If your bot is structured as a proper Python package — with relative imports like from . import handlers scattered across its modules — you've probably hit this the moment you tried to run it directly:

ImportError: attempted relative import with no known parent package
Enter fullscreen mode Exit fullscreen mode

This is one of the most common attempted relative import errors developers run into when deploying a packaged bot, and it has nothing to do with your bot's logic — it's purely about how the script gets launched. This guide covers why it happens and how StayPresent's bot_module parameter fixes it without restructuring your project.

Table of Contents

  1. Why the Error Happens
  2. python file.py vs python -m module
  3. Using bot_module
  4. Multiple Module-Based Bots
  5. Mixing File-Based and Module-Based Bots
  6. What's Different From bot_file
  7. Full Example
  8. Best Practices
  9. Common Mistakes
  10. FAQs
  11. Conclusion

Why the Error Happens

Python treats a script run directly (python bot.py) as a top-level module with no package context — it has no __package__, so any from . import something inside it (or inside anything it imports) fails immediately, because there's no "parent package" for the dot to refer to. This has nothing to do with your file structure being wrong; it's purely a consequence of how the interpreter was invoked.

python file.py vs python -m module

Running python mypkg/bot.py executes the file as a standalone script. Running python -m mypkg.bot instead executes it as a module inside its package, with __package__ set correctly — which is exactly what relative imports need to resolve.

project/
├── mypkg/
│   ├── __init__.py
│   ├── bot.py        # uses `from . import handlers`
│   └── handlers.py
Enter fullscreen mode Exit fullscreen mode
python mypkg/bot.py       # ImportError: attempted relative import
python -m mypkg.bot       # works correctly
Enter fullscreen mode Exit fullscreen mode

Using bot_module

staypresent.run()'s bot_file parameter runs your bot the same way python <file> would — which means it inherits this exact limitation. bot_module runs it the way python -m <module> would instead:

import staypresent

staypresent.run(bot_module="mypkg.bot")
Enter fullscreen mode Exit fullscreen mode

No changes to your bot's own code are needed — the fix is entirely in how StayPresent launches it.

Multiple Module-Based Bots

Just like bot_file, bot_module accepts a list for multiple bots, each independently supervised:

staypresent.run(bot_module=["mypkg.bot", "mypkg.worker"])
Enter fullscreen mode Exit fullscreen mode

bot_args/env apply identically to every module in the list, exactly as they would with bot_file.

Mixing File-Based and Module-Based Bots

Inside the bots parameter, use "module" instead of "file" per entry — and you can freely combine both styles in the same call:

import staypresent

staypresent.run(bots=[
    {"file": "telegram_bot.py", "args": ["--verbose"]},
    {"module": "discord_bot.worker", "env": {"SHARD": "0"}},
])
Enter fullscreen mode Exit fullscreen mode

Each entry must set exactly one of "file"/"module" — setting both, or neither, raises TypeError.

What's Different From bot_file

A few important differences to be aware of:

  • No existence check up front. A file path can be checked for existence before anything starts; a module path genuinely can't be verified as importable without actually importing it — which would mean running its parent packages' __init__.py as a side effect inside the orchestrating process, something StayPresent deliberately avoids. A missing or misspelled module isn't caught until that bot subprocess actually starts and Python itself reports No module named '...' — which is then handled through the normal crash/restart logic like any other failure.
  • Working directory matters. The subprocess is launched from the same working directory as your orchestrating script. bot_module="mypkg.bot" needs to be resolvable from wherever you actually run your main.py, exactly as if you'd typed python -m mypkg.bot from that same location yourself.
  • Log labels use the dotted module path. Instead of a filename, log lines show something like bot[0] 'mypkg.bot'. If two bots share the exact same module string, there's no further-qualifying fallback the way there is for file paths (no "directory" to disambiguate with) — those log lines are distinguished only by their bot[i] index.

Full Example

import os
import staypresent

staypresent.web.json({"status": "running"})

staypresent.run(
    bot_module="mypkg.bot",
    port=int(os.getenv("PORT", 8080)),
    restart_on_crash=True,
    max_restarts=5,
)
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • If your bot already runs correctly locally via python -m mypkg.bot, use bot_module in staypresent.run() rather than trying to work around relative imports with sys.path hacks.
  • Keep your deployment's working directory consistent between local testing and production — if python -m mypkg.bot only works from a specific directory locally, that same constraint applies to your hosting platform's start command.
  • Since module paths aren't validated up front, watch your logs closely on the very first deploy of a module-based bot to catch a typo'd module path quickly.

Common Mistakes

  • Passing a file path to bot_module (e.g. bot_module="mypkg/bot.py") instead of a dotted module path ("mypkg.bot") — this will fail the same way running python -m mypkg/bot.py would from the command line.
  • Assuming bot_module validates the module exists before starting, and being surprised when a typo only surfaces as a crash-and-restart cycle rather than an immediate, upfront error.
  • Running the orchestrating script from the wrong directory, so mypkg.bot can't be found even though the code itself is correct.

FAQs

Do I need to change my bot's code to use bot_module?
No — if your bot already works when launched with python -m, no code changes are needed at all.

Can I mix bot_file and bot_module at the top level of run()?
No, they're mutually exclusive at the top level — use the bots list if you need both styles in the same deployment.

Why doesn't StayPresent just validate the module up front like it does for files?
Verifying a module is importable requires actually importing it, which means executing its package's __init__.py as a side effect — something StayPresent avoids doing inside the process that's supposed to just be orchestrating, not running, your bot's code.

Conclusion

The attempted relative import error is one of the most common surprises when deploying a properly packaged bot. bot_module solves it by launching your bot exactly the way python -m would, with no restructuring of your project and no sys.path workarounds required.

pip install staypresent[prod]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)