0

First the code:

class Api():
    def __init__(self, baseurl='http://localhost:8080/api'):
        self.base_url = baseurl

    def _makerequest(self, arguments):
        data = urllib.urlencode(arguments)
        req = urllib2.Request(self.base_url, data)            
        response = urllib2.urlopen(req)
        return response

    def getId(self, **kwargs):
        arguments = kwargs
        arguments['Type'] = 'getId'
        return self._makerequest(arguments)

    def getKey(self, **kwargs):
        arguments = kwargs
        arguments['Type'] = 'getKey'
        return self._makerequest(arguments)   

As you can see every function eventually calls _makerequest(). I've written it this way to have different functions (and code completion suggests) them but to minimize code reuse.

What I'm wondering is if one could somehow only have one function but call that function with different names. For example I would call getId() and getKey() but it would trigger the same function inside the class.

Any suggestions how to simplify this ?

2
  • You're never calling _makerequest(). Commented Apr 24, 2014 at 12:43
  • What are you trying to say with minimize code reuse? You want to restrict the number of operations to urllib you expose through this interface? Commented Apr 24, 2014 at 12:57

5 Answers 5

1

If you really wanted you could just use makerequest without these wrapper functions. If you wanted it to be prettier you could do:

def get(self, type, **kwargs):
    arguments = kwargs
    arguments['Type'] = 'Get'+type
    return self.makerequest(arguments)

So you could call get('Id', args) or get('Key', args).

Sign up to request clarification or add additional context in comments.

1 Comment

If the OP doesn't ever envision adding additional required/optional named arguments to the individual function calls, this is probably the best answer.
1

It seems to me like what you're taking exception to is that you have the boilerplate code

arguments = kwargs
arguments['Type'] = # Something

In every API method. Could you not simply do the following:

def _makerequest(self, rtype, **kwargs):
    kwargs['Type'] = rtype
    data = urllib.urlencode(kwargs)
    req = urllib2.Request(api.base_url, data)            
    response = urllib2.urlopen(req)
    return response

Which would allow you to do:

def getId(self, **kwargs):
    return self._makerequest('getId', **kwargs)

Comments

0

This is where Python being a dynamic language really pays off -- it's easy to get something like this working.

class DynamicMethods(object):
   def __init__(self):
      pass

   class HiddenImplementation(object):
      ''' we create a separate implementation class that:
         1. stores the 'type' of the call (or whatever else you need to
           store on a per-call basis) 
         2. define a __call__() method that lets us perform whatever our 
         dynamic method call needs to do. In this example, we just print 
         the kwargs we're passed along with the call 'type'.
      '''  
      def __init__(self, typeName):
         self.typeName = typeName

      def __call__(self, **kwargs):
         args = kwargs
         args['type'] = self.typeName
         for k, v in args.items():
            print "{0}: {1}".format(k, v)



   def __getattr__(self, name):
     ''' any time code looks up an attribute that the class doesn't have 
        explicitly, this will be called. If the attribute being looked up
        starts with 'get', we create a HiddenImplementation object and 
        return that.
     '''
      if name.startswith('get'):
         return self.HiddenImplementation(name)


if __name__ == "__main__":
   d = DynamicMethods()
   d.getId(a=1, b=2, c=3)

...prints

bgporter@varese ~/temp:python dynamicFn.py
a: 1
c: 3
b: 2
type: getId

5 Comments

While this does more or less solve the problem, I'd argue it makes it a lot less clear what is actually happening, and creates an anonymous inner class for no real reason. @joconnor's answer is much less complicated and accomplishes the same thing.
It depends on your goal, right? If you like passing in extra parameters, then rock on. If you want to hide that behind method calls, this approach is better. Compare e.g. the python implementation of xmlrpclib' vs using XML-RPC on Java or C++. For a maintainer of the code a little more complex. For a user of the code, more straightforward IMO.
Sure, but now you've not actually declared an API, you've simply wrapped a method in some helper code. I.E. the user of the code has no idea what valid requests are. Just that they start with 'get'
Re-read joconnor's answer that you're championing and explain to me how accepting a string parameter declares an API. Structurally, the differences between the two approaches are syntactical. This was the shortest possible explanation of this technique. If there are other constraints in the OP's use case, they can easily be enforced through other, regular means.
It's not, I'm saying his method accomplishes the same thing as yours and is simpler/more explicit (something typically championed as being more pythonic). Yours is definitely more powerful, but is probably overkill for the specific case unless there was really something stateful that needed to be retained in the request calls. But in that case you could probably just return the urllib2.request instance.
0

You may use decorators. Here's a simplified example

In [21]: def make_request(func):
    def _get_request(*args):
        print func(*args)
    return _get_request

In [26]: class Api(object):
    @make_request
    def hello(self, s):
        return 'Hello, '+s
    @make_request
    def goodbye(self, s):
        return 'Goodbye, '+s

In [27]: api = Api()

In [28]: api.hello('world')
Hello, world

In [29]: api.goodbye('world')
Goodbye, world

Comments

0

in Python functions and methods are the same as any other object, so the name, this object is bound to, is totally free, and there even could be no name - think of the return value is a function, or lambdas. Give _makerequests a type-argument and move the duplicate code out of the get-methods.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.