Skip to content

← Everyday Standard Library Tools step 43 of 55

Easy Primitives

Close a Stream Only If You Opened It

Write two helpers that each make one decision. read_or_default falls back to a default only when a file is missing. process closes a stream only when it opened that stream itself.

read_or_default(lambda: "payload", "DEFAULT")  # "payload"
read_or_default(load_missing_file, "DEFAULT")  # "DEFAULT"
process(None, lambda: FakeStream("opened", log))
# "DATA FROM OPENED", and the new stream is closed

What to write

read_or_default(load, default):

  • Call load() once and return what it returns, even an empty string.
  • If it raises FileNotFoundError, return default.
  • Let every other error through, including PermissionError and IsADirectoryError.

Use suppress(FileNotFoundError). The starter suppresses Exception, which hides the permission error too.

process(stream, factory) returns the stream's read() result in upper case:

  • If the caller passes a stream, read it. Do not call factory, and do not close stream, because the caller owns it.
  • If stream is None, call factory() once, read the new stream, and close it when you finish, even if read() raises.
  • If factory() itself raises, let the error through.

Pick the context manager with nullcontext(stream) for a given stream and closing(factory()) for a new one. Annotate it as AbstractContextManager[FakeStream], and write the reading code once, inside one with block. The starter repeats the reading code and closes a stream it did not open.

FakeStream.read() returns "data from <name>". Its close() sets closed to True and appends "close:<name>" to a shared log. It has no __enter__, which is why it needs closing.

Write solve

solve(mode, provided) does these steps:

  1. Define a load function. It raises FileNotFoundError when mode is "missing", raises PermissionError when mode is "denied", and returns "payload" otherwise.
  2. Call read_or_default(load, "DEFAULT") and keep the result as text. If a PermissionError comes out, set text to the string "PermissionError". Go on to step 3 in every case.
  3. Create an empty log list. If provided is true, pass FakeStream("given", log) to process. Otherwise pass None. The factory always creates FakeStream("opened", log).
  4. Return a dict with the keys text, result, log and given_closed. result is what process returned. given_closed is the given stream's closed value, or None when there is no given stream.

Expected results

  • With provided=True, result is "DATA FROM GIVEN", log is [] and given_closed is False.
  • With provided=False, result is "DATA FROM OPENED", log is ["close:opened"] and given_closed is None.
  • text is "DEFAULT" for "missing", "PermissionError" for "denied", and "payload" for any other mode.