Building Your First Python Package with PyPI

Building Your First Python Package with PyPI

One of the best features of Python as a programming language is its large ecosystem of packages and modules that make it incredibly easy to access some really cool functionality. But how does it all work?

The Python Package Index (aka PyPI) is the biggest online repository of software for the Python programming language. It contains over 5M software releases, any one of which can be accessed through just one single line of code. Not only does PyPI make it incredibly easy to access and use Python software packages - it also makes it incredibly easy to publish your own software as well!

To get started you will need to register for a free account on the PyPI website. Once registered, you can head over to your account settings page to generate an API token. You will use this when uploading your package.

In order to actually publish software to PyPI there are a few configuration files you need to have in place as well as a specific directory structure (shown below)

packaging_tutorial/
├── LICENSE              # LICENSE for your software
├── setup.py             # Setup file for configuring your package
├── README.md            # Documentation markdown page
├── src/
│   └── example_package/ 
│       ├── __init__.py  # Organize imports from example.py file  
│       └── example.py   # Your awesome package functionality!
└── tests/

The main files we are concerned with are your setup.py, __init__.py and your example.py.

# Example setup.py
import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="example_package",
    version="1.0",
    author="John Doe",
    author_email="awesomeness@gmail.com",
    description="Brief description of your package",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="<url_for_your_package>",
    packages=setuptools.find_packages(exclude="tests"),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    install_requires=["package1", "package2"],
    python_requires='>=3.7',
)

Most of the information in this file is just information about your package with a couple important elements being the install_requires variable - in which you will include all the other Python packages that your package uses, and the python_requires variable - which specifies which versions of Python your package is compatible with.


# Example package file
class Hello:
    def say_hello(self):
        print("Hello World!")

This is the main file where your package functionality will be.You will just want to make sure that you encapsulate all your functionality into classes that can be easily imported in __init__.py


# Example __init__.py
from .example import Hello

This file just serves as an entry point to your package and manages importing your various functions, classes etc. Depending on the project this will be more or less complicated.


Once you have all this in place, publishing to PyPI is as simple as one command, which you can run from your main project directory (ie. packaging_tutorial/) (OK technically it's two commands)

python setup.py sdist bdist_wheel; python -m twine upload dist/*

When running twine upload , you should be prompted for a username and password - for which you can use the following values:

username: __token__         # yes, the actual text value '__token__'
password: <your_api_token>  # generated when you set up your account

With that - congratulations, you have officially published your first Python software package! As with everything, you can dive a whole lot deeper into publishing your software, including unit tests, automating releases through GitHub and even monitoring you usage stats with PyPI Stats!


The script below should get you going even faster. Happy Programming!

import os

name = input("Package Name: ")
setup = f'''
import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="{name}",
    version="1.0",
    author="<your name>",
    author_email="<your email>",
    description="<description>",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="<url_for_your_package>",
    packages=setuptools.find_packages(exclude="tests"),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    install_requires=[],
    python_requires='>=<minimum python version>',
)
'''
license = '''
MIT License

Copyright (c) <year> <your name>

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.'''

main = f'''
class {name}:
    def say_hello(self):
        print("Hello World!")
'''
init = f'''
from .{name} import {name}
'''
os.mkdir(name)
os.mkdir(f"{name}/src")
os.mkdir(f"{name}/test")

with open(f"{name}/LICENCE", "w") as f:
    f.write(license)

with open(f"{name}/README.md", "w") as f:
    f.write("## README.md")
    f.write("")

with open(f"{name}/setup.py", "w") as f:
    f.write(setup)

with open(f"{name}/src/__init__.py", "w") as f:
    f.write(init)

with open(f"{name}/src/{name}.py", "w") as f:
    f.write(main)

print(f"Successfully created project [ {name} ]")