How kill a Task ? Issue on Deserialisation time ?

37 views
Skip to first unread message

Philippe GONCALVES

unread,
Apr 3, 2012, 11:30:38 AM4/3/12
to nagare...@googlegroups.com
Hello,

Sorry for my bad english (i'm french).

I'm working in a project using Nagare. 

All components use render( AsyncRenderer( h ) ).

I implement some mechanisms to :
 - permit a component to update others components (using ajax.Updates) and kind of observer pattern.
 - optimize cross Updates (If father and child are concerned by an update, only the Update of father is called, childs are created). It's a kind of persistent DOM (I don't find who to do that with standard Nagare Object...).

The project goal is to select an Application Code by keyboard input (left side, grey block) and clic on "Charger".



Pink and Blue blocks are loaded via ajax.Updates.

A hierarchy of components exists :

aeras_viewer component aggregates aera_viewer component (dynamic multiplicity, "Réseau internet", "Réseau Ville"...)
aera_viewer component contains 1 envs_viewer component
envs_viewer aggregates env_viewer component (dynamic multiplicity, "Qualification", "Production"...)
env_viewer contains 1 appcomps_viewer component
appcomps_viewer aggregates appcomp_viewer component (dynamic multiplicity, "Apache", "Tomcat"...)
acppomp contains 1 : Task and 1 servers_viewer

The task's goal is :
1/ Ask for create a server
2/ Ask for confirmation
3/ If confirmed, add a server creation demand an return to step 1, if canceled, return to step 1

If the Appcode Selector is used, all components must be cleaned. And tasks killed.

In Nagare 0.3, a memory leak was present (Garbage Collector could not destroy object)
I found a work around using :

_channel.send_exception( TaskletExit ) .

In Nagare 0.4, the memory leak was corrected, in 1 situation I think, in /nagare/sessions/common.py with the add of :

        # Kill all the blocked tasklets, which are now serialized
        for t in tasklets:
            t.kill()

But, an other problem persists.

The time to deserialize object increases at each call IF the task has been used.

The standard time to load ("Charger") is 700ms if no appcode is selected (No Task used because no areas area printed).
For the moment where The Task appears, the load time increase by 200ms at each load ("Charger").

I'm working on a limited version of the application for demonstration purpose but it's not finish for the moment (I need to implement DOM for Updates).

The full code is accessible at :


I think commit 500f2955d799f58393da982bdbab89dd35bd7800 run.

In last commits, cross updates between appcode and areas are broken (in first version, childs are created at each father render, and I was working and cache on childs componentà.

I think it's better I'm made an application for demonstrate the problem. Because I don't write documentation to run application ;-)

But the message's purpose it's to know if a way to kill a task exists ? And if deserialisation time increase is known ?

Best regards

Philippe GONCALVES
Message has been deleted

Philippe GONCALVES

unread,
Apr 3, 2012, 11:53:51 AM4/3/12
to nagare...@googlegroups.com
An image to explain the interface
image.jpg

apoirier

unread,
Apr 4, 2012, 7:24:21 PM4/4/12
to Nagare users
Hi,

On Apr 3, 5:30 pm, Philippe GONCALVES <app-phili...@go.ncalv.es>
wrote:
> Hello,
>
> Sorry for my bad english (i'm french).

np, I understand ;)

> I'm working in a project using Nagare.
>
> All components use render( AsyncRenderer( h ) ).
>
> I implement some mechanisms to :
>  - permit a component to update others components (using ajax.Updates) and
> kind of observer pattern.
>  - optimize cross Updates (If father and child are concerned by an update,
> only the Update of father is called, childs are created). It's a kind of
> persistent DOM (I don't find who to do that with standard Nagare Object...).
>
> The project goal is to select an Application Code by keyboard input (left
> side, grey block) and clic on "Charger".
>
> Pink and Blue blocks are loaded via ajax.Updates.
>
> A hierarchy of components exists :
>
> aeras_viewer component aggregates aera_viewer component (dynamic
> multiplicity, "Réseau internet", "Réseau Ville"...)
> aera_viewer component contains 1 envs_viewer component
> envs_viewer aggregates env_viewer component (dynamic multiplicity,
> "Qualification", "Production"...)
> env_viewer contains 1 appcomps_viewer component
> appcomps_viewer aggregates appcomp_viewer component (dynamic multiplicity,
> "Apache", "Tomcat"...)
> acppomp contains 1 : Task and 1 servers_viewer
>
> The task's goal is :
> 1/ Ask for create a server
> 2/ Ask for confirmation
> 3/ If confirmed, add a server creation demand an return to step 1, if
> canceled, return to step 1
>
> If the Appcode Selector is used, all components must be cleaned. And tasks
> killed.
>
> In Nagare 0.3, a memory leak was present (Garbage Collector could not
> destroy object)
> I found a work around using :
>
> _channel.send_exception( TaskletExit ) .
>
> In Nagare 0.4, the memory leak was corrected, in 1 situation I think,
> in /nagare/sessions/common.py with the add of :
>
> *        # Kill all the blocked tasklets, which are now serialized*
> *        for t in tasklets:*
> *            t.kill()*
>
> But, an other problem persists.
>
> The time to deserialize object increases at each call IF the task has been
> used.
>
> The standard time to load ("Charger") is 700ms if no appcode is selected
> (No Task used because no areas area printed).
> For the moment where The Task appears, the load time increase by 200ms at
> each load ("Charger").
>
> I'm working on a limited version of the application for demonstration
> purpose but it's not finish for the moment (I need to implement DOM for
> Updates).
>
> The full code is accessible at :
>
> https://github.com/feeloo007/cloudmgrwebhttps://github.com/feeloo007/cloudmgrlib
>
> I think commit 500f2955d799f58393da982bdbab89dd35bd7800 run.
>
> In last commits, cross updates between appcode and areas are broken (in
> first version, childs are created at each father render, and I was working
> and cache on childs componentà.
>
> I think it's better I'm made an application for demonstrate the problem.
> Because I don't write documentation to run application ;-)

That's fine for the moment. I managed to install and launch your
application.

> But the message's purpose it's to know if a way to kill a task exists ?

As you described, the two ways are to raise a `TaskletExit` exception
or to call the `kill()` method of the tasklet

> And if deserialisation time increase is known ?

I don't know of such behavior.

Now your application is running, we can investigate the problem you
have. Stay tune.

> Best regards
>
> Philippe GONCALVES

Amicalement,
Alain

Philippe GONCALVES

unread,
Apr 6, 2012, 8:33:50 AM4/6/12
to nagare...@googlegroups.com
Thank You Alain ^^

If you needed, I think I can finalize an example next week (without IDomTree which implements childs - father relation and events dispatch). 

While writing this code sample, I was wondering if I have not missed the father - child persistence of AsyncRenderer.

Because I used 
<< cp.render(xhtml.AsyncRenderer(h) )
in KnownDiv, a component does not know his father because h "is empty" when calling from cp.render(xhtml.AsyncRenderer(h) ).

This allows me to update only the component (and its ingredients) through a call :
.action(fake_action) 

However, by writing the sample code I realized that if h is of type AsyncRenderer and the components are made only through 
h << cp 
This is the first component of h AsyncRenderer is updated through a call
.action(fake_action)

If instead of using .action(fake_action), I use .action(Update (..., fake_action, ...)), specifying the component to update, I can access complete tree via h ?


Have a nice weekend !

apoirier

unread,
Apr 6, 2012, 12:49:52 PM4/6/12
to Nagare users
Hi,

On Apr 3, 5:30 pm, Philippe GONCALVES <app-phili...@go.ncalv.es>
wrote:
> Hello,
>
> Sorry for my bad english (i'm french).
>
> I'm working in a project using Nagare.
>
> All components use render( AsyncRenderer( h ) ).
>
> I implement some mechanisms to :
> - permit a component to update others components (using ajax.Updates) and
> kind of observer pattern.
> - optimize cross Updates (If father and child are concerned by an update,
> only the Update of father is called, childs are created). It's a kind of
> persistent DOM (I don't find who to do that with standard Nagare Object...).

After reading the code, I'm not quite sure to understand why your need
such a "persistent DOM". Normally the only persistent objects in a
Nagare application are the "core" / "business" / "Models" (in the MVC
terminology) components. On each request, a temporary DOM is generated
by rendering these core components, then sent to the client and
finally discarded.

> The project goal is to select an Application Code by keyboard input (left
> side, grey block) and clic on "Charger".
>
> Pink and Blue blocks are loaded via ajax.Updates.
>
> A hierarchy of components exists :
>
> aeras_viewer component aggregates aera_viewer component (dynamic
> multiplicity, "Réseau internet", "Réseau Ville"...)
> aera_viewer component contains 1 envs_viewer component
> envs_viewer aggregates env_viewer component (dynamic multiplicity,
> "Qualification", "Production"...)
> env_viewer contains 1 appcomps_viewer component
> appcomps_viewer aggregates appcomp_viewer component (dynamic multiplicity,
> "Apache", "Tomcat"...)
> acppomp contains 1 : Task and 1 servers_viewer
>
> The task's goal is :
> 1/ Ask for create a server
> 2/ Ask for confirmation
> 3/ If confirmed, add a server creation demand an return to step 1, if
> canceled, return to step 1
>
> If the Appcode Selector is used, all components must be cleaned. And tasks
> killed.
>
> In Nagare 0.3, a memory leak was present (Garbage Collector could not
> destroy object)
> I found a work around using :
>
> _channel.send_exception( TaskletExit ) .
>
> In Nagare 0.4, the memory leak was corrected, in 1 situation I think,
> in /nagare/sessions/common.py with the add of :
>
> * # Kill all the blocked tasklets, which are now serialized*
> * for t in tasklets:*
> * t.kill()*
>
> But, an other problem persists.
>
> The time to deserialize object increases at each call IF the task has been
> used.
>
> The standard time to load ("Charger") is 700ms if no appcode is selected
> (No Task used because no areas area printed).
> For the moment where The Task appears, the load time increase by 200ms at
> each load ("Charger").

To see the number a pickled objets an each request, I created this
`pickle_debug.py` file:

import cPickle

class Pickler(object):
def __init__(self, file, protocol):
self.pickler = cPickle.Pickler(file, protocol)
self.nb = 0

def my_persistent_id(self, o):
self.nb += 1
return self._persistent_id(o)

def _set_persistent_id(self, f):
self._persistent_id = f
self.pickler.persistent_id = self.my_persistent_id
persistent_id = property(None, _set_persistent_id)

def dump(self, obj):
self.pickler.dump(obj)
print "Pickled objects", self.nb

and added this section in `cloudmgrweb.cfg`:

[sessions]
pickler = file <path_to_the_file>/pickler_debug.py:Pickler

So we can see an each load ("Charger"), about 30000 more Python
objects are created and accumulated, which is a huge number. That's
why the serialization takes more and more time on each clic.

> I'm working on a limited version of the application for demonstration
> purpose but it's not finish for the moment (I need to implement DOM for
> Updates).
>
> The full code is accessible at :
>
> https://github.com/feeloo007/cloudmgrwebhttps://github.com/feeloo007/cloudmgrlib
>
> I think commit 500f2955d799f58393da982bdbab89dd35bd7800 run.
>
> In last commits, cross updates between appcode and areas are broken (in
> first version, childs are created at each father render, and I was working
> and cache on childs componentà.
>
> I think it's better I'm made an application for demonstrate the problem.
> Because I don't write documentation to run application ;-)
>
> But the message's purpose it's to know if a way to kill a task exists ? And
> if deserialisation time increase is known ?

Well, I don't know yet where/when these 30000 new objects are created
but I'm sure this is the cause of the serialization time increase. Not
because your are using some `component.Task` tasklets.

> Best regards
>
> Philippe GONCALVES

Best regards
Alain

apoirier

unread,
Apr 6, 2012, 12:51:40 PM4/6/12
to Nagare users
Hi Philippe,

On Apr 6, 2:33 pm, Philippe GONCALVES <app-phili...@go.ncalv.es>
wrote:
> Thank You Alain ^^
>
> If you needed, I think I can finalize an example next week (without
> IDomTree which implements childs - father relation and events dispatch).

Yes, please. A minimalist example will be easier to debug. Also
without `IDomTree`, we will see if the number of objects created on
each request drops or not.

You could even made a version without any asynchronous renderings.
It's often how we start our own developments. Only adding later some
asynchronous parts, where needed.

> While writing this code sample, I was wondering if I have not missed the father
> - child persistence of AsyncRenderer.
>
> Because I used
> h << cp.render(xhtml.AsyncRenderer(h) )
> in KnownDiv, a component does not know his father because h "is empty" when
> calling from cp.render(xhtml.AsyncRenderer(h) ).
>
> This allows me to update only the component (and its ingredients) through a
> call :
> .action(fake_action)
>
> However, by writing the sample code I realized that if h is of type
> AsyncRenderer and the components are made only through
> h << cp
> This is the first component of h AsyncRenderer is updated through a call
> .action(fake_action)

Right, the latest component rendered with
`.render(xhtml.AsyncRenderer(h))` becomes the "asynchronous rendering
root".

For example , `c1`, `c2` and `c3` being 3 components:

1. The presentation function (the view) of `c1` component does a `h <<
c2` and a `h << c3`
2. `c1` is then rendered with `c1.render(xhtml.Renderer())`. So `c1`
becomes the asynchronous root
3. On each clic on a link generated by `c1`, `c2` or `c3`, the link
callback is first invoked then `c1` is rendered
4. As in 1., the `c1` view renders the `c2` and `c3` views

Or:

1. The view of `c1` do a `h << c2.render(xhtmlAsyncRenderer()` and `h
<< c3.render(xhtml.AsyncRenderer(h)). In this case, `c2` and `c3` are
asynchronous roots
2. `c1` is rendered with `c1.render(xhtml.Renderer())`. So `c1` is an
asynchronous root too
3. So, a clic on a link generated by `c2` will only rendered `c2`. A
clic on a link generated by `c3` will only rendered `c3`. A clic on a
link generated by `c1` will rendered `c1` and the view of `c1`, like
in step 1., will also rendered `c2` and `c3`

That's why you normally don't have to keep the DOM parent/child
relationships on the server side. Nagare already keeps them for you.

> If instead of using .action(fake_action), I use .action(Update (...,
> fake_action, ...)), specifying the component to update, I can access
> complete tree via h ?

No, the `h` passed to each view is always a new renderer.

The `ajax.Update()` actions are used when you want to bypass the
default asynchronous behaviour described above. When you want to have
more control: with a `ajax.Update()` action, you explicitly give the
callback to invoke, then the function to call to generate the DOM and
finally the element id where to put the DOM on the client.

> Have a nice weekend !

You too :)

> Le mardi 3 avril 2012 17:30:38 UTC+2, Philippe GONCALVES a écrit :
>
>
>
>
>
>
>
>
>
> > Hello,
>
> > Sorry for my bad english (i'm french).
>
> > I'm working in a project using Nagare.
>
> > All components use render( AsyncRenderer( h ) ).
>
> > I implement some mechanisms to :
> > - permit a component to update others components (using ajax.Updates) and
> > kind of observer pattern.
> > - optimize cross Updates (If father and child are concerned by an update,
> > only the Update of father is called, childs are created). It's a kind of
> > persistent DOM (I don't find who to do that with standard Nagare Object...).
>
> > The project goal is to select an Application Code by keyboard input (left
> > side, grey block) and clic on "Charger".
>
> > Pink and Blue blocks are loaded via ajax.Updates.
>
> > A hierarchy of components exists :
>
> > aeras_viewer component aggregates aera_viewer component (dynamic
> > multiplicity, "Réseau internet", "Réseau Ville"...)
> > aera_viewer component contains 1 envs_viewer component
> > envs_viewer aggregates env_viewer component (dynamic multiplicity,
> > "Qualification", "Production"...)
> > env_viewer contains 1 appcomps_viewer component
> > appcomps_viewer aggregates appcomp_viewer component (dynamic multiplicity,
> > "Apache", "Tomcat"...)
> > acppomp contains 1 : Task and 1 servers_viewer
>
> > The task's goal is :
> > 1/ Ask for create a server
> > 2/ Ask for confirmation
> > 3/ If confirmed, add a server creation demand an return to step 1, if
> > canceled, return to step 1
>
> > If the Appcode Selector is used, all components must be cleaned. And tasks
> > killed.
>
> > In Nagare 0.3, a memory leak was present (Garbage Collector could not
> > destroy object)
> > I found a work around using :
>
> > _channel.send_exception( TaskletExit ) .
>
> > In Nagare 0.4, the memory leak was corrected, in 1 situation I think,
> > in /nagare/sessions/common.py with the add of :
>
> > * # Kill all the blocked tasklets, which are now serialized*
> > * for t in tasklets:*
> > * t.kill()*
>

Philippe GONCALVES

unread,
Apr 10, 2012, 10:23:32 AM4/10/12
to nagare...@googlegroups.com
Hi Alain,

Thank you for your help ^_^ And for the Pickler implementation too !

I understand better differences between h << c2 and h << c2.render( xhtml.renderer( h ) ) with your explanations.

When I talk about persistent dom tree, I was thinking a case where :
c1 is a component
c2 is a component, c1's brother
t1 is a task, c2's child
c3 is a component, t1's "content"

c1's modifications result in c2's modifications via Updates

A t1 is a c2's child, t1 must be killed because c2 must be modified and an new t1 must be created.

That's why IDomTree implements some observers capabilities (add_event_for_knowndiv (add a component for an "event", APPCODE_MODIFIED, SERVER_CREATED ), get_l_known_div_for_change (return component concerned by an "event") and reset_in_dom who's clean childs, killing tasks).

My goal is découplage (sorry, I don't know the english word ^^). Components don't known brothers or fathers but can interact with using a restricted known events' list.


I'm always working in code example. But it's a little difficult to do it at my job. We are migrating 20 mairies d'arrondissement de Paris and it's time consuming... Sorry for the delay.

I hope I return soon !

Have a nice day !

Philippe

Philippe GONCALVES

unread,
Apr 12, 2012, 2:34:49 PM4/12/12
to nagare...@googlegroups.com
Hello,

I made an example code without IDomTree, an easier observer pattern in appcode change and for the moment, no task kill.

An other difference is that in my initial code, component where created at each render call. Component cache is under work. I think IDomTree has lot of circular reference.

I will work with Custom Pickler to determine which object are serialized / deserialized.

Thank you for your help Alain.

I return back after kill task will be added ;-)

Have a nice day !

Philippe

The exemple code :
To hide debug refresh button and debug border, put @debug_tools( activate_border = False, activate_refresh_button = False )
To add appcode, find xrange and modify second argument. In this example, only 2 app codes
To modify random entity number, find random and modify second argument. In this example, random max is 20.

On click on "Poke", a validation is asked.
If you modify appcode, and return back, validation waiting. It's here I want to add kill task.
And for that, I need father - child relations to determine which task to kill on father change.


# -*- encoding: utf8 -*-
import functools
import random
import collections
import sys
from   nagare            import presentation, component, var, ajax
from   nagare.namespaces import xhtml

import cPickle 

class Pickler(object): 
    def __init__(self, file, protocol): 
        self.pickler = cPickle.Pickler(file, protocol) 
        self.nb = 0 

    def my_persistent_id(self, o): 
        self.nb += 1 
        return self._persistent_id(o) 

    def _set_persistent_id(self, f): 
        self._persistent_id = f 
        self.pickler.persistent_id = self.my_persistent_id 
    persistent_id = property(None, _set_persistent_id) 

    def dump(self, obj): 
        self.pickler.dump(obj) 
        print "Pickled objects", self.nb 


def force_wrapper_to_generate( f ):
    @functools.wraps( f )
    def wrapped( self, h, comp, *args, **kwargs ):
        h.wrapper_to_generate = True
        result = f( self, h, comp, *args, **kwargs )
        comp._id = h.id
        def get_id( self ):
            return self._id
        comp.__class__.id_ = property( get_id, None, None, None )
        return result
    return wrapped


def create_debug_color_generator():
    debug_colors = [ '#FF0000', '#FFFF00', '#FF00FF', '#00FF00', '#00FFFF' , '#0000FF' ]
    while True:
        for color in debug_colors:
            yield color
debug_color_generator = create_debug_color_generator()


def fake_debug_action():
    pass


def _debug_tools_add_border_to_render( activate, color ):
    def wrapper( f ):
        @functools.wraps( f )
        def wrapped( self, h, *args, **kwargs ):
            if activate:
                return h.div( 
                           f( self, h, *args, **kwargs ), 
                           style = 'border: dashed 2px %s; padding: 1px; margin: 3px ;' % color 
                       )
            return f( self, h, *args, **kwargs )
        return wrapped
    return wrapper


def _debug_tools_add_refresh_button_to_render( activate, color ):
    def wrapper( f ):
        @functools.wraps( f )
        def wrapped( self, h, *args, **kwargs ):
            id_ = h.id
            if activate:
                with h.form():
                    h << h.input(
                             type = 'submit',
                             value = 'ajax refresh component %s %s' % ( self.__class__.__name__, id_ ),
                             style = 'background-color: %s' % color
                         ).action( lambda *args: None )
            return f( self, h, *args, **kwargs )
        return wrapped
    return wrapper


def debug_tools( activate_border, activate_refresh_button ):
    def wrapper( f ):
        @functools.wraps( f )
        def wrapped( self, h, *args, **kwargs ):
            color = debug_color_generator.next()
            return _debug_tools_add_border_to_render( activate_border, color )( 
                       _debug_tools_add_refresh_button_to_render( activate_refresh_button, color )( f )
                   )( self, h, *args, **kwargs )
        return wrapped
    return wrapper


class UpdatesWithLambdaUpdate(
         ajax.Update,
      ):
    def __init__(
          self,
          *updates_or_le,
          **kw
        ):
        self._updates_or_le = updates_or_le
        self._with_request = kw.get('with_request', False)
        self._action = kw.get('action', lambda *args: None)

        super(UpdatesWithLambdaUpdate, self).__init__(action=self.action, with_request=True)


    def action(self, request, response, *args):

        if self._with_request:
            self._action(request, response, *args)
        else:
            self._action(*args)

        updates = []

        for e in self._updates_or_le:
           if callable( e ):
               e = e()
               if isinstance(e, collections.Iterable):
                  for update in e:
                      updates.append( update )
               else:
                  updates.append( e )
           else:
               updates.append( e )

        for update in updates:
            if update.with_request:
                update.action(request, response, *args)
            else:
                update.action(*args)

    def _generate_render( self, renderer ):

        renders = [ super( UpdatesWithLambdaUpdate, self )._generate_render( renderer ) ]

        def ViewsToJs( r ):

            updates = []
            for e in self._updates_or_le:
                if callable( e ):
                    e = e()
                    if isinstance(e, collections.Iterable):
                        for update in e:
                            updates.append( update )
                    else:
                        updates.append( e )

            for update in updates:
                 renders.append( update._generate_render( r ) )

            return ajax.ViewsToJs( [ render( r ) for render in renders ] )

        return lambda r: ViewsToJs( r )


class Demo(object):
    def __init__( self ):
        self._cp_upper = component.Component( Upper() )

    def get_cp_upper( self ):
        return self._cp_upper
    cp_upper = property( get_cp_upper, None, None, None )

@presentation.render_for(Demo)
@force_wrapper_to_generate
def render(self, h, comp, *args, **kwargs):

    h << self.cp_upper.render( xhtml.AsyncRenderer( h ) )
    return h.root

class Upper( object ):
    def __init__( self ):
        self._cp_appcode_selector = component.Component( AppcodeSelector( 'A0' ) )
        self._cp_entities = component.Component( 
                                              Entities( 
                                                  'A0', 
                                                  lambda: [  
                                                           appcode 
                                                           for appcode, select_option 
                                                           in self.cp_appcode_selector.o.appcodes 
                                                          ] 
                                              ) 
                                          ) 

    def get_cp_appcode_selector( self ):
        return self._cp_appcode_selector
    cp_appcode_selector = property( get_cp_appcode_selector, None, None, None ) 

    def get_cp_entities( self ):
        return self._cp_entities
    cp_entities = property( get_cp_entities, None, None, None )

@presentation.render_for( Upper )
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render( self, h, comp, *args, **kwargs ):

    self.cp_appcode_selector.o.clean(
        AppcodeSelector.EVT_APPCODE_UPDATED
    )

    def update_selected_appcode_for_entities( appcode ):
        self.cp_entities.o.set_selected_appcode( appcode )

    with h.form():
        h << h.input( type = 'submit', value = 'Full page refresh' )

    h << 'Distributions'
    with h.div( style = 'display: table ; border-spacing: 15px 0px; border: solid 2px ;' ):
        for appcode, select_option in self.cp_appcode_selector.o.appcodes:
            h << h.div( 
                       '%s : %s' % ( appcode, Entities.get_multiplicity( appcode ) ), 
                       style = 'display: table-cell ;' 
                 )

    ar_cp_appcode_selector = xhtml.AsyncRenderer( h )
    h << self.cp_appcode_selector.render( ar_cp_appcode_selector )

    ar_cp_entities = xhtml.AsyncRenderer( h )
    h << self.cp_entities.render( xhtml.AsyncRenderer( h ) )

    self.cp_appcode_selector.o.register(
        AppcodeSelector.EVT_APPCODE_UPDATED,
        self.cp_entities,
        update_selected_appcode_for_entities
    )
    return h.root


class AppcodeSelector(object):

    EVT_APPCODE_UPDATED = 'EVT_APPCODE_UPDATED'
    EVTS                = [ EVT_APPCODE_UPDATED ]

    def __init__( self, appcode, *args, **kwargs ):
        self._index_generator   = xrange( 0, 2 )
        self._selected_appcode  = appcode
        self._d_evt             = dict( ( evt_name, [] ) for evt_name in AppcodeSelector.EVTS )

    def get_selected_appcode( self ):
        return self._selected_appcode

    def set_selected_appcode( self, appcode ):
        self._selected_appcode = appcode
        # Call to registred lambda expression
        for cp, le in self.d_evt[ AppcodeSelector.EVT_APPCODE_UPDATED ]:
            le( appcode )

    selected_appcode    = property( get_selected_appcode, set_selected_appcode, None, None )

    def get_d_evt( self ):
        return self._d_evt
    d_evt = property( get_d_evt, None, None, None )

    def get_appcodes( self ):
        def format_to_appcode( index ):
            return 'A%s' % index

        return [
                (
                 format_to_appcode( index ),
                 {
                  'selected':   None
                 }
                 if self.selected_appcode == format_to_appcode( index )
                 else
                 {}
                )
                for     index
                in      self._index_generator
               ]

    appcodes            = property( get_appcodes, None, None, None )

    def register( self, evt_name, cp, le ):
        assert( evt_name in AppcodeSelector.EVTS ), u'''%s must be part of %s''' % ( evt_name, AppcodeSelector.EVTS )
        self.d_evt[ evt_name ].append( ( cp, le ) )

    def clean( self, evt_name ):
        assert( evt_name in AppcodeSelector.EVTS ), u'''%s must be part of %s''' % ( evt_name, AppcodeSelector.EVTS )
        del( self.d_evt[ evt_name ][ : ] )


@presentation.render_for(AppcodeSelector)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):

    v_selected_appcode = var.Var()

    def update_selected_appcode():
        self.selected_appcode = v_selected_appcode()

    h << u'''Current Appcode %s''' % self.selected_appcode
    h << h.br
    h << u'''Select another Appcode'''
    h << h.br
    with h.form():
        with h.select() as selected_appcode:
            for appcode in self.appcodes:
                h << h.option( appcode[0], **appcode[1] )
            selected_appcode.action( v_selected_appcode )

        #h << h.input(
        #    type        = 'submit',
        #    value       = u'''Load with action'''
        #).action( update_selected_appcode )

        def get_associated_updates():
            return [ 
                    ajax.Update( 
                        lambda r: cp.render( xhtml.AsyncRenderer( r ) ), 
                        lambda *args: None, 
                        cp.id_ 
                    ) 
                    for cp, le 
                    in self.d_evt[ AppcodeSelector.EVT_APPCODE_UPDATED ] 
                   ] 

        h << h.input(
            type        = 'submit',
            value       = u'''Load with Updates'''
        ).action( 
            UpdatesWithLambdaUpdate(
                ajax.Update( 
                    lambda r: comp.render( xhtml.AsyncRenderer( r ) ), 
                    lambda *args: None, 
                    None 
                ),
                get_associated_updates,
                action = update_selected_appcode
            )
        )

    return h.root

class Entities( object ):

    _d_multiplicity_by_appcode = {}

    def __init__( self, selected_appcode, le_authorized_appcodes ):

        self._selected_appcode = selected_appcode

        self._le_authorized_appcodes = le_authorized_appcodes

        self._d_task = dict(
                            [ 
                             ( 
                              appcode, 
                              [ 
                               component.Component( PokeTask( appcode, i ) ) 
                               for i in range( 1, Entities.get_multiplicity( appcode ) + 1 ) 
                              ] 
                             ) for appcode in self._le_authorized_appcodes()
                            ]
                       )


    def get_selected_appcode( self ):
        return self._selected_appcode

    def set_selected_appcode( self, appcode ):
        self._selected_appcode = appcode
    selected_appcode = property( get_selected_appcode, set_selected_appcode, None, None )

    @classmethod
    def get_multiplicity( cls, appcode ):
        return cls._d_multiplicity_by_appcode.setdefault( appcode, random.randint( 1, 20 ) )

    def get_cp_tasks( self ):
        if self._d_task.has_key( self.selected_appcode ):
            return self._d_task[ self.selected_appcode ]
        else:
            return self._d_task.setdefault( 
                       self.selected_appcode, 
                       [ 
                        component.Component( PokeTask( self.selected_appcode, i ) 
                        for i 
                        in range( 1, Entities.get_multiplicity( appcode ) + 1 ) ) 
                       ]
                   )
    cp_tasks = property( get_cp_tasks, None, None, None )


@presentation.render_for(Entities)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
    
    h << h.div( 'Current appcode : %s' % self.selected_appcode )

    h << h.div( 'Current entities count : %s' % Entities.get_multiplicity( self.selected_appcode ) )

    with h.div( style = 'display: table ;' ):

        for cp_task in self.cp_tasks:
            h << h.div( 
                       cp_task.render( xhtml.AsyncRenderer( h ) ), 
                       style = 'display: table-cell ;' 
                 )

    return h.root

class PokeTask(
         component.Task
      ):


     def __init__( self, appcode, index ):

         self._appcode = appcode
         if callable(index):
             self._index = index()
         else:
             self._index = index
         self._pokeit   = PokeIt( self.appcode, self.index )
         self._cp_pokeit = component.Component( self._pokeit )

     def get_appcode( self ):
         return self._appcode
     appcode = property( get_appcode, None, None, None )

     def get_index( self ):
         return self._index
     index = property( get_index, None, None, None )

     def get_cp_pokeit( self ):
         return self._cp_pokeit
     cp_pokeit = property( get_cp_pokeit, None, None, None )

     def go( self, comp ):

         while True:

             comp.call( self.cp_pokeit )

             if comp.call( self.cp_pokeit, model = 'validate' ):
                 print True
             else:
                 print False

class PokeIt( object ):
    def __init__( self, appcode, index ):
        self._appcode = appcode
        self._index = index

    def get_appcode( self ):
        return self._appcode
    appcode = property( get_appcode, None, None, None )

    def get_index( self ):
        return self._index
    index = property( get_index, None, None, None )

@presentation.render_for(PokeIt)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
    with h.form():
        h << h.input( 
                     type = 'submit', 
                     value = 'Poke %s %s' % ( self.appcode, self.index ) 
             ).action( lambda: comp.answer() )
    return h.root

@presentation.render_for(PokeIt, model = 'validate' )
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
    with h.form( style = 'display: table ;' ):
        h << h.input(
                     type = 'submit',
                     value = 'Validate',
                     style = 'display: table-cell'
             ).action( lambda: comp.answer( True ) )
        h << h.input(
                     type = 'submit',
                     value = 'Cancel',
                     style = 'display: table-cell'
             ).action( lambda: comp.answer( False ) )
    return h.root
    

# ---------------------------------------------------------------

app = Demo
app.py
Reply all
Reply to author
Forward
0 new messages