Python3“类工厂”-例如:API(令牌).MyClass()

时间:2019-09-06 14:04:32

标签: python design-patterns factory

我正在为API编写python REST客户端。

API需要身份验证,我想在同一脚本上运行许多API客户端对象。

我当前的API代码如下:

class RestAPI:
  def __init__(self, id):
    self.id = id
    self.fetch()

  def fetch(self):
    requests.get(self.url+self.id, auth=self.apikey)

class Purchase(RestAPI):
  url = 'http://example.com/purchases/'
class Invoice(RestAPI):
  url = 'http://example.com/invoices/'
...

我想这样使用API​​:

api_admin = Api('adminmytoken')
api_user = Api('usertoken')
…
amount = api_admin.Purchase(2).amount

api_user.Purchase(2).amount # raises because api_user is not authorized for this purchase

问题在于,每个对象需要根据我要使用的客户端知道它的 apikey

在我看来,这种模式对“类工厂”来说很像:所有RestAPI类都需要知道所提供的令牌。

如何干净地做到这一点而无需手动为每个模型提供令牌?

1 个答案:

答案 0 :(得分:0)

我认为这里的问题是您的设计有些落后。继承可能不是这里的关键。我可能要做的是将api令牌作为User类的参数,然后传递给Rest接口上的实例级绑定:

class APIUser:
    def __init__(self, id, api_key, **kwargs):
        self._rest = Interface(id, api_key, **kwargs)

    def purchase(self, some_arg):
        # the interface itself does the actual legwork, 
        # and you are simply using APIUser to call functions with the interface
        return self._rest.fetch('PURCHASE', some_arg)


class Interface:
    methods = {
        # call you want  (class   url)
        'PURCHASE': (Purchase, 'https://myexample.com/purchases'),
        'INVOICE': (Invoice, 'https://myexample.com/invoices'),
        # add more methods here
    }
    def __init__(self, id, key):
        self.id = id
        self.key = key
        self.session = requests.Session()

    def _fetch(self, method, *args, **kwargs):
        # do some methods to go get data
        try:
            # use the interface to look up your class objects
            # which you may or may not need
            _class, url = self.methods[method]
        except KeyError as e:
            raise ValueError(f"Got unsupported method, expected "
                        f"{'\n'.join(self.methods)}") from e

        headers = kwargs.pop('headers', {})

        # I'm not sure the actual interface here, maybe you call the
        # url to get metadata to populate the class with first...
        req = requests.Request(_class.http_method, url+self.id, auth=self.key, headers=headers).prepare()
        resp = self.session.send(req)

        # this will raise the 401 ahead of time
        resp.raise_for_status()

        # maybe your object uses metadata from the response
        params = resp.json()
        # return the business object only if the user should see it
        return _class(*args, **kwargs, **params)


class Purchase:
    http_method = 'GET'
    def __init__(self, *args, **kwargs):
        # do some setup here with your params passed by the json
        # from the api


user = APIUser("token", "key") # this is my user session
some_purchase = user.purchase(2) # will raise a 401 Unauthorized error from the requests session

admin = APIUser("admintoken", "adminkey") # admin session
some_purchase = admin.purchase(2)

# returns a purchase object

some_purchase.amount

出于某些原因,您可能想采用这种方式:

  1. 如果不允许看到该物体,您不会将其取回
  2. 现在rest接口可以控制谁看到了什么,并且隐式绑定到用户对象本身,而其他所有类都无需知道发生了什么
  3. 您可以在一个地方更改网址(如果需要)
  4. 您的业务对象仅仅是业务对象,它们不需要执行其他任何操作

通过分离出对象的实际含义,您仍然只需要将api键和令牌一次传递给User类。接口绑定在实例上,仍然可以在同一脚本中为多个用户提供灵活性。

您还将获得明确调用的模型。如果您尝试建立模型,则必须调用它,然后接口才能执行身份验证。您不再需要由业务对象强制执行身份验证