FAQ
Hi,
I'm playing with python internals to make objects behave like this:


if I access to "object.attribute" I want to return the result of an
HTTP GET request. However if i call "object.attribute()" I want an
HTTP POST request to be executed.


So far I have been able to do the POST part, using two classes like this:


class HTTPAttribute(object):
    def __init__(self, name):
        self.name = name


    def __call__(self, *args, **kwargs):
        url = BASE_URL + self.name
        requests.post(url, *args, **kwargs)




class HTTPObject(object):
     def __getattr__(self, name):
         return HTTPAttribute(name)




But I'm stuck implementing the HTTP GET request when accessing
"object.attribute", Any idea ??


Thanks!!
--
Marc

Search Discussions

  • Devin Jeanpierre at Nov 24, 2013 at 1:04 pm

    On Sun, Nov 24, 2013 at 4:52 AM, Marc Aymerich wrote:
    Hi,
    I'm playing with python internals to make objects behave like this:

    if I access to "object.attribute" I want to return the result of an
    HTTP GET request. However if i call "object.attribute()" I want an
    HTTP POST request to be executed.

    Uh oh. What you want is impossible. You cannot call an attribute
    without first accessing it. :(


    -- Devin
  • Chris Angelico at Nov 24, 2013 at 1:05 pm

    On Sun, Nov 24, 2013 at 11:52 PM, Marc Aymerich wrote:
    if I access to "object.attribute" I want to return the result of an
    HTTP GET request. However if i call "object.attribute()" I want an
    HTTP POST request to be executed.

    That's fundamentally difficult, because object.attribute() first
    evaluates object.attribute, then calls it. The only way you can have
    that work, then, is if you have one version (calling it) do the POST
    call, and something else (maybe conversion to str?) do the GET call.
    But it's not going to be easy. I would recommend having both of them
    done with a call; since a POST request will almost always include a
    request body and a GET request almost never, I would be inclined to a
    model like this:


    def __call__(self, METHOD=None, **params):
         METHOD = method or ('POST' if params else 'GET')
         # proceed to use the given method


    This way, you simply call it with no args for a GET, or with form
    fill-out args for POST, and you can override if you want to (eg for a
    PUT request, or doing something unusual like POST without data).


    ChrisA
  • Steven D'Aprano at Nov 24, 2013 at 1:45 pm

    On Sun, 24 Nov 2013 05:04:16 -0800, Devin Jeanpierre wrote:

    On Sun, Nov 24, 2013 at 4:52 AM, Marc Aymerich wrote:
    Hi,
    I'm playing with python internals to make objects behave like this:

    if I access to "object.attribute" I want to return the result of an
    HTTP GET request. However if i call "object.attribute()" I want an HTTP
    POST request to be executed.
    Uh oh. What you want is impossible. You cannot call an attribute without
    first accessing it. :(

    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.




    class CallableString(str):
         # Like a string, but callable.
         def function(self):
             raise NotImplementedError(
                     "this must be overridden on the instance"
                     )
         def __call__(self):
             return self.function()




    class Magic_HTTP_Thing:
         @property
         def attribute(self):
             result = CallableStr(self.do_get())
             result.function = lambda: self.do_put()
         def do_get(self):
             # Do a HTTP GET request.
             return "Get stuff"
         def do_put(self):
             # Do a HTTP PUT request.
             return "Put stuff"




    Possible or not, it doesn't seem like a reasonable API to me.




    --
    Steven
  • Devin Jeanpierre at Nov 24, 2013 at 2:04 pm

    On Sun, Nov 24, 2013 at 5:45 AM, Steven D'Aprano wrote:
    On Sun, 24 Nov 2013 05:04:16 -0800, Devin Jeanpierre wrote:
    Uh oh. What you want is impossible. You cannot call an attribute without
    first accessing it. :(
    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.

    I admit that thought crossed my mind, but I assumed he didn't want a
    GET+POST, and also "impossible" is often a nice shorthand for "the
    possibility is extraordinarily awful".


    -- Devin
  • Chris Angelico at Nov 24, 2013 at 2:13 pm

    On Mon, Nov 25, 2013 at 1:04 AM, Devin Jeanpierre wrote:
    and also "impossible" is often a nice shorthand for "the
    possibility is extraordinarily awful".

    +1 QOTW!


    ChrisA
  • Chris Angelico at Nov 24, 2013 at 2:13 pm

    On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano wrote:
    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.

    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableStr(self.do_get())
    result.function = lambda: self.do_put()

    Problem with that is that it'll still call do_get immediately. You'd
    have to somehow defer this call until it's actually _used_, which is
    why I dropped a mention of "converting to str?" - which would
    presumably be a __str__ method. But I still don't like the API.


    ChrisA
  • Marc Aymerich at Nov 24, 2013 at 2:34 pm

    On Sun, Nov 24, 2013 at 3:13 PM, Chris Angelico wrote:
    On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano
    wrote:
    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.

    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableStr(self.do_get())
    result.function = lambda: self.do_put()
    Problem with that is that it'll still call do_get immediately. You'd
    have to somehow defer this call until it's actually _used_, which is
    why I dropped a mention of "converting to str?" - which would
    presumably be a __str__ method. But I still don't like the API.

    That's right.
    In my case deferring the GET call will not be a problem since this
    objects will be used in just a few particular places and the workflow
    is always something like:


    # Initiate firmware building
    node.ctl.firmware()
    # wait until finished
    while node.ctl.firmware.progress < 100:
        time.sleep(1)




    Thanks for sharing your knowledge guys !!
    --
    Marc
  • Marc Aymerich at Nov 24, 2013 at 2:16 pm

    On Sun, Nov 24, 2013 at 2:45 PM, Steven D'Aprano wrote:
    On Sun, 24 Nov 2013 05:04:16 -0800, Devin Jeanpierre wrote:

    On Sun, Nov 24, 2013 at 4:52 AM, Marc Aymerich <glicerinu@gmail.com>
    wrote:
    Hi,
    I'm playing with python internals to make objects behave like this:

    if I access to "object.attribute" I want to return the result of an
    HTTP GET request. However if i call "object.attribute()" I want an HTTP
    POST request to be executed.
    Uh oh. What you want is impossible. You cannot call an attribute without
    first accessing it. :(
    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.


    class CallableString(str):
    # Like a string, but callable.
    def function(self):
    raise NotImplementedError(
    "this must be overridden on the instance"
    )
    def __call__(self):
    return self.function()


    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableStr(self.do_get())
    result.function = lambda: self.do_put()
    def do_get(self):
    # Do a HTTP GET request.
    return "Get stuff"
    def do_put(self):
    # Do a HTTP PUT request.
    return "Put stuff"



    OMG steven, it actually works :)

    class CallableString(str):
    ... # Like a string, but callable.
    ... def function(self):
    ... raise NotImplementedError(
    ... "this must be overridden on the instance"
    ... )
    ... def __call__(self):
    ... return self.function()
    ...
    class Magic_HTTP_Thing:
    ... @property
    ... def attribute(self):
    ... result = CallableString(self.do_get())
    ... result.function = lambda: self.do_put()
    ... return result
    ... def do_get(self):
    ... # Do a HTTP GET request.
    ... return "Get stuff"
    ... def do_put(self):
    ... # Do a HTTP PUT request.
    ... return "Put stuff"
    ...
    Magic_HTTP_Thing().attribute
    'Get stuff'
    Magic_HTTP_Thing().attribute()
    'Put stuff'

    Possible or not, it doesn't seem like a reasonable API to me.

    yeah, this is a "corner case" of our REST API, I have some badly
    design endpoints that mostly behave like functions, but some of them
    also contain state information that you can GET, I was trying to map
    this behavior to python objects and this interface is the best that
    occurred to me :)




    --
    Marc
  • Chris Angelico at Nov 24, 2013 at 2:37 pm

    On Mon, Nov 25, 2013 at 1:16 AM, Marc Aymerich wrote:
    ... def do_get(self):
    ... # Do a HTTP GET request.
    ... return "Get stuff"
    ... def do_put(self):
    ... # Do a HTTP PUT request.
    ... return "Put stuff"

    To make this a bit more realistic, try this instead - tying in with
    what I said in response to Roy:


    class CallableString(str):
         # Like a string, but callable.
         def function(self):
             raise NotImplementedError(
                     "this must be overridden on the instance"
                     )
         def __call__(self):
             return self.function()




    class Magic_HTTP_Thing:
         @property
         def attribute(self):
             result = CallableString(self.do_get())
             result.function = lambda: self.do_put()
             return result
         def do_get(self):
             # Do a HTTP GET request.
             print("Doing the GET call, please wait...")
             time.sleep(1)
             return "Get stuff"
         def do_put(self):
             # Do a HTTP PUT request.
             print("Doing the PUT call, please wait...")
             time.sleep(1)
             return "Put stuff"


    (PUT or POST, makes no difference; I think you were looking for POST,
    but Steven wrote PUT here so I'll use that for simplicity)

    Magic_HTTP_Thing().attribute()
    Doing the GET call, please wait...
    Doing the PUT call, please wait...
    'Put stuff'


    And that's what you don't want happening. Your PUT / POST calls are
    going to take twice as long as they should.


    ChrisA
  • Marc Aymerich at Nov 24, 2013 at 2:48 pm

    On Sun, Nov 24, 2013 at 3:37 PM, Chris Angelico wrote:
    On Mon, Nov 25, 2013 at 1:16 AM, Marc Aymerich wrote:
    ... def do_get(self):
    ... # Do a HTTP GET request.
    ... return "Get stuff"
    ... def do_put(self):
    ... # Do a HTTP PUT request.
    ... return "Put stuff"
    To make this a bit more realistic, try this instead - tying in with
    what I said in response to Roy:

    class CallableString(str):
    # Like a string, but callable.
    def function(self):
    raise NotImplementedError(
    "this must be overridden on the instance"
    )
    def __call__(self):
    return self.function()


    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableString(self.do_get())
    result.function = lambda: self.do_put()
    return result
    def do_get(self):
    # Do a HTTP GET request.
    print("Doing the GET call, please wait...")
    time.sleep(1)
    return "Get stuff"
    def do_put(self):
    # Do a HTTP PUT request.
    print("Doing the PUT call, please wait...")
    time.sleep(1)
    return "Put stuff"

    (PUT or POST, makes no difference; I think you were looking for POST,
    but Steven wrote PUT here so I'll use that for simplicity)
    Magic_HTTP_Thing().attribute()
    Doing the GET call, please wait...
    Doing the PUT call, please wait...
    'Put stuff'

    And that's what you don't want happening. Your PUT / POST calls are
    going to take twice as long as they should.

    Thanks Chris,
    didn't realize about the implicit GET when calling the attribute :(


    I think I'll put the get call on __repr__, __str__ and __getattr__,
    something like


    class HTTPAttribute(object):
         """ functional endpoint representation """
         def __repr__(self):
             self._retrieve()
             return json.dumps(self.__dict__)


         def __str__(self):
             self._retrieve()
             return json.dumps(self.__dict__)


         def __init__(self, resource, uri):
             self._resource = resource
             self.uri = uri


         def __call__(self, *args, **kwargs):
             return self._resource._api.post(self.uri, *args, **kwargs).content


         def __getattr__(self, name):
             self._retrieve()
             if hasattr(self, name):
                 return getattr(self, name)
             raise AttributeError("'%s' object has no attribute '%s'" %
    (str(type(self)), name))


         def _retrieve(self):
             resource = self._resource._api.retrieve(self.uri)
             for key, value in resource._data.iteritems():
                 setattr(self, key, value)




    and that's it,




    But still I'll reconsider an interface with less magic :P




    --
    Marc
  • Chris Angelico at Nov 24, 2013 at 2:57 pm

    On Mon, Nov 25, 2013 at 1:48 AM, Marc Aymerich wrote:
    But still I'll reconsider an interface with less magic :P

    Yeah, I would definitely recommend that :) Magic can be fun sometimes,
    but it's often not worth the hassle.


    ChrisA
  • Steven D'Aprano at Nov 24, 2013 at 3:11 pm

    On Mon, 25 Nov 2013 01:37:12 +1100, Chris Angelico wrote:


    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableString(self.do_get())
    result.function = lambda: self.do_put()
    return result
    def do_get(self):
    # Do a HTTP GET request.
    print("Doing the GET call, please wait...")
    time.sleep(1)
    return "Get stuff"
    def do_put(self):
    # Do a HTTP PUT request.
    print("Doing the PUT call, please wait...")
    time.sleep(1)
    return "Put stuff"

    (PUT or POST, makes no difference; I think you were looking for POST,
    but Steven wrote PUT here so I'll use that for simplicity)

    Oops, did I screw that bit up? Sorry.


    One possible solution here is to cache the GET so it only occurs once.


         def do_get(self):
             # Do a HTTP GET request.
             if hasattr(self, "_result"):
                 result = self._result
             else:
                 print("Doing the GET call, please wait...")
                 time.sleep(1)
                 result = self._result = "Get stuff"
             return result


    You can give the cache a timestamp as well, in case you need to
    invalidate the cache once it reaches a certain age. I'll leave that as an
    exercise.






    --
    Steven
  • Roy Smith at Nov 24, 2013 at 2:21 pm
    In article <mailman.3131.1385302390.18130.python-list@python.org>,
      Chris Angelico wrote:

    On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano
    wrote:
    Not quite impossible. All you need is an object that behaves like a
    string, except it has a __call__ method. Here's a sketch of a solution,
    completely untested.

    class Magic_HTTP_Thing:
    @property
    def attribute(self):
    result = CallableStr(self.do_get())
    result.function = lambda: self.do_put()
    Problem with that is that it'll still call do_get immediately. You'd
    have to somehow defer this call until it's actually _used_, which is
    why I dropped a mention of "converting to str?" - which would
    presumably be a __str__ method. But I still don't like the API.

    ChrisA

    If the REST interface is designed properly, all the GETs are
    nullipotent, so modulo efficiency, it should all work.


    But, yeah, this seems really ugly.
  • Chris Angelico at Nov 24, 2013 at 2:31 pm

    On Mon, Nov 25, 2013 at 1:21 AM, Roy Smith wrote:
    If the REST interface is designed properly, all the GETs are
    nullipotent, so modulo efficiency, it should all work.

    Yes, but "modulo efficiency" is not something you want to do when
    you're talking network traffic. If this were just allocating a bit of
    memory, then I might be inclined to let it duplicate the work (if the
    API were sufficiently beautiful to justify it, which IMHO this one
    isn't), but a full HTTP round trip? And this is presumably all
    synchronous, as otherwise all sorts of things would get messy (you'd
    have a string that doesn't have a value yet... could be awkward).


    ChrisA

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedNov 24, '13 at 12:52p
activeNov 24, '13 at 3:11p
posts15
users5
websitepython.org

People

Translate

site design / logo © 2022 Grokbase