GitHub Actions CI/CD (1/10): Make It a Package You Can Test
Summary: Build a small Python package called
hellociwith 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.
| Key | Value |
|---|---|
| Package name | helloci |
| Python version | 3.12 |
| Working directory | ~/projects/helloci |
| Source layout | src/helloci/ |
| CLI command | helloci greet Alice |
0. Prerequisites
- Python
3.12or later installed pipavailable 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]— tellspipwhich tool builds the package (setuptools).[project]— metadata: name, version, Python requirement.[project.scripts]— maps the commandhellocito the functionhelloci.cli:main.[tool.setuptools.packages.find]— tells setuptools to look insidesrc/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.pyorsetup.cfg. Modern Python packaging usespyproject.tomlexclusively.
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 functionsrc/helloci/cli.py— the CLI that calls itpyproject.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.
GitHub Actions CI/CD — All Parts
- 1 GitHub Actions CI/CD (1/10): Make It a Package You Can Test You are here
- 2 GitHub Actions CI/CD (2/10): Unit Tests — Fast Feedback
- 3 GitHub Actions CI/CD (3/10): Quality Gate Before Tests — Lint and Formatting
- 4 GitHub Actions CI/CD (4/10): Your First CI Workflow — Run on Every PR
- 5 GitHub Actions CI/CD (5/10): Matrix Testing — Multiple Python Versions
- 6 GitHub Actions CI/CD (6/10): Integration Tests — Real Dependencies in CI
- 7 GitHub Actions CI/CD (7/10): Branch Protection — Make CI a Merge Gate
- 8 GitHub Actions CI/CD (8/10): Artifacts — Keep Evidence From a Run
- 9 GitHub Actions CI/CD (9/10): Release Workflow — Tag, Build, Publish
- 10 GitHub Actions CI/CD (10/10): Going Professional — Split Jobs, Caching, and Nightly Builds
