How It Works
This page explains the technical details of how rind works.
PEP 517 Build Backend
rind is a PEP 517 build
backend. When you run python -m build, the build frontend:
Creates an isolated virtual environment
Installs the packages listed in
build-system.requiresImports the module specified in
build-system.build-backendCalls the appropriate hook functions (
build_wheel,build_sdist)
The backend implements these hooks to generate packages without any Python code.
Wheel Structure
A typical Python wheel contains:
mypackage-1.0.0-py3-none-any.whl
├── mypackage/
│ ├── __init__.py
│ └── module.py
└── mypackage-1.0.0.dist-info/
├── METADATA
├── WHEEL
└── RECORD
A rind wheel contains only the metadata:
mypackage-1.0.0-py3-none-any.whl
└── mypackage-1.0.0.dist-info/
├── METADATA
├── WHEEL
└── RECORD
This is perfectly valid according to the wheel specification. When pip installs this wheel, it:
Records that
mypackage==1.0.0is installedInstalls all packages listed in
Requires-DistLeaves no Python files (because there are none)
Version Pinning
rind automatically uses whatever versioning system your core package uses.
It reads the core package’s pyproject.toml and detects:
Static versions: If
versionis in[project]and not indynamic, it’s used directly.setuptools_scm / hatch-vcs: If detected in build requirements or tool config, rind calls setuptools_scm directly for fast version detection.
Other backends: As a fallback, rind calls the core’s build backend via
prepare_metadata_for_build_wheelto get the version.
When you tag a release and build both packages:
$ git tag v1.2.3
$ python -m build . # In repo root
$ python -m build meta/ # In meta/ directory
Both builds use the same version source, so both get version 1.2.3.
The metapackage’s METADATA file contains:
Requires-Dist: mypackage-core==1.2.3
This ensures that pip install mypackage==1.2.3 always installs
mypackage-core==1.2.3.
Important
Version pinning only works correctly when both packages are built from the same git commit. Always build and release them together.
Metadata Inheritance
When inherit-metadata is true (the default), the backend:
Reads the core package’s
pyproject.toml(viacore-path)Extracts the
[project]tableUses those values as defaults for the metapackage
The inheritance priority is:
[tool.rind]values (highest priority)[project]values in the metapackage’s ownpyproject.tomlInherited values from core’s
pyproject.toml(lowest priority)
Set inherit-metadata = false to disable this behavior and only use the
core’s pyproject.toml for version detection.
Sdist Structure
When building a wheel from an sdist, the core package’s pyproject.toml isn’t
available (the sdist is extracted to a temporary directory).
To handle this, rind generates a resolved pyproject.toml in the sdist:
During
build_sdist, all metadata is computed from the core packageA new
pyproject.tomlis generated with all values hardcoded (version, dependencies, inherited metadata, etc.)This resolved file has no
core-pathsetting, which tells rind to read values directly from[project]instead of computing them
This ensures wheels built from sdists have the same metadata as wheels built directly from the repository, without needing access to the core package.
Import Name vs Package Name
A key feature of this setup is that the import name stays the same while the package name changes.
Consider this structure:
myproject/
├── pyproject.toml # name = "mypackage-core"
└── src/
└── mypackage/ # Import name: mypackage
└── __init__.py
The package installed via pip is mypackage-core, but the directory
containing the code is mypackage/, so users write:
import mypackage # Works!
When mypackage (the metapackage) is installed, it pulls in mypackage-core,
which provides the mypackage/ directory. The metapackage itself provides
no Python files, so there’s no conflict.
Comparison with Alternatives
Why not use extras on a single package?
You could use optional dependencies:
[project]
name = "mypackage"
dependencies = ["numpy"] # minimal
[project.optional-dependencies]
recommended = ["pandas", "matplotlib"]
Users install with pip install mypackage[recommended].
The metapackage approach is better when:
You want the “batteries included” experience to be the default
The package name without brackets should give the full experience
You want clear separation between “essential” and “recommended”
Why not two repositories?
You could maintain separate repos for core and meta packages. The single-repo approach is better because:
Versions are automatically synchronized via git tags
No coordination needed between repos for releases
Single source of truth for metadata
Easier CI/CD setup
Standalone Mode
rind also supports a standalone mode for creating metapackages without a core package. In this mode:
All metadata is specified directly in
[project]No
core-pathis neededVersion must be static (no dynamic versioning)
No metadata inheritance
This is useful for creating curated dependency bundles like “my-data-science-stack” that group related packages together.
Limitations
Both packages must be released together (core-package mode only): Since versions are synchronized, you can’t release one without the other.
No code in metapackage: The metapackage cannot contain any Python code. If you need wrapper code, it should go in the core package.
Static version in standalone mode: Standalone metapackages require a static version in
[project].