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.
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?
Example 1.1
The route, and where each step was taught
- a function+tests
"Setting up a Python project": a src layout, pyproject.toml and pytest.
- git init+.gitignore
"Commits and the staging area".
- FastAPI+Pydantic
"HTTP APIs with FastAPI".
- lock file+Dockerfile
"Setting up a Python project" and "Containers and deploying".
- 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.
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.
- A tested package
- An API around it
- Container, checks on every push, and the deploy
Engineering Toolkit overviewSpotted a mistake? Tell us.