Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #60364 > unrolled thread

Re: Implement logic on object.attribute and object.attribute()

Started byDevin Jeanpierre <jeanpierreda@gmail.com>
First post2013-11-24 05:04 -0800
Last post2013-11-25 01:57 +1100
Articles 13 — 5 participants

Back to article view | Back to comp.lang.python

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  Re: Implement logic on object.attribute and object.attribute() Devin Jeanpierre <jeanpierreda@gmail.com> - 2013-11-24 05:04 -0800
    Re: Implement logic on object.attribute and object.attribute() Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-11-24 13:45 +0000
      Re: Implement logic on object.attribute and object.attribute() Devin Jeanpierre <jeanpierreda@gmail.com> - 2013-11-24 06:04 -0800
      Re: Implement logic on object.attribute and object.attribute() Chris Angelico <rosuav@gmail.com> - 2013-11-25 01:13 +1100
        Re: Implement logic on object.attribute and object.attribute() Roy Smith <roy@panix.com> - 2013-11-24 09:21 -0500
          Re: Implement logic on object.attribute and object.attribute() Chris Angelico <rosuav@gmail.com> - 2013-11-25 01:31 +1100
      Re: Implement logic on object.attribute and object.attribute() Chris Angelico <rosuav@gmail.com> - 2013-11-25 01:13 +1100
      Re: Implement logic on object.attribute and object.attribute() Marc Aymerich <glicerinu@gmail.com> - 2013-11-24 15:16 +0100
      Re: Implement logic on object.attribute and object.attribute() Chris Angelico <rosuav@gmail.com> - 2013-11-25 01:37 +1100
        Re: Implement logic on object.attribute and object.attribute() Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-11-24 15:11 +0000
      Re: Implement logic on object.attribute and object.attribute() Marc Aymerich <glicerinu@gmail.com> - 2013-11-24 15:34 +0100
      Re: Implement logic on object.attribute and object.attribute() Marc Aymerich <glicerinu@gmail.com> - 2013-11-24 15:48 +0100
      Re: Implement logic on object.attribute and object.attribute() Chris Angelico <rosuav@gmail.com> - 2013-11-25 01:57 +1100

#60364 — Re: Implement logic on object.attribute and object.attribute()

FromDevin Jeanpierre <jeanpierreda@gmail.com>
Date2013-11-24 05:04 -0800
SubjectRe: Implement logic on object.attribute and object.attribute()
Message-ID<mailman.3128.1385298299.18130.python-list@python.org>
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. :(

-- Devin

[toc] | [next] | [standalone]


#60366

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-11-24 13:45 +0000
Message-ID<529202e3$0$29993$c3e8da3$5496439d@news.astraweb.com>
In reply to#60364
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"


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


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#60367

FromDevin Jeanpierre <jeanpierreda@gmail.com>
Date2013-11-24 06:04 -0800
Message-ID<mailman.3130.1385301889.18130.python-list@python.org>
In reply to#60366
On Sun, Nov 24, 2013 at 5:45 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> 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

[toc] | [prev] | [next] | [standalone]


#60368

FromChris Angelico <rosuav@gmail.com>
Date2013-11-25 01:13 +1100
Message-ID<mailman.3131.1385302390.18130.python-list@python.org>
In reply to#60366
On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> 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

[toc] | [prev] | [next] | [standalone]


#60373

FromRoy Smith <roy@panix.com>
Date2013-11-24 09:21 -0500
Message-ID<roy-4F23EB.09214724112013@news.panix.com>
In reply to#60368
In article <mailman.3131.1385302390.18130.python-list@python.org>,
 Chris Angelico <rosuav@gmail.com> wrote:

> On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> 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.

[toc] | [prev] | [next] | [standalone]


#60377

FromChris Angelico <rosuav@gmail.com>
Date2013-11-25 01:31 +1100
Message-ID<mailman.3135.1385303515.18130.python-list@python.org>
In reply to#60373
On Mon, Nov 25, 2013 at 1:21 AM, Roy Smith <roy@panix.com> 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

[toc] | [prev] | [next] | [standalone]


#60369

FromChris Angelico <rosuav@gmail.com>
Date2013-11-25 01:13 +1100
Message-ID<mailman.3132.1385302428.18130.python-list@python.org>
In reply to#60366
On Mon, Nov 25, 2013 at 1:04 AM, Devin Jeanpierre
<jeanpierreda@gmail.com> wrote:
> and also "impossible" is often a nice shorthand for "the
> possibility is extraordinarily awful".

+1 QOTW!

ChrisA

[toc] | [prev] | [next] | [standalone]


#60371

FromMarc Aymerich <glicerinu@gmail.com>
Date2013-11-24 15:16 +0100
Message-ID<mailman.3133.1385302626.18130.python-list@python.org>
In reply to#60366
On Sun, Nov 24, 2013 at 2:45 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> 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

[toc] | [prev] | [next] | [standalone]


#60378

FromChris Angelico <rosuav@gmail.com>
Date2013-11-25 01:37 +1100
Message-ID<mailman.3136.1385303840.18130.python-list@python.org>
In reply to#60366
On Mon, Nov 25, 2013 at 1:16 AM, Marc Aymerich <glicerinu@gmail.com> 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

[toc] | [prev] | [next] | [standalone]


#60384

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-11-24 15:11 +0000
Message-ID<5292172c$0$29993$c3e8da3$5496439d@news.astraweb.com>
In reply to#60378
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

[toc] | [prev] | [next] | [standalone]


#60380

FromMarc Aymerich <glicerinu@gmail.com>
Date2013-11-24 15:34 +0100
Message-ID<mailman.3138.1385304139.18130.python-list@python.org>
In reply to#60366
On Sun, Nov 24, 2013 at 3:13 PM, Chris Angelico <rosuav@gmail.com> wrote:
> On Mon, Nov 25, 2013 at 12:45 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> 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

[toc] | [prev] | [next] | [standalone]


#60381

FromMarc Aymerich <glicerinu@gmail.com>
Date2013-11-24 15:48 +0100
Message-ID<mailman.3139.1385304915.18130.python-list@python.org>
In reply to#60366
On Sun, Nov 24, 2013 at 3:37 PM, Chris Angelico <rosuav@gmail.com> wrote:
> On Mon, Nov 25, 2013 at 1:16 AM, Marc Aymerich <glicerinu@gmail.com> 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

[toc] | [prev] | [next] | [standalone]


#60382

FromChris Angelico <rosuav@gmail.com>
Date2013-11-25 01:57 +1100
Message-ID<mailman.3140.1385305076.18130.python-list@python.org>
In reply to#60366
On Mon, Nov 25, 2013 at 1:48 AM, Marc Aymerich <glicerinu@gmail.com> 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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web