Book Manager — Hexagonal Architecture Example
A minimal example demonstrating the ports & adapters (hexagonal) pattern
with a Typer CLI, mirroring the structure used in the smus package.
Structure
book_manager/
├── __init__.py ← Factory (wires adapter into service)
├── __main__.py ← Entry point: python -m book_manager
├── model.py ← Data classes (Book, requests)
├── ports.py ← Protocol (interface) — what the service expects
├── service.py ← Business logic — only talks to the port
├── cli.py ← Typer commands — parses args, calls service
└── adapters/
└── json_store.py ← Concrete implementation (stores books as JSON)
tests/
└── test_service.py ← Unit tests with mocked port
How the layers connect
CLI (cli.py)
│ parses user input, calls service
▼
Service (service.py)
│ contains business rules, uses self._store (typed as BookStorePort)
▼
Port (ports.py)
│ Protocol class — defines method signatures only
▼
Adapter (adapters/json_store.py)
implements the port methods, talks to the filesystem
Install
# With uv (recommended)
uv sync --extra dev
# Or with pip
pip install -e ".[dev]"
Run it
# Add a book
uv run books add --isbn "978-0441172719" --title "Dune" --author "Frank Herbert"
# List all books
uv run books list
# Get a specific book
uv run books get --isbn "978-0441172719"
# Remove a book
uv run books remove --isbn "978-0441172719"
The store location defaults to books.json in the current directory. Override it
with the BOOKS_FILE environment variable:
BOOKS_FILE=data/library.json uv run books list
Run tests
uv run pytest
Use it from another repo's GitHub Actions
The CLI is published on PyPI as aerkoc-bookstore, so consumers install it directly
from there — no token, no repo access needed. Three entry points are available:
| Entry point | File | Use case |
|---|---|---|
| Inline reusable workflow | book-manager-inline.yml |
Turnkey job, all steps self-contained |
| Action-backed reusable workflow | book-manager.yml |
Turnkey job, delegates to the composite action |
| Composite action | .github/actions/book-manager |
Drop-in step inside your own job |
Minimal example in a consumer repo (.github/workflows/manage-books.yml):
name: Manage bookstore
on:
workflow_dispatch:
inputs:
command: { type: choice, options: [add, list, get, remove], required: true }
isbn: { type: string, required: false }
title: { type: string, required: false }
author: { type: string, required: false }
jobs:
books:
uses: sly01/bookstore/.github/workflows/book-manager-inline.yml@v0.1.0
with:
command: ${{ github.event.inputs.command }}
isbn: ${{ github.event.inputs.isbn }}
title: ${{ github.event.inputs.title }}
author: ${{ github.event.inputs.author }}
books-file: books.json
commit-changes: true
# version: "0.1.0" # pin to a specific PyPI release; omit for latest
No secrets: block needed. A full copy-paste example with all three options lives in
examples/consumer-manage-books.yml.
Publishing a release
Pushing a version tag triggers the publish workflow automatically:
git tag v0.1.0
git push origin v0.1.0
This builds the wheel and uploads it to pypi.org/project/aerkoc-bookstore/ using Trusted Publisher (OIDC — no API
token stored in secrets). See .github/workflows/publish.yml for the one-time PyPI
setup instructions.
Key takeaway
The service never imports the adapter. It only knows about BookStorePort.
This means you can:
- Swap
JsonBookStorefor aPostgresBookStorewithout changing the service - In tests, inject a
MagicMock(spec=BookStorePort)— no file I/O needed - The CLI is just a thin shell that parses args and delegates to the service
Metadata
Release files for aerkoc-bookstore 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aerkoc_bookstore-0.1.0.tar.gz | 17.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aerkoc_bookstore-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.0 kB
Release files / aerkoc_bookstore-0.1.0.tar.gz
| Download URL | aerkoc_bookstore-0.1.0.tar.gz |
|---|---|
| Size | 17.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3daeaf22bb05f826ec21c6a7b33594f7ed1d51c289c454350842358031430ae
|
|
BLAKE2b-256 checksum How to use checksums |
c6d08116c75912bc98b82dcb8b1f2a614adebce03309fb607a8630ea41ed72f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / aerkoc_bookstore-0.1.0-py3-none-any.whl
| Download URL | aerkoc_bookstore-0.1.0-py3-none-any.whl |
|---|---|
| Size | 7.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07d76178e3cc8fe9bfb666b9b1700f46c7b36fb42fe95ea3b06d77c04686eda9
|
|
BLAKE2b-256 checksum How to use checksums |
4746badfe26f567e131c020346f0ce19d7a8d76957b4aa7d044c3a1a7033de1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logSecurity for aerkoc-bookstore 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for aerkoc_bookstore-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.