Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a guide about publishing dists via GH Actions #647

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions source/guides/index.rst
Expand Up @@ -29,3 +29,4 @@ introduction to packaging, see :doc:`/tutorials/index`.
migrating-to-pypi-org
using-testpypi
making-a-pypi-friendly-readme
publishing-package-distribution-releases-using-github-actions-ci-cd-workflows
@@ -0,0 +1,168 @@
Publishing Package Distribution 📦 Releases Using GitHub Actions CI/CD Workflows 🤖
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
===================================================================================

`GitHub Actions CI/CD`_ allow you to run a series of commands
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
whenever an event occurs in the GitHub Platform. One of the
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
popular choices is having a workflow that's triggered by a
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
``push`` event.
This guide will show you how to publish a Python distribution
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
package whenever a tagged commit is pushed.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

.. attention::

This guide *assumes* that you already have a project that
you know how to build dists for and *it lives on GitHub*.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

.. warning::

At the time of writing this guide, `GitHub Actions CI/CD`_
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
is still in public beta. If you don't have it enabled,
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
you should join the waitlist in order to gain the access.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

N.B. It is known that GitHub is going to make Actions
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
publicly available on the Nov 13th, 2019.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved


Saving credentials on GitHub
----------------------------

In this guide, we'll demonstrate uploading to both production
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
PyPI and Test PyPI meaning that we'll have two separate sets
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
of creds. And we'll need to save them in GitHub repo settings.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

Let's begin! 🚀

1. Go to https://pypi.org/manage/account/#api-tokens and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put the Test PyPI instructions before production PyPI?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think users usually have an account on prod, but rarely on test...

create a new `API token`_. If you have the project on PyPI
already, please limit the token scope to just that project.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
You can call it smth like
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
``GitHub Actions CI/CD — project-org/project-repo``
in order for it to be easily distinguishable in the token
list.
**Don't close the page just yet — you won't see that token
again.**
2. In a separate browser tab or window, go to the ``Settings``
tab of your target repository and then click on `Secrets`_
in the left sidebar.
3. Create a new secret called ``pypi_password`` and copy-paste
the token from the fist step.
4. Now, go to https://test.pypi.org/manage/account/#api-tokens
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
and repeat the steps. Save that Test PyPI token on GitHub
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
as ``test_pypi_password``.
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
as ``test_pypi_password``.
as ``testpypi_password``.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it's better readable to maintain snake_case here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is testpypi_password the snake_case version of TestPyPI (no space)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestPyPI is CamelCase version and it's clearly visible that test is a separate word, even though it's spelled together in the capitalized version. So I consider test_pypi_password a proper snake_case version.



Creating a workflow definition
------------------------------

GitHub CI/CD Workflows are declared in YAML files stored under
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
``.github/`` of your repository.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

Start it with a meaningful name and define the even that
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
should make GitHub run this workflow:

.. code-block:: yaml

name: Publish Python 🐛 distribution package 📦 to PyPIs
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

on: push


Defining a workflow job environment
-----------------------------------

Now, let's add initial setup for our job. It's a process that
will execute commands that we'll define later.
In this guide, we'll choose to use Ubuntu 18.04:
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: yaml

build-n-publish:
name: Build and publish Python 🐛 dist 📦 to PyPIs
runs-on: ubuntu-18.04


Checking out the project and building dists
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
-------------------------------------------

Then, add the following under the ``build-n-publish`` section:

.. code-block:: yaml

steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
version: 3.7

This will download your repository into the CI runner and then
install and activate Python 3.7.

And now we can build dists from source. In this example, we'll
use ``pep517`` package, *assuming that your project has a ``pyproject.toml`` properly set up (see :pep:`517`/:pep:`518`)*.

.. tip::

You can use any other method for building dists as long as
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
it produces ready-to-upload artifacts saved into ``dist/``
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
folder.

So add this to the steps list:

.. code-block:: yaml

- name: Install pep517
run: >-
python -m
pip install
pep517
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
pep517.build
--source
--binary
--out-dir dist/
.


Publishing dist to Test PyPI and production PyPI
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
------------------------------------------------

Finally, add the following steps in the end:
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: yaml

- name: Publish 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.test_pypi_password }}
repository_url: https://test.pypi.org/legacy/
- name: Publish 📦 to production PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.pypi_password }}

These two steps use `pypa/gh-action-pypi-publish`_ GitHub
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
Action: the first one uploads contents of the `dist/` folder
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
into Test PyPI unconditionally and the second does that to
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
production PyPI but only if the current commit is tagged.
webknjaz marked this conversation as resolved.
Show resolved Hide resolved


That's all, folks!
------------------

Now, whenever you push a tagged commit to your Git repo remote
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
on GitHub, this workflow will publish it to PyPI.
And it'll publish any push to Test PyPI which is useful for
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
providing test builds to your alpha users as well as making
sure that your release pipeline keeps being healthy!
webknjaz marked this conversation as resolved.
Show resolved Hide resolved


.. _API token: https://pypi.org/help/#apitoken
.. _GitHub Actions CI/CD: https://github.com/features/actions
.. _pypa/gh-action-pypi-publish:
.. _Secrets:
https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables