designing python api. reliable cleanup vs easy-to-use in interpreter

98 views Asked by At

I'm working on a design of the python-based API. At the moment I've met an issue with two divergent requirements. On the one side I would like to provide reliable way to cleanup API-related resources. Thus as I know the best way to do it is to use context managers like:

# lib

class Client(object):
  def __enter__(self):
    return self

  def __exit__(self, exc_type, exc_val, tb):
    do_cleanup()

  def method1(self):
    pass

  def method2(self):
    pass

# api user
import mylib

with mylib.Client() as client:
  client.method1()
  client.method2()

On the other hand I would like to provide a way to seamlessly use my lib in interactive interpreter. But using compound construct like with or try-except-finally in interpreter makes usage of interpreter not so groovy because with-block is treated as a single statement. And it's would be preferred to use single statement per single api method like:

# interpreter session

>>> import mylib
>>> client = mylib.Client()
<client object at ...>
>>> client.method1()
True
>>> client.method2()
100

So, could I have any options here? Definitely there is a way to provide different usage semantics for scripts and for interpreter, but I would like to use it as a last resort.

2

There are 2 answers

0
ford On

The typical way to do this is to provide a method to do the cleanup manually, and have __exit__ call that method. For your example, if do_cleanup was implemented as a method, it can just be called from the interpreter when done with it.

class Client(object):
  def __enter__(self):
    return self

  def __exit__(self, exc_type, exc_val, tb):
    self.do_cleanup()

  def do_cleanup(self):
    pass

  def method1(self):
    pass

  def method2(self):
    pass

Then, in the interpreter:

>>> import mylib
>>> client = mylib.Client()
<client object at ...>
>>> client.method1()
True
>>> client.method2()
100
>>> client.do_cleanup()

I would recommend renaming do_cleanup to close or similar, so that the similarity between File Objects is more obvious.

0
xtofl On

You could create your 'base' logic, with open and close functions, in an implementation module, and extend these classes with a context manager:

#in private module mymodule.impl
class Base(object):
    def open(self, ...):
      ...
    def close(self):
      ...

This file would normally not be included by the client code.

Instead, client code imports exported.api, which looks like this:

#in public package mymodule
import mymodule.impl

class Base(mymodule.impl.Base):
    def __enter__(self):
        return self
    def __exit__(self, ...):
        self.close()