Have a Python project? Compile it into a package with “pip”!

Christian Talavera
4 min readNov 8, 2021

Developing a program in Python?

Chances are that if the project is even a little complex, that multiple files are involved.

Luckily, the pip utility allows for creation of packaged up, platform agnostic programs!

Creating the package structure

First, be sure that all the required pip packages "setuptools" and "wheel" are installed:

$ sudo python -m pip install --upgrade pip setuptools wheel
  • setuptools - allows for binary Python packages to be built
  • wheel - optional package that creates a ".whl" binary, which is like a tarball (.tar.bz) but builds more efficient

Then, create the package directory structure; this is the generic file structure:

$ tree
.
└── package_name
├── code_base
│ ├── _init_.py
│ └── source_code.py
├── LICENSE.md
├── README.md
├── runtime_script
└── setup.py
  • package_name - the name of the created package
  • code_base - this is where the actual Python code goes
    _init_.py - every package must have this; can be empty but this code is the first program code run
    source_code.py - this is the program code
  • LICENSE.md - every program needs a license; this is stored here
  • README.md - a detailed description of the packaged program
  • runtime_script - a shell script that executes at package runtime; explained below
  • setup.py - the actual package build instructions; explained below

runtime_script

This is a required executable file that needs to be created; during runtime of the application, any commands in this file will be run.

Below is an example of a runtime_script file:

#!/usr/bin/env pythonecho "This will be displayed during package runtime!"

setup.py

This is the other required executable file that will need creation; these are the actual build instructions that will be executed with the pip application

Below is an example of a setup.py file:

from setuptools import setupsetup(
name='',
version='',
author='',
author_email='',
packages='[]',
scripts='[]',
url='',
license='',
description='',
long_description=open('').read(),
)

The first line of code:

from setuptools import setup

Exists to tell pip to use the setuptools module to run the package build process.

Within the setup() object, houses all the configuration data:

setup(
name='package_name',
version='1.0.0',
author='sample',
author_email='sample@example.com',
packages=['code_base'],
scripts=['runtime_script'],
url='http://example.com',
license='LICENSE.md',
description='Enter Description here',
long_description=open('README.md').read(),
)

To further explain each field:
(*** indicates project specific filenames)

  • ***name - the name of the created package; in this case "package_name"
  • version - the version of the created program
  • author - the author of the created program
  • author_email - the author's email
  • ***packages - this is where the actual Python code resides, can take multiple arguments; in this case "code_base"
  • ***scripts - a shell script that executes at package runtime, can take multiple arguments; in this case "runtime_script"
  • url - this is where the project is available for download
  • ***license - this points to the license file; in this case "LICENSE.MD"
  • description - a short description of the program
  • ***long_description - this has the function open('').read(), which opens up the created 'readme' file; in this case "README.md"

Building the binary

Now that the required scripts, directory structure and configuration is in place, the binary can now be built.

There are two options available:

  • Building a tarball package — binary would have a “*.tar.gz" file extension; standard packaging
  • Building a Python “Wheel” package — binary would have a “*.whl" file extension; improved Python packaging

Creating a “tarball” binary package

First, navigate to the directory containing the “setup.py" file:

$ tree
.
└── package_name <-- NAVIGATE HERE
├── code_base
│ ├── _init_.py
│ └── source_code.py
├── LICENSE.md
├── README.md
├── runtime_script
└── setup.py <-- RUN THIS

To build a tarball, run the following:

$ python setup.py sdist

The new project structure is listed below:

$ tree
.
└── package_name
├── code_base
│ ├── _init_.py
│ └── source_code.py
├── dist <-- DIRECTORY CREATED
│ └── package_name-1.0.0.tar.gz <-- TARBALL CREATED
├── LICENSE.md
├── package_name.egg-info <-- DIRECTORY CREATED
│ ├── dependency_links.txt
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ └── top_level.txt
├── README.md
├── runtime_script
└── setup.py

This creates two new directories:

  • dist - any tarball/wheel binaries are stored here
    package_name-1.0.0.tar.gz is the tarball binary created
  • package_name.egg-info - egg packages usually contains package information, dependency links, and compiled byte code; any information used by setup.py during tests is also captured here

Creating a “wheel” binary package

First, navigate to the directory containing the “setup.py" file:

$ tree
.
└── package_name <-- NAVIGATE HERE
├── code_base
│ ├── _init_.py
│ └── source_code.py
├── LICENSE.md
├── README.md
├── runtime_script
└── setup.py <-- RUN THIS

To build a “wheel” binary, run the following:

$ python setup.py bdist_wheel

The new project structure is listed below:

$ tree
.
└── package_name
├── build <-- DIRECTORY CREATED
│ ├── bdist.linux-x86_64
│ ├── lib
│ │ └── code_base
│ │ ├── _init_.py
│ │ └── source_code.py
│ └── scripts-3.10
│ └── runtime_script
├── code_base
│ ├── _init_.py
│ └── source_code.py
├── dist <-- DIRECTORY CREATED
│ └── package_name-1.0.0-py3-none-any.whl <-- BINARY CREATED
├── LICENSE.md
├── package_name.egg-info <-- DIRECTORY CREATED
│ ├── dependency_links.txt
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ └── top_level.txt
├── README.md
├── runtime_script
└── setup.py

This creates three new directories:

  • build - platform-specific build files are stored here
  • dist - any tarball/wheel binaries are stored here
    package_name-1.0.0-py3-none-any.whl is the "wheel" binary created
  • package_name.egg-info - egg packages usually contains package information, dependency links, and compiled byte code; any information used by setup.py during tests is also captured here

--

--