ARTICLE AD BOX
I feel like I am missing some basic concepts about how to use a hatchling CustomBuildHook.
During the package build process, I want to use a class from the same package that I am trying to build. I realize this creates a circular dependency when viewed at the package level. At the file or individual class level, there is no circular dependency. There is a clear linear pathway to build the package.
What I am trying to accomplish is:
At build time: Read some data from a source data text file Parse that data using a class that exists in the package Pickle the resulting object At import time: Load the pickled objectThe idea is that the text data gets parsed one time during build, rather than having to read and parse it each time the package is imported. This also allows me to .gitignore the pickled object. The pickled object should be a build artifact.
I have posted a minimal working example of the package here:
https://github.com/james-duvall/build-example
In pyproject.toml, I have commented out the build hook.
I am using uv as my package management tool, so here are the exact steps that allow me to build this project manually:
From the package root directory (where pyproject.toml is):
uv pip install -e . uv pip install hatchling uv run python >>> from hatch_build import compile_data >>> compile_data() >>> quit() uv buildObservations:
Installing the current package in editable mode allows me to import the package during the rest of the build steps. This doesn't cause any sort of circular dependency
Manually installing hatchling is required only to avoid an import error in the build_hatch.py file. During uv build, hatchling is already available because build-system.requires = ["hatchling"].
I can import and call compile_data without any issues. There is no circular import problem.
However, if I uncomment the [tool.hatch.build.targets.wheel.hooks.custom] line in pyproject.toml, I can't figure out how to build automatically. I know that I can create a shell script, but I would really like to accomplish this with pyproject.toml, uv, and hatchling only.
I have tried so many things that I have lost track. In general, the following don't work:
Adding build-system.requires = ["hatchling", "build-example"]. This creates a dependency that uv can't resolve even though build-example is installed in the current virtual environment as an editable package.
Adding [tool.uv.extra-build-dependencies] build-example = "build-example" or build-example = ".". Both of these just complain about unresolvable dependencies.
Adding [tool.uv.sources] build-example = { path = "." }. This complains about that a cyclic build dependency is detected.
Moving the Example class to a submodule. For me, this just added more confusion without addressing the circular import problem.
Using runpy.run_path(). At first, it seemed promising to execute a Python file using run_path and skip all the module problems. I was able to successfully import Example at build time and use it to parse data.txt. However, when pickle.dump tries to write the file, I get an error that pickle can't properly import Example. Apparently pickle needs to import the module even if the object already exists. I'm sure there are good reasons for this, but they are beyond me.
Using uv build --no-build-isolation. I don't really understand what this option does, but it came up in some of my error messages and web searches. In any case, it didn't solve my problem. If the solution does eventually require --no-build-isolation , I would love to have a more complete explanation than https://docs.astral.sh/uv/reference/cli/#uv-build--no-build-isolation
Thanks in advance.
