Top Rated on Upwork with a 100% Job Success ScoreView on Upwork
retzdev logo
logo
Building a Python Package

Building a Python Package: Pre-Release (5)

by Jarrett Retz

February 13th, 2021

    Introduction

    In this post, I want to go through using Python's test package index and then deploying a pre-release version to the actual Python index.

    First, I changed the version. The development release was discouraged, so I am going with an alpha pre-release.

    0.0.1.a1 (Python later changed this to 0.0.1a1)

    It could easily have been 1.0.1.a1. However, it doesn't seem unusual to use the 0 epoch.

    This wasn't the last change that I had to make to the project since the last article, so let's work through a few more.

    Add pyproject.toml

    We're going back to the documentation on packaging Python projects.

    I added a pyproject.toml per the documentation. This code is pulled straight from the docs and doesn't use anything custom. The file is in the root folder at the top level.

    [build-system]
    requires = [
        "setuptools>=42",
        "wheel"
    ]
    build-backend = "setuptools.build_meta"

    setup.cfg

    Previously, the project has a setup.py file. However, it's not recommended to use that type of file because the values can be dynamic. A static setup file is preferred.

    This other static setup file, setup.cfg, has many of the same inputs as setup.py. It's short, and the syntax is a little different (no quotation marks), but the information is fundamental.

    [metadata]
    
    name = beasy
    version = 0.0.1.a1
    url = https://github.com/jdretz/simple-bea-client
    author = Jarrett Retz
    author_email = jretz@jrts.info
    classifiers =
        Programming Language :: Python :: 3
        License :: OSI Approved :: MIT License
        Operating System :: OS Independent
    description = Simple Python API client for accessing data on the Bureau of Economic Analysis application programming interface.
    
    long_description = file: README.md
    long_description_content_type = text/markdown
    
    [options]
    python_requires = >=3.6
    here = pathlib.Path(__file__).parent.resolve()
    
    # Get the long description from the README file
    long_description = (here / 'README.md').read_text(encoding='utf-8')

    Distribution Archives

    Next, we can generate the distribution archive files. We'll need the latest version of Python's build module. We can run the command below:

    python3 -m pip install --upgrade build

    Then, in our project root directory, we can run the build command to generate the files.

    python -m build

    After executing the build command, I got the following output.

    Collecting wheel
      Using cached wheel-0.36.2-py2.py3-none-any.whl (35 kB)
    Collecting setuptools>=42
      Using cached setuptools-53.0.0-py3-none-any.whl (784 kB)
    Installing collected packages: wheel, setuptools
    Successfully installed setuptools-53.0.0 wheel-0.36.2
    WARNING: You are using pip version 20.3.3; however, version 21.0.1 is available.
    You should consider upgrading via the '/Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install --upgrade pip' command.
    /private/var/folders/qw/yqqx13kn74v_mjyknjykpm2h0000gn/T/build-env-39ze5cdf/lib/python3.9/site-packages/setuptools/dist.py:461: UserWarning: Normalizing '0.0.1.a1' to '0.0.1a1'
      warnings.warn(tmpl.format(**locals()))
    running sdist
    running egg_info
    writing beasy.egg-info/PKG-INFO
    writing dependency_links to beasy.egg-info/dependency_links.txt
    writing requirements to beasy.egg-info/requires.txt
    writing top-level names to beasy.egg-info/top_level.txt
    reading manifest file 'beasy.egg-info/SOURCES.txt'
    writing manifest file 'beasy.egg-info/SOURCES.txt'
    running check
    creating beasy-0.0.1a1
    creating beasy-0.0.1a1/beasy.egg-info
    copying files to beasy-0.0.1a1...
    copying README.md -> beasy-0.0.1a1
    copying pyproject.toml -> beasy-0.0.1a1
    copying setup.cfg -> beasy-0.0.1a1
    copying setup.py -> beasy-0.0.1a1
    copying beasy.egg-info/PKG-INFO -> beasy-0.0.1a1/beasy.egg-info
    copying beasy.egg-info/SOURCES.txt -> beasy-0.0.1a1/beasy.egg-info
    copying beasy.egg-info/dependency_links.txt -> beasy-0.0.1a1/beasy.egg-info
    copying beasy.egg-info/requires.txt -> beasy-0.0.1a1/beasy.egg-info
    copying beasy.egg-info/top_level.txt -> beasy-0.0.1a1/beasy.egg-info
    Writing beasy-0.0.1a1/setup.cfg
    Creating tar archive
    removing 'beasy-0.0.1a1' (and everything under it)
    /private/var/folders/qw/yqqx13kn74v_mjyknjykpm2h0000gn/T/build-env-39ze5cdf/lib/python3.9/site-packages/setuptools/dist.py:461: UserWarning: Normalizing '0.0.1.a1' to '0.0.1a1'
      warnings.warn(tmpl.format(**locals()))
    running bdist_wheel
    running build
    installing to build/bdist.macosx-10.9-x86_64/wheel
    running install
    running install_egg_info
    running egg_info
    writing beasy.egg-info/PKG-INFO
    writing dependency_links to beasy.egg-info/dependency_links.txt
    writing requirements to beasy.egg-info/requires.txt
    writing top-level names to beasy.egg-info/top_level.txt
    reading manifest file 'beasy.egg-info/SOURCES.txt'
    writing manifest file 'beasy.egg-info/SOURCES.txt'
    Copying beasy.egg-info to build/bdist.macosx-10.9-x86_64/wheel/beasy-0.0.1a1-py3.9.egg-info
    running install_scripts
    adding license file "LICENSE" (matched pattern "LICEN[CS]E*")
    creating build/bdist.macosx-10.9-x86_64/wheel/beasy-0.0.1a1.dist-info/WHEEL
    creating '/Users/jarrettretz/Programming/bea/dist/tmp6jhwb_pj/beasy-0.0.1a1-py3-none-any.whl' and adding 'build/bdist.macosx-10.9-x86_64/wheel' to it
    adding 'beasy-0.0.1a1.dist-info/LICENSE'
    adding 'beasy-0.0.1a1.dist-info/METADATA'
    adding 'beasy-0.0.1a1.dist-info/WHEEL'
    adding 'beasy-0.0.1a1.dist-info/top_level.txt'
    adding 'beasy-0.0.1a1.dist-info/RECORD'
    removing build/bdist.macosx-10.9-x86_64/wheel
    

    It appears that things worked!

    New files were created in the project. The project structure can be represented as:

    .
    ├── LICENSE
    ├── Pipfile
    ├── Pipfile.lock
    ├── README.md
    ├── beasy
    │ ├── __init__.py
    │ ├── base.py
    │ ├── beasy.py
    │ └── tables
    │     └── tables.py
    ├── beasy.egg-info
    │ ├── PKG-INFO
    │ ├── SOURCES.txt
    │ ├── dependency_links.txt
    │ ├── requires.txt
    │ └── top_level.txt
    ├── build
    │ └── bdist.macosx-10.9-x86_64
    ├── dist
    │ ├── beasy-0.0.1a1-py3-none-any.whl
    │ └── beasy-0.0.1a1.tar.gz
    ├── pyproject.toml
    ├── setup.cfg
    ├── setup.py
    └── tests
    

    The two new folders are:

    1. /build
    2. /dist

    Publishing to Test Python Index

    Python has a useful test package index. It's called Test PyPI. The first thing you’ll need to do is register an account on Test PyPI.

    Then, create an API token for uploading to the test index.

    We'll have to do both of these tasks (registering and creating an API token) when we upload to the actual index.

    Next, we can make sure that we have twine installed and in its latest version.

    python3 -m pip install --upgrade twine

    Once installed, run Twine to upload all of the archives under dist:

    python3 -m twine upload --repository testpypi dist/*

    You will be prompted for a username and password. For the username, use __token__. For the password, use the API token value, including the pypi- prefix.

    jarrettretz@MacBook-Pro bea % python3 -m twine upload --repository testpypi dist/*
    Uploading distributions to https://test.pypi.org/legacy/
    Enter your username: __token__
    Enter your password: 
    Uploading beasy-0.0.1a1-py3-none-any.whl
    100%|██████████████████████████████████████| 7.49k/7.49k [00:01<00:00, 6.25kB/s]
    Uploading beasy-0.0.1a1.tar.gz
    100%|██████████████████████████████████████| 9.55k/9.55k [00:01<00:00, 8.70kB/s]
    
    View at:
    https://test.pypi.org/project/beasy/0.0.1a1/

    Success, again! I was able to view the package online!

    You can also install the packages on the test index. However, it's not guarantee how long they will be available. To install the test package, I ran:

    pip install -i --no-deps --index-url https://test.pypi.org/simple/ beasy==0.0.1a1

    jarrettretz@MacBook-Pro ~ % conda activate base
    (base) jarrettretz@MacBook-Pro ~ % pip install -i --no-deps --index-url https://test.pypi.org/simple/ beasy==0.0.1a1
    Looking in indexes: https://test.pypi.org/simple/
    Requirement already satisfied: beasy==0.0.1a1 in ./Programming/bea (0.0.1a1)
    Requirement already satisfied: requests in /opt/anaconda3/lib/python3.8/site-packages (from beasy==0.0.1a1) (2.25.0)
    Requirement already satisfied: chardet<4,>=3.0.2 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (3.0.4)
    Requirement already satisfied: idna<3,>=2.5 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (2.10)
    Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (2020.11.8)
    Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (1.25.11)
    WARNING: You are using pip version 21.0; however, version 21.0.1 is available.
    You should consider upgrading via the '/opt/anaconda3/bin/python -m pip install --upgrade pip' command.
    (base) jarrettretz@MacBook-Pro ~ % python
    Python 3.8.3 (default, Jul  2 2020, 11:26:31) 
    [Clang 10.0.0 ] :: Anaconda, Inc. on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from beasy.beasy import Bea
    >>> 
    

    Now that we know the package works, we can try to publish it to the real index.

    Publish to PyPI

    As I mentioned, we now need to create an account on PyPI. The credentials aren't shared. Additionally, we need a token for the actual index.

    We already created the distribution files. Therefore, we need to publish these to PyPI using twine.

    In the root project folder, run:

    twine upload dist/*

    Enter __token__ for username and the new API token for the password.

    jarrettretz@MacBook-Pro bea % twine upload dist/*
    Uploading distributions to https://upload.pypi.org/legacy/
    Enter your username: __token__
    Enter your password: 
    Uploading beasy-0.0.1a1-py3-none-any.whl
    100%|████████████████████████████████████████████████████████████████████████████████████████| 7.49k/7.49k [00:00<00:00, 20.7kB/s]
    100%|████████████████████████████████████████████████████████████████████████████████████████| 7.49k/7.49k [00:01<00:00, 4.51kB/s]
    Uploading beasy-0.0.1a1.tar.gz
    100%|████████████████████████████████████████████████████████████████████████████████████████| 9.55k/9.55k [00:00<00:00, 10.7kB/s]
    
    View at:
    https://pypi.org/project/beasy/0.0.1a1/

    Now, the package is live at https://pypi.org/project/beasy/0.0.1a1/.

    Because the package is using an alpha version, it can be installed with pip install beasy==0.0.1a1. This statement is explicit about the version.

    Python removed the period that I had before the a in the version name, so I needed to update the README.md.

    Upload to Github

    Finally, since the package is live, I need to upload the project to Github.

    I found a basic .gitignore file online that will prevent unnecessary files from being pushed.

    To push the repository to Github, in the repository root, I first ran the command git init to push the repository to Github in the repository root.

    After that, I added the remote origin:

    git remote add origin https://github.com/jdretz/simple-bea-client.git

    Finally, I committed the files (with the message "init commit") and pushed up to Github.

    git push -u origin master

    The project is now ready for use on PyPI and available on Github at https://github.com/jdretz/simple-bea-client.

    Conclusion

    This has been a reasonable and enjoyable experience. If you have made it through reading the full series, I hope it was beneficial somehow.

    I still need to publish the first version and create examples for the README. However, the concludes this series on building a Python package. Thanks for reading!

    Jarrett Retz

    Jarrett Retz is a freelance web application developer and blogger based out of Spokane, WA.

    jarrett@retz.dev

    Subscribe to get instant updates

    Contact

    jarrett@retz.dev

    Legal

    Any code contained in the articles on this site is released under the MIT license. Copyright 2022. Jarrett Retz Tech Services L.L.C. All Rights Reserved.