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: .. code-block:: text 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: .. code-block:: text 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: .. code-block:: toml [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: .. code-block:: toml [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``: .. code-block:: 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: .. code-block:: bash # 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: .. list-table:: :header-rows: 1 :widths: 40 60 * - 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``: .. code-block:: toml [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: .. code-block:: bash $ python -m build . This creates a metapackage that bundles the listed dependencies. Users can then: .. code-block:: bash $ 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``.