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