Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a build process that creates a Python wheel using the following command:

python setup.py bdist_wheel

The build process can be run on many platforms (Windows, Linux, py2, py3 etc.) and I'd like to keep the default output names (e.g. mapscript-7.2-cp27-cp27m-win_amd64.whl) to upload to PyPI.

Is there anyway to get the generated wheel's filename (e.g. mapscript-7.2-cp27-cp27m-win_amd64.whl) and save to a variable so I can then install the wheel later on in the script for testing?

Ideally the solution would be cross platform. My current approach is to try and clear the folder, list all files and select the first (and only) file in the list, however this seems a very hacky solution.

like image 908
geographika Avatar asked Aug 20 '18 22:08

geographika


People also ask

How do I extract a WHL file?

whl file can be extracted using unzip or by right clicking on the file and extracting using the Extract Here graphical interface in Ubuntu/Debian systems. After extracting, one can inspect the source code of . py files and the contents of metadata files which will be located in library-name-with-version.

What are wheel files in Python?

What Is a Python Wheel? A Python . whl file is essentially a ZIP ( . zip ) archive with a specially crafted filename that tells installers what Python versions and platforms the wheel will support. A wheel is a type of built distribution.


1 Answers

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.

like image 176
hoefling Avatar answered Oct 05 '22 23:10

hoefling