On Fri, 02 May 2008 12:44:51 +0200, Jeroen Vloothuis wrote:

> At the Grokkerdam sprint Godefroid, Martijn Faassen and I had a
> discussion about the current KSS API. The example below shows how we
> currently use it (in `kss.core`):
> 
> class SomeKSSView(KSSView):
>     def update_something(self):
>         core = self.getCommandSet('core')
>         core.replaceInnerHTML('#some-node', 'some content') return
>         self.render()
> 
> Behind the scenes this uses a named adapter lookup to get a command set.
> Martijn does not like this since it is not clear where the code is
> implemented (because of the lookup based on a string). To make this
> better Martijn and I came to this proposal:
> 
> from kss.base import core
> 
> class SomeKSSView(KSSView):
>     def update_something(self):
>         core.replaceInnerHTML(self.commands, '#some-node', 'some
>         content') return self.render()
> 
> This makes it explicit where the code is implemented (see the import).
> Advantages of this proposal are that it would both work with a pure
> Python implementation (for kss.base) and makes the system a bit simpler
> (conceptually). 

First, I would like to understand why the change is proposed to the 
original proposal we followed with Jeroen in the working code
of kss.base and kss.zope. Since I am not present on the sprint, for me
the most important would be to understand the motivation.

To bring the discussion into context with the current solution (not the 
one in kss.core but the one we worked out in kss.zope / kss.base), I would
summarize it briefly and tell a bit of history too. Most of this was
discussed and implemented by me and Jeroen in last November in kss.zope 
code.

To state the general problem
-----------------------------

One general problem we realized when creating the kss.base / kss.zope
implementation is that the commandsets on Zope need to access to the
"commands" which are stored on the view. They also need to access
request and context. So in Zope the commandset adapts on the view or on 
the commandset it is called from. However in plain python all this is not
needed, there is hard to speak about the view or request even,
except the commandset still needs to access commands, but we
cannot use the "view" pattern we had till know, without the change.

The way Jeroen implemented the commandsets in kss.base first,
mainly considers the actual commandsets that are in the core, and
simplify these cases. However we have more complex usage needs in zope
and plone that the previous version supported. With the way the
kss.base handles the commandsets, 
- we cannot support our current needs with Zope and Plone, 
- but we also don't want different type of commandsets in python, Zope, 
and server X, 
so we were trying to find a way to fulfill both simplicity in the
plain python implementation, and support sophiscticated needs of
server frameworks, while having a unified commandset model. In addition 
we need to stay open for other, including non-pythonic implementations: 
we have these needs in Zope but we have no reason to assume that another 
server side framework will now raise its own specific needs which will 
even be different from that of Zope.

My proposal (also discussed and agreed with Jeroen previously) to attack 
this problem is "Implementation dependent adaptation of commandset". Let' 
make a rule: commandsets are identical in every server side 
implementation except that we allow the commandset (which
is always an adapter then) to adapt to different objects in different
implementations. For example in Zope the commandset as currently adapts
on  [context, request, view]. In python, the core commandset could
just adapt on [commands]. Besides this no differences are allowed.

To implement this every implementation must provide the actual way of
instantiation (adaptation) of the commandset, using whatever objects
it needs to adapt it on for the constructor. This is possible to
implement (needs to be implemented once per server framework), and if 
every view and commandset has the commodity view.getCommandSet() then in 
the end the usage is totally independent from the commandset's 
implementation and what it's adapting to.

This was on plain python the commandset would look like:

>>> class CommandSet(object):
...    def __init__(self, commands)
...        self.commands = commands)
  
>>> class someCommandSet(object):    
...     def replaceInnerHTML(self, selector, value):
...         """Replace the contents of a node (selector) with the new 
`value`"""
...            self.commands.add('replaceInnerHTML', selector, 
html=htmldata(value))

and it can be called like

>>>  commandset.getCommandSet('core').replaceInnerHTML(selector, html)

or directly:

...  getCommandSet('core')(commands).replaceInnerHTML(selector, html)

Zope commandsets would be different from python, and same as now (only
this example only adapts on the view since it's actually enough, no
need to differentiate on context or request):

>>>  class ZopeCommandSet(object):
...     def __init__(self, view)
...        self.view = view
...        self.request = view.request
...        self.response = view.response
...        self.commands = view.commands
...
...    def refreshPortlet(self, ...)
...        something = accessed_from(self.request, self.response)
...        self.issue_the_commands()    # can call directly from the
...                                     # same commandset

And they would be called like:

>>>  view.getCommandSet('core').replaceInnerHTML(selector, html)
>>>  commandset.getCommandSet('core').replaceInnerHTML(selector, html)

The implementation of the getCommandSets would be easy because for
example the python commandset need not know to instantiate a zope
commandset, only the other way around (ie. you cannot use zope
commandsets if you are not in zope, or call a zope commandset from a
plain python commandset). 

Iw would be important for me to understand what problems you identify
with this proposal? If it is only the inconvenience of getCommandSet() 
with the string in it, then we need to address this and I will return to 
this a few lines below.

Following I would like to emphasize two important issues:


1. KSS plugins have no global import, is Good
---------------------------------------------

In other words, since no code imports from the plugins directly, the 
plugins are agnostic of where they are included from. (In kss.core we use 
zcml for this, in kss.base we use setuptools and entry_points to locate 
the plugins, but the principle of registration is similar.)
This makes it easy to change the location of plugins, redistribute
them with one component or another to build a website. As often a plugin 
comes with a specific widget implementation, this is a big advantage. The 
already have a registry andso its location is known. The proposal in 
discussion basically suggests: let's strip this registry code for 
commandsets and access the commandsets as ordinary python code.

It is of course arguable: should not commandsets be accessed more 
pythonically as proposed in the thread? So let's argue about this, 
because in general I don't mind to be more pythonic, but in this case I 
am against loosing the import independance, and allow to decouple the 
commandsets from the kss registry, thus loosing all advantage of 
registration (like portability and platform independence).

I would like to add that (unlike Martijn said) the commandset's name
is not "just a string". It is a fully dashed global identifier of the
commandset (Example plone-issuePortalMessage, but we will support
multilevel namespaces my-fancy-app-command any moment): 
this is as much of an identifier for kss as the imports in python.
And thus identifies the module from point of view of kss (the concept), 
and not from point of view of python (the implementation). Imo this 
is good, because commandsets belong much more to the plugin itself,
then to the python server side and they can be used uniformly
everywhere where kss is. The slight separation from python land also
helps to understand to the reader of the code that executes is a
registered part of a kss plugin - much more important then to see
which python module it's importing from.

Also, the server side is python in our case but it could be anything
else and we would like to provide something that is manageable in
every implementation environment (while being very pythonic here
would mean giving no guideline for a generic implementation.)

Which means a view.getCommandSet(name) is not a large price for this.

We may think, however, other ways to make the syntax usage sweeter, if 
the view.getCommandSet() is the biggest annoyance in the
story. The view.getCommandSet can be removed and instead we can provide a 
kss import namespace like

>>> from kss.commandsets import Core
>>> from kss.commandsets import Zope

>>> Core(commands).replaceHTML

where the actual modules could be anywhere physically, and loaded to
this location from their original place initially. But even then: these 
are classes so you would need to instantiate the commandsets, which is 
why the view.getCommandSet comes handy. But instead of the getCommandSet 
method, it would also be possible to also provide the namespace on the 
view:

>>> view.commandsets.core.replaceHTML
>>> commandset.commandsets.core.replaceHTML

where view.commandsets.core would actually give an instantiated
commandset already. Note that disturbingly "Core" above was the 
commandset class, and "core" is already the instance in the last two 
lines, this is why I changed the first letter to uppercase.

Of course this would not work with "my-fancy-app-command", but then
nothing keeps us away to do view.commandsets.my.fancy.app.command instead.

2. Zope commandsets do need to access the view
----------------------------------------------

A commandset does not only execute a simple data dump with no other
sources then the parameters it receives, as it is in the case
of the core commandset. In complex kss applications in Zope we use
commandsets that access the request and response, call up and execute
other command sets. This allows the decomposition of bigger task into
elementary kss operation, iow, no need to build everything in the
client.

For example, the plone-issuePortalMessage command needs to access not
only to commands, but also to the content and the request. This could
not be handled sanely in the plain-python way, unless an arbitrary
number of parameters were passed to the actual command implementations
which would be insane. However with the "commandset as adapter"
pattern, this is easy since the implementation-dependent parameters
are all hidden. (In addition every other system may also want to
implement its own
issuePortalMessage.)

Another example is saveField that is used for inline editing and needs
to access the context object as well as the request.


To summarize
------------

If the proposal in the thread becomes the supported way of API for 
commandsets, first we loose import location independence, then we also 
won't be able to support existing commandsets code this way. As a result 
all the code that cannot be supported and inevitably will be implemented 
out of scope from kss (= differently from the supported way), which means 
loosing the advantage of platform independence and portability.

At the same time I feel that the "commandset adaptation" pattern would 
allow to keep best of all worlds, and it would not limit the
implementations. It may not be the most perfect option either so it's
essential to understand for me to get your reactions on the issue.





-- 
Balazs Ree

_______________________________________________
Kss-devel mailing list
Kss-devel@codespeak.net
http://codespeak.net/mailman/listinfo/kss-devel

Reply via email to