insideout Get Pro

12 · Capstone: a script becomes a deployed API

Pro

Engineering Toolkit, lesson 12

Capstone: a script becomes a deployed API

A word counter, tested, packaged, containerised and deployed.

The whole module, and "Containers and deploying" in particular. Each step below names the lesson it comes from.

Part 1. Where it starts: one script

This walkthrough takes the word counter from the Git lessons all the way to a tested API running on a server, using one step from each lesson. Here is where it ended up in "Branches, merges and conflicts": one file that reads a path from the command line, opens it, counts and prints.

count.py, as it was
import re
import sys
from collections import Counter

text = open(sys.argv[1]).read().lower()
top = int(sys.argv[2]) if len(sys.argv) > 2 else 3
words = re.findall(r"[a-z']+", text)
print("total words:", len(words), "different:", len(set(words)))
for word, n in Counter(words).most_common(top):
    print(word, n)

It works, and nothing else can use it. A test can't call it without a file on disk. An API can't call it without running a second program. Every concern, reading the arguments, opening the file, counting and printing, is wound into the others.

Your turn

Where should the work start?

Every later step calls the counting: the tests, the API and the command line. With it in a plain function, each of those is a few lines. A container or an API built around the script as it is would only package the tangle.

Example 1.1

The route, and where each step was taught

  1. a function+tests

    "Setting up a Python project": a src layout, pyproject.toml and pytest.

  2. git init+.gitignore

    "Commits and the staging area".

  3. FastAPI+Pydantic

    "HTTP APIs with FastAPI".

  4. lock file+Dockerfile

    "Setting up a Python project" and "Containers and deploying".

  5. tests on every push→deploy→logs

    "Rebasing, remotes and pull requests" and "Containers and deploying".

Here's the counting as a function, with one change. In "Setting up a Python project" a test showed that "open-plan" was counted as two words. That's a decision to make, not a fact to discover, so it's made here, written into the pattern, and later pinned down by a test: a hyphen joining two words keeps them as one.

The counting as a function, in Python
import re
from collections import Counter

# A word is letters and apostrophes, and may be joined by hyphens: "open-plan" is one word.
WORD = re.compile(r"[a-z']+(?:-[a-z']+)*")


def stats(text, top=3):
    """Count the words in text, and find the most common ones."""
    words = WORD.findall(text.lower())
    return {
        "words": len(words),
        "different": len(set(words)),
        "top": Counter(words).most_common(top),
    }


print(stats("An open-plan office. An office!", top=2))
print(stats(""))
# {'words': 5, 'different': 3, 'top': [('an', 2), ('office', 2)]}
# {'words': 0, 'different': 0, 'top': []}

Part 2. A tested package

Keep going with Pro

You've done the first part. Pro opens the rest of this lesson, and every other one.

Still to come
  1. A tested package
  2. An API around it
  3. Container, checks on every push, and the deploy

Engineering Toolkit overviewSpotted a mistake? Tell us.