<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <updated>2013-06-13T12:24:11-07:00</updated>

  <id>http://loose-bits.com/</id>
  <title>Loose Bits</title>
  <subtitle type="html">Thoughts on distributed systems, cloud computing, and the intersection of law and technology.</subtitle>

  <link href="http://loose-bits.com/atom.xml" rel="self"/>
  <link href="http://loose-bits.com/"/>

  <author>
    <name>Ryan Roemer</name>
    <email>ryan@loose-bits.com</email>
  </author>

  
    
    <entry>
      <id>http://loose-bits.com/2013/06/13/backbone-testing-book-announced</id>
      <title>Book Announcement - Backbone.js Testing</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2013-06-13T11:30:00Z</published>
      <updated>2013-06-13T11:30:00Z</updated>
      <link href="http://loose-bits.com/2013/06/13/backbone-testing-book-announced.html"/>
      <content type="html">&lt;h2 id='backbonejs_testing_book'&gt;Backbone.js Testing Book&lt;/h2&gt;

&lt;p&gt;I am very pleased to announce that I am in the last stages of publishing &lt;strong&gt;&lt;a href='http://www.packtpub.com/backbonejs-testing/book'&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt; - a book on &lt;a href='http://backbonejs.org/'&gt;Backbone.js&lt;/a&gt; test architecture and development due in final form around the end of June.&lt;/p&gt;
&lt;div class='pull-center'&gt;
  &lt;a href='http://www.packtpub.com/backbonejs-testing/book'&gt;
    &lt;img class='bordered' title='Backbone.js Testing' src='http://loose-bits.com/media/img/2013/06/13/book-cover.jpg' alt='Backbone.js Testing' /&gt;
  &lt;/a&gt;
  &lt;p /&gt;
&lt;/div&gt;
&lt;p&gt;A brief abstract of the book&amp;#8217;s content is available from the &lt;a href='http://www.packtpub.com/'&gt;Packt Publishing&lt;/a&gt; &lt;a href='http://www.packtpub.com/backbonejs-testing/book'&gt;website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Frontend web applications are soaring in popularity and the Backbone.js library is leading this charge with a modular, lightweight approach for organizing JavaScript web applications. At the same time, testing client-side JavaScript and Backbone.js programs remains a difficult and tedious undertaking.&lt;/p&gt;

&lt;p&gt;Backbone.js Testing brings sensible practices and current techniques to the challenges of Backbone.js test development. The book introduces fundamental testing concepts, comprehensive test infrastructure design, and practical exercises to easily and systematically test modern JavaScript web applications.&lt;/p&gt;

&lt;p&gt;The book progresses from Mocha test suites and Chai assertions to advanced test mocks and stubs with Sinon.JS. The requisite libraries and utilities are introduced with in-depth examples and best practices for integration with your applications. The book guides you through the test planning and implementation processes for your application models, views, routers, and other Backbone.js components.&lt;/p&gt;

&lt;p&gt;Backbone.js Testing gives you the tools, examples, and assistance to test your Backbone.js web applications thoroughly, quickly, and with confidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To provide a better idea of the book&amp;#8217;s structure, the tentative chapter titles are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up a Test Infrastructure&lt;/li&gt;

&lt;li&gt;Creating a Backbone.js Application Test Plan&lt;/li&gt;

&lt;li&gt;Test Assertions, Specs, and Suites&lt;/li&gt;

&lt;li&gt;Test Spies&lt;/li&gt;

&lt;li&gt;Test Stubs and Mocks&lt;/li&gt;

&lt;li&gt;Automated Web Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of the code examples in the book are provided online at the website &lt;a href='http://backbone-testing.com'&gt;backbone-testing.com&lt;/a&gt; and as an open source &lt;a href='https://github.com/ryan-roemer/backbone-testing'&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='the_road_to_my_first_book'&gt;The Road to my First Book&lt;/h2&gt;

&lt;p&gt;Application testing is difficult, and frontend JavaScript testing can often be a downright nightmare. After years of working through difficulties in frontend testing practices and technologies, I have gained a lot of experience through the various trials and tribulations bringing me to my current role in architecting (and testing) large Backbone.js applications.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://www.packtpub.com/'&gt;Packt Publishing&lt;/a&gt; approached me towards the end of last year about authoring a book on the subject and I signed on. My hope with the project is that I will be able to provide a resource to reduce the pain in frontend tests with a structured programming guide and practical examples using various (exciting and interesting) modern testing libraries.&lt;/p&gt;

&lt;p&gt;It has been a stressful, hectic, and wonderful experience writing the book, and I&amp;#8217;m really looking forward to completing the project. And, I will be happy to be able to reclaim my nights and weekends that have been devoted to the book over the past couple of months.&lt;/p&gt;

&lt;p&gt;After the book is published, I plan on writing a series of posts about authoring a technical book, with all of the ups and downs that the process entails. In the meantime, I just have to proof each chapter one final time and it should be off to the presses, so to speak!&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2013/05/10/seattle-nodejs-production-talk</id>
      <title>Node.js in Production</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2013-05-10T11:30:00Z</published>
      <updated>2013-05-10T11:30:00Z</updated>
      <link href="http://loose-bits.com/2013/05/10/seattle-nodejs-production-talk.html"/>
      <content type="html">&lt;h2 id='nodejs_in_production'&gt;Node.js in Production&lt;/h2&gt;

&lt;p&gt;Curiosity Media runs the world’s largest Spanish learning website, &lt;a href='http://spanishdict.com'&gt;SpanishDict.com&lt;/a&gt;, backed by many different &lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt; applications. Since rolling out our first Node.js applications over a year ago, we have collected a fair amount of good and bad operational experiences.&lt;/p&gt;

&lt;p&gt;I created a talk about our journey with Node.js for the &lt;a href='http://www.meetup.com/Seattle-Node-js/'&gt;Seattle Node.js Meetup&lt;/a&gt; group&amp;#8217;s &lt;a href='http://www.meetup.com/Seattle-Node-js/events/115959992/'&gt;May 8th, 2013 event&lt;/a&gt;. My slides are available at the following locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://ryan-roemer.github.io/seanode-prod-talk/'&gt;&lt;strong&gt;GitHub Site&lt;/strong&gt;&lt;/a&gt;: A live, navigable &lt;a href='https://github.com/hakimel/reveal.js/'&gt;reveal.js&lt;/a&gt; website. (&lt;em&gt;Note&lt;/em&gt;: use the space bar to advance slides and arrow keys to navigate.)&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.slideshare.net/RyanRoemer/seanode-prodtalk'&gt;&lt;strong&gt;SlideShare&lt;/strong&gt;&lt;/a&gt;: More traditional format, with download (PDF) links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, here&amp;#8217;s an embedded format:&lt;/p&gt;
&lt;div class='embed'&gt;
  &lt;iframe marginheight='0' scrolling='no' mozallowfullscreen='mozallowfullscreen' src='http://www.slideshare.net/slideshow/embed_code/20880870' marginwidth='0' allowfullscreen='allowfullscreen' frameborder='0' webkitallowfullscreen='webkitallowfullscreen'&gt;&amp;nbsp;&lt;/iframe&gt;
&lt;/div&gt;&lt;!-- more start --&gt;
&lt;h2 id='five_nodejs_production_tips'&gt;Five Node.js Production Tips&lt;/h2&gt;

&lt;p&gt;My talk discussed the following five topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Know when to Node&lt;/strong&gt;: &lt;em&gt;Should&lt;/em&gt; you use Node.js?&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Keep up with Node&lt;/strong&gt;: Stay up to date with Node.js and libraries.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Design for failure&lt;/strong&gt;: Fail and recover at &lt;em&gt;multiple levels&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Isolate services&lt;/strong&gt;: Separate &lt;em&gt;resource&lt;/em&gt; and &lt;em&gt;failure&lt;/em&gt; classes.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Analyze everything&lt;/strong&gt;: Data drives &lt;em&gt;problem discovery&lt;/em&gt; and &lt;em&gt;action&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is, of course, much more to running Node.js in production, but hopefully my talk and slides provide a bit of insight into some of the considerations to keep in mind when taking a Node.js application to production.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2013/04/10/sphinx-bootstrap-theme-bootswatch</id>
      <title>Sphinx Bootstrap Theme 0.2.0 - Now with Bootswatch!</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2013-04-10T11:30:00Z</published>
      <updated>2013-04-10T11:30:00Z</updated>
      <link href="http://loose-bits.com/2013/04/10/sphinx-bootstrap-theme-bootswatch.html"/>
      <content type="html">&lt;h2 id='bringing_bootswatch_to_sphinx'&gt;Bringing Bootswatch to Sphinx&lt;/h2&gt;

&lt;p&gt;The &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;Bootstrap Theme&lt;/a&gt; for &lt;a href='http://sphinx.pocoo.org/'&gt;Sphinx&lt;/a&gt; documentation continues to evolve, and I&amp;#8217;m pleased to announce the v0.2.0 release. The theme integrates &lt;a href='https://twitter.com/'&gt;Twitter&lt;/a&gt;&amp;#8217;s &lt;a href='http://twitter.github.com/bootstrap/'&gt;Bootstrap&lt;/a&gt; library into Sphinx, and v0.2.0 adds new support for &lt;a href='http://bootswatch.com/'&gt;Bootswatch&lt;/a&gt; CSS extensions, which provide a lot of great new UI possibilities for your documentation. We&amp;#8217;ve also internally updated to Bootstrap 2.3.1.&lt;/p&gt;

&lt;p&gt;The project&amp;#8217;s &lt;a href='http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html'&gt;demonstration site&lt;/a&gt; now uses the &amp;#8221;&lt;a href='http://bootswatch.com/united/'&gt;united&lt;/a&gt;&amp;#8221; Bootswatch theme to give a clean and well&amp;#8230; &lt;em&gt;orange-ish&lt;/em&gt; feel to the site. Here is a screenshot of the demonstration site in a desktop viewport:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2013/04/10/demo_desk.png'&gt;&lt;img src='http://loose-bits.com/media/img/2013/04/10/demo_desk_th.png' alt='Theme with Bootswatch' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is available for download from &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;GitHub&lt;/a&gt; and &lt;a href='http://pypi.python.org/pypi/sphinx-bootstrap-theme/0.2.0'&gt;PyPi&lt;/a&gt;. You are encouraged to update and try out some Bootswatch goodness!&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='adding_bootswatch'&gt;Adding Bootswatch&lt;/h2&gt;

&lt;p&gt;As a preliminary note, the use of Bootswatch themes is entirely optional, and disabled by default - the project starts with a vanilla Bootstrap appearance, which you can customize to your heart&amp;#8217;s content. To enable a specific Bootswatch theme, set the following option in your &amp;#8221;&lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py'&gt;conf.py&lt;/a&gt;&amp;#8221; file:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;html_theme_options&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;# Bootswatch (http://bootswatch.com/) theme.&lt;/span&gt;
    &lt;span class='c'&gt;#&lt;/span&gt;
    &lt;span class='c'&gt;# Options are nothing with &amp;quot;&amp;quot; (default) or the name of a valid theme such&lt;/span&gt;
    &lt;span class='c'&gt;# as &amp;quot;amelia&amp;quot; or &amp;quot;cosmo&amp;quot;.&lt;/span&gt;
    &lt;span class='c'&gt;#&lt;/span&gt;
    &lt;span class='c'&gt;# Note that this is served off CDN, so won&amp;#39;t be available offline.&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;bootswatch_theme&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;united&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The list of Bootswatch themes you can now try out include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://bootswatch.com/amelia/'&gt;&lt;strong&gt;amelia&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/cerulean/'&gt;&lt;strong&gt;cerulean&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/cosmo/'&gt;&lt;strong&gt;cosmo&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/cyborg/'&gt;&lt;strong&gt;cyborg&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/journal/'&gt;&lt;strong&gt;journal&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/readable/'&gt;&lt;strong&gt;readable&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/simplex/'&gt;&lt;strong&gt;simplex&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/slate/'&gt;&lt;strong&gt;slate&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/spacelab/'&gt;&lt;strong&gt;spacelab&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/spruce/'&gt;&lt;strong&gt;spruce&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/superhero/'&gt;&lt;strong&gt;superhero&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://bootswatch.com/united/'&gt;&lt;strong&gt;united&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a final note, the Bootstrap CSS files are served off of &lt;strong&gt;CDN&lt;/strong&gt;, which means that if you are offline, your site will have a non-themed, vanilla Bootstrap appearance. We are considering refactoring the Bootswatch code to be available offline as well.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2013/02/12/sphinx-bootstrap-theme-updates</id>
      <title>Sphinx Bootstrap Theme 0.1.6 - Bootstrap and Other Updates</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2013-02-12T11:30:00Z</published>
      <updated>2013-02-12T11:30:00Z</updated>
      <link href="http://loose-bits.com/2013/02/12/sphinx-bootstrap-theme-updates.html"/>
      <content type="html">&lt;h2 id='sphinx_bootstrap_theme'&gt;Sphinx Bootstrap Theme&lt;/h2&gt;

&lt;p&gt;The &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;Sphinx Bootstrap Theme&lt;/a&gt; is an extension for the &lt;a href='http://sphinx.pocoo.org/'&gt;Sphinx&lt;/a&gt; documentation tool, used for &lt;a href='http://python.org/'&gt;Python&lt;/a&gt;-based API documentation and static website authoring. The theme integrates &lt;a href='https://twitter.com/'&gt;Twitter&lt;/a&gt; &lt;a href='http://twitter.github.com/bootstrap/'&gt;Bootstrap&lt;/a&gt;, which is a wildly popular frontend framework. The &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;theme GitHub project&lt;/a&gt; provides a &lt;a href='http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html'&gt;demonstration site&lt;/a&gt; using the theme.&lt;/p&gt;

&lt;p&gt;The theme project continues to move along, and as it has been a while since my &lt;a href='http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates.html'&gt;last update post&lt;/a&gt;, here are some of the changes in version &lt;a href='http://pypi.python.org/pypi/sphinx-bootstrap-theme/0.1.6'&gt;0.1.6&lt;/a&gt; (since 0.1.0):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update to Bootstrap v2.3.0.&lt;/li&gt;

&lt;li&gt;Enable configuration of the depth of the Global TOC (&amp;#8220;Site&amp;#8221; tab).&lt;/li&gt;

&lt;li&gt;Add footer divider and various other styling tweaks.&lt;/li&gt;

&lt;li&gt;Add Bootstrap styling for tables and inline code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, much of the underlying rendering JavaScript code has been revised. For example, Twitter Bootstrap v2.3.0 handles behavior like dropdown menu clicks in mobile much better than previous versions, enabling removal of some hacks and patches in the theme.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='theme_options'&gt;Theme Options&lt;/h2&gt;

&lt;p&gt;Here is the current list of options you can set for the theme in the &amp;#8220;conf.py&amp;#8221; configuration file, as used by the demo site in &amp;#8221;&lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py'&gt;conf.py&lt;/a&gt;&amp;#8221;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;html_theme_options&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;# Global TOC depth for &amp;quot;site&amp;quot; navbar tab. (Default: 1)&lt;/span&gt;
    &lt;span class='c'&gt;# Switching to -1 shows all levels.&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;globaltoc_depth&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='c'&gt;# HTML navbar class (Default: &amp;quot;navbar&amp;quot;) to attach to &amp;lt;div&amp;gt; element.&lt;/span&gt;
    &lt;span class='c'&gt;# For black navbar, do &amp;quot;navbar navbar-inverse&amp;quot;&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;navbar_class&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;navbar navbar-inverse&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='c'&gt;# Fix navigation bar to top of page?&lt;/span&gt;
    &lt;span class='c'&gt;# Values: &amp;quot;true&amp;quot; (default) or &amp;quot;false&amp;quot;&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;navbar_fixed_top&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='c'&gt;# Location of link to source.&lt;/span&gt;
    &lt;span class='c'&gt;# Options are &amp;quot;nav&amp;quot; (default), &amp;quot;footer&amp;quot; or anything else to exclude.&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;source_link_position&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;nav&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates</id>
      <title>Sphinx Bootstrap Theme Updates - Mobile, Dropdowns, and More</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-11-19T11:30:00Z</published>
      <updated>2012-11-19T11:30:00Z</updated>
      <link href="http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates.html"/>
      <content type="html">&lt;h2 id='sphinx_bootstrap_theme_updates'&gt;Sphinx Bootstrap Theme Updates!&lt;/h2&gt;

&lt;p&gt;Almost a year ago, I created the &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;Sphinx Bootstrap Theme&lt;/a&gt; project to bring together two of my favorite open source technologies - Sphinx and Twitter Bootstrap. &lt;a href='http://sphinx.pocoo.org/'&gt;Sphinx&lt;/a&gt; is a widely-used &lt;a href='http://python.org/'&gt;Python&lt;/a&gt;-based authoring tool for creating static websites and API documentation. &lt;a href='https://twitter.com/'&gt;Twitter&lt;/a&gt; &lt;a href='http://twitter.github.com/bootstrap/'&gt;Bootstrap&lt;/a&gt; is a frontend JavaScript / CSS framework that offers a consistent UI experience, typography, grid systems, navigation / button / other components, and responsive features for mobile design.&lt;/p&gt;

&lt;p&gt;Since its introduction, I now use Sphinx with the Bootstrap Theme for my open source Python projects and we use the theme for all internal technical documentation at work. Coming up on the project&amp;#8217;s anniversary, it seemed appropriate to add some tweaks and enhancements to increase the usability and extensibility of the theme.&lt;/p&gt;

&lt;p&gt;Improvements now available in version 0.1.0 include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upgraded the Bootstrap libraries to version 2.2.1.&lt;/li&gt;

&lt;li&gt;Added new theme options to control UI appearance and behavior.&lt;/li&gt;

&lt;li&gt;Navigation bar menus now have hierarchical sub-menus for a more intuitive experience.&lt;/li&gt;

&lt;li&gt;Reworked the underlying layout code to support fully responsive design, allowing any site to look great on mobile devices like an iPhone or iPad.&lt;/li&gt;

&lt;li&gt;Created a PyPI package to permit installation via &lt;code&gt;pip install sphinx_bootstrap_theme&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let&amp;#8217;s see how everything looks and works with the new v0.1.0 theme.&lt;/p&gt;

&lt;h2 id='theme_options'&gt;Theme Options&lt;/h2&gt;

&lt;p&gt;The theme has several new options that you can set in the &amp;#8220;conf.py&amp;#8221; configuration file.&lt;/p&gt;

&lt;h3 id='bootstrap_theme_demo_site'&gt;Bootstrap Theme Demo Site&lt;/h3&gt;

&lt;p&gt;Let&amp;#8217;s start with the Bootstrap Theme&amp;#8217;s own &lt;a href='http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html'&gt;demonstration website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_desk.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_desk.png' alt='Sphinx Bootstrap Theme Desktop View' /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;The demo site has the following theme option settings in &amp;#8221;&lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py'&gt;conf.py&lt;/a&gt;&amp;#8221;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;html_theme_options&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;# HTML navbar class (Default: &amp;quot;navbar&amp;quot;) to attach to &amp;lt;div&amp;gt;.&lt;/span&gt;
    &lt;span class='c'&gt;# For black navbar, do &amp;quot;navbar navbar-inverse&amp;quot;&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;navbar_class&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;navbar navbar-inverse&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='c'&gt;# Fix navigation bar to top of page?&lt;/span&gt;
    &lt;span class='c'&gt;# Values: &amp;quot;true&amp;quot; (default) or &amp;quot;false&amp;quot;&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;navbar_fixed_top&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='c'&gt;# Location of link to source.&lt;/span&gt;
    &lt;span class='c'&gt;# Options are &amp;quot;nav&amp;quot; (default), &amp;quot;footer&amp;quot;.&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;source_link_position&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;nav&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notably, this gives us a dark navigation bar that is fixed to the top of the page during any scrolling.&lt;/p&gt;

&lt;h3 id='django_cloud_browser'&gt;Django Cloud Browser&lt;/h3&gt;

&lt;p&gt;&lt;a href='https://github.com/ryan-roemer/django-cloud-browser/'&gt;Django Cloud Browser&lt;/a&gt; is a reusable &lt;a href='https://www.djangoproject.com/'&gt;Django&lt;/a&gt; application for browsing and downloading files stored in various cloud datastores (e.g., Amazon Web Services S3, Rackspace Cloud Files). The &lt;a href='http://ryan-roemer.github.com/django-cloud-browser/'&gt;API documentation&lt;/a&gt; uses the Bootstrap Theme with (mostly) the default options.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_cb.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_cb.png' alt='Django Cloud Browser' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only real tweak is setting &lt;code&gt;&amp;#39;source_link_position&amp;#39;: &amp;quot;footer&amp;quot;&lt;/code&gt; to move the documentation source code link down from the navigation bar to the bottom of each page.&lt;/p&gt;

&lt;h2 id='navigation_menu_dropdowns'&gt;Navigation Menu Dropdowns&lt;/h2&gt;

&lt;p&gt;The navigation bar menus are now hierarchically arranged into dropdowns and sub-menus for easier navigation. This looks a lot better than the previous versions (which just used hacked-up CSS whitespace padding).&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_nav_dropdown_hl.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_nav_dropdown_hl.png' alt='Navbar Menu Dropdowns' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id='mobile_ui_support'&gt;Mobile UI Support&lt;/h2&gt;

&lt;p&gt;One of the most popular aspects of the Bootstrap library is its great support for &lt;a href='http://twitter.github.com/bootstrap/scaffolding.html#responsive'&gt;responsive mobile design&lt;/a&gt;. These responsive features are integrated into version 0.1.0 of the theme.&lt;/p&gt;

&lt;p&gt;The Bootstrap Theme now renders a collapsed, clickable navigation bar in smaller viewports (like a mobile phone and some tablet orientations). Viewing the theme demo site in an iPhone gives us a slightly more terse UI.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_ios.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_ios_th.png' alt='Mobile Phone View' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the right navigation bar button gives us a basic first-level dropdown menu.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_hl.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_hl_th.png' alt='Mobile Phone Navbar' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we can further click through sub-menus to navigate to the desired section or page of a website.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown_hl.png'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown_hl_th.png' alt='Mobile Phone Navbar Menu' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these and other responsive Bootstrap features, the Sphinx Bootstrap Theme now gives your API documentation or website real usability for mobile devices.&lt;/p&gt;

&lt;h2 id='installation'&gt;Installation&lt;/h2&gt;

&lt;p&gt;The Bootstrap Theme supports installation via &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/downloads'&gt;download&lt;/a&gt; as detailed in my &lt;a href='http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme.html#installation'&gt;previous blog post&lt;/a&gt; and now adds full &lt;a href='http://pypi.python.org/pypi/sphinx-bootstrap-theme'&gt;PyPI&lt;/a&gt; installation support as a Python package.&lt;/p&gt;

&lt;p&gt;To get the theme from PyPI, use &lt;code&gt;pip&lt;/code&gt; to install it:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;pip install sphinx_bootstrap_theme
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In your Sphinx &amp;#8220;conf.py&amp;#8221; configuration file, import the theme module at the top:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;sphinx_bootstrap_theme&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, configure the HTML theme values in &amp;#8220;conf.py&amp;#8221;, using the module to get values for &lt;code&gt;html_theme_path&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='c'&gt;# Activate the theme.&lt;/span&gt;
&lt;span class='n'&gt;html_theme&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;bootstrap&amp;#39;&lt;/span&gt;
&lt;span class='n'&gt;html_theme_path&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;sphinx_bootstrap_theme&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_html_theme_path&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And, that&amp;#8217;s pretty much it. The theme should now be available to Sphinx for documentation building.&lt;/p&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The Bootstrap Theme brings Twitter Bootstrap to Sphinx sites and with version 0.1.0 adds even more useful Bootstrap features and options. So try it out, see if Bootstrap is right for your Sphinx website, and please pass on any feedback (&lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/issues'&gt;issue reports&lt;/a&gt;, &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/pulls'&gt;pull requests&lt;/a&gt;, etc.).&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/11/15/nodedc-amd-requirejs-talk</id>
      <title>Shared JavaScript Code with AMD/RequireJS</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-11-15T11:30:00Z</published>
      <updated>2012-11-15T11:30:00Z</updated>
      <link href="http://loose-bits.com/2012/11/15/nodedc-amd-requirejs-talk.html"/>
      <content type="html">&lt;h2 id='amdrequirejs'&gt;AMD/RequireJS&lt;/h2&gt;

&lt;p&gt;The &lt;a href='https://github.com/amdjs/amdjs-api/wiki/AMD'&gt;Asynchronous Module Definition&lt;/a&gt; (&lt;em&gt;AMD&lt;/em&gt;) provides a framework for predictable loading of JavaScript modules and dependencies. AMD is an extension of (and possible transport for) the &lt;a href='http://www.commonjs.org/'&gt;CommonJS&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;The &lt;a href='http://requirejs.org/'&gt;RequireJS&lt;/a&gt; library implements AMD with loading, bundling and other functionality. It brings sensible imports (with &lt;code&gt;define&lt;/code&gt; and &lt;code&gt;require&lt;/code&gt;) to frontend JavaScript, instead of the usual array of script includes into the global namespace. Interestingly, as &lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt; implements CommonJS-style &lt;code&gt;require&lt;/code&gt;&amp;#8217;s, there are also support modules to bring Node.js code under AMD.&lt;/p&gt;

&lt;p&gt;Take both of things together and we have the real potential for shared JavaScript code across the browser and server. As we use this exact approach at work for our new Backbone.js frontend / Express Node.js backend web application, I created a short talk that I presented at the November 11, 2012 &lt;a href='http://www.meetup.com/node-dc/events/89233812/'&gt;Node.DC&lt;/a&gt; meetup, entitled &amp;#8220;Shared Code with AMD/RequireJS&amp;#8221;.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/nodedc-requirejs-talk/'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/15/nodedc-requirejs.png' alt='AMD/RequireJS Talk' /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='source_code_and_demo'&gt;Source Code and Demo&lt;/h2&gt;

&lt;p&gt;The &lt;a href='https://github.com/ryan-roemer/nodedc-requirejs-talk/'&gt;source&lt;/a&gt; for the presentation is available on GitHub. The repository further includes all of the &lt;a href='https://github.com/ryan-roemer/nodedc-requirejs-talk/tree/master/demo'&gt;demonstration code&lt;/a&gt; I used to create a shared JavaScript library and then expose it via the frontend (as straight JavaScript) and the backend (as a REST service).&lt;/p&gt;

&lt;p&gt;The talk and the source give a good overview and run-through of how to implement simple shared code, but to give a better sense of the final result, here is a RequireJS-compliant code module (for shuffling elements in a string) that has an &lt;a href='http://underscorejs.org/'&gt;underscore&lt;/a&gt; dependency, and works in both the browser and as a standard module in Node.js:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// Node.js hook boilerplate.&lt;/span&gt;
&lt;span class='c1'&gt;// This adds the `define` function to Node.&lt;/span&gt;
&lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;typeof&lt;/span&gt; &lt;span class='nx'&gt;define&lt;/span&gt; &lt;span class='o'&gt;!==&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;function&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;define&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;amdefine&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)(&lt;/span&gt;&lt;span class='nx'&gt;module&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='c1'&gt;// Define wrapper gives us `require`&lt;/span&gt;
&lt;span class='nx'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kd'&gt;function&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='c1'&gt;// Looks like a normal `require` for Node.js.&lt;/span&gt;
  &lt;span class='c1'&gt;// Note: Both browser and Node.js should use&lt;/span&gt;
  &lt;span class='c1'&gt;// the **same** version of libraries.&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;_&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;underscore&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Slice a string into a list of items&lt;/span&gt;
  &lt;span class='c1'&gt;// and return the shuffled values.&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;shuffle&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;val&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;val&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;val&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nx'&gt;_&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;shuffle&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;val&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;split&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/[\s,]+/&lt;/span&gt;&lt;span class='p'&gt;));&lt;/span&gt;
  &lt;span class='p'&gt;};&lt;/span&gt;

  &lt;span class='c1'&gt;// Export our shuffle function by returning.&lt;/span&gt;
  &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nx'&gt;shuffle&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We add some boilerplate at the top of the library for Node.js, then use the &lt;code&gt;define&lt;/code&gt; function to make a &lt;code&gt;require&lt;/code&gt; import function available to both the browser and Node.js, and we&amp;#8217;re off and code sharing!&lt;/p&gt;

&lt;p&gt;AMD, RequireJS, and shared code are topics that have a lot more depth, features, and challenges. Hopefully this provides the start to a journey into better JavaScript dependency management and more extensible full stack web application development.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/11/05/deck-js-starter</id>
      <title>Deck.js Starter</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-11-05T10:00:00Z</published>
      <updated>2012-11-05T10:00:00Z</updated>
      <link href="http://loose-bits.com/2012/11/05/deck-js-starter.html"/>
      <content type="html">&lt;h2 id='deckjs_starter'&gt;Deck.js Starter&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://imakewebthings.com/deck.js/'&gt;Deck.js&lt;/a&gt; is a modern presentation framework for creating interactive HTML-based interactive slide decks. The framework provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic boilerplate.&lt;/li&gt;

&lt;li&gt;CSS themes.&lt;/li&gt;

&lt;li&gt;Extensions including a menu, &amp;#8220;go to&amp;#8221; button, transitions, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a presentation, simply edit the HTML files and host a static website and you have an auto-magic slide deck, available to the world.&lt;/p&gt;

&lt;p&gt;I have now done a couple of deck.js-based presentations, and find it incredibly useful, but noticed I was re-implementing the same scaffolding enhancements. I wrapped up these common enhancements in a GitHub project, &lt;a href='https://github.com/ryan-roemer/deck.js-starter'&gt;Deck.js Starter&lt;/a&gt;. The notable enhancements the starter kit has are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://jade-lang.com'&gt;Jade templates&lt;/a&gt; with &lt;a href='http://daringfireball.net/projects/markdown/'&gt;Markdown&lt;/a&gt; support for faster slide authoring.&lt;/li&gt;

&lt;li&gt;Executable JavaScript code snippets using &lt;a href='http://codemirror.net/'&gt;CodeMirror&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;Executable CoffeeScript code snippets via some specific tweaks I made to the &lt;a href='https://github.com/iros/deck.js-codemirror'&gt;CodeMirror deck.js extension&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, you can download and install the starter kit, and just start authoring new slides. The starting deck provides a short demo and is available at:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/deck.js-starter'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/05/deck_slides.png' alt='Deck.js Starter' /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='deckjs_introduction'&gt;Deck.js Introduction&lt;/h2&gt;

&lt;p&gt;Getting up to speed &lt;a href='http://imakewebthings.com/deck.js/'&gt;deck.js&lt;/a&gt; is pretty straightforward: check out a &lt;a href='http://imakewebthings.com/deck.js/introduction'&gt;tutorial / exemplary demo&lt;/a&gt; or dive right into the &lt;a href='http://imakewebthings.com/deck.js/docs/'&gt;docs&lt;/a&gt; or &lt;a href='https://github.com/imakewebthings/deck.js/wiki'&gt;wiki&lt;/a&gt;. After the basics are mastered, there are plenty of avenues for further exploration / additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Themes&lt;/strong&gt;: Deck.js includes three by default and there are several more available as open source projects.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Extensions&lt;/strong&gt;: Deck.js includes a menu, navigation and other controls by default. There is a fantastic open source ecosystem around deck providing all manner of additional extensions. To review a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote control a presentation.&lt;/li&gt;

&lt;li&gt;Add a laser pointer to the presentation online.&lt;/li&gt;

&lt;li&gt;Easy &amp;#8220;presenter&amp;#8221; views.&lt;/li&gt;

&lt;li&gt;&amp;#8230; and many more!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;: Several open source projects offer meta-level enhancements like markdown support, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='working_with_deckjs'&gt;Working with Deck.js&lt;/h2&gt;

&lt;p&gt;I have used deck for two of my presentations to date. My basic workflow was to fork the repository from GitHub, then make my edits and add in extensions. Finally, I pushed the static web project to GitHub Pages for free hosting.&lt;/p&gt;

&lt;p&gt;For a better sense of what the end products look like, here are the two live presentation decks.&lt;/p&gt;

&lt;h3 id='getting_started_with_nodejs_in_the_cloud'&gt;Getting Started with Node.js in the Cloud&lt;/h3&gt;

&lt;p&gt;My talk to the Nova Node meetup on using Node.js in the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/novanode-cloud-talk/'&gt;&lt;img src='http://loose-bits.com/media/img//2012/03/24/nodejs-cloud.png' alt='Node.js cloud presentation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='5_things_i_like_about_coffeescript'&gt;5 Things I Like About CoffeeScript&lt;/h3&gt;

&lt;p&gt;A simple CoffeeScript survey I presented at the Node.DC meetup.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/nodedc-coffeescript-talk/'&gt;&lt;img src='http://loose-bits.com/media/img//2012/08/16/nodedc-coffeescript.png' alt='CoffeeScript Talk' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id='a_starter_kit'&gt;A Starter Kit&lt;/h2&gt;

&lt;p&gt;After writing these presentations and looking to my next one, I realized I was writing the same boilerplate again, and decided to finally wrap everything up in a concise, opinionated starting point: &lt;a href='https://github.com/ryan-roemer/deck.js-starter'&gt;Deck.js Starter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started, checkout the &lt;a href='http://ryan-roemer.github.com/deck.js-starter'&gt;online demo&lt;/a&gt;, and then review the &lt;a href='https://github.com/ryan-roemer/deck.js-starter/blob/master/README.md'&gt;README&lt;/a&gt;. The &amp;#8220;tl;dr&amp;#8221; version is:&lt;/p&gt;

&lt;p&gt;First, clone the repository:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git clone https://github.com/ryan-roemer/deck.js-starter.git my-presentation&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, make sure Node.js is installed, and install all the necessary support packages.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd my-presentation
$ npm install&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you are ready to author, start the &amp;#8220;watch&amp;#8221; task that will automatically build &amp;#8221;&lt;a href='https://github.com/ryan-roemer/deck.js-starter/blob/master/layout.jade'&gt;layout.jade&lt;/a&gt;&amp;#8221; and &amp;#8221;&lt;a href='https://github.com/ryan-roemer/deck.js-starter/blob/master/index.jade'&gt;index.jade&lt;/a&gt;&amp;#8221; into the &amp;#8220;index.html&amp;#8221; static page. You can then preview your slide deck at &amp;#8220;index.html&amp;#8221;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ npm run-script watch&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Authoring is pretty easy. To add themes, extensions, or extra scripts, edit &amp;#8221;&lt;a href='https://github.com/ryan-roemer/deck.js-starter/blob/master/layout.jade'&gt;layout.jade&lt;/a&gt;&amp;#8221;. For slides, edit &amp;#8221;&lt;a href='https://github.com/ryan-roemer/deck.js-starter/blob/master/index.jade'&gt;index.jade&lt;/a&gt;&amp;#8221; and add &lt;code&gt;section.slide&lt;/code&gt; elements following the boilerplate as a guide. Make sure to take a look at the following possibilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable JavaScript&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/deck.js-starter/#js'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/05/deck_js.png' alt='Deck.js Starter' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable CoffeeScript&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/deck.js-starter/#cs'&gt;&lt;img src='http://loose-bits.com/media/img/2012/11/05/deck_cs.png' alt='Deck.js Starter' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After exploring the basics of the starter kit, feel free to add in any other Deck.js extensions or other useful components or tweaks. You&amp;#8217;ll end up with an interactive, visually appealing presentation that you can easily author and host online.&lt;/p&gt;

&lt;p&gt;As a parting thought, the Deck.js Starter was written in a couple of hours over the weekend. I do plan to enhance it as I author real presentations in the future (particularly with an eye towards adding more themes). But any feedback or enhancements are most welcome.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/09/30/responsive-web-design-twitter-bootstrap</id>
      <title>Responsive Web Design with Twitter Bootstrap</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-09-30T18:37:07Z</published>
      <updated>2012-09-30T18:37:07Z</updated>
      <link href="http://loose-bits.com/2012/09/30/responsive-web-design-twitter-bootstrap.html"/>
      <content type="html">&lt;h2 id='bootstrap'&gt;Bootstrap&lt;/h2&gt;

&lt;p&gt;Twitter&amp;#8217;s &lt;a href='http://twitter.github.com/bootstrap/'&gt;Bootstrap&lt;/a&gt; library is one of the most popular front-end frameworks, with an amazing adoption rate by various web sites, from small to large. Bootstrap provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Responsive Features&lt;/strong&gt;: Enables seamless transitions between desktop, tablet, and mobile view sizes, with intuitive look-and-feel switching and custom single-view overrides.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Layout Support&lt;/strong&gt;: Fixed and fluid grid systems for web design.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Style Components&lt;/strong&gt;: Really nice looking form helpers, fonts, tables, etc.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;UI Components&lt;/strong&gt;: Dropdown menus, buttons, accordions, alerts, progress bars and more, with JavaScript support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, Bootstrap makes it easy to design a responsive website (that looks good in desktop or mobile views) and add user interface components that keep with the overall user experience.&lt;/p&gt;

&lt;h2 id='moving_loose_bits_to_bootstrap'&gt;Moving Loose Bits to Bootstrap&lt;/h2&gt;

&lt;p&gt;Loose Bits (this blog) previously had a non-responsive design - the main heading bar was far too wide when viewed on a mobile phone (like my iPhone). I had been itching to make this site mobile-friendly, and finally decided to bite the bullet and integrate Bootstrap.&lt;/p&gt;

&lt;p&gt;I downloaded the &lt;a href='https://github.com/twitter/bootstrap'&gt;full bootstrap build&lt;/a&gt; from source, so that I could customize parts of the framework and only add in what I needed. As this blog is hosted as an open source project on &lt;a href='http://github.com'&gt;GitHub&lt;/a&gt;, the source code and build system is available for checkout or download at my &lt;a href='https://github.com/ryan-roemer/loose-bits'&gt;Loose Bits repository&lt;/a&gt;.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;While I did customize some colors and variables for Bootstrap and spent some time inserting Bootstrap-compatible CSS classes in my HTML layout, I have to say the overall experience was quite easy, required minimal changes, and I am pleased with the final website.&lt;/p&gt;

&lt;p&gt;The different layout effects for various viewports can be viewed simply by resizing a browser window on a desktop computer. In desktop view, I&amp;#8217;ve retooled the main header and navigation bar to look like:&lt;/p&gt;
&lt;div class='pull-center'&gt;
  &lt;img class='bordered' src='http://loose-bits.com/media/img/2012/09/30/desktop.png' /&gt;
  &lt;p /&gt;
&lt;/div&gt;
&lt;p&gt;My layout uses the basic &amp;#8220;inverse&amp;#8221; (dark) Bootstrap theme and integrates the fantastic &lt;a href='http://fortawesome.github.com/Font-Awesome/'&gt;Font Awesome&lt;/a&gt; icons for each navigation bar menu item.&lt;/p&gt;

&lt;h2 id='loose_bits_on_mobile'&gt;Loose Bits on Mobile!&lt;/h2&gt;

&lt;p&gt;Moving on to the mobile experience, the header and full navigation bars are replaced by a title-only heading with a clickable navigation menu button. Resizing a browser window down to mobile phone size (or if already on a mobile device), we get:&lt;/p&gt;
&lt;div class='pull-center'&gt;
  &lt;img class='bordered' src='http://loose-bits.com/media/img/2012/09/30/mobile.png' /&gt;
  &lt;p /&gt;
&lt;/div&gt;
&lt;p&gt;I&amp;#8217;ve used the simple navigation collapse feature to have a clickable menu of all of the previous navigation bar items, so as to more effectively use the reduced real estate in a mobile view:&lt;/p&gt;
&lt;div class='pull-center'&gt;
  &lt;img class='bordered' src='http://loose-bits.com/media/img/2012/09/30/mobile-menu.png' /&gt;
  &lt;p /&gt;
&lt;/div&gt;
&lt;p&gt;The rest of the website has a few other adjustments in mobile view mode, nearly all of which auto-magically happen without any further tweaks to Bootstrap configuration or JavaScript hacking.&lt;/p&gt;

&lt;p&gt;At the end of the day, I&amp;#8217;m pretty happy with the Bootstrap development experience and the end result for my website. Like any web framework under active development, there are still a few gotcha&amp;#8217;s - like some loss of browser compatibility and weird JavaScript bugs (e.g., my navigation buttons sometimes disappear). But, ultimately, for anyone searching for the fastest path to a reliable and intuitive responsive web site, I would definitely recommend looking at Twitter Bootstrap.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/08/16/five-things-i-like-about-coffeescript</id>
      <title>5 Things I Like About CoffeeScript</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-08-16T11:30:00Z</published>
      <updated>2012-08-16T11:30:00Z</updated>
      <link href="http://loose-bits.com/2012/08/16/five-things-i-like-about-coffeescript.html"/>
      <content type="html">&lt;h2 id='coffeescript'&gt;CoffeeScript&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://coffeescript.org/'&gt;CoffeeScript&lt;/a&gt; is self-described as &amp;#8220;a little language that compiles into JavaScript&amp;#8221;. CoffeeScript implements a subset of the full JavaScript language and adds a little bit of common boilerplate code to take out some of the less savory parts of JavaScript. CoffeeScript works both in &lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt; on the backend, as well as in the browser.&lt;/p&gt;

&lt;p&gt;I gave a short survey talk at the August 15, 2012 &lt;a href='http://www.meetup.com/node-dc/events/73746422/'&gt;Node.DC&lt;/a&gt; meetup, &amp;#8220;5 Things I Like About CoffeeScript&amp;#8221;.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/nodedc-coffeescript-talk/'&gt;&lt;img src='http://loose-bits.com/media/img/2012/08/16/nodedc-coffeescript.png' alt='CoffeeScript Talk' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The talk&amp;#8217;s &lt;a href='https://github.com/ryan-roemer/nodedc-coffeescript-talk/'&gt;source&lt;/a&gt; is available on GitHub, and uses the awesome &lt;a href='http://imakewebthings.com/deck.js/'&gt;deck.js&lt;/a&gt; presentation framework with the &lt;a href='https://github.com/iros/deck.js-codemirror'&gt;CodeMirror plugin&lt;/a&gt; to enable editable and runnable code samples. I further hacked up the CodeMirror plugin to additionally make the CoffeeScript code examples executable (CodeMirror only does JavaScript by default).&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='five_cool_things_in_coffeescript'&gt;Five Cool Things in CoffeeScript&lt;/h2&gt;

&lt;p&gt;For the impatient, the five topics I covered are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Syntax&lt;/li&gt;

&lt;li&gt;Functions&lt;/li&gt;

&lt;li&gt;Classes&lt;/li&gt;

&lt;li&gt;Existential Operator&lt;/li&gt;

&lt;li&gt;Loops, Comprehensions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The talk just skims the surface of the powerful (yet slim) CoffeeScript language. For further reading / real introductory tutorials, I would suggest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://coffeescript.org/'&gt;CoffeeScript Documentation&lt;/a&gt;: The official API.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://coffeescriptcookbook.com/'&gt;The CoffeeScript Cookbook&lt;/a&gt;: Lots of &amp;#8220;recipes&amp;#8221; for common programming problems and projects.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://autotelicum.github.com/Smooth-CoffeeScript/'&gt;Smooth Coffeescript&lt;/a&gt;: Interactive online eBook with live code execution and editing.&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/08/02/nodejs-read-write-streams-pipes</id>
      <title>Better Data Slinging with Node.js Readable/Writable Streams and Pipes</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-08-02T14:00:00Z</published>
      <updated>2012-08-02T14:00:00Z</updated>
      <link href="http://loose-bits.com/2012/08/02/nodejs-read-write-streams-pipes.html"/>
      <content type="html">&lt;h2 id='nodejs_streams'&gt;Node.js Streams&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt; provides an extensible and fast platform for web servers, proxies, and middle-tier services. Node.js applications often utilize some transformation from one data format (e.g., a database or cloud store) to another (e.g., an HTML or JSON page).&lt;/p&gt;

&lt;p&gt;Most folks are familiar with the callback-style of hooking together various JavaScript data components in a Node.js program. However, an often overlooked and very powerful data binding abstraction for Node.js is found in the &lt;a href='http://nodejs.org/api/stream.html'&gt;stream&lt;/a&gt; class.&lt;/p&gt;

&lt;p&gt;Streams are an abstract interface for data objects in Node.js which can be readable and/or writable. They can be hooked from one to another in a similar style to Unix pipes &amp;#8211; in fact, the stream operation we&amp;#8217;ll mostly focus on here is the not-coincidentally-named &lt;code&gt;pipe()&lt;/code&gt;. Some potential advantages of stream pipes over other binding styles include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Often much less code for the actual binding (can just call &lt;code&gt;pipe()&lt;/code&gt;).&lt;/li&gt;

&lt;li&gt;Streams can handle pausing / resuming of data flows. Implementing classes, however, have to implement this logic internally if supported.&lt;/li&gt;

&lt;li&gt;Don&amp;#8217;t have to set specific callbacks or listeners for intermediate data events &amp;#8211; just &lt;code&gt;pipe()&lt;/code&gt; the stream and forget it.&lt;/li&gt;

&lt;li&gt;Avoiding buffering by processing data and re-emitting it directly to another stream (unless &lt;em&gt;all&lt;/em&gt; of the data is required in one chunk).&lt;/li&gt;

&lt;li&gt;Compatible with the many Node.js core modules that already implement streams, including HTTP, HTTPS, and file and process I/O.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a brief example, we can create a download client to retrieve a web page and write it to a file as follows (ignoring error handling):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// Get Google&amp;#39;s home page.&lt;/span&gt;
&lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;http://www.google.com/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;response&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='c1'&gt;// The callback provides the response readable stream.&lt;/span&gt;
  &lt;span class='c1'&gt;// Then, we open our output text stream.&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;outStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;createWriteStream&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;out.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Pipe the input to the output, which writes the file.&lt;/span&gt;
  &lt;span class='nx'&gt;response&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;outStream&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that we didn&amp;#8217;t set any explicit &amp;#8221;&lt;code&gt;data&lt;/code&gt;&amp;#8221; listeners or buffer any of the data, even if it came in as chunks. We simply &lt;code&gt;pipe()&lt;/code&gt;&amp;#8216;ed it with our two stream objects: &lt;code&gt;response&lt;/code&gt; and &lt;code&gt;outStream&lt;/code&gt;. The output of &lt;code&gt;response&lt;/code&gt; is hooked to the input of &lt;code&gt;outStream&lt;/code&gt; and we&amp;#8217;re done.&lt;/p&gt;

&lt;p&gt;More importantly (as we&amp;#8217;ll get to below), we can add many more &lt;code&gt;pipe()&lt;/code&gt; calls to do other transformations / data-slinging inline to our chained call. Ultimately, it just takes a little bit of glue code to hook together data flows in a terse and efficient manner with streams.&lt;/p&gt;

&lt;h2 id='the_stream_interfaces'&gt;The Stream Interfaces&lt;/h2&gt;

&lt;p&gt;So how do we do this for our own classes and objects?&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;The Node.js &lt;a href='http://nodejs.org/api/stream.html'&gt;streams&lt;/a&gt; documentation offers the full rundown of how to implement the interfaces, but we&amp;#8217;ll give an abbreviated version here to get going. All of the code examples discussed in this post are available as a GitHub &lt;a href='https://gist.github.com/3221453'&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id='readable_streams'&gt;Readable Streams&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://nodejs.org/api/stream.html#stream_readable_stream'&gt;Readable Streams&lt;/a&gt; must emit &amp;#8221;&lt;code&gt;data&lt;/code&gt;&amp;#8221; events whenever they have data to be read and &amp;#8221;&lt;code&gt;end&lt;/code&gt;&amp;#8221; when the data stream is finished. The implementing constructor should also set &lt;code&gt;this.readable = true&lt;/code&gt;. The interface additionally provides a lot more implementer flexibility for things like pausing and resuming a stream, as well as resource management and cleanup.&lt;/p&gt;

&lt;h3 id='writable_streams'&gt;Writable Streams&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://nodejs.org/api/stream.html#stream_writable_stream'&gt;Writable Streams&lt;/a&gt; must implement the &lt;code&gt;write()&lt;/code&gt; method to accept new data chunks into the stream and the &lt;code&gt;end()&lt;/code&gt; method to instruct the stream that the data is finished. The implementing constructor should also set &lt;code&gt;this.writable = true&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id='putting_it_together'&gt;Putting it Together&lt;/h3&gt;

&lt;p&gt;Let&amp;#8217;s take a look at a simple example of a custom-implemented readable and writable stream that simply passes through data &amp;#8211; data input is simply emitted unchanged as output.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// Set both readable and writable in constructor.&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;readable&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;writable&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='c1'&gt;// Inherit from base stream class.&lt;/span&gt;
&lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;util&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;inherits&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;stream&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;));&lt;/span&gt;

&lt;span class='c1'&gt;// Extract args to `write` and emit as `data` event.&lt;/span&gt;
&lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;write&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;args&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;Array&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;slice&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;arguments&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;emit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;apply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;].&lt;/span&gt;&lt;span class='nx'&gt;concat&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;args&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='c1'&gt;// Extract args to `end` and emit as `end` event.&lt;/span&gt;
&lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;args&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;Array&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;slice&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;arguments&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;emit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;apply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;].&lt;/span&gt;&lt;span class='nx'&gt;concat&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;args&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is essentially the bare minimum for a readable and writable stream class. Not too much work! And for more complicated streams, we can simply augment &lt;code&gt;write&lt;/code&gt;/&lt;code&gt;data&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;/&lt;code&gt;end&lt;/code&gt; to do whatever data transformations we want.&lt;/p&gt;

&lt;p&gt;We can now take the web scraping example from above and add the pass-through stream in the middle with the same effect &amp;#8211; we still get a file written to output.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// Download the same page again, but with the NOP stream&lt;/span&gt;
&lt;span class='c1'&gt;// in the middle.&lt;/span&gt;
&lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;http://www.google.com/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;response&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;outStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;createWriteStream&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;out.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='nx'&gt;response&lt;/span&gt;
    &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
    &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;outStream&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Checking the output file (&amp;#8220;out.txt&amp;#8221;), we can see the download results are the same as our original example.&lt;/p&gt;

&lt;p&gt;And in fact, we could even reuse the pass-through stream multiple times to illustrate repeated &lt;code&gt;pipe()&lt;/code&gt; data flows (although there&amp;#8217;s absolutely no practical sense to the following):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;response&lt;/span&gt;
  &lt;span class='c1'&gt;// Wow, that&amp;#39;s a lot of nop&amp;#39;s!&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;NopStream&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
  &lt;span class='c1'&gt;// OK, finally write out to file.&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;outStream&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So that&amp;#8217;s the basics. Let&amp;#8217;s look at creating something a tad more useful.&lt;/p&gt;

&lt;h2 id='lets_uppercase_some_data'&gt;Let&amp;#8217;s Upper-case Some Data!&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;UpperCaseStream&lt;/code&gt; class takes a data source (in string or &lt;code&gt;Buffer&lt;/code&gt; format) and converts string data into upper case letters. Not ultimately that useful or extensible, but it&amp;#8217;s definitely a data transformation that can illustrate the ease of creation and use of a custom stream.&lt;/p&gt;

&lt;p&gt;We mostly take our simple pass-through stream above and add a custom &lt;code&gt;_transform&lt;/code&gt; helper method to transform the data in either a &lt;code&gt;write()&lt;/code&gt; or &lt;code&gt;end()&lt;/code&gt; call, then re-emit the upper-cased data in a &lt;code&gt;data&lt;/code&gt; event.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * A simple upper-case stream converter.&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;readable&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;writable&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;util&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;inherits&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;stream&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;));&lt;/span&gt;

&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * Handle various params and upper-case string data.&lt;/span&gt;
&lt;span class='cm'&gt; *&lt;/span&gt;
&lt;span class='cm'&gt; * Signature can be in format of:&lt;/span&gt;
&lt;span class='cm'&gt; *  - string, [encoding]&lt;/span&gt;
&lt;span class='cm'&gt; *  - buffer&lt;/span&gt;
&lt;span class='cm'&gt; *&lt;/span&gt;
&lt;span class='cm'&gt; * Our example implementation hacks the data into a simpler&lt;/span&gt;
&lt;span class='cm'&gt; # (string) form -- real implementations would need more.&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;_transform&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;data&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='c1'&gt;// Here, we&amp;#39;ll just shortcut to a string.&lt;/span&gt;
  &lt;span class='nx'&gt;data&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;data&lt;/span&gt; &lt;span class='o'&gt;?&lt;/span&gt; &lt;span class='nx'&gt;data&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;toString&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

  &lt;span class='c1'&gt;// Upper-case the string and emit data event with transformed data.&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;emit&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;data&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;toUpperCase&lt;/span&gt;&lt;span class='p'&gt;());&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * Stream write (override).&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;write&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;_transform&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;apply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;arguments&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * Stream end (override).&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;prototype&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;_transform&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;apply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;arguments&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;emit&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;end&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To test things out, we can take an input file (&amp;#8220;input.txt&amp;#8221;), read it in, upper case all text, then write it out to &amp;#8220;out.txt&amp;#8221; using three streams.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;fs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;fs&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;input&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;fs&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;createReadStream&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;input.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;output&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;fs&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;createWriteStream&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;out.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;upperCase&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nx'&gt;UpperCaseStream&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;

&lt;span class='c1'&gt;// Open our read input, uppercase it, then write out.&lt;/span&gt;
&lt;span class='nx'&gt;input&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;upperCase&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;output&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The resulting output file is now uppercased! All in all, not that amazing, but considering the ease of our implementation, other (more useful) read/write stream applications could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XML to JSON conversion.&lt;/li&gt;

&lt;li&gt;Unzipping zipped data.&lt;/li&gt;

&lt;li&gt;Image resizing.&lt;/li&gt;

&lt;li&gt;&amp;#8230; any other transformations you&amp;#8217;d like to use with your existing streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Streams provide a great means of binding together lots of data in a sane and manageable way. Beyond the core library documents, there are a lot of great stream introductions for further reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://blog.dump.ly/post/19819897856/why-node-js-streams-are-awesome'&gt;&amp;#8220;Why Node.js Streams are Awesome&amp;#8221;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://docs.jit.su/articles/advanced/streams/how-to-use-stream-pipe'&gt;&amp;#8220;How to Use stream.pipe&amp;#8221;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://felixge.s3.amazonaws.com/11/nodejs-streams.pdf'&gt;&amp;#8220;Streams, Pipes and Mega Pipes&amp;#8221;&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://maxogden.com/node-streams'&gt;&amp;#8220;Node Streams: How do they work?&amp;#8221;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/03/24/novanode-nodejs-cloud-presentation</id>
      <title>Getting Started with Node.js in the Cloud - Presentation</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-03-24T22:08:21Z</published>
      <updated>2012-03-24T22:08:21Z</updated>
      <link href="http://loose-bits.com/2012/03/24/novanode-nodejs-cloud-presentation.html"/>
      <content type="html">&lt;h2 id='getting_started_with_nodejs_in_the_cloud'&gt;Getting Started with Node.js in the Cloud&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://www.meetup.com/Nova-Node'&gt;NovaNode&lt;/a&gt; had its &lt;a href='http://www.meetup.com/Nova-Node/events/52749282/'&gt;first meetup event&lt;/a&gt; on March 20, 2012 at &lt;a href='http://spanishdict.com/'&gt;SpanishDict&lt;/a&gt;&amp;#8217;s offices. I gave a talk titled &amp;#8220;Getting Started with Node.js in the Cloud&amp;#8221; covering Node.js application development and deployment to the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/novanode-cloud-talk/'&gt;&lt;img src='http://loose-bits.com/media/img/2012/03/24/nodejs-cloud.png' alt='Node.js cloud presentation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My talk goes through a first simple &amp;#8220;hello world&amp;#8221; application and builds up to a realtime chat application using Redis and websockets. We deploy the applications to &lt;a href='http://www.heroku.com/'&gt;Heroku&lt;/a&gt; and cover everything from logs and monitoring to add-on services and scaling. The talk concludes with an assessment and use cases for deploying on a Platform-as-a-Service provider like Heroku versus an Infrastructure-as-a-Service provider like &lt;a href='http://aws.amazon.com/'&gt;Amazon Web Services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href='http://ryan-roemer.github.com/novanode-cloud-talk/'&gt;presentation&lt;/a&gt; is available live on GitHub, and uses the &lt;a href='http://imakewebthings.com/deck.js/'&gt;deck.js&lt;/a&gt; presentation framework. I have also posted the full demo &lt;a href='https://github.com/ryan-roemer/novanode-cloud-talk/'&gt;source code&lt;/a&gt; to GitHub, including the chat server that we used during the meetup. Try it out!&lt;/p&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/03/24/return-oriented-programming-acm-tissec-paper</id>
      <title>Return-Oriented Programming - Systems, Languages, and Applications</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-03-24T16:40:56Z</published>
      <updated>2012-03-24T16:40:56Z</updated>
      <link href="http://loose-bits.com/2012/03/24/return-oriented-programming-acm-tissec-paper.html"/>
      <content type="html">&lt;h2 id='returnoriented_programming'&gt;Return-Oriented Programming&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://en.wikipedia.org/wiki/Return-oriented_programming'&gt;Return-oriented programming&lt;/a&gt; is a software exploit technique to take over a program by diverting control flow without injecting any code. At UCSD, I did most of my graduate research around this specific attack, working with &lt;a href='http://ucsd.erikbuchanan.com/'&gt;Erik Buchanan&lt;/a&gt;, &lt;a href='http://cseweb.ucsd.edu/~hovav/'&gt;Hovav Shacham&lt;/a&gt;, and &lt;a href='http://cseweb.ucsd.edu/~savage/'&gt;Stefan Savage&lt;/a&gt;. Now three years later, the ACM Transactions of Information and System Security (TISSEC) journal has published our full article, &amp;#8221;&lt;a href='http://dl.acm.org/citation.cfm?id=2133377'&gt;Return-Oriented Programming: Systems, Languages, and Applications&lt;/a&gt;&amp;#8221; in the March 2012 issue.&lt;/p&gt;

&lt;p&gt;For a great introduction to return-oriented programming attacks, see Hovav&amp;#8217;s Black Hat presentation, &amp;#8221;&lt;a href='http://cseweb.ucsd.edu/~hovav/dist/blackhat08.pdf'&gt;Return-oriented Programming: Exploitation without Code Injection&lt;/a&gt;&amp;#8221;.&lt;/p&gt;

&lt;p&gt;By way of a little background, software exploit techniques such as buffer overflows traditionally injected code into a vulnerable buffer, and then pointed control to that injected code whereby the attacker executed their own instructions. The original attack was succinctly described in Aleph One&amp;#8217;s &amp;#8221;&lt;a href='http://insecure.org/stf/smashstack.html'&gt;Smashing The Stack For Fun And Profit&lt;/a&gt;&amp;#8221;.&lt;/p&gt;

&lt;p&gt;Software vendors responded to injected code attacks with various defenses, one popular being &amp;#8220;write XOR execute&amp;#8221; (&amp;#8220;W&amp;#8853;X&amp;#8221;), whereby memory is either writable (e.g., data structure storage) or executable (e.g., library code), but &lt;strong&gt;not both&lt;/strong&gt;. The logic behind the defense was that an attacker could inject arbitrary data in writable memory but could not execute it, thus preventing the classic type of buffer overflow attack.&lt;/p&gt;

&lt;p&gt;However, preventing code &lt;em&gt;injection&lt;/em&gt; is not sufficient to prevent arbitrary &lt;em&gt;computation&lt;/em&gt;. Following a long line of W&amp;#8853;X exploit research, return-oriented programming works by taking existing executable code (e.g., loaded libraries) and using small chunks of it in unintended ways by hijacking control flow via compromising the stack or writable memory.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='returnoriented_programming_systems_languages_and_applications'&gt;Return-Oriented Programming: Systems, Languages, and Applications&lt;/h2&gt;

&lt;p&gt;Erik and I became interested in return-oriented programming when we read Hovav&amp;#8217;s paper that introduced the technique, &amp;#8221;&lt;a href='http://cseweb.ucsd.edu/~hovav/papers/s07.html'&gt;The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86).&lt;/a&gt;&amp;#8221;. Hovav focused on the x86 architecture and hypothesized that the attack would possibly be mitigated on a RISC architecture.&lt;/p&gt;

&lt;p&gt;Fortunately, Erik and I were both TA&amp;#8217;s for the SPARC (a RISC architecture) assembly programming course for undergraduates, and figured we could apply the attack further than originally anticipated. We were extremely lucky to get Hovav on board with our research and to later succeed in porting the attack to the SPARC architecture. We presented our findings in a paper at the 2008 &lt;a href='http://www.sigsac.org/ccs/CCS2008/'&gt;ACM Conference on Computer and Communications Security&lt;/a&gt; (CCS). We were later invited to turn our conference paper into a full journal paper for TISSEC.&lt;/p&gt;

&lt;p&gt;We essentially coalesced both the x86 and SPARC research to be the definitive statement on return-oriented programming, and submitted our draft in early 2009. Academic publishing does not always move at the most rapid pace, and it took about three years to finally get &amp;#8220;Return-Oriented Programming: Systems, Languages, and Applications&amp;#8221; published.&lt;/p&gt;

&lt;p&gt;I think the paper is a good statement of the original development and first level extensions of the attack (and I&amp;#8217;m obviously biased in recommending it, as I&amp;#8217;m an author). But, it is worth pointing out that a &lt;strong&gt;lot&lt;/strong&gt; has happened in the time since we submitted our draft, and the state of the art in return-oriented programming has advanced &lt;em&gt;significantly&lt;/em&gt; past that described in our paper. Since our original research, the attack has found its way into &lt;a href='http://www.sciencedaily.com/releases/2009/08/090810161902.htm'&gt;voting machines&lt;/a&gt;, &lt;a href='http://www.slideshare.net/amiable_indian/cisco-ios-attack-defense-the-state-of-the-art'&gt;routers&lt;/a&gt;, &lt;a href='http://www.theregister.co.uk/2011/08/04/secret_iphone_hacking_tool/'&gt;mobile devices&lt;/a&gt; and other places. It is fascinating (and scary) to see the ways in which the technique has now entered the mainstream, but I&amp;#8217;m at least happy to have (in a small way) helped expose the problem to software vendors early to give them a chance to build up defenses against return-oriented programming.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/03/06/novanode-nodejs-cloud-meetup</id>
      <title>NovaNode Inaugural Meetup - Getting Started with Node.js in the Cloud</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-03-06T15:01:00Z</published>
      <updated>2012-03-06T15:01:00Z</updated>
      <link href="http://loose-bits.com/2012/03/06/novanode-nodejs-cloud-meetup.html"/>
      <content type="html">&lt;h2 id='nodejs_in_nova'&gt;Node.js in NoVA!&lt;/h2&gt;

&lt;p&gt;We recently launched the &lt;a href='http://www.meetup.com/Nova-Node/'&gt;NovaNode meetup&lt;/a&gt; group focusing on the &lt;a href='http://nodejs.org/'&gt;Node.js&lt;/a&gt; event-driven JavaScript framework. We have a great set of talks lined up for our &lt;a href='http://www.meetup.com/Nova-Node/events/52749282/'&gt;inaugural meetup event&lt;/a&gt; on March 20, 2012 at 6:30 pm. We&amp;#8217;re hosting things at &lt;a href='http://spanishdict.com/'&gt;SpanishDict&lt;/a&gt;&amp;#8217;s offices in Arlington, VA near the Clarendon metro stop.&lt;/p&gt;
&lt;div class='pull-center'&gt;
  &lt;a href='http://www.meetup.com/Nova-Node/'&gt;&lt;img class='bordered' src='http://loose-bits.com/media/img/2012/03/06/nova_node.png' alt='Nova Node' /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Our talks are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;#8220;Getting Started with Node.js in the Cloud&amp;#8221;&lt;/strong&gt;, which I&amp;#8217;ll present.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&amp;#8220;Everyauth: OAuth for Busy Nerds&amp;#8221;&lt;/strong&gt; by Jason Bond Pratt, Co-founder of &lt;a href='http://launch.tixelated.com/'&gt;Tixelated&lt;/a&gt;. OAuth is the standard interface for web services integration for user authentication, and is your starting point for linking an application to Google, Facebook, Twitter, etc. Jason&amp;#8217;s talk will take us through Everyauth&amp;#8217;s basic architecture and integration with other data stores like MongoDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='getting_started_with_nodejs_in_the_cloud'&gt;Getting Started with Node.js in the Cloud&lt;/h2&gt;

&lt;p&gt;Node.js provides a solid platform for scalable and responsive web applications. Development in Node.js further has a lower learning curve compared to many highly-concurrent / asynchronous frameworks as it uses JavaScript, which is very familiar territory for web developers.&lt;/p&gt;

&lt;p&gt;The cloud complements Node.js by providing the means to deploy applications rapidly and scale up with no upfront investment costs. The &amp;#8220;big&amp;#8221; cloud providers such as &lt;a href='http://aws.amazon.com/'&gt;Amazon Web Services&lt;/a&gt; and &lt;a href='http://www.rackspace.com/cloud/'&gt;Rackspace&lt;/a&gt; easily support an infrastructures for hosting Node.js applications. Moreover, there has been a burst of Platform-as-a-Service contenders that provide even easier Node.js application hosting, including: &lt;a href='http://www.heroku.com/'&gt;Heroku&lt;/a&gt;, &lt;a href='http://nodejitsu.com/'&gt;NodeJitsu&lt;/a&gt; and even Microsoft&amp;#8217;s &lt;a href='http://www.windowsazure.com/en-us/develop/nodejs/'&gt;Azure&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;In my talk, I&amp;#8217;ll walk through a basic Node.js application that we develop, deploy and then scale easily in the cloud. I&amp;#8217;ll discuss best practices and what opportunities and pitfalls lie ahead when taking your Node.js applications to the cloud.&lt;/p&gt;

&lt;p&gt;Hope to see you at the meetup!&lt;/p&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/02/21/aysnc-js-talk-node-dc-jquery</id>
      <title>Async.js Presentation for Joint Node.DC / DC jQuery Meetup</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-02-21T20:54:38Z</published>
      <updated>2012-02-21T20:54:38Z</updated>
      <link href="http://loose-bits.com/2012/02/21/aysnc-js-talk-node-dc-jquery.html"/>
      <content type="html">&lt;h2 id='asyncjs_a_javascript_control_flow_library'&gt;Async.js, a JavaScript Control Flow Library&lt;/h2&gt;

&lt;p&gt;On February 20, 2012, I gave a talk on &lt;a href='https://github.com/caolan/async'&gt;Async.js&lt;/a&gt; to the joint meetup of &lt;a href='http://www.meetup.com/node-dc/events/49905452/'&gt;Node.DC&lt;/a&gt; and &lt;a href='http://www.meetup.com/DC-jQuery-Users-Group/events/51798912/'&gt;DC jQuery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Async.js is a control flow library that provides great abstractions for writing serial, parallel, and combined asynchronous code without ending up in the dreaded trenches of nested callback insanity.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://ryan-roemer.github.com/nodedc-async-talk/#/title'&gt;&lt;img src='http://loose-bits.com/media/img/2012/02/21/async-talk.png' alt='Async.js presentation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My &lt;a href='http://ryan-roemer.github.com/nodedc-async-talk/#/title'&gt;presentation&lt;/a&gt; is available live on GitHub, and uses &lt;a href='http://bartaz.github.com/impress.js'&gt;Impress.js&lt;/a&gt; for &lt;a href='http://prezi.com/'&gt;Prezi&lt;/a&gt;-like functionality in the browser. As a consequence, it might not render in older browsers and IE. My talk covers the basic abstractions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Series&lt;/li&gt;

&lt;li&gt;Waterfalls&lt;/li&gt;

&lt;li&gt;Parallel&lt;/li&gt;

&lt;li&gt;Collections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more, so check it out and feel free to ping me with any questions or comments!&lt;/p&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2012/01/08/authenticated-static-html-site-with-google-app-engine</id>
      <title>Authenticated, Static Web Sites on Google App Engine</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2012-01-08T14:00:00Z</published>
      <updated>2012-01-08T14:00:00Z</updated>
      <link href="http://loose-bits.com/2012/01/08/authenticated-static-html-site-with-google-app-engine.html"/>
      <content type="html">&lt;h2 id='static_html_web_site_hosting_with_google_app_engine'&gt;Static HTML Web Site Hosting with Google App Engine&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://code.google.com/appengine/'&gt;Google App Engine&lt;/a&gt; is a platform-as-a-service (PAAS) product that provides scalable, cloud-hosted web applications using Google&amp;#8217;s massive engineering infrastructure. While App Engine is primarily used by web developers (e.g., programming in Python or Java), it offers three features which make it uniquely helpful for static site hosting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arbitrary static file handling.&lt;/li&gt;

&lt;li&gt;Extensible authentication support.&lt;/li&gt;

&lt;li&gt;Very inexpensive (most likely free) for hosting static content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we&amp;#8217;ll walk through uploading a static HTML site to App Engine, and configuring it such that it requires users to log in to via a Google Apps domain account before viewing any content. This is a common situation for organizations already using Google Apps to manage email, documents, etc. that want to host a web site without having extra configuration hassles. App Engine essentially takes care of all the authentication / user management, and you just have to upload the static web site.&lt;/p&gt;

&lt;p&gt;The assumption throughout the rest of this post is that you already have a domain name managed by Google Apps (e.g., &amp;#8220;example.com&amp;#8221;). We will create an App Engine application and restrict it to users of the specific Google Apps domain, requiring a login of a user &amp;#8220;@example.com&amp;#8221;. Then we&amp;#8217;ll upload the static site and verify that authentication works as expected.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='create_an_authenticated_app_engine_application'&gt;Create an Authenticated App Engine Application&lt;/h2&gt;

&lt;p&gt;The first step is to create an App Engine web application. You&amp;#8217;ll need to &lt;a href='https://appengine.google.com/'&gt;sign up&lt;/a&gt; for an App Engine account, &lt;a href='http://code.google.com/appengine/downloads.html'&gt;download&lt;/a&gt; the App Engine Python SDK (make sure you get the &lt;strong&gt;Python&lt;/strong&gt; one!), and you should read the &amp;#8221;&lt;a href='http://code.google.com/appengine/docs/python/gettingstarted/'&gt;getting started&lt;/a&gt;&amp;#8221; documentation. Also, if given the option to &amp;#8220;install command symlinks&amp;#8221;, make sure you choose to do this (which will give us an &lt;code&gt;appcfg.py&lt;/code&gt; executable in our path for use later).&lt;/p&gt;

&lt;p&gt;Once App Engine is setup and installed, we can create the actual application. The important thing to point out here is that you must select the authentication method you want for your web site &lt;strong&gt;at&lt;/strong&gt; creation time, as it cannot be changed later. (Although, you can create a &lt;em&gt;new&lt;/em&gt; application with different authentication and delete your original application).&lt;/p&gt;

&lt;p&gt;Take a moment to review the App Engine &lt;a href='http://code.google.com/appengine/articles/auth.html'&gt;authentication&lt;/a&gt; article, as we&amp;#8217;re basically going to follow these steps. Go to the &lt;a href='http://appengine.google.com/start/createapp'&gt;create application&lt;/a&gt; web page. Fill in the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Application Identifier&lt;/strong&gt;: Choose a descriptive name for your site (e.g. &amp;#8220;internal-docs&amp;#8221;. There cannot be an existing matching identifier, so have some alternates handy. This will result in a domain name of &amp;#8220;internal-docs.appspot.com&amp;#8221; (or whatever your identifier is) for your finished website.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Application Title&lt;/strong&gt;: A free text description of your website. Feel free to put anything in that provides a simple title for your site.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Authentication Options (Advanced)&lt;/strong&gt;: You&amp;#8217;ll need to click the &amp;#8220;edit&amp;#8221; link which then gives us three options for authentication: (1) &amp;#8220;Open to all Google Accounts users (default)&amp;#8221;, (2) &amp;#8220;Restricted to the following Google Apps domain:&amp;#8221;, or (3) &amp;#8220;(Experimental) Open to all users with an OpenID Provider&amp;#8221;. Click the button for &amp;#8220;Restricted to the following Google Apps domain:&amp;#8221; and enter your Google Apps-managed domain (e.g., &amp;#8220;example.com&amp;#8221;). I should point out again that you &lt;em&gt;already&lt;/em&gt; should have Google Apps set up for the domain name you are entering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there you can click &amp;#8220;Create Application&amp;#8221; and the application should be created. Make sure to keep your application identifier handy.&lt;/p&gt;

&lt;p&gt;To enable Google Apps domain authentication for the new application, we need to follow the instructions in the App Engine &lt;a href='http://code.google.com/appengine/articles/auth.html'&gt;authentication&lt;/a&gt; article. Basically, you need to open a web browser to: &amp;#8220;http://www.google.com/a/YOUR DOMAIN&amp;#8221; and click on the &amp;#8220;Dashboard&amp;#8221; tab. Go to &amp;#8220;Service settings&amp;#8221; and click on the &amp;#8220;Add more services&amp;#8221; link. In the &amp;#8220;Other services&amp;#8221; section, there will be a place to add an App Engine service. Type in your application identifier code here and click &amp;#8220;Add it now&amp;#8221;. This will hook up your specific Google Apps Domain with the App Engine service.&lt;/p&gt;

&lt;h2 id='configure_and_upload_static_web_site'&gt;Configure and Upload Static Web Site&lt;/h2&gt;

&lt;p&gt;The next step is to gather your static web site files, add an application configuration, and upload all the content to the App Engine application. We will place all of our application content in a directory called &amp;#8220;my_site&amp;#8221; (or something else of your choosing). You are best off keeping this directory under a source control management system (e.g., git), so that you can monitor, track and revert changes to all of your files.&lt;/p&gt;

&lt;h3 id='configuration'&gt;Configuration&lt;/h3&gt;

&lt;p&gt;We need an application configuration file call &amp;#8220;app.yaml&amp;#8221; in the root of our project directory. This file controls various aspects of the application, including how the application routes URLs to handlers. We&amp;#8217;ll use a configuration that handles all static file types (including HTML), and just simply serves them.&lt;/p&gt;

&lt;p&gt;There are &lt;a href='http://blog.engelke.com/2008/07/30/google-appengine-for-web-hosting/'&gt;various&lt;/a&gt; &lt;a href='http://www.instantfundas.com/2011/02/how-to-host-static-websites-on-google.html'&gt;other&lt;/a&gt; posts out there discussing configurations for static web sites on App Engine, but the best configuration that I found was a &lt;a href='https://gist.github.com/873098'&gt;gist&lt;/a&gt; by GitHub user &amp;#8221;&lt;a href='https://github.com/darktable'&gt;darktable&lt;/a&gt;&amp;#8221;. However, this configuration didn&amp;#8217;t including authentication, so I forked the gist and added authentication attributes to produce our final &lt;a href='https://raw.github.com/gist/1570659/app.yaml'&gt;app.yaml&lt;/a&gt; file that you should download to &amp;#8220;my_site/app.yaml&amp;#8221;. You can also view a basic &lt;a href='https://gist.github.com/1570659#file_readme.markdown'&gt;Readme&lt;/a&gt; file and other information at the GitHub &lt;a href='https://gist.github.com/1570659'&gt;gist&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a snippet of the &amp;#8220;app.yaml&amp;#8221; file that you&amp;#8217;ll need to slightly modify:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='yaml'&gt;&lt;span class='l-Scalar-Plain'&gt;application&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;you-app-name-here&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;version&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;1&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;runtime&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;python&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;api_version&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;1&lt;/span&gt;

&lt;span class='l-Scalar-Plain'&gt;default_expiration&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;30d&amp;quot;&lt;/span&gt;

&lt;span class='l-Scalar-Plain'&gt;handlers&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt;
&lt;span class='p-Indicator'&gt;-&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;url&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;/(.*\.(appcache|manifest))&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;mime_type&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;text/cache-manifest&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;static_files&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;static/\1&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;upload&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;static/(.*\.(appcache|manifest))&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;expiration&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;0m&amp;quot;&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;login&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;required&lt;/span&gt;

&lt;span class='c1'&gt;# ... OTHER CONTENT SNIPPED ...&lt;/span&gt;

&lt;span class='c1'&gt;# site root&lt;/span&gt;
&lt;span class='p-Indicator'&gt;-&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;url&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;/&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;static_files&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;static/index.html&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;upload&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;static/index.html&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;expiration&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;15m&amp;quot;&lt;/span&gt;
  &lt;span class='l-Scalar-Plain'&gt;login&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='l-Scalar-Plain'&gt;required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After downloading to &amp;#8220;my_site/app.yaml&amp;#8221;, update the &lt;code&gt;application: you-app-name-here&lt;/code&gt; directive with the specific App Engine application identifier you chose in the application creation section above.&lt;/p&gt;

&lt;h3 id='static_content'&gt;Static Content&lt;/h3&gt;

&lt;p&gt;Now that we have a configuration file, create a folder named &amp;#8220;my_site/static&amp;#8221; which will house actual static web site. As we want to check that the authentication works first before uploading potentially sensitive information, I would recommend creating a test HTML page that just contains the content &amp;#8220;It worked!&amp;#8221; and adding that as &amp;#8220;my_site/static/index.html&amp;#8221;.&lt;/p&gt;

&lt;p&gt;Now, we should have a project layout that looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;my_site/
  app.yaml
  static/
    index.html&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point we can upload the full site to our static server using &lt;a href='http://code.google.com/appengine/docs/python/tools/uploadinganapp.html'&gt;appcfg.py&lt;/a&gt;. Make sure that we have &lt;code&gt;appcfg.py&lt;/code&gt; available:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;which appcfg.py
/usr/local/bin/appcfg.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you don&amp;#8217;t get an executable path back (any path is fine as long as &lt;em&gt;something&lt;/em&gt; is returned by the &lt;code&gt;which&lt;/code&gt; command), then review the App Engine &amp;#8221;&lt;a href='http://code.google.com/appengine/docs/python/gettingstarted/'&gt;getting started&lt;/a&gt;&amp;#8221; documents for installation of the runtime.&lt;/p&gt;

&lt;p&gt;Assuming we do have &lt;code&gt;appcfg.py&lt;/code&gt; available, change directory in your terminal to the directory containing the &amp;#8220;my_site&amp;#8221; project folder and upload the static site with the following command:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;appcfg.py update my_site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You will have to enter your Google credentials here. After the upload finishes, you should be able to open a web browser to: &amp;#8221;&amp;#60;your application identifier&amp;#62;.appspot.com&amp;#8221;. If you are authenticated to your Google Apps domain, you should see the &amp;#8220;It worked!&amp;#8221; test page. If not, you should be prompted to login to your Google Apps domain. A good way to test the authentication works is to open a new Google Chrome Incognito window. It should always force a new Google Apps login if you have configured things properly. If the authentication doesn&amp;#8217;t work quite right, review the App Engine &lt;a href='http://code.google.com/appengine/articles/auth.html'&gt;authentication&lt;/a&gt; page for tips and pointers, or leave a comment below on this post.&lt;/p&gt;

&lt;p&gt;Assuming authentication does work correctly, then you can now remove the test &amp;#8220;index.html&amp;#8221; file and upload your real site content to the &amp;#8220;my_site/static&amp;#8221; directory. Every time you change the content, make sure to re-upload the project with &lt;code&gt;appcfg.py&lt;/code&gt; and enjoy your static web site!&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme</id>
      <title>Twitter Bootstrap Theme for Sphinx</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-12-09T16:00:00Z</published>
      <updated>2011-12-09T16:00:00Z</updated>
      <link href="http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme.html"/>
      <content type="html">&lt;h2 id='bringing_twitter_bootstrap_to_sphinx'&gt;Bringing Twitter Bootstrap to Sphinx&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://twitter.github.com/bootstrap/'&gt;Bootstrap&lt;/a&gt; is an open source CSS/JS framework from the folks at &lt;a href='https://twitter.com/'&gt;Twitter&lt;/a&gt; that provides base typography, layout (grids), forms / form inputs, tables and navigation, and has been gaining wide popularity among developers. In my personal experience with a couple of sites and projects, Bootstrap really provides a great starting point for a frontend that provides good extensibility over time.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://sphinx.pocoo.org/'&gt;Sphinx&lt;/a&gt; is the most popular &lt;a href='http://python.org/'&gt;Python&lt;/a&gt; documentation generator tool. It has fantastic Python module docstring / comment parsing in addition to a sensible static pages framework, providing a great final web site built from both inline code documentation and outside web pages.&lt;/p&gt;

&lt;p&gt;In the process of starting up a number of new Sphinx projects, I decided that I would really like to use Bootstrap as the underlying UI framework. Fortunately, Sphinx supports custom theme plugins, so I spent a couple of days hacking, and created a &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme'&gt;Sphinx Bootstrap Theme&lt;/a&gt;, which is now available on GitHub.&lt;/p&gt;

&lt;p&gt;To show all of the coolness of Bootstrap and Sphinx, I&amp;#8217;ve put up a simple &lt;a href='http://ryan-roemer.github.com/sphinx-bootstrap-theme'&gt;demo&lt;/a&gt;, which provides a skeleton Sphinx site with the project &lt;a href='http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html'&gt;README&lt;/a&gt; file rendered as content. Here is a screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/12/09/sbt_teaser.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/12/09/sbt_teaser_thumb.png' alt='Sphinx Bootstrap Theme Demo' /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='installation'&gt;Installation&lt;/h2&gt;

&lt;p&gt;Sphinx themes can be installed either as a directory of files or a zip file. For simplicity&amp;#8217;s sake, we&amp;#8217;ll go with a pre-packaged zip file installation here.&lt;/p&gt;

&lt;p&gt;First, set up a themes directory, which typically is something like &amp;#8220;_themes&amp;#8221; located in your Sphinx source directory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd&lt;/span&gt; /path/to/source
&lt;span class='nv'&gt;$ &lt;/span&gt;mkdir -p _themes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, download the most current &amp;#8221;&lt;a href='https://github.com/downloads/ryan-roemer/sphinx-bootstrap-theme/bootstrap.zip'&gt;bootstrap.zip&lt;/a&gt;&amp;#8221; archive from GitHub. Note that older zipped theme files are available at the &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/downloads'&gt;downloads&lt;/a&gt; page, with git hash suffixes.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd&lt;/span&gt; /path/to/source/_themes
&lt;span class='nv'&gt;$ &lt;/span&gt;wget https://github.com/downloads/ryan-roemer/sphinx-bootstrap-theme/bootstrap.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As an alternative, you can just clone the theme repository, and place the &amp;#8220;bootstrap&amp;#8221; directory in your themes directory. The &amp;#8220;bootstrap.zip&amp;#8221; file should be the most up-to-date version, but getting the directory from source will remain the authoritative current version.&lt;/p&gt;

&lt;p&gt;Finally, add the themes directory path and theme name to your source Sphinx &amp;#8220;conf.py&amp;#8221; file:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='c'&gt;# Activate the theme.&lt;/span&gt;
&lt;span class='n'&gt;sys&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;append&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;os&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;_themes&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='n'&gt;html_theme_path&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;_themes&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;span class='n'&gt;html_theme&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;bootstrap&amp;#39;&lt;/span&gt;

&lt;span class='c'&gt;# Optional. Use a shorter name to conserve nav. bar space.&lt;/span&gt;
&lt;span class='n'&gt;html_short_title&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Demo&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild your Sphinx documentation, and you should now have the Bootstrap theme up and running! See the &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/README.rst'&gt;README&lt;/a&gt; for more information on customizing and hacking on the theme.&lt;/p&gt;

&lt;h2 id='brief_tour'&gt;Brief Tour&lt;/h2&gt;

&lt;p&gt;The Bootstrap Sphinx theme basically rolls up the site-wide / page-level table of contents, page navigation, and search form into the main navigation top bar.&lt;/p&gt;

&lt;h3 id='site_navigation'&gt;Site Navigation&lt;/h3&gt;

&lt;p&gt;The first dropdown tab is the &amp;#8220;Site&amp;#8221; button (expanded and highlighted below). Internally this wraps up the &lt;code&gt;globaltoc&lt;/code&gt; template using the &lt;code&gt;toctree&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/12/09/sbt_nav_site.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/12/09/sbt_nav_site_thumb.png' alt='Site Navigation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For both the site and page navigation dropdowns, the Bootstrap theme uses JavaScript hackery to unpack the internal list structure and convert it to an ordered flat list with hierarchy displayed using extra left padding corresponding to levels. The natural levels of table of content displayed normally by Sphinx should be preserved in the dropdown menus.&lt;/p&gt;

&lt;h3 id='page_navigation'&gt;Page Navigation&lt;/h3&gt;

&lt;p&gt;The second dropdown tab is the &amp;#8220;Page&amp;#8221; navigation for table of contents link within a single page. This wraps the &lt;code&gt;localtoc&lt;/code&gt; template using the &lt;code&gt;toc&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/12/09/sbt_nav_page.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/12/09/sbt_nav_page_thumb.png' alt='Page Navigation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='search'&gt;Search&lt;/h3&gt;

&lt;p&gt;The right &amp;#8220;Search&amp;#8221; box in the navigation top bar connects to the built-in, client-side search that Sphinx provides. Here we search for the term &amp;#8220;bootstrap&amp;#8221;:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/12/09/sbt_nav_search.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/12/09/sbt_nav_search_thumb.png' alt='Search Navigation' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;#8230; which gives us the following page matches:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/12/09/sbt_search.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/12/09/sbt_search_thumb.png' alt='Search Results' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='final_thoughts'&gt;Final Thoughts&lt;/h3&gt;

&lt;p&gt;The Bootstrap Theme provides an easy means for Twitter Bootstrap integration for my current Sphinx sites. But, as I&amp;#8217;ve just put together the theme this week, it is probably still rough around the edges. Any &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/issues'&gt;issue reports&lt;/a&gt;, &lt;a href='https://github.com/ryan-roemer/sphinx-bootstrap-theme/pulls'&gt;pull requests&lt;/a&gt;, and feedback are most welcome.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/10/20/node-sunny-presentation-and-proxy-demo</id>
      <title>Sunny.js Presentation and Demo Server</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-10-20T16:00:00Z</published>
      <updated>2011-10-20T16:00:00Z</updated>
      <link href="http://loose-bits.com/2011/10/20/node-sunny-presentation-and-proxy-demo.html"/>
      <content type="html">&lt;h2 id='sunnyjs_presentation'&gt;Sunny.js Presentation&lt;/h2&gt;

&lt;p&gt;I gave a lightning talk at last night&amp;#8217;s &lt;a href='http://nodedc-october-eorg.eventbrite.com/'&gt;Node.js Meetup&lt;/a&gt; introducing &lt;a href='http://sunnyjs.org'&gt;Sunny.js&lt;/a&gt;, a multi-cloud library for &lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt;. For more background on Sunny, see my previous &lt;a href='http://loose-bits.com/2011/10/16/node-sunny-cloud-library.html'&gt;blog post&lt;/a&gt; to get started with the library.&lt;/p&gt;

&lt;p&gt;Instead of the usual PowerPoint deck, I used &lt;a href='http://prezi.com'&gt;Prezi&lt;/a&gt;, which I have been wanting to try out for some time. Here is the &lt;a href='http://prezi.com/tlaj11poclwg/sunnyjs/'&gt;Sunny.js prezi&lt;/a&gt; I gave at the meetup:&lt;/p&gt;
&lt;div class='prezi-player'&gt;
  &lt;style type='text/css' media='screen'&gt;
    .prezi-player {
      width: 500px;
      margin-left: auto;
      margin-right: auto;
    }
    .prezi-title {
      margin-top: 10px;
      font-size: 1.25em;
      text-align: center;
    }
  &lt;/style&gt;
  &lt;object name='prezi_tlaj11poclwg' id='prezi_tlaj11poclwg' height='350' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' width='500'&gt;
    &lt;param name='movie' value='http://prezi.com/bin/preziloader.swf' /&gt;
    &lt;param name='allowfullscreen' value='true' /&gt;
    &lt;param name='allowscriptaccess' value='always' /&gt;
    &lt;param name='bgcolor' value='#ffffff' /&gt;
    &lt;param name='flashvars' value='prezi_id=tlaj11poclwg&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0' /&gt;
    &lt;embed name='preziEmbed_tlaj11poclwg' src='http://prezi.com/bin/preziloader.swf' allowfullscreen='true' id='preziEmbed_tlaj11poclwg' type='application/x-shockwave-flash' allowscriptaccess='always' height='350' flashvars='prezi_id=tlaj11poclwg&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0' width='500' bgcolor='#ffffff' /&gt;
  &lt;/object&gt;
  &lt;div class='prezi-title'&gt;
    &lt;a href='http://prezi.com/tlaj11poclwg/sunnyjs/'&gt;Sunny.js Meetup Presentation&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Although the Prezi took some time to put together (especially being my first time using the service), I was very happy with the appearance and flow of the presentation.&lt;/p&gt;

&lt;h2 id='simple_cloudweb_proxy_server'&gt;Simple Cloud/Web Proxy Server&lt;/h2&gt;

&lt;p&gt;As part of my presentation, I demo&amp;#8217;ed a simple proxy service using Sunny and Node. I took the v0.0.1 HTML documentation for Sunny, and uploaded all of it to an Amazon &lt;a href='http://aws.amazon.com/s3/'&gt;S3&lt;/a&gt; bucket, being careful to preserve paths from the original documentation files when naming S3 keys. (Actually, this wasn&amp;#8217;t hard at all &amp;#8211; I just used the reliable &lt;a href='http://cyberduck.ch/'&gt;CyberDuck&lt;/a&gt; S3 client).&lt;/p&gt;

&lt;p&gt;I then wrote a really simple 40-line web server. It basically takes credentials from the process environment, checks if a specified container exists, and then translates web request GET paths to blob names in the container I set up. The result is that I could serve my entire documentation site straight from by Amazon S3 bucket!&lt;/p&gt;

&lt;p&gt;The source for the project, &lt;a href='https://github.com/ryan-roemer/sunny-proxy'&gt;sunny-proxy&lt;/a&gt;, is available on GitHub, and is fairly straightforward to setup for yourself using the Readme instructions.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;For the impatient, here&amp;#8217;s the entire relevant code for the web server:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;http&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;mime&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;mime&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;sunny&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;sunny&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;Configuration&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;fromEnv&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;ADDR&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;process&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;env&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;ADDRESS&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;PORT&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;process&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;env&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;PORT&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt; &lt;span class='mi'&gt;2000&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;CONTAINER&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;process&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;env&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;SUNNY_PROXY_CONTAINER&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

&lt;span class='c1'&gt;// Get our container and create server inside to get blobs.&lt;/span&gt;
&lt;span class='nx'&gt;conn&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getContainer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;CONTAINER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nx'&gt;validate&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt; &lt;span class='p'&gt;})&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='k'&gt;throw&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='p'&gt;})&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c1'&gt;// We have a valid container, so let&amp;#39;s create the server now.&lt;/span&gt;
    &lt;span class='nx'&gt;http&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;createServer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;res&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;path&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;req&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;url&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;replace&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/\/$/&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;/index.html&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;replace&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/^\/*/&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nx'&gt;status&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;200&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='nx'&gt;logResult&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
          &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;[%s] %s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;status&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;path&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='p'&gt;},&lt;/span&gt;
        &lt;span class='nx'&gt;stream&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

      &lt;span class='c1'&gt;// Header based on MIME type (re-write on error).&lt;/span&gt;
      &lt;span class='nx'&gt;res&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;writeHead&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;status&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;content-type&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='nx'&gt;mime&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;lookup&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;path&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;});&lt;/span&gt;

      &lt;span class='c1'&gt;// Get blob and pass through error or pipe to response.&lt;/span&gt;
      &lt;span class='nx'&gt;stream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;path&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;stream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='nx'&gt;status&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;statusCode&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt; &lt;span class='mi'&gt;500&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='nx'&gt;res&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;writeHead&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;status&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;content-type&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;text/html&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;});&lt;/span&gt;
        &lt;span class='nx'&gt;res&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&amp;lt;h1&amp;gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nx'&gt;status&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;: &amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;message&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;lt;/h1&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='nx'&gt;logResult&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
      &lt;span class='p'&gt;});&lt;/span&gt;
      &lt;span class='nx'&gt;stream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;logResult&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;stream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;res&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;stream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='p'&gt;}).&lt;/span&gt;&lt;span class='nx'&gt;listen&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;PORT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;ADDR&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Server running at http://%s:%s/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;ADDR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;PORT&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='p'&gt;})&lt;/span&gt;
  &lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We use the magic of &lt;code&gt;stream.pipe(res)&lt;/code&gt; to handle all of the transfer details of passing through our blob data chunks to the HTTP response.&lt;/p&gt;

&lt;p&gt;I even took it a step further and set up a simple &lt;a href='http://www.heroku.com/'&gt;Heroku&lt;/a&gt; app to run the application. All it took was a quick review of the &lt;a href='http://devcenter.heroku.com/articles/node-js'&gt;Node app instructions&lt;/a&gt; and a &amp;#8220;Procfile&amp;#8221; file (mine is available in the GitHub repo) and I was able to get my node proxy server up and running on the web in under a half hour! Moreover, I was pleasantly surprised at how fast the web application was running &amp;#8211; especially considering the web server doesn&amp;#8217;t use any caching logic or any intelligent optimizations that a real web server would.&lt;/p&gt;

&lt;p&gt;The Sunny-based proxy server certainly isn&amp;#8217;t anything amazing or revolutionary &amp;#8211; it just serves blobs as if they were static files for a web server &amp;#8211; but it does show just how easily Node.js and libraries like Sunny can glue cloud / service-based components together for fast and scalable applications.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/10/16/node-sunny-cloud-library</id>
      <title>Sunny.js, a Cloud Library for Node.js</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-10-16T16:00:00Z</published>
      <updated>2011-10-16T16:00:00Z</updated>
      <link href="http://loose-bits.com/2011/10/16/node-sunny-cloud-library.html"/>
      <content type="html">&lt;h2 id='sunnyjs'&gt;Sunny.js&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://nodejs.org'&gt;Node.js&lt;/a&gt; provides a great environment for cloud applications and development. Node&amp;#8217;s asynchronous design and great HTTP/REST support provide the building blocks for architecting non-blocking and scalable applications in the cloud.&lt;/p&gt;

&lt;p&gt;There are a lot of great Node cloud clients out there for cloud datastores. Amazon Web Services &lt;a href='http://aws.amazon.com/s3/'&gt;S3&lt;/a&gt; has: &lt;a href='https://github.com/LearnBoost/knox'&gt;knox&lt;/a&gt;, &lt;a href='https://github.com/tricknik/node-sissy'&gt;node-sissy&lt;/a&gt;, &lt;a href='https://github.com/grippy/node-s3'&gt;node-s3&lt;/a&gt;, &lt;a href='https://github.com/nuxusr/Node.js---Amazon-S3'&gt;Node.js-Amazon-S3&lt;/a&gt;, and Rackspace &lt;a href='http://www.rackspacecloud.com/cloud_hosting_products/files/'&gt;Cloud Files&lt;/a&gt; has: &lt;a href='https://github.com/nodejitsu/node-cloudfiles'&gt;node-cloudfiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After reviewing these libraries, I found a few features variously lacking that I would like in an ideal cloud datastore client:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event-based interface.&lt;/li&gt;

&lt;li&gt;&amp;#8220;One-shot&amp;#8221; requests wherever possible.&lt;/li&gt;

&lt;li&gt;Able to create / delete arbitrary containers (buckets).&lt;/li&gt;

&lt;li&gt;Configurable request headers, cloud options and metadata.&lt;/li&gt;

&lt;li&gt;SSL support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, it would be nice to have a library that worked across multiple clouds like &lt;a href='http://www.jclouds.org/'&gt;jclouds&lt;/a&gt; for Java and &lt;a href='http://libcloud.apache.org/'&gt;Libcloud&lt;/a&gt; for Python.&lt;/p&gt;

&lt;p&gt;With these goals in mind, I put together a basic multi-cloud datastore client called &lt;a href='http://sunnyjs.org'&gt;Sunny.js&lt;/a&gt;. Sunny initially supports &lt;a href='http://aws.amazon.com/'&gt;Amazon Web Service&lt;/a&gt;&amp;#8217;s &lt;a href='http://aws.amazon.com/s3/'&gt;Simple Storage Service&lt;/a&gt; (S3) and &lt;a href='http://code.google.com/apis/storage/'&gt;Google Storage for Developers&lt;/a&gt; (&lt;a href='http://code.google.com/apis/storage/docs/reference/v1/apiversion1.html'&gt;version 1&lt;/a&gt;). Sunny hits all the points above, and also has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A really well-documented &lt;a href='http://sunnyjs.org/api/index.html'&gt;API&lt;/a&gt; and &lt;a href='http://sunnyjs.org/guide.html'&gt;user guide&lt;/a&gt;. (At least I think so!)&lt;/li&gt;

&lt;li&gt;Full unit tests for all cloud operations and providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some basic resources to get started with Sunny, which we&amp;#8217;ll dive into a little deeper in the subsequent sections of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href='http://sunnyjs.org'&gt;Sunny.js Documentation&lt;/a&gt;: Guides and API documentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://sunnyjs.org/guide.html'&gt;User Guide&lt;/a&gt;: Guide for programmers using Sunny in your applications and library.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://sunnyjs.org/development.html'&gt;Developer&amp;#8217;s Guide&lt;/a&gt;: Guide for developing, extending, and hacking on the Sunny.js source code.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://sunnyjs.org/api/index.html'&gt;API&lt;/a&gt;: Full API.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='http://github.com/ryan-roemer/node-sunny'&gt;Sunny.js GitHub Page&lt;/a&gt;: Source repository and issue tracker.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;a href='http://search.npmjs.org/#/sunny'&gt;Sunny.js NPM Page&lt;/a&gt;: NPM page and history.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- more start --&gt;
&lt;h2 id='getting_started'&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;First, install sunny from &lt;a href='http://search.npmjs.org/#/sunny'&gt;npm&lt;/a&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;npm install sunny
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once Sunny is installed, we need to set up some configuration information for our Node program. We can provide cloud account and option information to Sunny either via the process environment or a JavaScript object / file. For simplicity, we&amp;#8217;ll just export some environment variables (see the &lt;a href='http://sunnyjs.org/guide.html'&gt;user guide&lt;/a&gt; for more on configuration and using files).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;SUNNY_PROVIDER&lt;/span&gt;&lt;span class='o'&gt;=(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;aws&amp;quot;&lt;/span&gt;|&lt;span class='s2'&gt;&amp;quot;google&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;SUNNY_ACCOUNT&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;ACCOUNT_NAME&amp;quot;&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;SUNNY_SECRET_KEY&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;ACCOUNT_SECRET_KEY&amp;quot;&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;SUNNY_SSL&lt;/span&gt;&lt;span class='o'&gt;=(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;true&amp;quot;&lt;/span&gt;|&lt;span class='s2'&gt;&amp;quot;false&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From here, we can create our Node program, and get a live cloud datastore &lt;a href='http://sunnyjs.org/api/symbols/base.Connection.html'&gt;connection&lt;/a&gt; as follows:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunny&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;Configuration&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;fromEnv&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;connection&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id='cloud_operations'&gt;Cloud Operations&lt;/h2&gt;

&lt;p&gt;Now that we have our cloud connection object (&lt;code&gt;conn&lt;/code&gt;), we can perform all of our cloud operations on containers and blobs. For those used to Amazon Web Services parlance, a Sunny container is equivalent to an S3 &amp;#8220;bucket&amp;#8221;, and a Sunny blob is an S3 &amp;#8220;key&amp;#8221;.&lt;/p&gt;

&lt;p&gt;Sunny cloud operations are asynchronous and event-based. The cloud methods return either a request or a stream object, that is then used to set listeners and callbacks on, before calling &lt;code&gt;end()&lt;/code&gt; and starting the underlying real cloud network operation.&lt;/p&gt;

&lt;h3 id='requests'&gt;Requests&lt;/h3&gt;

&lt;p&gt;Sunny request objects are not true Node request objects, but approximate a subset of a Node HTTP request object events. The most common request events are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;#8220;error&amp;#8221;&lt;/strong&gt;: The underlying real cloud operation failed.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&amp;#8220;end&amp;#8221;&lt;/strong&gt;: The operation finished and we have results.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&amp;#8220;data&amp;#8221;&lt;/strong&gt;: A GET request returned a data chunk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let&amp;#8217;s perform a basic asynchronous operation to get a container named &amp;#8220;sunny&amp;#8221; from our cloud store.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;request&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;conn&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getContainer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunnyjs&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nx'&gt;validate&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt; &lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;We received error: %s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Retrieved container \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which gives us the output:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Retrieved container &amp;quot;sunnyjs&amp;quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Breaking the code down, we call &lt;code&gt;getContainer&lt;/code&gt; with the container name and the option &lt;code&gt;validate: true&lt;/code&gt;. The validate option means we actually perform a cloud request to check the container exists before continuing. This is a good check to start with before launching in to other code. But, Sunny allows the programmer to choose to not validate, and wait for the first blob request to actually perform any network operation, which is faster.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getContainer&lt;/code&gt; method returns a &lt;code&gt;request&lt;/code&gt; object, which we then set our listeners on for &amp;#8220;error&amp;#8221; and &amp;#8220;end&amp;#8221; (when we have results). Finally, the call to &lt;code&gt;request.end()&lt;/code&gt; initiates the actual network operation.&lt;/p&gt;

&lt;p&gt;Our &amp;#8220;end&amp;#8221; callback takes a &lt;code&gt;results&lt;/code&gt; parameter which contains a &lt;code&gt;container&lt;/code&gt; method for further use with blob methods, and a &lt;code&gt;meta&lt;/code&gt; object with information from the actual cloud call (metadata, HTTP headers, etc.) See the &lt;a href='http://sunnyjs.org/api/symbols/base.Connection.html#getContainer'&gt;&lt;code&gt;getContainer&lt;/code&gt;&lt;/a&gt; documentation for more options, results and uses.&lt;/p&gt;

&lt;p&gt;The other request-based Sunny methods work in a similar fashion, and include the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connection&lt;/strong&gt;: Get a list of containers. Get/create/delete a single container.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Container&lt;/strong&gt;: Get/create/delete a single container. Get a list of blobs. Head/delete a single blob. Get/put blob to/from a file.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Blob&lt;/strong&gt;: Head/delete a single blob. Get/put blob to/from a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href='http://sunnyjs.org/api/index.html'&gt;API&lt;/a&gt; for full method details.&lt;/p&gt;

&lt;h3 id='streams'&gt;Streams&lt;/h3&gt;

&lt;p&gt;Sunny returns stream objects for data-based cloud operations (PUT or GET), which are real implementations of Node &lt;a href='http://nodejs.org/docs/v0.4.9/api/streams.html'&gt;streams&lt;/a&gt;. A GET blob method returns a &lt;a href='http://nodejs.org/docs/v0.4.9/api/streams.html#readable_Stream'&gt;Readable Stream&lt;/a&gt; object, and a PUT blob method returns a &lt;a href='http://nodejs.org/docs/v0.4.9/api/streams.html#writable_Stream'&gt;Writable Stream&lt;/a&gt; object.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s take the &lt;code&gt;container&lt;/code&gt; object we received from our successful &lt;code&gt;getContainer&lt;/code&gt; request above, and perform a PUT blob operation with a simple string. (Note: programmatically, this would be within the &amp;#8220;end&amp;#8221; callback of the get container request). Data can be written with any number of &lt;code&gt;write()&lt;/code&gt; calls and a single &lt;code&gt;end()&lt;/code&gt; call (which starts the data transfer and ignores all subsequent writes).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;writeStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;putBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;We received error: %s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Put blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;My data string.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which produces:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Put blob &amp;quot;my-blob.txt&amp;quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, in a later operation (say, within the &lt;code&gt;putBlob&lt;/code&gt; &amp;#8220;end&amp;#8221; callback, after we know the blob was written), we can retrieve the data:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;readStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;We received error: %s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Data chunk: \&amp;quot;%s\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Get blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which gives us:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Data chunk: &amp;quot;My data string.&amp;quot;
Get blob &amp;quot;my-blob.txt&amp;quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that we&amp;#8217;re getting the data in raw chunks from the &amp;#8220;data&amp;#8221; event, which is somewhat tedious to cobble together (as strings if encoded or &lt;code&gt;Buffer&lt;/code&gt; objects otherwise). Given that we have full read / write stream implementations, Sunny really shines with the availability of &lt;code&gt;pipe()&lt;/code&gt;&amp;#8216;ing data. Essentially, you can simply connect a Sunny read / write stream to another read / write stream, call &lt;code&gt;pipe()&lt;/code&gt; and let Node and Sunny take care of the rest.&lt;/p&gt;

&lt;p&gt;For example, Sunny provides helper methods for getting / putting blobs to and from files. Looking at the source code, the real gist of, for example, getting a blob to a local file is the following:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;getStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;writeStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;fs&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;createWriteStream&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;filePath&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='c1'&gt;// Start the pipe and call &amp;#39;end()&amp;#39;.&lt;/span&gt;
&lt;span class='nx'&gt;getStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;pipe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;getStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There&amp;#8217;s a little more to it, but it is mostly this easy. Take a look at the &lt;a href='https://github.com/ryan-roemer/node-sunny/blob/master/lib/base/blob/blob.js'&gt;source code&lt;/a&gt; for the blob convenience methods &lt;code&gt;putFromFile&lt;/code&gt; and &lt;code&gt;getToFile&lt;/code&gt; to see a full implementation that binds together file and cloud streams.&lt;/p&gt;

&lt;p&gt;Node streams available for use with Sunny get / put &lt;code&gt;pipe()&lt;/code&gt; calls include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP/HTTPS requests and responses. See my &lt;a href='https://github.com/ryan-roemer/sunny-proxy'&gt;sunny-proxy&lt;/a&gt; project for a simple web server that proxies web requests to cloud blobs using Sunny blob get streams piped to HTTP responses.&lt;/li&gt;

&lt;li&gt;File reads and writes &amp;#8211; &lt;a href='http://nodejs.org/docs/v0.4.9/api/fs.html#fs.ReadStream'&gt;&lt;code&gt;fs.ReadStream&lt;/code&gt;&lt;/a&gt;, &lt;a href='http://nodejs.org/docs/v0.4.9/api/fs.html#fs.WriteStream'&gt;&lt;code&gt;fs.WriteStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='cloud_headers_and_metadata'&gt;Cloud Headers and Metadata&lt;/h2&gt;

&lt;p&gt;Sunny abstracts common cloud provider header and metadata. For example, AWS S3 has a metadata header prefix of &amp;#8220;x-amz-meta-&amp;#8220;, while Google Storage has an analogous one of &amp;#8220;x-goog-meta-&amp;#8220;.&lt;/p&gt;

&lt;p&gt;Sunny cloud operations return a &lt;code&gt;meta&lt;/code&gt; object that looks like:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;headers&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* HTTP headers. */&lt;/span&gt;
  &lt;span class='p'&gt;},&lt;/span&gt;
  &lt;span class='nx'&gt;cloudHeaders&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* Cloud-specific HTTP headers (e.g., &amp;quot;x-amz-&amp;quot;). */&lt;/span&gt;
  &lt;span class='p'&gt;},&lt;/span&gt;
  &lt;span class='nx'&gt;metadata&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* Metadata headers (e.g., &amp;quot;x-amz-meta-&amp;quot;). */&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;with cloud-specific prefixes stripped out. Moreover, you can &lt;em&gt;set&lt;/em&gt; cloud headers in the same manner by passing any of the fields above (&amp;#8220;headers&amp;#8221;, &amp;#8220;cloudHeaders&amp;#8221;, &amp;#8220;metadata&amp;#8221;) in the &lt;code&gt;options&lt;/code&gt; object of a cloud request. In this manner, Sunny makes it easy to utilize metadata operations as part of your application, while remaining agnostic to your actual cloud provider. See the Sunny &lt;a href='http://sunnyjs.org/guide.html'&gt;user guide&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2 id='error_handling'&gt;Error Handling&lt;/h2&gt;

&lt;p&gt;For programmatic ease, and to better abstract across cloud providers, Sunny wraps cloud-based errors with a custom class, that adds in some additional useful attributes. The &lt;code&gt;CloudError&lt;/code&gt; class has the following members:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;: The error message (usually in XML). (from &lt;code&gt;Error&lt;/code&gt;).&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;code&gt;statusCode&lt;/code&gt;&lt;/strong&gt;: HTTP status code, if any.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and error type methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href='http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotFound'&gt;&lt;code&gt;isNotFound&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Container / blob does not exist.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;a href='http://sunnyjs.org/api/symbols/errors.CloudError.html#isInvalidName'&gt;&lt;code&gt;isInvalidName&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Invalid name requested for container / blob.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;a href='http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotEmpty'&gt;&lt;code&gt;isNotEmpty&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Container cannot be deleted if blobs still exist within it.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;a href='./api/symbols/errors.CloudError.html#isAlreadyOwnedByYou'&gt;&lt;code&gt;isAlreadyOwnedByYou&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Creating a container that is already owned by you.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;a href='http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotOwner'&gt;&lt;code&gt;isNotOwner&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: AWS and Google Storage have a global namespace, so if someone else owns the container name requested, your operations will fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, Sunny &amp;#8220;error&amp;#8221; event listeners will get &lt;code&gt;CloudError&lt;/code&gt; objects instead of raw &lt;code&gt;Error&lt;/code&gt; objects on cloud method errors, which can be used to more easily programmatically react (especially since Sunny correctly handles different error codes and XML from cloud providers that correspond to the same type of error).&lt;/p&gt;

&lt;h2 id='putting_it_all_together_with_a_little_help_from_asyncjs'&gt;Putting it all Together, with a Little Help from Async.js&lt;/h2&gt;

&lt;p&gt;Now that we&amp;#8217;ve seen how to create a Sunny connection, get a container and perform operations on blobs within the container, let&amp;#8217;s put all of our code together in a single script. (Note: I&amp;#8217;ve collapsed the error handling to a single utility function).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunny&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;Configuration&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;fromEnv&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;errFn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='c1'&gt;// Op 1: GET container.&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;request&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;conn&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getContainer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunnyjs&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nx'&gt;validate&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt; &lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;errFn&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Retrieved container \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Op 2: PUT blob.&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;writeStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;putBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;errFn&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Put blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='c1'&gt;// Op 3: GET blob.&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;readStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;errFn&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Data chunk: \&amp;quot;%s\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Get blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;});&lt;/span&gt;
  &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;My data string.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not too shabby! However, you&amp;#8217;ll note that we have three serial cloud operations, each of which have to complete before the next starts, so we end up repeatedly nesting callbacks in &amp;#8220;end&amp;#8221; event listeners. Even with a mere three operations, the nesting makes the code hard to understand at first blush, and an indentation nightmare. Wouldn&amp;#8217;t it be better if we could code our operations to match the general outline of what we&amp;#8217;re trying to do? That is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a container.&lt;/li&gt;

&lt;li&gt;Once we have a container, put data to a blob.&lt;/li&gt;

&lt;li&gt;Once we have put data to the blob, get the data back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fortunately, there are many asynchronous helper libraries for Node to help in just this situation. I like &lt;a href='https://github.com/caolan/async'&gt;Async.js&lt;/a&gt;, which from the project page &amp;#8220;provides around 20 functions that include the usual &amp;#8216;functional&amp;#8217; suspects (map, reduce, filter, forEach&amp;#8230;) as well as some common patterns for asynchronous control flow (parallel, series, waterfall&amp;#8230;).&amp;#8221;&lt;/p&gt;

&lt;p&gt;In our simple three-cloud operation script, we just need &lt;code&gt;async.series&lt;/code&gt; to serialize our operations for easier control flow. I am skipping over a lot of details about using Async.js, such as callbacks at the end of functions or errors, and passing state across serialized functions, but you can probably intuit most of how things are working here:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;async&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;async&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;sunny&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;require&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunny&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nx'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;sunny&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;Configuration&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;fromEnv&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

&lt;span class='nx'&gt;async&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;series&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;
  &lt;span class='c1'&gt;// Op 1: GET container.&lt;/span&gt;
  &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;request&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;conn&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getContainer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;sunnyjs&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nx'&gt;validate&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt; &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;container&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Retrieved container \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kc'&gt;null&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;  &lt;span class='c1'&gt;// Signal function &amp;quot;done&amp;quot; to async.series.&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;request&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;},&lt;/span&gt;

  &lt;span class='c1'&gt;// Op 2: PUT blob.&lt;/span&gt;
  &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;writeStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;putBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Put blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kc'&gt;null&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;  &lt;span class='c1'&gt;// Signal function &amp;quot;done&amp;quot; to async.series.&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;My data string.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;writeStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;},&lt;/span&gt;

  &lt;span class='c1'&gt;// Op 3: GET blob.&lt;/span&gt;
  &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;readStream&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;container&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;getBlob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-blob.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Data chunk: \&amp;quot;%s\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;chunk&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;end&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;meta&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;blob&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;results&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
      &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Get blob \&amp;quot;%s\&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;blob&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
      &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kc'&gt;null&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;  &lt;span class='c1'&gt;// Signal function &amp;quot;done&amp;quot; to async.series.&lt;/span&gt;
    &lt;span class='p'&gt;});&lt;/span&gt;
    &lt;span class='nx'&gt;readStream&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;end&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;result&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;err&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='k'&gt;else&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;async.series is done!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which gives us our expected output:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Retrieved container &amp;quot;sunnyjs&amp;quot;.
Put blob &amp;quot;my-blob.txt&amp;quot;.
Data chunk: &amp;quot;My data string.&amp;quot;
Get blob &amp;quot;my-blob.txt&amp;quot;.
async.series is done!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our code now appears in a serialized, contained order that matches the conceptual execution of the actual cloud operations, instead of a lot of callback nesting.&lt;/p&gt;

&lt;p&gt;For more on developing with Async.js, please see the excellent Async.js &lt;a href='https://github.com/caolan/async/blob/master/README.md'&gt;Readme / tutorial&lt;/a&gt;. It is an investment well worth your time. Sunny internally uses Async.js extensively for unit tests. Explore the source &lt;a href='https://github.com/ryan-roemer/node-sunny/tree/master/test/live'&gt;tests&lt;/a&gt; directory for a lot of good use cases (parallel and serial).&lt;/p&gt;

&lt;h2 id='have_fun_in_the_sun'&gt;Have Fun in the Sun&lt;/h2&gt;

&lt;p&gt;That wraps up our introduction to Sunny.js. Hopefully the library provides a useful interface for your cloud programming needs.&lt;/p&gt;

&lt;p&gt;The project is still very much in an early development state. See the Sunny &lt;a href='https://github.com/ryan-roemer/node-sunny/blob/master/TODO.md'&gt;&amp;#8220;to do&amp;#8221; list&lt;/a&gt; to get a better idea of what features and fixes are coming down the pipeline. The big ticket items I would really like to see added are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More cloud providers, notable Rackspace &lt;a href='http://www.rackspacecloud.com/cloud_hosting_products/files/'&gt;Cloud Files&lt;/a&gt; and OpenStack &lt;a href='http://openstack.org/projects/storage/'&gt;Storage&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;A basic retry function wrapper to handle expected, occasional cloud operation failures and throttling.&lt;/li&gt;

&lt;li&gt;Advanced cloud operation support for ACL&amp;#8217;s, policies, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Help, bug reports, and pull requests for the project are most welcome. Please review the &lt;a href='http://sunnyjs.org/development.html'&gt;developer&amp;#8217;s guide&lt;/a&gt;, and contact me with any questions or comments.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/09/20/pivot-facets-solr</id>
      <title>Pivot Faceting (Decision Trees) in Solr 1.4.</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-09-20T16:00:00Z</published>
      <updated>2011-09-20T16:00:00Z</updated>
      <link href="http://loose-bits.com/2011/09/20/pivot-facets-solr.html"/>
      <content type="html">&lt;h2 id='solr_pivot_facets'&gt;Solr Pivot Facets&lt;/h2&gt;

&lt;p&gt;Solr &lt;a href='http://wiki.apache.org/solr/SolrFacetingOverview'&gt;faceting&lt;/a&gt; breaks down searches for terms, phrases, and fields in the &lt;a href='http://lucene.apache.org/solr/'&gt;Solr&lt;/a&gt; into aggregated counts by matched fields or queries. Facets are a great way to &amp;#8220;preview&amp;#8221; further searches, as well as a powerful aggregation tool in their own right.&lt;/p&gt;

&lt;p&gt;Before &lt;a href='http://wiki.apache.org/solr/Solr4.0'&gt;Solr 4.0&lt;/a&gt;, facets were only available at one level, meaning something like &amp;#8220;counts for field &amp;#8216;foo&amp;#8217;&amp;#8221; for a given query. Solr 4.0 introduced &lt;a href='http://wiki.apache.org/solr/SimpleFacetParameters#Pivot_.28ie_Decision_Tree.29_Faceting'&gt;pivot facets&lt;/a&gt; (also called decision trees) which enable facet queries to return &amp;#8220;counts for field &amp;#8216;foo&amp;#8217; for each different field &amp;#8216;bar&amp;#8217;&amp;#8221; &amp;#8211; a multi-level facet across separate Solr fields.&lt;/p&gt;

&lt;p&gt;Decision trees come up a lot, and at work, we need results along multiple axes &amp;#8211; typically in our case &amp;#8220;field/query by year&amp;#8221; for a time series. However, we use Solr 1.4.1 and are unlikely to migrate to Solr 4.0 in the meantime. Our existing approach was to simply query for the top &amp;#8220;n&amp;#8221; fields for a first query, then perform a second-level facet query by year for &lt;em&gt;each&lt;/em&gt; field result. So, for the top 20 results, we would perform 1 + 20 queries &amp;#8211; clearly not optimal, when we&amp;#8217;re trying to get this done in the context of a blocking HTTP request in our underlying web application.&lt;/p&gt;

&lt;p&gt;Hoping to get &lt;em&gt;something&lt;/em&gt; better than our 1 + &lt;em&gt;n&lt;/em&gt; separate queries approach, I began researching the somewhat more obscure facet features present in Solr 1.4.1. And after some investigation, experimentation and a good amount of hackery, I was able to come up with a &amp;#8220;faux&amp;#8221; pivot facet scheme that mostly approximates true pivot faceting using Solr 1.4.1.&lt;/p&gt;

&lt;p&gt;We&amp;#8217;ll start by examining some real pivot facets in Solr 4.0, then look at the components and full technique for simulated pivot facets in Solr 1.4.1.&lt;/p&gt;

&lt;h2 id='pivot_faceting_in_solr_40'&gt;Pivot Faceting in Solr 4.0&lt;/h2&gt;

&lt;p&gt;Pivot facets were added to Solr in &lt;a href='https://issues.apache.org/jira/browse/SOLR-792#referrer=solr.pl'&gt;SOLR-792&lt;/a&gt;. A good introductory &lt;a href='http://solr.pl/en/2010/10/25/hierarchical-faceting-pivot-facets-in-trunk/'&gt;article&lt;/a&gt; is available on the Solr.pl site. To see the basic operation in action, let&amp;#8217;s just use the &amp;#8220;example&amp;#8221; setup that comes with the Solr 4.0 distribution (located at &amp;#8220;solr_4.0_path/solr/example&amp;#8221;).&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;Let&amp;#8217;s start the Solr process:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Start Solr as non-daemon.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd &lt;/span&gt;solr_4.0_path/solr/example
&lt;span class='nv'&gt;$ &lt;/span&gt;java -jar start.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we want to upload a series of documents. We&amp;#8217;ll take the provided &amp;#8220;exampledocs/books.csv&amp;#8221; file, tweak it slightly and update via a CSV handler. The CSV format is: first line is field names, other lines are data. Note that I have changed some field names in the original sample &amp;#8220;exampledocs/books.csv&amp;#8221; file. The following should be written out to a new file, which I am calling &amp;#8220;sample_books.csv&amp;#8221;.&lt;/p&gt;

&lt;p&gt;Note that we use &lt;code&gt;_s&lt;/code&gt; fields for simplicity, forcing string fields for what would ordinarily be text fields &amp;#8211; Solr facets only return results on &lt;em&gt;indexed&lt;/em&gt;, not &lt;em&gt;stored&lt;/em&gt; terms, and string fields are identical for both. In a real deployment, you would use &lt;code&gt;copyField&lt;/code&gt; directives to copy &lt;code&gt;text&lt;/code&gt; fields to &lt;code&gt;string&lt;/code&gt; fields for faceting.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Create CSV file.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;vim sample_books.csv
id,cat_s,name_s,price_f,inStock_b,author_s,series_s,sequence_i,genre_s
0553573403,book,A Game of Thrones,7.99,true,George R.R. Martin,A Song of Ice and Fire,1,fantasy
0553579908,book,A Clash of Kings,7.99,true,George R.R. Martin,A Song of Ice and Fire,2,fantasy
055357342X,book,A Storm of Swords,7.99,true,George R.R. Martin,A Song of Ice and Fire,3,fantasy
0553293354,book,Foundation,7.99,true,Isaac Asimov,Foundation Novels,1,scifi
0812521390,book,The Black Company,6.99,false,Glen Cook,The Chronicles of The Black Company,1,fantasy
0812550706,book,Enders Game,6.99,true,Orson Scott Card,Ender,1,scifi
0441385532,book,Jhereg,7.95,false,Steven Brust,Vlad Taltos,1,fantasy
0380014300,book,Nine Princes In Amber,6.99,true,Roger Zelazny,the Chronicles of Amber,1,fantasy
0805080481,book,The Book of Three,5.99,true,Lloyd Alexander,The Chronicles of Prydain,1,fantasy
080508049X,book,The Black Cauldron,5.99,true,Lloyd Alexander,The Chronicles of Prydain,2,fantasy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We&amp;#8217;ll upload this file to Solr using curl:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Upload to Solr.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/update/csv &lt;span class='se'&gt;\&lt;/span&gt;
  --data-binary @sample_books.csv &lt;span class='se'&gt;\&lt;/span&gt;
  -H &lt;span class='s1'&gt;&amp;#39;Content-type:text/plain; charset=utf-8&amp;#39;&lt;/span&gt;

&lt;span class='c'&gt;# Commit the upload.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/update &lt;span class='se'&gt;\&lt;/span&gt;
  --data-binary &lt;span class='s1'&gt;&amp;#39;&amp;lt;commit/&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
  -H &lt;span class='s1'&gt;&amp;#39;Content-type:application/xml&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should now be able to query the 10 sample documents at: &lt;code&gt;http://localhost:8983/solr/admin/form.jsp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have some documents to work with, let&amp;#8217;s do a simple pivot query on price by genre:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Pivot query.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/select --data &lt;span class='nv'&gt;indent&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;on&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;wt&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;json&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;*%3A*&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;rows&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;0&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;facet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;true&lt;/span&gt;&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class='o'&gt;=&lt;/span&gt;1&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class='o'&gt;=&lt;/span&gt;index&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.pivot&lt;span class='o'&gt;=&lt;/span&gt;price_f,genre_s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Note that I&amp;#8217;ve added line breaks and escapes to show the parameters more clearly).&lt;/p&gt;

&lt;p&gt;This gives us decision tree results for the &lt;code&gt;facet_pivot&lt;/code&gt; field:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;facet_counts&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s2'&gt;&amp;quot;facet_pivot&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;price_f,genre_s&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mf'&gt;5.99&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;pivot&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mf'&gt;6.99&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;pivot&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
            &lt;span class='p'&gt;{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;scifi&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mf'&gt;7.95&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;pivot&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mf'&gt;7.99&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='s2'&gt;&amp;quot;pivot&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
            &lt;span class='p'&gt;{&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;field&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;genre_s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;scifi&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
              &lt;span class='s2'&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;}]}]}}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nice intuitive results, for a fairly straightforward facet query. However, now to the bigger question &amp;#8211; can we approximate this in Solr 1.4.1, which doesn&amp;#8217;t have the &lt;code&gt;facet.pivot&lt;/code&gt; query option?&lt;/p&gt;

&lt;h2 id='pivot_faceting_in_solr_141'&gt;Pivot Faceting in Solr 1.4.1&lt;/h2&gt;

&lt;p&gt;Solr 1.4.1 has much more limited facet support than 4.0. The building blocks that we will use to cobble together a &amp;#8220;faux&amp;#8221; pivot query are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;facet.field&lt;/code&gt;&lt;/strong&gt;: The normal facet field option.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;facet.query&lt;/code&gt;&lt;/strong&gt;: The normal facet query option.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;fq&lt;/code&gt;&lt;/strong&gt;: Basic field queries (for restrictions).&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local Params&lt;/strong&gt;: We use a couple of Solr &lt;a href='http://wiki.apache.org/solr/LocalParams'&gt;local parameters&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tag&lt;/code&gt;&lt;/strong&gt;: Tags a &lt;code&gt;fq&lt;/code&gt; with an arbitrary name.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;code&gt;key&lt;/code&gt;&lt;/strong&gt;: Tags a facet field an arbitrary name (instead of field name).&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;code&gt;ex&lt;/code&gt;&lt;/strong&gt;: Excludes tagged &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s from being operative on a given facet field/query.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that either facet fields or facet queries can be used with this technique &amp;#8211; I&amp;#8217;ll only show fields, but everything applies equally to queries.&lt;/p&gt;

&lt;h3 id='setup'&gt;Setup&lt;/h3&gt;

&lt;p&gt;At this point, you should take a Solr 1.4.1 distribution and set it up exactly as we did above for Solr 4.0 and upload our simple 10-document CSV file to the running server. For simplicity here (and to keep my head on straight), I ended up running my Solr 1.4.1 server on port 8984, so that I could also keep the Solr 4.0 server running on port 8983. Here&amp;#8217;s what I did:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Start Solr as non-daemon.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd &lt;/span&gt;solr_1.4.1_path/solr/example
&lt;span class='nv'&gt;$ &lt;/span&gt;java -Djetty.port&lt;span class='o'&gt;=&lt;/span&gt;8984 -jar start.jar

&lt;span class='c'&gt;# (Copy sample_books.csv)&lt;/span&gt;
&lt;span class='c'&gt;# Upload to Solr.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/update/csv &lt;span class='se'&gt;\&lt;/span&gt;
  --data-binary @sample_books.csv &lt;span class='se'&gt;\&lt;/span&gt;
  -H &lt;span class='s1'&gt;&amp;#39;Content-type:text/plain; charset=utf-8&amp;#39;&lt;/span&gt;

&lt;span class='c'&gt;# Commit the upload.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/update &lt;span class='se'&gt;\&lt;/span&gt;
  --data-binary &lt;span class='s1'&gt;&amp;#39;&amp;lt;commit/&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
  -H &lt;span class='s1'&gt;&amp;#39;Content-type:application/xml&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From here on, it is assumed you now have a populated Solr 1.4.1 server running on port 8984 (switch addresses / ports as appropriate for your actual setup).&lt;/p&gt;

&lt;h3 id='excluding_restrictions_from_facets'&gt;Excluding Restrictions from Facets&lt;/h3&gt;

&lt;p&gt;The starting point for our pivot facets is excluding certain query restrictions for facets. A &lt;a href='http://wiki.apache.org/solr/SimpleFacetParameters#Multi-Select_Faceting_and_LocalParams'&gt;basic example&lt;/a&gt; is provided for tagging and excluding facets on the Solr wiki.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s do a simple facet query on prices with a restriction of &lt;code&gt;genre_s:scifi&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Restricted facet query.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\i&lt;/span&gt;ndent&lt;span class='o'&gt;=&lt;/span&gt;on&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;wt&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;json&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;*%3A*&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;rows&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;0&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;genre_s:scifi&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;facet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;true&lt;/span&gt;&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class='o'&gt;=&lt;/span&gt;1&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class='o'&gt;=&lt;/span&gt;index&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;=&lt;/span&gt;price_f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking to our results in &lt;code&gt;facet_fields&lt;/code&gt;, we see that we only have 2 hits (&lt;code&gt;numFound&lt;/code&gt;), and the facet counts also add up to 2 (which represent our 2 SciFi books).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;response&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;numFound&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;facet_counts&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s2'&gt;&amp;quot;facet_fields&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;6.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;7.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For situations like a drill down, Solr developers often want to run a basic query with full restrictions for the returned records, but get more information for facets. In this case, Solr allows tagging of &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s, and keys / excludes on facets to conditionally remove &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s from a &lt;em&gt;given facet only&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So, let&amp;#8217;s tag our &lt;code&gt;fq&lt;/code&gt; as &amp;#8220;SCIFI_FQ&amp;#8221; and exclude it from our facet counts with &lt;code&gt;ex&lt;/code&gt;, and then rename the facet results to &amp;#8220;PRICE_KEY&amp;#8221; using the &lt;code&gt;key&lt;/code&gt; option:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Tag fq and exclude only from the field facet.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\i&lt;/span&gt;ndent&lt;span class='o'&gt;=&lt;/span&gt;on&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;wt&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;json&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;*%3A*&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;rows&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;0&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;tag&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;SCIFI_FQ&lt;span class='o'&gt;}&lt;/span&gt;genre_s:scifi&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;facet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;true&lt;/span&gt;&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class='o'&gt;=&lt;/span&gt;1&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class='o'&gt;=&lt;/span&gt;index&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;PRICE_KEY&lt;span class='se'&gt;\ &lt;/span&gt;&lt;span class='nv'&gt;ex&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;SCIFI_FQ&lt;span class='o'&gt;}&lt;/span&gt;price_f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that I have to escape the exclamation points and other characters for a command line example here. Now, let&amp;#8217;s look at the results:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;response&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;numFound&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
 &lt;span class='s2'&gt;&amp;quot;facet_counts&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s2'&gt;&amp;quot;facet_fields&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;PRICE_KEY&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;5.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;6.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;7.95&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;7.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can first see that the exclusion of the tagged &amp;#8220;SCIFI_FQ&amp;#8221; field query did not affect the overall &lt;code&gt;numFound&lt;/code&gt;, which is still 2. However, for the facet field we applied the exclusion to, we now have facet results for records in the whole set (which is the effective query after the exclusion). Finally, our facet field has been renamed &amp;#8220;PRICE_KEY&amp;#8221; instead of the field name (&amp;#8220;price_f&amp;#8221;).&lt;/p&gt;

&lt;h3 id='constructing_a_pivot_query'&gt;Constructing a Pivot Query&lt;/h3&gt;

&lt;p&gt;With the basic tag/key/exclude technique in mind, let&amp;#8217;s now return to our original goal &amp;#8211; create a pivot query on price by genre using Solr 1.4.1. We will do this by performing two queries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Perform a facet query for the top price results ordered by index.&lt;/li&gt;

&lt;li&gt;Create &lt;code&gt;fq&lt;/code&gt; tagged exclusions for each facet result, then create multiple keyed facets on genre to give us each of our decision tree &amp;#8220;leaf&amp;#8221; results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first query is a very basic facet field query:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# First level facet field query.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class='nv'&gt;indent&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;on&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;wt&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;json&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;*%3A*&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;rows&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;0&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;facet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;true&lt;/span&gt;&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class='o'&gt;=&lt;/span&gt;1&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class='o'&gt;=&lt;/span&gt;index&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;=&lt;/span&gt;price_f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which gives us four individual facet results: &amp;#8220;5.99&amp;#8221;, &amp;#8220;6.99&amp;#8221;, &amp;#8220;7.95&amp;#8221;, &amp;#8220;7.99&amp;#8221;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;response&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;numFound&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
 &lt;span class='s2'&gt;&amp;quot;facet_counts&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s2'&gt;&amp;quot;facet_fields&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;price_f&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;5.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;6.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;7.95&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;7.99&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We take each of those results and create specific &lt;code&gt;fq&lt;/code&gt; tagged restrictions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;fq={!tag=FQ5.99}price_f:5.99
fq={!tag=FQ6.99}price_f:6.99
fq={!tag=FQ7.95}price_f:7.95
fq={!tag=FQ7.99}price_f:7.99
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each excludes one of the components we&amp;#8217;ll want facet results for our next level field (genre) on. To then get the pivot facet result for each of our four facets, we will exclude all the &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s above &lt;strong&gt;except&lt;/strong&gt; the matching one for the facet. Translating into facet parameters, this is:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;facet.field={!key=5.99_GENRE ex=FQ6.99,FQ7.95,FQ7.99}genre_s
facet.field={!key=6.99_GENRE ex=FQ5.99,FQ7.95,FQ7.99}genre_s
facet.field={!key=7.95_GENRE ex=FQ5.99,FQ6.99,FQ7.99}genre_s
facet.field={!key=7.99_GENRE ex=FQ5.99,FQ6.99,FQ7.95}genre_s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The key is that we can specify &lt;em&gt;multiple&lt;/em&gt; exclusions using a comma. Thus, looking to the facet key &amp;#8220;5.99_GENRE&amp;#8221;, we exclude all the &lt;code&gt;fq&lt;/code&gt; restrictions &lt;em&gt;except&lt;/em&gt; &amp;#8220;FQ5.99&amp;#8221;, which means that the facet results for that facet field key will be the facet counts for &amp;#8221;&lt;code&gt;fq=price_f:5.99&lt;/code&gt;&amp;#8221; only. It&amp;#8217;s kind of a twisted-double-negative logic, but it all works out.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s put everything into our second-level query now:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;# Second level pivot query.&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='nv'&gt;indent&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;on&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;wt&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;json&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;*%3A*&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;rows&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;0&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;facet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;true&lt;/span&gt;&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class='o'&gt;=&lt;/span&gt;1&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class='o'&gt;=&lt;/span&gt;index&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;tag&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ5.99&lt;span class='o'&gt;}&lt;/span&gt;price_f:5.99&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;tag&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ6.99&lt;span class='o'&gt;}&lt;/span&gt;price_f:6.99&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;tag&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ7.95&lt;span class='o'&gt;}&lt;/span&gt;price_f:7.95&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;&lt;span class='nv'&gt;fq&lt;/span&gt;&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;tag&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ7.99&lt;span class='o'&gt;}&lt;/span&gt;price_f:7.99&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;5.99_GENRE&lt;span class='se'&gt;\ &lt;/span&gt;&lt;span class='nv'&gt;ex&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ6.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.95&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.99&lt;span class='o'&gt;}&lt;/span&gt;genre_s&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;6.99_GENRE&lt;span class='se'&gt;\ &lt;/span&gt;&lt;span class='nv'&gt;ex&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ5.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.95&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.99&lt;span class='o'&gt;}&lt;/span&gt;genre_s&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;7.95_GENRE&lt;span class='se'&gt;\ &lt;/span&gt;&lt;span class='nv'&gt;ex&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ5.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ6.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.99&lt;span class='o'&gt;}&lt;/span&gt;genre_s&lt;span class='se'&gt;\&lt;/span&gt;
&lt;span class='se'&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class='o'&gt;={&lt;/span&gt;&lt;span class='se'&gt;\!&lt;/span&gt;&lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;7.99_GENRE&lt;span class='se'&gt;\ &lt;/span&gt;&lt;span class='nv'&gt;ex&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;FQ5.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ6.99&lt;span class='se'&gt;\,&lt;/span&gt;FQ7.95&lt;span class='o'&gt;}&lt;/span&gt;genre_s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which gives us the &amp;#8220;leaves&amp;#8221; of the decision tree with our result keys: &amp;#8220;5.99_GENRE&amp;#8221;, &amp;#8220;6.99_GENRE&amp;#8221;, &amp;#8220;7.95_GENRE&amp;#8221;, and &amp;#8220;7.99_GENRE&amp;#8221;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;response&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;numFound&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
 &lt;span class='s2'&gt;&amp;quot;facet_counts&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s2'&gt;&amp;quot;facet_fields&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;5.99_GENRE&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;6.99_GENRE&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;scifi&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;7.95_GENRE&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
      &lt;span class='s2'&gt;&amp;quot;7.99_GENRE&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;fantasy&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;scifi&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='cm'&gt;/* ... */&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking at our original Solr 4.0 pivot query, we can cobble together our two Solr 1.4.1 queries to get an equivalent result. In the end, both produce the following decision tree for price by genre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5.99&lt;/strong&gt;: 2&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;6.99&lt;/strong&gt;: 3&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 2&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;scifi&lt;/strong&gt;: 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;7.95&lt;/strong&gt;: 1&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;strong&gt;7.99&lt;/strong&gt;: 4&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 3&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;scifi&lt;/strong&gt;: 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Victory!&lt;/p&gt;

&lt;h2 id='discussion_and_practical_implications'&gt;Discussion and Practical Implications&lt;/h2&gt;

&lt;p&gt;Our &amp;#8220;price by genre&amp;#8221; example is a bit simplistic in that we can mostly get the same results with two standard Solr 1.4.1 facet field queries. But, the faux pivot facet technique really shines for a &amp;#8220;foo by bar&amp;#8221;-type query where there are large numbers of first (&amp;#8220;foo&amp;#8221;) level facet results. Say, the first level has 10 results, this would mean 11 queries (one for the top 10 &amp;#8220;foo&amp;#8220;&amp;#8216;s, then one &lt;em&gt;each&lt;/em&gt; for the 10 second-level &amp;#8220;bar&amp;#8220;&amp;#8216;s for each &amp;#8220;foo&amp;#8221;). The faux pivot facet technique cuts this down to 2 queries total.&lt;/p&gt;

&lt;p&gt;The method is generally applicable too. Although our examples here only use facet fields, the technique equally works for &lt;a href='http://wiki.apache.org/solr/SimpleFacetParameters#facet.query_:_Arbitrary_Query_Faceting'&gt;facet queries&lt;/a&gt;. And &lt;a href='http://wiki.apache.org/solr/DistributedSearch'&gt;distributed search&lt;/a&gt; supports the approach as well.&lt;/p&gt;

&lt;p&gt;Looking further, the technique can be applied to additional decision tree levels. In the Solr 4.0 world, this simply means adding another field like &lt;code&gt;facet.pivot=price_f,genre_s,inStock_b&lt;/code&gt; to get further breakdowns for the &amp;#8220;in stock&amp;#8221; boolean field. For Solr 1.4.1, we would do a third query, with permutations of our previous tagged &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s, as well as second-level &lt;code&gt;fq&lt;/code&gt;&amp;#8217;s. Then, we would have third-level keyed facet fields like: &amp;#8220;6.99_fantasy_INSTOCK&amp;#8221;, &amp;#8220;6.99_scifi_INSTOCK&amp;#8221;, etc. At this level, it certainly wouldn&amp;#8217;t be pretty and would result in a beastly query, but shows that the technique only adds 1 more actual Solr query for each additional level in the faux decision tree.&lt;/p&gt;

&lt;p&gt;However, on the topic of query complexity, it is fair to point out that this type of query hackery really should be done programmatically to ensure correctness, and definitely not via the manual queries I provided above using curl. It&amp;#8217;s tough keeping track of just the 4 first-level pivots in our example above, let alone a larger first level group, or more than 2 levels deep of pivots. Another benefit is that you can collapse your tag / key names to integer or other simple keys, and then have your program match things up later for the final assembled decision tree result.&lt;/p&gt;

&lt;p&gt;As a final performance note, the faux pivot facet approach doesn&amp;#8217;t really lighten the Solr server load, it just collapses what would otherwise be multiple queries into one query.&lt;/p&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Reflecting on the above method, pivot facets are possible in Solr 1.4.1 at a cost of &lt;em&gt;n&lt;/em&gt; separate queries, where &lt;em&gt;n&lt;/em&gt; is the number of levels in the decision tree. So, if reducing the number of round trips between a web application and Solr is the goal, and you need pivot facets in a pre-4.0 Solr, this may well be the ticket.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/09/16/moving-to-jekyll</id>
      <title>How I Learned to Stop Worrying and Love the Static Blog</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-09-16T16:00:00Z</published>
      <updated>2011-09-16T16:00:00Z</updated>
      <link href="http://loose-bits.com/2011/09/16/moving-to-jekyll.html"/>
      <content type="html">&lt;h2 id='farewell_blogger'&gt;Farewell Blogger&amp;#8230;&lt;/h2&gt;

&lt;p&gt;Blogger provides an easy, integrated blogging service, but I have not been posting with anywhere near the frequency I had hoped for, and have recently started thinking of the reasons why this is. The list I cam up with includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page load times&lt;/strong&gt;: To get syntax highlighting, a nice theme, etc., my posts have an incredible amount of download cruft.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Editing Interface&lt;/strong&gt;: While blogger&amp;#8217;s WYSIWYG editor is fairly intuitive and the HTML editor is the appropriate power tool, I don&amp;#8217;t like the fact I have to (1) be online, and (2) find a lot of situations where I want something in between a WYSIWYG and raw HTML.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Offline Editing&lt;/strong&gt;: Expanding on the above point, I travel a lot, and it would be great to be able to compose and view posts offline (like on a plane).&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Versioning/Backups&lt;/strong&gt;: Blogger doesn&amp;#8217;t allow for easily versioning of posts, which would be nice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I essentially run a programming blog, and as such, I don&amp;#8217;t need a lot of frills, bells or whistles. What I really want is text-based, powerful and configurable blog engine. Enter &lt;a href='http://jekyllrb.com/'&gt;Jekyll&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id='hello_jekyll_and_github'&gt;Hello Jekyll and GitHub!&lt;/h2&gt;

&lt;p&gt;After a decent amount of research, I settled on &lt;a href='http://jekyllrb.com/'&gt;Jekyll&lt;/a&gt;. Jekyll is a static website generator, written in Ruby, supporting &lt;a href='http://daringfireball.net/projects/markdown/'&gt;Markdown&lt;/a&gt;, &lt;a href='http://liquidmarkup.org/'&gt;Liquid templates&lt;/a&gt;, and custom &lt;a href='https://github.com/mojombo/jekyll/wiki/liquid-extensions'&gt;Liquid extensions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;a href='http://www.google.com/search?q=moving+to+jekyll'&gt;many&lt;/a&gt; stories of folks moving successfully to Jekyll, and the feature set really hit my pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All Text&lt;/strong&gt;: All files are text, and either configuration, markdown, template language, or whatnot.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Versioned&lt;/strong&gt;: It&amp;#8217;s all source, so place your source under Git, and you&amp;#8217;re ready to go.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Offline&lt;/strong&gt;: The whole site can be generated or locally served without and internet connection.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Markdown&lt;/strong&gt;: There are other pre-processing options, but I just went with Markdown, and it&amp;#8217;s really nice and easy to write posts now without jumping back and forth from a WYSIWYG editor to straight HTML. I stay in Markdown and everything (mostly) ends up looking correct.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Syntax Highlighting&lt;/strong&gt;: Jekyll uses &lt;a href='http://pygments.org/'&gt;Pygments&lt;/a&gt; for source code highlighting, which is mostly pre-processed, and not an after-the-fact JavaScript processing step (which had previously been slowing down my Blogger site).&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;GitHub Support&lt;/strong&gt;: GitHub&amp;#8217;s default document generator is Jekyll, and (separately) has full website support with CNAMEs. This provides an easy means of both site storage / versioning as well as the actual serving.&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- more start --&gt;
&lt;p&gt;However, this is not to say that a Jekyll site is everyone&amp;#8217;s cup of tea. There are some drawbacks to this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static Site&lt;/strong&gt;: The generated site is static, which means no database or dynamic pages for things like comments, etc. All dynamic content has to come in via JavaScript / JavaScript-based services (like &lt;a href='http://disqus.com/'&gt;Disqus&lt;/a&gt; for comments). And, you have to implement a manual RSS feed, etc.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Manual CSS, HTML, etc.&lt;/strong&gt;: There are no built-in themes, etc., because you are crafting a site from scratch. Really, this isn&amp;#8217;t too bad because other Jekyll-site authors often publicly version their source with permissive / public domain licenses at GitHub, so you can pull source for examples. Also, there are a lot of starter kits for static CSS / HTML sites. For me, it&amp;#8217;s really a nice opportunity to get re-acquainted with site design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='packing_up_and_moving_out'&gt;Packing Up and Moving Out&lt;/h2&gt;

&lt;h3 id='posts'&gt;Posts&lt;/h3&gt;

&lt;p&gt;The first step to migrating my Blogger site was to simply download all of the posts in a Jekyll-friendly format. I used a very simple &lt;a href='https://gist.github.com/1115810'&gt;script&lt;/a&gt; that a good job of downloading basic HTML from the Blogger feed URL.&lt;/p&gt;

&lt;p&gt;I then took the HTML pages and further converted them to Markdown using the handy &lt;a href='http://www.aaronsw.com/2002/html2text/'&gt;html2text&lt;/a&gt; script. From there, I had to manually edit the gremlins left in the conversion process, including redoing anchor tags, removing Blogger-specific extras, and getting local images into my source repository static media directory.&lt;/p&gt;

&lt;p&gt;Jekyll has its own default (but customizable) format for blog post names. I like it, but it is slightly different than Blogger, which all my existing posts already use. Fortunately, each Jekyll post has a &lt;code&gt;permalink&lt;/code&gt; attribute allowing the post to escape the normal URL processing rules and use a one-time path instead.&lt;/p&gt;

&lt;h3 id='comments'&gt;Comments&lt;/h3&gt;

&lt;p&gt;After getting my posts ported over, the next step was to get my comments out of Blogger and in to something that would work with the static Jekyll web site. Enter &lt;a href='http://disqus.com/'&gt;Disqus&lt;/a&gt;, which is inserted via JavaScript, and this is usable for my new static website.&lt;/p&gt;

&lt;p&gt;Making things easier, Disqus has &lt;a href='http://loose-bits.disqus.com/admin/blogger/'&gt;Blogger support&lt;/a&gt;. So, I first switched my Blogger site comments to Disqus, then used the Disqus blogger import tool, available at:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://&amp;lt;DISQUS SHORT NAME&amp;gt;.disqus.com/admin/tools/import/platform/blogger/&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I followed the instructions and started the Blogger comment import. I expected the import to finish quickly, as I only have a handful of comments at the time of the migration, but it ended up taking 6 or so hours. Searching the web, there are reports of the Disqus comment import taking up to 2-3 days, so the lesson is: be patient. Oh, and don&amp;#8217;t get freaked out when your comments disappear entirely from Blogger in the meantime.&lt;/p&gt;

&lt;p&gt;Once the import finished, I looked at my Blogger site and saw that all of the comments had successfully been transitioned over to Disqus. I then set up Disqus support on my post template using the Disqus &amp;#8221;&lt;a href='http://docs.disqus.com/developers/universal/'&gt;universal&lt;/a&gt;&amp;#8221; installation guidelines.&lt;/p&gt;

&lt;p&gt;Before fully transitioning over to my new Jekyll/GitHub site, I wanted to make sure that everything was good to go with Disqus. Fortunately, Disqus allows you to specify a &amp;#8220;developer mode&amp;#8221; to have comments pulled down, even when running a localhost site. With these configuration variables in the Disqus JavaScript block, I can view the comments for this post on my laptop at localhost:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// Live page URL.&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;disqus_url&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;http://loose-bits.com/2011/09/16/moving-to-jekyll.html&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

&lt;span class='c1'&gt;// Developer mode for localhost.&lt;/span&gt;
&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;disqus_developer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id='rss_feed'&gt;RSS Feed&lt;/h3&gt;

&lt;p&gt;You have to specify your own &amp;#8220;atom.xml&amp;#8221; file for RSS feeds. Fortunately, there are many examples from other Jekyll sites, including Tom Preston-Werner&amp;#8217;s &lt;a href='https://github.com/mojombo/mojombo.github.com'&gt;site&lt;/a&gt;, as well as a good Google Groups &lt;a href='http://groups.google.com/group/jekyll-rb/browse_thread/thread/5a8af8abb59ff9ac/914585f6e43e9b92'&gt;thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is my finished &lt;a href='http://github.com/ryan-roemer/loose-bits/blob/master/atom.xml'&gt;atom.xml&lt;/a&gt; for this site for reference.&lt;/p&gt;

&lt;h3 id='post_excerpts'&gt;Post Excerpts&lt;/h3&gt;

&lt;p&gt;My Blogger site displays a few recents post excerpts on the index page, which I liked. However, there is no native &amp;#8220;excerpt&amp;#8221; functionality in Jekyll at the moment. Fortunately, a very clever &lt;a href='http://kaspa.rs/2011/04/jekyll-hacks-html-excerpts/'&gt;hack&lt;/a&gt; enabled me to add post excerpts of my most recent couple of articles to my new index page.&lt;/p&gt;

&lt;h2 id='and_were_up'&gt;And, We&amp;#8217;re Up!&lt;/h2&gt;

&lt;p&gt;And that finishes my journey from Blogger to Jekyll and GitHub. As a final commentary Blogger does a great many things right and is a solid platform, but my use cases favor power and extensibility over many of the conveniences that Blogger provides.&lt;/p&gt;

&lt;p&gt;Although it took some time to put my new site together, everything feels &amp;#8220;right&amp;#8221; to me. Even writing this first post for the new site has been considerably easier &amp;#8211; I&amp;#8217;ve been both online and offline composing the post in TextMate (which has Markdown syntax highlighting as well as spell check), and it has never been as easy or fast to write up posts, so here&amp;#8217;s to hoping for a more consistent stream of posts in the future!&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/06/constantdict-yet-another-python</id>
      <title>ConstantDict, Yet Another Python Enumeration Pattern</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-06-13T13:30:00Z</published>
      <updated>2011-06-13T13:30:00Z</updated>
      <link href="http://loose-bits.com/2011/06/constantdict-yet-another-python.html"/>
      <content type="html">&lt;h2 id='introduction'&gt;Introduction&lt;/h2&gt;

&lt;p&gt;The Python language does not natively provide enumerations. Rejected &lt;a href='http://www.python.org/dev/peps/pep-0354/'&gt;PEP 354&lt;/a&gt; proposed a string-constructor-based enumeration, later provided as the separate enum &lt;a href='http://pypi.python.org/pypi/enum/'&gt;package&lt;/a&gt;. The web is rife with various cookbook recipes and ad-hoc solutions &amp;#8211; &lt;em&gt;see, e.g.&lt;/em&gt;, &lt;a href='http://code.activestate.com/recipes/413486/'&gt;ActiveState&lt;/a&gt; and &lt;a href='http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python'&gt;Stack Overflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is &lt;code&gt;ConstantDict&lt;/code&gt;, one of my preferred enumeration patterns.&lt;/p&gt;

&lt;h2 id='id1'&gt;&lt;code&gt;ConstantDict&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;This pattern (like most Python enumeration recipes) is quite simple and straightforward, so I&amp;#8217;ll actually start with the enumeration class, and then discuss the merits / drawbacks after.&lt;/p&gt;

&lt;p&gt;The enumeration class is as follows:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ConstantDict&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;object&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;An enumeration class.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;_dict&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;None&lt;/span&gt;

    &lt;span class='nd'&gt;@classmethod&lt;/span&gt;
    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;dict&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Dictionary of all upper-case constants.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_dict&lt;/span&gt; &lt;span class='ow'&gt;is&lt;/span&gt; &lt;span class='bp'&gt;None&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
            &lt;span class='n'&gt;val&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;lambda&lt;/span&gt; &lt;span class='n'&gt;x&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='nb'&gt;getattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;x&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
            &lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_dict&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;dict&lt;/span&gt;&lt;span class='p'&gt;(((&lt;/span&gt;&lt;span class='n'&gt;c&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;val&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;c&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='n'&gt;c&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='nb'&gt;dir&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                             &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;c&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;c&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;upper&lt;/span&gt;&lt;span class='p'&gt;()))&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;cls&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_dict&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;__contains__&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dict&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;values&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;__iter__&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dict&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;values&lt;/span&gt;&lt;span class='p'&gt;():&lt;/span&gt;
            &lt;span class='k'&gt;yield&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;!-- more start --&gt;
&lt;p&gt;Essentially, we create an internal dictionary of all class variables that are upper-cased (well, and methods too), and wrap up a couple of instance built-in methods. Implementing an enumeration with the pattern is pretty straightforward. Here is a &amp;#8220;days of the week&amp;#8221; enumeration contrived by multiply inheriting two base enumeration classes:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Weekdays&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ConstantDict&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Weekday days.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;MONDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Monday&amp;#39;&lt;/span&gt;
    &lt;span class='n'&gt;TUESDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Tuesday&amp;#39;&lt;/span&gt;
    &lt;span class='n'&gt;WEDNESDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Wednesday&amp;#39;&lt;/span&gt;
    &lt;span class='n'&gt;THURSDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Thursday&amp;#39;&lt;/span&gt;
    &lt;span class='n'&gt;FRIDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Friday&amp;#39;&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Weekend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ConstantDict&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Weekend days.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;SATURDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Saturday&amp;#39;&lt;/span&gt;
    &lt;span class='n'&gt;SUNDAY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;Sunday&amp;#39;&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;DaysOfTheWeek&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Weekdays&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;Weekend&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Days of the week.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And a simple use case example:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='c'&gt;# Get enumeration instance.&lt;/span&gt;
&lt;span class='n'&gt;DAYS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;DaysOfTheWeek&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;DAYS&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;MONDAY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;        &lt;span class='c'&gt;# &amp;quot;Monday&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;Saturday&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;DAYS&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='c'&gt;# True&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;Notaday&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;DAYS&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;  &lt;span class='c'&gt;# False&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;x&lt;/span&gt; &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='n'&gt;x&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;DAYS&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;  &lt;span class='c'&gt;# [&amp;quot;Monday&amp;quot;, &amp;quot;Tuesday&amp;quot;, ...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id='discussion'&gt;Discussion&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ConstantDict&lt;/code&gt; pattern shines in a couple of key areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It has a terse, clean, and extensible structure. The enumerations are inheritable, and even support multiple inheritance (as the above examples shows). Also, the enumerations can be enhanced with helper methods, etc., because they are real classes.&lt;/li&gt;

&lt;li&gt;The structure allows arbitrary enumeration values. In my example I used strings, but anything (even including functions) are valid values.&lt;/li&gt;

&lt;li&gt;The enumeration members are real class variables and not strings. For me this is a key point over a pure string-based solution, as I want to be able to run &lt;code&gt;pylint&lt;/code&gt; and have it throw errors on invalid enumeration names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, &lt;code&gt;ConstantDict&lt;/code&gt; probably isn&amp;#8217;t the best choice for many situations. Some of the drawbacks of the pattern include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enumeration members are unordered. Because everything is shoved into a dictionary, there are no order guarantees.&lt;/li&gt;

&lt;li&gt;Despite being called &lt;code&gt;ConstantDict&lt;/code&gt;, the enumeration values aren&amp;#8217;t actually enforced constants.&lt;/li&gt;

&lt;li&gt;Enumeration values must be manually specified and are not guaranteed to be unique. As noted above arbitrary (including identical) enumeration values are allowed. However, the pattern could be easily extended to raise an exception on detection of duplicate values.&lt;/li&gt;

&lt;li&gt;An enumeration class cannot have other upper-cased class variables or members. This should not be too big of a deal as the enumeration class is ideally used just as a small constant holder and not a multi-purpose class.&lt;/li&gt;

&lt;li&gt;The caller has to instantiate an instance of the &lt;code&gt;ConstantDict&lt;/code&gt; class. This is necessary for the &lt;code&gt;__contains__&lt;/code&gt; and &lt;code&gt;__iter__&lt;/code&gt; convenience method. In the past, I have used a different version of the &lt;code&gt;ConstantDict&lt;/code&gt; class that is used only at the class variable / method level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are certainly other tweaks and enhancements that can make the &lt;code&gt;ConstantDict&lt;/code&gt; pattern better for a given situation. But, in its most basic form as described above, it provides an easy, extensible thin-wrapper enumeration.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/04/extending-django-settings-with-derived</id>
      <title>Extending Django Settings with Derived Attributes and Methods</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-04-20T05:07:00Z</published>
      <updated>2011-04-20T05:07:00Z</updated>
      <link href="http://loose-bits.com/2011/04/extending-django-settings-with-derived.html"/>
      <content type="html">&lt;p&gt;&lt;a href='http://docs.djangoproject.com/en/dev/topics/settings/'&gt;Django settings&lt;/a&gt; construction and configuration is fairly straightforward. Configure Django and installed application settings, maybe add your own project-specific settings, and let Django take care of the rest. As long as you import settings from django.conf all the options and defaults are properly resolved into the ultimate settings object.&lt;/p&gt;

&lt;p&gt;However, every so often I run across a situation where I wish the Django settings could do just a little bit more. Rather than try and wade into the depths of Django runtime configuration and risk mucking things up, the approach I take is to use a thin wrapper class around the already configured settings and pass through all calls directly.&lt;/p&gt;

&lt;p&gt;What we essentially want is &amp;#8220;pass through&amp;#8221; wrapper class like:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.conf&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt; &lt;span class='k'&gt;as&lt;/span&gt; &lt;span class='n'&gt;_settings&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Settings&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;object&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Thin settings wrapper.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;__init__&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;default_settings&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Initializer.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;default_settings&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;default_settings&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;__getattr__&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Get setting.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nb'&gt;getattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;default_settings&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;settings&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;Settings&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;_settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fortunately, Django already has a slightly more complex version of this in the django.conf.UserSettingsHolder class, so we&amp;#8217;ll use that instead. I usually have a common application in my Django projects and insert the settings wrapper as the common.settings or common.conf module.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;From there, we can add any number of &amp;#8220;extra&amp;#8221; settings variables or derived methods. For instance, if we wanted to add a settings method to tell if our database was SQLite or PostgreSQL using knowledge of the driver names, we could implement some fairly simple methods:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;is_sqlite&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return ``True`` if default database is SQLite.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='c'&gt;# Driver: &amp;#39;sqlite3&amp;#39;&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;sqlite3&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;DATABASES&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;][&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;is_postgres&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return ``True`` if default database is PostgreSQL.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='c'&gt;# Drivers: &amp;#39;postgresql_psycopg2&amp;#39;, &amp;#39;postgresql&amp;#39;&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;postgresql&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;DATABASES&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;][&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While perhaps too simplistic and brittle for production use, the above example illustrates how easy it is to add extra dynamic checks. To use the above methods if very easy &amp;#8211; just import settings from our module (here common.settings instead of django.conf).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;common.settings&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;

&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Using SQLite?: &lt;/span&gt;&lt;span class='si'&gt;%s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;is_sqlite&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Using Postgres?: &lt;/span&gt;&lt;span class='si'&gt;%s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;is_postgres&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which outputs:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Using SQLite?: True
Using Postgres?: False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;for a project configured with &lt;code&gt;&amp;#39;ENGINE&amp;#39;: &amp;#39;django.db.backends.sqlite3&amp;#39;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a typical Django project there are several file paths that are usually derived and hacked into the settings file &amp;#8211; the Django project root directory, the Django admin media directory, and sometimes the path to the settings file itself. Here are methods for our wrapped settings class to intuit these paths:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;settings_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to project settings.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;os&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;
    &lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.utils&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;importlib&lt;/span&gt;

    &lt;span class='c'&gt;# Have to use real settings directly because UserSettingsHolder&lt;/span&gt;
    &lt;span class='c'&gt;# sets SETTINGS_MODULE to ``None`` as a class variable.&lt;/span&gt;
    &lt;span class='n'&gt;mod&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;importlib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;import_module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
      &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;default_settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;SETTINGS_MODULE&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='c'&gt;# Split off the extension to replace &amp;quot;.pyc&amp;quot; with &amp;quot;.py&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;splitext&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;mod&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;))[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;.py&amp;quot;&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;project_root_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to project root.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;os&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;

    &lt;span class='c'&gt;# We &amp;quot;cheat&amp;quot; here to find project root because we have know&lt;/span&gt;
    &lt;span class='c'&gt;# this file is ``PROJECT_ROOT/common/settings.py``.&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;..&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;admin_media_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to Django admin media directory.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;os&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;
    &lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;admin&lt;/span&gt;

    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
      &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;admin&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;media&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we can view our results with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;common.settings&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;

&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Settings file path: &lt;/span&gt;&lt;span class='si'&gt;%s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;settings_path&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Project root directory path: &lt;/span&gt;&lt;span class='si'&gt;%s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt;
      &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;project_root_path&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Admin media directory path: &lt;/span&gt;&lt;span class='si'&gt;%s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt;
      &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;admin_media_path&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which outputs:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;Settings file path: /Users/ryan/demo/settings.py
Project root directory path: /Users/ryan/demo
Admin media directory path: /Users/ryan/env/demo/lib/python2.6/site-packages/django/contrib/admin/media
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Putting a full file together with our simple extension methods. In our case, the finished &amp;#8220;common/settings.py&amp;#8221; file will be:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;os&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;admin&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.conf&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt; &lt;span class='k'&gt;as&lt;/span&gt; &lt;span class='n'&gt;_settings&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;UserSettingsHolder&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.utils&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;importlib&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;CustomSettings&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;UserSettingsHolder&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Thin settings wrapper.&lt;/span&gt;

&lt;span class='sd'&gt;    .. note:: &amp;quot;Real&amp;quot; settings object is available as&lt;/span&gt;
&lt;span class='sd'&gt;      ``.default_settings`` member.&lt;/span&gt;
&lt;span class='sd'&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;is_sqlite&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return ``True`` if default database is SQLite.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='c'&gt;# Driver: &amp;#39;sqlite3&amp;#39;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;sqlite3&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;DATABASES&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;][&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;is_postgres&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return ``True`` if default database is PostgreSQL.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='c'&gt;# Drivers: &amp;#39;postgresql_psycopg2&amp;#39;, &amp;#39;postgresql&amp;#39;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;postgresql&amp;#39;&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;DATABASES&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;][&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;settings_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to project settings.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='n'&gt;mod&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;importlib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;import_module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
          &lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;default_settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;SETTINGS_MODULE&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

        &lt;span class='c'&gt;# Split off the extension to replace &amp;quot;.pyc&amp;quot; with &amp;quot;.py&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;splitext&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;mod&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;))[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;.py&amp;quot;&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;project_root_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to project root.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
          &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;..&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;admin_media_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Return path to Django admin media directory.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;abspath&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
          &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;admin&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;__file__&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;media&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='n'&gt;settings&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;CustomSettings&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;_settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While these contrived example method extensions are solving problems perhaps best solved elsewhere (like in the actual project settings file), they serve to illustrate a simple means of how to extend the Django settings module easily and without interfering with the runtime settings configuration process.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/03/django-cloud-browser</id>
      <title>Django Cloud Browser</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-03-10T05:44:00Z</published>
      <updated>2011-03-10T05:44:00Z</updated>
      <link href="http://loose-bits.com/2011/03/django-cloud-browser.html"/>
      <content type="html">&lt;h2 id='introduction'&gt;Introduction&lt;/h2&gt;

&lt;p&gt;I recently pushed the v.0.1.2 release of my new Django application, django- cloud-browser, to &lt;a href='http://pypi.python.org/pypi'&gt;PyPi&lt;/a&gt;. Cloud Browser is a simple web-based file browser for cloud-based datastores, which so far includes &lt;a href='http://aws.amazon.com/s3/'&gt;Amazon S3&lt;/a&gt;, Rackspace &lt;a href='http://www.rackspace.com/cloud/cloud_hosting_products/files/'&gt;Cloud Files&lt;/a&gt; (including &lt;a href='http://www.openstack.org/projects/storage/'&gt;OpenStack&lt;/a&gt;) and local file system. The application can expose read-only cloud files to users and/or administrators with configurations for both normal and administrative deployments.&lt;/p&gt;

&lt;p&gt;The basic reference points to get started include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://ryan-roemer.github.com/django-cloud-browser/'&gt;Cloud Browser API / documentation&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='https://github.com/ryan-roemer/django-cloud-browser/'&gt;GitHub page&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://pypi.python.org/pypi/django-cloud-browser'&gt;PyPi page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='background'&gt;Background&lt;/h2&gt;

&lt;p&gt;At work, we have some containers with very large numbers of objects &amp;#8211; over 5 million and counting. While objects ultimately are named in a flat namespace, we divide up the namespace with slashes (e.g., &amp;#8220;path/to/cloud/object/file.txt&amp;#8221;). Nearly all cloud providers (Amazon S3, Microsoft Azure, and Rackspace) include functionality in their object APIs to return results grouped around hierarchical &amp;#8220;implied&amp;#8221; or &amp;#8220;pseudo-&amp;#8221; directory objects based on a separator (like the slash that we use).&lt;/p&gt;

&lt;p&gt;Despite the fact that Rackspace&amp;#8217;s underlying &lt;a href='http://docs.rackspacecloud.com/files/api/cf-devguide-latest.pdf'&gt;REST&lt;/a&gt; and &lt;a href='https://github.com/rackspace/python-cloudfiles/'&gt;Python&lt;/a&gt; APIs both support implying directories from a delimiter like a slash, Rackspace&amp;#8217;s own &lt;a href='https://manage.rackspacecloud.com/CloudFiles.do'&gt;management console&lt;/a&gt; will only display objects using a completely flat namespace. As an example, I have an old side project where I stored US patent data in XML form in both Rackspace Cloud Files and Amazon S3, and broke up each object with a slash-delimited path. (I have obscured my bucket / container names in all of the screen shots in this post). Let&amp;#8217;s view the objects in the Rackspace container using their web interface:&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/03/10/rackspace.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/03/10/rackspace_thumb.png' alt='Rackspace control panel' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thus, you have to wade through multiple pages of flat objects to find one you are looking for, and all of the work in sectioning out the object namespace has basically gone to waste.&lt;/p&gt;

&lt;p&gt;By contrast, Amazon&amp;#8217;s &lt;a href='https://console.aws.amazon.com/s3/home?'&gt;web dashboard&lt;/a&gt; for S3, will imply pseudo-directories, as we can see from the analogous view in S3:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/03/10/aws.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/03/10/aws_thumb.png' alt='AWS web dashboard' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given the hassles with trying to browse our millions of objects in the Rackspace dashboard, I finally decided to bite the bullet and just code up a Django application to do the job (such that I could easily integrate it into our system at work). A few months later, I finally hacked together a first version of the &lt;code&gt;django-cloud-browser&lt;/code&gt; application.&lt;/p&gt;

&lt;h2 id='django_cloud_browser'&gt;Django Cloud Browser&lt;/h2&gt;

&lt;p&gt;At a high level, Cloud Browser application exposes containers and objects in a Cloud datastore to users or administrators. Let&amp;#8217;s look at a basic example:&lt;/p&gt;

&lt;h3 id='basic_deployment'&gt;Basic Deployment&lt;/h3&gt;

&lt;p&gt;On your system, first install the application:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ pip install django-cloud-browser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, edit your Django &amp;#8220;settings.py&amp;#8221;. Here, I&amp;#8217;m setting up a Rackspace Cloud Files account for browsing:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;cloud_browser&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Rackspace&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_RACKSPACE_ACCOUNT&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;lt;my_account&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_RACKSPACE_SECRET_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;lt;my_secret_key&amp;gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, add the URLs to your &amp;#8220;urls.py&amp;#8221;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;urlpatterns&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;patterns&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
    &lt;span class='n'&gt;url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;r&amp;#39;^cb/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;cloud_browser.urls&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)),&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And start up your Django project! You should be able to click through container and object navigation links, as well as page results when they are over the maximum number of objects per page (configurable via &lt;code&gt;CLOUD_BROWSER_DEFAULT_LIST_LIMIT&lt;/code&gt;, but defaults to 20 objects). Here is my navigation into the same Rackspace object as before, but now the objects have implied pseudo-directories.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/03/10/cb_basic.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/03/10/cb_basic_thumb.png' alt='Basic Cloud Browser' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Objects that lead to ultimate files will be downloaded / displayed when clicked. Breadcrumb links are displayed along the top of the page as well.&lt;/p&gt;

&lt;p&gt;The application even figures out and sets appropriate headers for content type and content encoding, which means, e.g., that compressed files will be uncompressed on the fly by browsers that support the compression scheme. At this point, it is worth noting that all access through Cloud Browser is read-only, although in the future we will look to enhancements like uploads, metadata retrieval / modification, etc.&lt;/p&gt;

&lt;h3 id='configuration_options'&gt;Configuration Options&lt;/h3&gt;

&lt;h4 id='other_datastores'&gt;Other Datastores&lt;/h4&gt;

&lt;p&gt;In addition to Rackspace Cloud Files, the application supports Amazon S3 as well as the local file system (with a mock container / objects scheme imposed on real directories and files). Examples:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='c'&gt;# AWS S3&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;AWS&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_AWS_ACCOUNT&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;lt;my_account&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_AWS_SECRET_KEY&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;lt;my_secret_key&amp;gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='c'&gt;# Local filesystem&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Filesystem&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_FILESYSTEM_ROOT&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/usr/share/doc&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id='white__black_lists'&gt;White / Black Lists&lt;/h4&gt;

&lt;p&gt;By default, all containers and all objects are browsable. However, the application supports basic white and black lists at the container level. If a whitelist is specified, only those containers will be browsable.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;CLOUD_BROWSER_CONTAINER_WHITELIST&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;my_container&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;more_stuff&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If a blacklist is specified, only those containers are excluded from browsing.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;CLOUD_BROWSER_CONTAINER_BLACKLIST&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;secret_stuff&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;archive&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If both are specified, access is effectively only what is allowed by the whitelist.&lt;/p&gt;

&lt;h4 id='view_decorators'&gt;View Decorators&lt;/h4&gt;

&lt;p&gt;The settings variable &lt;code&gt;CLOUD_BROWSER_VIEW_DECORATOR&lt;/code&gt; can be set to a function (e.g., a decorator) that wraps all browsing views. Setting this to Django&amp;#8217;s &lt;code&gt;login_required&lt;/code&gt; decorator permits only logged-in users to use the Cloud Browser.&lt;/p&gt;

&lt;h4 id='static_media'&gt;Static Media&lt;/h4&gt;

&lt;p&gt;Cloud Browser contains a minimal amount of CSS and JavaScript. If you set up Django to statically serve these files, then set the &lt;code&gt;CLOUD_BROWSER_STATIC_MEDIA_DIR&lt;/code&gt; variable to the relative path from &lt;code&gt;settings.MEDIA_ROOT&lt;/code&gt;. The application has a fallback where if this is not set, the CSS / JavaScript will be included (inefficiently) in the HTML of each application view. See the documentation for further details.&lt;/p&gt;

&lt;h3 id='admin_deployment'&gt;Admin Deployment&lt;/h3&gt;

&lt;p&gt;Cloud Browser also allows for integration with the Django admin. Here, we just make a simple change to our &amp;#8220;urls.py&amp;#8221;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;urlpatterns&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;patterns&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
    &lt;span class='c'&gt;# Admin URLs. Note: Include ``urls_admin`` **before** admin.&lt;/span&gt;
    &lt;span class='n'&gt;url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;r&amp;#39;^admin/cb/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;cloud_browser.urls_admin&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)),&lt;/span&gt;
    &lt;span class='n'&gt;url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;r&amp;#39;^admin/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;admin&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;site&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;urls&lt;/span&gt;&lt;span class='p'&gt;)),&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is also a really good idea to manually set the &amp;#8220;staff required&amp;#8221; decorator in &amp;#8220;settings.py&amp;#8221; to mirror the other admin view restrictions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.contrib.admin.views.decorators&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;staff_member_required&lt;/span&gt;
&lt;span class='n'&gt;CLOUD_BROWSER_VIEW_DECORATOR&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;staff_member_required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here&amp;#8217;s the same view as above in the admin:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/03/10/cb_admin_open.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/03/10/cb_admin_open_thumb.png' alt='Cloud Browser admin (open)' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The administrative views also have a closable container element (click the +/- box in the upper right hand corner to toggle):&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/03/10/cb_admin_closed.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/03/10/cb_admin_closed_thumb.png' alt='Cloud Browser admin (closed)' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This work is at a very early stage and will likely change a lot, as there are many unresolved issues: links don&amp;#8217;t appear on the dashboard because there are no models, there are no configurable admin permissions to the Cloud Browser views (either all or none), etc. But, it&amp;#8217;s close enough for now.&lt;/p&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Cloud Browser provides a very basic cloud file viewer for your Django projects, but it gets the job done, particularly for Rackspace Cloud Files. Any and all feedback and bugs are strongly encouraged &amp;#8211; just open a ticket on the &lt;a href='https://github.com/ryan-roemer/django-cloud-browser/issues'&gt;GitHub issues page&lt;/a&gt;. Happy browsing.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/02/browserless-ajax-testing-pt2</id>
      <title>Browserless AJAX Testing with Rhino and Envjs, Part 2.</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-02-16T15:17:00Z</published>
      <updated>2011-02-16T15:17:00Z</updated>
      <link href="http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and_16.html"/>
      <content type="html">&lt;h2 id='introduction'&gt;Introduction&lt;/h2&gt;

&lt;p&gt;This is the second of two posts looking at browserless AJAX / JavaScript testing with &lt;a href='http://www.mozilla.org/rhino/'&gt;Rhino&lt;/a&gt; and &lt;a href='http://www.envjs.com/'&gt;Envjs&lt;/a&gt;. &lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html'&gt;Last time&lt;/a&gt;, we set up Rhino and Envjs to run a basic set of &lt;a href='http://docs.jquery.com/Qunit'&gt;QUnit&lt;/a&gt; tests from the command line, exercising pure JavaScript code with no AJAX or DOM manipulation. Today, we will add tests for code that hacks on the DOM and makes AJAX calls.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s start with the files we had last time (with links to previous blog post):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;env.rhino.1.2.js&lt;/code&gt;&lt;/strong&gt;: Envjs for Rhino.&lt;/li&gt;

&lt;li&gt;&lt;a href='https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.css'&gt;&lt;strong&gt;&lt;code&gt;qunit.css&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit styles. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;

&lt;li&gt;&lt;a href='https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.js'&gt;&lt;strong&gt;&lt;code&gt;qunit.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit code. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;

&lt;li&gt;&lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html#setup.js'&gt;&lt;strong&gt;&lt;code&gt;setup.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Our custom hook Envjs to QUnit.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html#run-tests.js'&gt;&lt;strong&gt;&lt;code&gt;run-tests.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Include our setup script and actually invoke the tests at on our test HTML page.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html#my-lib.js'&gt;&lt;strong&gt;&lt;code&gt;my-lib.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: An example JavaScript library (that we want to test).&lt;/li&gt;

&lt;li&gt;&lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html#my-tests.js'&gt;&lt;strong&gt;&lt;code&gt;my-tests.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: An example custom QUnit tests for my-lib.js.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html#test.html'&gt;&lt;strong&gt;&lt;code&gt;test.html&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Basic QUnit HTML page (see QUnit documentation) with script links to &amp;#8220;my-lib.js&amp;#8221; and &amp;#8220;my-tests.js&amp;#8221;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='new_files_and_tests'&gt;New Files and Tests&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s add some new libraries and tests to our working set, to include DOM actions and AJAX:&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;jquery.js&lt;/code&gt;&lt;/strong&gt;: The venerable &lt;a href='http://jquery.com/'&gt;jQuery library&lt;/a&gt;, which we&amp;#8217;ll use for DOM and AJAX calls.&lt;/li&gt;

&lt;li&gt;&lt;a href='/2011/02/browserless-ajax-testing-with-rhino-and_16.html#my-tests2.js'&gt;&lt;strong&gt;&lt;code&gt;my-tests2.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Our new tests, described in full detail below.&lt;/li&gt;

&lt;li&gt;my-lib2.js: We&amp;#8217;ll add two functions &amp;#8211; one which does simply DOM manipulation with events, the other which does an AJAX call and inserts the text from another URL into a div element.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='c1'&gt;// my-lib2.js&lt;/span&gt;
&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * Add text and clickable elements to div.&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='nx'&gt;addTextAndClicker&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
    &lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
    &lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;rgb(0, 0, 0)&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
    &lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;pointer&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;click&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
      &lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;rgb(255, 255, 255)&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
      &lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt; * Dump text from URL into &amp;#39;code&amp;#39; div.&lt;/span&gt;
&lt;span class='cm'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='nx'&gt;dumpText&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;url&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;ajax&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;
    &lt;span class='nx'&gt;type&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nx'&gt;url&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='nx'&gt;url&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nx'&gt;dataType&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nx'&gt;success&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;data&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='c1'&gt;// Update the div.&lt;/span&gt;
      &lt;span class='c1'&gt;// Note: The &amp;quot;&amp;lt;pre/&amp;gt;&amp;quot; tag has some issues with Envjs.&lt;/span&gt;
      &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&amp;lt;code /&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;appendTo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;$div&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;data&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

      &lt;span class='c1'&gt;// User callback.&lt;/span&gt;
      &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='nx'&gt;callback&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
      &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
  &lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Interestingly, I originally had the &lt;code&gt;dumpText()&lt;/code&gt; function create a new &amp;#8221;&amp;#60;pre/&amp;#62;&amp;#8221; tag, but something between Rhino and Envjs did not like it, and Rhino would error out on running this code. So, I switched to &amp;#8221;&amp;#60;code/&amp;#62;&amp;#8221; and everything worked fine. I guess the point is that nothing absolutely substitutes for a real browser, but Envjs does a really great job notwithstanding!&lt;/p&gt;

&lt;h2 id='serving_our_files_for_ajax'&gt;Serving Our Files for AJAX&lt;/h2&gt;

&lt;p&gt;One important thing to note is that because we are doing AJAX calls now, we quickly will run into the &lt;a href='http://en.wikipedia.org/wiki/Same_origin_policy'&gt;same origin policy&lt;/a&gt;. To easily get around this for purposes of this demonstration, open a new terminal window, point it to the test directory we&amp;#8217;re working in and serve all files in the directory directly from localhost with the python built-in web server (here I chose port 8001):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ cd /path/to/test/code
$ python -m SimpleHTTPServer 8001 .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check things out by navigating a real browser to: http://127.0.0.1:8001/test.html. Our QUnit tests from last time should run in the window.&lt;/p&gt;

&lt;h2 id='testing_our_new_code'&gt;Testing our New Code&lt;/h2&gt;

&lt;p&gt;Now that we have created our new library and are running a slim web server (so we can actually make AJAX requests), let&amp;#8217;s write some test code in &amp;#8220;my- tests2.js&amp;#8221;. At the module level, we make use of the QUnit setup and teardown options to attach test div elements to the QUnit fixture element, so we can actually do some (temporary) DOM manipulation.&lt;/p&gt;

&lt;p&gt;We use an asynchronous test pattern for the &lt;code&gt;dumpText&lt;/code&gt; AJAX tests to handle latency between the call and response. The call to &lt;code&gt;stop()&lt;/code&gt; stops QUnit execution to allow the code and tests to run. If the asynchronous callback doesn&amp;#8217;t finish (and invoke &lt;code&gt;start()&lt;/code&gt;) within the designated time period (here 1 second), then QUnit will raise an error. Assuming we have given ourselves enough time, once &lt;code&gt;start()&lt;/code&gt; is invoked, then QUnit starts up again for the remaining tests and a final summary. For a good overview of QUnit testing with asynchronous examples, see &amp;#8221;&lt;a href='http://net.tutsplus.com/tutorials/javascript-ajax/how-to-test-your-javascript-code-with-qunit/'&gt;How to Test your JavaScript Code with QUnit&lt;/a&gt;&amp;#8221;.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s everything together for &amp;#8220;my-tests2.js&amp;#8221;: &lt;a name='my-tests2.js'&gt; &lt;/a&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;my-lib2&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt;   * Add new elements before each test.&lt;/span&gt;
&lt;span class='cm'&gt;   */&lt;/span&gt;
  &lt;span class='nx'&gt;setup&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;self&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

    &lt;span class='c1'&gt;// Store test page fixture.&lt;/span&gt;
    &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$fixture&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;#qunit-fixture&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='c1'&gt;// Create new test div&amp;#39;s.&lt;/span&gt;
    &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;lt;div/&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
      &lt;span class='nx'&gt;attr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;add-text&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
      &lt;span class='nx'&gt;appendTo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$fixture&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$dumpText&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;lt;div/&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
      &lt;span class='nx'&gt;attr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;dump-text&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
      &lt;span class='nx'&gt;appendTo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$fixture&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='p'&gt;},&lt;/span&gt;

  &lt;span class='cm'&gt;/**&lt;/span&gt;
&lt;span class='cm'&gt;   * Remove tests elements after.&lt;/span&gt;
&lt;span class='cm'&gt;   */&lt;/span&gt;
  &lt;span class='nx'&gt;teardown&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;self&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

    &lt;span class='c1'&gt;// Remove test div&amp;#39;s&lt;/span&gt;
    &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;remove&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$dumpText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;remove&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;

&lt;span class='nx'&gt;test&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;addTextAndClicker&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;self&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Div starts empty&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Add the text and clicker.&lt;/span&gt;
  &lt;span class='nx'&gt;addTextAndClicker&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Hi There!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Hi There!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Div has text&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
         &lt;span class='s2'&gt;&amp;quot;rgb(0, 0, 0)&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Starting color&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
         &lt;span class='s2'&gt;&amp;quot;pointer&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Changed cursor&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Click the clicker and check.&lt;/span&gt;
  &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;click&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Hi There!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Div still has text&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
         &lt;span class='s2'&gt;&amp;quot;rgb(255, 255, 255)&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Changed color&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$addText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;css&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;cursor&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Changed cursor&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;


&lt;span class='nx'&gt;test&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;dumpText&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;self&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$dumpText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Div starts empty&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

  &lt;span class='c1'&gt;// Stop QUnit execution to wait for AJAX call and tests.&lt;/span&gt;
  &lt;span class='nx'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1000&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;  &lt;span class='c1'&gt;// Wait up to 1 second.&lt;/span&gt;

  &lt;span class='c1'&gt;// Perform AJAX call.&lt;/span&gt;
  &lt;span class='c1'&gt;// Just dump our setup script as straight text.&lt;/span&gt;
  &lt;span class='nx'&gt;dumpText&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$dumpText&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://127.0.0.1:8001/setup.js&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c1'&gt;// Now, run tests.&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;$code&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;self&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;$dumpText&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;$code&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;length&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Verify code element.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='c1'&gt;// Find some known text strings.&lt;/span&gt;
    &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;find&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='nx'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;$code&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;html&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;indexOf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;text&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;!=&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='s2'&gt;&amp;quot;Find string \&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nx'&gt;text&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='p'&gt;};&lt;/span&gt;
    &lt;span class='nx'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;beforeScriptLoad&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;afterScriptLoad&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;QUnit test runner loaded&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;QUnit.log&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='c1'&gt;// Restart QUnit execution.&lt;/span&gt;
    &lt;span class='nx'&gt;start&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;});&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, lets update our &amp;#8220;test.html&amp;#8221; file to run both our previous and new unit tests (and add the jQuery dependency):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='html'&gt;&lt;span class='nt'&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;title&amp;gt;&lt;/span&gt;QUnit&lt;span class='nt'&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;link&lt;/span&gt; &lt;span class='na'&gt;rel=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;href=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit.css&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/css&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;jquery.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-lib.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-tests.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-lib2.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-tests2.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h1&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-header&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;QUnit Test Suite&lt;span class='nt'&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h2&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-banner&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;div&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-testrunner-toolbar&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h2&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-userAgent&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;ol&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-tests&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;div&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-fixture&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, we need to tweak &amp;#8220;run-tests.js&amp;#8221; to point to the locally served test page instead of simply the local filesystem.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;setup.js&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Starting QUnit tests...&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nb'&gt;window&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;location&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://127.0.0.1:8001/test.html&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And, let&amp;#8217;s run the tests!&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ java -cp ~/rhino1_7R2/js.jar org.mozilla.javascript.tools.shell.Main -opt -1 run-tests.js
[  Envjs/1.6 (Rhino; U; Mac OS X x86_64 10.6.6; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13  ]
Starting QUnit tests...
* QUnit test runner loaded.
  * { addTwo }( 0 )[ PASS ]  Add nothing., expected: 0
  * { addTwo }( 1 )[ PASS ]  Add numbers., expected: 3
  * { addTwo }( 2 )[ PASS ]  Add negatives., expected: -3
  * { addTextAndClicker }( 3 )[ PASS ]  Div starts empty, expected: &amp;quot;&amp;quot;
  * { addTextAndClicker }( 4 )[ PASS ]  Div has text, expected: &amp;quot;Hi There!&amp;quot;
  * { addTextAndClicker }( 5 )[ PASS ]  Starting color, expected: &amp;quot;rgb(0, 0, 0)&amp;quot;
  * { addTextAndClicker }( 6 )[ PASS ]  Changed cursor, expected: &amp;quot;pointer&amp;quot;
  * { addTextAndClicker }( 7 )[ PASS ]  Div still has text, expected: &amp;quot;Hi There!&amp;quot;
  * { addTextAndClicker }( 8 )[ PASS ]  Changed color, expected: &amp;quot;rgb(255, 255, 255)&amp;quot;
  * { addTextAndClicker }( 9 )[ PASS ]  Changed cursor, expected: &amp;quot;default&amp;quot;
  * { dumpText }( 10 )[ PASS ]  Div starts empty, expected: &amp;quot;&amp;quot;
  * { dumpText }( 11 )[ PASS ]  Verify code element., expected: 1
  * { dumpText }( 12 )[ PASS ]  Find string &amp;quot;beforeScriptLoad&amp;quot;
  * { dumpText }( 13 )[ PASS ]  Find string &amp;quot;afterScriptLoad&amp;quot;
  * { dumpText }( 14 )[ PASS ]  Find string &amp;quot;QUnit test runner loaded&amp;quot;
  * { dumpText }( 15 )[ PASS ]  Find string &amp;quot;QUnit.log&amp;quot;

*****************
* QUnit Results *
*****************
* PASSED:  16
* FAILED:  0
* Completed  16  tests total in  2.981  seconds.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From the results, we can see that we now have all of our unit tests running from pure JavaScript code to our new DOM manipulation and AJAX calls.&lt;/p&gt;

&lt;h2 id='other_enhancements'&gt;Other Enhancements&lt;/h2&gt;

&lt;p&gt;Beyond the setup we discussed today, there are several really cool additions and improvements you can add to your browserless testing solution.&lt;/p&gt;

&lt;h3 id='use_mocks_for_ajax_calls'&gt;Use Mocks for AJAX Calls&lt;/h3&gt;

&lt;p&gt;Although running a simple python web server gets us through this demonstration pretty easily for our AJAX calls, in practice AJAX calls might rely on more than a simple static server can provide. At that point, you either have to deal with running your development web application server at the same time, or switch to mocking out the AJAX calls. A great solution is to use fantastic &lt;a href='http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development/'&gt;MockJax&lt;/a&gt; jQuery plugin, which overrides $.ajax() calls to substitute with a configurable mock response.&lt;/p&gt;

&lt;h3 id='add_code_coverage_reports'&gt;Add Code Coverage Reports&lt;/h3&gt;

&lt;p&gt;Amazingly, the &lt;a href='http://siliconforks.com/jscoverage/'&gt;JSCoverage&lt;/a&gt; code coverage library does work with this Rhino and Envjs setup. I hooked things up by running jscoverage-server in a similar fashion to how we did the command line python server above (which completely replaces the need for it), and then added the following to &amp;#8220;setup.js&amp;#8221; in the QUnit.done option:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;QUnit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;done&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;fail&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;total&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
  &lt;span class='c1'&gt;// ... previous code ...&lt;/span&gt;

  &lt;span class='c1'&gt;// Code coverage report.&lt;/span&gt;
  &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;window&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;jscoverage_report&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Writing coverage report.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;jscoverage_report&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
  &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is important that the coverage report come at the very &lt;strong&gt;end&lt;/strong&gt; of the QUnit.done handler to ensure that all of our tests truly are finished running. See the JSCoverage &lt;a href='http://siliconforks.com/jscoverage/manual.html'&gt;manual&lt;/a&gt; for more details about running the coverage server and writing reports.&lt;/p&gt;

&lt;h2 id='conclusion'&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Revisiting the original motivation for this post series, the original pain point was that frontend tests are complicated to write and a hassle to run. With the fully browserless testing environment we have explored here, it is possible to create a system out of Rhino, Envjs and QUnit that runs all tests with a single command line argument, making the former hassle into something so easy you could add it to your pre-/post- commit hooks in your version control software. And, by being able to write our tests in mostly straight QUnit (with a little extra setup help), test writing for developers remains relatively straightforward, even dealing with AJAX, etc.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2011/02/browserless-ajax-testing-pt1</id>
      <title>Browserless AJAX Testing with Rhino and Envjs, Part 1.</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2011-02-08T06:15:00Z</published>
      <updated>2011-02-08T06:15:00Z</updated>
      <link href="http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html"/>
      <content type="html">&lt;h2 id='updates__5272011'&gt;Updates - 5/27/2011&lt;/h2&gt;

&lt;p&gt;Since this original post, there has been a lot of good feedback surrounding getting an Envjs + QUnit + Rhino setup going, so please read the comments at the bottom of this post. Here are the summarized points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QUnit&lt;/strong&gt;: Newer versions of QUnit have issues with Envjs, adding a &lt;code&gt;&amp;lt;pre/&amp;gt;&lt;/code&gt; to output that Envjs can&amp;#8217;t handle. A comment from &amp;#8220;Ryan&amp;#8221; (not me &amp;#8211; I&amp;#8217;m &amp;#8220;Ryan Roemer&amp;#8221; below) also points out the QUnit has changed the function signature of the logging callbacks and links to an improved &amp;#8221;&lt;a href='https://github.com/zepheira/exhibit3/blob/master/scripted/lib/setup.js'&gt;setup.js&lt;/a&gt;&amp;#8221; file. I have inserted links to a known &lt;a href='https://github.com/jquery/qunit/commit/a46610796b457fab05587945e743d4e857f580b5'&gt;working version&lt;/a&gt; of QUnit.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;jQuery 1.5&lt;/strong&gt;: jQuery 1.5+ also has issues with Envjs due to calls to a couple of methods not currently available in either Envjs 1.2 or 1.3. There is a &lt;a href='https://github.com/orslumen/env-js/commit/c3e702cfa84872782dd40a2c4cd8a4c8f9bac3a3'&gt;patch&lt;/a&gt; on GitHub for the issue for Envjs 1.3. I have backported the patch (and some other fixes) to a &lt;a href='https://github.com/ryan-roemer/envjs-1.2'&gt;forked version&lt;/a&gt; of Envjs 1.2 on my GitHub account.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Envjs 1.3&lt;/strong&gt;: Envjs 1.2 was a single file, with primarily Rhino support. &lt;a href='https://github.com/thatcher/env-js'&gt;Envjs 1.3&lt;/a&gt; is massive enhancement project that supports a large number of JavaScript engines including: Node.js, SpiderMonkey, etc. It&amp;#8217;s still in early development, but definitely worth taking a look at.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='introduction'&gt;Introduction&lt;/h2&gt;

&lt;p&gt;This is the first of two blog posts focusing on browserless testing of AJAX / JavaScript using Rhino and Envjs. Today, we discuss getting browserless &lt;a href='http://docs.jquery.com/Qunit'&gt;QUnit&lt;/a&gt; tests up and running from the command line. A &lt;a href='http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and_16.html'&gt;follow-on&lt;/a&gt; post will discuss running QUnit tests which involve DOM manipulation and AJAX interaction.&lt;/p&gt;

&lt;p&gt;Testing web applications can be an enormous pain. Tests take time to write correctly and usefully, the interaction between the front and back ends is complicated, and it is often a hassle just to get people to run the tests on a regular basis. At work as of late, we have been trying to kick start our JavaScript unit testing &amp;#8211; our system has a Django/Python backend, and a JavaScript (mostly jQuery-based) frontend.&lt;/p&gt;

&lt;p&gt;On the backend, we are in good shape, test-wise &amp;#8211; the Django &lt;a href='http://docs.djangoproject.com/en/dev/topics/testing/'&gt;test case extensions&lt;/a&gt; to the Python unit test framework make backend unit testing easy and straightforward. Everything runs fairly quickly, and from the command line. In fact, we have actually made a (soft) requirement that all developers run a &lt;a href='http://fabfile.org/'&gt;Fabric&lt;/a&gt; target called &amp;#8220;precommit&amp;#8221; that runs all of our Django unit tests (in addition to Python and JavaScript style checkers).&lt;/p&gt;

&lt;p&gt;On the frontend, it&amp;#8217;s a different story. Our previous test suite relied entirely on a browser and a running development server loaded with data in a known state. Our unit tests were written against QUnit, and for the most part worked fine for the code they did exercise. But, even where our frontend tests would catch newly introduced bugs, all too often the tests just weren&amp;#8217;t run, and we would actually discover the bug several commit cycles later.&lt;/p&gt;

&lt;p&gt;While we clearly need to integrate a fully automated infrastructure around browser-based tests (using great tools like &lt;a href='http://seleniumhq.org/'&gt;Selenium&lt;/a&gt; or &lt;a href='http://www.getwindmill.com/'&gt;Windmill&lt;/a&gt;), my thoughts turned to the idea that the heavyweight nature of full end-to-end AJAX testing was getting in the way of us writing and running frontend tests. More specifically, the difficulties come down to two fundamental problems &amp;#8211; our tests on the frontend are: (1) not easy to write and run, and (2) are not integrated into an automated, &amp;#8220;one-touch&amp;#8221; running scheme.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='javascript_engines_and_the_pesky_browser_environment'&gt;JavaScript Engines and the Pesky Browser Environment&lt;/h2&gt;

&lt;p&gt;Last week I decided to finally bite the bullet and push hard for the easiest and simplest JavaScript testing solution &amp;#8211; execution of a full suite of QUnit tests that would execute without a browser from a single command line command.&lt;/p&gt;

&lt;p&gt;The first stop is getting a JavaScript engine to actually run JavaScript (test and code) in lieu of a browser. Fortunately, there are lots of great open source options including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://www.mozilla.org/rhino/'&gt;Rhino&lt;/a&gt;: Mozilla&amp;#8217;s Java JavaScript implementation.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.mozilla.org/js/spidermonkey/'&gt;SpiderMonkey&lt;/a&gt;: Mozilla&amp;#8217;s C implementation of JavaScript.&lt;/li&gt;

&lt;li&gt;&lt;a href='http://code.google.com/p/v8/'&gt;V8&lt;/a&gt;: Google&amp;#8217;s JavaScript virtual machine for Chrome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A JavaScript engine only gets you pure JavaScript language support. The hard part is that any nontrivial web application with a good amount of JavaScript has a large percentage of code that manipulates the DOM and essentially needs a browser environment for a lot of functionality. So much so, that as general assertion, it is probably not worth trying to separate out pure and browser- dependent JavaScript code in development solely for this purpose.&lt;/p&gt;

&lt;p&gt;Thus, the second step necessary for running QUnit tests is a simulation of the browser environment (e.g., window, console, etc.), just without the browser. This is a harder problem.&lt;/p&gt;

&lt;p&gt;A few years ago, while working on some prototype code for a friend&amp;#8217;s startup, I started researching browserless JavaScript testing, specifically looking for browser emulation options. At the time, the only serious emulation solution I found was in a &lt;a href='http://ejohn.org/blog/bringing-the-browser-to-the-server/'&gt;blog post&lt;/a&gt; by &lt;a href='http://ejohn.org/about/'&gt;John Resig&lt;/a&gt; (of &lt;a href='http://jquery.org/'&gt;jQuery&lt;/a&gt; fame), where he had successfully hacked together a pure JavaScript browser environment (called &amp;#8220;env.js&amp;#8221;) that was capable of AJAX, DOM manipulation, and even running the jQuery set of QUnit tests.&lt;/p&gt;

&lt;p&gt;Unfortunately, my buddy&amp;#8217;s side project eventually went on hiatus, and I never got around to rigging up a completely browserless test project. Working on purely backend server code for the past couple of years (C++, C#, python), it has taken me some time to get both the opportunity and desire to research a browserless JavaScript environment.&lt;/p&gt;

&lt;p&gt;So years later, looking back into the issue, I was pleased to revisit Resig&amp;#8217;s proof-of-concept browser simulation environment and find that it had evolved into a full open source project &amp;#8211; &lt;a href='http://www.envjs.com/'&gt;Envjs&lt;/a&gt; &amp;#8211; which provides an amazingly powerful browser-like environment, capable of DOM manipulation, AJAX interaction, cookie setting, etc.&lt;/p&gt;

&lt;h2 id='making_rhino_envjs_qunit_and_javascript_tests_play_nicely_together'&gt;Making Rhino, Envjs, QUnit, and JavaScript Tests Play Nicely Together&lt;/h2&gt;

&lt;p&gt;Rediscovering Envjs and finding it in good shape meant that I simply had to plug in a JavaScript engine, make sure it works with QUnit, and then fire up my test suites without a browser. I ended up choosing Rhino as my JavaScript engine as Envjs has specific support for it, but presumably other engines can be made to work with Envjs and get equivalent results as I will describe below.&lt;/p&gt;

&lt;p&gt;So, let&amp;#8217;s get started.&lt;/p&gt;

&lt;h3 id='rhino_and_envjs_installation'&gt;Rhino and Envjs Installation&lt;/h3&gt;

&lt;p&gt;In terms of extra tools / libraries, all we need is Rhino and Envjs:&lt;/p&gt;

&lt;p&gt;Rhino is available from the Mozilla &lt;a href='ftp://ftp.mozilla.org/pub/mozilla.org/js'&gt;FTP site&lt;/a&gt;. I ended up going with &lt;a href='ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_7R2.zip'&gt;rhino1_7R2&lt;/a&gt; &amp;#8211; the Envjs web site notes that it is best to go with a recent Rhino release. The package can be installed anywhere, but we are going to need to know the path to the file &amp;#8220;js.jar&amp;#8221; in the package to pass as a class path argument to Java.&lt;/p&gt;

&lt;p&gt;After installing, let&amp;#8217;s check that Rhino actually works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd /path/to/rhino
$ java -cp js.jar org.mozilla.javascript.tools.shell.Main -opt -1
Rhino 1.7 release 2 2009 03 22
js&amp;gt; print(&amp;quot;hello world&amp;quot;);
hello world
js&amp;gt; quit();&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, install Envjs. I went with &lt;a href='http://www.envjs.com/release/envjs-1.2'&gt;Envjs 1.2&lt;/a&gt;. (I did end up having to patch the library to get cookies working properly, but that&amp;#8217;s a post for another day &amp;#8211; and presumably fixed in trunk). Envjs can be installed anywhere the rest of your code can access it &amp;#8211; I went with the same JavaScript directory as all our other third party library code.&lt;/p&gt;

&lt;h3 id='example_libraries_and_test_code'&gt;Example Libraries and Test Code&lt;/h3&gt;

&lt;p&gt;Ignoring how I actually installed and placed files, for the rest of this discussion, let&amp;#8217;s assume we have a simplified world view in a single working directory, where we&amp;#8217;ll place all our third party, test and library code. None of these files really need (or should) be in the same directory, but it is easier to describe things without multiple directory traversal.&lt;/p&gt;

&lt;p&gt;Here is the third party code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://www.envjs.com/dist/env.rhino.1.2.js'&gt;&lt;strong&gt;&lt;code&gt;env.rhino.1.2.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: The Envjs file we downloaded. If you are using jQuery 1.5+, you may want this &lt;a href='https://github.com/ryan-roemer/envjs-1.2'&gt;patched Envjs&lt;/a&gt; instead.&lt;/li&gt;

&lt;li&gt;&lt;a href='https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.css'&gt;&lt;strong&gt;&lt;code&gt;qunit.css&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit styles. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;

&lt;li&gt;&lt;a href='https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.js'&gt;&lt;strong&gt;&lt;code&gt;qunit.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit code. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are going to create two more files in a subsequent section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setup.js&lt;/code&gt;&lt;/strong&gt;: Hook Envjs to QUnit.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;&lt;code&gt;run-tests.js&lt;/code&gt;&lt;/strong&gt;: Include our setup script and actually invoke the tests at on our test HTML page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, here is some library and test code files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;my-lib.js&lt;/code&gt;&lt;/strong&gt;: An example JavaScript library (that we want to test). &lt;a name='my-lib.js'&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='nx'&gt;addTwo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;x&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;y&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nx'&gt;x&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nx'&gt;y&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;my-tests.js&lt;/code&gt;&lt;/strong&gt;: An example custom QUnit tests for my-lib.js. &lt;a name='my-tests.js'&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;My Module&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='nx'&gt;test&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;addTwo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;addTwo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Add nothing.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;addTwo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Add numbers.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nx'&gt;equals&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;addTwo&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Add negatives.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;test.html&lt;/code&gt;&lt;/strong&gt;: Basic QUnit HTML page (see QUnit documentation) with script links to &amp;#8220;my-lib.js&amp;#8221; and &amp;#8220;my-tests.js&amp;#8221;. &lt;a name='test.html'&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='html'&gt;&lt;span class='nt'&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;title&amp;gt;&lt;/span&gt;QUnit&lt;span class='nt'&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;link&lt;/span&gt; &lt;span class='na'&gt;rel=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;href=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit.css&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/css&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-lib.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my-tests.js&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h1&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-header&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;QUnit Test Suite&lt;span class='nt'&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h2&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-banner&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;div&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-testrunner-toolbar&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;h2&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-userAgent&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;ol&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-tests&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;div&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;qunit-fixture&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once we&amp;#8217;ve got all of these custom and third party files, we can just open test.html in a browser, and the QUnit tests should all run, pass and look something like:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://loose-bits.com/media/img/2011/02/08/qunit.png'&gt;&lt;img src='http://loose-bits.com/media/img/2011/02/08/qunit_thumb.png' alt='QUnit results' /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='configuring_envjs_and_qunit'&gt;Configuring Envjs and QUnit&lt;/h3&gt;

&lt;p&gt;We need to hook QUnit into Envjs, so we can display output to stdout (using &lt;code&gt;console.log()&lt;/code&gt;). Fortunately, QUnit explicitly provides a lot of hooks to do everything we want, which I&amp;#8217;ve implemented in a file named &amp;#8220;setup.js&amp;#8221;, placed alongside our other JavaScript libraries.&lt;/p&gt;

&lt;p&gt;I mostly followed the excellent &lt;a href='http://www.envjs.com/doc/guides'&gt;guide&lt;/a&gt; from the Envjs folks, and here&amp;#8217;s the gist of my configuration for &amp;#8220;setup.js&amp;#8221;: &lt;a name='setup.js'&gt; &lt;/a&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;env.rhino.1.2.js&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nx'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;qunit.js&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;starttime&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nb'&gt;Date&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;getTime&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;

&lt;span class='c1'&gt;// Envjs/QUnit Bridge.&lt;/span&gt;
&lt;span class='nx'&gt;Envjs&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;
    &lt;span class='c1'&gt;// Straight from the Envjs guide.&lt;/span&gt;
    &lt;span class='nx'&gt;scriptTypes&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='s2'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;
    &lt;span class='p'&gt;},&lt;/span&gt;
    &lt;span class='c1'&gt;// Straight from the Envjs guide.&lt;/span&gt;
    &lt;span class='nx'&gt;beforeScriptLoad&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='s1'&gt;&amp;#39;sharethis&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;script&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='nx'&gt;script&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;src&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
            &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='kc'&gt;false&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;},&lt;/span&gt;

    &lt;span class='c1'&gt;// Hook QUnit logging to console.&lt;/span&gt;
    &lt;span class='nx'&gt;afterScriptLoad&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='s1'&gt;&amp;#39;qunit&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;count&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;testName&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

            &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;* QUnit test runner loaded.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

            &lt;span class='c1'&gt;// Grab current test name.&lt;/span&gt;
            &lt;span class='nx'&gt;QUnit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;testStart&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;testEnvironment&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='nx'&gt;testName&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;name&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
            &lt;span class='p'&gt;};&lt;/span&gt;
            &lt;span class='c1'&gt;// Override log to display to stdout.&lt;/span&gt;
            &lt;span class='nx'&gt;QUnit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;result&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;message&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='c1'&gt;// Strip out HTML in results messages.&lt;/span&gt;
                &lt;span class='nx'&gt;message&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;message&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;replace&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/&amp;lt;\/?.*?&amp;gt;/g&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;  * {%s}(%s)[%s] %s&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='nx'&gt;testName&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;count&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='nx'&gt;result&lt;/span&gt; &lt;span class='o'&gt;?&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;PASS&amp;#39;&lt;/span&gt; &lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;FAIL&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;message&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='p'&gt;};&lt;/span&gt;
            &lt;span class='nx'&gt;QUnit&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;done&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;fail&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;total&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
                &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;endtime&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='nb'&gt;Date&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;&lt;span class='nx'&gt;getTime&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
                &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;pass&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nx'&gt;total&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt; &lt;span class='nx'&gt;fail&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
                &lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;*****************\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;* QUnit Results *\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;*****************\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;* PASSED: %s\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;* FAILED: %s\n&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;* Completed %s tests total in %s seconds.\n&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='nx'&gt;pass&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;fail&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nx'&gt;total&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='nb'&gt;parseFloat&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;endtime&lt;/span&gt;&lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='nx'&gt;starttime&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='mf'&gt;1000.0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
           &lt;span class='p'&gt;};&lt;/span&gt;
        &lt;span class='p'&gt;},&lt;/span&gt;

        &lt;span class='c1'&gt;// Straight from the Envjs guide.&lt;/span&gt;
        &lt;span class='s1'&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='kd'&gt;function&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nx'&gt;script&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='nx'&gt;script&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;type&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;text/envjs&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that the Envjs guide actually went further than this example, as the guide&amp;#8217;s setup code writes the QUnit HTML report out to a new file. The interesting thing about running JavaScript in Rhino is that code runs with full system permissions outside of the JavaScript sandbox, so you can also do normal file system manipulation. (And, as the Envjs documentation notes with big scary warnings, there are huge security issues with running outside code within an Envjs / Rhino environment).&lt;/p&gt;

&lt;h3 id='test_runner'&gt;Test Runner&lt;/h3&gt;

&lt;p&gt;The last file we have to compose is our actual test runner. Fortunately, this is quite simple, as shown in &amp;#8220;run-tests.js&amp;#8221;&amp;#8211; just load the setup and point to the test page: &lt;a name='run-tests.js'&gt; &lt;/a&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='nx'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;setup.js&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='nx'&gt;console&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;log&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Starting QUnit tests...&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='nb'&gt;window&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='nx'&gt;location&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;test.html&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id='test_it_out'&gt;Test it Out!&lt;/h3&gt;

&lt;p&gt;Now that we have Rhino, Envjs and QUnit all hooked together, it&amp;#8217;s time to actually run some QUnit tests. Assuming your file / directory setup matches the above contrived example, we should be able to run the tests by invoking Rhino with our test runner script:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ java -cp ~/rhino1_7R2/js.jar org.mozilla.javascript.tools.shell.Main -opt -1 run-tests.js
[  Envjs/1.6 (Rhino; U; Mac OS X x86_64 1 ... ]
Starting QUnit tests...
* QUnit test runner loaded.
  * { addTwo }( 0 )[ PASS ]  Add nothing., expected: 0
  * { addTwo }( 1 )[ PASS ]  Add numbers., expected: 3
  * { addTwo }( 2 )[ PASS ]  Add negatives., expected: -3

*****************
* QUnit Results *
*****************
* PASSED:  3
* FAILED:  0
* Completed  3  tests total in  1.989  seconds.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Awesome. We have QUnit testing goodness for our library, running within Rhino, and log / results output to the console, all through a single bash command. We can add and include more libraries and test suites to test.html, and they automatically get picked up by the test suite.&lt;/p&gt;

&lt;h2 id='next_time__browserless_ajax_playing_with_the_dom_and_more'&gt;Next Time &amp;#8211; Browserless AJAX, Playing with the DOM and More!&lt;/h2&gt;

&lt;p&gt;Today we examined and set up basic browserless JavaScript unit tests with Rhino and Envjs. Despite the promises of the title of this post, we&amp;#8217;ll have to wait until next time to extend out QUnit test suite to include actual / mocked AJAX testing and DOM manipulation with this set up. But, just removing the browser from the frontend test path is a great step towards more lightweight JavaScript unit tests &amp;#8211; and situating us for the type of tests that developers will want to write and run.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2010/12/rackspace-cloud-files-and-pseudo</id>
      <title>Rackspace Cloud Files and Pseudo-Directories</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2010-12-28T19:57:00Z</published>
      <updated>2010-12-28T19:57:00Z</updated>
      <link href="http://loose-bits.com/2010/12/rackspace-cloud-files-and-pseudo.html"/>
      <content type="html">&lt;h2 id='cloud_providers_and_listing_storage_objects'&gt;Cloud Providers and Listing Storage Objects&lt;/h2&gt;

&lt;p&gt;Rackspace&amp;#8217;s &lt;a href='http://www.rackspacecloud.com/cloud_hosting_products/files/'&gt;Cloud Files&lt;/a&gt; provide a basic distributed blob / file abstraction. Like &lt;a href='http://aws.amazon.com/s3/'&gt;Amazon Web Services Simple Storage Service&lt;/a&gt; (AWS S3) and &lt;a href='http://msdn.microsoft.com/en-us/library/dd135733.aspx'&gt;Microsoft Azure Blobs&lt;/a&gt;, Cloud Files are organized into a two level hierarchy consisting of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Containers&lt;/strong&gt;: A collection of file objects. In AWS S3 parlance this is called a &amp;#8220;bucket&amp;#8221;. For all three cloud providers, a container is a single collection unit that cannot be further subdivided.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Storage Objects&lt;/strong&gt;: A single file object. In AWS S3, this is an &amp;#8220;object&amp;#8221;, while in Azure this is a &amp;#8220;blob&amp;#8221;. A storage object for all the providers similarly cannot be further nested / subdivided.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, there are no further levels of hierarchy than container, then object, and for all three providers, the storage object namespace is completely flat.&lt;/p&gt;

&lt;p&gt;As most of the world is used to nested file hierarchies much deeper than this, cloud providers allow wide leeway in naming storage objects, most notably allowing characters like a slash (&amp;#8221;/&amp;#8221;). To provide the illusion of a nested hierarchy, &lt;a href='http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketGET.html'&gt;AWS S3&lt;/a&gt; and &lt;a href='http://msdn.microsoft.com/en-us/library/dd135734.aspx'&gt;Azure Blobs&lt;/a&gt; provide listing operations that take a delimiter character to treat as a nested directory delimiter. In this manner, calls to the list objects API will return results as if there are intermediate directories in the (really) flat storage object namespace.&lt;/p&gt;

&lt;h2 id='the_old_way__rackspace_cloud_files_and_dummy_directory_objects'&gt;The Old Way - Rackspace Cloud Files and Dummy Directory Objects&lt;/h2&gt;

&lt;p&gt;To much frustration, Rackspace Cloud Files did not originally provide a delimiter query API for treating a delimiter as a nested directory organizer. Instead of delimiter queries, Rackspace required that clients upload a storage object of type &amp;#8220;application/directory&amp;#8221; at each level in which a nested pseudo- directory was desired. Only then would the results start to resemble those from AWS / Azure with delimiter queries.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;Here&amp;#8217;s a quick example showing how the dummy directory objects work. First let&amp;#8217;s create a container:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;cloudfiles&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ACCOUNT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;SECRET_KEY&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_container&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my_test_container&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, put in some objects with internal slashes, and list all.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar/file1.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;some text.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar/file2.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;some text.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/diff/file3.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;some text.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;baz/file4.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;some text.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;baz/file4.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file1.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file2.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/diff/file3.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can use the &amp;#8220;prefix&amp;#8221; parameter to limit files to only &amp;#8220;foo&amp;#8221;, but the internal pseudo-directories are not inferred.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;prefix&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/bar/file1.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file2.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/diff/file3.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &amp;#8220;path&amp;#8221; parameter will infer directories, but not without internal dummy directory objects.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, let&amp;#8217;s create the dummy objects for &amp;#8220;foo&amp;#8221;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;content_type&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;application/directory&amp;quot;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sync_metadata&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;content_type&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;application/directory&amp;quot;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sync_metadata&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;create_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/diff&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;content_type&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;application/directory&amp;quot;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sync_metadata&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;dummy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And, now we get the pseudo-directory results we expect for &amp;#8220;foo&amp;#8221; and children paths.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/bar&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/diff&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/bar/file1.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file2.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/diff&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/diff/file3.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While the dummy objects technically make nested pseudo-directories possible, the onus falls to the user to create and maintain all the dummy objects. This really becomes a pain when creating new objects that might contain nested delimiters for which there is not already a delimiter (say adding a nested pseudo-directory). The user then has to either check / scrub all existing files for missing dummy objects, or create duplicate dummy directory objects on each new object creation.&lt;/p&gt;

&lt;h2 id='the_new_way__rackspace_cloud_files_and_delimiter_queries'&gt;The New Way - Rackspace Cloud Files and Delimiter Queries&lt;/h2&gt;

&lt;p&gt;Fortunately, Rackspace &lt;a href='http://www.rackspacecloud.com/blog/2010/12/21/rackspace-cloud-files-a-look-back-at-2010/'&gt;finally enabled&lt;/a&gt; &amp;#8220;real&amp;#8221; delimiter queries in their list storage objects API. I couldn&amp;#8217;t actually find the date that this was added to REST API and added server side (as the current API PDF doesn&amp;#8217;t even note the change, although I expect it soon will).&lt;/p&gt;

&lt;p&gt;I first noticed the new query functionality when perusing commits on the &lt;a href='https://github.com/rackspace/python-cloudfiles'&gt;Python API GitHub page&lt;/a&gt;. On Dec. 3, 2010, a &lt;a href='https://github.com/rackspace/python-cloudfiles/commit/f37b51af1372f428918ac289e7062d7c4a9369b5'&gt;change&lt;/a&gt; was pushed that added the &amp;#8220;delimiter&amp;#8221; parameter to object listing queries for the Python API. The Python API was incremented to version 1.7.4 for the change, so make sure to pull the proper version of the library to test the new feature out:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ pip install -U https://github.com/rackspace/python-cloudfiles/tarball/1.7.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Interestingly, while the container list_objects() and list_objects_info() methods picked up a delimiter parameter, the get_objects() method did not. At any rate, this gets the job done, and we can remove our dummy objects now to retry our examples:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;delete_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;delete_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;delete_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/diff&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;#8217;s check we don&amp;#8217;t have the dummy objects anymore:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;baz/file4.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file1.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file2.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/diff/file3.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now, let&amp;#8217;s try equivalent queries using the prefix and delimiter parameters. (Note that you must append an extra slash (e.g. &amp;#8220;foo/&amp;#8221;, not &amp;#8220;foo&amp;#8221;) for the queries to work.)&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;prefix&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;delimiter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/bar/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/diff/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;prefix&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/bar/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;delimiter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/bar/file1.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='s'&gt;&amp;#39;foo/bar/file2.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;cont&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_objects&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;prefix&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;foo/diff/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;delimiter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo/diff/file3.txt&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;#8230; and we finally get the results we want without having to track or create any extraneous objects.&lt;/p&gt;

&lt;p&gt;At the time of this post, many clients do not take advantage of more convenient / advanced object querying. The &lt;a href='http://cyberduck.ch/'&gt;CyberDuck&lt;/a&gt; client currently will only display pseudo-directories via the dummy directory objects method. In fact, Rackspace&amp;#8217;s own control panel doesn&amp;#8217;t display any pseudo-directories using either the old dummy directory or new delimiter methods. So, hopefully with the new delimiter query API support, some folks out there will make some New Year&amp;#8217;s resolutions to improve pseudo-directory support in Cloud Files clients.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2010/12/celery-logging-with-python-logging</id>
      <title>Celery Logging with Python Logging Handlers</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2010-12-13T15:54:00Z</published>
      <updated>2010-12-13T15:54:00Z</updated>
      <link href="http://loose-bits.com/2010/12/celery-logging-with-python-logging.html"/>
      <content type="html">&lt;h2 id='logging_in_celery'&gt;Logging in Celery&lt;/h2&gt;

&lt;p&gt;We use &lt;a href='http://celeryproject.org/'&gt;Celery&lt;/a&gt; as our backend messaging abstraction at work, and have lots of disparate nodes (and across different development, test, and production deployments). As each system deployment now contains a large (and growing) number of nodes, we have been making a heavy push towards consolidated logging through a central logging server sink, using syslog (specifically &lt;a href='http://www.rsyslog.com/'&gt;rsyslog&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, ideally, we would just use Celery&amp;#8217;s configuration to specify a syslog handler (or maybe use a pipe). Unfortunately, it seems there is just not a simple way of doing this straight from Celery, as the only logging configuration parameters (with possible values) are:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;CELERYD_LOG_FILE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/path/to/file.log&amp;quot;&lt;/span&gt;  &lt;span class='c'&gt;# File logging.&lt;/span&gt;
                                        &lt;span class='c'&gt;# (OR)&lt;/span&gt;
&lt;span class='n'&gt;CELERYD_LOG_FILE&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;None&lt;/span&gt;                 &lt;span class='c'&gt;# stderr.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which means you either get a file logger (&lt;a href='http://docs.python.org/library/logging.html#filehandler'&gt;filehandler&lt;/a&gt;) or a stderr logger (&lt;a href='http://docs.python.org/library/logging.html#streamhandler'&gt;streamhandler&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Hints and guidance on the web in terms of getting arbitrary logging handlers (or syslog specifically) is sparse, as well as noisy. A lot of the code and discussion I found used Celery as the &lt;em&gt;backend&lt;/em&gt; for a generic logging framework or library. But, I found very little in terms of getting Celery tasks and processes to actually log &lt;em&gt;to&lt;/em&gt; syslog.&lt;/p&gt;

&lt;h2 id='bringing_arbitrary_logging_to_celery'&gt;Bringing Arbitrary Logging to Celery&lt;/h2&gt;

&lt;p&gt;As nothing magically appeared as the &amp;#8220;right&amp;#8221; solution, I considered a couple of different ways to hook things up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Watched Files&lt;/strong&gt;: Have Celery log to a file per usual, then add extra configuration and scripts to watch the file for changes and submit the changes to syslog directly. I didn&amp;#8217;t go with this approach, as I really prefer to have configuration for our project within generic Python settings, and not need extra, system-specific scripts and setup.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Patch Celery&lt;/strong&gt;: The Celery logging hooks and code are fairly straightforward. I could have added the patch and submitted upstream. Unfortunately, for the project at work, we are moving to fast to wait for changes, and as we are looking forward to some upstream Celery releases, I&amp;#8217;d rather not maintain a private custom patch set through all of that.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;Monkey Patch Celery&lt;/strong&gt;: In the same vein as the former option, the same hooks and changes could be applied as a &lt;a href='http://en.wikipedia.org/wiki/Monkey_patch'&gt;monkey patch&lt;/a&gt; instead. This is what I eventually went with.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id='patching_logging_handlers_into_celery'&gt;Patching Logging Handlers into Celery&lt;/h2&gt;

&lt;p&gt;Monkey patching is oft-controversial and generally discouraged practice, as getting things wrong is easy, and interactions within the patched library / code can get really messy. In our case, I reluctantly chose monkey patching, as it was a short patch, easy to disable, and I had no good other solutions.&lt;/p&gt;

&lt;p&gt;Now that I&amp;#8217;ve given the standard disclaimer, let&amp;#8217;s get to the patch! The relevant Celery function we want to patch in celery.log._setup_logger():&lt;/p&gt;
&lt;!-- more start --&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;_setup_logger&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;ColorFormatter&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;

    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;handlers&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;                 &lt;span class='c'&gt;# already configured&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;logger&lt;/span&gt;

    &lt;span class='n'&gt;handler&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;_detect_handler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;setFormatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;use_color&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;addHandler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;logger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As we can see, there is only one shot at a handler, and it comes from &lt;code&gt;celery.log._detect_handler()&lt;/code&gt;, which either returns a stream or file handler. What I would ideally like is to define be able to define a Django settings variable (we use Django and not native Celery settings at work):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;
    &lt;span class='k'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;FileHandler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;path/to/another_log.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='k'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;handlers&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;SysLogHandler&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and define a new &lt;code&gt;_setup_logger()&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;celery.log&lt;/span&gt;

&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;celery.log&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;ColorFormatter&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.conf&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;

&lt;span class='n'&gt;old_setup_logger&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;celery&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_setup_logger&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;_setup_logger&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;ColorFormatter&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;

    &lt;span class='n'&gt;patched_logger&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;old_setup_logger&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
        &lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='n'&gt;handler_fn&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='nb'&gt;getattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;PATCH_CELERYD_LOG_HANDLERS&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[]):&lt;/span&gt;
        &lt;span class='n'&gt;handler&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;handler_fn&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
        &lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;setFormatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;use_color&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
        &lt;span class='n'&gt;patched_logger&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;addHandler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;patched_logger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I would have preferred a pure &amp;#8221;&lt;code&gt;*args, **kwargs&lt;/code&gt;&amp;#8221; version of the patched function, but unfortunately, we need to dig out the formatter parameter if set, so I copied the method signature of the original. Also, I used lambda&amp;#8217;s in &lt;code&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/code&gt; and called the function in the new setup logger so as to create a &lt;em&gt;new&lt;/em&gt; handler object each time for loggers. It is important to have different handler objects because formats attach to handler objects, and Celery uses different formats for general/default/process logging vs. task-specific logging. If a handler has last been set to a task logger format, that includes a format for among other things &amp;#8220;task_name&amp;#8221;, then you will get a KeyError on trying to issue a logging statement.&lt;/p&gt;

&lt;p&gt;Other than that, the patched method is very easy to read &amp;#8211; it iterates the custom handler function list and creates new handlers for each different logging object. Putting this in to a patched version, I also added a marker attribute to the logger object (to tell the patching is done), as well as the patching code to lock, switch and mark the methods:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;logging&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;celery.log&lt;/span&gt;

&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;celery.log&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;ColorFormatter&lt;/span&gt;
&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;django.conf&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;

&lt;span class='n'&gt;old_setup_logger&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;celery&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_setup_logger&lt;/span&gt;  &lt;span class='c'&gt;# Store the real method.&lt;/span&gt;
&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;my_setup_logger&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;ColorFormatter&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Replacement for :method:`celery.log._setup_logger`.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class='n'&gt;patched_logger&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;old_setup_logger&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
        &lt;span class='n'&gt;logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='c'&gt;# Check if not patched.&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='ow'&gt;not&lt;/span&gt; &lt;span class='nb'&gt;getattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;patched_logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;_LOG_PATCH_DONE&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='c'&gt;# Lock and patch.&lt;/span&gt;
        &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_acquireLock&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
        &lt;span class='k'&gt;try&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
            &lt;span class='n'&gt;handler_fns&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;getattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;PATCH_CELERYD_LOG_HANDLERS&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[])&lt;/span&gt;
            &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='n'&gt;handler_fn&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;handler_fns&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
                &lt;span class='n'&gt;handler&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;handler_fn&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
                &lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;setFormatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;formatter&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;use_color&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;colorize&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
                &lt;span class='n'&gt;patched_logger&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;addHandler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;handler&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

            &lt;span class='c'&gt;# Mark logger object patched.&lt;/span&gt;
            &lt;span class='nb'&gt;setattr&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;patched_logger&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;_LOG_PATCH_DONE&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='bp'&gt;True&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='k'&gt;finally&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
            &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_releaseLock&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;patched_logger&lt;/span&gt;

&lt;span class='c'&gt;# Apply patches.&lt;/span&gt;
&lt;span class='n'&gt;CELERY_MOD_PATCHED&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;False&lt;/span&gt;
&lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='ow'&gt;not&lt;/span&gt; &lt;span class='n'&gt;CELERY_MOD_PATCHED&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
    &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_acquireLock&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;  &lt;span class='c'&gt;# Lock logging during patch.&lt;/span&gt;
    &lt;span class='k'&gt;try&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='n'&gt;celery&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;log&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_setup_logger&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;my_setup_logger&lt;/span&gt;  &lt;span class='c'&gt;# Patch old w/ new.&lt;/span&gt;
        &lt;span class='n'&gt;CELERY_MOD_PATCHED&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;True&lt;/span&gt;
    &lt;span class='k'&gt;finally&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='n'&gt;logging&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;_releaseLock&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the patch applied, any number of Python standard logging handlers can now be added to the celery task / process loggers.&lt;/p&gt;

&lt;h2 id='applying_the_patch_at_runtime'&gt;Applying the Patch at Runtime&lt;/h2&gt;

&lt;p&gt;So we&amp;#8217;ve defined the patch, but how do we apply it? Part of the problem is celery configuration. If you apply the patch too early, you get the following exception:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;.../python2.6/site-packages/celery/loaders/default.py:53: NotConfigured:
No celeryconfig.py module found! Please make sure it exists and is available to Python.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, we have to find a proper place in the initialization process, after Celery settings. The best solution I found for this is to create a faux Django &amp;#8220;application&amp;#8221; , that contains only the patch code. Let&amp;#8217;s call the application &amp;#8220;patch_celery&amp;#8221;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='text'&gt;$ cd path/to/project
$ django-admin.py startapp patch_celery
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Copy and paste all of the patch code above into the &lt;strong&gt;init&lt;/strong&gt;.py. And then, we simply add the patch application to the Django settings.py installed apps:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;djcelery&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='s'&gt;&amp;#39;patch_celery&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='c'&gt;# ...&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The best place to put the patch is right &lt;em&gt;after&lt;/em&gt; the djcelery application. Now, add your patch variable, &lt;code&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/code&gt;, to settings.py with any designated handlers (per previous example) and you&amp;#8217;re off with any logging handlers for celery tasks that you want! You can disable the patch by simply removing the &amp;#8220;patch_celery&amp;#8221; application from installed applications in settings, which enables easily toggling per appropriate context without major code swaps.&lt;/p&gt;

&lt;h2 id='afterthoughts'&gt;Afterthoughts&lt;/h2&gt;

&lt;p&gt;The monkey patch to Celery certainly gets the job done for now. However, the fact that the full method signature for &lt;code&gt;celery.log._setup_logger()&lt;/code&gt; is used makes the patch very brittle &amp;#8211; if a parameter changes in the underlying method, the patched method may cease to work, or worse, give a wrong parameter to the underlying method.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2010/10/rackspace-cloud-files-and-servicenet</id>
      <title>Rackspace Cloud Files and Servicenet</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2010-10-18T00:01:00Z</published>
      <updated>2010-10-18T00:01:00Z</updated>
      <link href="http://loose-bits.com/2010/10/rackspace-cloud-files-and-servicenet.html"/>
      <content type="html">&lt;h2 id='cloud_files_cloud_servers'&gt;Cloud Files, Cloud Servers&lt;/h2&gt;

&lt;p&gt;At work, we use &lt;a href='http://www.rackspacecloud.com/'&gt;Rackspace&lt;/a&gt; &lt;a href='http://www.rackspacecloud.com/cloud_hosting_products/files'&gt;Cloud Files&lt;/a&gt; for bulk blob storage of large sets of documents (in our case, patents). We use a farm of Rackspace &lt;a href='http://www.rackspacecloud.com/cloud_hosting_products/servers'&gt;Cloud Servers&lt;/a&gt; to process and serve blobs from Cloud Files. Rackspace&amp;#8217;s offering has been very solid to date (modulo some idiosyncrasies of their particular APIs) and quite interestingly, the underlying software is being &lt;a href='http://techcrunch.com/2010/07/18/openstack-org-rackspace-open-sources-their-cloud-services-platform-and-gets-nasa-on-board/'&gt;released&lt;/a&gt; as an open source project, &lt;a href='http://openstack.org/'&gt;OpenStack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the nice things building a solution on Rackspace is that bandwidth is free between Cloud Servers and Cloud Files, if properly configured. However, as our recent experience bears out, there are some pitfalls and caveats towards getting everything set up correctly.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;h2 id='public_private_interfaces_and_servicenet'&gt;Public, Private Interfaces and Servicenet&lt;/h2&gt;

&lt;p&gt;Rackspace Cloud Servers are essentially Xen VMs that come with two network interfaces: (1) a public interface on eth0 with a world-routable IP address, and (2) a &amp;#8220;private&amp;#8221; interface on eth1 with an &lt;a href='http://www.faqs.org/rfcs/rfc1918.html'&gt;RFC 1918&lt;/a&gt; address. (The private interface isn&amp;#8217;t really &amp;#8220;private&amp;#8221; in the sense that any other VMs on the same network can route to your VM, so security-wise, it should be treated as a public, internet-facing interface). For more details, Rackspace has published a good &lt;a href='http://www.rackspacecloud.com/blog/2010/02/23/networking-and-cloud-servers-more-on-the-interfaces/'&gt;article&lt;/a&gt; on the public and private interfaces.&lt;/p&gt;

&lt;p&gt;For billing purposes, bandwidth between Cloud Files and a Cloud Server VM on the public interface is charged per normal, whereas bandwidth on the private interface is free. To enable Cloud Files communication on the private interface, you can either set an environment variable in your application like follows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export RACKSPACE_SERVICENET=True&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or programmatically specify the private network, as shown here using the &lt;a href='http://github.com/rackspace/python-cloudfiles'&gt;Python Cloud Files&lt;/a&gt; client library:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;cloudfiles&lt;/span&gt;
&lt;span class='n'&gt;conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;acct&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;api_key&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;servicenet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;True&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In both cases, &amp;#8220;Servicenet&amp;#8221; refers to the private interface network internal to a Rackspace data center.&lt;/p&gt;

&lt;h2 id='servicenet_gotchas'&gt;Servicenet Gotchas&lt;/h2&gt;

&lt;p&gt;All seems pretty simple! However, when we finally enabled &amp;#8220;servicenet=True&amp;#8221; in our Cloud Servers, we immediately hit errors talking to Cloud Files and everything refused to run. After a lot of back and forth with the Rackspace folks, we finally found out the main issue: Servicenet only works between Cloud Files and Cloud Servers in &lt;strong&gt;the same data center&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before we ran into this snag, we hadn&amp;#8217;t even checked that our servers and blob storage were in the same data center. As it turned out, our Cloud Files location was in Texas (DFW) and our Cloud Servers were in Chicago (ORD). And, after much hand-wringing, we learned that: (1) new Cloud Servers will most likely be provisioned in Chicago, and (2) you can&amp;#8217;t easily migrate Cloud Files accounts from Texas to Chicago.&lt;/p&gt;

&lt;p&gt;So, how can one easily tell where your Cloud Servers and Cloud Files are, and whether or not Servicenet will be available? The answer for Cloud Servers is straightforward: in the Rackspace &lt;a href='https://manage.rackspacecloud.com/CloudServers/ServerList.do'&gt;management dashboard&lt;/a&gt;, look at your server list and the &amp;#8220;Datacenter&amp;#8221; column. In our case, all servers list as &amp;#8220;ORD1&amp;#8221;, which means Chicago. Moving on to Cloud Files, one way to figure things out is to fire up a Python shell and issue some basic commands to see what we have. In all cases, looking at a connection object&amp;#8217;s &amp;#8221;&lt;code&gt;connection_args[0]&lt;/code&gt;&amp;#8221; value will yield a string for public interfaces like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;storage&amp;lt;NUMBER&amp;gt;.&amp;lt;DATACENTER&amp;gt;.clouddrive.com&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and on the private interface:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;snet-storage&amp;lt;NUMBER&amp;gt;.&amp;lt;DATACENTER&amp;gt;.clouddrive.com&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, on to some example code using the Python client. Here is a Chicago Cloud Server talking to Chicago Cloud Files on the public interface:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;cloudfiles&lt;/span&gt;

&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_pub_conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;chicago_acct&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;1234567890&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;servicenet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_pub_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connection_args&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;span class='s'&gt;&amp;#39;storage101.ord1.clouddrive.com&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_pub_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_containers&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;bar1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;baz1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;#8230; and on the private interface:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_priv_conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;chicago_acct&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;1234567890&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;servicenet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;True&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_priv_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connection_args&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;span class='s'&gt;&amp;#39;snet-storage101.ord1.clouddrive.com&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;chicago_priv_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_containers&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;bar1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;baz1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, switching to a Chicago Cloud Server talking to Texas Cloud Files on the public interface, we get:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;texas_pub_conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;texas_acct&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;1234567890&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;servicenet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;texas_pub_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connection_args&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;span class='s'&gt;&amp;#39;storage101.dfw1.clouddrive.com&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;texas_pub_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_containers&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;foo2&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;bar2&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;#39;baz2&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;but, trying to talk on the private interface yields the following exception:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;texas_priv_conn&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_connection&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;texas_acct&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;1234567890&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;servicenet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;True&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;texas_priv_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;connection_args&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
&lt;span class='s'&gt;&amp;#39;snet-storage101.dfw1.clouddrive.com&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;texas_priv_conn&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;list_containers&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='n'&gt;Traceback&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;most&lt;/span&gt; &lt;span class='n'&gt;recent&lt;/span&gt; &lt;span class='n'&gt;call&lt;/span&gt; &lt;span class='n'&gt;last&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
  &lt;span class='n'&gt;File&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;line&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;module&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt;
  &lt;span class='n'&gt;File&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/usr/local/lib/python2.6/dist-packages/cloudfiles/connection.py&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;line&lt;/span&gt; &lt;span class='mi'&gt;410&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ow'&gt;in&lt;/span&gt; &lt;span class='n'&gt;list_containers&lt;/span&gt;
    &lt;span class='k'&gt;raise&lt;/span&gt; &lt;span class='n'&gt;ResponseError&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;status&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;reason&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;cloudfiles&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;errors&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ResponseError&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='mi'&gt;404&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='n'&gt;Not&lt;/span&gt; &lt;span class='n'&gt;Found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Confusingly, the exception raised is a 404 &amp;#8220;Not Found&amp;#8221;, when it really should be something more context-specific.&lt;/p&gt;

&lt;h2 id='final_thoughts'&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;The last time I chatted with the Rackspace folks, they noted most new accounts will have both Cloud Servers and Cloud Files allocated in Chicago, so using Servicenet should be as simple as setting the appropriate variable. In our specific case, we got around our Texas-Chicago split by allocating an entirely new account that has both servers and files in Chicago, and ported over our Texas files. Somewhat tedious, but a worthwhile long-term investment.&lt;/p&gt;

&lt;p&gt;But, if you are trying to use Servicenet and experience 404&amp;#8217;s or other strange failures, you probably want to go through the above exercise and identify the data center where your servers and blobs reside. And, if you use Cloud Servers and Cloud Files and aren&amp;#8217;t already using Servicenet, you definitely should because you are paying for bandwidth that should otherwise be free!&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
    
    <entry>
      <id>http://loose-bits.com/2010/10/distributed-task-locking-in-celery</id>
      <title>Distributed Task Locking in Celery</title>
      <author>
        <name>Ryan Roemer</name>
        <email>ryan@loose-bits.com</email>
      </author>
      <published>2010-10-10T20:20:00Z</published>
      <updated>2010-10-10T20:20:00Z</updated>
      <link href="http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html"/>
      <content type="html">&lt;h2 id='background'&gt;Background&lt;/h2&gt;

&lt;p&gt;We use the &lt;a href='http://celeryq.org/'&gt;Celery&lt;/a&gt; distributed task queue library at work, which is great for running asynchronous tasks across multiple processes and servers. Celery has both user-initiated and periodic (think cron replacement) tasks, and we have found in practice that the system distributes tasks quite nicely across our farm of celery servers.&lt;/p&gt;

&lt;p&gt;One issue we have is that for several of our periodic tasks, we need to ensure that only one task is running at a time, and that later instances of the same periodic task are skipped if a previous incarnation is still running.&lt;/p&gt;

&lt;p&gt;The &lt;a href='http://celeryq.org/docs'&gt;Celery documentation&lt;/a&gt; has a cookbook recipe for this scenario: &amp;#8221;&lt;a href='http://celeryq.org/docs/cookbook/tasks.html#ensuring-a-task-is-only-executed-one-at-a-time'&gt;Ensuring a task is only executed one at a time&lt;/a&gt;&amp;#8221;. The crux of the solution is to make a distributed lock using the Django cache (&lt;a href='http://memcached.org/'&gt;memcached&lt;/a&gt; in the example) with the following lambda&amp;#8217;s:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='n'&gt;lock_id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;something unique&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;lock_expire&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;60&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt;  &lt;span class='c'&gt;# five minutes&lt;/span&gt;

&lt;span class='n'&gt;acquire_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='n'&gt;cache&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;lock_id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;lock_expire&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;release_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='n'&gt;cache&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;delete&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;lock_id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id='nonpersistent_locks'&gt;Non-persistent Locks?&lt;/h2&gt;

&lt;p&gt;This approach works fine if the cache is shared across all celery worker nodes and the cache is persistent. However, if memcached (or some other non- persistent cache) is used and (1) the cache daemon crashes or (2) the cache key is culled before the appropriate expiration time / lock release, then you have a race condition where two or more tasks could simultaneously acquire the task lock. This &amp;#8220;distributed cache lock&amp;#8221; approach has been discussed in &lt;a href='http://bluxte.net/musings/2009/10/28/simple-distributed-lock-memcached'&gt;various&lt;/a&gt; &lt;a href='http://www.regexprn.com/2010/05/using-memcached-as-distributed-locking.html'&gt;posts&lt;/a&gt;, which all acknowledge the danger of relying on memcached for persistent data.&lt;/p&gt;

&lt;h2 id='distributed_locks_with_redis'&gt;Distributed Locks with Redis&lt;/h2&gt;

&lt;p&gt;As noted by one of the links above, the simplest solution to this problem if you like using the cache for distributed locks is to switch to &lt;a href='http://memcachedb.org/'&gt;memcachedb&lt;/a&gt; which is not a caching solution &lt;em&gt;per se&lt;/em&gt;, but rather a persistent key-value store that implements the memcached interface.&lt;/p&gt;
&lt;!-- more start --&gt;
&lt;p&gt;However, for our system, using memcached as our Django cache is great in its current non-persistent form for what it is &amp;#8211; a cache. So, I investigated more generally to find a high performance, persistent key-value store (ideally with a decent Python interface). After reviewing a lot of neat and interesting systems, I finally settled on &lt;a href='http://code.google.com/p/redis/'&gt;Redis&lt;/a&gt;. Redis provides a very high performance key-value store (data is maintained in memory) with persistence (using the &lt;a href='http://code.google.com/p/redis/wiki/AppendOnlyFileHowto'&gt;append only file&lt;/a&gt; feature) and &lt;a href='http://code.google.com/p/redis/wiki/ReplicationHowto'&gt;distribution / replication&lt;/a&gt;. As an added bonus, I found the server setup, installation and CLI interaction to be very straightforward.&lt;/p&gt;

&lt;p&gt;So, back to locking&amp;#8230; The Redis &lt;a href='http://github.com/andymccurdy/redis-py'&gt;python client&lt;/a&gt; already has a lock class with &amp;#8221;&lt;code&gt;with&lt;/code&gt;&amp;#8221; operator support:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;redis&lt;/span&gt;

&lt;span class='k'&gt;with&lt;/span&gt; &lt;span class='n'&gt;redis&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Redis&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;lock&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my_key&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Got lock.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, the above example is a blocking lock, and for the &amp;#8220;single task&amp;#8221; issue, we want a non-blocking lock, that simply exits if the lock is not acquired:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;redis&lt;/span&gt;

&lt;span class='n'&gt;have_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;False&lt;/span&gt;
&lt;span class='n'&gt;my_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;redis&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Redis&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;lock&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;my_key&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;try&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
    &lt;span class='n'&gt;have_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;my_lock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;acquire&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;blocking&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;have_lock&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Got lock.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;else&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Did not acquire lock.&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='k'&gt;finally&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;have_lock&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
        &lt;span class='n'&gt;my_lock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;release&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Beyond this simple example, the Lock class implements key expiration (via the Redis &lt;a href='http://code.google.com/p/redis/wiki/SetnxCommand'&gt;setnx&lt;/a&gt; command) to enable timeouts in the python client.&lt;/p&gt;

&lt;h2 id='enforced_single_celery_task'&gt;Enforced Single Celery Task&lt;/h2&gt;

&lt;p&gt;So, bringing this back to our celery tasks, we can use this distributed lock to have our tasks try to acquire a non-blocking lock, and exit if the lock isn&amp;#8217;t acquired. Also, we want to set a lock timeout (lasting for a generous overestimate of task duration time) so that tasks will eventually be able to re-acquire the lock if some task / celery node hard crashes or goes in to an unresponsive state before releasing the lock.&lt;/p&gt;

&lt;p&gt;All of this can be put together as a decorator around a Task.run() method:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;redis&lt;/span&gt;

&lt;span class='n'&gt;REDIS_CLIENT&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;redis&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Redis&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;only_one&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;function&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;None&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;timeout&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;None&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Enforce only one celery task at a time.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;_dec&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;run_func&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Decorator.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;_caller&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;args&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
            &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Caller.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class='n'&gt;ret_value&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;None&lt;/span&gt;
            &lt;span class='n'&gt;have_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='bp'&gt;False&lt;/span&gt;
            &lt;span class='n'&gt;lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;REDIS_CLIENT&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;lock&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;timeout&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;timeout&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
            &lt;span class='k'&gt;try&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
                &lt;span class='n'&gt;have_lock&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;lock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;acquire&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;blocking&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;have_lock&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
                    &lt;span class='n'&gt;ret_value&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;run_func&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;args&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
            &lt;span class='k'&gt;finally&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
                &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;have_lock&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;
                    &lt;span class='n'&gt;lock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;release&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;

            &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;ret_value&lt;/span&gt;

        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;_caller&lt;/span&gt;

    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;_dec&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;function&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;function&lt;/span&gt; &lt;span class='ow'&gt;is&lt;/span&gt; &lt;span class='ow'&gt;not&lt;/span&gt; &lt;span class='bp'&gt;None&lt;/span&gt; &lt;span class='k'&gt;else&lt;/span&gt; &lt;span class='n'&gt;_dec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that this decorator preserves task return values. If your tasks don&amp;#8217;t have return values, you can get rid of the &lt;code&gt;ret_value&lt;/code&gt; code.&lt;/p&gt;

&lt;p&gt;Using the decorator is easy &amp;#8211; just annotate a task &lt;code&gt;run()&lt;/code&gt; method:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;from&lt;/span&gt; &lt;span class='nn'&gt;celery.task&lt;/span&gt; &lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='n'&gt;Task&lt;/span&gt;

&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;SingleTask&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Task&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
    &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;A task.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class='nd'&gt;@only_one&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;SingleTask&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;timeout&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;60&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;run&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;self&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;kwargs&lt;/span&gt;&lt;span class='p'&gt;):&lt;/span&gt;
        &lt;span class='sd'&gt;&amp;quot;&amp;quot;&amp;quot;Run task.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='k'&gt;print&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Acquired lock for up to 5 minutes and ran task!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;#8230; and your task will only ever have one running instance at any given time.&lt;/p&gt;
&lt;!-- more end --&gt;</content>
    </entry>
  
</feed>