Skip to content

Python Build Utils Modules

This module provides a command-line interface (CLI) for Python build utilities. It uses the click library to create a CLI group and add commands for renaming wheel files and removing tarballs.

Functions:

Name Description
cli

Defines the CLI group and adds commands for renaming wheel files and removing tarballs.

Commands: rename_wheel_files: Command to rename wheel files. remove_tarballs: Command to remove tarballs.

cli()

A collection of CLI tools for Python build utilities.

Source code in src\python_build_utils\cli_tools.py
22
23
24
25
@click.group()
@click.version_option(__version__, "--version", "-v", message="%(version)s", help="Show the version and exit.")
def cli() -> None:
    """A collection of CLI tools for Python build utilities."""

This module provides functionality to create a wheel file from a .pyd file.

Classes:

Name Description
PydFileFormatError

Exception raised for errors in the .pyd file format.

Functions:

Name Description
pyd2wheel

Path, package_version: Optional[str | None] = None, abi_tag=Optional[str | None]) -> Path: CLI interface for converting a .pyd file to a wheel file.

convert_pyd_to_wheel

Path, package_version: str | None = None, abi_tag: str | None = None) -> Path:

_create_metadata_file(dist_info, name, package_version)

Create the METADATA file.

Source code in src\python_build_utils\pyd2wheel.py
200
201
202
203
204
205
def _create_metadata_file(dist_info: Path, name: str, package_version: str) -> None:
    """Create the METADATA file."""
    metadata_filename = dist_info / "METADATA"
    metadata_content = _make_metadata_content(name, package_version)
    with open(metadata_filename, "w", encoding="utf-8") as f:
        f.write(metadata_content)

_create_record_file(root_folder, dist_info)

Create the RECORD file.

Source code in src\python_build_utils\pyd2wheel.py
215
216
217
218
219
220
def _create_record_file(root_folder: Path, dist_info: Path) -> None:
    """Create the RECORD file."""
    record_content = _make_record_content(root_folder)
    record_filename = dist_info / "RECORD"
    with open(record_filename, "w", encoding="utf-8") as f:
        f.write(record_content)

_create_wheel_archive(pyd_file, wheel_file_name, root_folder)

Create the .whl file by zipping the contents of the temporary directory.

Source code in src\python_build_utils\pyd2wheel.py
223
224
225
226
227
228
229
230
231
232
233
def _create_wheel_archive(pyd_file: Path, wheel_file_name: str, root_folder: Path) -> Path:
    """Create the .whl file by zipping the contents of the temporary directory."""
    wheel_file_path = pyd_file.parent / wheel_file_name
    result_file = wheel_file_path.with_suffix(".zip")
    if result_file.exists():
        result_file.unlink()
    created_name = shutil.make_archive(str(wheel_file_path), "zip", root_folder)
    if wheel_file_path.exists():
        wheel_file_path.unlink()
    os.rename(created_name, wheel_file_path)
    return wheel_file_path

_create_wheel_file(dist_info, python_version, abi_tag, platform)

Create the WHEEL file.

Source code in src\python_build_utils\pyd2wheel.py
208
209
210
211
212
def _create_wheel_file(dist_info: Path, python_version: str, abi_tag: str, platform: str) -> None:
    """Create the WHEEL file."""
    wheel_content = _make_wheel_content(python_version, abi_tag, platform)
    with open(dist_info / "WHEEL", "w", encoding="utf-8") as f:
        f.write(wheel_content)

_display_wheel_info(name, package_version, python_version, platform, abi_tag)

Display the wheel information.

Source code in src\python_build_utils\pyd2wheel.py
171
172
173
174
175
176
177
178
179
180
181
182
def _display_wheel_info(name: str, package_version: str, python_version: str, platform: str, abi_tag: str) -> None:
    """Display the wheel information."""
    field_width = 25
    click.echo(f"{'=' * 80}")
    click.echo(f"{'Field':<{field_width}}{'Value'}")
    click.echo(f"{'-' * 80}")
    click.echo(f"{'Name:':<{field_width}}{name}")
    click.echo(f"{'Version:':<{field_width}}{package_version}")
    click.echo(f"{'Python Version:':<{field_width}}{python_version}")
    click.echo(f"{'Platform:':<{field_width}}{platform}")
    click.echo(f"{'ABI Tag:':<{field_width}}{abi_tag}")
    click.echo(f"{'-' * 80}")

_extract_pyd_file_info(pyd_file)

Extract the name, version, python version, and platform from the pyd file name.

Source code in src\python_build_utils\pyd2wheel.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def _extract_pyd_file_info(pyd_file: Path) -> tuple:
    """Extract the name, version, python version, and platform from the pyd file name."""
    # remove suffix and split the filename on the hyphens

    if pyd_file.suffix != ".pyd":
        raise PydFileSuffixError(pyd_file.name)

    bare_file_name = pyd_file.stem

    # Assume the base_file_name is like:
    #   dummy-0.1.0-py311-win_amd64"  or
    #   dummy-0.1.0.py311-win_amd64"  or
    # where the version can be 0, 0.1, or 0.1.1 and at least a python version and a platform are provided
    match = re.match(r"(.*?)-((?:\d\.){0,2}\d)[.-](.*)-(.*)", bare_file_name)
    if match:
        name, package_version, python_version, platform = match.groups()
        return name, package_version, python_version, platform

    # Assume base_file_name is like  DAVEcore.cp310-win_amd64
    # i.e. the version is not provided and the build version and platform are separated by a dot
    match = re.match(r"(.*?)\.(.*)-(.*)", bare_file_name)
    if match:
        name, python_version, platform = match.groups()
        package_version = None
        return name, package_version, python_version, platform

    raise PydFileFormatError(bare_file_name)

_get_package_version(package_version, version_from_filename)

Get the package version from the provided version or the pyd file name.

Source code in src\python_build_utils\pyd2wheel.py
160
161
162
163
164
165
166
167
168
def _get_package_version(package_version: str | None, version_from_filename: str | None) -> str:
    """Get the package version from the provided version or the pyd file name."""
    if package_version is None and version_from_filename is not None:
        return version_from_filename

    if package_version is None:
        raise VersionNotFoundError

    return package_version

_make_metadata_content(name, version)

Create the metadata for the wheel file.

Source code in src\python_build_utils\pyd2wheel.py
84
85
86
87
88
89
def _make_metadata_content(name: str, version: str) -> str:
    """Create the metadata for the wheel file."""
    meta_data = "Metadata-Version: 2.1\n"
    meta_data += f"Name: {name}\n"
    meta_data += f"Version: {version}\n"
    return meta_data

_make_record_content(root_folder)

Create the RECORD file content for the wheel.

RECORD is a list of (almost) all the files in the wheel and their secure hashes.

Source code in src\python_build_utils\pyd2wheel.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def _make_record_content(root_folder: Path) -> str:
    """Create the RECORD file content for the wheel.

    RECORD is a list of (almost) all the files in the wheel and their secure hashes.
    """
    record_content = ""
    # loop over all the files in the wheel and add them to the RECORD file
    for root, _, files in os.walk(root_folder):
        for file in files:
            # get the hash of the file using sha256
            sha256_hash = hashlib.sha256()

            file_path = os.path.join(root, file)
            with open(file_path, "rb") as f:
                while chunk := f.read(4096):  # Read in 4KB chunks
                    sha256_hash.update(chunk)

            sha256_digest = sha256_hash.hexdigest()

            file_size_in_bytes = os.path.getsize(os.path.join(root, file))

            # officially the HASH should be added here
            record_content += f"{root}/{file},sha256={sha256_digest},{file_size_in_bytes}\n"

    # add record itself
    record_content += f"{root_folder}/RECORD,,\n"
    return record_content

_make_wheel_content(python_version, abi_tag, platform)

Create the wheel data for the wheel file.

Source code in src\python_build_utils\pyd2wheel.py
92
93
94
95
96
97
98
99
def _make_wheel_content(python_version: str, abi_tag: str, platform: str) -> str:
    """Create the wheel data for the wheel file."""
    wheel_data = "Wheel-Version: 1.0\n"
    wheel_data += "Generator: bdist_wheel 1.0\n"
    wheel_data += "Root-Is-Purelib: false\n"
    wheel_data += f"Tag: {python_version}-{abi_tag}-{platform}\n"
    wheel_data += "Build: 1"
    return wheel_data

convert_pyd_to_wheel(pyd_file, package_version=None, abi_tag=None)

Creates a wheel from a .pyd file.

Parameters:

Name Type Description Default
pyd_file Path

The path to the .pyd file.

required
package_version str | None

The version of the package. If not provided, it will be extracted from the filename. Defaults to None.

None
abi_tag str | None

The ABI tag for the wheel. If not provided, defaults to "none".

None

Returns:

Name Type Description
Path Path | None

The path to the created wheel file.

Source code in src\python_build_utils\pyd2wheel.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def convert_pyd_to_wheel(pyd_file: Path, package_version: str | None = None, abi_tag: str | None = None) -> Path | None:
    """
    Creates a wheel from a .pyd file.

    Args:
        pyd_file (Path): The path to the .pyd file.
        package_version (str | None, optional): The version of the package. If not provided, it will be extracted
            from the filename. Defaults to None.
        abi_tag (str | None, optional): The ABI tag for the wheel. If not provided, defaults to "none".

    Returns:
        Path: The path to the created wheel file.
    """
    pyd_file = Path(pyd_file)
    try:
        name, version_from_filename, python_version, platform = _extract_pyd_file_info(pyd_file)
    except (PydFileFormatError, PydFileSuffixError) as e:
        click.echo(e, err=True)
        return None

    try:
        package_version = _get_package_version(package_version, version_from_filename)
    except VersionNotFoundError as e:
        click.echo(e, err=True)
        return None

    if abi_tag is None:
        abi_tag = "none"

    _display_wheel_info(name, package_version, python_version, platform, abi_tag)

    wheel_file_name = f"{name}-{package_version}-{python_version}-{abi_tag}-{platform}.whl"
    root_folder = create_temp_directory(pyd_file)
    dist_info = create_dist_info_directory(root_folder, name, package_version)

    _create_metadata_file(dist_info, name, package_version)
    _create_wheel_file(dist_info, python_version, abi_tag, platform)
    _create_record_file(root_folder, dist_info)

    wheel_file_path = _create_wheel_archive(pyd_file, wheel_file_name, root_folder)
    click.echo(f"created wheel file: {wheel_file_path}")
    click.echo(f"{'-' * 80}")

    shutil.rmtree(root_folder)
    return wheel_file_path

create_dist_info_directory(root_folder, name, package_version)

Create the .dist-info directory.

Source code in src\python_build_utils\pyd2wheel.py
193
194
195
196
197
def create_dist_info_directory(root_folder: Path, name: str, package_version: str) -> Path:
    """Create the .dist-info directory."""
    dist_info = root_folder / f"{name}-{package_version}.dist-info"
    dist_info.mkdir(exist_ok=True)
    return dist_info

create_temp_directory(pyd_file)

Create a temporary directory to store the contents of the wheel file.

Source code in src\python_build_utils\pyd2wheel.py
185
186
187
188
189
190
def create_temp_directory(pyd_file: Path) -> Path:
    """Create a temporary directory to store the contents of the wheel file."""
    root_folder = pyd_file.parent / "wheel_temp"
    root_folder.mkdir(exist_ok=True)
    shutil.copy(pyd_file, root_folder / pyd_file.name)
    return root_folder

pyd2wheel(pyd_file, package_version=None, abi_tag=None)

Create a wheel from a compiled python *.pyd file.

Source code in src\python_build_utils\pyd2wheel.py
26
27
28
29
30
31
32
33
34
@click.command(name="pyd2wheel")
@click.version_option(__version__, "--version", "-v", message="%(version)s", help="Show the version and exit.")
@click.argument("pyd_file", type=click.Path(exists=True))
@click.option("--package_version", help="The version of the package.", default=None)
@click.option("--abi_tag", help="The ABI tag of the package. Default is 'none'.", default=None)
def pyd2wheel(pyd_file: Path, package_version: str | None = None, abi_tag: str | None = None) -> Path | None:
    """Create a wheel from a compiled python *.pyd file."""

    return convert_pyd_to_wheel(pyd_file, package_version, abi_tag)

Remove the tar.gz files from the dist build folder.

remove_tarballs(dist_dir)

Remove tarball files from dist.

This function removes tarball files from the given distribution directory.

Parameters:

Name Type Description Default
dist_dir str

The directory containing the tarball files to be removed.

required

Returns:

Type Description
None

None

Example

remove_tarballs("dist")

Source code in src\python_build_utils\remove_tarballs.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@click.command(name="remove-tarballs")
@click.version_option(__version__, "--version", "-v", message="%(version)s", help="Show the version and exit.")
@click.option(
    "--dist_dir",
    default="dist",
    help=textwrap.dedent("""
    Directory containing wheel the files.
    Default is 'dist'
"""),
)
def remove_tarballs(dist_dir: str) -> None:
    """Remove tarball files from dist.

    This function removes tarball files from the given distribution directory.

    Args:
        dist_dir (str): The directory containing the tarball files to be removed.

    Returns:
        None

    Example:
        remove_tarballs("dist")
    """

    dist_dir = dist_dir.rstrip("/")

    found_files = False

    for tarball_file in glob.glob(f"{dist_dir}/*.tar.gz"):
        found_files = True
        try:
            os.remove(tarball_file)
        except FileNotFoundError as e:
            click.echo(f"Error {e}")
        else:
            click.echo(f"Removed {tarball_file}")

    if not found_files:
        click.echo(f"No tarball files found in {dist_dir}")

Rename wheel files in the dist folder of your python build directory to include platform and python version tags.

rename_wheel_files(dist_dir, python_version_tag, platform_tag, wheel_tag)

Rename wheel files in the dist folder.

This function renames wheel files in the given distribution directory by replacing the "py3-none-any" tag with a custom build version tag. The build version tag is constructed using the provided python_version_tag, platform_tag, and wheel_tag. If wheel_tag is provided, it is used directly as the build version tag. Otherwise, the build version tag is constructed using the python_version_tag and platform_tag.

Args:

dist_dir (str): The directory containing the wheel files to be renamed.
Default is 'dist'.

python_version_tag (str): The Python version tag to be included in the
new file name. Default is cp{major}{minor}.

platform_tag (str): The platform tag to be included in the new file
name. Default is sysconfig.get_platform().

wheel_tag (str): The custom wheel tag to be used as the build version
tag. If this is provided, it is used directly as the build version tag
and the other tags are ignored. If this is not provided, the build
tag is constructed using the `python_version_tag` and `platform_tag` as
described above.

Returns:

Type Description
None

None

Example

rename_wheel_files("dist", "cp39", "win_amd64", "")

Source code in src\python_build_utils\rename_wheel_files.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@click.command(name="rename-wheel-files")
@click.version_option(__version__, "--version", "-v", message="%(version)s", help="Show the version and exit.")
@click.option("--dist_dir", default="dist", help="Directory containing wheel files. Default is 'dist'")
@click.option("--python_version_tag", help="Explicitly specify the python version tag. Default is cp{major}{minor}")
@click.option("--platform_tag", help="Explicitly specify the platform tag. Default is sysconfig.get_platform()")
@click.option(
    "--wheel_tag",
    help=textwrap.dedent("""
    Explicitly specify the total wheel tag.
    Default is {python_version_tag}-{python_version_tag}-{platform_tag}
    """),
)
def rename_wheel_files(dist_dir: str, python_version_tag: str, platform_tag: str, wheel_tag: str) -> None:
    """Rename wheel files in the dist folder.

    This function renames wheel files in the given distribution directory by
    replacing the "py3-none-any" tag with a custom build version tag. The
    build version tag is constructed using the provided `python_version_tag`,
    `platform_tag`, and `wheel_tag`. If `wheel_tag` is provided, it is used
    directly as the build version tag. Otherwise, the build version tag is
    constructed using the `python_version_tag` and `platform_tag`.

    Args:

        dist_dir (str): The directory containing the wheel files to be renamed.
        Default is 'dist'.

        python_version_tag (str): The Python version tag to be included in the
        new file name. Default is cp{major}{minor}.

        platform_tag (str): The platform tag to be included in the new file
        name. Default is sysconfig.get_platform().

        wheel_tag (str): The custom wheel tag to be used as the build version
        tag. If this is provided, it is used directly as the build version tag
        and the other tags are ignored. If this is not provided, the build
        tag is constructed using the `python_version_tag` and `platform_tag` as
        described above.

    Returns:
        None

    Example:
        rename_wheel_files("dist", "cp39", "win_amd64", "")
    """

    if wheel_tag:
        build_version_tag = wheel_tag
    else:
        if not python_version_tag:
            python_version_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
        if not platform_tag:
            platform_tag = sysconfig.get_platform().replace("-", "_")
        build_version_tag = f"{python_version_tag}-{python_version_tag}-{platform_tag}"

    dist_dir = dist_dir.rstrip("/")

    found_files = False

    for wheel_file in glob.glob(f"{dist_dir}/*py3-none-any.whl"):
        found_files = True
        new_file = wheel_file.replace("py3-none-any.whl", f"{build_version_tag}.whl")
        try:
            os.rename(wheel_file, new_file)
        except FileExistsError as e:
            click.echo(f"Error {e}")
        else:
            click.echo(f"Renamed {wheel_file} -> {new_file}")

    if not found_files:
        click.echo(f"No wheel files found in {dist_dir}")