Project Layout
Maturin expects a particular project layout depending on the contents of the package.
Pure Rust project
For a pure Rust project, the structure is as expected and what you get from cargo new
:
my-rust-project/
├── Cargo.toml
├── pyproject.toml # required for maturin configuration
└── src
├── lib.rs # default for library crates
└── main.rs # default for binary crates
Maturin will add a necessary __init__.py
to the package when building the
wheel. For convenience, this file includes the following:
from .my_project import *
__doc__ = my_project.__doc__
if hasattr(my_project, "__all__"):
__all__ = my_project.__all__
such that the module functions may be called directly with:
import my_project
my_project.foo()
rather than:
from my_project import my_project
Note: there is currently no way to tell maturin to include extra data (e.g.
package_data
in setuptools) for a pure Rust project. Instead, consider using the layout described below for the mixed Rust/Python project.
Mixed Rust/Python project
To create a mixed Rust/Python project, add a directory with your package name
(i.e. matching lib.name
in your Cargo.toml
) to contain the Python source:
my-rust-and-python-project
├── Cargo.toml
├── my_project # <<< add this directory and put Python code in here
│ ├── __init__.py
│ └── bar.py
├── pyproject.toml
├── README.md
└── src
└── lib.rs
Note that in a mixed Rust/Python project, maturin does not modify the
existing __init__.py
in the root package, so now to import the rust module in
Python you must use:
from my_project import my_project
You can modify __init__.py
yourself (see above) if you would like to import
Rust functions from a higher-level namespace.
You can specify a different python source directory in pyproject.toml
by setting tool.maturin.python-source
, for example
pyproject.toml
[tool.maturin]
python-source = "python"
then the project structure would look like this:
my-rust-and-python-project
├── Cargo.toml
├── python
│ └── my_project
│ ├── __init__.py
│ └── bar.py
├── pyproject.toml
├── README.md
└── src
└── lib.rs
Note
This structure is recommended to avoid a common
ImportError
pitfall
Alternate Python source directory (src layout)
Having a directory with package_name
in the root of the project can
occasionally cause confusion as Python allows importing local packages and
modules. A popular way to avoid this is with the src
-layout, where the Python
package is nested within a src
directory. Unfortunately this interferes with
the structure of a typical Rust project. Fortunately, Python is not particular
about the name of the parent source directory.
maturin will detect the following src layout automatically:
my-rust-and-python-project
├── src # put python code in src folder
│ └── my_project
│ ├── __init__.py
│ └── bar.py
├── pyproject.toml
├── README.md
└── rust # put rust code in rust folder
|── Cargo.toml
└── src
└── lib.rs
Import Rust as a submodule of your project
If the Python module created by Rust has the same name as the Python package in a mixed Rust/Python project, IDEs might get confused.
You might also want to discourage end users from using the Rust functions directly by giving it a different name, say '_my_project'.
This can be done by adding module-name = <package name>.<rust pymodule name>
to the [tool.maturin]
in your pyproject.toml
. For example:
[tool.maturin]
module-name = "my_project._my_project"
You can then import your Rust module inside your Python source as follows:
from my_project import _my_project
IDEs can then recognize the _my_project
module as separate from your main Python source module. This allows for code completion of the types inside your Rust Python module for certain IDEs.
Adding Python type information
To distribute typing information, you need to add:
- an empty marker file called
py.typed
in the root of the Python package - inline types in Python files and/or
.pyi
"stub" files
In a pure Rust project, add type stubs in a <module_name>.pyi
file in the
project root. Maturin will automatically include this file along with the
required py.typed
file for you.
my-rust-project/
├── Cargo.toml
├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here
├── pyproject.toml
└── src
└── lib.rs
In a mixed Rust/Python project, additional files in the Python source dir (but
not in .gitignore
) will be automatically included in the build outputs
(source distribution and/or wheel). Type information can be therefore added to
the root Python package directory as you might do in a pure Python package.
This requires you to add the py.typed
marker file yourself.
my-project
├── Cargo.toml
├── python
│ └── my_project
│ ├── __init__.py
│ ├── py.typed # <<< add this empty file
│ ├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here
│ ├── bar.pyi # <<< add type stubs for bar.py here OR type bar.py inline
│ └── bar.py
├── pyproject.toml
├── README.md
└── src
└── lib.rs
Data
You can add wheel data by creating a <module_name>.data
folder or setting its location as data
in pyproject.toml under [tool.maturin]
or in Cargo.toml under [project.metadata.maturin]
.
The data folder may have the following subfolder:
data
: The contents of this folder will simply be unpacked into the virtualenvscripts
: Treated similar to entry points, files in there are installed as standalone executableheaders
: For.h
C header filespurelib
: This also exists, but seems to be barely usedplatlib
: This also exists, but seems to be barely used
If you add a symlink in the data directory, we'll include the actual file so you have more flexibility.