Skip to content

The fyn build backend

A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel.

fyn supports all build backends (as specified by PEP 517), but also provides a native build backend (fyn_build) that integrates tightly with fyn to improve performance and user experience.

Choosing a build backend

The fyn build backend is a great choice for most Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with fyn, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast.

The fyn build backend currently only supports pure Python code. An alternative backend is required to build a library with extension modules.

Tip

While the backend supports a number of options for configuring your project structure, when build scripts or a more flexible project layout are required, consider using the hatchling build backend instead.

Using the fyn build backend

To use fyn as a build backend in an existing project, add fyn_build to the [build-system] section in your pyproject.toml:

pyproject.toml
[build-system]
requires = ["fyn_build>=0.10.14,<0.11.0"]
build-backend = "fyn_build"

Note

The fyn build backend follows the same versioning policy as fyn. Including an upper bound on the fyn_build version ensures that your package continues to build correctly as new versions are released.

To create a new project that uses the fyn build backend, use fyn init:

$ fyn init

When the project is built, e.g., with fyn build, the fyn build backend will be used to create the source distribution and wheel.

Bundled build backend

The build backend is published as a separate package (fyn_build) that is optimized for portability and small binary size. However, the fyn executable also includes a copy of the build backend, which will be used during builds performed by fyn, e.g., during fyn build, if its version is compatible with the fyn_build requirement. If it's not compatible, a compatible version of the fyn_build package will be used. Other build frontends, such as python -m build, will always use the fyn_build package, typically choosing the latest compatible version.

Modules

Python packages are expected to contain one or more Python modules, which are directories containing an __init__.py. By default, a single root module is expected at src/<package_name>/__init__.py.

For example, the structure for a project named foo would be:

pyproject.toml
src
└── foo
    └── __init__.py

fyn normalizes the package name to determine the default module name: the package name is lowercased and dots and dashes are replaced with underscores, e.g., Foo-Bar would be converted to foo_bar.

The src/ directory is the default directory for module discovery.

These defaults can be changed with the module-name and module-root settings. For example, to use a FOO module in the root directory, as in the project structure:

pyproject.toml
FOO
└── __init__.py

The correct build configuration would be:

pyproject.toml
[tool.fyn.build-backend]
module-name = "FOO"
module-root = ""

Namespace packages

Namespace packages are intended for use-cases where multiple packages write modules into a shared namespace.

Namespace package modules are identified by a . in the module-name. For example, to package the module bar in the shared namespace foo, the project structure would be:

pyproject.toml
src
└── foo
    └── bar
        └── __init__.py

And the module-name configuration would be:

pyproject.toml
[tool.fyn.build-backend]
module-name = "foo.bar"

Important

The __init__.py file is not included in foo, since it's the shared namespace module.

It's also possible to have a complex namespace package with more than one root module, e.g., with the project structure:

pyproject.toml
src
├── foo
│   └── __init__.py
└── bar
    └── __init__.py

While we do not recommend this structure (i.e., you should use a workspace with multiple packages instead), it is supported by setting module-name to a list of names:

pyproject.toml
[tool.fyn.build-backend]
module-name = ["foo", "bar"]

For packages with many modules or complex namespaces, the namespace = true option can be used to avoid explicitly declaring each module name, e.g.:

pyproject.toml
[tool.fyn.build-backend]
namespace = true

Warning

Using namespace = true disables safety checks. Using an explicit list of module names is strongly recommended outside of legacy projects.

The namespace option can also be used with module-name to explicitly declare the root, e.g., for the project structure:

pyproject.toml
src
└── foo
    ├── bar
    │   └── __init__.py
    └── baz
        └── __init__.py

The recommended configuration would be:

pyproject.toml
[tool.fyn.build-backend]
module-name = "foo"
namespace = true

Stub packages

The build backend also supports building type stub packages, which are identified by the -stubs suffix on the package or module name, e.g., foo-stubs. The module name for type stub packages must end in -stubs, so fyn will not normalize the - to an underscore. Additionally, fyn will search for a __init__.pyi file. For example, the project structure would be:

pyproject.toml
src
└── foo-stubs
    └── __init__.pyi

Type stub modules are also supported for namespace packages.

File inclusion and exclusion

The build backend is responsible for determining which files in a source tree should be packaged into the distributions.

To determine which files to include in a source distribution, fyn first adds the included files and directories, then removes the excluded files and directories. This means that exclusions always take precedence over inclusions.

By default, fyn excludes __pycache__, *.pyc, and *.pyo.

When building a source distribution, the following files and directories are included:

From these, items matching tool.fyn.build-backend.source-exclude and the default excludes are removed.

When building a wheel, the following files and directories are included:

From these, tool.fyn.build-backend.source-exclude, tool.fyn.build-backend.wheel-exclude and the default excludes are removed. The source dist excludes are applied to avoid source tree to wheel builds including more files than source tree to source distribution to wheel build.

There are no specific wheel includes. There must only be one top level module, and all data files must either be under the module root or in the appropriate data directory. Most packages store small data in the module root alongside the source code.

Tip

When using the fyn build backend through a frontend that is not fyn, such as pip or python -m build, debug logging can be enabled through environment variables with RUST_LOG=fyn=debug or RUST_LOG=fyn=verbose. When used through fyn, the fyn build backend shares the verbosity level of fyn.

Include and exclude syntax

Includes are anchored, which means that pyproject.toml includes only <root>/pyproject.toml and not <root>/bar/pyproject.toml. To recursively include all files under a directory, use a /** suffix, e.g. src/**. Recursive inclusions are also anchored, e.g., assets/**/sample.csv includes all sample.csv files in <root>/assets or any of its children.

Note

For performance and reproducibility, avoid patterns without an anchor such as **/sample.csv.

Excludes are not anchored, which means that __pycache__ excludes all directories named __pycache__ regardless of its parent directory. All children of an exclusion are excluded as well. To anchor a directory, use a / prefix, e.g., /dist will exclude only <root>/dist.

All fields accepting patterns use the reduced portable glob syntax from PEP 639, with the addition that characters can be escaped with a backslash.