How to publish a package to PyPI

in #python4 years ago (edited)

I guess you have heard about PyPI. Well if you use pip command then you should. And if not, in short  - PyPI is the place where all the packages comes from.

Maybe you would like to contribute with your cool software too. It's not hard at all but it's not that one-click stuff. Let's see what you have to do to publish your own package to PyPI where the rest of the world can download it.

PyPI account

You will need 2 accounts. 1st on pypi.org and second on test.pypi.org. Yea, right. You will need to test your releases and then upload it to "production" repository. So register on both ;)

We will talk about uploading your package later. Now we need to prepare the package.

Setuptools

One of the ways how to add some metadata, some info, to your package is via setuptools. These tools help you to manage your package info, uploads, install process and much more.

First we want to setup the package…. so create the setup.py file in your project root. Let's see one example of the eagle project.

#!/usr/bin/env python3

import setuptools
from eagle import __version__, __description__


## Long description
with open("description.rst", "r") as f:
    long_description = f.read()

setuptools.setup(
    name="eagle-cli",
    # version="0.1.1",
    version=__version__,
    description=__description__,
    long_description=long_description,
    author="n1",
    url="https://gitlab.com/n1_/eagle",
    packages=setuptools.find_packages(),
    entry_points={
        "console_scripts": ["eagle = eagle.eagle:eagle"]
    },  
    python_requires=">=3.6",
    classifiers=[
        "Environment :: Console",
        "Programming Language :: Python :: 3.6",
        "Intended Audience :: End Users/Desktop",
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3 :: Only",
        "Topic :: Office/Business :: Scheduling ",
    ]   
)

Package description

PyPI allows you to have something like Github does with README.md files. Package "homepage" might have pretty rich description. The thing is they support reStructuredText. With this in mind we can have package "homepages" like the following homepage of the eagle package:

1_VjvVTW1VrhxE5DHYBgtquQ.png

All this is done just by parsing setup.py file. Let's describe very briefly what we see and what params are used from setup.py:

Header

  • package name: name
  • package version: version
  • subtitle: description

Left panel

  • homepage: url
  • license: LICENSE file
  • requires: python_requires
  • maintainers: author
  • classifiers: classifiers - grouped by category

Content

  • the massive description: long_description

Once you have your setup.py file you are ready to build your app.

Make me one build please

It's dead simple. Run this command in your project root and the package will be build under build directory and even a distribution package will be prepared for you in dist directory.

./setup.py sdist bdist_wheel

Now you can install the package locally.

pip3 install -I -e .

Now let's make your first test upload.

Test upload

PyPI provides it's own test environment on test.pypi.org. The reason why we might need this is you don't know how your package is going to be presented to the world until you upload one. You might need to edit long_description because it looks weird (aka it's not being parsed) or maybe you just forgot author field.

TestPyPI works just like it's production brother. You do register yourself, confirm your email and then you can start uploading.

Weird non-overwriting

PyPI has one security feature which is worth to mention. Once you upload a package under a specific name (name + version number) you cannot override it. You can delete that version from the portal by a few clicks but the system still remembers the name and returns 400 error once you try to reupload a package you have uploaded just 5 minutes ago. This might be cool because it gives us the trust as a user that a specific package in a specific version can be uploaded only once. No cheating. The weird part is that this behavior applies also to TestPyPI. Here is whole discusion about this behavior.

Though, to be honest, it's a huge PITA for the test repo. I support integrity and all that stuff on "production" systems. However, developers need to have their code / packages checked somewhere and it's a PITA if you can't upload them same version twice while testing a new release.

With the new policy you basically say: You've ONE SINGLE TRY and that one SHOULD WORK. No chance for a 2nd try. IMHO this isn't the purpose of a testing system and breaks the whole "we've a testing repo" idea. To be honest, I think this only leads to annoyed developers and a lot of "crippled versions" because developers couldn't properly test their versions before going live.

PITA: pain in the ass

The solution is to fake releases with postfixed version numbers. If you are testing release 0.1 you can put version="0.1-0" to the setup.py file and you can upload the package. If needed just increment the last number. Once you are happy change it back (or comment the line out and write a new one) with version="0.1". Then you can happily upload to production PyPI.

Upload

You will need one more tool that helps you to perform the uploads. It's twine library. Don't worry it's super simple. To perform our test upload just type this to your console:

twine upload -s --repository-url https://test.pypi.org/legacy/ dist/*

Now you have your package uploaded to TestPyPI portal. Open up your profile on TestPyPI , click "Your projects" in the menu and check the package detail.

Not satisfied? Just make changes and upload again. Do not forget to increment version number (or just the postfix) otherwise the server will reject the upload.

Once you are happy you can upload your awesome package to production PyPI. Just remove the --repository-url param from twine command.

P.S.
Feel free to copy-cat any code that will help you from the eagle repo. I'm totaly OK with that. I hope it will help you.

Coin Marketplace

STEEM 0.25
TRX 0.07
JST 0.031
BTC 22888.71
ETH 1677.82
USDT 1.00
SBD 3.15