Tutorial - How to create an installable package in Python - Part 2
This post continues the first part Tutorial – How to create an installable package in Python – Part 1.
How to make your own package?
Now that you know a little about other packages it is time to create your own package.
This section will teach you this as we work our way through a simple sample application named calculator. But first a little something on how I like to organize my projects.
The Default Python-project Directory Structure
When you create a Python-project, you are very free to use whatever structure you like. The total freedom is one of Pythons forces, and might also be confusing because you see different approaches here and there.
In this section I introduce The Default Python-project Directory Structure as I like it.
First create an directory for your application (or later, your own package). In our case the top, root directory for the project, is called “calculator”, like this;
d:\python\calculator
This will be the home for our little sample. Now create a calculator.py file in the calculator-directory
d:\python\calculator\calculator.py
Below you see the simple Python-code inside my calculator.py as displayed in my development environment Visual Studio 2019 (or IDE - Integrated Development Environment, a fancy word for the code-editor and other tools bundled together into a program. You can of course use whatever you like, and I recommend taking a look at Visual Studio Code or PyCharm)
On the left you clearly see the directory structure, and on the right you see the code inside calculator.py, which is;
number1 = 10
number2 = 20
print(number1 + number2)
At the moment this isn’t a very effective calculator .
Now we will add a package to this project!
Package type #1 - The packages inside your projects
My calculator should have a package doing some other functions. Create a directory named mypackage placed in the same root directory as your calculator.py file. Below you see how that looks in the IDE
The newly created and highlighted directory above, will contain the upcoming package. The mypackage-directory will in our case, contain two files - myfunctions.py which will contain our math-functions, and a special file named __init__.py.
The __init__.py (that is two double underscores prefixing and suffixing the word “init”) tells Python “Hey, the code inside this directory is a package!”
The mere presence of this file denotes that the directory should be regarded as a package.
It needn’t contain anything for the moment.
Below you see my directory structure with the mypackage-directory containing the __init__.py and the myfunctions.py;
The code for the myfunctions.py file looks like this;
def add(number1, number2):
return number1 + number2
def subtract(number1, number2):
return number1 – number2
def multiply(number1, number2):
return number1 * number2
def divide(number1, number2):
return number1 / number2
Very briefly I define 4 Python-functions, allowing for basic math-operations. All functions accept two parameters, number1 and number2 and return the result of the operation.
My modified calculator.py, using mypackage and the functions inside it
Now I want to import the functions from myfunctions.py into my calculator.py script. This can be done with either the import <package name> or with from <package name> import <function name>. I will dive into the differences between these two techniques soon.
from mypackage.myfunctions import *
number1 = 10
number2 = 20
result1 = add(number1, number2)
print(result1)
Below you see the same code as above;
Marked by 1, you see how to import all functions from file myfunctions.py located in the mypackage-directory. Note that the from-syntax uses a dot to separate directory name from file name, and that you don’t need the suffix “.py” here. This means that when I say that the syntax is from <package name>, it actually means from <directory name>.<python file with functions>. Note the use of the *-symbol too. This means “hey, import all functions” from myfunctions. Below you see how the so-called intellisense in many IDEs can help you find what symbols a file contain by pressing a short-cut key (CTRL + SPACE in Visual Studio 2019);
There you can clearly see the names of the functions, such as add, divide, multiply and subtract
This means that you can limit the import too, to just one or a few functions if needed. If you for example only need add and divide, then the statement can look like from mypackage.myfunctions import add, divide.
Marked by 2 in the previous screen shot you see that I can use the add-function from myfunctions directly, since it was imported with from mypackage.myfunctions import *.
Pay extra attention to the fact that we can call the add-function without any prefix. What if you import other packages that also contain an add-function? Well, in the case above, you will get a symbol crash since Python can’t decide which add-function to use!
Therefore, to avoid symbol crashes, you might choose to use import <package name> instead. Below you see an alternative version of calculator.py;
Marked by 1 you now see how I use import instead of from … import. The import-function load the myfunction.py into it’s own socalled namespace, meaning that we have to use the full prefix as highlighted in the codeline marked by 2. Now you can import other packages without the fear of risking symbol crashes
Use an alias to shorten the package name
Do you think the prefix “mypackage.myfunctions” will be too long in all your subsequent code? Then you can use an alias, like this;
By using import mypackage.myfunctions as mm, you now give the package-name mypackage.myfunctionsan alias of mm. This is then used in your subsequent code.
However, choose the alias wisely, as you now might run into other symbols with the same name (again)
Package type #2 - The packages outside your projects
This is the cool section! Now you know how to use packages inside your project, this section will teach you how to create packages outside your project. This means that you can import them in multiple projects, just like you use import configparser and from common import dcs_db. You can even upload them to https://pypi.org/
However, before you run off and do just that, please remember that official packages should follow even more Best Practices, which instruct you to include files such as LICENSE, README.md and others. Here I focus on the bare minimal requirements to convert a previously “inside the project-package” to an “installable package”.
I continue with the small calculator-sample, which now contains a single package in the mypackage-directory with all functions in the myfunctions.py file.
This tutorial will show you how to use the default Package Manager pip to create an installable package.
Below you see the current directory structure and the latest content of the calculator.py file;
An installable package needs a setup.py file
Create a setup.py file in the same directory as calculator.py.
Below you see a bare minimum content from setup.py;
For the sake of copy and paste, I retype the code below;
from setuptools import setup
setup(
name='mypackage',
version='0.1.0',
description='A sample package',
url='#',
author='Bob Voith',
author_email='moreinfo@vcode.no',
packages=['mypackage'],
)
The file above is an ordinary Python-file, importing the function setup from the package setuptools (you know how to do this yourself now! The package setuptools happen to be one of the packages that gets installed automatically when you install Python )
The code calls the function setup with some keyword-named variables. The most important ones to get correct its the name-parameter which will be the importable package name later (marked by 1). The next super-important parameter is the packages-parameter, which actually is a Python-list of directory names within the package to include. Remember, the directory structure is now;
The directory mypackage, marked with 2, matches the packages-parameter in the previous screen shot, also marked with 2!
The nature of the packages-parameter makes it possible to include more than one package into our installable package, by adding more directory names, like this packages=['mypackage', ‘subdirectory 2’, subdirectory and packagename 3']
Check your setup.py file
Before going any further, check your setup.py file with the following command line command;
python setup.py check
This will check the Python-script for any syntactical errors. A successful check returns only the string “running check” like shown below;
Install your package
Be sure your are placed in the very same directory as setup.py and calculator.py, and issue the following command;
pip install .
Remember the last dot! This should look like this;
pip does it’s magic and amongst the output you see that your package with the importable name mypackage has been created as mypackage-0.1.0 (yes, the version number from your setup.py).
You can now see your own package in the list of “pip controlled packages” by using the command pip list;
You can even try a pip show mypackage, and see some of the content of setup.py
Cool, you are now only a single step away from having a fully importable package on your computer!
Create a so-called “source distribution” of your newly installed package
Still being in the root directory of your project with the setup.py and calculator.py, issue the command;
python setup.py sdist
The result is that the sdist-argument also emits a bunch of output, like this;
Wooho! You now have a so-called tarball file which has been placed in the dist-directory in your project. A tarball file is essentially a zip file, which happen to be installable by pip!
Below you see the directory structure of the project as it looks after the pip install . (marked by 1) and after the python setup.py sdist (marked by 2);
Use your importable package in your code!
You can now distribute your tarball-file, and instruct the users to install it with the following command;
pip install ./mypackage-0.1.0.tar.gz
Note that I have then placed the tarball in the same directory as I isssue the pip-command from
Below I show a sequence, where I first have uninstalled mypackage with pip uninstall mypackage (marked by 1). Note how Python complain about a missing package with “ModuleNotFoundError: No module named 'mypackage'“
Then, marked by 2, I install the tarball with pip install ./mypackage-0.1.0.tar.gz, and finally successfully calls my calculator.py program marked by 3.
The code is equal to the last code-block in the section “Package type #1 - The packages inside your projects”;
Good luck and happy Python-coding!