Now we can just put the pieces together and add a function to serve a Web display of a list of tasks:
def list_tasks(task_repository: TaskRepository = repo):
return "<br>".join([str(task) for task in task_repository.get_all_tasks()])
No, this is not valid HTML. We will worry about that later. Any modern Web browser will display this without trouble.
You can verify that this code does, in fact, show you a list of tasks by navigating to 0.0.0.0:8000 and viewing it.
It's worth thinking about why that sort of manual verification is the best way to test the prototype's functionality right now. From the beginning we have worked to include code that tests the software. Now, for what might seem to be the essential core of the program, we are just firing up a Web browser and manually verifying that things work.
First, you are now in a position to see that what seems to be the essential core of the program in fact is not. Web servers are not easy to program, but we have taken one as a dependency, and once we have one, we only need to feed it the relevant information to display. Ensuring that that information is correct is the job of other infrastructure: e.g., properly architected domain objects and repositories.
If this seems obvious to you, great. It is not obvious to most professional programmers, and the modal piece of the world's software is tremendously damaged as a result.
list_tasks() does almost nothing: that's how it should be. It exists to be wrapped by Bottle's
@route decorator. Given a task repository, it retreives a list of tasks: that process is tested with the repository. It constructs some invalid HTML: things can certainly go wrong there, but soon enough we'll separate this out into a separate, and separately testable, unit of code. And it serves that result over HTTP: that's Bottle's job.
That is emphatically not to say that nothing can go wrong. Lots can go wrong. This is an emergent system. Its behavior can go wrong even if its parts have been tested to the moon. (And we have not tested those parts to the moon.) But it is little enough that we can worry about it later, testing it with our Web browsers and manual checks rather than with Pytest.
The flipside is that we really can't test
list_tasks() easily with Pytest. The effects of routing it will give Pytest problems; from Pytest's perspective, it is no longer the one-liner it appears to be (because of how decorators work under the hood).
Other Web frameworks are vastly more popular than Bottle, but the pattern of wrapping functions in decorators to produce Web views is extremely common. Please remember that for these functions to be very short is a sign of health. Again, this is for two complementary reasons: First, any substantial work relevant to their functioning is almost always properly located elsewhere; second, they can rarely be tested with the same infrastructure you'll be using for your lowest-friction, everyday-support tests.
Next post: Python task manager from scratch, part 17: Generating some HTML