<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2536364715247944978</id><updated>2012-01-19T10:49:32.933-08:00</updated><category term='flash'/><category term='pyamf'/><category term='galaxy'/><category term='javascript'/><category term='workflow'/><category term='sphinx'/><category term='messaging'/><category term='SQLite'/><category term='irods'/><category term='bioinformatics'/><category term='slm'/><category term='pycon'/><category term='agile'/><category term='uagc'/><category term='python'/><category term='spring'/><category term='protovis'/><category term='access'/><category term='amf'/><category term='performance'/><category term='apache'/><category term='xml'/><category term='visualization'/><category term='transaction'/><category term='tornado'/><category term='ajax'/><category term='security'/><category term='optimizing'/><category term='tutorial'/><category term='sqlalchemy'/><category term='plater'/><category term='selenium testing browsermob amfast flex'/><category term='dojango'/><category term='real-time'/><category term='role'/><category term='django'/><category term='java python'/><category term='flex'/><category term='gae'/><category term='vhost'/><category term='c'/><category term='lims'/><category term='flex360'/><category term='dojo'/><category term='filesystem'/><category term='sql'/><category term='senchacon'/><category term='AmFast'/><category term='adapter'/><category term='ssl'/><category term='https'/><category term='MySql'/><category term='json-rpc'/><category term='extjs'/><category term='profiling'/><category term='json'/><title type='text'>limscoder</title><subtitle type='html'>All about enterprise web-apps and development.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.limscoder.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>43</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-7338482773746789440</id><published>2011-12-10T15:29:00.000-08:00</published><updated>2011-12-10T15:29:56.274-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><title type='text'>AmFast 0.5.3 Released</title><content type='html'>&lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; version 0.5.3 has been released. This release contains several important bug fixes. The code can be downloaded from &lt;a href="http://pypi.python.org/pypi?:action=display&amp;amp;name=AmFast&amp;amp;version=0.5.3-r541"&gt;PyPi&lt;/a&gt;&amp;nbsp;or checked out from &lt;a href="http://code.google.com/p/amfast/source/browse/#svn%2Ftags%2F0.5.3"&gt;SVN&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-7338482773746789440?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/7338482773746789440/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/12/amfast-053-released.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7338482773746789440'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7338482773746789440'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/12/amfast-053-released.html' title='AmFast 0.5.3 Released'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-3708396598414450519</id><published>2011-11-23T07:42:00.001-08:00</published><updated>2011-11-23T07:42:59.984-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='extjs'/><title type='text'>ExtJs Component Config</title><content type='html'>Cross post:&lt;a href="http://www.rallydev.com/engblog/2011/11/23/extjs-component-config/"&gt; ExtJs Component Config&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-3708396598414450519?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/3708396598414450519/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/11/extjs-component-config.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3708396598414450519'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3708396598414450519'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/11/extjs-component-config.html' title='ExtJs Component Config'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-1474151607369684880</id><published>2011-10-24T14:33:00.000-07:00</published><updated>2011-10-24T14:34:34.001-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='senchacon'/><title type='text'>Javascript taking a step backwards?</title><content type='html'>I love Javascript, and believe its model of prototype inheritance is the future of programming. The prototype&amp;nbsp;is elegant and eliminates many problems associated with traditional class-based OOP languages. I've used many Javascript libraries, from&amp;nbsp;lite weight&amp;nbsp;JQuery to everything's included libraries like ExtJS and Dojo. I'm excited by the capabilities of modern browsers, but the problem I see with Javascript, is that there are not very many tools available for creating large scale enterprise applications. Sure, a widget here, and a widget there are easy to build, but how about a real life application with hundreds of files and 10s of thousands of lines of code?&lt;br /&gt;&lt;br /&gt;At the &lt;a href="http://secure.lenos.com/lenos/sencha/SenchaCon2011/?gclid=CJHJsrycgqwCFcHr7QodJixCJw"&gt;SenchaCon&lt;/a&gt;&amp;nbsp;conference this morning the CEO of Sencha claimed that Javascript 'has won' and plugin based RIA technologies like Flex and Silverlight are being left behind. He is&amp;nbsp;undeniably&amp;nbsp;right, but at the same time I find it very odd that Javascript is still very far behind in some areas. For example, directly after the statement that Javascript has won, Sencha employees went on to debut several new features and products that have already been part of the Adobe/Flex ecosystem for many years. Messaging, a graphical component builder, an animation builder, a component market place, and an MVC framework are some of the features and products that are just starting to appear in the Javascript world.&lt;br /&gt;&lt;br /&gt;These features have been around the Flex community for quite some time, and these new products seem rudimentary in comparison. Yet, there are no better options, because as far as I know, Sencha is the only company offering similar tools. Adobe is attempting to rework some of their existing tools to compete in the Javascript arena, but they aren't very developed yet. &lt;a href="http://code.google.com/webtoolkit/"&gt;GWT&lt;/a&gt;&amp;nbsp;also offers some similar tools, but why oh why would you want to write Javascript with Java?&amp;nbsp;How long will it be before we have truly robust support for building large enterprise applications with Javascript?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-1474151607369684880?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/1474151607369684880/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/10/javascript-taking-step-backwards.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1474151607369684880'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1474151607369684880'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/10/javascript-taking-step-backwards.html' title='Javascript taking a step backwards?'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4740384554581015397</id><published>2011-10-08T08:08:00.000-07:00</published><updated>2011-10-08T08:08:55.883-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='selenium testing browsermob amfast flex'/><title type='text'>Testing With Browser Mob</title><content type='html'>&lt;b&gt;The Project&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I recently got the chance to work on a project using &lt;a href="https://browsermob.com/"&gt;BrowserMob&lt;/a&gt; for automated testing.&amp;nbsp;&amp;nbsp;&lt;a href="https://browsermob.com/"&gt;BrowserMob&lt;/a&gt;&amp;nbsp;allows you to run &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; test scripts "in the cloud". In my case I was not testing functionality, but instead &amp;nbsp;testing performance of a Flex app. I needed to test latency and throughput of messages being dispatched through the Flex messaging system via&amp;nbsp;&lt;a href="http://code.google.com/p/amfast/"&gt;http://code.google.com/p/amfast/&lt;/a&gt;. This proved very difficult to test locally, but was a snap with&amp;nbsp;&lt;a href="https://browsermob.com/"&gt;BrowserMob&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Testing&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I created a simple Flex client to send and receive Flex messages in a way that replicated a production environment. I also created a custom server component to replicate the production environment and to help log message data to be analyzed later. After getting the client and server running locally, I signed up for a&amp;nbsp;&lt;a href="https://browsermob.com/"&gt;BrowserMob&lt;/a&gt;&amp;nbsp;account and&amp;nbsp;launched several browsers with their&amp;nbsp;web interface.&lt;br /&gt;&lt;br /&gt;The whole process was simpler than it should have been, and I was very impressed with how well it worked. I highly recommend trying out&amp;nbsp;but&amp;nbsp;&lt;a href="https://browsermob.com/"&gt;BrowserMob&lt;/a&gt;&amp;nbsp;for performance and load testing applications, and I'm hoping to get a chance to try out running more full featured automated functional tests in the future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4740384554581015397?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4740384554581015397/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/10/testing-with-browser-mob.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4740384554581015397'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4740384554581015397'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/10/testing-with-browser-mob.html' title='Testing With Browser Mob'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-7470607239446646369</id><published>2011-08-30T17:30:00.000-07:00</published><updated>2011-08-30T17:30:07.315-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><title type='text'>Paired Programming</title><content type='html'>&lt;a href="http://www.rallydev.com/"&gt;Rally&lt;/a&gt;&amp;nbsp;encourages its engineering staff to post to the company blog, so I decided to &lt;a href="http://www.rallydev.com/engblog/2011/08/29/first-impressions-of-paired-programming/"&gt;write a little about my initial experience with paired programming&lt;/a&gt;. TLDR: paired programming is better than I expected, increases code quality, and probably productivity.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-7470607239446646369?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/7470607239446646369/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/08/paired-programming.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7470607239446646369'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7470607239446646369'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/08/paired-programming.html' title='Paired Programming'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4037812461890287890</id><published>2011-07-12T19:51:00.000-07:00</published><updated>2011-07-12T19:51:22.289-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='gae'/><title type='text'>tirtle: a Spring Web MVC project running on GAE</title><content type='html'>I decided to put together a simple web app on my way to learning Java and Spring. &lt;a href="http://tirtlepower.tirtle.com/"&gt;Tirtle&lt;/a&gt;&amp;nbsp;allows users to track daily numbers such as how many calories they eat in a day. The project uses the &lt;a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html"&gt;Spring Web MVC framework&lt;/a&gt;&amp;nbsp;and is running on &lt;a href="http://code.google.com/appengine/docs/whatisgoogleappengine.html"&gt;Google App Engine&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The most frustrating part of the project was just getting everything configured and getting a working server up and running. I feel there is a lack of documentation aimed at beginners, although part of my problems may have been related to jumping right in with Spring + GAE. I chose Spring Framework version 3.0 (the latest version), but there seemed to be&amp;nbsp;more documentation and blog tutorials available for version 2.5. I found an official Web MVC tutorial on the Spring Source site, but it only covered version 2.5.&lt;br /&gt;&lt;br /&gt;I had several problems figuring out the base configuration settings, and getting all the correct .jar files in my classpath. &lt;span class="Apple-style-span" style="background-color: #cccccc;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;webmvc.jar&lt;/span&gt;&lt;/span&gt; was especially&amp;nbsp;mysterious, because it is not included in the Spring Framework distribution. I ended up finding it via a Google search, but I have yet to find the official download from Spring Source.&lt;br /&gt;&lt;br /&gt;Once I got a basic server running, the available documentation for how to actually use Spring seemed pretty good. The Web MVC framework works similar to every other MVC framework you have used. Classes are annotated to turn them into controllers, and controller methods are annotated to turn them into request handlers. You can use either JSPs (Java Server Pages: Java embedded in HTML similar to PHP) or a templating system for your view layer.&lt;br /&gt;&lt;br /&gt;The Web MVC stuff is all built on top of Spring's dependency injection framework, so the simple MVC annotations you use are actually shortcuts to lots of complicated XML configuration. Spring's standard DI tools can be used to inject other non-mvc dependencies into your controllers.&amp;nbsp;I used the DI features to configure a simple authentication object (didn't have time to figure out how to get Spring Security working) and also an ORM interface to Google's DataStore (&lt;a href="http://code.google.com/p/objectify-appengine/"&gt;objectify-appengine&lt;/a&gt;&amp;nbsp;with the help of &lt;a href="http://code.google.com/p/objectify-appengine-spring/"&gt;objectify-appengine-spring&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Unfortunately, the simple web app I developed didn't require much logic, so it didn't help too much in terms of learning how to code Java, but it was an excellent&amp;nbsp;exercise&amp;nbsp;in getting up and running with Spring. The code for the project is on &lt;a href="https://github.com/limscoder/tirtle"&gt;github&lt;/a&gt;. Other new-to-Spring users may find Spring easier to learn by starting with a working app like this one, and learning by modification. I hope to have time to add additional features to the code as I learn how they are accomplished in the Java/Spring ecosystem (unit tests, Spring Security, Javascript framework integration, REST api,&amp;nbsp;templating instead of JSPs).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4037812461890287890?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4037812461890287890/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/07/tirtle-spring-web-mvc-project-running.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4037812461890287890'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4037812461890287890'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/07/tirtle-spring-web-mvc-project-running.html' title='tirtle: a Spring Web MVC project running on GAE'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-5600252787581470358</id><published>2011-06-22T10:48:00.000-07:00</published><updated>2011-06-26T09:17:33.150-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java python'/><title type='text'>Python Vs Java</title><content type='html'>After using Python for the past several years, I'm going to be taking on a Java project. I am in the process of learning Java, and I thought I would write up a comparison of the features in each language.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Features Java has that Python doesn't:&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Static typing&lt;/li&gt;&lt;li&gt;Strict access control (package, public, protected, private)&lt;/li&gt;&lt;li&gt;Traditional threading implementation wrapped in a decent API&lt;/li&gt;&lt;li&gt;Bytecode backwards compatibility&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;Features Python has that Java doesn't:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Dynamic objects&lt;/li&gt;&lt;li&gt;No&amp;nbsp;explicit&amp;nbsp;compile step&lt;/li&gt;&lt;li&gt;Properties (transparent getters/setters)&lt;/li&gt;&lt;li&gt;List comprehensions&lt;/li&gt;&lt;li&gt;Operator overloading&lt;/li&gt;&lt;li&gt;Generators (create iterators with the 'yield' statement)&lt;/li&gt;&lt;li&gt;Optional keyword arguments, *args, and **kwargs&lt;/li&gt;&lt;li&gt;&lt;a href="http://pypi.python.org/pypi"&gt;pypi (the cheeseshop)&lt;/a&gt; and pip&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;Static Typing&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Static typing has 2 main advantages. Type errors can be caught at compile time, and the compiler can make more optimizations.&amp;nbsp;Statically&amp;nbsp;typed code takes more time to write (creating explicit interface definitions), but you don't need to write manual type checking functions&amp;nbsp;(&lt;a href="http://en.wikipedia.org/wiki/Duck_typing"&gt;duck typing&lt;/a&gt;)&amp;nbsp;like you often need to in a dynamic language. Static typing also allows you to avoid runtime type errors that you would probably need a unit test to catch with a dynamic language. Javascript and Python both have decent JIT compilers available, so the speed difference between dynamic languages and static languages will continue to narrow.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Strong vs Weak Typing&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While static typing may be helpful for some projects, I believe the biggest factor in type&amp;nbsp;usability&amp;nbsp;is not static vs dynamic but strong vs weak. Weakly typed languages allow you to cast objects from one type to another. If you've ever used Perl, PHP, of Javascript you've probably run into some hard-to-debug problems that were caused by implicit casting or automatic type coercion. Languages like these usually have confusing operators like '==='. C doesn't do any implicit casting, but it will let you manually cast in unsafe ways.&amp;nbsp;Java also allows casting, but is safer than C, as it will throw a runtime exception if you try to cast to an incompatible type. On the other hand, Python is strongly typed. There is no such thing as a 'cast' in Python, and there are very few situations where automatic type conversion takes place (arithmetic with operands of different number types&amp;nbsp;automatically&amp;nbsp;convert all operands to the widest type used in the expression). Python's combination of strongly typed objects and dynamic objects with duck typed interfaces is a winner.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Access Controls&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Python has no access control modifiers, and instead uses a convention of naming private attributes with a leading under score: '_private_method'. Client code is not 'supposed' to use attributes named with a leading underscore, but there is nothing technical stopping it. I must admit there have been several times when I wish Python had some equivalent construct. Java's 'final' modifier is&amp;nbsp;particularly&amp;nbsp;useful. Considering that many of Python's standard data types are immutable&amp;nbsp;(string, unicode, tuple, frozenset), it's surprising that Python does not offer an easy way to define immutable objects. In Java it's as easy as adding 'private final'.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Threading&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Unlike Python and the &lt;a href="http://en.wikipedia.org/wiki/Global_Interpreter_Lock"&gt;GIL&lt;/a&gt;, Java can utilize multiple cores when executing threads, and it's concurrency interface is wrapped in a nice API. However, I'm not sure how much I'll get to use the threading features. Networking code is&amp;nbsp;increasingly being&amp;nbsp;moved away from threaded implementations to asynchronous/non-blocking solutions such as &lt;a href="http://nodejs.org/"&gt;NodeJS&lt;/a&gt;&amp;nbsp;and &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt;, and computation is&amp;nbsp;being distributed on a cluster or 'in the cloud', instead of being run on a single machine.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Other Features&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Java lacks many useful features present in Python, and I'm sure I will miss many of them. I hope the list of useful Java constructs grows as I learn more about the language and start working on a production code base.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-5600252787581470358?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/5600252787581470358/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/06/python-vs-java.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5600252787581470358'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5600252787581470358'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/06/python-vs-java.html' title='Python Vs Java'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-5589351938983976568</id><published>2011-05-29T10:31:00.000-07:00</published><updated>2011-05-29T10:31:02.720-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='galaxy'/><category scheme='http://www.blogger.com/atom/ns#' term='bioinformatics'/><category scheme='http://www.blogger.com/atom/ns#' term='irods'/><category scheme='http://www.blogger.com/atom/ns#' term='extjs'/><title type='text'>Javascript File Browser for Server-Side Files</title><content type='html'>&lt;b&gt;Problem - Galaxy&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;a href="http://galaxy.psu.edu/"&gt;Galaxy&lt;/a&gt;&amp;nbsp;is a web-based bioinformatics toolkit that allows users to create customized data analysis pipelines. It is becoming an extremely common tool, especially in the sequencing field. The project has one major flaw: it is difficult to get large data files into the system. Uploading 2G files (generated from sequencing runs) through a browser is not a workable solution.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Solution - iRods&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;a href="http://irods.org/"&gt;iRods&lt;/a&gt;&amp;nbsp;is a virtual file system commonly used to transfer and share files in the scientific community. It abstracts the storage details and provides tools for access control, sharing, metadata tracking, file type conversion, and high performance multi-threaded file transfer.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Integration&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;Myself and co-workers Fred Bevins and Susan Miller were tasked with integrating iRods and Galaxy during the iRods code sprint hosted by &lt;a href="http://www.iplantcollaborative.org/"&gt;iPlant&lt;/a&gt; in April. My share of the work was a &lt;a href="https://github.com/limscoder/js-file-browser"&gt;client-side Javascript file browser&lt;/a&gt;. The file browser allows users to browse and select server side files. The version I built talks to the &lt;a href="https://pods.iplantcollaborative.org/wiki/display/docs/Foundational+API+v1.0"&gt;iPlant Foundational API&lt;/a&gt;, which exposes iRods directories and files. Other back-ends could easily be added to allow the file browser to talk with a standard file system, or any other file repository.&amp;nbsp;Fred and Susan worked on integrating the file browser into Galaxy, which allows Galaxy users to browse and select files in an iRods repository for use in an analysis. The javascript code is available now, and the Galaxy integration code should be available sometime soon.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-5589351938983976568?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/5589351938983976568/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/05/javascript-file-browser-for-server-side.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5589351938983976568'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5589351938983976568'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/05/javascript-file-browser-for-server-side.html' title='Javascript File Browser for Server-Side Files'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4501029157603771264</id><published>2011-04-04T11:31:00.000-07:00</published><updated>2011-04-04T11:31:34.239-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='protovis'/><category scheme='http://www.blogger.com/atom/ns#' term='workflow'/><title type='text'>Using Protovis to Create Simple Flow Charts</title><content type='html'>&lt;a href="http://vis.stanford.edu/protovis/"&gt;Protovis&lt;/a&gt; is a Javascript library for creating SVG graphics to visualize datasets. The API is great, and I've been using it to visualize all sorts of data for a project I'm working on. I had a need to display a very simple (&amp;lt; 15 nodes) branching flow chart. The screen is simple enough that it doesn't justify a custom Protovis layout component or anything fancy like that.&lt;br /&gt;&lt;br /&gt;I cooked up a scheme where the nodes are absolute positioned div elements that can be styled with CSS, and the edges are drawn with Protovis. I pass an object that defines the edge properties to a Javascript function that uses &lt;a href="http://jquery.com/"&gt;JQuery&lt;/a&gt; to find the exact positions of the nodes, and then I use Protovis to draw the edges.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Example:&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-qRfui5DAsb0/TZoLx8RJS0I/AAAAAAAACmU/cxUVEOFkcxk/s1600/protovis_workflow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="428" src="http://4.bp.blogspot.com/-qRfui5DAsb0/TZoLx8RJS0I/AAAAAAAACmU/cxUVEOFkcxk/s640/protovis_workflow.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;b&gt;HTML:&lt;/b&gt;&lt;br /&gt;&lt;pre class="brush: html"&gt;&amp;lt;div id="workflowContainer"&amp;gt;&lt;br /&gt;  &amp;lt;!--&lt;br /&gt;    -- Draw Simple divs to represent workflow nodes, and connect them with Protovis.&lt;br /&gt;    --&lt;br /&gt;    -- Nodes are positioned absolutely.&lt;br /&gt;    -- Node positions can be static and manually determined,&lt;br /&gt;    -- or dynamic and determined by server-side or client-side&lt;br /&gt;    -- code. This example uses hard coded node positions.&lt;br /&gt;    --&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;div id="workflowChart" &amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Clickable node --&amp;gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="startFlow" style="top 0; left: 440px;"&amp;gt;Start&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Foo branch --&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Unclickable node --&amp;gt;  &lt;br /&gt;    &amp;lt;div id="foo1Flow" style="top: 100px; left: 200px;"&amp;gt;Foo 1&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="foo2Flow"  style="top: 175px; left: 100px;"&amp;gt;Foo 2&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;div id="fooChoice1Flow"  style="top: 300px; left: 0px;"&amp;gt;Foo Choice 1&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;div id="fooChoice2Flow" class="inactive" style="top: 300px; left: 165px;"&amp;gt;Foo Choice 2&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;div id="fooChoice3Flow" class="inactive" style="top: 300px; left: 360px;"&amp;gt;Foo Choice 3&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;div id="fooOptionFlow"  style="top: 400px; left: 50px;"&amp;gt;Foo Option&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="fooCombineFlow"  style="top: 500px; left: 200px;"&amp;gt;Foo Combine&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="fooSplit1Flow"  style="top: 575px; left: 25px;"&amp;gt;Foo Split 1&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="fooSplit2Flow"  style="top: 575px; left: 250px;"&amp;gt;Foo Split 2&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;!-- bar branch --&amp;gt;&lt;br /&gt;    &amp;lt;div id="barFlow" style="top: 100px; left: 700px;"&amp;gt;Bar&amp;lt;/div&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="bar1Flow" class="inactive" style="top: 200px; left: 550px;"&amp;gt;Bar 1&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &lt;br /&gt;    &amp;lt;a href=""&amp;gt;&amp;lt;div id="bar2Flow" class="inactive" style="top: 200px; left: 825px;"&amp;gt;Bar 2&amp;lt;/div&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;CSS:&lt;/b&gt;&lt;br /&gt;&lt;pre class="brush: css"&gt;/* Contains both nodes and edges. */&lt;br /&gt;#workflowChartContainer {&lt;br /&gt; position: relative;&lt;br /&gt; width: 1000px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/* This is where the edges will be drawn by protovis. */&lt;br /&gt;#workflowChartContainer span {&lt;br /&gt; position: absolute;&lt;br /&gt; top: 0;&lt;br /&gt; left: 0;&lt;br /&gt; background: transparent;&lt;br /&gt; z-index: 1000; /* SVG needs to be drawn on top of existing layout. */&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart {&lt;br /&gt; position: relative;&lt;br /&gt; top: 0;&lt;br /&gt; left: 0;&lt;br /&gt; height: 700px;&lt;br /&gt; width: 1000px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart div {&lt;br /&gt; border-color: #5b9bea;&lt;br /&gt; background-color: #b9cde5;&lt;br /&gt; position: absolute;&lt;br /&gt; margin: 0;&lt;br /&gt; padding: 4px;&lt;br /&gt; border: 2px solid #5b9bea;&lt;br /&gt; background: #b9cde5;&lt;br /&gt; border-radius: 4px;&lt;br /&gt; -moz-border-radius: 4px;&lt;br /&gt; -webkit-border-radius: 4px;&lt;br /&gt; color: #000;&lt;br /&gt; z-index: 10000; /* Needs to be drawn on top of SVG to be clickable. */&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart a {&lt;br /&gt; cursor: pointer;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart a div {&lt;br /&gt; border-color: #f89c51;&lt;br /&gt; background: #fcd5b5;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart div.inactive {&lt;br /&gt; border-color: #ccc;&lt;br /&gt; background-color: #eee;&lt;br /&gt; color: #ccc;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#workflowChart div:hover {&lt;br /&gt; border-color: #700000;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Javascript:&lt;/b&gt;&lt;br /&gt;&lt;pre class="brush: javascript"&gt;/* Initialize workflow screen. */&lt;br /&gt;var initWorkflow = function() {&lt;br /&gt;    // List HTML nodes to connect.&lt;br /&gt;    //&lt;br /&gt;    // The edges are hardcoded in this example,&lt;br /&gt;    // but could easily be made dynamic.&lt;br /&gt;    var edges = [&lt;br /&gt;        {&lt;br /&gt;            source: 'startFlow',&lt;br /&gt;            target: 'foo1Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'foo1Flow',&lt;br /&gt;            target: 'foo2Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'foo2Flow',&lt;br /&gt;            target: 'fooChoice1Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'foo2Flow',&lt;br /&gt;            target: 'fooChoice2Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'foo2Flow',&lt;br /&gt;            target: 'fooChoice3Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooChoice1Flow',&lt;br /&gt;            target: 'fooOptionFlow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooChoice2Flow',&lt;br /&gt;            target: 'fooOptionFlow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooOptionFlow',&lt;br /&gt;            target: 'fooCombineFlow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooChoice3Flow',&lt;br /&gt;            target: 'fooCombineFlow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooCombineFlow',&lt;br /&gt;            target: 'fooSplit1Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'fooCombineFlow',&lt;br /&gt;            target: 'fooSplit2Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'startFlow',&lt;br /&gt;            target: 'barFlow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'barFlow',&lt;br /&gt;            target: 'bar1Flow'&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            source: 'barFlow',&lt;br /&gt;            target: 'bar2Flow'&lt;br /&gt;        },&lt;br /&gt;    ];&lt;br /&gt;      &lt;br /&gt;    // Us JQUery to set height and width equal to background div.&lt;br /&gt;    var workflow = $('#workflowChart'),&lt;br /&gt;        h = workflow.height(),&lt;br /&gt;        w = workflow.width();&lt;br /&gt;  &lt;br /&gt;    // Create Protovis Panel used to render SVG.&lt;br /&gt;    var vis = new pv.Panel()&lt;br /&gt;        .width(w)&lt;br /&gt;        .height(h)&lt;br /&gt;        .antialias(false);&lt;br /&gt;      &lt;br /&gt;    // Attach Panel to dom&lt;br /&gt;    vis.$dom = workflow[0];&lt;br /&gt;      &lt;br /&gt;    // Render connectors&lt;br /&gt;    drawEdges(vis, edges);&lt;br /&gt;    var test = vis.render();&lt;br /&gt; };&lt;br /&gt; &lt;br /&gt; /* Draw edges specified in input array. */&lt;br /&gt; var drawEdges = function(vis, edges) {&lt;br /&gt;     // Direction indicators,&lt;br /&gt;     var directions = []; &lt;br /&gt; &lt;br /&gt;     $.each(edges, function(idx, item){&lt;br /&gt;         // Color of edges&lt;br /&gt;         var color = '#000';&lt;br /&gt;         &lt;br /&gt;         // Arrow radius         &lt;br /&gt;         var r = 5;&lt;br /&gt;         &lt;br /&gt;         // Use JQuery to get source and destination elements&lt;br /&gt;         var source = $('#' + item.source);&lt;br /&gt;         var target = $('#' + item.target);&lt;br /&gt;         &lt;br /&gt;         if (!(source.length &amp;amp;&amp;amp; target.length)) {&lt;br /&gt;             // One of the nodes is not present in the DOM; skip it.&lt;br /&gt;             return;&lt;br /&gt;         }&lt;br /&gt;         &lt;br /&gt;         var data = edgeCoords(source, target);&lt;br /&gt;         if (item.sourceLOffset) {&lt;br /&gt;             data[0].left += item.sourceLOffset;&lt;br /&gt;         }&lt;br /&gt;         if (item.targetLOffset) {&lt;br /&gt;             data[1].left += item.targetLOffset;&lt;br /&gt;         }&lt;br /&gt;         &lt;br /&gt;         if (source.hasClass('inactive') || target.hasClass('inactive')) {&lt;br /&gt;             // If target is disabled, change the edge color.&lt;br /&gt;             color = '#ccc';&lt;br /&gt;         }&lt;br /&gt;         &lt;br /&gt;         // Use Protovis to draw edge line.&lt;br /&gt;         vis.add(pv.Line)&lt;br /&gt;             .data(data)&lt;br /&gt;             .left(function(d) {return d.left;})&lt;br /&gt;             .top(function(d) {&lt;br /&gt;                 if (d.type === 'target') {&lt;br /&gt;                     return d.top - (r * 2);&lt;br /&gt;                 }&lt;br /&gt;                 &lt;br /&gt;                 return d.top;&lt;br /&gt;              })&lt;br /&gt;             .interpolate('linear')&lt;br /&gt;             .segmented(false)&lt;br /&gt;             .strokeStyle(color)&lt;br /&gt;             .lineWidth(2);&lt;br /&gt;         &lt;br /&gt;         // Here you may want to calculate an angle&lt;br /&gt;         // to twist the direction arrows to make the graph&lt;br /&gt;         // prettier. I've left out the code to keep thing simple.&lt;br /&gt;         var a = 0;&lt;br /&gt;         &lt;br /&gt;         // Add direction indicators to array.&lt;br /&gt;         var d = data[1];&lt;br /&gt;         directions.push({&lt;br /&gt;             left: d.left,&lt;br /&gt;             top: d.top - (r * 2),&lt;br /&gt;             angle: a,&lt;br /&gt;             color: color&lt;br /&gt;         });&lt;br /&gt;     });&lt;br /&gt;     &lt;br /&gt;     // Use Protovis to draw all direction indicators&lt;br /&gt;     //&lt;br /&gt;     // Here you may want to check and make&lt;br /&gt;     // sure you're only drawing a single indicator&lt;br /&gt;     // at each position, to avoid drawing multiple&lt;br /&gt;     // indicators for targets that have multiple sources.&lt;br /&gt;     // I've left out the code for simplicity.&lt;br /&gt;     vis.add(pv.Dot)&lt;br /&gt;         .data(directions)&lt;br /&gt;         .left(function (d) {return d.left;})&lt;br /&gt;         .top(function (d) {return d.top;})&lt;br /&gt;         .radius(r)&lt;br /&gt;         .angle(function (d) {return d.angle;})&lt;br /&gt;         .shape("triangle")&lt;br /&gt;         .strokeStyle(function (d) {return d.color;})&lt;br /&gt;         .fillStyle(function (d) {return d.color;});&lt;br /&gt; };&lt;br /&gt; &lt;br /&gt; /* Returns the bottom-middle offset for a dom element. */&lt;br /&gt; var bottomMiddle = function(node) {&lt;br /&gt;     var coords = node.position();&lt;br /&gt;     coords.top += node.outerHeight();&lt;br /&gt;     coords.left += node.width() / 2;&lt;br /&gt;     return coords;&lt;br /&gt; };&lt;br /&gt; &lt;br /&gt; /* Returns the top-middle offset for a dom element. */&lt;br /&gt; var topMiddle = function(node) {&lt;br /&gt;     var coords = node.position();&lt;br /&gt;     coords.left += node.width() / 2;&lt;br /&gt;     return coords;&lt;br /&gt; };&lt;br /&gt; &lt;br /&gt; /* Return start/end coordinates for an edge. */&lt;br /&gt; var edgeCoords = function(source, target) {&lt;br /&gt;     var coords = [bottomMiddle(source), topMiddle(target)];&lt;br /&gt;     coords[0].type = 'source';&lt;br /&gt;     coords[1].type = 'target';&lt;br /&gt;     return coords;&lt;br /&gt; };&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4501029157603771264?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4501029157603771264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/04/using-protovis-to-create-simple-flow.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4501029157603771264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4501029157603771264'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/04/using-protovis-to-create-simple-flow.html' title='Using Protovis to Create Simple Flow Charts'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-qRfui5DAsb0/TZoLx8RJS0I/AAAAAAAACmU/cxUVEOFkcxk/s72-c/protovis_workflow.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6183638115841515833</id><published>2011-03-16T14:41:00.000-07:00</published><updated>2011-03-16T14:41:26.535-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon'/><title type='text'>PyCon 2011 Report</title><content type='html'>Here is a presentation covering the status of Python and sessions I attended at &lt;a href="http://us.pycon.org/2011/home/"&gt;PyCon 2011&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px" id="__ss_7287208"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/limscoder/pycon-2011" title="Pycon 2011"&gt;Pycon 2011&lt;/a&gt;&lt;/strong&gt; &lt;object id="__sse7287208" width="425" height="355"&gt; &lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=pycon2011-110316163140-phpapp02&amp;stripped_title=pycon-2011&amp;userName=limscoder" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse7287208" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=pycon2011-110316163140-phpapp02&amp;stripped_title=pycon-2011&amp;userName=limscoder" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt; &lt;/object&gt; &lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/limscoder"&gt;limscoder&lt;/a&gt; &lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6183638115841515833?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6183638115841515833/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/03/pycon-2011-report.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6183638115841515833'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6183638115841515833'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/03/pycon-2011-report.html' title='PyCon 2011 Report'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6079615584494807443</id><published>2011-03-09T15:05:00.000-08:00</published><updated>2011-03-16T14:42:00.950-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='lims'/><category scheme='http://www.blogger.com/atom/ns#' term='dojo'/><category scheme='http://www.blogger.com/atom/ns#' term='slm'/><title type='text'>SLM Presentation</title><content type='html'>&lt;a href="http://www.slideshare.net/limscoder/slm-sample-lifecycle-manager"&gt;Presentation slides&lt;/a&gt; from &lt;a href="http://uaweb.arizona.edu/"&gt;UAWEBDEV&lt;/a&gt;&amp;nbsp;presentation about&lt;a href="https://services.arl.arizona.edu/"&gt; SLM (Sample Lifecycle Manager).&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px" id="__ss_7210195"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/limscoder/slm-sample-lifecycle-manager" title="SLM (Sample Lifecycle Manager)"&gt;SLM (Sample Lifecycle Manager)&lt;/a&gt;&lt;/strong&gt; &lt;object id="__sse7210195" width="425" height="355"&gt; &lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=slmtechnical-110309170128-phpapp01&amp;stripped_title=slm-sample-lifecycle-manager&amp;userName=limscoder" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse7210195" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=slmtechnical-110309170128-phpapp01&amp;stripped_title=slm-sample-lifecycle-manager&amp;userName=limscoder" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt; &lt;/object&gt; &lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/limscoder"&gt;limscoder&lt;/a&gt; &lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6079615584494807443?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6079615584494807443/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/03/slm-presentation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6079615584494807443'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6079615584494807443'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/03/slm-presentation.html' title='SLM Presentation'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-9175749590435679733</id><published>2011-02-18T08:08:00.000-08:00</published><updated>2011-02-18T09:57:34.822-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='uagc'/><category scheme='http://www.blogger.com/atom/ns#' term='lims'/><category scheme='http://www.blogger.com/atom/ns#' term='slm'/><title type='text'>SLM (Sample Lifecycle Manager)</title><content type='html'>&lt;div style="text-align: left;"&gt;&lt;span class="Apple-style-span" style="font-weight: bold; "&gt;SLM&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;We released the latest version of &lt;a href="http://services.arl.arizona.edu/"&gt;SLM (Sample Lifecycle Manager)&lt;/a&gt; on February 1st, and the site has been a resounding success so far. SLM supports life sciences laboratory services offered by &lt;a href="http://uagc.arl.arizona.edu/"&gt;UAGC&lt;/a&gt; including:&lt;br /&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;DNA extraction&lt;/li&gt;&lt;li&gt;Sanger sequencing&lt;/li&gt;&lt;li&gt;DNA fragment analysis (str/microsatellite)&lt;/li&gt;&lt;li&gt;Sequenom genotyping&lt;/li&gt;&lt;li&gt;Sequenom methylation analysis&lt;/li&gt;&lt;li&gt;Taqman genotyping&lt;/li&gt;&lt;li&gt;454 sequencing&lt;/li&gt;&lt;li&gt;Ion Torrent sequencing (coming soon)&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;img src="http://1.bp.blogspot.com/-EaNPCLz5Po0/TV6bhFxoflI/AAAAAAAACl0/ql5-MEOUTTA/s400/slm_grid.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5575064381519593042" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 281px; " /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;EAGER&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://services.arl.arizona.edu/"&gt;SLM&lt;/a&gt; is built with &lt;a href="http://ccp.arl.arizona.edu/trac/eager"&gt;Eager&lt;/a&gt;, an application framework for developing custom LIMS. Eager is a collection of &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; apps that provide common LIMS functionality including:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Workflow management with GLP compliant status logging&lt;/li&gt;&lt;li&gt;GLP compliant user and lab access control and management&lt;/li&gt;&lt;li&gt;Sample/tube/grid submission and management&lt;/li&gt;&lt;li&gt;Volume and concentration tracking&lt;/li&gt;&lt;li&gt;Automated sample and reagent dilution and 'cherry picking' transfers&lt;/li&gt;&lt;li&gt;Reagent lot tracking&lt;/li&gt;&lt;li&gt;Data management and collaboration&lt;/li&gt;&lt;li&gt;Integration with SOP management system&lt;/li&gt;&lt;li&gt;Environmental monitoring&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The core features of Eager can be used 'out-of-the-box' for a complete LIMS solution with a generic sample tracking workflow, or can be customized to provide service specific workflows (such as Sequenom, 454, Ion Torrent, etc.) The framework includes tons of features, and additional workflows can be easily added by an experienced &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; developer. Custom workflows are simply custom &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; apps that hook into Eager's workflow definition system. All client-side code is written with the &lt;a href="http://dojotoolkit.org/"&gt;Dojo framework&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I am hoping to release the Eager framework on &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt; this spring or summer (it will be the first "open-source LIMS that doesn't suck"), but it currently needs to be reviewed by our IP/legal department first.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-9175749590435679733?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/9175749590435679733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/02/slm-sample-lifecycle-manager.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9175749590435679733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9175749590435679733'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/02/slm-sample-lifecycle-manager.html' title='SLM (Sample Lifecycle Manager)'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-EaNPCLz5Po0/TV6bhFxoflI/AAAAAAAACl0/ql5-MEOUTTA/s72-c/slm_grid.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-8793737966647981627</id><published>2011-01-02T14:41:00.000-08:00</published><updated>2011-01-02T15:11:05.891-08:00</updated><title type='text'>Django ORM Tools</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Django ORM Tools&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Django ORM is a great tool that makes it easy to work with simple data models, but it quickly shows its limitations as the complexity of the data model grows. The orm_tools module is an attempt to keep the simplicity of the Django ORM, while adding some extra features that make it much easier to work with complex object graphs. The code is available on &lt;a href="http://djangosnippets.org/snippets/2305/"&gt;Django Snippets&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Object Instances/Sessions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Django ORM loads each object separately from the database. If different QuerySets select multiple objects with the same primary key, the resulting objects will all be different instances.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;MyModel.objects.get(pk=1) is MyModel.objects.get(pk=1)&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt; ORM solves this problem with &lt;a href="http://www.sqlalchemy.org/docs/orm/session.html"&gt;sessions&lt;/a&gt;. orm_tools contains a Session class to provide similar functionality. Use the 'with' statement in combination with a Session instance to force QuerySets to retrieve cached object instances from the session.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;from orm_tools import Session&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;with Session():&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MyModel.objects.get(pk=1) is MyModel.objects.get(pk=1)&lt;br /&gt;True&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When QuerySet objects are executed inside of the 'with' block, all SQL queries are performed normally, but cached object instances are returned if an instance with an identical primary key already exists in the session. The session applies throughout any code called from within the 'with' block. Any objects inserted into the DB within the 'with' block are automatically added to the session.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Object Graphs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Django ORM does not automatically save model object dependencies, so Django model instances must be saved one at a time.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;parent = MyModel()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;child = MyChild(parent=parent)&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;child.save()&lt;br /&gt;IntegrityError: app_mychild.parent_id may not be NULL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For simple data models, this problem is easily fixed by inserting the models into the database at the same time that they are created.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;parent = MyModel.objects.create()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;child = MyChild.objects.create(parent=parent)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;However this is not always ideal for more complex data models, especially if the objects involved already exist in the database, and changes need to be persisted by updating existing rows. orm_tools contains a GraphSaver class that will save an entire object graph at once.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;from orm_tools import GraphSaver&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;parent = MyModel()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;child = MyChild(parent=parent)&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;saver = GraphSaver()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;saver.save(child)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When the 'save' method of the GraphSaver object is called, all dependencies will be detected and their 'save' methods will be called in the correct order, so that the entire object graph is saved. The GraphSaver's 'save' method works equally well for both inserts and updates, although updates can optionally be ignored by setting the 'update' argument to False. In the future, I hope to increase performance significantly by modifying the code to exeucte batched insert/update queries for databases that support it (postgres w/ psycopg 2).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Collections&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Django ORM supports one-to-many object relations. Objects on the 'many' side of a one-to-many relation cannot be attached to the 'one' unless the 'one' is already saved in the database. This causes some of the same problems as described in the 'Object Graphs' section. The orm_tools module contains a Collection class that enables 'many' objects to be added to a 'one' object, regardless of whether the 'one' object has been saved yet.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;from django.db import models&lt;br /&gt;&lt;br /&gt;from orm_tools import Collection&lt;br /&gt;&lt;br /&gt;class One(models.Model):&lt;br /&gt;    label = models.CharField(default='blank', max_length=20)&lt;br /&gt;&lt;br /&gt;# Call the 'set_property' static method&lt;br /&gt;# to create a collection object.&lt;br /&gt;#&lt;br /&gt;# Arguments&lt;br /&gt;# ==========&lt;br /&gt;#  * Model to add collection to&lt;br /&gt;#  * Collection attribute name&lt;br /&gt;#  * Many's foreign key attribute name&lt;br /&gt;#  * One's 'many set' attribute name&lt;br /&gt;Collection.set_property(One, 'children', 'parent', 'many_set')&lt;br /&gt;&lt;br /&gt;class Many(models.Model):&lt;br /&gt;    label = models.CharField(default='blank', max_length=20)&lt;br /&gt;    parent = models.ForeignKey(One, null=False)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;one = One()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;one.children.add(Many())&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;one.children.add(Many())&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;saver = GraphSaver()&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;saver.save(one)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Collection object can be iterated through, indexed, and sliced regardless of whether the 'one' object and the 'many' objects have been saved yet. The GraphSaver's 'save' method will also automatically save all 'many' objects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-8793737966647981627?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/8793737966647981627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2011/01/django-orm-tools.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8793737966647981627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8793737966647981627'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2011/01/django-orm-tools.html' title='Django ORM Tools'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6840582704512593004</id><published>2010-10-16T09:07:00.000-07:00</published><updated>2010-10-16T09:44:56.064-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='vhost'/><category scheme='http://www.blogger.com/atom/ns#' term='ssl'/><category scheme='http://www.blogger.com/atom/ns#' term='https'/><category scheme='http://www.blogger.com/atom/ns#' term='apache'/><title type='text'>Apache, Virtual Hosts, and HTTPS</title><content type='html'>Apache cannot use https with name-based virtual hosts due to the way the &lt;a href="http://httpd.apache.org/docs/2.2/vhosts/name-based.html"&gt;SSL handshake works&lt;/a&gt;. I've run across this problem several times in the past, and I always forget how to solve it. So I'll record it here for posterity.&lt;br /&gt;&lt;br /&gt;To get things working, the Apache setup needs to be changed from name-based virtual hosting to ip-based virtual hosting. After configuring a separate ip for each vhost that requires https, the Apache config files (/etc/httpd/conf/ on RHEL, Apache 2.2) need to be updated to use ip-based virtual hosting.&lt;br /&gt;&lt;br /&gt;If name-based vhosting was previously configured, it will need to be modified. If all vhosts are being converted to ip-based vhosting, then name-based vhosting can be completely turned off by commenting or deleting any 'NameVirtualHost' directives. However, it is also possible to continue to use name-based vhosting for vhosts that do not require https. Any existing 'NameVirtualHost' directives that contain wildcards ('NameVirtualHost *:80') will need to be modified. Replace the wildcard with the ip that will be shared by name-based vhosts.&lt;br /&gt;&lt;br /&gt;Next, modify any existing 'VirtualHost' directives that contain wildcards in their definition ('VirtualHost *:80'). Replace the wildcard with the ip that the vhost will be using. Virtual hosts that do not require HTTPS can continue to use name-based virtual hosting, and can share the same ip, but all vhosts that require HTTPS must use a unique ip address.&lt;br /&gt;&lt;br /&gt;Finally, configure a 'VirtualHost' directive for each ip-based vhost in the ssl section of the Apache configuration file ('/etc/httpd/conf.d/ssl.conf' on RHEL, Apache 2.2). Any name-based vhosts will continue to share the ssl config within the '_default_:80' 'VirtualHost' directive. Restart Apache for the changes to take affect.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6840582704512593004?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6840582704512593004/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/10/apache-virtual-hosts-and-https.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6840582704512593004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6840582704512593004'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/10/apache-virtual-hosts-and-https.html' title='Apache, Virtual Hosts, and HTTPS'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-89849333748620004</id><published>2010-09-04T12:05:00.000-07:00</published><updated>2010-09-04T13:30:45.987-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='filesystem'/><category scheme='http://www.blogger.com/atom/ns#' term='transaction'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Transactions for File Transfer</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Transactions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Database transactions are a convenient way to maintain consistent state during data processing functions. If an error occurs during processing, just rollback the transaction to avoid incomplete or incorrect data being stored.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I've worked on many problems where data processing involves retrieving a source file, performing some type of processing, and then writing to a destination file. These functions are tricky, because if a problem arises during the processing, you're left with an inconsistent, partially processed batch of files. This problem is especially pronounced if you're storing file metadata in a database. If you perform a rollback of your database transaction when an error occurs, then you've lost any updated metadata about the files that were processed correctly.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Solution&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In an attempt to remedy this problem I've developed a somewhat naive implementation of a file transaction class that can be used to maintain consistent state during processing function involving many files. The transaction object keeps track of all files that have been created and all files that should be deleted. All files marked for deletion are deleted when a commit occurs. All files marked as created are removed when a rollback occurs. If a file needs to be moved, it is instead copied, and the source file is marked for deletion, and the destination file is marked as being created.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Implementation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;import glob&lt;br /&gt;import os&lt;br /&gt;import shutil&lt;br /&gt;&lt;br /&gt;class Transaction(object):&lt;br /&gt;    """&lt;br /&gt;    Manages transactions for file storage.&lt;br /&gt;&lt;br /&gt;    Assumes each file is only being operated on by one person at a time.&lt;br /&gt;&lt;br /&gt;    If multiple users try to operate on the same file, then the last&lt;br /&gt;    to access gets an exception.&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    lock_postfix = 't_lock'&lt;br /&gt;&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self._level = 0&lt;br /&gt;&lt;br /&gt;    def _get_lock_path(self, path):&lt;br /&gt;        """Return lock file path."""&lt;br /&gt;&lt;br /&gt;        if path.endswith('/'):&lt;br /&gt;            end = len(path) - 1&lt;br /&gt;            path = path[:end]&lt;br /&gt;&lt;br /&gt;        return path + '.%s' % self.lock_postfix&lt;br /&gt;&lt;br /&gt;    def _set_files(self):&lt;br /&gt;        """Resets file lists."""&lt;br /&gt;&lt;br /&gt;        self._files_added = set()&lt;br /&gt;        self._files_removed = set()&lt;br /&gt;        self._dirs_added = set()&lt;br /&gt;        self._dirs_removed = set()&lt;br /&gt;        self._locked_files = set()&lt;br /&gt;&lt;br /&gt;        # Unlike the other types,&lt;br /&gt;        # move operations&lt;br /&gt;        # must be ordered!!&lt;br /&gt;        self._files_moved = []&lt;br /&gt;&lt;br /&gt;    def _check_level(self):&lt;br /&gt;        """Raises exception if level is not 1 or above."""&lt;br /&gt;&lt;br /&gt;        if self._level &lt; 1:&lt;br /&gt;            raise exceptions.TransactionError('Transaction not active.')&lt;br /&gt;&lt;br /&gt;    def _rm(self, file_paths, dir_paths):&lt;br /&gt;        """Remove all files."""&lt;br /&gt;&lt;br /&gt;        for dir_path in dir_paths:&lt;br /&gt;            if os.path.exists(dir_path):&lt;br /&gt;                shutil.rmtree(dir_path)&lt;br /&gt;&lt;br /&gt;        for file_path in file_paths:&lt;br /&gt;            if os.path.exists(file_path):&lt;br /&gt;                os.unlink(file_path)&lt;br /&gt;&lt;br /&gt;    def _rev_moves(self):&lt;br /&gt;        """Reverse moved files."""&lt;br /&gt;&lt;br /&gt;        for move in reversed(self._files_moved):&lt;br /&gt;            shutil.move(move[1], move[0])&lt;br /&gt;&lt;br /&gt;    def _acquire_lock(self, path):&lt;br /&gt;        """Attempt to lock a file."""&lt;br /&gt;&lt;br /&gt;        # Make sure transaction is started&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        if path not in self._locked_files:&lt;br /&gt;            # Create lock file on file system&lt;br /&gt;            lock_path = self._get_lock_path(path)&lt;br /&gt;            if os.path.exists(lock_path):&lt;br /&gt;                # Multi-user access is not allowed!&lt;br /&gt;                raise exceptions.TransactionError('File is locked.')&lt;br /&gt;            out_file = open(lock_path, 'w')&lt;br /&gt;            out_file.write('\n')&lt;br /&gt;            out_file.close()&lt;br /&gt;            self._locked_files.add(path)&lt;br /&gt;&lt;br /&gt;    def _release_lock(self, path):&lt;br /&gt;        """Release a lock file."""&lt;br /&gt;&lt;br /&gt;        lock_path = self._get_lock_path(path)&lt;br /&gt;        if os.path.exists(lock_path):&lt;br /&gt;            os.unlink(lock_path)&lt;br /&gt;        self._locked_files.discard(path)&lt;br /&gt;&lt;br /&gt;    def _release_locks(self):&lt;br /&gt;        """Release all locks."""&lt;br /&gt;&lt;br /&gt;        locked_paths = self._locked_files.copy()&lt;br /&gt;        for path in locked_paths:&lt;br /&gt;            self._release_lock(path)&lt;br /&gt;&lt;br /&gt;    def copy_file(self, src_path, dest_path, remove_existing=False, directory=False):&lt;br /&gt;        """Copy a file. Set remove_existing to True to move file."""&lt;br /&gt;&lt;br /&gt;        if directory is True:&lt;br /&gt;            shutil.copytree(src_path, dest_path, symlinks=True)&lt;br /&gt;        else:&lt;br /&gt;            shutil.copyfile(src_path, dest_path)&lt;br /&gt;        self.add_file(dest_path, directory=directory)&lt;br /&gt;&lt;br /&gt;        if remove_existing is True:&lt;br /&gt;            self.remove_file(src_path, directory=directory)&lt;br /&gt;&lt;br /&gt;    def add_file(self, path, directory=None):&lt;br /&gt;        """Add a file to the transaction."""&lt;br /&gt;&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        self._acquire_lock(path)&lt;br /&gt;&lt;br /&gt;        if directory is None:&lt;br /&gt;            directory = os.path.isdir(path)&lt;br /&gt;&lt;br /&gt;        if directory is True:&lt;br /&gt;            self._dirs_added.add(path)&lt;br /&gt;        else:&lt;br /&gt;            self._files_added.add(path)&lt;br /&gt;&lt;br /&gt;    def remove_file(self, path, directory=None):&lt;br /&gt;        """Remove a file from the transaction."""&lt;br /&gt;&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        self._acquire_lock(path)&lt;br /&gt;&lt;br /&gt;        if directory is None:&lt;br /&gt;            directory = os.path.isdir(path)&lt;br /&gt;&lt;br /&gt;        if directory is True:&lt;br /&gt;            self._dirs_removed.add(path)&lt;br /&gt;        else:&lt;br /&gt;            self._files_removed.add(path)&lt;br /&gt;&lt;br /&gt;    def move_file(self, src_path, dest_path):&lt;br /&gt;        """Move a file from one location to another."""&lt;br /&gt;&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        self._acquire_lock(src_path)&lt;br /&gt;        self._acquire_lock(dest_path)&lt;br /&gt;&lt;br /&gt;        shutil.move(src_path, dest_path)&lt;br /&gt;        self._files_moved.append((src_path, dest_path))&lt;br /&gt;&lt;br /&gt;    def begin(self):&lt;br /&gt;        """Begin transaction."""&lt;br /&gt;&lt;br /&gt;        if self._level == 0:&lt;br /&gt;            self._set_files()&lt;br /&gt;&lt;br /&gt;        self._level += 1&lt;br /&gt;&lt;br /&gt;    def commit(self):&lt;br /&gt;        """Removes all 'removed' files and dirs."""&lt;br /&gt;&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        self._level -= 1&lt;br /&gt;        if self._level == 0:&lt;br /&gt;            self._rm(self._files_removed, self._dirs_removed)&lt;br /&gt;            self._release_locks()&lt;br /&gt;&lt;br /&gt;    def rollback(self):&lt;br /&gt;        """Removes all 'added' files and dirs."""&lt;br /&gt;&lt;br /&gt;        self._check_level()&lt;br /&gt;&lt;br /&gt;        self._level -= 1&lt;br /&gt;        if self._level == 0:&lt;br /&gt;            self._rm(self._files_added, self._dirs_added)&lt;br /&gt;            self._rev_moves()&lt;br /&gt;            self._release_locks()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;def process():&lt;br /&gt;    transaction = Transaction()&lt;br /&gt;    transaction.begin()&lt;br /&gt;    try:&lt;br /&gt;        # Mark a file as created&lt;br /&gt;        transaction.add_file(new_file)&lt;br /&gt;&lt;br /&gt;        # Mark a file as deleted&lt;br /&gt;        transaction.remove_file(delete_file)&lt;br /&gt;&lt;br /&gt;        # Copy a file&lt;br /&gt;        transaction.copy_file(src_file, dest_file)&lt;br /&gt;&lt;br /&gt;        # Move a file&lt;br /&gt;        transaction.move_file(mov_src_file, mov_dest_file)&lt;br /&gt;        transaction.commit()&lt;br /&gt;    except:&lt;br /&gt;        transaction.rollback()&lt;br /&gt;        raise&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Limitations&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The class only works for single user environments. A lock file is created for every file added to a transaction. If a different transaction tries to acquire a lock for a file that is already locked, an exception is raised. Negotiating multi-user access would be quite tricky, especially in the case of delete files, where the file no longer exists after the lock is released.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-89849333748620004?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/89849333748620004/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/09/transactions-for-file-transfer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/89849333748620004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/89849333748620004'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/09/transactions-for-file-transfer.html' title='Transactions for File Transfer'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-7901351619115851106</id><published>2010-06-01T09:49:00.001-07:00</published><updated>2010-06-01T09:51:11.334-07:00</updated><title type='text'>AmFast 0.5.1 Released</title><content type='html'>&lt;a href="http://code.google.com/p/amfast/"&gt;AmFast 0.5.1&lt;/a&gt; has been released. This is a bug-fix release and can be &lt;a href="http://pypi.python.org/pypi/AmFast/0.5.1"&gt;downloaded from PyPi&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-7901351619115851106?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/7901351619115851106/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/06/amfast-051-released.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7901351619115851106'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7901351619115851106'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/06/amfast-051-released.html' title='AmFast 0.5.1 Released'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4483583880007072571</id><published>2010-05-10T22:33:00.001-07:00</published><updated>2010-05-11T11:14:47.217-07:00</updated><title type='text'>Python Workshop</title><content type='html'>It's that time of the year again. Time to learn Python! I will be teaching a Python tutorial May 24-28. The tutorial will cover Python for users with little or no programming experience. Examples are geared toward biologists, but most concepts are applicable to all. Critiques and comments of the &lt;a href="http://ccp.arl.arizona.edu/dthompso/python_workshop/"&gt;workshop tutorial material&lt;/a&gt; are greatly appreciated.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://bcf.arl.arizona.edu/bcfworkshops"&gt;Register for the workshop.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4483583880007072571?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4483583880007072571/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/05/python-workshop.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4483583880007072571'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4483583880007072571'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/05/python-workshop.html' title='Python Workshop'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-2380630418432025203</id><published>2010-04-22T09:39:00.000-07:00</published><updated>2010-08-30T10:56:52.213-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='dojo'/><category scheme='http://www.blogger.com/atom/ns#' term='dojango'/><title type='text'>Django + Dojo</title><content type='html'>When I work on HTML projects, I usually use the &lt;a href="http://www.dojotoolkit.org/"&gt;Dojo Toolkit&lt;/a&gt; for my Javascript needs. Lately I've been spending some time playing around with the Python web framework &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;. I did some internet searching and found &lt;a href="http://code.google.com/p/dojango/"&gt;Dojango&lt;/a&gt;, a project that integrates Django with Dojo. Dojango has features for automatically turning Django form fields into Dijits (Dojo UI widgets), but unfortunately Dojango uses Dojo's custom HTML attributes with Dojo's parseOnLoad option. I prefer to create Dijits programatically so that my markup stays clean. I decided to develop a Django app to meet my needs.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/limscoder/source/browse/#svn/trunk/dojo"&gt;Code is available from SVN&lt;/a&gt;. Instructions are below.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Instructions:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Settings&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;# Setup app in settings.py&lt;br /&gt;&lt;br /&gt;# Required attributes:&lt;br /&gt;&lt;br /&gt;# The URL to get dojo.js from&lt;br /&gt;DOJO_URL = MEDIA_URL + 'js/dojo'&lt;br /&gt;&lt;br /&gt;# Optional attributes:&lt;br /&gt;&lt;br /&gt;# Set to True to add Dojo setup to template&lt;br /&gt;DOJO_ENABLED = True&lt;br /&gt;&lt;br /&gt;# Set Dojo theme&lt;br /&gt;DOJO_THEME = 'tundra' &lt;br /&gt;&lt;br /&gt;# Set the value of djconfig&lt;br /&gt;DOJO_DJCONFIG = {'isDebug': False, 'parseOnLoad': False, &lt;br /&gt;                 'modulePaths': {'app': MEDIA_URL + 'js/app'}}&lt;br /&gt;&lt;br /&gt;# More on this later&lt;br /&gt;DOJO_FORM_FUNCTION = None&lt;br /&gt;&lt;br /&gt;# Attach middleware&lt;br /&gt;MIDDLEWARE_CLASSES = (&lt;br /&gt;    'dojo.middleware.DojoMiddleware',&lt;br /&gt;    ...&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The dojo object&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The middleware attaches a Dojo object to each request. You can access the object from your views, and use it to set Dojo parameters.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;# The dojo object has several useful attributes and methods.&lt;br /&gt;&lt;br /&gt;# The path to dojo.js (from settings.DOJO_URL), read-only&lt;br /&gt;request.dojo.src&lt;br /&gt;&lt;br /&gt;# Theme&lt;br /&gt;request.dojo.theme = 'soria'&lt;br /&gt;&lt;br /&gt;# DjConfig&lt;br /&gt;request.dojo.dj_config['isDebug'] = True&lt;br /&gt;&lt;br /&gt;# Convenience method to set module paths in dj_config&lt;br /&gt;request.dojo.set_module_path('custom', 'url_to_custom_module')&lt;br /&gt;&lt;br /&gt;# Add stylesheets&lt;br /&gt;request.dojo.append_stylesheet('url_to_custom_stylesheet')&lt;br /&gt;&lt;br /&gt;# Require modules&lt;br /&gt;request.dojo.append_module('module.to.require')&lt;br /&gt;&lt;br /&gt;# Set function to addOnLoad&lt;br /&gt;request.dojo.append_aol('function() {do_something();}')&lt;br /&gt;&lt;br /&gt;# Set function to addOnLoad before other already set&lt;br /&gt;request.dojo.prepend_aol('function() {do_something();}'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Forms&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Django forms can easily be 'dijitized'.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;from dojo import models as dojo&lt;br /&gt;&lt;br /&gt;class Register(forms.Form):&lt;br /&gt;    username = forms.RegexField(&lt;br /&gt;        label='Choose a username (letters and numbers only)',&lt;br /&gt;        min_length=2,&lt;br /&gt;        max_length=16,&lt;br /&gt;        regex=r'^[\w]{2,16}$',&lt;br /&gt;        error_messages={&lt;br /&gt;            'invalid': 'Username must be 16 characters or shorter, and can only' &lt;br /&gt;                       ' contain letters, numbers, underscores and dashes.'&lt;br /&gt;         }&lt;br /&gt;    )&lt;br /&gt;&lt;br /&gt;    # Use the dojo_field function to attach dijit&lt;br /&gt;    # parameters to a Django form field.&lt;br /&gt;    dojo.dojo_field(username, 'dijit.form.ValidationTextBox')&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   """&lt;br /&gt;   dojo_field arguments&lt;br /&gt;&lt;br /&gt;   required&lt;br /&gt;   ==========&lt;br /&gt;    * field - Django field to attach dijit parameters to.&lt;br /&gt;    * dojo_type - str, the qualified name of the dijit class to use&lt;br /&gt;&lt;br /&gt;   keyword&lt;br /&gt;   =========&lt;br /&gt;    * attr_map - dict, Used to map Django field parameters to Dijit parameters.&lt;br /&gt;                 Overrides the default values in dojo.models.default_attr_map.&lt;br /&gt;                 The dict elements should be structured as follows:&lt;br /&gt;&lt;br /&gt;                 Key == Django attribute name&lt;br /&gt;                 Value == tuple with elements:&lt;br /&gt;                     [0] == Dijit attribute name to map to&lt;br /&gt;                     [1] == None, or callable to convert Django value to Dijit value&lt;br /&gt;&lt;br /&gt;                 EXAMPLE:&lt;br /&gt;                 {&lt;br /&gt;                     'max_length': ('maxLength', None),&lt;br /&gt;                     'regex': ('regExp', lambda a: '%s' % a.pattern)&lt;br /&gt;                 }&lt;br /&gt;    * dojo_attrs - dict, Attributes will be applied directly to dijit.&lt;br /&gt;                   Key == dojo attribute name&lt;br /&gt;                   Value == dojo attribute value&lt;br /&gt;   """&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;After creating a form, it must be instrumented to create the Javascript required to create the dijits.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&lt;br /&gt;def my_view(request):&lt;br /&gt;&lt;br /&gt;    my_form = Register()&lt;br /&gt;&lt;br /&gt;    # This call generates all the necessary Javascript code&lt;br /&gt;    request.dojo.dojo_form(my_form)&lt;br /&gt;&lt;br /&gt;    # By default, the function code generated&lt;br /&gt;    # is a string to be added in-line.&lt;br /&gt;    #&lt;br /&gt;    # If you prefer to call a pre-defined JS function,&lt;br /&gt;    # just set the request.dojo.form_function attribute.&lt;br /&gt;    #&lt;br /&gt;    # The value of the attribute should be a tuple where:&lt;br /&gt;    # [0] == qualified Dojo module name where function exists&lt;br /&gt;    # [1] == function name&lt;br /&gt;    #&lt;br /&gt;    # request.dojo.form_function can also be set automatically&lt;br /&gt;    # by setting DOJO_FORM_FUNCTION in settings.py&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Template&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Include the following tags within the 'head' tag of your HTML template:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: html"&gt;&lt;br /&gt;{% load dojo %}&lt;br /&gt;{% dojo request.dojo %}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Dojo app also includes a script for creating a Dojo build:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;python manage.py dojo_build&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-2380630418432025203?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/2380630418432025203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/04/django-dojo.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/2380630418432025203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/2380630418432025203'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/04/django-dojo.html' title='Django + Dojo'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-3650905568231004180</id><published>2010-03-06T08:35:00.000-08:00</published><updated>2010-03-06T10:57:05.535-08:00</updated><title type='text'>UI Design</title><content type='html'>In the world of scientific software, UIs tend to be horrifically awful. When we started working on our next generation LIMS (called SLM: Sample Lifecycle Manager), one of our goals was to provide an intuitive UI. This post covers some of what we went through on the UI design front.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;First Try&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;After evaluating several Javascript libraries, we decided that Flex would be a good choice, because it would be easy to create a desktop-like application to manage the large feature set. The main screen for our 1st alpha release looked something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_66-WgdRymuc/S5Kg6dzEwQI/AAAAAAAACiQ/oft99iQ0ZGQ/s1600-h/slm_pic_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 233px;" src="http://2.bp.blogspot.com/_66-WgdRymuc/S5Kg6dzEwQI/AAAAAAAACiQ/oft99iQ0ZGQ/s400/slm_pic_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445591825736057090" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The design is similar to a complex desktop app such as Thunderbird or Eclipse. The app has a menu bar, a left panel with data/navigation, a bottom panel with several features, and a main tabbed panel with content.&lt;br /&gt;&lt;br /&gt;Our users hated it. We quickly learned that our users don't want desktop-style apps with many features spread across multiple panels. Instead, they are looking for web-style apps where each function of the application can be performed on a single, simple screen, and links are used to navigate between feature screens.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Second Try&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Our revised UI eliminated the menu bar. The left and bottom panels are still available for power users, but they are now hidden by default. These simple changes dramatically improved the user's acceptance of the app.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhE9AqPuI/AAAAAAAACiY/AoNIpKu-r8g/s1600-h/slm_pic_2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 232px;" src="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhE9AqPuI/AAAAAAAACiY/AoNIpKu-r8g/s400/slm_pic_2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445592005913231074" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Forms&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Flex's form system is great for building complex forms (as long as you can live with the standard theme). Flex has built-in validator classes that can provide real-time visual feed back to users as they enter information into the form. It is very important to customize your validators to provide useful messages to your users, so they know how to correct their input.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhOZGQjjI/AAAAAAAACig/jAAf802hzqc/s1600-h/validator_message.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 147px;" src="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhOZGQjjI/AAAAAAAACig/jAAf802hzqc/s400/validator_message.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445592168071728690" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Always make sure your forms are fully navigable by keyboard, and make sure that by default, the form will be submitted when the user hits 'Enter'.&lt;br /&gt;&lt;br /&gt;SLM has many features, and some of the forms rely on optionally selected fields. For example, if a user selects an option from a drop-down list, then other fields in the form become enabled or disabled. Many desktop-style apps 'grey-out' the disabled fields (this can be done using Flex UIComponent's 'enabled' property), but we found that un-usable fields that remain visible can be confusing for users. Instead, adding or removing the optional fields feels much more intuitive.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhT6B26aI/AAAAAAAACio/ljvx9u6ZMY0/s1600-h/form_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 314px; height: 164px;" src="http://2.bp.blogspot.com/_66-WgdRymuc/S5KhT6B26aI/AAAAAAAACio/ljvx9u6ZMY0/s400/form_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445592262810986914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_66-WgdRymuc/S5Khav_6W_I/AAAAAAAACiw/N_K6MLUNnJg/s1600-h/form_2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 185px;" src="http://1.bp.blogspot.com/_66-WgdRymuc/S5Khav_6W_I/AAAAAAAACiw/N_K6MLUNnJg/s400/form_2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445592380377553906" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Visual Cues&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Animations are not just eye-candy, they can also help users navigate through the system. For example, SLM has several drag-and-drop features. At first, some users were unsure about which items could be dragged into a drop-zone. To fix this problem we added a simple glow animation to highlight draggable items whenever the user's mouse is hovering over the drop-zone.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Trust&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Customers use SLM to submit samples for testing at our facility. The submission process is similar to a shopping cart check-out on a retail site. It is extremely important to allow a user the ability to move forward and backward through the process. User's don't feel confident unless they trust the system to move back and forth between steps without munging any data they've already entered. If a user needs to change an option in step 2, it shouldn't affect the data they've already entered in step 3. Users must also be confident that nothing will be stored on the server until the final submit step.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_66-WgdRymuc/S5KhhK6Pn7I/AAAAAAAACi4/7hy5wrxv1ks/s1600-h/slm_submit.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 218px;" src="http://4.bp.blogspot.com/_66-WgdRymuc/S5KhhK6Pn7I/AAAAAAAACi4/7hy5wrxv1ks/s400/slm_submit.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5445592490680754098" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;For single step operations, the UI must always provide a 'Cancel' option or some other way for a user to back-out of the changes they've made.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-3650905568231004180?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/3650905568231004180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2010/03/ui-design.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3650905568231004180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3650905568231004180'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2010/03/ui-design.html' title='UI Design'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_66-WgdRymuc/S5Kg6dzEwQI/AAAAAAAACiQ/oft99iQ0ZGQ/s72-c/slm_pic_1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-400071871535212919</id><published>2009-11-24T15:16:00.000-08:00</published><updated>2009-12-13T20:19:35.803-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='optimizing'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='profiling'/><title type='text'>Profiling and Optimizing Python Code</title><content type='html'>All programmers have heard the advice: "Don't prematurely optimize code." What exactly does that mean? It means that you shouldn't guess about what is causing your code to run slowly. Chances are you'll guess wrong. Instead of guessing, use profiling and benchmarking tools to quickly and accurately identify the performance bottlenecks in your scripts.&lt;br /&gt;&lt;br /&gt;Every now and then I run into a piece of Python code that just doesn't run as fast as I would like. I use 3 different Python tools to find and fix Python performance problems.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://docs.python.org/library/profile.html"&gt;cProfile&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;cProfile is a module that is included in the Python Standard Library. It logs function calls and execution times. There is also a pure-python version named 'profile'.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://kcachegrind.sourceforge.net/html/Home.html"&gt;KCachegrind&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;KCachegrind was written to visualize the output generated by Callgrind (a C profiler), but the function call logs from cProfile can be converted to the KCachegrind format with &lt;a href="http://www.gnome.org/~johan/lsprofcalltree.py"&gt;this script&lt;/a&gt;. KCachegrind uses the KDE framework. On Linux boxes, KCachegrind is usually bundled in a package with other development tools written for KDE. The package is named 'kdesdk' or something similar. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://docs.python.org/library/timeit.html"&gt;timeit&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;timeit is a module included in the Python Standard Library that is used for measuring the execution time of arbitrary pieces of code.&lt;br /&gt;&lt;br /&gt;Here is the code that needs to be improved:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;class LameStringBuilder(object):&lt;br /&gt;    def __init__(self, cols=40, rows=10000):&lt;br /&gt;        self.cols = cols&lt;br /&gt;        self.rows = rows&lt;br /&gt;&lt;br /&gt;    def build(self, val):&lt;br /&gt;        built = ''&lt;br /&gt;        for i in range(self.rows):&lt;br /&gt;            built += self.build_row(val) + "\n"&lt;br /&gt;        return built&lt;br /&gt;&lt;br /&gt;    def build_row(self, val):&lt;br /&gt;        built = ''&lt;br /&gt;        for i in range(self.cols - 1):&lt;br /&gt;            built += val + ','&lt;br /&gt;        built += val&lt;br /&gt;        return built&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is the code to execute a profile test and format the results into something that KCachegrind can understand:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;import cProfile&lt;br /&gt;&lt;br /&gt;# Use this module to convert profile data&lt;br /&gt;# into the KCacheGrind format.&lt;br /&gt;#&lt;br /&gt;# The module is available here:&lt;br /&gt;# http://www.gnome.org/~johan/lsprofcalltree.py&lt;br /&gt;import lsprofcalltree&lt;br /&gt;&lt;br /&gt;import lame_string_builder&lt;br /&gt;&lt;br /&gt;def test():&lt;br /&gt;    """Code to profile."""&lt;br /&gt;    l = lame_string_builder.LameStringBuilder(40, 10000)&lt;br /&gt;    l.build('foo')&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    # Profile function&lt;br /&gt;    p = cProfile.Profile()&lt;br /&gt;    p.run('test()');&lt;br /&gt;&lt;br /&gt;    # Get profile log in KCacheGrind format&lt;br /&gt;    # and dump to file.&lt;br /&gt;    k = lsprofcalltree.KCacheGrind(p)&lt;br /&gt;    out = open('lame_profile.kgrind', 'w')&lt;br /&gt;    k.output(out)&lt;br /&gt;    out.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;After running the test, launch KCachegrid and open the profile file to view the results.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_66-WgdRymuc/SxRUOUV6MeI/AAAAAAAACgk/s70vT5xSXeg/s1600/kcachgrind_example.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 256px;" src="http://2.bp.blogspot.com/_66-WgdRymuc/SxRUOUV6MeI/AAAAAAAACgk/s70vT5xSXeg/s400/kcachgrind_example.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5410041657334313442" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The 'Flat Profile' and 'Callee Map' are the 2 most useful displays for finding the bottle necks in your code.&lt;br /&gt;&lt;br /&gt;The 'Flat Profile' describes each function called in the test with the following columns:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Incl. - The total percentage of time spent within a function.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Self - The percentage of time spent within a function NOT including inner function calls.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Called - The total number of times the function was called during the test.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Function - The name of the function being described.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The 'Callee Map' represents the different functions called during the test. Each function is drawn as a rectangle. Inner function calls are drawn on top of their parent function's rectangle. The area used to draw each function is proportional to the execution time for each function call relative to the total execution time of the parent (also printed as a percentage in the graph).&lt;br /&gt;&lt;br /&gt;In the example above it is easy to pick out where the bottle necks are by looking at the 'Callee Map'. Most of the test time was spent within the 'LameStringBuilder.build_row' method, so it is the main bottle neck. Other significant sources of time include calls to 'LameStringBuilder.build' and the built-in function 'range'.&lt;br /&gt;&lt;br /&gt;After identifying the bottlenecks, I created a better version of the 'LameStringBuilder' class:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;import lame_string_builder&lt;br /&gt;&lt;br /&gt;class LessLameStringBuilder(lame_string_builder.LameStringBuilder):&lt;br /&gt;    def build(self, val):&lt;br /&gt;        return "\n".join([self.build_row(val) for i in xrange(self.rows)])&lt;br /&gt;&lt;br /&gt;    def build_row(self, val):&lt;br /&gt;        return ",".join([val for i in xrange(self.cols)])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;After making changes to the code, Python's built-in 'timeit' module can be used to simply and accurately measure the differences in execution speed between the old and the new versions:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;import timeit&lt;br /&gt;&lt;br /&gt;import lame_string_builder&lt;br /&gt;import less_lame_string_builder&lt;br /&gt;&lt;br /&gt;def test_lame(val, cols, rows):&lt;br /&gt;    l = lame_string_builder.LameStringBuilder(cols, rows)&lt;br /&gt;    l.build(val)&lt;br /&gt;&lt;br /&gt;def test_less_lame(val, cols, rows):&lt;br /&gt;    l = less_lame_string_builder.LessLameStringBuilder(cols, rows)&lt;br /&gt;    l.build(val)&lt;br /&gt;&lt;br /&gt;def test_generic(repeat, name, args):&lt;br /&gt;    print "%s (x%i):" % (name, repeat)&lt;br /&gt;    t = timeit.Timer("%s(%s)" % (name, args), "from __main__ import %s" % name)&lt;br /&gt;    print t.timeit(repeat)&lt;br /&gt;&lt;br /&gt;def test_all(repeat, val, cols, rows):&lt;br /&gt;    args = "'%s', %i, %i" % (val, cols, rows)   &lt;br /&gt;    &lt;br /&gt;    for name in ("test_lame", "test_less_lame"):&lt;br /&gt;         test_generic(repeat, name, args)&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    test_all(100, 'foo', 40, 10000)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_66-WgdRymuc/SxRUdGhcUUI/AAAAAAAACgs/nm5PL55HGEE/s1600/timer_example.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 69px;" src="http://3.bp.blogspot.com/_66-WgdRymuc/SxRUdGhcUUI/AAAAAAAACgs/nm5PL55HGEE/s400/timer_example.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5410041911322628418" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The new code is significantly faster!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-400071871535212919?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/400071871535212919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/11/profiling-and-optimizing-python-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/400071871535212919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/400071871535212919'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/11/profiling-and-optimizing-python-code.html' title='Profiling and Optimizing Python Code'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_66-WgdRymuc/SxRUOUV6MeI/AAAAAAAACgk/s70vT5xSXeg/s72-c/kcachgrind_example.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-1496499947050263909</id><published>2009-11-02T17:30:00.000-08:00</published><updated>2009-11-02T18:41:24.277-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='access'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='role'/><title type='text'>Role based security with Python</title><content type='html'>Most moderately complex enterprise applications require some sort of role based security and access control. For example, an employee should have access to fill-out her time card, but a manager should also be able to approve the time card. This post outlines how to create an 'access control list' (acl) for Python.&lt;br /&gt;&lt;br /&gt;The acl is an object that can be queried to determine if a particular role has permission to access a resource. Each permission is also associated with a 'privilege'. For example, a manager may have both the 'read' and 'write' privileges for the weekly schedule, but an employee only has the 'read' privilege.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;import acl&lt;br /&gt;&lt;br /&gt;# Each user name should be associated with a role.&lt;br /&gt;role = get_role(username)&lt;br /&gt;&lt;br /&gt;# Query the acl to see if a role has access to a resource.&lt;br /&gt;# params: role name, resource, privilege&lt;br /&gt;acl = acl.Acl()&lt;br /&gt;if not acl.check_access(role, 'time_card', 'write'):&lt;br /&gt;    # User doesn't have access to this resource!!&lt;br /&gt;    pass&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Acl class supports role inheritance. If the 'manager' role inherits the 'employee' role, then managers will have all of the same permissions as employees.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;# Build acl list&lt;br /&gt;acl = acl.Acl()&lt;br /&gt;&lt;br /&gt;employee = acl.Role('employee')&lt;br /&gt;resource = acl.Resource('time_card')&lt;br /&gt;resource.set_privilege('write')&lt;br /&gt;employee.set_resource(resource)&lt;br /&gt;acl.set_role(employee)&lt;br /&gt;&lt;br /&gt;manager = acl.Role('manager')&lt;br /&gt;manager.set_parent(employee)&lt;br /&gt;resource = acl.Resource('time_card')&lt;br /&gt;resource.set_privilege('approve')&lt;br /&gt;manager.set_resource(resource)&lt;br /&gt;acl.set_role(manager)&lt;br /&gt;&lt;br /&gt;if acl.check_access('manager', 'time_card', 'write'):&lt;br /&gt;    # YES!&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;if acl.check_acces('manager', 'time_card', 'approve'):&lt;br /&gt;    # YES!&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;if acl.check_access('employee', 'time_card', 'write'):&lt;br /&gt;    # YES !&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;if acl.check_access('employess', 'time_card', 'approve');&lt;br /&gt;    # NO!&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm normally not a very big fan of XML, but in this particular case it is a good format to use if you prefer to store your acl in a configuration file. The Acl object's 'build_acl' method populates the object from a XML file with the following format:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;- This file configures the roles (user groups)&lt;br /&gt;- and permissions for accessing the system.&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;config&amp;gt;&lt;br /&gt;    &amp;lt;!--&lt;br /&gt;    - Setup roles here.&lt;br /&gt;    - Use the 'inheritFrom' tag to&lt;br /&gt;    - inherit permissions from another role.&lt;br /&gt;    --&amp;gt;&lt;br /&gt;    &amp;lt;roleSet&amp;gt;   &lt;br /&gt;        &amp;lt;role&amp;gt;&lt;br /&gt;            &amp;lt;name&amp;gt;customer&amp;lt;/name&amp;gt;&lt;br /&gt;        &amp;lt;/role&amp;gt;&lt;br /&gt;        &amp;lt;role&amp;gt;&lt;br /&gt;            &amp;lt;name&amp;gt;employee&amp;lt;/name&amp;gt;&lt;br /&gt;            &amp;lt;inheritFrom&amp;gt;customer&amp;lt;/inheritFrom&amp;gt;&lt;br /&gt;        &amp;lt;/role&amp;gt;&lt;br /&gt;        &amp;lt;role&amp;gt;&lt;br /&gt;            &amp;lt;name&amp;gt;manager&amp;lt;/name&amp;gt;&lt;br /&gt;            &amp;lt;inheritFrom&amp;gt;employee&amp;lt;/inheritFrom&amp;gt;&lt;br /&gt;        &amp;lt;/role&amp;gt;&lt;br /&gt;    &amp;lt;/roleSet&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!--&lt;br /&gt;    - Set permissions for accessing application components here.&lt;br /&gt;    - resource -&amp;gt; property being access controlled.&lt;br /&gt;    - role -&amp;gt; group or user that can access resource.&lt;br /&gt;    - privilege -&amp;gt; privilege that role can use with resource.&lt;br /&gt;    -&lt;br /&gt;    - Each permission tag can contain multiple&lt;br /&gt;    - resources, roles, and privileges.&lt;br /&gt;    --&amp;gt;&lt;br /&gt;    &amp;lt;permissions&amp;gt;&lt;br /&gt;        &amp;lt;permission&amp;gt;&lt;br /&gt;            &amp;lt;resources&amp;gt;&lt;br /&gt;                &amp;lt;resource&amp;gt;contact_details&amp;lt;/resource&amp;gt;&lt;br /&gt;                &amp;lt;resource&amp;gt;profile&amp;lt;/resource&amp;gt;&lt;br /&gt;            &amp;lt;/resources&amp;gt;&lt;br /&gt;            &amp;lt;roles&amp;gt;&lt;br /&gt;                &amp;lt;role&amp;gt;customer&amp;lt;/role&amp;gt;&lt;br /&gt;            &amp;lt;/roles&amp;gt;&lt;br /&gt;            &amp;lt;privileges&amp;gt;&lt;br /&gt;                &amp;lt;privilege&amp;gt;read&amp;lt;/privilege&amp;gt;&lt;br /&gt;                &amp;lt;privilege&amp;gt;write&amp;lt;/privilege&amp;gt;&lt;br /&gt;            &amp;lt;/privileges&amp;gt;&lt;br /&gt;        &amp;lt;/permission&amp;gt;&lt;br /&gt;&lt;br /&gt;        &amp;lt;permission&amp;gt;&lt;br /&gt;            &amp;lt;resources&amp;gt;&lt;br /&gt;                &amp;lt;resource&amp;gt;time_card&amp;lt;/resource&amp;gt;&lt;br /&gt;            &amp;lt;/resources&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;roles&amp;gt;&lt;br /&gt;                &amp;lt;role&amp;gt;employee&amp;lt;/role&amp;gt;&lt;br /&gt;            &amp;lt;/roles&amp;gt;&lt;br /&gt;            &amp;lt;privileges&amp;gt;&lt;br /&gt;                &amp;lt;privilege&amp;gt;read&amp;lt;/privilege&amp;gt;&lt;br /&gt;                &amp;lt;privilege&amp;gt;write&amp;lt;/privilege&amp;gt;&lt;br /&gt;            &amp;lt;/privileges&amp;gt;&lt;br /&gt;        &amp;lt;/permission&amp;gt;&lt;br /&gt;        &lt;br /&gt;        &amp;lt;permission&amp;gt;&lt;br /&gt;            &amp;lt;resources&amp;gt;&lt;br /&gt;                &amp;lt;resource&amp;gt;time_card&amp;lt;/resource&amp;gt;&lt;br /&gt;            &amp;lt;/resources&amp;gt;&lt;br /&gt;            &amp;lt;roles&amp;gt;&lt;br /&gt;                &amp;lt;role&amp;gt;manager&amp;lt;/role&amp;gt;&lt;br /&gt;            &amp;lt;/roles&amp;gt;&lt;br /&gt;            &amp;lt;privileges&amp;gt;&lt;br /&gt;                &amp;lt;privilege&amp;gt;approve&amp;lt;/privilege&amp;gt;&lt;br /&gt;            &amp;lt;/privileges&amp;gt;&lt;br /&gt;        &amp;lt;/permission&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;/permissions&amp;gt;&lt;br /&gt;&amp;lt;/config&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is the code for the acl.py module:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;"""Role based security"""&lt;br /&gt;from xml.dom.minidom import parse&lt;br /&gt;&lt;br /&gt;class AccessError(Exception):&lt;br /&gt;    pass&lt;br /&gt;&lt;br /&gt;class Resource(object):&lt;br /&gt;    """An Resource is an object that can be accessed by a Role."""&lt;br /&gt;    def __init__(self, name=''):&lt;br /&gt;        self.name = name&lt;br /&gt;        self._privileges = {}&lt;br /&gt;&lt;br /&gt;    def set_privilege(self, privilege, allowed=True):&lt;br /&gt;        self._privileges[privilege] = allowed&lt;br /&gt;&lt;br /&gt;    def has_access(self, privilege):&lt;br /&gt;        if privilege in self._privileges:&lt;br /&gt;            return self._privileges[privilege]&lt;br /&gt;        return False&lt;br /&gt;    &lt;br /&gt;    def __str__(self):&lt;br /&gt;        rpr = self.name + ': '&lt;br /&gt;        for privilege, access in self._privileges.iteritems():&lt;br /&gt;            rpr += "%s:%s " % (privilege, access)&lt;br /&gt;        return rpr&lt;br /&gt;&lt;br /&gt;class Role(object):&lt;br /&gt;    def __init__(self, name=''):&lt;br /&gt;        """An Acl role has access to resources with specific privileges."""&lt;br /&gt;        self.name = name&lt;br /&gt;        self._parents = {}&lt;br /&gt;        self._resources = {}&lt;br /&gt;&lt;br /&gt;    def set_parent(self, parent):&lt;br /&gt;        self._parents[parent.name] = parent&lt;br /&gt;&lt;br /&gt;    def set_resource(self, resource):&lt;br /&gt;        self._resources[resource.name] = resource&lt;br /&gt;&lt;br /&gt;    def has_access(self, attr_name, privilege):&lt;br /&gt;        if attr_name in self._resources:&lt;br /&gt;            if self._resources[attr_name].has_access(privilege):&lt;br /&gt;                return True&lt;br /&gt;&lt;br /&gt;        for parent in self._parents.values():&lt;br /&gt;            if parent.has_access(attr_name, privilege):&lt;br /&gt;                return True&lt;br /&gt;&lt;br /&gt;        return False&lt;br /&gt;    &lt;br /&gt;    def __str__(self):&lt;br /&gt;        rpr = self.name + ":\n"&lt;br /&gt;        rpr += "parents:\n"&lt;br /&gt;        for parent in self._parents.keys():&lt;br /&gt;            rpr += "\t%s\n" % parent&lt;br /&gt;        rpr += "resources:\n"&lt;br /&gt;        for resource in self._resources.values():&lt;br /&gt;            rpr += "\t%s\n" % resource.describe()&lt;br /&gt;        return rpr&lt;br /&gt;&lt;br /&gt;class Acl(object):&lt;br /&gt;    """Manages roles and resources.&lt;br /&gt;    &lt;br /&gt;    Singleton class.&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;    class __impl:&lt;br /&gt;        """Implementation of the singleton interface"""&lt;br /&gt;        &lt;br /&gt;        def __init__(self):&lt;br /&gt;            self._acl = {}&lt;br /&gt;&lt;br /&gt;        def set_role(self, role):&lt;br /&gt;            self._acl[role.name] = role&lt;br /&gt;            &lt;br /&gt;        def check_access(self, role_name, resource, privilege):&lt;br /&gt;            """Check whether a role has access to a resource or not."""&lt;br /&gt;&lt;br /&gt;            if not role_name in self._acl:&lt;br /&gt;                raise AccessError('Role does not exist.')&lt;br /&gt;            return self._acl[role_name].has_access(resource, privilege)&lt;br /&gt;&lt;br /&gt;        def build_acl(self, file): &lt;br /&gt;            """Build acl from an XML file."""&lt;br /&gt;&lt;br /&gt;            self._acl = {}&lt;br /&gt;            roles_to_create = {}&lt;br /&gt;            dom = parse(file)&lt;br /&gt;            &lt;br /&gt;            # Find roles to create&lt;br /&gt;            roles_nodes = dom.getElementsByTagName('roleSet')&lt;br /&gt;            for roles_node in roles_nodes:&lt;br /&gt;                role_nodes = roles_node.getElementsByTagName('role')&lt;br /&gt;                for role_node in role_nodes:&lt;br /&gt;                    name_nodes = role_node.getElementsByTagName('name')&lt;br /&gt;                    parent_nodes = role_node.getElementsByTagName('inheritFrom')&lt;br /&gt;                    role_name = name_nodes[0].childNodes[0].data&lt;br /&gt;                    roles_to_create[role_name] = []&lt;br /&gt;&lt;br /&gt;                    # Find role parents&lt;br /&gt;                    for parent_node in parent_nodes:&lt;br /&gt;                        roles_to_create[role_name].append(parent_node.childNodes[0].data)&lt;br /&gt;&lt;br /&gt;            # build inheritence chain&lt;br /&gt;            for role, parents in roles_to_create.iteritems():&lt;br /&gt;                self.set_role(self._create_role(role, roles_to_create))&lt;br /&gt;&lt;br /&gt;            # assign permissions&lt;br /&gt;            permissions = dom.getElementsByTagName('permissions')&lt;br /&gt;            for permissions_node in permissions:&lt;br /&gt;                permission_nodes = permissions_node.getElementsByTagName('permission')&lt;br /&gt;                for permission_node in permission_nodes:&lt;br /&gt;                    resource_nodes = permission_node.getElementsByTagName('resource')&lt;br /&gt;                    role_nodes = permission_node.getElementsByTagName('role')&lt;br /&gt;                    privilege_nodes = permission_node.getElementsByTagName('privilege')&lt;br /&gt;&lt;br /&gt;                    for resource_node in resource_nodes:&lt;br /&gt;                       resource = Resource()&lt;br /&gt;                       resource.name = resource_node.childNodes[0].data&lt;br /&gt;                       for privilege_node in privilege_nodes:&lt;br /&gt;                           resource.set_privilege(privilege_node.childNodes[0].data)&lt;br /&gt;&lt;br /&gt;                       for role_node in role_nodes:&lt;br /&gt;                           try:&lt;br /&gt;                               role = self._acl[role_node.childNodes[0].data]&lt;br /&gt;                           except:&lt;br /&gt;                               raise AccessError('Role in permission is not defined.')&lt;br /&gt;&lt;br /&gt;                           role.set_resource(resource)&lt;br /&gt;&lt;br /&gt;        def _create_role(self, role_name, roles_to_create):&lt;br /&gt;            """Recursively create parent roles and then create child role."""&lt;br /&gt;            &lt;br /&gt;            if role_name in self._acl:&lt;br /&gt;                role = self._acl[role_name]&lt;br /&gt;            else:&lt;br /&gt;                role = Role()&lt;br /&gt;                role.name = role_name&lt;br /&gt;                &lt;br /&gt;            for parent_name in roles_to_create[role_name]:&lt;br /&gt;                if parent_name in self._acl:&lt;br /&gt;                    parent = self._acl[parent_name]&lt;br /&gt;                else:&lt;br /&gt;                    parent = self._create_role(parent_name, roles_to_create)&lt;br /&gt;                    self.set_role(parent)&lt;br /&gt;                role.set_parent(parent)&lt;br /&gt;            return role&lt;br /&gt;        &lt;br /&gt;        def __str__(self):&lt;br /&gt;            rpr = ''&lt;br /&gt;            for role in self._acl.values():&lt;br /&gt;                rpr += '----------\n'&lt;br /&gt;                rpr += role.describe()&lt;br /&gt;            return rpr&lt;br /&gt;&lt;br /&gt;    __instance = None&lt;br /&gt;&lt;br /&gt;    def __init__(self):&lt;br /&gt;        """ Create singleton instance """&lt;br /&gt;        &lt;br /&gt;        # Check whether an instance already exists.&lt;br /&gt;        # If not, create it.&lt;br /&gt;        if Acl.__instance is None:&lt;br /&gt;            Acl.__instance = Acl.__impl()&lt;br /&gt;&lt;br /&gt;        self.__dict__['_Acl__instance'] = Acl.__instance&lt;br /&gt;&lt;br /&gt;    def __getattr__(self, attr):&lt;br /&gt;        """ Delegate get access to implementation """&lt;br /&gt;&lt;br /&gt;        return getattr(self.__instance, attr)&lt;br /&gt;&lt;br /&gt;    def __setattr__(self, attr, val):&lt;br /&gt;        """ Delegate set access to implementation """&lt;br /&gt;&lt;br /&gt;        return setattr(self.__instance, attr, val)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-1496499947050263909?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/1496499947050263909/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/11/role-based-security-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1496499947050263909'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1496499947050263909'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/11/role-based-security-with-python.html' title='Role based security with Python'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-5962736359917265758</id><published>2009-10-19T10:35:00.000-07:00</published><updated>2009-10-19T10:51:50.627-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='MySql'/><category scheme='http://www.blogger.com/atom/ns#' term='SQLite'/><category scheme='http://www.blogger.com/atom/ns#' term='bioinformatics'/><title type='text'>SQL Workshop</title><content type='html'>I've recently finished a &lt;a href="http://ccp.arl.arizona.edu/dthompso/sql_workshop/"&gt;SQL tutorial&lt;/a&gt; for a relational database workshop I'll be teaching. The tutorial covers basic SQL queries with SQLite and MySql, as well as basic database creation with SQLite. The tutorial is very basic, and the examples are geared toward biologists who need to use a database for performing research.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-5962736359917265758?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/5962736359917265758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/10/sql-workshop.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5962736359917265758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5962736359917265758'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/10/sql-workshop.html' title='SQL Workshop'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4368169838611657739</id><published>2009-09-20T22:16:00.000-07:00</published><updated>2009-09-20T23:13:22.415-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tornado'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Facebook's Tornado</title><content type='html'>Today I got my first chance to play with Facebook's Python based web server and framework &lt;a href="http://www.tornadoweb.org/"&gt;Tornado&lt;/a&gt;. The framework is single threaded and asynchronous, similar to how the &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt; framework operates, and Facebook uses the technology to provide data to the 'Friend Feed'.&lt;br /&gt;&lt;br /&gt;Python thread creation and maintenance have relatively high overhead, so asynchronous solutions can provide better performance and scalability than more traditional threaded server implementations. This is especially true for servers with high numbers of concurrent connections, and for &lt;a href="http://en.wikipedia.org/wiki/Push_technology#Long_polling"&gt;long-polling&lt;/a&gt; and streaming connections that spend most of their time waiting around for a message to be published to a client. &lt;a href="http://www.tornadoweb.org/documentation#performance"&gt;Tornado's benchmarks&lt;/a&gt; look impressive against threaded Python web servers, but I wonder why they didn't include Twisted, their closest competitor, in the benchmarking.&lt;br /&gt;&lt;br /&gt;My first impression is that Tornado is much easier to use than Twisted, but also much less powerful. Twisted is a monolithic package that can be used with many different networking protocols, but Tornado is focused only on http. Tornado provides built in support for templating, authentication, and signed cookies. One really cool feature Tornado provides is semi-automatic &lt;a href="http://en.wikipedia.org/wiki/Cross_site_request_forgery"&gt;XSRF&lt;/a&gt; protection, which should save time for developers who would otherwise need to manually implement countermeasures.&lt;br /&gt;&lt;br /&gt;The documentation for Tornado is very slim. For example, I couldn't find any information about how to interact directly with Tornado's main event loop (IOLoop). Fortunately, Tornado's code base is small and readable, so reading the source will quickly get you up to speed.&lt;br /&gt;&lt;br /&gt;While implementing a Tornado channel for the &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; project, I ran into a problem that I have also encountered with Twisted. Both Tornado and Twisted use callbacks to complete a request. As an example, if a long-poll client is waiting for a message, you call RequestHandler.finish() to send a response back to the client when a message is published.&lt;br /&gt;&lt;br /&gt;The above method works well for a single server instance, but what about when you have multiple servers behind a proxy? A server process may publish a message for a client that is connected to an entirely different server process. The publishing process has no way to notify the subscribing process that a client has received a message. &lt;br /&gt;&lt;br /&gt;This problem can be solved by polling a database table or a file that is accessible to all server processes, but that takes system resources, especially if you have many clients and your polling interval is short. One of my future goals is to figure out a more elegant way for the publishing server process to notify the subscribing server process when a client receives a message.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4368169838611657739?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4368169838611657739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/09/facebooks-tornado.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4368169838611657739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4368169838611657739'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/09/facebooks-tornado.html' title='Facebook&apos;s Tornado'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-1027054292458663606</id><published>2009-08-01T13:08:00.001-07:00</published><updated>2009-08-01T13:14:35.989-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='django'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><category scheme='http://www.blogger.com/atom/ns#' term='gae'/><title type='text'>AmFast adds support for Google App Engine and Django</title><content type='html'>I just released version 0.4.0 beta of &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt;, a Flash remoting package for Python. The new release includes support for Google App Engine and the Django framework.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/amfast/source/browse/#svn/tags/0.4.0b"&gt;Browse the code here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://amfastchat.appspot.com/flash/chat.html"&gt;&lt;br /&gt;Check out the live Google App Engine demo.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;To get the code:&lt;br /&gt;&lt;pre class="brush: bash"&gt;svn checkout https://amfast.googlecode.com/svn/tags/0.4.0b&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-1027054292458663606?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/1027054292458663606/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/08/amfast-adds-support-for-google-app.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1027054292458663606'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1027054292458663606'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/08/amfast-adds-support-for-google-app.html' title='AmFast adds support for Google App Engine and Django'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-656459228535552742</id><published>2009-06-20T09:53:00.000-07:00</published><updated>2009-06-20T09:59:46.678-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bioinformatics'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='sphinx'/><title type='text'>YAPT (Yet Another Python Tutorial)</title><content type='html'>The tutorial I created for the Python workshop is &lt;a href="http://ccp.arl.arizona.edu/dthompso/python_workshop"&gt;posted here&lt;/a&gt;. The tutorial is geared towards bioinformatics users, and includes molecular biology related exercises.&lt;br /&gt;&lt;br /&gt;I created the tutorial with the &lt;a href="http://sphinx.pocoo.org/"&gt;Sphinx documentation package&lt;/a&gt;, which turned out to be the perfect tool for the job.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-656459228535552742?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/656459228535552742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/06/yapt-yet-another-python-tutorial.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/656459228535552742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/656459228535552742'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/06/yapt-yet-another-python-tutorial.html' title='YAPT (Yet Another Python Tutorial)'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6150371141502062714</id><published>2009-05-29T10:45:00.000-07:00</published><updated>2009-05-29T10:49:05.290-07:00</updated><title type='text'>Learn Python Workshop</title><content type='html'>I'm going to be teaching an intro to Python workshop next month. It will consist of 3 3 hour days and will cover the basics of Python. Dates are June 15th, 17th, 19th. The workshop is free for all U of A students, staff, and faculty. I have never lead a workshop before, so it should be a lot of fun.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://bcf.arl.arizona.edu/attend-events/view-2.html"&gt;Register for the Python workshop&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6150371141502062714?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6150371141502062714/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/learn-python-workshop.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6150371141502062714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6150371141502062714'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/learn-python-workshop.html' title='Learn Python Workshop'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4322785035805001762</id><published>2009-05-20T21:49:00.000-07:00</published><updated>2009-07-26T19:43:42.008-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><category scheme='http://www.blogger.com/atom/ns#' term='real-time'/><title type='text'>Real-time Flex messaging with AmFast</title><content type='html'>This example is very similar to the &lt;a href="http://limscoder.blogspot.com/2009/05/create-chat-client-and-server-with-flex.html"&gt;previous&lt;/a&gt;, but we're going to use &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast's&lt;/a&gt; HTTP streaming channel to implement real-time flex messaging. Get the complete &lt;a href="http://code.google.com/p/amfast/wiki/RealTimeMessaging"&gt;example code here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;First let's setup the Producer and Consumer in Actionscript.&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;&lt;br /&gt;// Configure the ChannelSet that&lt;br /&gt;// messages will be sent and received over.&lt;br /&gt;import mx.messaging.ChannelSet;&lt;br /&gt;&lt;br /&gt;// To use HTTP streaming, the&lt;br /&gt;// StreamingAMFChannel class must be used.&lt;br /&gt;import mx.messaging.channels.StreamingAMFChannel;&lt;br /&gt;var channelSet:ChannelSet = new ChannelSet();&lt;br /&gt;var channel:StreamingAMFChannel = new StreamingAMFChannel("channel-name", "server-url");&lt;br /&gt;channelSet.addChannel(channel);&lt;br /&gt;&lt;br /&gt;// Setup a Consumer to receive messages.&lt;br /&gt;import mx.messaging.Consumer;&lt;br /&gt;var consumer:Consumer = new Consumer();&lt;br /&gt;&lt;br /&gt;// The consumer's destination is the 'topic'&lt;br /&gt;// name that the consumer will be subscribed to.&lt;br /&gt;// The consumer will receive all messages&lt;br /&gt;// published to the topic.&lt;br /&gt;consumer.destination = "topic";&lt;br /&gt;&lt;br /&gt;// Use the ChannelSet that was already created.&lt;br /&gt;consumer.channelSet = channelSet;&lt;br /&gt;&lt;br /&gt;// This event listener will be called whenever&lt;br /&gt;// the consumer receives a message from the server.&lt;br /&gt;consumer.addEventListener(MessageEvent.MESSAGE, newMsgHandler);&lt;br /&gt;&lt;br /&gt;// The consumer won't start receiving messages&lt;br /&gt;// until it is subscribed.&lt;br /&gt;consumer.subscribe();&lt;br /&gt;&lt;br /&gt;// Setup a Producer to publish messages.&lt;br /&gt;import mx.messaging.Producer;&lt;br /&gt;var producer:Producer = new Producer();&lt;br /&gt;producer.destination = "topic";&lt;br /&gt;producer.channelSet = channelSet;&lt;br /&gt;&lt;br /&gt;// Create an Async message and send it&lt;br /&gt;// to all other clients subscribed to the topic.&lt;br /&gt;import mx.messaging.messages.AsyncMessage;&lt;br /&gt;var msg:AsyncMessage = new AsynMessage();&lt;br /&gt;&lt;br /&gt;// Set the message's body attribute to the&lt;br /&gt;// object that is going to be published.&lt;br /&gt;//&lt;br /&gt;// In this case the object contains&lt;br /&gt;// the X and Y coordinates of a Sprite.&lt;br /&gt;msg.body = {'x': 10, 'y': 10};&lt;br /&gt;producer.send(msg);&lt;/pre&gt;&lt;br /&gt;Next we'll setup the server.&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;# Create a ChannelSet to serve messages.&lt;br /&gt;from amfast.remoting.channel import ChannelSet&lt;br /&gt;from amfast.remoting.wsgi_channel import StreamingWsgiChannel&lt;br /&gt;channel_set = ChannelSet()&lt;br /&gt;&lt;br /&gt;# Each individual ChannelSet can use&lt;br /&gt;# one or more Channels. When messages&lt;br /&gt;# are published through the ChannelSet,&lt;br /&gt;# they will be published to all subscribed clients,&lt;br /&gt;# regardless of which Channel the clients&lt;br /&gt;# are connected through.&lt;br /&gt;&lt;br /&gt;# Create a HTTP streaming channel.&lt;br /&gt;#&lt;br /&gt;# When a client connects to a streaming channel,&lt;br /&gt;# the HTTP connection remains open until the client&lt;br /&gt;# disconnects. When messages are published,&lt;br /&gt;# they are immediately dispatched to clients connected&lt;br /&gt;# to HTTP streaming channels.&lt;br /&gt;stream_channel = StreamingWsgiChannel('stream-channel')&lt;br /&gt;&lt;br /&gt;# WsgiChannels objects are wsgi applications&lt;br /&gt;# that can be served with any Wsgi server.&lt;br /&gt;# CherryPy is being used in this example.&lt;br /&gt;cherrypy.tree.graft(stream_channel, '/amf')&lt;br /&gt;&lt;br /&gt;# Start the server.&lt;br /&gt;# App() is your root controller class&lt;br /&gt;# for non-AMF functions.&lt;br /&gt;cherrypy.quickstart(App(), '/')&lt;br /&gt;&lt;br /&gt;# That's it, our server is up and running.&lt;br /&gt;# It's that simple.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To test the example, download the &lt;a href="http://code.google.com/p/amfast/wiki/RealTimeMessaging"&gt;full code from the repo&lt;/a&gt;. To run the example you'll need to install &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; and &lt;a href="http://www.cherrypy.org/"&gt;CherryPy&lt;/a&gt; or &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt; 1st.&lt;br /&gt;&lt;pre class="brush: bash"&gt;&lt;br /&gt;# To server the example with cherrypy&lt;br /&gt;python cp_server.py&lt;br /&gt;&lt;br /&gt;# To server the example with Twisted&lt;br /&gt;twistd -noy twisted_server.tac&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Open two browser windows and browse to 'http://localhost:8000'. Click the 'Subscribe' button in both windows. In the 1st window click on the 'Master' button. In the 1st window, click on the yellow circle, and drag the circle around the screen. As the circle is dragged around the screen, the position of the circle will be updated in the 2nd window. Pretty cool stuff.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4322785035805001762?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4322785035805001762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/real-time-flex-messaging-with-amfast.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4322785035805001762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4322785035805001762'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/real-time-flex-messaging-with-amfast.html' title='Real-time Flex messaging with AmFast'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-9163716553749580938</id><published>2009-05-16T14:23:00.000-07:00</published><updated>2009-11-20T12:41:07.676-08:00</updated><title type='text'>Create a chat client and server with Flex and Python</title><content type='html'>In this example we'll create a simple Flex chat client that uses Producer/Consumer messaging to send and receive messages through a Python server. The server will use &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast's&lt;/a&gt; HTTP polling and long-polling channels. Get the complete &lt;a href="http://code.google.com/p/amfast/wiki/ProducerConsumer"&gt;example code here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;First let's setup the Producer and Consumer in Actionscript.&lt;br /&gt;&lt;pre class="brush: as3"&gt;&lt;br /&gt;// Configure the ChannelSet that&lt;br /&gt;// messages will be sent and received over.&lt;br /&gt;import mx.messaging.ChannelSet;&lt;br /&gt;import mx.messaging.channels.AMFChannel;&lt;br /&gt;var channelSet:ChannelSet = new ChannelSet();&lt;br /&gt;var channel:AMFChannel = new AMFChannel("channel-name", "server-url");&lt;br /&gt;channelSet.addChannel(channel);&lt;br /&gt;&lt;br /&gt;// Setup a Consumer to receive messages.&lt;br /&gt;import mx.messaging.Consumer;&lt;br /&gt;var consumer:Consumer = new Consumer();&lt;br /&gt;&lt;br /&gt;// The consumer's destination is the 'topic'&lt;br /&gt;// name that the consumer will be subscribed to.&lt;br /&gt;// The consumer will receive all messages&lt;br /&gt;// published to the topic.&lt;br /&gt;consumer.destination = "topic";&lt;br /&gt;&lt;br /&gt;// Use the ChannelSet that was already created.&lt;br /&gt;consumer.channelSet = channelSet;&lt;br /&gt;&lt;br /&gt;// This event listener will be called whenever&lt;br /&gt;// the consumer receives a message from the server.&lt;br /&gt;consumer.addEventListener(MessageEvent.MESSAGE, newMsgHandler);&lt;br /&gt;&lt;br /&gt;// The consumer won't start receiving messages&lt;br /&gt;// until it is subscribed.&lt;br /&gt;consumer.subscribe();&lt;br /&gt;&lt;br /&gt;// Setup a Producer to publish messages.&lt;br /&gt;import mx.messaging.Producer;&lt;br /&gt;var producer:Producer = new Producer();&lt;br /&gt;producer.destination = "topic";&lt;br /&gt;producer.channelSet = channelSet;&lt;br /&gt;&lt;br /&gt;// Create an Async message and send it&lt;br /&gt;// to all other clients subscribed to the topic.&lt;br /&gt;import mx.messaging.messages.AsyncMessage;&lt;br /&gt;var msg:AsyncMessage = new AsynMessage();&lt;br /&gt;&lt;br /&gt;// Set the message's body attribute to the&lt;br /&gt;// object that is going to be published.&lt;br /&gt;//&lt;br /&gt;// In this case the object is a String,&lt;br /&gt;// but the message body can be any&lt;br /&gt;// type of object.&lt;br /&gt;msg.body = "This is being published!";&lt;br /&gt;producer.send(msg);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next we'll setup the server.&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;# Create a ChannelSet to serve messages.&lt;br /&gt;from amfast.remoting.channel import ChannelSet&lt;br /&gt;from amfast.remoting.wsgi_channel import WsgiChannel&lt;br /&gt;channel_set = ChannelSet()&lt;br /&gt;&lt;br /&gt;# Each individual ChannelSet can use&lt;br /&gt;# one or more Channels. When messages&lt;br /&gt;# are published through the ChannelSet,&lt;br /&gt;# they will be published to all subscribed clients,&lt;br /&gt;# regardless of which Channel the clients&lt;br /&gt;# are connected through.&lt;br /&gt;&lt;br /&gt;# Create s standard polling channel.&lt;br /&gt;polling_channel = WsgiChannel('poll-channel')&lt;br /&gt;&lt;br /&gt;# A long-polling channel is created similar&lt;br /&gt;# to a normal channel, but the wait_interval&lt;br /&gt;# argument must be set to -1.&lt;br /&gt;long_channel = WsgiChannel('long-channel', wait_interval=-1)&lt;br /&gt;&lt;br /&gt;# WsgiChannels objects are wsgi applications&lt;br /&gt;# that can be served with any Wsgi server.&lt;br /&gt;# CherryPy is being used in this example.&lt;br /&gt;cherrypy.tree.graft(polling_channel, '/amf')&lt;br /&gt;cherrypy.tree.graft(long_channel, '/longPoll')&lt;br /&gt;&lt;br /&gt;# Start the server.&lt;br /&gt;# App() is your root controller class&lt;br /&gt;# for non-AMF functions.&lt;br /&gt;cherrypy.quickstart(App(), '/')&lt;br /&gt;&lt;br /&gt;# That's it, our server is up and running.&lt;br /&gt;# It's that simple.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To test the example, download the &lt;a href="http://code.google.com/p/amfast/source/browse/trunk/examples/messaging/"&gt;full code from the repo&lt;/a&gt;. To run the example you'll need to install &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; and &lt;a href="http://www.cherrypy.org/"&gt;CherryPy&lt;/a&gt; or &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt; 1st.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python"&gt;&lt;br /&gt;# To server the example with cherrypy&lt;br /&gt;python cp_server.py&lt;br /&gt;&lt;br /&gt;# To server the example with Twisted&lt;br /&gt;twistd -noy twisted_server.tac&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Open two browser windows and browse to 'http://localhost:8000'. In the 1st window set the messaging url to 'http://localhost:8000/amf' to use the polling channel. In the 2nd window set the messaging url to 'http://localhost:8000/longPoll' to use the long-polling channel. Click the 'Subscribe' button in both windows. Enter a message in the message entry box, and press the 'Publish' button to publish the message. The published message will appear in both clients.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-9163716553749580938?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/9163716553749580938/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/create-chat-client-and-server-with-flex.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9163716553749580938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9163716553749580938'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/create-chat-client-and-server-with-flex.html' title='Create a chat client and server with Flex and Python'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-7683691295539461016</id><published>2009-05-15T15:47:00.000-07:00</published><updated>2009-05-16T22:54:14.935-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><title type='text'>'Pushing' messages to Flex clients with AmFast</title><content type='html'>One of the coolest aspects of Flex development is the ability to 'push' messages from the server to clients. With the newest release of &lt;a href="http://code.google.com/p/amfast/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;AmFast&lt;/span&gt;&lt;/a&gt;, you can finally utilize this capability with all of the goodness of Python. This will be the 1st of 3 posts describing how you can use the &lt;a href="http://code.google.com/p/amfast/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;AmFast&lt;/span&gt;&lt;/a&gt; Flash &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;remoting&lt;/span&gt; package to push messages to Flex clients from a Python server.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/amfast/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;AmFast&lt;/span&gt;&lt;/a&gt; supports pushing messages to clients over HTTP using 3 different strategies: polling, long-polling, and streaming. &lt;a href="http://www.flexlive.net/?p=102"&gt;Here is a good article describing the differences between those strategies&lt;/a&gt;. Go ahead and skip past the XML configuration sections, since you won't need any XML to configure &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;AmFast&lt;/span&gt; :)&lt;br /&gt;&lt;br /&gt;Streaming channels deliver messages in real time, but also consume the most server resources. Standard polling consumes the least amount of server resources, but is also the slowest of the three strategies. Long-polling sits in the middle and offers a nice &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_5"&gt;compromise&lt;/span&gt; between latency and resource consumption.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/amfast/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;AmFast&lt;/span&gt;&lt;/a&gt; has built-in support for using any of the 3 messaging strategies with either a multi-threaded &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;WSGI&lt;/span&gt; server, or the &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted&lt;/a&gt; web framework. One thing to think about when implementing a multi-threaded server is that any client using  long-polling or streaming may consume a thread for a significant amount of time, even if all it's doing is waiting for a new message. I haven't done any tests, but I'm guessing that &lt;a href="http://twistedmatrix.com/trac/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;Twisted's&lt;/span&gt;  &lt;/a&gt;single-threaded, asynchronous design can handle more concurrent connections then a multi-threaded implementation.&lt;br /&gt;&lt;br /&gt;On the client side, the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;Actionscript&lt;/span&gt; &lt;a href="http://livedocs.adobe.com/flex/3/langref/mx/messaging/Consumer.html"&gt;Consumer&lt;/a&gt; class is used to receive messages from a server, and the &lt;a href="http://livedocs.adobe.com/flex/3/langref/mx/messaging/Producer.html"&gt;Producer&lt;/a&gt; class is used to send messages to a server. Any type of object can be pushed to clients, so the same technology can be used to build many different types of applications.&lt;br /&gt;&lt;br /&gt;In the next post I'll explain the details of using &lt;a href="http://code.google.com/p/amfast/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;AmFast&lt;/span&gt;&lt;/a&gt;'s HTTP polling and HTTP long-polling channels to create a simple chat client. In the following post I'll show an example of using HTTP streaming to create an app where a master client can manipulate a graphical entity on the screen, and changes are replicated to all other subscribed clients.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-7683691295539461016?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/7683691295539461016/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/pushing-messages-to-flex-clients-with.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7683691295539461016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7683691295539461016'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/pushing-messages-to-flex-clients-with.html' title='&apos;Pushing&apos; messages to Flex clients with AmFast'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-245447807856365390</id><published>2009-05-09T21:24:00.000-07:00</published><updated>2009-05-09T21:35:06.464-07:00</updated><title type='text'>AmFast 0.3.0 Released!</title><content type='html'>The latest version of &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; is out. In addition to the changes listed below, the &lt;a href="http://code.google.com/p/amfast/wiki/Documentation"&gt;documentation&lt;/a&gt; has been updated, and there are several new &lt;a href="http://code.google.com/p/amfast/wiki/ExampleImplementations"&gt;examples&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:180%;" &gt;remoting&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Producer/Consumer messaging via HTTP polling.&lt;/li&gt;&lt;li&gt; Channels and ChannelSets allow resources to be exposed in multiple ways.&lt;/li&gt;&lt;li&gt; Built in Channels for CherryPy, Twisted Web, and straight-up WSGI.&lt;/li&gt;&lt;li&gt; Easy to configure authentication for NetConnection and RemoteObject.&lt;/li&gt;&lt;li&gt; Connection objects hold session attributes tied to Flex client id.&lt;/li&gt;&lt;li&gt; Customizable Endpoints to encode/decode messages with external encoding/decoding libraries.&lt;/li&gt;&lt;li&gt; PyAmfEndpoint to encode/decode messages with pure Python, with no C compiler dependencies.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:180%;" &gt;encode/decode&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Read and write to file-like-objects or strings (using strings is much faster).&lt;/li&gt;&lt;li&gt;Automatic type conversion.&lt;/li&gt;&lt;li&gt;amfast.class_def.as_types.AsProxy and amfast.class_def.as_types.AsNoProxy allow overriding 'use_collections' and 'use_proxies' settings for a individual objects.&lt;/li&gt;&lt;li&gt;readExternal and writeExternal methods of the IEXTERNALIZABLE interface accept different parameters to make custom encodings easier.&lt;/li&gt;&lt;li&gt;amfast.encoder.Encoder and amfast.decoder.Decoder: Wrappers around amfast.encode and amfast.decode, so you don't need to pass the same arguments every time you encode or decode.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-245447807856365390?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/245447807856365390/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/amfast-030-released.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/245447807856365390'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/245447807856365390'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/amfast-030-released.html' title='AmFast 0.3.0 Released!'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4525734882912324442</id><published>2009-05-06T17:08:00.000-07:00</published><updated>2009-05-06T17:13:00.840-07:00</updated><title type='text'>Plater 0.1.3 maintenance release</title><content type='html'>&lt;a href="http://ccp.arl.arizona.edu/trac/Plater/wiki/WikiStart"&gt;Plater 0.1.3&lt;/a&gt; (laboratory information management package) maintenance release today. The release contains a bug fix that was affecting parsing of CSV plate maps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4525734882912324442?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4525734882912324442/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/plater-013-maintenance-release.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4525734882912324442'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4525734882912324442'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/plater-013-maintenance-release.html' title='Plater 0.1.3 maintenance release'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-187858338578663789</id><published>2009-05-02T15:24:00.000-07:00</published><updated>2009-05-02T15:29:16.617-07:00</updated><title type='text'>AmFast 0.3 release is coming soon</title><content type='html'>I've been working on AmFast 0.3 for a while now, and it looks like I should be able to release it next weekend. 0.3 has cool new features such as: Producer/Consumer messaging, pure-Python encoding and decoding using PyAmf, and a bunch of other cool stuff.&lt;br /&gt;&lt;br /&gt;The coding is done, and I'm working on documentation, testing, and a couple of new example applications.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-187858338578663789?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/187858338578663789/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/05/amfast-03-release-is-coming-soon.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/187858338578663789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/187858338578663789'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/05/amfast-03-release-is-coming-soon.html' title='AmFast 0.3 release is coming soon'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4916170434199915546</id><published>2009-03-22T21:14:00.000-07:00</published><updated>2009-03-22T21:16:49.759-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><title type='text'>AmFast 0.2.2 Released!</title><content type='html'>The latest version of &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt; is out. Improvements include an Actionscript code generator, an example using the &lt;a href="http://twistedmatrix.com/trac/"&gt;Twisted framework&lt;/a&gt;, and several bug fixes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4916170434199915546?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4916170434199915546/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/03/amfast-022-released.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4916170434199915546'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4916170434199915546'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/03/amfast-022-released.html' title='AmFast 0.2.2 Released!'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-7401629533291214208</id><published>2009-03-21T15:31:00.000-07:00</published><updated>2009-03-21T15:38:59.489-07:00</updated><title type='text'>Flash and Python Presentation</title><content type='html'>On Friday I presented information about Flash and Python to several developers from&lt;a href="http://iplantcollaborative.org/"&gt; iPlant&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The presentation covered the current state of Python tools designed to communicate with various flavors of Flash. I will be giving the same presentation to &lt;a href="http://groups.google.com/group/TuPLEgroup"&gt;TuPLE&lt;/a&gt; this coming Tuesday.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ccp.arl.arizona.edu/dthompso/flash_python.pdf"&gt;Here are the slides&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-7401629533291214208?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/7401629533291214208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/03/flash-and-python-presentation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7401629533291214208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/7401629533291214208'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/03/flash-and-python-presentation.html' title='Flash and Python Presentation'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6324949956794391158</id><published>2009-03-11T23:00:00.000-07:00</published><updated>2009-03-11T23:08:36.105-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><title type='text'>AmFast 0.2 Released!</title><content type='html'>Check out the latest version of &lt;a href="http://code.google.com/p/amfast/"&gt;AmFast&lt;/a&gt;. The 0.2 version implements AMF0, AMF3 and supports configurable remoting with NetConnection and RemoteObject.&lt;br /&gt;&lt;br /&gt;It's fast, flexible, and easy to use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6324949956794391158?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6324949956794391158/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/03/amfast-02-released.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6324949956794391158'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6324949956794391158'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/03/amfast-02-released.html' title='AmFast 0.2 Released!'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-8758208818818413357</id><published>2009-03-02T17:41:00.000-08:00</published><updated>2009-03-02T18:01:25.645-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='c'/><category scheme='http://www.blogger.com/atom/ns#' term='AmFast'/><category scheme='http://www.blogger.com/atom/ns#' term='pyamf'/><title type='text'>AmFast Released!</title><content type='html'>I released the alpha version of &lt;a href="http://code.google.com/p/amfast"&gt;AmFast&lt;/a&gt; today. &lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;AmFast is an AMF3 encoder/decoder Python extension.&lt;br /&gt;&lt;br /&gt;Some un-sophisticated testing shows that AmFast is around ~18x quicker than &lt;a href="http://pyamf.org/"&gt;PyAmf&lt;/a&gt;. 10,000 runs through the &lt;span class="nf"&gt;test_complex_encode_decode_dict() unit test takes ~105 seconds on my machine using PyAmf, and ~5.8 seconds using AmFast.&lt;br /&gt;&lt;br /&gt;If you want to try it out, you can download the package from &lt;a href="http://pypi.python.org/pypi/AmFast/0.1.0"&gt;PyPi&lt;/a&gt;, and take a look at the &lt;a href="http://code.google.com/p/amfast/wiki/Documentation"&gt;meager documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;AmFast supports all of the data types that PyAmf does, but there is not yet any functionality for implementing remoting, or working with AMF0. I'm hoping that AmFast will be able to integrate into PyAmf, so that you can optionally use either the existing pure Python AMF3 encoder/decoder, or AmFast within the PyAmf framework.&lt;br /&gt;&lt;br /&gt;This was my first time working with the &lt;a href="http://docs.python.org/c-api/"&gt;Python C API&lt;/a&gt;, and I think it's pretty cool. Besides having to remember to increment and decrement Python object reference counts in the right spots, it was pretty easy to learn. I'm not anywhere near a C expert, and I wrote this extension in my spare time over the course of a couple of weeks.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-8758208818818413357?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/8758208818818413357/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/03/amfast-released.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8758208818818413357'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8758208818818413357'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/03/amfast-released.html' title='AmFast Released!'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-8459358154172758626</id><published>2009-02-19T10:20:00.000-08:00</published><updated>2009-02-19T10:31:00.928-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='lims'/><category scheme='http://www.blogger.com/atom/ns#' term='plater'/><title type='text'>Plater Released!</title><content type='html'>I released &lt;a href="http://ccp.arl.arizona.edu/trac/Plater"&gt;Plater 0.1&lt;/a&gt; today.&lt;br /&gt;&lt;br /&gt;Plater is a Python package that contains common LIMS classes that can be used to build a custom LIMS application.&lt;br /&gt;&lt;br /&gt;Instead of starting your LIMS application from scratch, use the Plater package to represent common LIMS domain models, and piece them together in whatever configuration your application requires.&lt;br /&gt;&lt;br /&gt;Take a look at the &lt;a href="http://ccp.arl.arizona.edu/trac/Plater"&gt;Plater project page&lt;/a&gt; for details on how to download/install/use Plater.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-8459358154172758626?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/8459358154172758626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/02/plater-released.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8459358154172758626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/8459358154172758626'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/02/plater-released.html' title='Plater Released!'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-5462588452396022420</id><published>2009-02-10T20:14:00.000-08:00</published><updated>2009-02-10T20:37:03.487-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='amf'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><title type='text'>AMF vs. JSON</title><content type='html'>You can find many blog posts comparing AJAX data transfer formats JSON and XML to AMF3, but they all seem to miss what I see as the 3 biggest advantages to AMF3.&lt;br /&gt;&lt;br /&gt;1: AMF3 supports references for objects, strings, and class definitions. If you're encoding an object graph that contains references to the same object over and over, or even the same object type over and over, AMF3 will be significantly smaller over the wire, and once you get the data you won't need to do any manually reconciling of objects that are really the same thing.&lt;br /&gt;&lt;br /&gt;2: AMF3 can decode typed objects that have methods, derived properties, and a static class definition. That saves time because you have to do less post-parsing processing of the data. It also makes development a lot quicker, especially if you're using an ORM on the server side. You can send objects retrieved from the database straight over to the client, the user can modify the objects with the GUI, and the objects can be sent straight back to the server to save changes. Your server-side AMF encoder/decoder library and ORM library take can care of all the CRUD grunt work, so all you have to do is figure out a decent data model and how to represent it in the client GUI. I use Python and &lt;a href="http://pyamf.org/"&gt;PyAMF&lt;/a&gt; on the server side, which even has support for lazy-loading attributes of objects persisted with the &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy ORM&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;3: You can implement the &lt;a href="http://livedocs.adobe.com/flex/3/langref/flash/utils/IExternalizable.html"&gt;IExternalizable&lt;/a&gt; interface on any AS3 class to fully customize the object encoding/decoding process. If you're brave and need optimization for large data sets, you can even write and read directly to/from the AMF3 byte stream and use your own custom encoding scheme suited for your data type&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-5462588452396022420?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/5462588452396022420/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/02/amf-vs-json.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5462588452396022420'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/5462588452396022420'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/02/amf-vs-json.html' title='AMF vs. JSON'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-4642096569509623204</id><published>2009-01-22T08:45:00.001-08:00</published><updated>2009-01-22T11:27:05.133-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='sqlalchemy'/><category scheme='http://www.blogger.com/atom/ns#' term='pyamf'/><title type='text'>PyAMF SQLAlchemy Adapter</title><content type='html'>PyAMF 0.4 was recently released, and the new code contains SQLAlchemy support by default, plus a bunch of other cool new features and bug fixes.&lt;br /&gt;&lt;br /&gt;Check out my example &lt;a href="http://pyamf.org/wiki/SQLAlchemyAdapter"&gt;Flex/PyAMF/SQLAlchemy project&lt;/a&gt; for ideas of how to get started with PyAMF and SQLAlchemy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-4642096569509623204?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/4642096569509623204/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2009/01/pyamf-sqlalchemy-adapter.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4642096569509623204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/4642096569509623204'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2009/01/pyamf-sqlalchemy-adapter.html' title='PyAMF SQLAlchemy Adapter'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-1014385890327641906</id><published>2008-09-21T13:44:00.000-07:00</published><updated>2008-09-21T15:35:58.912-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='sqlalchemy'/><category scheme='http://www.blogger.com/atom/ns#' term='lims'/><category scheme='http://www.blogger.com/atom/ns#' term='pyamf'/><title type='text'>Using PyAMF and SQLAlchemy together</title><content type='html'>This past week I recieved the go-ahead to re-code the backend of our LIMS application with Python (it's currently PHP). It's not a minute too late, as I don't think I can stand typing another '-&gt;' or '$' without throwing something at the wall. I could go on about why I'm really sick of &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;PHP&lt;/span&gt; and why Python rocks, but I'm sure you can read all about that elsewhere.&lt;br /&gt;&lt;br /&gt;We'll be using Python's &lt;a href="http://www.sqlalchemy.org/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;SQLAlchemy&lt;/span&gt;&lt;/a&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;ORM&lt;/span&gt;, which kicks some serious ass. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;SQLAlchemy&lt;/span&gt; makes it amazingly simple to model complex relationships like inheritance &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_4"&gt;hierarchies&lt;/span&gt; and many-to-many relationships that I could only implement with gobs of gnarly custom code in &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;PHP&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Since we're already re-writing the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;backend&lt;/span&gt; we decided that we may as well tweak the Flex client at the same time. We are going to be making the switch from &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;JSON&lt;/span&gt; to &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;AMF&lt;/span&gt; for server-client communication, which will be more &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;performant&lt;/span&gt; and allow us to transfer richer objects back and forth.&lt;br /&gt;&lt;br /&gt;Python is pretty awesome for web-development, but the project that aims to support &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;AMF&lt;/span&gt; (&lt;a href="http://pyamf.org/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;PyAmf&lt;/span&gt;&lt;/a&gt;) is still new and immature (but it does encode and decode as advertised and is under active development). In fact, the current stable release of &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;PyAmf&lt;/span&gt; will not properly encode objects that are being managed by &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;SQLAlchemy&lt;/span&gt;. There is a &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;SQLAlchemy&lt;/span&gt; 'adapter' in the development branch of the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;PyAmf&lt;/span&gt; project that can be used, but I found the code to be lacking.&lt;br /&gt;&lt;br /&gt;I decided to take matters into my own hands, and I am currently working on my own &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;SQLAlchemy&lt;/span&gt; adapter that will hopefully work a little better. The code for my &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;SQLAlchemy&lt;/span&gt; adapter can be found on &lt;a href="http://code.google.com/p/limscoder/source/browse/trunk/#trunk/pyamf"&gt;Google code&lt;/a&gt;. It is not production ready, but if you want to experiment with it (tested only with SA 0.5), you should be able to drop it in the 'adapters' directory of the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_18"&gt;PyAmf&lt;/span&gt; 4.* branch to use it.&lt;br /&gt;&lt;br /&gt;The adapter has 2 main features. It &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_19"&gt;overrides&lt;/span&gt; the pyamf.util.get_attrs function so that the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_20"&gt;pyamf&lt;/span&gt; encoder skips any attributes of an object that are used by &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_21"&gt;SQLAlchemy&lt;/span&gt; (_&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_22"&gt;sa&lt;/span&gt;_instance_state,  _&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_23"&gt;sa&lt;/span&gt;_manager, etc), and it also identifies and translates &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_24"&gt;SQLAlchemy&lt;/span&gt; collection objects, such as &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_25"&gt;InstrumentedList&lt;/span&gt; objects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-1014385890327641906?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/1014385890327641906/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2008/09/using-pyamf-and-sqlalchemy-together.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1014385890327641906'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/1014385890327641906'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2008/09/using-pyamf-and-sqlalchemy-together.html' title='Using PyAMF and SQLAlchemy together'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-6868008168906826926</id><published>2008-08-25T20:23:00.000-07:00</published><updated>2008-08-25T22:04:25.060-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='json-rpc'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><title type='text'>Flex JSON-RPC implementation</title><content type='html'>So, you're checking out Flex and you're thinking to yourself, "Gee this e4x stuff is pretty rad, but it's 2008 and I could really use a good JSON implementation so I can integrate my bitchin' Flex component into my AJAX page." Amazingly, I was thinking the same thing, so I put together a couple of classes to implement a &lt;a href="http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html"&gt;JSON-RPC v1.1&lt;/a&gt; client.&lt;br /&gt;&lt;br /&gt;The application code for this post is available on &lt;a href="http://code.google.com/p/limscoder/source/browse/trunk/json_rpc/"&gt;Google code&lt;/a&gt;. In addition to the JSON-RPC classes, the application contains a simple gui with controls for testing and logging JSON-RPC communication. To compile the code, you'll also need &lt;a href="http://code.google.com/p/as3corelib/"&gt;as3corelib&lt;/a&gt; in your compile path, which handles the JSON serialization/deserialization.&lt;br /&gt;&lt;br /&gt;The 1st thing you'll need is a JSON-RPC server. If you don't already have one, it shouldn't be too difficult to put together. Follow this&lt;a href="http://pythonpaste.org/webob/jsonrpc-example.html"&gt; tutoria&lt;/a&gt;&lt;a href="http://pythonpaste.org/webob/jsonrpc-example.html"&gt;l&lt;/a&gt; to create a simple Python JSON-RPC server, or use any 1.1 compliant server.&lt;br /&gt;&lt;br /&gt;Next, we need to extend mx.rpc.AbstractOperation. This class will send the method invokation to the server and decode the result. Two important methods are overriden. The first is 'send', which will encode the request according to the JSON-RPC v1.1 spec, create a HTTPRequestMessage, and then call the parent invoke() to send the message to the server. If JSON encoding fails, a FaultEvent must be dispatched. Use the parent method dispatchRpcEvent() to make sure the event is dispatched by both the service and the operation. &lt;br /&gt;&lt;br /&gt;The other important method is 'processResult', which decodes the response from the server and checks for any errors defined by the server application. The 'processResult' method is in the mx_internal namespace. If you're not familiar with using mx_internal, you should probably read up on it. The parent class takes care of most of the heavy lifting, but you will need to dispatch a FaultEvent if the JSON decode fails, or if a server application error was defined. Set the _result variable equal to the decoded result, and you're good to go.&lt;br /&gt;&lt;br /&gt;Finally, we need to extend mx.rpc.AbstractService. This class is used to instantiate mx.rpc.AbstractOperation instances and invoke remote method calls in our application code. The class's main purpose is to act as a proxy that intercepts method calls, so we will be able to invoke a remote method with the syntax: service.method(arguments) instead of operation.send(method, args). You will need to override the 'getOperation' method to instantiate the correct mx.rpc.AbstractOperation subclass.&lt;br /&gt;&lt;br /&gt;Here's how the jsonrpc.JsonRpcService can be used in your application code to communicate with the server:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import mx.rpc.events.*;&lt;br /&gt;&lt;br /&gt;import jsonrpc.JsonRpcService;&lt;br /&gt;&lt;br /&gt;private function callFoo(fooArgs:Object):void&lt;br /&gt;{&lt;br /&gt;    // Create service object and&lt;br /&gt;    // call remote method 'foo'.&lt;br /&gt;    var jsonService:JsonRpcService = new JsonRpcService();&lt;br /&gt;    jsonService.rootUrl = 'http://localhost';&lt;br /&gt;    jsonService.url = 'test';&lt;br /&gt;    jsonService.addEventListener(ResultEvent.RESULT, gotBar);&lt;br /&gt;    jsonService.foo(fooArgs);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private function gotBar(event:ResultEvent):void&lt;br /&gt;{&lt;br /&gt;    event.target.removeEventListener(ResultEvent.RESULT, gotBar);&lt;br /&gt;    var bar:Object = ResultEvent.result;&lt;br /&gt;    // do something with bar&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-6868008168906826926?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/6868008168906826926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2008/08/flex-json-rpc-implementation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6868008168906826926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/6868008168906826926'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2008/08/flex-json-rpc-implementation.html' title='Flex JSON-RPC implementation'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-9126742868817485256</id><published>2008-08-23T09:58:00.001-07:00</published><updated>2008-08-23T10:03:57.816-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='flex360'/><title type='text'>Flex360</title><content type='html'>We've been using Adobe Flex for the past 6-months to develop SLiM, our RIA client to access our backend GAPSS LIMS server. Flex is the perfect fit for LIMS, because we can do really cool stuff, such as performant drag and drop plate maps right in the browser.&lt;br /&gt;&lt;br /&gt;I got my first chance to interact with the larger Flex community this past week at &lt;a href="http://www.360conferences.com/360flex/"&gt;Flex360&lt;/a&gt; in San Jose. I was half-expecting to meet a gaggle of converted Flash developers of the 'creative' type, but I was pleasantly surprised by the number of attendees using Flex for enterprise level applications.  Many big companies are developing internal apps with Flex, and most of the community seemed to be very high quality.&lt;br /&gt;&lt;br /&gt;Overall, the conference re-enforced the decision to use Flex for our app. I learned a ton of new technical info at the sessions and I'm trying to prioritize what was coolest, so I can go back and re-code sections of SLiM to be better.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-9126742868817485256?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/9126742868817485256/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2008/08/flex360.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9126742868817485256'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/9126742868817485256'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2008/08/flex360.html' title='Flex360'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2536364715247944978.post-3806787648914303659</id><published>2008-08-23T09:30:00.000-07:00</published><updated>2008-08-23T09:58:16.323-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lims'/><title type='text'>Lims Coder</title><content type='html'>I'm starting this blog to discuss my experiences with the development of web-based LIMS. I'm drawing most of my content from experience developing applications for a university biotechnology core facility.&lt;br /&gt;&lt;br /&gt;The topics will include server-side design, client-side design, lab equipment issues, code examples, and anything generally related to web-application development for life-sciences laboratories.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2536364715247944978-3806787648914303659?l=www.limscoder.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.limscoder.com/feeds/3806787648914303659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.limscoder.com/2008/08/lims-coder.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3806787648914303659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2536364715247944978/posts/default/3806787648914303659'/><link rel='alternate' type='text/html' href='http://www.limscoder.com/2008/08/lims-coder.html' title='Lims Coder'/><author><name>Dave Thompson</name><uri>http://www.blogger.com/profile/05289219104297308435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/-ixDc5TnrU_Y/Tl2BIiWZvJI/AAAAAAAACp8/VcKtWiJtC3Y/s220/idaho.jpg'/></author><thr:total>0</thr:total></entry></feed>
