pythonpipsetuptoolshatch

How to define a script in the venv/bin dir with pyproject.toml (in hatch or any other wrapper)


Im unsure about the new doc on packaging with hatch and wonder if someone worked out how to define a script in a pip installable package. So in short I need to be able to direct python -m build to make a package with open_foo_bar.py as in the example, install into the (virtual env)/bin dir.

my package looks like this (after a python -m build step that generated dist dir)

pypi_package/
├── bin
│   └── open_foo_bar.py
├── dist
│   ├── foo-0.1.0-py3-none-any.whl
│   └── foo-0.1.0.tar.gz
├── pyproject.toml
├── README.md
└── test_pkg
    ├── foolib.py
    └── __init__.py

Im trying to get bin/open_foo_bar.py installed into the $(virtual env)/bin instead it installs it into the site-packages/bin

./lib/python3.10/site-packages/bin/open_foo_bar.py

myproject.toml is

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"


[project]
name = "FOO"
version = "0.1.0"
authors = [
  { name="Mr Foo", email="foo@bar.com" },
]
description = "a Foo bar without drinks"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

dependencies = [
  'requests'
]

[project.urls]
"Homepage" = "http://footopia.s/howto_foo"

This used to be easy by defining the scripts section in setup.py

setuptools.setup(
   ...
   scripts ['bin/script1'],
   ... 
)

Solution

  • In theory the Python spec defines how entrypoints and scripts are supposed to be handled.

    Hatch has chosen to do it a bit differently, and I'm not sure if this is even considered best-practice. This approach combines Forced Inclusion with Metadata Entrypoints/CLI.

    Here's how you might achieve your desired outcome:

    1. Add a main or run function to your script open_foo_bar.py
    2. Force hatch to make your script appear as part of the package once it is installed. You would add the lines:
    [tool.hatch.build.targets.sdist.force-include]
    "bin/open_foo_bar.py" = "test_pkg/open_foo_bar.py"
    
    [tool.hatch.build.targets.wheel.force-include]
    "bin/open_foo_bar.py" = "test_pkg/open_foo_bar.py"
    
    1. Create an entrypoint using the scripts section:
    [project.scripts]
    open_foo_bar = "test_pkg.open_foo_bar:main"
    

    When you install the package, open_foo_bar should be in your virtualenv bin/ directory, and it will call your main function within open_foo_bar.py, which will have been moved to reside within your package in site-packages.

    It's a hacky solution, but there doesn't appear to be a 1:1 feature for supporting arbitrary scripts. Hatch actually does mention the usage of an Entrypoints section, but it's catered towards plugin management.