Skip to content

← Memory, Copies and Slow Code step 9 of 38

Easy Primitives

Write a Context Manager Class

Your code borrows a database connection from a pool and must give it back, even when a query fails. A with block can do that for you. It needs a context manager, an object that with calls at the start and the end of a block. In the standard-library track you wrote one with @contextmanager. Here you write the class form, which the next exercise builds on.

with Borrowed(pool) as name:
    run_query(name)
# The connection is back in the pool here, even if run_query raised.

with calls two methods:

  • __enter__(self) runs at the start. Its return value is what as gives you.
  • __exit__(self, exc_type, exc, tb) runs at the end, whether the block finished or raised. If the block raised, the three arguments describe the exception. If it finished, all three are None.

If __exit__ returns True, Python hides the block's exception. Return None, and the exception reaches the code around the with.

What to write

Fill in __enter__ and __exit__ in Borrowed. Keep Pool and solve as they are.

  • __enter__ checks out one connection with self.pool.checkout(), stores its name in self.name, and returns the name.
  • __exit__ checks that connection back in with self.pool.checkin(...), after a step that raised as well as after one that finished.
  • __exit__ returns None, so an error raised in the block reaches the driver.

The pool

Pool(size) starts with free connections named conn0, conn1 and so on. checkout() takes the first free name, logs out:<name> and returns the name. checkin(name) adds the name to the end of the free list and logs in:<name>. With no free connection, checkout() raises RuntimeError("no free connection").

Edge cases

  • A step that raised must still give its connection back, so the next step can borrow it.
  • With a pool of size 0, checkout() raises inside __enter__. Let it raise. Python then skips the block and does not call __exit__.

What the driver reports

solve(pool_size, steps) runs each step in its own with Borrowed(pool) as name: block. The block logs use:<name>, and a "fail" step then raises ValueError("query failed"). The driver catches ValueError and RuntimeError outside the block.

  • log is the pool's log of out, use and in lines, in order.
  • free is the pool's free list at the end.
  • errors lists each error that reached the driver, such as "ValueError: query failed".