|

GitHub Actions CI/CD (1/10): Make It a Package You Can Test

← Previous GitHub Actions CI/CD (1/10) Next →

Summary: Build a small Python package called helloci with a testable function and a CLI entry point. This is the project you will carry through all ten parts of the series — by the end, it will have a full CI/CD pipeline on GitHub Actions.

KeyValue
Package namehelloci
Python version3.12
Working directory~/projects/helloci
Source layoutsrc/helloci/
CLI commandhelloci greet Alice

0. Prerequisites

  • Python 3.12 or later installed
  • pip available on your PATH
  • A terminal open and ready

Note: This series builds one project from scratch. Every part adds to what you built in the previous part. Start here.


1. Create the Project Directory

Create a working directory and move into it.

mkdir -p ~/projects/helloci
cd ~/projects/hellociCode language: Shell Session (shell)

Create a virtual environment and activate it.

python -m venv .venv
source .venv/bin/activateCode language: Shell Session (shell)

Your prompt should now show (.venv) at the beginning. Every command from here on runs inside this virtual environment.


2. Set Up the Source Layout

Python packages use a src layout so that tests import the installed package, not the local source files. This small detail prevents a whole class of “works on my machine” bugs.

Create the package directory.

mkdir -p src/hellociCode language: Shell Session (shell)

Create the file src/helloci/__init__.py.

"""helloci — a tiny package for learning CI/CD."""


def greet(name: str) -> str:
    """Return a greeting string."""
    if not name or not name.strip():
        raise ValueError("name must not be empty")
    return f"Hello, {name}!"Code language: Python (python)

This is the function that the rest of the series will test, lint, and eventually ship to PyPI. It does one thing — takes a name and returns a greeting. The ValueError guard gives you something meaningful to test later.


3. Add a CLI Entry Point

A CLI entry point lets users run helloci greet Alice from the terminal after installing the package. Create the file src/helloci/cli.py.

"""Command-line interface for helloci."""

import argparse
import sys

from helloci import greet


def main(argv: list[str] | None = None) -> None:
    """Parse arguments and print a greeting."""
    parser = argparse.ArgumentParser(
        prog="helloci",
        description="A tiny CLI for learning CI/CD.",
    )
    subparsers = parser.add_subparsers(dest="command")

    greet_parser = subparsers.add_parser("greet", help="Greet someone by name")
    greet_parser.add_argument("name", help="Name to greet")

    args = parser.parse_args(argv)

    if args.command == "greet":
        print(greet(args.name))
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()Code language: Python (python)

The argv parameter defaults to None, which makes argparse read from sys.argv. Passing an explicit list makes this function easy to test in Part 2 — you can call main(["greet", "Alice"]) without spawning a subprocess.


4. Create pyproject.toml

pyproject.toml is the single configuration file that tells Python how to build and install your package. Create it in the project root ~/projects/helloci/pyproject.toml.

[build-system]
requires = ["setuptools>=68.0", "setuptools-scm"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "helloci"
version = "0.1.0"
description = "A tiny package for learning GitHub Actions CI/CD."
requires-python = ">=3.10"
license = "MIT"
authors = [
    { name = "Rex Bytes", email = "[email protected]" },
]

[project.scripts]
helloci = "helloci.cli:main"

[tool.setuptools.packages.find]
where = ["src"]Code language: TOML, also INI (ini)

Key fields:

  • [build-system] — tells pip which tool builds the package (setuptools).
  • [project] — metadata: name, version, Python requirement.
  • [project.scripts] — maps the command helloci to the function helloci.cli:main.
  • [tool.setuptools.packages.find] — tells setuptools to look inside src/ for packages.

Tip: requires-python = ">=3.10" means your package claims to work on Python 3.10 and above. In Part 5 you will prove this claim with matrix testing.


5. Install the Package Locally

Install in editable mode so that changes to your source files take effect immediately without reinstalling.

pip install -e .Code language: Shell Session (shell)
Successfully installed helloci-0.1.0

Editable mode (-e) creates a link from your virtual environment to the src/ directory. This is exactly what CI will do later — install the package, then run checks against it.


6. Test the CLI

Run the CLI command.

helloci greet AliceCode language: Shell Session (shell)
Hello, Alice!Code language: Shell Session (shell)

Try it without arguments to see the help text.

hellociCode language: Shell Session (shell)
usage: helloci [-h] {greet} ...

A tiny CLI for learning CI/CD.

positional arguments:
  {greet}
    greet     Greet someone by name

options:
  -h, --help  show this help message and exitCode language: Shell Session (shell)

The package installs, the CLI runs, and the function returns the expected output. You now have something worth protecting with automated tests.


7. Check Your Project Structure

Your project should look like this.

~/projects/helloci/
├── pyproject.toml
├── src/
│   └── helloci/
│       ├── __init__.py
│       └── cli.py
└── .venv/Code language: Shell Session (shell)

Note: You do not need a setup.py or setup.cfg. Modern Python packaging uses pyproject.toml exclusively.


Summary

You created a Python package called helloci with a greet() function and a CLI entry point. The package installs cleanly with pip install -e . and runs from the terminal with helloci greet Alice.

  • src/helloci/__init__.py — the core function
  • src/helloci/cli.py — the CLI that calls it
  • pyproject.toml — build configuration and metadata

Everything CI will do later — install, test, lint, build — starts with this installable package. In Part 2 you will add pytest and write your first unit tests.

Similar Posts

Leave a Reply