How to package a Python app (pip) for PyPi

In this tutorial, we will create a Python application that can be installed directly from pip that will show the 10 latest blog posts from this website (the one you are reading this on!).

If you just want to avoid all of this hard work and publish a python script directly, you can always make use of my makepip package right from the command line.

Learn more about Makepip here.

Getting started

Make sure you have registered at Pypy and have an account, we will need this to upload our package once we’re done.

Now create a directory to work from:

mkdir -p ~/src/tmp/aogl_feed && cd $_
Code language: Bash (bash)

In our new directory, make sure to have a python virtual environment to make life a little simpler.

virtualenv -p python3 venv
Code language: Bash (bash)

And activate it:

source venv/bin/activate
Code language: Bash (bash)

Now make sure that we have all the neccessary things installed to complete this tutorial successfully.

python -m pip install --upgrade pip setuptools wheel python -m pip install tqdm python -m pip install twine
Code language: Bash (bash)

Creating our structure and files

At this stage we should create our directory structure for our package:

Because our package will be quite simple to demonstrate what it takes, create the following 4 files:

LICENCE README.md aogl/ __init__.py __main__.py aogo.py setup.py

In the licence file, you can place the following (customise it as you need):

Copyright (c) 2020 Andrew Odendaal https://ao.gl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Code language: JavaScript (javascript)

For the readme file, we will add this:

A python package to retrieve the latest 10 blog posts from https://ao.gl
Code language: JavaScript (javascript)

In the setup.py file, we configure all our project-specific information:

import setuptools with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name='aogl', version='0.1', author="Andrew Odendaal", author_email="[email protected]", description="A python package to retrieve the latest 10 blog posts from https://ao.gl", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/ao/aogl_pip", packages=["aogl"], entry_points = { "console_scripts": ['aogl = aogl.aogl:main'] }, install_requires=[ "requests", "feedparser" ], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], )
Code language: Python (python)

Create a repository to store everything

You may notice that we have listed the url key to point to https://github.com/ao/aogl_pip, which doesn’t yet exist, so let’s go and create that.

Go to Github and create a new repository.

We will name ours aogl_pip to match with what we told setup.py it would be.

We don’t want to initialise with a README, as we already have one. Click Create repository to proceed.

We can now push to our new repository, but we aren’t quite ready yet. So let’s finish up our local setup.

Add our main code

At this point, we can create our aogl.py file mentioned above and populate it with the following code:

#!/usr/bin/env python import feedparser, requests response = requests.get("https://ao.gl/feed") feed = feedparser.parse(response.content) feed.entries = feed.entries[0:9] for entry in feed.entries: print(entry.title) print(entry.links[0].href) print()
Code language: Python (python)

Also, make sure to set it to be executable:

chmod +x aogl.py
Code language: Bash (bash)

Let us also create the blank aogl/__init__.py file and the aogl/__main__.py file which contains the following code:

from .aogl import main main()
Code language: Python (python)

As we have used a couple dependencies ourselves, will should install them to our virtual environment as follows:

pip install requests pip install feedparser
Code language: Bash (bash)

Distributing these dependencies is easy when we allow people to install our app through pip, because we have specified these dependencies in the setup.py file, remember this block?

install_requires=[ "requests", "feedparser" ],
Code language: Python (python)

Now when we run python aogl.py, our script prints out the 10 latest blog posts for us.

Build our package

It’s now time to build our package and push it to Pypi.

We do this by running:

python setup.py bdist_wheel
Code language: Bash (bash)

This creates a whole lot of files for us, the output looks something like this:

running sdist running egg_info creating aogl.egg-info writing aogl.egg-info/PKG-INFO writing dependency_links to aogl.egg-info/dependency_links.txt writing entry points to aogl.egg-info/entry_points.txt writing top-level names to aogl.egg-info/top_level.txt writing manifest file 'aogl.egg-info/SOURCES.txt' reading manifest file 'aogl.egg-info/SOURCES.txt' writing manifest file 'aogl.egg-info/SOURCES.txt' running check creating aogl-0.1 creating aogl-0.1/aogl creating aogl-0.1/aogl.egg-info copying files to aogl-0.1... copying README.md -> aogl-0.1 copying setup.py -> aogl-0.1 copying aogl/__init__.py -> aogl-0.1/aogl copying aogl/__main__.py -> aogl-0.1/aogl copying aogl/aogl.py -> aogl-0.1/aogl copying aogl.egg-info/PKG-INFO -> aogl-0.1/aogl.egg-info copying aogl.egg-info/SOURCES.txt -> aogl-0.1/aogl.egg-info copying aogl.egg-info/dependency_links.txt -> aogl-0.1/aogl.egg-info copying aogl.egg-info/entry_points.txt -> aogl-0.1/aogl.egg-info copying aogl.egg-info/top_level.txt -> aogl-0.1/aogl.egg-info Writing aogl-0.1/setup.cfg creating dist Creating tar archive removing 'aogl-0.1' (and everything under it) running bdist_wheel running build running build_py creating build creating build/lib creating build/lib/aogl copying aogl/__init__.py -> build/lib/aogl copying aogl/aogl.py -> build/lib/aogl copying aogl/__main__.py -> build/lib/aogl installing to build/bdist.macosx-10.15-x86_64/wheel running install running install_lib creating build/bdist.macosx-10.15-x86_64 creating build/bdist.macosx-10.15-x86_64/wheel creating build/bdist.macosx-10.15-x86_64/wheel/aogl copying build/lib/aogl/__init__.py -> build/bdist.macosx-10.15-x86_64/wheel/aogl copying build/lib/aogl/aogl.py -> build/bdist.macosx-10.15-x86_64/wheel/aogl copying build/lib/aogl/__main__.py -> build/bdist.macosx-10.15-x86_64/wheel/aogl running install_egg_info Copying aogl.egg-info to build/bdist.macosx-10.15-x86_64/wheel/aogl-0.1-py3.7.egg-info running install_scripts adding license file "LICENCE" (matched pattern "LICEN[CS]E*") creating build/bdist.macosx-10.15-x86_64/wheel/aogl-0.1.dist-info/WHEEL creating 'dist/aogl-0.1-py3-none-any.whl' and adding 'build/bdist.macosx-10.15-x86_64/wheel' to it adding 'aogl/__init__.py' adding 'aogl/__main__.py' adding 'aogl/aogl.py' adding 'aogl-0.1.dist-info/LICENCE' adding 'aogl-0.1.dist-info/METADATA' adding 'aogl-0.1.dist-info/WHEEL' adding 'aogl-0.1.dist-info/entry_points.txt' adding 'aogl-0.1.dist-info/top_level.txt' adding 'aogl-0.1.dist-info/RECORD' removing build/bdist.macosx-10.15-x86_64/wheel
Code language: Bash (bash)

Test our package

Let’s take a look at what was created; using the tree command, we limit the output to 2 levels of depth:

tree -L 2 . ├── LICENCE ├── README.md ├── aogl │   ├── __init__.py │   ├── __main__.py │   └── aogl.py ├── aogl.egg-info │   ├── PKG-INFO │   ├── SOURCES.txt │   ├── dependency_links.txt │   ├── entry_points.txt │   └── top_level.txt ├── build │   ├── bdist.macosx-10.15-x86_64 │   └── lib ├── dist │   ├── aogl-0.1-py3-none-any.whl │   └── aogl-0.1.tar.gz ├── setup.py └── venv ├── bin ├── include └── lib
Code language: Bash (bash)

We can see there is a build directory, which contains build package information.

The aogl.egg.info directory contains all dependency and package information.

There is also a dist directory, which contains our *.whl, which is a Wheel file.

This wheel can be installed directory with pip if we wanted to, by running pip install dist/aogl-0.1-py3-none-any.whl.

We will actually do this to make sure that everything works as expected before we publish our new code to the world.

It works pretty well!

Let’s uninstall this local pip, so that we are able to install it from Pypi once it’s successfully pushed.

pip uninstall aogl
Code language: Bash (bash)

Upload our code to Pypi

Next we will create a file under our home directory called ~/.pypirc and configure it:

[distutils] index-servers=pypi [pypi] repository = https://upload.pypi.org/legacy/ username = aogl
Code language: Bash (bash)

My username happens to be the same as the package I’m currently building, so make sure to adjust the username value to whatever your registered username is on the Pypi website.

Now we can use twine to upload our wheel.

python -m twine upload dist/*
Code language: Bash (bash)

If all was successful, we should see this:

Uploading distributions to https://upload.pypi.org/legacy/ Enter your password: Uploading aogl-0.1-py3-none-any.whl 100%|█████████████████████████████████████████████| 5.89k/5.89k [00:00<00:00, 7.29kB/s] NOTE: Try --verbose to see response content. HTTPError: 403 Client Error: Invalid or non-existent authentication information. See https://pypi.org/help/#invalid-auth for details for url: https://upload.pypi.org/legacy/ (venv) ➜ aogl_feed python -m twine upload dist/* Uploading distributions to https://upload.pypi.org/legacy/ Enter your password: Uploading aogl-0.1-py3-none-any.whl 100%|█████████████████████████████████████████████| 5.89k/5.89k [00:03<00:00, 1.96kB/s] Uploading aogl-0.1.tar.gz 100%|█████████████████████████████████████████████| 4.37k/4.37k [00:01<00:00, 2.93kB/s] View at: https://pypi.org/project/aogl/0.1/
Code language: JavaScript (javascript)

Our package is now available at https://pypi.org/project/aogl/0.1/

Push our code to Github

Don’t forget to push the code to Github, so that we can update new versions later.

git init git add LICENCE README.md aogl/ setup.py git commit -m 'Pushing code for aogl version 0.1' git remote add origin https://github.com/ao/aogl_pip.git git push -u origin master
Code language: Bash (bash)

Test everything as the world would see it

Finally we get to test out installing our new package using pip off of Pypi!

pip install aogl
Code language: Bash (bash)

It installed successfully!

aogl
Code language: Bash (bash)

Our wonderful new contribution to the world has returned a list of the latest 10 blog posts!

Excellent! Job done.

Remember, you can also use the makepip package to do all of this automatically for you!

Learn more about Makepip here.

Tags:
Subscribe
Notify of
guest
2 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
trackback

[…] In this tutorial, we will create a Python application that can be installed directly from pip that will show the 10 latest blog posts from this website (the one you are reading this on!). If you just want to avoid all of this hard work and publish a pytho… Read more […]

trackback

[…] In this tutorial, we will create a Python application that can be installed directly from pip that will show the 10 latest blog posts from this website (the one you are reading this on!). If you just want to avoid all of this hard work and publish a pytho… Read more […]