Usage Guide

This guide walks through setting up a metapackage for an existing project.

Prerequisites

rind automatically detects and uses whatever versioning system your core package uses. It supports:

  • Static versions in pyproject.toml (no dependencies needed)

  • setuptools_scm or hatch-vcs for git tag-based versioning

  • Any PEP 517 backend as a fallback (calls the backend’s metadata hook)

Both packages will get their version from the same source, ensuring that mypackage==1.2.3 always installs mypackage-core==1.2.3.

Repository Structure

A typical setup has the metapackage configuration in a meta/ subdirectory:

myproject/
├── pyproject.toml          # Core package (mypackage-core)
│   mypackage/          # Actual code
│   └── __init__.py
└── meta/
    └── pyproject.toml      # Meta-package (mypackage)

or if you use the src layout for the main package:

myproject/
├── pyproject.toml          # Core package (mypackage-core)
├── src/
│   └── mypackage/          # Actual code
│       └── __init__.py
└── meta/
    └── pyproject.toml      # Meta-package (mypackage)

Step 1: Configure the Core Package

In your root pyproject.toml, change the package name to include e.g. -core and configure setuptools_scm for versioning:

[project]
name = "mypackage-core"  # Was: "mypackage"
dynamic = ["version"]
description = "My package (core)"
# ... rest of metadata

[build-system]
requires = ["setuptools>=61", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

Make sure dependencies are organized into the minimal required set plus optional extras:

[project]
dependencies = [
    "numpy>=1.20",
    "requests>=2.25",
]

[project.optional-dependencies]
# Features that require heavy dependencies
ml = ["tensorflow>=2.0", "scikit-learn>=1.0"]
viz = ["matplotlib>=3.5", "plotly>=5.0"]

# Development extras
test = ["pytest>=7.0", "pytest-cov"]
docs = ["sphinx>=7.0", "sphinx-rtd-theme"]

Important

The import name stays the same! Users still write import mypackage, even though the package is now named mypackage-core on PyPI.

Note

You can technically choose to not rename your core package and give your metapackage a different name (say mypackage-all), although for established packages users will still need to discover the new metapackage name, which may not be much of an improvement compared to discovering extras. But it is your choice!

Step 2: Create the Meta-Package

Create meta/pyproject.toml:

[build-system]
requires = ["rind"]
build-backend = "rind"

[tool.rind]
# Path to core package directory
core-path = ".."

# This is the metapackage name
name = "mypackage"

# Optional: override the description
description = "My package (batteries included)"

# Extras from core to make required in the metapackage
include-extras = ["ml", "viz"]

# Extras to pass through and expose on the metapackage (still optional)
passthrough-extras = ["test", "docs"]

Step 3: Build and Release

Both packages should be built and released simultaneously with the same version:

# Tag the release
$ git tag v1.0.0
$ git push --tags

# Build the core package
$ python -m build .

# Build the metapackage
$ python -m build meta/

# Upload both to PyPI
$ twine upload dist/* meta/dist/*

This creates and uploads:

  • dist/mypackage_core-1.0.0.tar.gz

  • dist/mypackage_core-1.0.0-py3-none-any.whl

  • meta/dist/mypackage-1.0.0.tar.gz

  • meta/dist/mypackage-1.0.0-py3-none-any.whl

Note

If you use automated CI workflows (e.g., GitHub Actions) for building and releasing packages, you’ll need to update them to build and upload both packages.

User Experience

After release, users can choose their install:

Command

Result

pip install mypackage-core

Minimal: numpy, requests only

pip install mypackage-core[ml]

Core + TensorFlow, scikit-learn

pip install mypackage

Full: core + ml + viz

pip install mypackage[test]

Full + pytest

All options provide import mypackage because the actual code lives in mypackage-core but the package directory is named mypackage/.

Standalone Mode

rind also supports standalone mode for creating metapackages that don’t wrap a core package. This is useful for creating curated dependency bundles.

In standalone mode, you specify all metadata directly in [project] without a core-path:

[build-system]
requires = ["rind"]
build-backend = "rind"

[project]
name = "my-data-science-stack"
version = "1.0.0"
description = "A curated collection of data science packages"
requires-python = ">=3.9"
dependencies = [
    "pandas>=2.0",
    "numpy>=1.24",
    "matplotlib>=3.7",
]

[project.optional-dependencies]
ml = ["scikit-learn>=1.3", "tensorflow>=2.13"]

Build with:

$ python -m build .

This creates a metapackage that bundles the listed dependencies. Users can then:

$ pip install my-data-science-stack      # Get pandas, numpy, matplotlib
$ pip install my-data-science-stack[ml]  # Also get scikit-learn, tensorflow

Note

In standalone mode, the version must be specified statically in [project]. Dynamic versioning requires a core package with core-path.