How I published/deployed my Python package to PYPI easily.
I love reading Medium post time to time, but never have enough time between juggling school, work and family the dream of reading daily posts just happens to fade into the abyss over time. And the links to posts that interest me keep on piling up and up on my Google Keep.
Over time, I could not take it anymore so I decided to create a Python script that would export the blog posts (using a Docker Image) into mp3
files for me to listen to, instead while busy with other boring stuff that boring people do.
In this post, I will walk you through how I managed to publish/deploy my package on PyPi.
At first, I always thought that deploying/packaging your Python package was cumbersome until now. It is now easier than ever and below I will tell you all about it.
Some of the benefits of writing your first Python package and deploying it to PyPi will be:
- Being forced to think much more modularly.
- Writing code with tests in mind.
- Thinking about making the API simple enough for others to intuitively grasp and reuse your work.
- Learning new tools that come along with writing tests.
Before I digress, you might be wondering what is this PyPi I speak of!
What is PyPi?
Well, according to their Website.
The Python Package Index (PyPI) is a repository of software for the Python programming language. PyPI helps you find and install software developed and shared by the Python community. Learn about installing packages. Package authors use PyPI to distribute their software. Learn how to package your Python code for PyPI. If you use
pip
command, you are already using PyPi.
When you finally get to publish to PyPi, everyone can install and use it with a familiar simple command:
pip install {package_name}
If you are still reading then I’m sure you are like, that is pretty cool.
So, In order to get our package to PyPI we will need to do the following steps:
- Create Python code and make it publish-ready i.e. create a Python package, add the files needed for PyPi.
- Create a PyPi account if you haven’t.
- Generating distribution archives and upload to PyPi.
- Install your own package using
pip
Step #1: Make your code publish-ready.
In this post, I will take you through my package called medium-speech
as a real example. You can find it here on PyPi, source code is here on Github.
Things to note before deploying:
- Remove all print statement from your code. If you want to inform or log something, use Python
logging
module. - Remove all code that stays outside of a class or function. Such code (if really necessary), put it under
__main__
function:
if __name__ == "__main__":
# code outside of a class or function goes here
Create a python package
Reinventing the wheel is a bad idea, don’t do it.
Thanks to @kennethreitz for making it easier with his “Human’s Ultimate Guide to setup.py”.
For us not to reinvent the wheel, let’s fork and/or clone the setup.py
repo from GitHub and create a new repository on GitHub called {you-package-name}
for simplicity.
git clone https://github.com/kennethreitz/setup.py.git
mv setup.py "{you-package-name}"
rm -rf "{you-package-name}"/.git
git init
git remote add origin git@github.com:"{github-username}"/"{you-package-name}".git
git fetch -a
git checkout master
cd && ls
Package in Python is simply a folder with name of your package. This folder contains files (modules) and other sub-folders (sub-packages).
Rename mypackage
directory to “{you-package-name}”, and inside your directory you’ll need to put a file __init__.py
(two underscores before and after init) assuming it is not there in order to mark this directory as a Python package. vim
or nano
inside this __init__.py
file you can specify which classes you want the user to access through the package interface.
Sample of my __init.py__
file:
from .MediumToSpeech import MediumToSpeech
Versioning you package is very important, in the same directory you should find __version__.py
file, create it if it doesn’t exist. This file tells PyPi which version your package is under.
Sample of my __version__.py
file:
VERSION = (0, 1, 3)
__version__ = ".".join(map(str, VERSION))
Add files needed for PyPi
PyPi needs following file in order to work:
-
setup.py
(detail will follow.) -
LICENSE
(the license file, if you choose MIT, get content from here.) -
MANIFEST.in
(optional see reasons here.) -
README.md
(Highly recommended, but optional)
Sample project structure:
.
├── LICENSE
├── MANIFEST.in
├── medium_speech
│ ├── __init__.py
│ ├── MediumToSpeech.py
│ └── __version__.py
├── README.md
├── scripts
│ └── play_medium_post.py
├── setup.py
├── tests
│ ├── __init__.py
│ ├── markdown_test.md
│ ├── unit_tests.py
│ └── utils.py
└── tox.ini
└── .travis.yml
└── .gitignore
Let’s dive down and explore the files illustrated above, starting with the setup.py
file.
The setup.py file.
The setup.py
file contains information about your package that PyPi needs, like its name, a description, installation requirements and etc. We will look directly into a real simple setup.py
which you can find here:
Most of the options are self-explanatory, you can just copy the content of setup.py
above and modify it to your needs. Don’t forget to list all dependencies of your package in install_requires
list, so that this requirement can be installed automatically while your package is being installed.
The scripts directory
Many Python packages include command line tools including this one. This is useful for distributing support tools which are associated with a library, or just taking advantage of the setuptools
/ PyPI infrastructure to distribute a command line tool that happens to use Python.
For medium-speech
, I added a play_medium_post.py
command line tool.
There are two mechanisms that setuptools.setup()
provides to do this: the scripts
keyword argument, and the console_scripts
entry point.
In my case I used the scripts
keyword argument, go here to read more.
My approach was to write my script in a separate file called play_medium_post.py
, under the scripts
directory.
Let’s look into the script play_medium_post.py
which you can find here
Then we can declare the script in setup.py
like this:
SCRIPTS = []
## Assuming you named your scripts directory "scripts"
for dirname, dirnames, filenames in os.walk("scripts"):
for filename in filenames:
SCRIPTS.append(os.path.join(dirname, filename))
setup(
...
scripts=SCRIPTS,
...
)
When we install the package, setuptools
will copy the script to our PATH
and make it available for general use, for example:
play_medium_post.py -ps 1 -u https://medium.com/@mmphego/how-i-managed-to-harness-imposter-syndrome-391fdb754820
This has the advantage of being generalizable to non-python scripts, as well: play_medium_post.py
could have been a shell script, or something completely different.
Tests, Tests, Tests, Tests, Tests, Tests!!!
Need I say more if you still need convincing read this blog post: Why Use Test Driven Development: 6 Benefits for Your Project. Any piece of code that you write, you should be writing with tests in mind so that you can better break apart larger functions into base components to hopefully get more reuse out of them.
In my case, I wrote my package code first. However, you can also write your tests first, knowing your function can’t fulfill them and only then fill in the code until the test passes. There is a lot of great material out there for Test Driven Development (TDD) if you want to explore the philosophy behind it more. This is something that is a major level up in best practice coding that once you force yourself to do, everything else will be so much easier.
For my package, I used unittest
framework together with nose
you are welcome to use any framework/module - I just prefer nose
.
Within our tests
directory, we will need an empty __init__.py
file as with our package. Next, you write some tests!. If you would like to check out and contribute to my tests go here.
The tox.ini file
Now that we have our code and our tests, let’s expand the functionality of our testing basis a little by introducing tox.
What is Tox?
From their website
tox
aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software.
Tox allows us to run through tests in multiple environments so that you can be sure your code will work with the differences between Python versions. Let’s look directly into a simple tox.ini
file which you can find here:
The code above simply tells us we are going to test the package using python3.6
and python3.7
, as well as a flake8
environment layer for checking any pep8 violations.
So when we run tox
in our shell, it will create virtualenv
for the individual envlist
and install all dependencies listed under deps
when done it will execute any commands listed under commands
in our case I am running python setup.py test
. To read more about setuptools
testing go here.
The .travis.yml file
So at this point, we have our package logic, our tests, multiple environments tests using tox
. Now, what happens if others start contributing to our repo and someone edits code, but forgets to run tox
or nosetests
locally before pushing to our repo?
Bugs could be introduced and people who depend on your code now have things that break. This is where Continuous Integration (CI) comes in. For this, I used Travis CI.
What is Travis CI?
From stackoverflow:
The simplest way to explain Travis CI is that it runs your program’s tests every time you commit to GitHub (this can be configured in many ways, and you can always disable builds on some branches). The point of this is that you can often discover very quickly if your commit broke something, and fix it before it becomes a problem. You can read more about Travis CI here.
To set up Travis, for the first time I would highly recommend the tutorial here which is very detailed and well written.
After you have registered you need to connect your repo
, and we are good to go!
The README.md file
A README
is a reflection of how a repository is maintained. A good one doesn’t necessarily mean an active, bug-free project with perfect tests. But it suggests that the owner cares about you, the user (or future maintainer). A good README
tells you everything you need to know to use the project and get involved. It sells the project — but concurrently respects a visitor’s time by letting them know if they need a different solution.
Before you push your project to GitHub, I would recommend you to read up about Readme Driven Development (RDD) and why we need a README
file.
I personally prefer to use stackedit which is an online real-time Markdown (md) editor which beautifully renders your markdown texts, you are welcome to use any tool you need.
Below is a detailed template which I followed when creating my README.md
and you can find here:
README: -Badges
Mostly standardized by badges/shields, GitHub badges are one of the first things a visitor sees as they scroll down. Build status badges describe the stability of a project. In my case, I have Python version badge which state which version of Python is supported, Licence badge, PyPi version release badge, Number of package downloads from PyPi, and Thank You badge. Badges aren’t compulsory but much like GIFs/Memes, they are a huge bonus.
shields.io have an API for creating your own badges, If you would like to create your own SVG badges locally there’s a Python package owned by Google called pybadges.
Read more about badges and README files here
Step #2: Create a PyPi account.
Now that you package is ready for deployment we will need to register on PyPi account. If you do not have a PyPi account, go here and register.
Step #3: Generate dist archives and upload to PyPi.
Now, the for the finale.
First, open your cli
and navigate into your the folder where you have all your files and your package located.
You will need to install twine which will upload our package to PyPi.
pip install -U twine
Then, run the following command:
If you want to dive down as to how it all works go here.
python setup.py upload
# To see more options, hit:
# python setup.py --help-commands
You will be asked to provide your username
and password
. Provide the credentials you used to register to PyPi earlier.
After successful uploading, go to PyPi website, under your project, you can found your published package.
My public listing is here
Step #4: Install your own package using pip
Okay, now let’s test this out. Open your cli and type the following command:
pip install "yourpackagename"
In my case it is:
pip install -U medium-speech
When it is finished installing, Open the python/ipython
shell and import your package.
Step #5 (optional): Changes to your package
If you maintain your package well, you will need to change the source code from time to time.
Simply make the changes and Do not forget to change the version number under {mypackage}/__version__.py
, commit and push your changes to GitHub.
Then run Step 3 to upload the new release to PyPi.
Finally, update your package via pip to see whether your changes worked:
pip install -U "yourpackagename"
That’s it. Enjoy building and sharing your Python packages!
Sorry for the long post, here’s a Potato!
If you found this post interesting, please leave a comment or an emoji.