Charlie Brown:All I can make is cereal and maybe toast.Linus:That's right Charlie Brown, I have seen you make toast before! You can't butter it, but maybe we can help you!(Quote lifted from the Charlie Brown Thanksgiving. Speaking of Charlie Brown, flip through this tribute to Charles Schulz by a large section of top editorial cartoonists.)
Articles with unidentified authorsThis interests me because one of my favorite lunchtime reads is The Economist, which very very rarely identifies its writers. It doesn't bother me at all. At the same time, however, it is a rather solid and respectable magazine. In a sense, it's almost a big magazine blog. It has short articles, wide geographical and topical coverage, and is as timely as a printed weekly can be.When it comes to blogs and other web sites, I haven't yet figured out when I care about the authors name and when I don't. In general, I trust The Economist. I think that anonymous correspondent reporting works out well for it, and I think it probably works out for similar styled web sites/blogs as well.
729 votes (164/245/173/111/36). 22.5% don't care, 57.3% dislike it somewhat, and 20.2% hate it when a site doesn't identify its writers.
<table> <% for row in data { out.writeln('<tr>'); out.writeln('<td>' + row.name + '</td>'); out.writeln('</tr>'); } %> </table>Why bother having the HTML abilities of ?SP/PHP languages at all? I can understand why it happens. I just don't think it should. ";->". But, in the words of Dennis Miller, "Of course, that's just my opinion, I could be wrong".In any case, I present the Goldberg Variations:
import os radioWwwFolder = '/Applications/Radio Userland/www' def listOutlines(): out =[] path = os.path.join(radioWwwFolder, 'outlines') def visit(arg, dirname, fnames): for fname in fnames: if fname.endswith('.opml'): fn = os.path.splitext(fname)[0] # Split extension off if dirname != path: # Deal with subdirectories fn = os.path.join(dirname[len(path):], fn) arg.append(fn) os.path.walk(path, visit, out) return out ZPT = """ <ol> <li tal:repeat="item here/listOutlines"> <a tal:attributes="href string:${item}.html" tal:content="item">Outline</a> </li> </ol> """ DTML = """ <ol> <dtml-in listOutlines> <li><a href="&dtml-sequence-item;.html"><dtml-var item></a></li> </dtml-in> </ol> """ USERTALK = """ on listOutlines() { local(out = "<ol>\n"); local(path=user.radio.prefs.wwwFolder + "outlines"); on visit(f) { f = file.fileFromPath(f); if string.hasSuffix(".opml", f) { f = string.popSuffix(f, '.'); out = out + " <li><a href=\"" + f + ".html\">" + f + "</a></li>\n"}; return true}; file.visitFolder(path, 1, @visit); out = out + "</ol>"; return out } """
id: getPackage params: package_id="" SELECT package_id, name, price FROM PACKAGES <!--#sqlgroup where--> <!--#sqltest package_id type=int multiple optional--> <!--#/sqlgroup-->When a 'package_id' parameter is passed, the entire 'sqlgroup where' block will render out into a full SQL 'Where' clause - if more than one value is passed in for 'package_id', the sqltest statement will even generate 'package_id IN ...' code. If no value(s) are passed in, the whole 'where' clause will be dropped (in this case - there's a lot of power in the dtml-sqlXXX tags).The cool tricks come with direct traversal, where you can treat a SQL Result object like a local one. What this means is that a URL like:../site/getPackage/package_id/2/menu.htmlactually causes the getPackage SQL Method to be called, with the value '2' passed in to the package_id parameter. Traversal continues at this point, allowing menu.html to be acquired and rendered in the context of the result of 'getPackage'. Thus, if 'menu.html' were a Page Template, it could contain code like:
<h2 tal:content="here/name">Package Name</h2> <p tal:content="here/price">package price...</p> <a href="packages.html" tal:attributes="href string:${container/absolute_url}/packages.html" >Return to package listing</a>Notice the use of here, a page template context meaning the local context of where the template is being executed. The final line uses the 'container' context, which is where the template physically resides in Zope, to render a URL to get back to the package listing (placing the value of the TALES expression in the 'href' attribute of the anchor tag).Since this example SQL Method has only one parameter, it can actually be configured to do Simple Direct Traversal, whereby the URL could be shortened down to:.../site/getMenu/2/menu.htmland "Zope" would match the first value after the SQL Method in the URL to its 'package_id' parameter. Or, you could have URL's with many parameters, such as:.../site/getMenu/menu_id/2/restaurant_id/4/menu.htmlWhich would pass in '2' to the 'menu_id' parameter, and '4' to a notional 'restaurant_id' parameter. There's even more power that can be reaped by using Brains, classes (Python or ZClasses) that can be mixed in with the result to add extra computing power (or other tricks one might figure out to make a simple O-R Mapping system). And ever more power beyond that.
<ul> <li tal:repeat="item here/getNames" tal:content="item/lastName">McKinley</li> <li tal:replace="nothing">Macinroe</li> <li tal:replace="nothing">Macintosh</li> </ul>Using the 'replace="nothing"' construct, I was able to keep his code basically as it was, while still allowing a loop to happen (basically treating his code as example content). This was especially handy in a situation where I had to effectively "rotate" data - where rows in a database had to be displayed as columns in a table. I was able to go down each row, marking up the first data cell to be repeated (actually the second cell in each row), add in a 'tal:repeat' and a 'tal:content' statement, and then add a 'tal:replace="nothing"' to the remaining cells on the row. What I'm left with is a page that still looks fine in Dreamweaver, still draws fine in the browser without rendering, and also renders beautifully. The rest of this very well designed page I left completely untouched. Very cool. This could not be done in DTML, and would be tricky (ie - require a very smart tool) with ASP/JSP/PHP and TEA/Cheetah style templates. And the fact that there are few pages that need server side markup (at this point), coupled with a very rich design, XSLT would have been grossly overkill.Granted, this process has gone very smoothly because the new design is based off of an existing site that we're being paid to replace. The existing site has driven the database schema design (five tables, all in Gadfly), as well as the general content part of the new site - there are few surprises. The design has been shown to the customer independently of the dynamic nature of the site, and the dynamic parts shown have been rather well designed administrative pages (the customer was pleasantly surprised with them, and quite happy, and made only a single change requirement to them). So, at this integration point, it's just a combination of running GoLive/Dreamweaver in split Code/WYSIWYG mode, and quickly adding in a few TAL attributes into the settling design, while still allowing the designer to make tweaks. And while there are cases where things could still get messed up in regards to the placement and rendering of the Page Template, the chance is pretty low, especially this late in the design.Ultimately, the designer has been quite happy with the new ZPT driven sites we've been doing, since the developers no longer wrest control away from him once a page is done (with DTML, a page would typically be diced into plenty of smaller parts, bad DTML tricks, and other what-nots in such a way that the designer would never recognize what had been done anyways). And, if something breaks, the Undo/History features of ZODB (the object database behind "Zope") offer good help towards fixing a problem before it gets too bad.Very Nice.
"Investment guru Warren Buffett offered a bleak prediction for the nation's national security, saying a terrorist attack on American soil is ''virtually a certainty.''" [AP]
Perhaps the most illumining part of Paul's essay is when he describes his optimized doSpellingSuggestion API.Ê In this case, he declares that XML is overkill for the job.Ê Unquestionably, omitting XML in some cases creates a tighter data stream.Ê It can also require custom marshallers and parsers to be written.Ê More tradeoffs to consider.The second to last sentence is important. In my view, XML "scraping" is not much better than HTML "scraping". The Google API's were immediately usable in everything from Ruby to Python to AppleScript to Frontier/Radio, and more, with only a SOAP library needed - nothing specific to Google. The calls were Google specific, yes, but the client immediately understood the results of the call.Even though it's a silly application, I would never have even attempted to write something like HyprSwank, as mentioned in "AppleScript Studio, XML-RPC, and Zope", if I had to parse the results out of my data structure myself. I'm not sure what XML parsing libraries even exist for AppleScript, but I'm sure if they do (or were to) exist, it couldn't be any easier to use than:tell application "http://euc.cx/" to set link_list to call xmlrpc {method name:"hyprSWNK", parameters:{}}Or in Python:
import xmlprclib, pprint link_list = xmlrpclib.ServerProxy('http://euc.cx/').hyprSWNK() pprint.pprint(link_list) [{'sitename': 'mobileffe', 'link': 'http://www.mobileffe.com/'}, {'sitename': 'Prada', 'link': 'http://www.prada.com/'}, {'sitename': 'ACFny', 'link': 'http://www.acfny.org/'}, {'link': 'http://www.peopleusedtodream', 'sitename': 'People Used To Dream About The Future'}, {'sitename': 'David Lynch', 'link': 'http://www.davidlynch.com/'}, {'sitename': '.tiln', 'link': 'http://tiln.net/'}, {'link': 'http://www.timecube.com/', 'sitename': 'educated people are stupid cowards'}, {'link': 'http://www.antigirl.com/', 'sitename': 'absolut antigirl !(absolut cybele)'}, {'sitename': 'no ~ type', 'link': 'http://www.notype.com/'}, {'sitename': 'meta.am +=', 'link': 'http://meta.am/'}, {'sitename': 'fals.ch', 'link': 'http://fals.ch/'}, {'sitename': 'infotron', 'link': 'http://infotron.web.fm'}, {'link': 'http://www.m9ndfukc.org/korporat/', 'sitename': 'aaaaaaa n t i o r p;uss'}, {'sitename': 'nato0+55+3d', 'link': 'http://www.eusocial.org/'}, {'link': 'http://www.angelfire.com/electronic/campmsp/', 'sitename': 'themoonstealingproject'}, {'link': 'http://artists.mp3s.com/artists/42/musiquemotpol.html', 'sitename': 'musique:motpol @ mp3.com'}, {'link': 'http://www.helmutlang.com/', 'sitename': 'finest cotton armbags since 1997 (HL)'}, {'sitename': 'One Bit Louder', 'link': 'http://mi.cz/obl/'}, {'sitename': 'purple', 'link': 'http://purple.fr/'}, {'sitename': 'OFRPublications', 'link': 'http://www.ofrpublications.com/'}]While I could have written my own parser, written my own format, or spent time researching common XML formats and then scouring the web for an already written parser for my language or sitting down with a copy of "XML and Python", I chose not to.For as much as I rag sometimes on some of XML-RPC's shortfalls, it's much better than doing all of that! It gives one common format for a common internet use, and has spread around to many languages. If I had to write my parser, or wait for someone to write one, or deal with DOM navigation, in order to deal with GoogleML, I'd be much less curious about "hmm, what can I write today to take advantage of that?".A nice thing that Bobo got right from day one was to implement and handle marshalling of incoming HTTP requests in such a way that my code never has to go through a "is it a list? is it a string? is it a string I can convert to a list?" somersault again. Or at least, not often.
The classes you'll want to annotate for such treatment should, in many cases, be abstractions of those that implement behavior.ÊCreating such abstractions is, of course, more work -- and the kind of work that doesn't easily yield to automation.Sounds like Interfaces. Which is where my post was going earlier, but I found flaws in my thinking. While I think the following is possible, and may actually work, it will invariably get more complex than this:wsdl = WSDLWriter.generateForInstance(obj, onlyInterface=IBlogger)shrug. I'm too tired right now to explore this notion too much. The issues I started running into is how much to tie web service or security related metadata attached to an Interface (ie, abstraction) to an implementation. It's an issue they struggled with as Interfaces were starting to get heavily used for "Zope" (particularly in the growing Zope 3 architecture), and I believe such information has been moved to configuration files(?). I could be wrong.Anyways, as long as we're going in the direction of generating WSDL out of code versus generating code out of
def enumerate(collection): 'Generates an indexed series: (0,coll[0]), (1,coll[1]) ...' i = 0 it = iter(collection) while 1: yield (i, it.next()) i += 1
For example if your app keeps track of company employees, you will probably want a separate Employee object for each employee. That way you can access these objects with URLs like:/Employees/amos/set_menu?lunch=spamThis URL assumes that Employees is a mapping object that contains Employee objects. Employees["amos"] is an Employee object that represents the amos employee. The above URL calls amos' set_menu method with lunch=spam for arguments:Employees["amos"].set_menu(lunch=spam)It seems to me like an awful lot of XML-RPC interfaces are not much better than this last example. They're usually a single function that is handed some sort of key to operate on another object with, rather than going to that object directly. It defeats polymorphism, it defeats generalization, and ultimately it can lead to a large and unwieldy adapter layer, tucked away in a Web Services directory somewhere.There are merits to this design. You are basically writing an Adapter pattern of sorts, mapping a Web Service friendly interface into whatever the internal system really has. And, if that's the only gateway into your system from the outside, you have some level of security by limiting the amount of access to a collection of XML-RPC dedicated functions instead of opening up access to every potential object.But there are merits to the other design too. In theory, you can program Zope the same way on the inside as on the outside, so long as you have the credential to do so. You have instances of classes out there, on the web, already. .NET seems to be following this route somewhat. Doing Web Services by this route is scarier. Why? Security for one reason. And, you just don't want to turn every single method on the object to be callable over the web, or - at the very least - you want to attach conditions. This is what Microsoft is doing. Let's look at how a simple 'return hello' class for Zope looks, disregarding any Zope OFS framework stuff (making the class addable through the web interface). Putting an instance of this class in a Zope tree would yield the same results over XML-RPC as calls to this .NET example would:
Ed: this could also be Employees.amos.set_menu(lunch=spam)For contrast lets look at a non-bobo way of doing the same thing:/set_menu.py?empid=amos&meal=lunch&food=spam
from Security import ClassSecurityInfo from Persistence import Persistent from Globals import InitializeClass class Hello(Persistent): security = ClassSecurityInfo() #Used to do declarative security security.declareObjectPublic() #Make these objects inherently public security.declarePublic('sayHello') #And make this method public def sayHello(self, name): """ Say hello to 'name' """ return "Hello %s" % name InitializeClass(Hello) #This processes the security declarationsNow, it's about the same overhead as the Microsoft ASP.NET example. note: There's more overhead involved with Zope itself in order to fit the Hello class into the Zope framework, but this is purely an example. This could have been easily written as a Python Script object, where the security settings could be set through a web UI.. Why do all this? Because web services as API's are ultimately programmatically exposed. There are two ways this is done:
class BlogEntry(...): security.declareProtected('Edit Entries', 'editPost') def editPost(self, content, publish=0): """ Set the content of the post, and publishes it if publish is true """ self._content = content if publish: self._publishedContent = content return "OK" #simple return valueAnd, for simplicities sake, lets say we had a flat structure for all entries to make URL's easier, and we have an entry identified as '123'. So, to edit it via URL. Hmm, this is almost REST-like:.../Posts/123/editPost?content=boy+howdy&publish:int=1And do to it via BCI (ZPublisher.Client):
myPost = ZPublisher.Object('.../Posts/123', username='bob', password='bobby') myPost.editPost(content='Boy howdy!', publish=1)And XML-RPC in Python. Note that in order to do this, due to Zope's built in security, you either have to have a hacked XML-RPC library locally that generates authentication headers, of have RPCAuth by Nathan Sain (which I haven't had time to test yet) installed on Zope:
myPost = xmlrpclib.Server('.../Posts/123') myPost.editPost('Boy Howdy!', 1))These are very similar, but ZPublisher.Client was doing this long before XML-RPC. Why? Because Zope was already doing distributed objects (of sorts) on the web, translating HTTP requests into object calls. It only makes sense that there be some simple client lib.Now, to implement this as a proper Blogger API, and do it the way that Radio style web services are done, instead of necessarily having all of those security declarations on the BlogPost class itself, I would instead have to write this:
def editPost(appkey, postid, username, password, content, publish): """ Lookup the post by its ID, verify that the user can access it, and set its content and published flag """ thePost = Posts[postid] ## security assertions based on username/password would go in here, somehow. thePost.editPost(content=content, publish=publish) return 1Now, this isn't too bad. But, it's a similar amount of overhead to turn an otherwise already existing published web method as a Web Service. Huh.As people start exposing ever more valuable resources as Web Services, or even REST, it's no wonder complications arise. As we move beyond the 'getStateName' example and start getting into exchanging business data (Microsoft's heavily advertised 1 degree of separation), more care must be taken. I don't want everything I write automatically turned into a Web Service. I don't necessarily want to have to write extra adapters either.To sum up and say it all over again, the way that "Radio" seems to promote for Web Services is easy. And simple. But you're effectively writing scripted adapters and bridges into other code, something that could turn out to be a maintenance nightmare. The way that "Zope" has promoted for years (even though it's not a terrific Web Services player, sadly) says that "Objects and their methods are already published on the web. No gateway scripts needed.", but with that comes the price of maintaining security declarations similar to C#.NET's ASPX's declarative Web Service calls. Dave calls the latter too much overhead. I say it's the other way around. It should be easy to write Web Services, but you should have to think about what you publish. Those declarations aren't much worse anyways than the long signatures verbose languages like Java can make you write (protected final synchronous int Bob()).