Back to home

PrerequisitesGetting startedExploring the current repoAltering our files for a packageEditing our setup.py fileAdd a licenseAdding a READMEInclude non-Python files in uploadPackaging the filesTesting out the packageSummaryResources and further reading
Your First Pip Package In Python main image

Your First Pip Package In Python

This post will use the setuptools package to deploy a package to the Pip package repository.

It will work off the code written in my post "PyTest With GitHub Actions".

Final project code can be found here.

Prerequisites

  1. Familiarity with Pipenv. See here for my post on Pipenv.
  2. You must have a registered PyPi account.

Getting started

Let's clone the start repository and add the files and packages we will need to deploy.

1 2 3 4 5 6 7 8 9 10 11 # Make the `hello-world` directory $ git clone https://github.com/okeeffed/hello-pytest-github-actions your-first-pip-package-in-python $ cd your-first-pip-package-in-python # Install dependencies $ pipenv install # Install the setuptools package $ pipenv install --dev setuptools wheel twine tqdm # Add some files to prepare for our package shipping $ touch setup.py LICENSE MANIFEST.in

At this stage, the repo will now be ready to add our required code to deploy to Pip.

Exploring the current repo

I will use tree -L 2 -a to display the current repo:

1 2 3 4 5 6 7 8 9 10 11 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 . ├── .git │ ├── COMMIT_EDITMSG │ ├── FETCH_HEAD │ ├── HEAD │ ├── config │ ├── description │ ├── hooks │ ├── index │ ├── info │ ├── logs │ ├── objects │ └── refs ├── .github │ └── workflows ├── .gitignore ├── .mypy_cache │ ├── .gitignore │ ├── 3.9 │ └── CACHEDIR.TAG ├── .pytest_cache │ ├── .gitignore │ ├── CACHEDIR.TAG │ ├── README.md │ └── v ├── .venv │ ├── .gitignore │ ├── .project │ ├── bin │ ├── lib │ ├── pyvenv.cfg │ └── src ├── Pipfile ├── Pipfile.lock ├── README.md ├── src │ ├── __init__.py │ └── math.py └── tests ├── __init__.py └── test_math.py

What is important for us here to know is that src/math.py contains the code which we will want to deploy and tests/test_math.py contains a simple test for our code.

If we look at the test file, we can see the code we want to deploy contains simple math functions:

1 2 3 4 5 6 7 8 9 10 11 12 13 from src.math import add, subtract, multiply def test_add(): assert add(3, 2) == 5 def test_subtract(): assert subtract(3, 2) == 1 def test_multiply(): assert multiply(3, 2) == 6

We can confirm that this works as expected by running pipenv run pytest.

1 2 3 4 5 6 7 8 9 $ pipenv run pytest ===================== test session starts ===================== platform darwin -- Python 3.9.6, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 rootdir: /path/to/your-first-pip-package-in-python collected 3 items tests/test_math.py ... [100%] ====================== 3 passed in 0.03s ======================

We can now focus on deploying this as a package.

Altering our files for a package

We need to move the src folder to be the name of the package we want demo-pip-math:

1 mv src demo-pip-math

We can now adjust our tests/test_math.py file to reflect this:

1 2 3 4 5 6 7 8 9 10 11 12 13 from demo_pip_math.math import add, subtract, multiply def test_add(): assert add(3, 2) == 5 def test_subtract(): assert subtract(3, 2) == 1 def test_multiply(): assert multiply(3, 2) == 6

You can run pipenv run pytest to confirm everything is as it should be.

Editing our setup.py file

We need to edit setup.py to use the setuptools package and contain information about our package:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import setuptools with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name="demo_pip_math", version="0.1.0", author="Dennis O'Keeffe", author_email="hello@dennisokeeffe.com", description="Demo your first Pip package.", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/okeeffed/your-first-pip-package-in-python", packages=setuptools.find_packages(), classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], python_requires='>=3.6', keywords='pip-demo math', project_urls={ 'Homepage': 'https://github.com/okeeffed/your-first-pip-package-in-python', }, )

Our setup.py makes use of the setuptools.setup function to prepare our package.

Some of the more important arguments we are passing:

ArgumentDoes
nameName of the package
versionCurrent package version in semantic versioning
authorYour name
descriptionYour package description
long_descriptionA description that comes from our README.md file
python_requiresWhat versions of Python does your package work with
keywordsWords to match the package when searching for it

Add a license

You can choose a license to copy into our LICENSE file. I have gone with the MIT license:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Copyright (c) 2021 Dennis O'Keeffe 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.

Adding a README

I am adding a simple README for the sake of completing the package deployment. My README.md file reads as follows:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # Your first Pip package in Python This repository is the companion post to my blog ["Your first Pip package in Python"](https://blog.dennisokeeffe.com) ## Usage ```py from demo_pip_math.math import add, subtract, multiply def test_add(): assert add(3, 2) == 5 def test_subtract(): assert subtract(3, 2) == 1 def test_multiply(): assert multiply(3, 2) == 6 ```

Include non-Python files in upload

To enure our README.md and LICENSE are included, we can edit our MANIFEST.in file to have the following:

1 2 include README.md include LICENSE

Packaging the files

Let's edit our Pipfile and add the following to [scripts]:

1 2 3 4 [scripts] test = "pytest" build = "python3 setup.py sdist bdist_wheel" # Added line here deploy = "twine upload dist/*" # Added line here

This will enable us to run pipenv run build to generate our distribution archives and pipenv run deploy to upload our dist/ folder.

1 2 3 4 5 6 7 8 9 10 11 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 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 85 86 87 88 $ pipenv run build running sdist running egg_info creating demo_pip_math.egg-info writing demo_pip_math.egg-info/PKG-INFO writing dependency_links to demo_pip_math.egg-info/dependency_links.txt writing top-level names to demo_pip_math.egg-info/top_level.txt writing manifest file 'demo_pip_math.egg-info/SOURCES.txt' reading manifest file 'demo_pip_math.egg-info/SOURCES.txt' reading manifest template 'MANIFEST.in' adding license file 'LICENSE' writing manifest file 'demo_pip_math.egg-info/SOURCES.txt' running check creating demo_pip_math-0.1.0 creating demo_pip_math-0.1.0/demo_pip_math creating demo_pip_math-0.1.0/demo_pip_math.egg-info creating demo_pip_math-0.1.0/tests copying files to demo_pip_math-0.1.0... copying LICENSE -> demo_pip_math-0.1.0 copying MANIFEST.in -> demo_pip_math-0.1.0 copying README.md -> demo_pip_math-0.1.0 copying setup.py -> demo_pip_math-0.1.0 copying demo_pip_math/__init__.py -> demo_pip_math-0.1.0/demo_pip_math copying demo_pip_math/math.py -> demo_pip_math-0.1.0/demo_pip_math copying demo_pip_math.egg-info/PKG-INFO -> demo_pip_math-0.1.0/demo_pip_math.egg-info copying demo_pip_math.egg-info/SOURCES.txt -> demo_pip_math-0.1.0/demo_pip_math.egg-info copying demo_pip_math.egg-info/dependency_links.txt -> demo_pip_math-0.1.0/demo_pip_math.egg-info copying demo_pip_math.egg-info/top_level.txt -> demo_pip_math-0.1.0/demo_pip_math.egg-info copying tests/__init__.py -> demo_pip_math-0.1.0/tests copying tests/test_math.py -> demo_pip_math-0.1.0/tests Writing demo_pip_math-0.1.0/setup.cfg creating dist Creating tar archive removing 'demo_pip_math-0.1.0' (and everything under it) running bdist_wheel running build running build_py creating build creating build/lib creating build/lib/demo_pip_math copying demo_pip_math/__init__.py -> build/lib/demo_pip_math copying demo_pip_math/math.py -> build/lib/demo_pip_math creating build/lib/tests copying tests/__init__.py -> build/lib/tests copying tests/test_math.py -> build/lib/tests warning: build_py: byte-compiling is disabled, skipping. installing to build/bdist.macosx-11-x86_64/wheel running install running install_lib creating build/bdist.macosx-11-x86_64 creating build/bdist.macosx-11-x86_64/wheel creating build/bdist.macosx-11-x86_64/wheel/demo_pip_math copying build/lib/demo_pip_math/__init__.py -> build/bdist.macosx-11-x86_64/wheel/demo_pip_math copying build/lib/demo_pip_math/math.py -> build/bdist.macosx-11-x86_64/wheel/demo_pip_math creating build/bdist.macosx-11-x86_64/wheel/tests copying build/lib/tests/__init__.py -> build/bdist.macosx-11-x86_64/wheel/tests copying build/lib/tests/test_math.py -> build/bdist.macosx-11-x86_64/wheel/tests warning: install_lib: byte-compiling is disabled, skipping. running install_egg_info Copying demo_pip_math.egg-info to build/bdist.macosx-11-x86_64/wheel/demo_pip_math-0.1.0-py3.9.egg-info running install_scripts adding license file "LICENSE" (matched pattern "LICEN[CS]E*") creating build/bdist.macosx-11-x86_64/wheel/demo_pip_math-0.1.0.dist-info/WHEEL creating 'dist/demo_pip_math-0.1.0-py3-none-any.whl' and adding 'build/bdist.macosx-11-x86_64/wheel' to it adding 'demo_pip_math/__init__.py' adding 'demo_pip_math/math.py' adding 'tests/__init__.py' adding 'tests/test_math.py' adding 'demo_pip_math-0.1.0.dist-info/LICENSE' adding 'demo_pip_math-0.1.0.dist-info/METADATA' adding 'demo_pip_math-0.1.0.dist-info/WHEEL' adding 'demo_pip_math-0.1.0.dist-info/top_level.txt' adding 'demo_pip_math-0.1.0.dist-info/RECORD' removing build/bdist.macosx-11-x86_64/wheel $ pipenv run deploy Uploading distributions to https://upload.pypi.org/legacy/ Enter your username: dennisokeeffe Enter your password: Uploading demo_pip_math-0.1.0-py3-none-any.whl 100%|█████████████████████| 6.80k/6.80k [00:02<00:00, 2.90kB/s] Uploading demo_pip_math-0.1.0.tar.gz 100%|█████████████████████| 6.12k/6.12k [00:01<00:00, 3.83kB/s] View at: https://pypi.org/project/demo-pip-math/0.1.0/

In my particular case, if I now head to the provided project link then I can see my package has now been deployed!

Package on PyPi

Package on PyPi

Testing out the package

To test out the new package, you can create a new project and install:

1 2 3 4 5 6 $ mkdir test-pip-package $ cd test-pip-package $ pipenv --three $ pipenv install demo-pip-math==0.1.0 $ pipenv shell $ touch main.py

Add the following code to main.py:

1 2 3 4 5 6 from demo_pip_math.math import add, subtract, multiply print(add(3, 2)) # print 5 print(subtract(3, 2)) # print 1 print(multiply(3, 2)) # print 6

Run python main.py:

1 2 3 4 $ python main.py 5 1 6

Great success!

Summary

Today's post demonstrated how to use the Pillow package to add text and icons layers to an image programmatically.

This will be the technique I use for helping to build out my blog post main images (including this one!).

Resources and further reading

Photo credit: teodosiev

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on workingoutloud.dev, Den Dribbles and LandPad .

Related articles


1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.