How to import from a sibling directory in python3?
Asked Answered
G

2

11

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?

Gallstone answered 6/3, 2019 at 1:52 Comment(5)
from . import foo?Sinistrad
Does it work if you put a __init__.py in your top-level directory?Osier
@hd1: from . import parse results in ImportError: cannot import name 'parse'Gallstone
@jnrbsn: Sadly it does notGallstone
https://mcmap.net/q/14224/-sibling-package-imports is the answer.Sheikdom
O
6

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.

Osier answered 6/3, 2019 at 4:27 Comment(0)
U
1

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
Unprintable answered 8/7, 2020 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.