How do you get the filename of a Python wheel when running setup.py?

setuptools

If you are using a setup.py script to build the wheel distribution, you can use the bdist_wheel command to query the wheel file name. The drawback of this method is that it uses bdist_wheel's private API, so the code may break on wheel package update if the authors decide to change it.

from setuptools.dist import Distribution


def wheel_name(**kwargs):
    # create a fake distribution from arguments
    dist = Distribution(attrs=kwargs)
    # finalize bdist_wheel command
    bdist_wheel_cmd = dist.get_command_obj('bdist_wheel')
    bdist_wheel_cmd.ensure_finalized()
    # assemble wheel file name
    distname = bdist_wheel_cmd.wheel_dist_name
    tag = '-'.join(bdist_wheel_cmd.get_tag())
    return f'{distname}-{tag}.whl'

The wheel_name function accepts the same arguments you pass to the setup() function. Example usage:

>>> wheel_name(name="mydist", version="1.2.3")
mydist-1.2.3-py3-none-any.whl
>>> wheel_name(name="mydist", version="1.2.3", ext_modules=[Extension("mylib", ["mysrc.pyx", "native.c"])])
mydist-1.2.3-cp36-cp36m-linux_x86_64.whl

Notice that the source files for native libs (mysrc.pyx or native.c in the above example) don't have to exist to assemble the wheel name. This is helpful in case the sources for the native lib don't exist yet (e.g. you are generating them later via SWIG, Cython or whatever).

This makes the wheel_name easily reusable in the setup.py script where you define the distribution metadata:

# setup.py
from setuptools import setup, find_packages, Extension
from setup_helpers import wheel_name

setup_kwargs = dict(
    name='mydist',
    version='1.2.3',
    packages=find_packages(),
    ext_modules=[Extension(...), ...],
    ...
)
file = wheel_name(**setup_kwargs)
...
setup(**setup_kwargs)

If you want to use it outside of the setup script, you have to organize the access to setup() args yourself (e.g. reading them from a setup.cfg script or whatever).

This part is loosely based on my other answer to setuptools, know in advance the wheel filename of a native library

poetry

Things can be simplified a lot (it's practically a one-liner) if you use poetry because all the relevant metadata is stored in the pyproject.toml. Again, this uses an undocumented API:

from clikit.io import NullIO

from poetry.factory import Factory
from poetry.masonry.builders.wheel import WheelBuilder
from poetry.utils.env import NullEnv


def wheel_name(rootdir='.'):
    builder = WheelBuilder(Factory().create_poetry(rootdir), NullEnv(), NullIO())
    return builder.wheel_filename

The rootdir argument is the directory containing your pyproject.toml script.

flit

AFAIK flit can't build wheels with native extensions, so it can give you only the purelib name. Nevertheless, it may be useful if your project uses flit for distribution building. Notice this also uses an undocumented API:

from flit_core.wheel import WheelBuilder
from io import BytesIO
from pathlib import Path


def wheel_name(rootdir='.'):
    config = str(Path(rootdir, 'pyproject.toml'))
    builder = WheelBuilder.from_ini_path(config, BytesIO())
    return builder.wheel_filename

Implementing your own solution

I'm not sure whether it's worth it. Still, if you want to choose this path, consider using packaging.tags before you find some old deprecated stuff or even decide to query the platform yourself. You will still have to fall back to private stuff to assemble the correct wheel name, though.


I used a modified version of hoefling's solution. My goal was to copy the build to a "latest" wheel file. The setup() function will return an object with all the info you need, so you can find out what it actually built, which seems simpler than the solution above. Assuming you have a variable version in use, the following will get the file name I just built and then copies it.

setup = setuptools.setup(
    # whatever options you currently have
    )
wheel_built = 'dist/{}-{}.whl'.format(
    setup.command_obj['bdist_wheel'].wheel_dist_name,
    '-'.join(setup.command_obj['bdist_wheel'].get_tag()))
wheel_latest = wheel_built.replace(version, 'latest')
shutil.copy(wheel_built, wheel_latest)
print('Copied {} >> {}'.format(wheel_built, wheel_latest))

I guess one possible drawback is you have to actually do the build to get the name, but since that was part of my workflow, I was ok with that. hoefling's solution has the benefit of letting you plan the name without doing the build, but it seems more complex.


My current approach to install the wheel is to point pip to the folder containing the wheel and let it search itself:

python -m pip install --no-index --find-links=build/dist mapscript

twine also can be pointed directly at a folder without needing to know the exact wheel name.