PEP8 – import not at top of file with sys.path
Question:
Problem
PEP8 has a rule about putting imports at the top of a file:
Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.
However, in certain cases, I might want to do something like:
import sys
sys.path.insert("..", 0)
import my_module
In this case, the pep8
command line utility flags my code:
E402 module level import not at top of file
What is the best way to achieve PEP8 compliance with sys.path
modifications?
Why
I have this code because I’m following the project structure given in The Hitchhiker’s Guide to Python.
That guide suggests that I have a my_module
folder, separate from a tests
folder, both of which are in the same directory. If I want to access my_module
from tests
, I think I need to add ..
to the sys.path
Answers:
To comply with the pep8, you should include your project path to the python path in order to perform relative / absolute imports.
To do so, you can have a look at this answer: Permanently add a directory to PYTHONPATH
Often I have multiple files with tests in a subdirectory foo/tests
of my project, while the modules I’m testing are in foo/src
. To run the tests from foo/tests
without import errors I create a file foo/tests/pathmagic.py
that looks like this;
"""Path hack to make tests work."""
import os
import sys
bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)
In every test file, I then use
import pathmagic # noqa
as the first import. The “noqa” comment prevents pycodestyle
/pep8
from complaining about an unused import.
There is another workaround.
import sys
... all your other imports...
sys.path.insert("..", 0)
try:
import my_module
except:
raise
If there are just a few imports, you can just ignore PEP8 on those import
lines:
import sys
sys.path.insert("..", 0)
import my_module # noqa: E402
I’ve just struggled with a similar question, and I think I found a slightly nicer solution than the accepted answer.
Create a pathmagic
module that does the actual sys.path manipulation, but make the change within a context manager:
"""Path hack to make tests work."""
import os
import sys
class context:
def __enter__(self):
bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)
def __exit__(self, *args):
pass
Then, in your test files (or wherever you need this), you do:
import pathmagic
with pathmagic.context():
import my_module
# ...
This way you don’t get any complaints from flake8/pycodestyle, you don’t need special comments, and the structure seems to make sense.
For extra neatness, consider actually reverting the path in the __exit__
block, though this may cause problems with lazy imports (if you put the module code outside of the context), so maybe not worth the trouble.
EDIT: Just saw a much simpler trick in an answer to a different question: add assert pathmagic
under your imports to avoid the noqa
comment.
Did you already try the following:
import sys
from importlib import import_module
sys.path.insert("..", 0)
# import module
my_mod = import_module('my_module')
# get method or function from my_mod
my_method = getattr(my_mod , 'my_method')
This problem already has several solutions that work, but in the case that you have a number of non-initial imports, and don’t want to annotate each with # noqa: E402
(or use one of the other proposals), the following works for a whole block:
import sys
sys.path.insert("..", 0)
if True: # noqa: E402
import my_module_1
import my_module_2
...
Given that the path to be added is relative to the script, this can be solved by using relative path.
from ..mypath import mymodule
from ..mypath.mysubfolder import anothermodule
Then do not need to use sys.path.insert()
anymore. Lint stoped complaining.
Problem
PEP8 has a rule about putting imports at the top of a file:
Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.
However, in certain cases, I might want to do something like:
import sys
sys.path.insert("..", 0)
import my_module
In this case, the pep8
command line utility flags my code:
E402 module level import not at top of file
What is the best way to achieve PEP8 compliance with sys.path
modifications?
Why
I have this code because I’m following the project structure given in The Hitchhiker’s Guide to Python.
That guide suggests that I have a my_module
folder, separate from a tests
folder, both of which are in the same directory. If I want to access my_module
from tests
, I think I need to add ..
to the sys.path
To comply with the pep8, you should include your project path to the python path in order to perform relative / absolute imports.
To do so, you can have a look at this answer: Permanently add a directory to PYTHONPATH
Often I have multiple files with tests in a subdirectory foo/tests
of my project, while the modules I’m testing are in foo/src
. To run the tests from foo/tests
without import errors I create a file foo/tests/pathmagic.py
that looks like this;
"""Path hack to make tests work."""
import os
import sys
bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)
In every test file, I then use
import pathmagic # noqa
as the first import. The “noqa” comment prevents pycodestyle
/pep8
from complaining about an unused import.
There is another workaround.
import sys
... all your other imports...
sys.path.insert("..", 0)
try:
import my_module
except:
raise
If there are just a few imports, you can just ignore PEP8 on those import
lines:
import sys
sys.path.insert("..", 0)
import my_module # noqa: E402
I’ve just struggled with a similar question, and I think I found a slightly nicer solution than the accepted answer.
Create a pathmagic
module that does the actual sys.path manipulation, but make the change within a context manager:
"""Path hack to make tests work."""
import os
import sys
class context:
def __enter__(self):
bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)
def __exit__(self, *args):
pass
Then, in your test files (or wherever you need this), you do:
import pathmagic
with pathmagic.context():
import my_module
# ...
This way you don’t get any complaints from flake8/pycodestyle, you don’t need special comments, and the structure seems to make sense.
For extra neatness, consider actually reverting the path in the __exit__
block, though this may cause problems with lazy imports (if you put the module code outside of the context), so maybe not worth the trouble.
EDIT: Just saw a much simpler trick in an answer to a different question: add assert pathmagic
under your imports to avoid the noqa
comment.
Did you already try the following:
import sys
from importlib import import_module
sys.path.insert("..", 0)
# import module
my_mod = import_module('my_module')
# get method or function from my_mod
my_method = getattr(my_mod , 'my_method')
This problem already has several solutions that work, but in the case that you have a number of non-initial imports, and don’t want to annotate each with # noqa: E402
(or use one of the other proposals), the following works for a whole block:
import sys
sys.path.insert("..", 0)
if True: # noqa: E402
import my_module_1
import my_module_2
...
Given that the path to be added is relative to the script, this can be solved by using relative path.
from ..mypath import mymodule
from ..mypath.mysubfolder import anothermodule
Then do not need to use sys.path.insert()
anymore. Lint stoped complaining.