I am trying to use explicit relative imports in cython. From the release notes it seems like relative imports should work after cython 0.23, and I'm using 0.23.4 with python 3.5. But I get this strange error that I cannot find many references to. The error is only from the cimport:
driver.pyx:4:0: relative cimport beyond main package is not allowed
The directory structure is:
myProject/
setup.py
__init__.py
test/
driver.pyx
other.pyx
other.pxd
It seems like I'm probably messing up in setup.py so I included all the files below.
setup.py
from setuptools import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [
Extension('other', ['test/other.pyx'],),
Extension('driver', ['test/driver.pyx'],),
]
setup(
name='Test',
ext_modules=ext_modules,
include_dirs=["test/"],
cmdclass={'build_ext': build_ext},
)
driver.pyx
#!/usr/bin/env python
from . import other
from . cimport other
other.pyx
#!/usr/bin/env python
HI = "Hello"
cdef class Other:
def __init__(self):
self.name = "Test"
cdef get_name(self):
return self.name
other.pxd
cdef class Other:
cdef get_name(self)
I've tried moving __init__.py
into test/
. I've tried running setup.py
in the test
directory (adjusting the include_dirs
appropriately). They both give the same error.
If I do cimport other
and remove the .
it works but this is a toy example and I need relative imports so other folders can import properly. This is the only example I can find for this error and I'm pretty confident my issue is different.
There are at least four solutions to the Cythonization error (these results are with cython == 0.29.24
):
adding the file example_package/__init__.pxd
and changing the names of the Extension
s being built to be submodules of the module being built, i.e., example_package.other
and example_package.driver
(in the question these would be Test.other
and Test.driver
).
This change is anyway necessary for importing the installed submodules driver
and other
, as described below. Note that the installed package is actually a namespace package in this case, due to absence of the keyword parameter and argument packages=['example_package']
, as discussed below.
adding the file example_package/__init__.py
and changing the names of the Extension
s being built to be submodules of the module being built, i.e., example_package.other
and example_package.driver
. Even in this case, where an __init__.py
is present, the installed package example_package
will be a namespace package. Turning it into a regular package requires passing packages=['example_package']
to the function setuptools.setup
.
Likewise to adding an __init__.pxd
, this change is necessary for importing the installed submodules.
adding the file example_package/__init__.pxd
and changing the cimport
statement to an absolute cimport
inside the file example_package/driver.pyx
(The package builds and installs with this alternative, but does not import, due to the need to also change the names of Extension
s):
from . import other
from example_package cimport other
adding the file example_package/__init__.py
and changing the cimport
statement to an absolute cimport
inside the file example_package/driver.pyx
, as done in the previous item. The package builds and installs with this, but does not import.
The question is explicitly asking for relative imports, so in that sense, the first two alternatives are the answers to the question, because they do work with relative imports.
Either of the four changes listed above avoids the following error:
Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------
example_package/driver.pyx:2:0: relative cimport beyond main package is not allowed
but as already noted above, and also discussed below, the change of Extension
names of the first or second alternatives is necessary for importing the installed submodules (additionally passing the parameter and keyword argument packages=[PACKAGE_NAME]
in the fourth alternative allows the Python package example_package
to import, but not its submodules driver
and other
).
setup.py
The file setup.py
that I recommend, with all additional changes (not only those changes necessary for building and installing, listed above) is:
"""Installation script."""
import os
import setuptools
try:
from Cython.Build import cythonize
cy_ext = f'{os.extsep}pyx'
except ImportError:
# this case is intended for use when installing from
# a source distribution (produced with `sdist`),
# which, as recommended by Cython documentation,
# should include the generated `*.c` files,
# in order to enable installation in absence of `cython`
print('`import cython` failed')
cy_ext = f'{os.extsep}c'
PACKAGE_NAME = 'example_package'
def run_setup():
"""Build and install package."""
ext_modules = extensions()
setuptools.setup(
name=PACKAGE_NAME,
ext_modules=ext_modules,
packages=[PACKAGE_NAME],
package_dir={PACKAGE_NAME: PACKAGE_NAME})
def extensions():
"""Return C extensions, cythonize as needed."""
extensions = dict(
other=setuptools.extension.Extension(
f'{PACKAGE_NAME}.other',
sources=[f'{PACKAGE_NAME}/other{cy_ext}'],),
driver=setuptools.extension.Extension(
f'{PACKAGE_NAME}.driver',
sources=[f'{PACKAGE_NAME}/driver{cy_ext}'],))
if cy_ext == f'{os.extsep}pyx':
ext_modules = list()
for k, v in extensions.items():
c = cythonize(
[v],
# show_all_warnings=True # this line requires `cython >= 3.0`
)
ext_modules.append(c[0])
else:
ext_modules = list(extensions.values())
return ext_modules
if __name__ == '__main__':
run_setup()
The other changes in this answer are not necessary for successfully building and installing the package, but recommended for other reasons. For some of the other changes, I describe the motivation below.
Note that:
example_package/__init__.pxd
or example_package/__init__.py
is insufficient, andExtension
names is insuffient, andcimport
statement to from example_package cimport other
is insufficient.Two of these changes are needed together for building and installing, i.e., one of the four alternatives listed earlier.
For being able to import the extension modules built from the Cython sources driver.pyx
and other.pyx
, it is also necessary to change the names of the extensions to:
Extension('example_package.other', ...)
Extension('example_package.driver', ...)
Note that this makes import
work because now example_package
has become a namespace package (CPython glossary entry):
>>>
<module 'example_package' (namespace)>
>>> import example_package.driver
>>> import example_package.other
(Also, I have omitted the parameter include_dirs
of setuptools.setup
in the setup.py
file that I used, and which I include below.)
These changes are needed for building and installing the package, and for importing the extension modules. For importing the installed package from Python in case it did not include any extensions modules (and thus had not become a namespace package):
__init__.py
needs to be added in the directory example_package/
(which in the question is the directory Test/
), andpackages=[
example_package],
needs to be passed to the function setuptools.setup
.Otherwise, the statement import example_package
will raise a ModuleNotFoundError
. The addition of an __init__.py
file is also necessary to make the package a regular package (CPython glossary entry), which is usually what is intended, instead of a namespace package.
__init__.pxd
A regular Python package includes an __init__.py
file. An __init__.pxd
file is only relevant in case other packages need the *.pxd
headers. If this is not the case, it seems that the file example_package/__init__.py
suffices, since the four solutions above are essentially two solutions, each with either __init__.py
or __init__.pxd
as alternatives.
So my recommendation for files and their arrangement is:
.
├── example_package
│ ├── __init__.py
│ ├── driver.pyx
│ ├── other.pxd
│ └── other.pyx
└── setup.py
Only adding the __init__.pxd
file raises the cythonization error:
Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: relative cimport beyond main package is not allowed
and only changing the cimport
statement (without an __init__.pxd
) raises the cythonization error:
Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: 'example_package.pxd' not found
Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: 'example_package/other.pxd' not found
Above I write example_package
as the package's name, though I did build and install the example also with the name Test/
as it is named in the question, in order to ensure that this indeed works, and so that the minimal changes needed are the __init__.pxd
file and the from example_package cimport other
.
For uniformity, I actually also renamed the directory to Test/
when running setup.py
with this name of the package, but I am not on a case-sensitive filesystem at the moment, so I do not know whether a directory named test/
together with the keyword argument name='Test',
in setup.py
, as in the question, would have caused issues on a case-sensitive filesystem.
So:
Test
as package name and Test
as directory name worked for me for building and installing, andtest
as package name and test
as directory name worked for me for building and installing.I recommend using another package name. Also, for reasons described below:
Test
is done with the statement import Test
. Writing import test
will import another package (see below).test
as package name does not import the installed test
package, for reasons explained below, even if an __init__.py
file is added.In any case, for reasons explained below, my recommendation is to change the package name, even if it is intended to be an auxiliary package that is intended to be used only as a test harness for the main package.
Also, lowercase package naming is mandated by PEP 8, thus leading to test
, which may be understood to be a directory of tests, which is not the case if this actually is intended to be an example of a main package.
The error that happens after building and installing, when the package and directory are named test
is (the dots ... are the result of editing the actual output):
>>> import test
>>> test
<module 'test' from '.../lib/python3.9/test/__init__.py'>
In other words, CPython includes a package called test
:
The
test
package contains all regression tests for Python as well as the modulestest.support
andtest.regrtest
.
Therefore, the name test
cannot be used for an example package that is intended to be imported after installation (though the package does get built and installed, and even uninstalled by pip uninstall -y test
, fine).
Another detail is that from test cimport other
is actually wrong, even though it compiles, because had the built test
package actually been magically imported somehow (in the presence of CPython's test
package), at runtime this cimport
statement would have defaulted to CPython's test
package. Nonetheless, Cython's translation may transform this cimport
to some other form that would have actually imported from test.other
of the built package. Since the import of the installed test
package appears to be impossible in the presence of CPython's test
package, it is unknown whether this cimport
would have raised a runtime error.
Also, note that:
Note: The
test
package is meant for internal use by Python only. It is documented for the benefit of the core developers of Python. Any use of this package outside of Python’s standard library is discouraged as code mentioned here can change or be removed without notice between releases of Python.
Between all experiments, I run rm -rf build dist *.egg-info test/*.c
.
So before changing the file arrangement to the one shown earlier, what I used is the same with the question.
example_package
I changed the name of the package to example_package
, assuming that test/
contains the actual package to be installed, based on the argument given to the parameter name=
within the file setup.py
of the question.
The motivation for this renaming is that "test" or "tests" is usually used to name the directory of tests that accompany a Python package. There are many arrangements for such directories, and for how the tests are used. In the next section, I discuss my suggestion for arranging tests.
Regarding possibilities, arrangements other than what I describe in the next section have been used in general, including placing the tests in directories within the package itself. Given that the question writes myProject/
, and has a file myProject/__init__.py
, I am not sure whether the question actually uses such an arrangement.
In that case, though, driver
and other
would actually be test modules. Though installing the tests as a separate package (called Test
in the question), which is what the module myProject/setup.py
does, suggests that driver
and other
are the main package's modules, and thus that the main package is called "Test".
If not, i.e. if driver
and other
are actually test modules, and setup.py
is not the main package's setup script, but instead a setup script that builds and installs an "auxiliary" package that is intended for only testing the main package (which may in this case be named "myProject", with a setup.py
present in the directory that contains the directory myProject/
of the question), then my renaming of Test
to example_package/
would not correspond to this being the main package. (It is also interesting to have a test-harness package that includes Cython code and thus requires to be compiled--and possibly installed.)
In that case, perhaps Test
could be renamed instead to tests_of_example_package
. In other words, in that case it is relevant to include the word "test" in the package's name, though it seems that qualifying the package as auxiliary to example_package
is explicit. Explicit is better than implicit (PEP 20).
(Tests sometimes are arranged as packages (using __init__.py
), even when not installing this as an auxiliary Python package (intended as only a test harness of the main Python package it accompanies). The motivation is to enable importing common modules of the test suite that are used by multiple test modules, but are not themselves modules that are run directly by the test runner.)
If this is the main package, then I assume that "Test" was used for the purpose of writing an example in the example. If so, then my only reason for renaming (lowercase aside) is to distinguish the main package itself from its tests.
Lowercase names for Python packages are mandated by PEP 8:
Python packages should also have short, all-lowercase names, although the use of underscores is discouraged.
The underscore in example_package
is only for the sake of example.
Tests might be placed in a test/
directory that is in the same directory with the directory that contains the Python package, and is named after the package. I strongly recommend this approach, for example (this tree was created with the program tree
):
.
├── example_package
│ └── __init__.py
├── setup.py
└── tests
└── module_name_test.py
For testing without accidentally importing the package example_package
from its source directory, but from where it is installed (usually under site-packages/
), I recommend in all cases first cd
ing to the directory tests/
before running any tests. This is the most reliable approach to testing, does not rely on how each testing framework works, how the testing framework's various configuration options work, how the options interact with each other, nor how bugs in the testing framework itself affect testing.
In this way, the package source can be placed inside the directory example_package
, without any reason to use any other directory arrangement.
The shebang inside the *.pyx
files can be removed, because it has no effect. The shebang line is treated by Cython as a Python comment line that is moved to inside a C comment somewhere later inside the *.c
files that Cython generates from the *.pyx
files. So it has no effect. I am not aware of any use of shebang lines in C sources that are intended to be compiled by directly calling gcc
(or another C compiler), as Cython does (whether Cython calls gcc
, or another compiler depends on the system, environment path, environment variables, and other information).
Also, the shebang is only relevant to Python modules that might be executed as executables. This is not intended to be the case for modules inside a Python package, so shebang lines are almost never used there.
An exception might be a package module that might be infrequently be run directly during development, e.g., for experimentation or debugging purposes. Nonetheless, such a module would be expected to have a __main__
stanza.
So Python modules to which a shebang is relevant, also typically have a __main__
stanza.
For completeness, setup.py
is intended to be run as __main__
, and does have a __main__
stanza, but the way setup scripts are run (when not using pip
--using pip
is strongly recommended) is by python setup.py
, so there is no need for a shebang in setup.py
(no shebang appears there in the question--I just mention this for completeness).
setuptools
in setup.py
, instead of distutils
The distutils
module]() is deprecated as of Python 3.10, as specified in PEP 632, and will be removed in Python 3.12.
.c
, instead of .pyx
This is in accord with Cython recommendations:
It is strongly recommended that you distribute the generated
.c
files as well as your Cython sources, so that users can install your module without needing to have Cython available.
setup.py
that remain unchanged ("constants")
Module-scope Python variables that are intended to be used as constants, i.e., remain unchanged after the initial assignment, are mandated by PEP 8 to have identifiers that are uppercase with underscores:
Constants are usually defined on a module level and written in all capital letters with underscores separating words. Examples include
MAX_OVERFLOW
andTOTAL
.
Hence the identifier PACKAGE_NAME
.
I used formatted string literals, which require Python >= 3.6.
setup.py
This is good practice in general, enabling naming different sections of code via function names, executing the code only when run as __main__
, by including a __main__
stanza, and thus enabling importing setup.py
and using specific functionality that may be relevant to outside code (e.g., an installation framework) without necessarily running all code--e.g., without running the function setuptools.setup
.
The question presents a minimal working example, so a small setup.py
is relevant in the question. I am writing this section as recommendation for what to do in actual packages, not in questions.
The same observation applies to module and function docstrings inside setup.py
.
Also, I recommend the top-down arrangement of functions: callers above callees, because this layout is more readable.
I used os.extsep
for generality, though using a dot I think will still work, and is more readable.
As I noted earlier, the only change to the question's example that was needed to avoid the build error "relative cimport beyond main package is not allowed" was the addition of either an __init__.py
or an __init__.pxd
, and either an absolute cimport
inside driver.pyx
or the renaming of the Extensions
.
__init__.py
In the final version, I removed the file __init__.py
that is in the same directory with setup.py
. My understanding is that this file has no effect in this example. If the example is intended to have test/
as the main package's directory, then any __init__.py
would appear inside test/
.
If test/
is actually an auxiliary package of tests for the main package, then the __init__.py
would be part of the main package, and unrelated to the test/
package. However, in that case, it seems that there would be a setup.py
file above myProject/
, which would be responsible for building both the main package, and the test-harness package.
The default language_level
in cython < 3.0.0
is 2, even on Python 3:
language_level
(2/3/3str) Globally set the Python language level to be used for module compilation. Default is compatibility with Python 2. To enable Python 3 source code semantics, set this to 3 (or 3str) at the start of a module or pass the "-3" or "--3str" command line options to the compiler.
The question uses Python 3.5 and cython == 0.23.4
, so this is the case.
The default Cython semantics are changing in cython >= 3.0.0
:
The default language level was changed to
3str
, i.e. Python 3 semantics, ...
With both Python 2 and Python 3 semantics (passing compiler_directives=dict(language_level=3)
, or installing the pre-release cython == 3.0.0a8
), the first two solutions (which use relative imports) do work.
Nonetheless, absolute imports are recommended by PEP 8:
Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured ...
Absolute imports are also robust to refactoring a package's structure. They are explicit, and explicit is better than implicit (PEP 20).
The resulting module driver.pyx
after this change would be:
from example_package import other
from example_package cimport other
The setup.py
code in this answer is based on what I have written in the file download.py
of the Python package dd
.
The only other example I could find of this error was in hal.pyx of the machinekit project. I was pretty confident that this was a different error but today I realized that after that error was solved machinekit was working which means explicit relative imports must work. Their setup.py
file refers to linuxcnc
which isn't in the directory tree but I guess is created at some point during compile time. The important thing is that the include_dirs
includes the parent directory instead of the child directory.
Translated to my project structure it would mean I put myProject
in include_dirs
instead of test/
. After read this guide for the second time I finally started to understand a little of how python thinks of packages. The problem was that the include_dirs
was the child directory. It seems like this effectively made cython view it as a single flat directory in which case no relative imports would be allowed? An error like this might have made it more clear:
ValueError: Attempted relative import in non-package
I still don't have a sufficiently deep understanding to know exactly what was going on but luckily the solution was relatively simple. I just changed the include_dirs
to make cython recognize the nested file structure:
from setuptools import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [
Extension('other', ['test/other.pyx'],),
Extension('driver', ['test/driver.pyx'],),
]
setup(
name='Test',
ext_modules=ext_modules,
include_dirs=["."],
cmdclass={'build_ext': build_ext},
)
It all works now!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With