I am building a Python API wrapper for a SOAP API. I need to design a set of API classes which contain a varied number of verb actions (add
, get
, list
, remove
etc.). Additionally, because of the nature of the objects in the SOAP API, these may also contain additional methods from a subset of (sync
, options
, apply
, restart
and reset
). Internals of each verb means that very few cases require overriding and can easily be inherited. My problem is that some endpoints are singletons, or for whatever reason may only support a subset of these methods. Meaning that:
EndpointA
get
onlyEndpointB
add
, get
, list
, remove
apply
and restart
EndpointC
add
and get
onlyEndpointD
add
, get
, list
, remove
apply
, restart
, reset
I have more than 100 endpoints. Most fit a common theme of:
add
, get
, list
, remove
There are, however, many exceptions.
In my current design, all endpoints instantiate with Client
attribute that controls the SOAP connection, requests and responses on the wire. I am looking for an approach to flexibly create a class design allowing me to drop in methods without needing to duplicate code or inadvertently inherit methods that an API endpoint doesn't support.
A flat inheritance is problematic as I don't have enough flexibility for all the permutations for methods.
BaseAPI(object):
def __init___(self, client):
self.client = client
ChildAPI(BaseAPI):
def __init___(self, client):
super().__init__(client)
def get(self, **kwargs):
soap_method = methodcaller("".join(["get", self.__class__.__name__]), **kwargs)
resp = soap_method(self.client.service)
return resp
def list(self, **kwargs):
soap_method = methodcaller("".join(["list", self.__class__.__name__]), **kwargs)
resp = soap_method(self.client.service)
return stuff
# same for add and remove...
EndpointA(BaseAPI):
def __init___(self, client):
super().__init__(client)
# now i have a problem as i only wanted the get method...
EndpointD(BaseAPI):
def __init___(self, client):
super().__init__(client)
# I have all the methods I wanted...
I was thinking about mixins, but as you can see, the verb methods are dependent on the common client. From my understanding, mixins should only inherit from object
.
Can anyone suggest how to lay out my class design to promote re-use as far as possible, and avoid a lack of flexibility?
Do I understand it right that the method's implementation, if the Endpoints support it, are all the same for all Endpoints? In this case, you could solve it with a little list that holds the supported methods names and gets overwritten by every Endpoint subclass:
import functools
class BaseAPI:
def __init___(self, client):
self.client = client
class ChildAPI(BaseAPI):
supported_methods = ["add", "get", "list", "remove", "apply", "restart", "reset"]
@classmethod
def assert_supported(cls, func):
"""Decorator looks up func's name in self.supported_methods."""
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
if func.__name__ not in self.supported_methods:
raise AttributeError("This Endpoint does not support this method.")
return func(self, *args, **kwargs)
return wrapper
# implementation of methods here, each decorated with @assert_supported
@assert_supported
def get(self, **kwargs):
soap_method = methodcaller("".join(["get", self.__class__.__name__]), **kwargs)
resp = soap_method(self.client.service)
return resp
@assert_supported
def list(self, **kwargs):
soap_method = methodcaller("".join(["list", self.__class__.__name__]), **kwargs)
resp = soap_method(self.client.service)
return stuff
class EndpointA(BaseAPI):
supported_methods = ["get"]
class EndpointB(BaseAPI):
supported_methods = ["add", "get", "list", "remove", "apply", "restart"]
class EndpointC(BaseAPI):
supported_methods = ["add", "get"]
class EndpointD(BaseAPI):
pass # no change to supported_methods => all of them
This reduces the effort for the adaption of the base methods to one single line per class, and still gives you the flexibility to add whatever code necessary.