Catalog PyPI
PACKAGE EXPLORERView in registry

aerkoc-bookstore

PyPI

Book management CLI demonstrating hexagonal architecture

Versions collected1
Direct dependencies2From release 0.1.0
Last observed20 Oct, 20:29 UTC

README

Version 0.1.0 documentation

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 pointFileUse case
Inline reusable workflowbook-manager-inline.ymlTurnkey job, all steps self-contained
Action-backed reusable workflowbook-manager.ymlTurnkey job, delegates to the composite action
Composite action.github/actions/book-managerDrop-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:

  1. Swap JsonBookStore for a PostgresBookStore without changing the service
  2. In tests, inject a MagicMock(spec=BookStorePort) — no file I/O needed
  3. The CLI is just a thin shell that parses args and delegates to the service

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.012 Jun 20262

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 20:29 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:1fdbd51b443bcd157345509f2456c4b77ab5eec8f4fbc726bde57fe1596a153a