Create a PyPI (pip) package, test it and publish it using Github Actions (PART 1)
Tags: python, devops, github, pip
Published: 2021-01-12 17:26:40

Creating a [PyPI](https://pypi.org) package is easier than you think, because all you have to do is create a python package, a `setup.py` file that defines your package and upload it on PyPI (The Python Package Index) for millions of users to install via `pip`.

## Writing our code

First, we need to create a python package. I have a very simple package in mind, one that allows you to interact and manipulate strings, like [lodash](https://npmjs.com/packages/lodash).

I'm gonna be using PyCharm as my IDE of choice, but feel free to use anything you want (VSCode, Spyder, IDLE, Vim, etc).

My folder will be named `pydash`, as it is the name of my pip package. Inside the `pydash` folder, let's create another folder called `pydash`. This new `pydash` folder will house all of our code. Create an `__init__.py` file in that folder. We'll be editing that file.

```python
class PyDash:
    def __init__(self):
        pass

    @staticmethod
    def lower(string: str):
        """
        Converts a string to lowercase
        """
        return string.lower()

    @staticmethod
    def upper(string: str):
        """
        Converts a string to uppercase
        """
        return string.upper()

    @staticmethod
    def title(string: str):
        """
        Converts a string to titlecase
        """
        return string.title()

    @staticmethod
    def kebab(string: str):
        """
        Converts a string to kebabcase
        """
        return string.replace(" ", "-").lower()
```

### Testing our package

Create a python file named `test.py`, `index.py` or whatever you want **OUTSIDE** the `pydash` folder (the one containing `__init__.py`. We can import and test our package like so:

```python
from pydash import PyDash

print(pydash.lower("TEST"))
print(pydash.upper("test"))
print(pydash.title("there WAS a MAN!"))
print(pydash.kebab("Generate a Slug for my Post"))
```

```
OUTPUT:
test
TEST
There Was A Man!
generate-a-slug-for-my-post
```

## Adding a `setup.py`

Our package works great! You can now delete your test python file. We're almost ready to get this package up and running on PyPI. There's just one problem. PyPI knows nothing about our project, so, let's tell it something. Create a file named `setup.py` in your project directory. This directory listing should help:

```
- pydash
| - pydash
  | - __init__.py
| - setup.py
```

First, we have to decide on a name that **is not taken** on PyPI. Unfortunately for me, PyDash already exists, so I'm gonna go with the name `pydash-arnu515`.

Once you've gotten setup with your name, let's add some stuff to `setup.py`

{% gist https://gist.github.com/arnu515-test/617f410b54ce51c7c628a6dcbb0aae50 file=setup.py %}

### Create some more files

Let's now add a `README.md` file so that `setup.py` can reference it.

{% gist https://gist.github.com/arnu515-test/617f410b54ce51c7c628a6dcbb0aae50 file=README.md %}

Let's add a `.gitignore`, so that we don't commit any files we aren't supposed to.
```plain
__pycache__/
build/
dist/
*.egg-info/
*.egg
venv
```

> I recommend marking these directories as `excluded` in PyCharm to avoid getting errors like "Duplicated Code Fragment".

Now, one last file, the `LICENSE`. In `setup.py`, I set my license to be `MIT`, so that's what I'm going for.

{% gist https://gist.github.com/arnu515-test/617f410b54ce51c7c628a6dcbb0aae50 file=LICENSE %}

And that's it for our python package!

## Testing our package with `unittest`.

Unittest is a builtin python module that allows us to test our package. This blog post doesn't introduce `unittest` to you. For that, there are many more tutorials online. Here, I'll just show you how you can test your python package.

### Creating our tests.

Create a folder named `tests` in the project folder and create a file named `__init__.py` in it. This should be how your folder looks like:

```
- pydash
| - pydash
  | - __init__.py
| - tests
  | - __init__.py
| - setup.py
```

> Since `tests` is a package, it will be included in our pip package because `setuptools.findpackage` will add it to our pip package. This is not what we want, so let's do a quick change in `setup.py`
>
> ```python
> packages=find_packages(exclude="tests")
> ```

### Adding our first test

Create a file named `test_string.py`.

{% gist https://gist.github.com/arnu515-test/617f410b54ce51c7c628a6dcbb0aae50 file=test_string.py %}

PyCharm allows me to run unittests automatically, but I'm not going to do that, since not everyone uses PyCharm. Instead, we'll use the command line to test our app.

```bash
cd tests
python3 -m unittest discover
```

> The `discover` keyword tells `unittest` to find any files with the name of `test_*.py` and run the unittests inside them.

You may have gotten an error stating:
```plain
No module named pydash
```

We can fix that by installing pydash using `setup.py`
```bash
python3 setup.py install
cd tests
python3 -m unittest discover
```

Your tests should've run successfully.

## Uploading to TestPyPI

Now, for the fun, wait, what is [TestPyPI](https://test.pypi.org)? TestPyPI is a separate instance of PyPI meant to test your packages first, so let's upload our package there first. 

We will need to install a few packages first.

```
pip install twine wheel
```

We need to create an account on TestPyPI first. Head over to [TestPyPI](https://test.pypi.org/account/register) and create an account (or login if you have one already). You will need to verify your email address in order to upload a package.

#### Create an access token

Let's create an access token which will allow us to upload to TestPyPI without having to type in our email and password all the time. Go to your [API tokens in Account Settings](https://test.pypi.org/manage/accounts#api-tokens) and create a **full access** API token. Give it a name and set its scope to Entire Account.

> Copy your API Token and store it in a safe place once it is displayed to you, as it will **never** be displayed to you again

> It is **VERY** important that you don't share your API token and don't commit it to git because anyone with access to that token can upload packages to your account.

### Building and Uploading your package to TestPyPI

Now comes the fun! Let's build your package using this command:

```python
python3 setup.py sdist bdist_wheel
```

This should create a new folder called `dist` in your current directory.

Let's upload that folder using `twine`:

```python
python3 -m twine upload -r testpypi dist/*
```

> For the username enter `__token__`, and for the password, paste your API Token.

Voila! Your package has successfully been uploaded to TestPyPI!

View it at `https://test.pypi.org/project/YOUR_PROJECT_NAME`

For example, mine would be <https://test.pypi.org/project/pydash-arnu515>

## Uploading to PyPI

Now that we've uploaded our package to TestPyPI, we can do the same thing to regular PyPI. Create an account on [PyPI.org](https://pypi.org/account/register) and then create an [API Token](https://pypi.org/manage/accounts#api-tokens).

Build your package again:
```bash
python3 setup.py sdist bdist_wheel
```

And upload on PyPI:
```python
python3 -m twine upload dist/*
```

> For the username enter `__token__`, and for the password, paste your API Token from PyPI.

## And we're done

Congratulations! You have a new package up and running on PyPI. For automation with Github Actions, [view Part 2](https://dev.to/arnu515/create-a-pypi-pip-package-test-it-and-publish-it-using-github-actions-part-2-1o83)