snazzybouche
snazzybouche

Reputation: 2426

How to import from a sibling directory in python3?

I have the following file structure:

bot
├── LICENSE.md
├── README.md
├── bot.py # <-- file that is executed from command line
├── plugins
│   ├── __init__.py
│   ├── debug.py
│   └── parsemessages.py
├── helpers
│   ├── __init__.py
│   ├── parse.py
│   └── greetings.py
└── commands
    ├── __init__.py
    └── search.py

bot.py, when executed from the command line, will load in everything in the plugins directory.

I want plugins/parsemessages.py to import parse from the helpers directory, so I do that:

# parsemessages.py
from ..helpers import parse
parse.execute("string to be parsed")

I run python3 bot.py from the command line.

I get the following error:

File "/home/bot/plugins/parsemessages.py", line 2, in <module>
  from ..helpers import parse
ValueError: attempted relative import beyond top-level package

So I change two dots to one:

# parsemessages.py
from .helpers import parse
parse.execute("string to be parsed")

...but I get another error:

File "/home/bot/plugins/parsemessages.py", line 2, in <module>
  from .helpers import parse
ImportError: No module named 'plugins.helpers'

How can I get this import to work?

It's worth noting that I'm not attempting to make a package here, this is just a normal script. That being said, I'm not willing to mess around with sys.path - I want this to be clean to use.

Additionally, I want parse to be imported as parse - so for the example above, I should be typing parse.execute() and not execute().

I found this post and this post, but they start with a file that's quite deep in the file structure (mine is right at the top). I also found this post, but it seems to be talking about a package rather than just a regular .py.

What's the solution here?

Upvotes: 11

Views: 4625

Answers (2)

Marcos Bressan
Marcos Bressan

Reputation: 19

If you want to execute your code from the root, my best answer to this is adding to the Path your root folder with os.getcwd(). Be sure your sibling folder has a init.py file.

import os
os.sys.path.insert(0, os.getcwd())

from sibling import module

Upvotes: 1

jnrbsn
jnrbsn

Reputation: 2533

You could remove the dots, and it should work:

# parsemessages.py
from helpers import parse
parse.execute("string to be parsed")

That's probably your best solution if you really don't want to make it a package. You could also nest the entire project one directory deeper, and call it like python3 foo/bot.py.

Explanation:

When you're not working with an actual installed package and just importing stuff relative to your current working directory, everything in that directory is considered a top-level package. In your case, bot, plugins, helpers, and commands are all top-level packages/modules. Your current working directory itself is not a package.

So when you do ...

from ..helpers import parse

... helpers is considered a top-level package, because it's in your current working directory, and you're trying to import from one level higher than that (from your current working directory itself, which is not a package).

When you do ...

from .helpers import parse

... you're importing relative to plugins. So .helpers resolves to plugins.helpers.

When you do ...

from helpers import parse

... it finds helpers as a top-level package because it's in your current working directory.

Upvotes: 6

Related Questions