2
\$\begingroup\$

I have an API which I want to consume in Python 3.x using the requests module. Right now, my solution is to create Requests objects for each URL like the LoginRequest, etc. (see below).

I have one request per API URL and need to write the __init__ method for each class depending on the parameters that are requested for the API. I find it tedious and I am not sure that's a good design.

Could you suggest a better design? (e.g. not using OOP, with metaclass, ... ?)

class BaseRequest:
    USER_AGENT = 'My App 1.0'
    headers = {'User-Agent': USER_AGENT}
    url = None
    data = None
    params = None

    def __init__(self, url, data=None, headers=None, params=None):
        self.url = url
        if data:
            data.update(self.data or {})
            self.data = data
        if headers:
            headers.update(self.headers or {})
            self.headers = headers
        if params:
            params.update(self.params or {})
            self.params = params

    def process(self):
        r = requests.post(self.url, data=self.data, headers=self.headers, params=self.params)
        try:
            j = r.json()
        except:
            print(self.url, self.params, self.data)
            print(r.text)
            exit()
        return j


class AuthenticatedRequest(BaseRequest):
    API_KEY = 'blah'
    APP_ID = 'OfficialClient'
    data = {'appkey': API_KEY, 'appid': APP_ID}


class LoginRequest(AuthenticatedRequest):
    url = 'https://my.api.com/login'

    def __init__(self, email, password):
        super().__init__(self.url, data=dict(email=email, password=password, action='login'))

# now let's play with the API !
user = LoginRequest(email, password).process()
GetUserInfoDetailsRequest(...).process()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

It makes sense to model the API like this if things are more complicated, e.g. ability to pass around requests and responses as objects might come in handy.

The last example shows why this alone isn't that user-friendly though: In addition to mentioning the specific request (with the Request suffix always around) you also have to call the process method.

I'd suggest an additional wrapper to make things easier in the normal case, i.e. have login function or method that does this for you.


The code as-is looks good to me with the exception of the process method - firstly you can just write return r.json(), secondly the exit call in there is ... questionable. I most certainly wouldn't want to have a library call that on my program ever.

A minor thing I've also noticed is that the subclasses of BaseRequest can't override any values via the constructor. I'm guessing that's deliberate, I just usually expect to be able to do that (so in the base class call self.headers.update(headers) instead of the other way round).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.