2

I have the following directory structure:

testcython/
    setup.py
    testcython/
        __init__.py
        foo.pyx
        stuff.py
        bar/
            __init__.pxd
            __init__.py
            bar.pxd
            bar.pyx

where the file contents are as follows:

bar.pxd

# cython: language_level=3

cdef int square(int x)

bar.pyx

# cython: language_level=3

cdef int square(int x):
    return x * x

foo.pyx

# cython: language_level=3

import cython
cimport numpy as np
import numpy as np

from .Bar cimport square

def do_square(x):
    return square(x)

stuff.py

from __future__ import print_function

from .Foo import do_square

def do():
    print(do_square(2))

setup.py

import os, sys

from Cython.Build import build_ext, cythonize
from setuptools import setup, Extension, find_packages

def ext_modules():
    import numpy as np

    include_dirs = ['.', np.get_include()]
    root_dir = os.path.abspath(os.path.dirname(__file__))
    bar_ext = Extension(
        "Bar",
        sources=[root_dir + "/testcython/bar/bar.pyx"],
        include_dirs=include_dirs,
    )
    foo_ext = Extension(
        "Foo",
        sources=[root_dir + "/testcython/foo.pyx"],
        include_dirs=include_dirs
    )
    exts = [bar_ext, foo_ext]

    return cythonize(exts)

REQUIREMENTS = [
    "numpy",
    "cython"
]

setup(
    name="testcython",
    packages=find_packages(),
    ext_package="testcython",
    ext_modules=ext_modules(),
    cmdclass={"build_ext" : build_ext},
    zip_safe=False,
    install_requires=REQUIREMENTS
)

Question

The problem is that when I attempt to install this (with pip install -e . in the top testcython directory), I get the following errors from Cython:

Complete output from command python setup.py egg_info:

    Error compiling Cython file:
    ------------------------------------------------------------
    ...

    import cython
    cimport numpy as np
    import numpy as np

    from .Bar cimport square
    ^
    ------------------------------------------------------------

    testcython/foo.pyx:7:0: relative cimport beyond main package is not allowed

    Error compiling Cython file:
    ------------------------------------------------------------
    ...
    import numpy as np

    from .Bar cimport square

    def do_square(x):
        return square(x)
              ^
    ------------------------------------------------------------

This answer (cython: relative cimport beyond main package is not allowed) implies that including the root dir ('.') in the include_dirs argument(s) of the Extension objects should resolve the issue.

Whilst this part of the Cython documentation mentions to use zip_safe=False in the args of setup when using the setuptools package.

As you can see from my setup.py file above, I have included both of these - yet I still receive the error above.

Note: If I change the name of the extensions (in Extension constructor) from Bar and Foo to testcython.Bar and testcython.Foo, respectively, then I get a different error:

Complete output from command python setup.py egg_info:

    Error compiling Cython file:
    ------------------------------------------------------------
    ...

    import cython
    cimport numpy as np
    import numpy as np

    from .Bar cimport square
    ^
    ------------------------------------------------------------

    testcython/foo.pyx:7:0: 'testcython/Bar/square.pxd' not found

    Error compiling Cython file:
    ------------------------------------------------------------
    ...
    import numpy as np

    from .Bar cimport square

    def do_square(x):
        return square(x)
              ^
    ------------------------------------------------------------
0 _
  • 10,524
  • 11
  • 77
  • 109
sjrowlinson
  • 3,297
  • 1
  • 18
  • 35
  • 1
    This isn't a case sensitive problem is it? You have `foo` and `bar` in your directory structure but `Foo` and `Bar` in your imports (and also your setup.py). – DavidW Jun 12 '19 at 11:51
  • 1
    And shouldn't it be `from .bar.Bar cimport square`? – DavidW Jun 12 '19 at 11:53
  • @DavidW No I think that you import from another Cython extension using its name as you created it in when making the `Extension` - which in this case in `Bar` as initialised in setup.py. – sjrowlinson Jun 12 '19 at 13:06
  • But `cimport` has to work at runtime and (mainly) compiletime. At runtime I think the files are in the right place, but at compiletime it's looking for the pxd files. For the purpose of finding those where you eventually put the modules is irrelevant. – DavidW Jun 12 '19 at 13:15
  • Hmmm, I see. I've tried switching to lowercase and using `from .bar.Bar cimport square` (and several other variations) but I still either get the relative import error, or unable to find the `.pxd` file. The one case which does result in successful compilation is using `from testcython.bar.Bar cimport square` in `foo.pyx` - however it then cannot find the module `testcython.bar.Bar` when trying to run `stuff.py`. Besides, I need to use relative imports in the actual project (rather than this minimal example) anyway. – sjrowlinson Jun 12 '19 at 15:49
  • Relevant: https://stackoverflow.com/questions/57493310/set-setuptools-to-create-cimportable-package-with-headers-availible – 0 _ Jun 30 '21 at 03:41
  • Relevant: https://stackoverflow.com/q/56115159/1959808 – 0 _ Jun 30 '21 at 03:42
  • Relevant: https://stackoverflow.com/questions/58083432/how-to-import-depending-cython-modules-from-parent-folder-in-python – 0 _ Jun 30 '21 at 03:43

1 Answers1

4

I resolved this issue, with the help of a colleague, so I'll mention the solution here in case it helps people in the future.

The problem is related to how the Cython modules are imported, and more specifically - where the .so file is placed upon building the extension. Originally, the Bar.so file was generated in the testcython directory - such that when attempting import from the bar sub-module, it couldn't find the corresponding shared object file.

To solve this I needed to use the name "bar.bar" when creating this extension, this then results in the .so file being generated into the testcython/bar directory. Then, in foo.pyx, to use members from this bar module the import had to be changed to from testcython.bar.bar cimport <name>.

Note:

Additionally, the function square shown in the question cannot be used from another Cython module in this form as no __pyx_capi__ is generated for free cdef functions. Instead, this function has to be wrapped in some cdef class as a static method in order to use it from another Cython module, i.e.:

cdef class Square:
    @staticmethod
    cdef int square(int x)

Then this can be imported, in foo.pyx for example, with from testcython.bar.bar cimport Square. The class Square then essentially acts like a "namespace".

sjrowlinson
  • 3,297
  • 1
  • 18
  • 35