<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:admin="http://webns.net/mvcb/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:html="http://www.w3.org/1999/html" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>Random notes from mg</title><link>http://mg.pov.lt/blog</link><description>a blog by Marius Gedminas</description><language>en</language><ttl>60</ttl><dc:creator>Marius Gedminas</dc:creator><admin:generatorAgent rdf:resource="http://pyblosxom.sourceforge.net/"/><admin:errorReportsTo rdf:resource="mailto:marius@gedmin.as"/><item><title>Stuff I've been doing recently</title><guid isPermaLink="false">december-updates-2010</guid><link>http://mg.pov.lt/blog/december-updates-2010</link><description>objgraph got shiny documentation built with Sphinx . Python 3.x support, thanks to Stefano Rivera. Python 2.4 and 2.5 support, ...</description><content:encoded><![CDATA[
<p><a href="http://pypi.python.org/pypi/objgraph">objgraph</a> got</p>
<ul>
  <li><a href="http://mg.pov.lt/objgraph/">shiny documentation</a> built with
      <a href="http://sphinx.pocoo.org/">Sphinx</a>.</li>
  <li>Python 3.x support, thanks to Stefano Rivera.</li>
  <li>Python 2.4 and 2.5 support, thanks to Daniel Blackburn.</li>
  <li><a href="http://mg.pov.lt/objgraph/CHANGES.html">assorted smaller improvements</a>.</li>
</ul>

<p><a href="http://pypi.python.org/pypi/zodbbrowser">zodbbrowser</a> got</p>
<ul>
  <li>support for all ZODB databases, not just those with a
      Zope 3/Bluebream-style root folder/local site.</li>
  <li>the ability to cope better with broken objects (due to the way ZODB
      works, not having some of your modules on the Python pack can break
      unpickling; zodbbrowser now handles this kind of situation better).</li>
  <li><a href="http://pypi.python.org/pypi/zodbbrowser#changes">assorted smaller improvements</a>.</li>
  <li>a slow but inevitable shift of focus from "use it as a plugin for your
      Zope 3 app" to "it's a standalone tool for inspecting ZODB contents".
      (Both use cases are still supported, and will be for the foreseeable
      future.)</li>
</ul>

<p><a href="http://pypi.python.org/pypi/imgdiff">imgdiff</a> got</p>
<ul>
  <li>its first public release.</li>
  <li>some experimental code to actually find and highlight the differing
      parts of the images:
      <br />
      <br />
      <center>
        <img src="http://mg.pov.lt/imgdiff-highlighting.png"
             width="739" height="403"
             alt="example of highlighted image differences" />
      </center>
      <br />
      This works better when both images are the same size, although there's
      experimental (and somewhat buggy) code to try all possible alignments.
      I could use some help here; image processing is not something I'm
      familiar with, and <a href="http://stackoverflow.com/search?q=difference+between+images">searching
        StackOverflow</a> didn't help beyond reminding me of the existence
      of PIL's ImageChops.difference(), which is for same-sized images only.
      Many of the results there are about comparing photos, where things like
      lighting levels matter.  What I need is a diff for computer-generated
      images, where some things may be shifted around a bit, by different
      amounts, but are essentially the same.  Are there any two-dimensional
      sequence diff algorithms?
    </li>
  </ul>

]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-12-19T00:35:40Z</dc:date></item><item><title>Profiling with Dozer</title><guid isPermaLink="false">profiling-with-dozer</guid><link>http://mg.pov.lt/blog/profiling-with-dozer</link><description>Dozer is mostly known for its memory profiling capabilities, but the as-yet unreleased version has more. I've talked about log ...</description><content:encoded><![CDATA[
<p><a href="http://pypi.python.org/pypi/Dozer">Dozer</a> is mostly known for
its memory profiling capabilities, but the as-yet unreleased version has
more.  I've <a href="http://mg.pov.lt/blog/capturing-logs-with-dozer.html">talked
  about log capturing</a>, now it's time for</p>

<p><strong>Profiling</strong></p>

<p>This WSGI middleware profiles every request with the cProfile module.
To see the profiles, visit a hidden URL <tt>/_profiler/showall</tt>:</p>

<p><a href="http://mg.pov.lt/dozer-all-profiles.png"><img src="http://mg.pov.lt/dozer-all-profiles-small.png" alt="List of profiles" /></a></p>

<p>What you see here is heavily tweaked in <a
  href="http://bitbucket.org/mgedmin/dozer">my <del>fork</del> <ins>branch</ins>
  of Dozer</a>; upstream version had no Cost column and
didn't vary the background of Time by age (that
last bit helps me see clumps of requests).</p>

<p>Here's what an individual profile looks like:</p>

<p><a href="http://mg.pov.lt/dozer-one-profile.png"><img src="http://mg.pov.lt/dozer-one-profile-small.png" alt="One profile" /></a></p>

<p>The call tree nodes can be expanded and collapsed by clicking on the
function name.  There's a hardcoded limit of 20 nesting levels (upstream had a
limit of 15), sadly that appears not to be enough for practical purposes,
especially if you start profiling Zope 3 applications...</p>

<p>You can also take a look at the WSGI environment:</p>

<p><a href="http://mg.pov.lt/dozer-environ.png"><img src="http://mg.pov.lt/dozer-environ-small.png" alt="WSGI environment expanded" /></a></p>

<p>Sadly, nothing about the response is captured by Dozer.  I'd've liked to
show the Content-Type and perhaps Content-Length in the profile list.</p>

<p>The incantation in <tt>development.ini</tt> is</p>
<blockquote><pre>
[filter-app:profile]
use = egg:Dozer#profile
profile_path = /tmp/profiles
next = main
</pre></blockquote>
<p>Create an empty directory /tmp/profiles and <strong>make sure other users
  cannot write to it</strong>.  Dozer stores captured profiles as Python
pickles, which are <a
  href="http://docs.python.org/library/pickle.html#relationship-to-other-python-modules">insecure</a>
and allow <strong>arbitrary
  command execution</strong>.</p>

<p>To enable the profiler, run paster like this:</p>
<blockquote><pre>
<span class="prompt">$</span> <span class="typing">paster serve development.ini -n profile</span>
</pre></blockquote>

<p><strong>Bonus feature: call graphs</strong></p>

<p>Dozer also writes a call graph in Graphviz "dot" format in the profile
directory.  Here's the graph corresponding to the profile you saw earlier,
as displayed by the excellent <a
  href="http://code.google.com/p/jrfonseca/wiki/XDot">XDot</a>:</p>

<p><a href="http://mg.pov.lt/dozer-graph.png"><img src="http://mg.pov.lt/dozer-graph-small.png" alt="Call graph" /></a></p>

<p>See the fork where the "hot" red path splits into two?</p>

<p><a href="http://mg.pov.lt/dozer-graph-zoom1.png"><img src="http://mg.pov.lt/dozer-graph-zoom1-small.png" alt="Call graph, zoomed in" /></a></p>

<p>On the left we have Routes deciding to spend 120 ms (70% total time)
recompiling its route maps.  On the right we have the actual request dispatch.
The actual controller action is called a bit further down:</p>

<p><a href="http://mg.pov.lt/dozer-graph-zoom2.png"><img src="http://mg.pov.lt/dozer-graph-zoom2-small.png" alt="Call graph, zoomed in" /></a></p>

<p>Here it is, highlighted.  42 ms (24% total time), almost all of which is
spent in SQLAlchemy, loading the model object (a 2515 byte image stored as a
blob) from SQLite.</p>

<p><strong>A mystery: pickle errors</strong></p>

<p>When I first tried to play with the Dozer profiler, I was attacked by
innumerable exceptions.  Some of those were due to a lack of configuration
(profile_path) or invalid configuration (directory not existing), or not
knowing the right URL (going to <tt>/_profiler</tt> raised TypeError).  I
tried to make Dozer's profiler more forgiving or at least produce clearer
error messages in <a href="http://bitbucket.org/mgedmin/dozer">my <del>fork</del> <ins>branch</ins></a>,
e.g. going to <tt>/_profiler</tt> now displays the profile list.</p>

<p>However some errors were very mysterious: some pickles, written by Dozer
itself, could not be unpickled.  I added a try/except that put those at the end
of the list, so you can see and delete them.</p>

<p><a href="http://mg.pov.lt/dozer-errors.png"><img src="http://mg.pov.lt/dozer-errors-small.png" alt="Pickle errors" /></a></p>

<p>Does anybody have any clues as to why <a
  href="http://bitbucket.org/mgedmin/dozer/src/6b6884445863/dozer/profile.py#cl-183">profile.py</a>
might be writing out broken pickles?</p>

<p><strong>Update</strong>: as Ben says in the comments, my changes have
been accepted upstream.  Yay!</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-08-07T03:06:33Z</dc:date></item><item><title>Capturing logs with Dozer</title><guid isPermaLink="false">capturing-logs-with-dozer</guid><link>http://mg.pov.lt/blog/capturing-logs-with-dozer</link><description>Dozer is mostly known for its memory profiling capabilities, but the as-yet unreleased version has more: Log capturing This WSGI ...</description><content:encoded><![CDATA[
<p><a href="http://pypi.python.org/pypi/Dozer">Dozer</a> is mostly known for
its memory profiling capabilities, but the as-yet unreleased version has
more:</p>

<p><strong>Log capturing</strong></p>

<p>This WSGI middleware intercepts logging calls for every request.  Here
we see a toy <a href="http://pylonshq.com/">Pylons</a> application I've
been working on in my spare time. Dozer added an info bar at the top:</p>

<p><a href="http://mg.pov.lt/dozer-logview-prompt.png"><img src="http://mg.pov.lt/dozer-logview-prompt-small.png" alt="Dozer's infobar" /></a></p>

<p>When you click on it, you get to see all the log messages produced for this
request.  I've set SQLAlchemy's loglevel to INFO in my
<tt>development.ini</tt>, which produces:</p>

<p><a href="http://mg.pov.lt/dozer-logview.png"><img src="http://mg.pov.lt/dozer-logview-small.png" alt="Dozer's log viewer" /></a></p>

<p>(Why on Earth does SQLAlchemy think I want to see the memory address of the
Engine object in my log files, I don't know.  The parentheses contain
argument values for parametrized queries, of which there are none on this
page.)</p>

<p>Upstream version displays absolute timestamps (of the YYYY-MM-DD
HH:MM:SS.ssssss variety) in the first column; <a
  href="http://bitbucket.org/mgedmin/dozer">my fork</a> shows deltas in
milliseconds.  The incantation in <tt>development.ini</tt> is</p>
<blockquote><pre>
[filter-app:logview]
use = egg:Dozer#logview
next = main
</pre></blockquote>
<p>which makes it disabled by default.  To enable, you run paster like this:</p>
<blockquote><pre>
<span class="prompt">$</span> <span class="typing">paster serve development.ini -n logview</span>
</pre></blockquote>

<p>(Upstream version lacks the paste entry point for logview; it's in my
fork, for which I submitted a pull request weeks ago like a good open-source
citizen.  Incidentally, patches for stuff I maintain have been known to
languish for <em>years</em> in <em>my</em> inbox, so I'm not one to throw
stones.)</p>

<p>Next: <a href="http://mg.pov.lt/blog/profiling-with-dozer.html">profiling with Dozer</a>.</p>

<p><strong>Update:</strong> Tom Longson <a
  href="http://truefalsemaybe.com/2008/11/profiling-sql-in-pylons-with-dozer/">blogged
  about this</a> back in 2008!  And his CSS is prettier.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-08-07T02:27:59Z</dc:date></item><item><title>irclog2html is now on PyPI</title><guid isPermaLink="false">irclog2html-on-pypi</guid><link>http://mg.pov.lt/blog/irclog2html-on-pypi</link><description>irclog2html , the IRC log to HTML converter, is now (finally!) available from the Python Package Index . In other ...</description><content:encoded><![CDATA[
<p><a href="http://mg.pov.lt/irclog2html/">irclog2html</a>, the IRC log to HTML
converter, is now (finally!) available from the <a
  href="http://pypi.python.org/pypi/irclog2html">Python Package Index</a>.</p>

<p>In other news, logs2html now copies <a
  href="http://mg.pov.lt/maemo-irclog/irclog.css">irclog.css</a> to the
destination directory (if it doesn't exist there already).  I've been noticing
logs produced with irclog2html on random places, and sometimes they were
unstyled; hopefully this will become rare now.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-08-06T01:32:57Z</dc:date></item><item><title>ImportError: No module named _md5</title><guid isPermaLink="false">no-module-md5</guid><link>http://mg.pov.lt/blog/no-module-md5</link><description>If you're using virtualenv, and after a system upgrade you get errors like ... File &quot;...&quot;, line ... from hashlib ...</description><content:encoded><![CDATA[
<p>If you're using virtualenv, and after a system upgrade you get errors like</p>
<blockquote><pre>
...
  File "...", line ...
    from hashlib import md5
  File "/usr/lib/python2.6/hashlib.py", line 63, in __get_builtin_constructor
     import _md5
ImportError: No module named _md5
</pre></blockquote>

<p>this means that the copy of the python executable in your virtualenv/bin
directory is outdated and you should update it:</p>

<blockquote><pre>
<span class="prompt">$</span> cp /usr/bin/python2.6 /path/to/venv/bin/python
</pre></blockquote>

<p>or, better yet, recreate the virtualenv.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-07-06T22:20:04Z</dc:date></item><item><title>Re: Web frameworks considered useful.</title><guid isPermaLink="false">web-frameworks-useful</guid><link>http://mg.pov.lt/blog/web-frameworks-useful</link><description>Martijn Faassen defends web frameworks in a rather longish post (you can tell it's 5 AM in the morning and ...</description><content:encoded><![CDATA[
<p>Martijn Faassen <a
  href="http://faassen.n--tree.net/blog/view/weblog/2010/04/15/0">defends web
  frameworks</a> in a rather longish post (you can tell it's 5 AM in the
morning and I've <em>nearly</em> defeated the unread post queue in Google
Reader).  I'd like to propose a condensed
version.  Consider this slogan:

<blockquote>
  Simple things should be easy; complicated things should be possible.
</blockquote>

<p><em>Frameworks</em> make simple things easy.  <em>Good</em> frameworks
keep the complicated thing possible; poorly-designed frameworks make the
complicated thing more difficult than necessary; bad frameworks make even
simple things complicated.</p>

<p>Doing everything from scratch merely makes things possible, but rarely
easy.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-04-18T01:57:07Z</dc:date></item><item><title>Review: Grok 1.0 Web Development</title><guid isPermaLink="false">grok-book-review</guid><link>http://mg.pov.lt/blog/grok-book-review</link><description>Disclaimer: I received a free review copy of this book. The book links are affiliate links; I get a small ...</description><content:encoded><![CDATA[
<p>Disclaimer: I received a free review copy of this book. The book links are
affiliate links; I get a small amount from any purchase you make through them.</p>

<p><a href="http://grok.zope.org/">Grok</a> is a Python web framework, built on
top of the Zope Toolkit, which is the core of what used to be called Zope 3 and
is now rebranded as BlueBream.  Confused yet?  Get used to it: the small
pluggable components are the heart and soul of ZTK, and the source of its
flexibility. It's not surprising that people take the same approach on a larger
scale: take Zope 3 apart into smaller packages and reassemble them into
different frameworks such as Grok, BlueBream or repoze.bfg.</p>

<p style="float: right; margin: 0 0 1em 1em">
<a href="http://www.packtpub.com/grok-1-0-web-development/book/mid/150310wgpfxa?utm_source=mg.pov.lt&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_002715">
  <img src="http://mg.pov.lt/grok-book.jpg"
       width="250" height="309" alt="Grok 1.0 Web Development by Carlos de la Guardia" />
</a>
</p>

<p>The <a
  href="http://www.packtpub.com/grok-1-0-web-development/book/mid/150310wgpfxa?utm_source=mg.pov.lt&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_002715">Grok
  book by Carlos de la Guardia</a> introduces the framework by demonstrating
how to create a small but realistic To-do list manager.  I like this technique,
and it works pretty well.  The author covers many topics:</p> 
<ul>
  <li> creation of a new project </li>
  <li> simple views with Zope Page Templates </li>
  <li> automatic form generation from schemas (with tweaks) </li>
  <li> catalogs and indexes (my favourite chapter) </li>
  <li> security: users, roles, permissions; authentication and authorization </li>
  <li> extremely pluggable page layouts with viewlets and pagelets </li>
  <li> basic ZODB, blobs, ZEO, database packing, backups with repozo </li>
  <li> SQL databases, integration with SQLAlchemy (including a common
       transactional model) </li>
  <li> component architecture: adapters and utilities </li>
  <li> Martian: extending Grok by defining custom component directives </li>
  <li> very short intro to testing (zope.testing, unit tests and doctests,
       functional tests with zope.testbrowsing) and debugging (pdb; AJAXy
       debugger, which looks exactly like the Pylons one with an uglier skin) </li>
  <li> deployment (my second favourite chapter): paster, apache and mod_proxy,
       mod_wsgi, pound, squid, varnish, scalable deployments. </li>
</ul>
<p>Some important topics like internationalization, time zones, testing with
Selenium, and (especially) database migration (which is pretty specific for
ZODB) were not covered.</p>

<p> <strong>If you want to learn about Grok, this book will be useful</strong>,
but there's a caveat: there's the usual slew of typographical mistakes and
other errors I've come to expect from books published by Packt.  It's their
third book I've seen; all three had surprisingly high numbers of errors.  Some
had more, others had fewer.  The Grok book was on the high side and the first
one where I was tempted to record a "<a
  href="http://www.osnews.com/story/19266/WTFs_m">WTFs per page</a>" metric.
The mistakes are easy to notice and correct, so they didn't impede my
understanding of the book's content.  Disclaimer: I've been working with
Zope 3 for the last six-or-so years, so I was pretty familiar with the
underlying technologies, just not the thin Grok convenience layer. <strong>If
  minor errors annoy you, stay away.</strong>  I haven't noticed any major
factual errors, although there were what I would consider some pretty important
omissions:</p>

<ul>
  <li>ZODB is not as transparent as people tell you.  There are many gotchas,
  especially if you want to refactor your code without throwing away old
  databases.</li>
  <li><tt>bin/buildout</tt> is free to recursively remove anything under
  <tt>parts</tt>.  Keeping your database there is fine only if you don't mind
  occasionally starting from scratch.</li>
  <li><tt>repozo</tt> does not back up blobs.</li>
  <li>The ZODB transaction conflict resolution depends on being able to
  repeat requests several times; this is important if your code has external
  side effects (e.g. sends emails, creates files, pings 3rd party websites over
  HTTP).  Packages like megrok.rdb or zope.sendmail take care of this; it'd be
  nice to be shown how to do that  for your own code before you discover this
  issue the hard way when your app starts charging people's credit cards three
  times every now and then. </li>
  <li>You need to make sure you send out object events at appropriate times, or
  your catalog indexes won't be updated.</li>
  <li>Permission and role grants are persistent: if you delete a user and then
  create a new one with the same username, the new user will have all the roles
  and permissions granted to the old one.  If you implement user deletion, you
  need to explicitly remove old grants.</li>
  <li>The Zope security model expects every object to have a valid <code>__parent__</code>
  attribute; permission/role grants will not work properly on objects without a
  <code>__parent__</code>.  Most of the time this is taken care of
  automatically, but when it's not, you can get really confusing errors.</li>
  <li><code>applySkin</code> should only be used for browser requests; blindly
  calling it from a traversal event handler can break WebDAV/XML-RPC.
  (Incidentally, I should file a bug about that; it should abort if you pass a
  non-browser request instead of silently converting it into a browser
  request.)</li>
  <li>Allowing end-users to specify <code>++skin++</code> in the URL can be a
  security hole.</li>
</ul>

<p>Overall, Grok is pretty nice, especially compared to vanilla Zope 3.
However, when compared to frameworks like Pylons or Django, Grok appears more
complex and seemingly requires you to do additional work for unclear gain.  For
example, chapter 8 has you writing three components for every new form you add:
one for the form itself, one for a pagelet wrapping the form, and one for a
page containing the pagelet.  Most of that code is very similar with only the
names being different.  I'm sure there are situations where this kind of
extreme componentization pays off (e.g. it lets you override particular bits on
particular pages to satisfy a particular client's requests, without affecting
any other clients), but the book doesn't convincingly demonstrate those
advantages.  Again, I may be biased here since I've been enjoying those
advantages for the past six years, without ever having felt the pain of doing
similar customizations with a less flexible framework.  (It's a gap in my
professional experience that I'm itching to fill.) </p>

<p><strong>Update:</strong> <a
  href="http://lateral.netmanagers.com.ar/weblog/posts/BB885.html">some</a> <a
  href="http://blog.pythonisito.com/2010/03/review-grok-10-web-development.html">other</a>
<a
  href="http://www.jmcneil.net/2010/03/review-grok-1-0-web-development/">reviews</a>
on Planet Python.</p>

<p><strong>Update 2:</strong> <a
  href="http://blog.lowkster.com/2010/04/review-of-grok-web-development-10-part.html">Another
  review</a> (well, part 1 of one, but I got tired waiting for part 2).
</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-04-04T19:30:12Z</dc:date></item><item><title>Review: Python Testing: Beginner's Guide</title><guid isPermaLink="false">python-testing-review</guid><link>http://mg.pov.lt/blog/python-testing-review</link><description>I've been testing (as well as writing) Python code for the last eight years, so a book with the words ...</description><content:encoded><![CDATA[
<p>I've been testing (as well as writing) Python code for the last eight years,
so a book with the words <em>Begginer's Guide</em> prominently displayed on
the cover isn't something I'd've decided to buy for myself.  Nevertheless
I jumped at the offer of receiving a free e-copy for reviewing it.</p>

<p style="float: right; margin: 0 0 1em 1em">
<a href="http://www.packtpub.com/python-testing-beginners-guide/book/mid/1503107sqs3w?utm_source=mg.pov.lt&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_002714">
  <img src="http://mg.pov.lt/python-testing.jpg"
       width="250" height="309" alt="Python Testing: Beginner's Guide by Danien Arbuckle" />
</a>
</p>

<p><strong>Short summary:</strong> it's good book.  I learned a thing or two
from it.  I don't know well it would work as an introductionary text for
someone new to unit testing (or Python).  Some of the bits seemed
overcomplicated and underexplained, parts of the example code/tests seemed to
contain design decisions received from mysterious sources.</p>

<p>Incidentally, <a href="http://www.packtpub.com/">Packt</a> uses a simple yet
effective method for watermarking e-books: my name and street address are
displayed in the footer of every page.  What's funny is that the two non-ASCII
characters in the street name are replaced with question marks.  It's not a
data entry problem: the website that let me download those books shows my
address correctly, so it must be happening somewhere in the PDF production
process.  I didn't expect this kind of Unicode buggyness from a publisher.
Then again there were occasional strange little typographical errors in the
text, like not leaving a space in front of an opening parenthesis in an English
sentence, or using a never-seen-before <tt>+q=</tt> operator in Python code.  I
was also left wondering how the following sentence (page 225) could slip past
the editing process:</p>

<blockquote>
  doctest ignores everything between the <tt>Traceback (most recent last call)</tt>.
</blockquote>

<p>Thankfully those small mistakes did not detract from the overall message of
the book.</p>

<p>I liked the author's technique of showing subtly incorrect code, letting the
reader look at it and miss all the bugs, and then showing how unit or
integration tests catch the bugs the reader missed.  I'm pretty sure there's at
least one remaining bug that the author missed in the example package (storing
a schedule doesn't erase old data), which could serve for a new chapter on
regression testing if there's a second edition.</p>

<p>Summary of topics covered:</p>
<ul>
  <li>Terms: unit testing, integration testing, system testing.</li>
  <li>Basics of doctest and unittest, their strengths and weaknesses.</li>
  <li>Using mocks (with Mocker).</li>
  <li>Using Nose.</li>
  <li>Test-Driven Development with lots of example code.</li>
  <li>Using Twill.</li>
  <li>Integration testing with lots of example code.</li>
  <li>Using coverage</li>
  <li>Post-commit hooks to run tests with Bazaar, Mercurial, Git, Darcs,
      Subversion.</li>
  <li>Continuous integration with Buildbot</li>
</ul>

<p>I found the TDD cycle a bit larger than I generally like, but I believe it's
a matter of taste, and perhaps a shorter cycle wouldn't work as well in a
written medium.</p>

<p>I found it a bit jarring how the Twill chapter intrudes between the two
chapters showing unit testing and integration testing of the same sample
package.  I think it would've been better to swap the order of chapters 8 and
9.</p>

<p>I liked the technique presented for picking subsets of the code for
integration tests, although I wonder how well it would work on a larger
project.</p>

<p>Topics not covered:</p>
<ul>
  <li>Functional testing (which is very close but not exactly the same as
      system testing).</li>
  <li>Regression testing (page 46 contains advice about this without mentioning
      the term <em>regression testing</em>).</li>
  <li>Continuous integration with Hudson (simpler to set up than buildbot,
      easily covers 80% of cases).</li>
</ul>

<p>As you can see these holes are all rather small.</p>

<p>Probably the biggest weakness of the book is the complexity of some
things shown:
</p>

<ul>
  <li> writing mocks for pure unit tests </li>
  <li> mocking other instances of the same class under test </li>
  <li> even occasionally mocking <em>self</em>, which needs tricks like
       calling a method's <tt>im_func</tt> directly </li>
  <li> mocking <tt>__reduce_ex__</tt> so you can pickle mocks in an
       <em>integration test</em>, instead of using real classes or simple
       stubs. </li>
  <li> testing the same code multiple times: unit tests, several sets of
       integration tests that test ever-increasing subsets of classes </li>
  <li> <a href="http://buildbot.net/">Buildbot</a> instead of
       <a href="http://hudson-ci.org/">Hudson</a> </li>
</ul>

<p> Seeing the repetitive and redundant mock code in the first few doctest
examples I started asking <em>what's the point?</em>, but the book failed to
provide a compelling answer (the answer provided&mdash;it's easier to locate
bugs&mdash;works just as well for integration tests that focus on individual
classes).  And there are good answers for that question, like instant feedback
from your unit test suite.  Are they worth the additional development effort?
Maybe that depends on the developer.  I don't think they would help me, so I
tend to stick with low-level integration tests I call "unit tests" (as well as
system tests; it's always a mistake to keep all your tests in a single level).
I'm slightly worried that this book might give the wrong impression (testing is
hard) and turn away beginning Python programmers from writing tests
altogether.</p>

<p>Overall I do not feel that I have wasted my time reading <a
<a href="http://www.packtpub.com/python-testing-beginners-guide/book/mid/1503107sqs3w?utm_source=mg.pov.lt&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_002714">Python
  Testing</a>.  I look forward to reading <a
  href="http://python.genedrift.org/2010/03/03/python-testing-beginner’s-guide-review/">the</a>
<a
  href="http://www.blog.pythonlibrary.org/2010/03/06/book-review-python-testing/">other</a>
<a
  href="http://mcjeff.blogspot.com/2010/03/review-python-testing-beginners-guide.html">reviews</a>
that showed up on Planet Python.  I gathered that not all reviewers were happy
with the book, but avoided reading their reviews in order not to influence my
own.</p>

<p><strong>Update:</strong> I especially liked <a
  href="http://www.protocolostomy.com/2010/03/18/python-testing-beginners-guide-the-review/">this
  review by Brian Jones</a>.  The lack of awkward page breaks in code examples
is something that I only noticed after reading a different book, which is full
of such awkward breaks, <em>sigh</em>.</p>

<p><strong>Update 2:</strong> The book links are now affiliate links; I get a small
amount from any purchase you make through them. </p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-03-13T19:54:48Z</dc:date></item><item><title>You've got to love profiling</title><guid isPermaLink="false">you-gotta-love-profiling</guid><link>http://mg.pov.lt/blog/you-gotta-love-profiling</link><description>Yesterday I slashed 50% of run time from our applications functional test suite by modifying a single function. I had ...</description><content:encoded><![CDATA[
<p>Yesterday I slashed 50% of run time from our applications functional test
suite by modifying a single function.  I had no idea that function was
responsible for 50% of the run time until I started profiling.</p>

<p>Profiling a Python program is getting easier and easier:</p>

<blockquote>
<pre>
<span class="prompt">$</span> <span class="typing">python -m cProfile -o prof.data bin/test -f</span>
</pre>
</blockquote>

<p>runs our test runner (which is a Python script) under the <a
  href="http://docs.python.org/library/profile.html">profiler</a> and stores
the results in prof.data.</p>

<blockquote>
<pre>
<span class="prompt">$</span> <span class="typing">runsnake prof.data</span>
</pre>
</blockquote>

<p>launches the <a
  href="http://www.vrplumber.com/programming/runsnakerun/">RunSnakeRun</a>
profile viewer, which displays the results visually:</p>

<p style="text-align: center">
<a href="http://mg.pov.lt/run-snake-run.png">
<img style="border: 1px solid #ccc; padding: 6px"
     src="http://mg.pov.lt/run-snake-run-square-map.png"
     alt="RunSnakeRun square map display" width="505" height="370" />
</a>
<br/>
The square map display of RunSnakeRun, with the 'render_restructured_text'
function highlighted.
</p>

<p>Who knew that ReStructuredText rendering could be such a time waster?  A
short caching decorator and the test suite is twice as fast.  The whole
exercise took me less than an hour.  I should've done it sooner.</p>

<p>Other neat tools:</p>

<ul>
  <li><a href="http://docs.python.org/library/profile.html?highlight=pstats#pstats.Stats">pstats</a>
  from the standard library lets you load and display profiler results from the
  command line (try <tt>python -m pstats prof.data</tt>).</li>
  <li><a href="http://pypi.python.org/pypi/pyprof2calltree">pyprof2calltree</a>
  converts Python profiler data files to a format that the popular profiler
  visualization tool <a
    href="http://kcachegrind.sourceforge.net/html/Home.html">kcachegrind</a>
  can understand.  It's somewhat less useful now that RunSnakeRun exists.</li>
  <li><a href="http://pypi.python.org/pypi/profilehooks">profilehooks</a> by
  yours truly has decorators for easily profiling individual functions instead
  of entire scripts.</li>
  <li><a href="http://pypi.python.org/pypi/keas.profile">keas.profile</a> and <a
    href="http://pypi.python.org/pypi/repoze.profile">repoze.profile</a> hook
  up the profiler as WSGI middleware for easy profiling of web apps.</li>
</ul>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-03-06T18:49:00Z</dc:date></item><item><title>Bye, bye, free time!</title><guid isPermaLink="false">volunteeritis</guid><link>http://mg.pov.lt/blog/volunteeritis</link><description>Things I've taken up to do in the nearest future: Read and review Python Testing: Beginner's Guide and Grok 1.0 ...</description><content:encoded><![CDATA[
<p>Things I've taken up to do in the nearest future:</p>

<ul>
  <li>
    Read and review <a
      href="http://www.packtpub.com/python-testing-beginners-guide/book?utm_source=mg.pov.lt&amp;utm_medium=bookrev&amp;utm_content=blog&amp;utm_campaign=mdb_002640">Python
      Testing: Beginner's Guide</a> and <a
      href="http://www.packtpub.com/grok-1-0-web-development/book?utm_source=mg.pov.lt&amp;utm_medium=bookrev&amp;utm_content=blog&amp;utm_campaign=mdb_002639">Grok
      1.0 Web Development</a> for Packt.  (The links are trackable to my blog,
    but I'm not getting anything out of it.  Other than free copies of the
    e-books, which I already received, in exchange for a promise to review them
    on this blog.)
  </li>
  <li>
    Help <a href="http://www.reportlab.com/software/opensource/">Reportlab</a>
    folks set up continuous integration (most likely <a
      href="http://hudson-ci.org/">Hudson</a>, since <a
      href="http://buildbot.net/">Buildbot</a>, while powerful, has a steep
    learning curve).
  </li>
  <li>
    Think about becoming the buildbotmaster for Zope.  Originally I intended
    to volunteer to set up a few buildbots for various Zopeish projects
    (ZTK, BlueBream, Grok, Zope 2) since half of the <a
      href="http://docs.zope.org/zopetoolkit/process/buildbots.html">existing
      ones</a> were down or broken.  Then various people fixed some of the
    broken ones and other people chimed in mentioning existing buildbots that
    nobody else knew about.  There is a need for somebody to coordinate all
    this activity: make sure we have up-to-date test results for all kinds of
    projects, aggregate them in one place, chase up build slaves for exotic
    OSes (i.e. Windows)...  I don't think I'm well suited for this kind of
    organisational activity.
  </li>
  <li>
    Push along the various scratch-my-itch open source projects (<a
      href="http://mg.pov.lt/gtimelog/">GTimeLog</a>, <a
      href="http://mg.pov.lt/irclog2html/">irclog2html</a>,
      <a href="https://launchpad.net/zodbbrowser">zodbbrowser</a>).
  </li>
  <li>
    No idea what, but I've been wanting to do something for <a
      href="http://maemo.org/">Maemo</a>.  Something small, given the copious
    amounts of free time I have.
  </li>
  <li>
    Then there's the paying work.  On the plus side, there are opportunities
    for fun there (today I slashed functional test run time by a half, by
    adding a small caching decorator in front of a single function.
    <a href="http://www.vrplumber.com/programming/runsnakerun/">RunSnakeRun</a>
    and <a href="http://docs.python.org/library/profile.html">cProfile</a>
    rule!)
  </li>
  <li>
    You know what, scratch the Zope buildbotmaster idea.  Maybe I can do
    something technical there, e.g. a cron script to ping the various buildbot,
    scrape HTML/parse emails and aggregate build results.  Maybe.
  </li>
  <li>
    I hope I don't get <a
      href="http://en.wikipedia.org/wiki/Burnout_(psychology)">burnout</a>
    again.  Because that would suck.  Again.  Been there, done that, didn't
    even get a T-shirt.
  </li>
</ul>

<p>I really ought to read Getting Things Done.  Reading it has been on my
todo-list for <em>years</em>.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-03-05T21:02:41Z</dc:date></item><item><title>Weekly Zope developer IRC meetings</title><guid isPermaLink="false">weekly-zope-irc-meeting</guid><link>http://mg.pov.lt/blog/weekly-zope-irc-meeting</link><description>On Tuesday we started what will hopefully become a tradition: weekly IRC meetings for Zope developers. Topics covered include buildbot ...</description><content:encoded><![CDATA[
<p>On Tuesday we started what will hopefully become a tradition: weekly IRC
meetings for Zope developers.  Topics covered include buildbot organization and
maintenance, open issues with the ZTK development process, and the fate of Zope
3.5 (= BlueBream 1.0).</p>

<p>There are <a
  href="http://zope3.pov.lt/irclogs-zope/%23zope.2010-03-02.log.html#t2010-02-02T16:59:56"
  >IRC logs</a> of the meeting, and Christian Theune posted a <a
  href="https://mail.zope.org/pipermail/zope-dev/2010-March/039642.html">summary</a>
  to the mailing list.</p>

<p>My take on this can be summed up as: Zope ain't dead yet!  The project has
fragmented a bit (Zope 2, Zope Toolkit, Grok, BlueBream, Repoze), but we all
share a set of core packages and we want to keep them healthy.</p>

<p>Next meeting is also happening on a Tuesday, at 15:00 UTC on #zope in
FreeNode.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-03-03T11:09:23Z</dc:date></item><item><title>Latin-1 or Windows-1252?</title><guid isPermaLink="false">latin1-or-cp1252</guid><link>http://mg.pov.lt/blog/latin1-or-cp1252</link><description>Michael Foord wrote about some Latin-1 control character fun in a blog that's hard to read (the RSS feed syndicated ...</description><content:encoded><![CDATA[
<p>Michael Foord wrote about <a
  href="http://www.voidspace.org.uk/python/weblog/arch_d7_2010_01_02.shtml#e1147">some
  Latin-1 control character fun</a> in a blog that's hard to read (the RSS feed
syndicated on Planet Python is truncated, grr!) and hard to reply (<del>no comments
  on the blog!</del> my Chromium's AdBlock+ hid the comment link so I couldn't
find it), but never mind that.</p>

<blockquote>
  <q>Unfortunately the data from the customers included some \x85 characters,
    which were breaking the CSV parsing.</q>
</blockquote>

<p>0x85 is a control character (NEXT LINE or NEL) in Latin-1, but it's a
printable character (HORIZONTAL ELLIPSIS) in Microsoft's code page 1252, which
is often mistaken for Latin-1.  I would venture a suggestion that the encoding
of the customer data was not latin-1 but rather cp1252.</p>

<blockquote><pre>
<span class="prompt">&gt;&gt;&gt;</span> <span class="string">'<span class="escape">\x85</span>'</span>.<span class="name">decode</span>(<span class="string">'cp1252'</span>)
<span class="string">u'<span class="escape">\u2026</span>'</span>
</pre></blockquote>

]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2010-01-07T21:29:39Z</dc:date></item><item><title>GTimeLog: not dead yet!</title><guid isPermaLink="false">gtimelog-not-dead-yet</guid><link>http://mg.pov.lt/blog/gtimelog-not-dead-yet</link><description>Back in 2004 I wrote a small Gtk+ app to help me keep track of my time, and called it ...</description><content:encoded><![CDATA[
<p>Back in 2004 I wrote a small Gtk+ app to help me keep track of my time, and
called it <a href="http://mg.pov.lt/gtimelog/">GTimeLog</a>.  I shared it with
my coworkers, put it on the web (on the general "release early, release often"
principles), and it got sort-of popular before I found the time to polish it
into a state where I wouldn't be ashamed to show it to other people.</p>

<p>Fast-forward to 2008: there are actual users out there (much to my
surprise), I still haven't added the originally-envisioned spit and polish,
haven't done anything to foster a development community, am wracked by guilt of
not doing my maintainerly duties properly, which leads to depression and
burnout.  So I do the only thing I can think of: run away from the project and
basically ignore its existence for a year.  Unreviewed patches accumulate in my
inbox.</p>

<p>It seems that the sabbatical helped: yesterday, triggered by a <a
  href="http://bugs.debian.org/560981">new Debian bug report</a>, I sat down,
fixed the <a href="">bug</a>, implemented a <a
  href="https://bugs.launchpad.net/gtimelog/+bug/308750">feature</a>, applied a
<a href="https://bugs.launchpad.net/gtimelog/+bug/328118">couple</a> of <a
  href="https://bugs.launchpad.net/gtimelog/+bug/255618">patches</a>
languishing in the bug tracker, and <a
  href="http://pypi.python.org/pypi/gtimelog">released version 0.3</a> (which
was totally broken thanks to setuptools magic that suddenly stopped
working; so released 0.3.1 just now).  Then went through my old unread email,
created <a
  href="https://bugs.launchpad.net/gtimelog">bugs in Launchpad</a> and sent
replies to everyone.  Except <a href="http://blog.pierlux.com/en/">Pierre-Luc
  Beaudoin</a>, since his @collabora.co.uk email address bounced.  If anyone
knows how to contact him, I'd appreciate a note.</p>

<p><img src="http://mg.pov.lt/gtimelog-about-dialog.png"
        alt="version is now shown in the about dialog" /></p>

<p>There are also some older changes that I made before I emerged out of the
funk and so hadn't widely announced:</p>

<ul>
  <li>
    There's a <a href="http://groups.google.com/group/gtimelog">mailing
      list</a> for user and developer discussions (if there still are any ;).
  </li>
  <li>
    GTimeLog's <a href="https://code.launchpad.net/gtimelog">source code</a>
    now lives on Launchpad (actually, I <a
      href="http://mg.pov.lt/blog/happenings.html">mentioned</a> this on my
    blog once).
  </li>
</ul>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-12-17T23:22:52Z</dc:date></item><item><title>Unix is an IDE, or my Vim plugins</title><guid isPermaLink="false">unix-is-an-ide</guid><link>http://mg.pov.lt/blog/unix-is-an-ide</link><description>Unix is an IDE . I do my development (Python web apps mostly) with Vim with a bunch of custom ...</description><content:encoded><![CDATA[
<p><a href="http://c2.com/cgi/wiki?UnixIsAnIde">Unix is an IDE</a>.  I do my
development (Python web apps mostly) with <a href="http://www.vim.org/">Vim</a>
with a <a href="http://mg.pov.lt/vim/">bunch of custom plugins</a>, shell
(in GNOME Terminal: tabs rule!), GNU make, ctags, find + grep,
svn/bzr/hg/git.</p>

<p>The current working directory is my project configuration/state.  I run
tests here (bin/test), I search for code here (vim -t TagName, find + grep), I
run applications here (make run or bin/<em>appname</em>).  I can multitask
freely, for example, if I'm in the middle of typing an SVN commit message, I
can hit Ctrl+Shift+T, get a new terminal tab in the same working directory, and
look something up.  No aliases/environment variables/symlinks<!--
/<a
  href="http://blog.doughellmann.com/2009/12/switching-development-contexts-with.html">scripts
making changes to config files</a>
(I've no idea why I assumed those; I must've misread something in Doug's post)
-->.  I can work on multiple projects at the
same time.  I can work remotely (over ssh).</p>

<p><a href="http://vimeo.com/user1043515">Gary Bernhardt's screencasts on
  Vimeo</a> show how productive you can get if you learn Vim and tailor it
to your needs.  I have Vim scripts that let me</p>

<ul>
  <li>
    See the name of the class and function that I'm editing in the statusbar,
    even if the class/function definition is offscreen:
    <a href="http://mg.pov.lt/vim/plugin/pythonhelper.vim">pythonhelper.vim</a>.
  </li>
  <li>
    See all pyflakes warnings and errors in a list as soon as I press F2 to
    save the file: <a
      href="http://mg.pov.lt/vim/plugin/python_check_syntax.vim">python_check_syntax.vim</a>.
  </li>
  <li>
    Add a "from foo.bar import Something" line at the top of the file if I
    press F5 when my cursor is on Something, looking up the package and module
    from ctags: <a
      href="http://mg.pov.lt/vim/plugin/python-imports.vim">python-imports.vim</a>.
  </li>
  <li>
    Switch between production code and unit tests with a single key if the
    project uses one of several conventions for tests (e.g. ./foo.py
    <tt>&lt;-&gt;</tt> ./tests/test_foo.py):
    <a href="http://mg.pov.lt/vim/plugin/py-test-switcher.vim">py-test-switcher.vim</a>.
  </li>
  <li>
    Generate a command line for running one particular unit test (the one
    my cursor is inside) and copy it into the system clipboard, so I can
    run that test by Alt-Tabbing into my terminal window and pasting.
    <a href="http://mg.pov.lt/vim/plugin/py-test-runner.vim">py-test-runner.vim</a>.
  </li>
  <li>
    Open the right file and move the cursor to the right line if I
    triple-click a line of traceback in a shell (or an email) then press F7 in
    my gvim window:
    <a href="http://mg.pov.lt/vim/plugin/py-test-locator.vim">py-test-locator.vim</a>.
  </li>
  <li>
    Compare my version of the code with the pristine version in source control
    in an interactive side-by-side diff that lets me revert bits I no longer
    want:
    <a href="http://mg.pov.lt/vim/plugin/vcscommand.vim">vcscommand.vim</a>.
  </li>
  <li>
    Highlight which lines of the source are covered by my tests, if I have
    coverage information in trace.py format:
    <a href="http://mg.pov.lt/vim/plugin/py-coverage-highlight.vim">py-coverage-highlight.vim</a>.
  </li>
  <li>
    Show the signature of a function/class's __init__ when I type the name
    of that class/function and an open parenthesis (looked up from tags):
    <a href="http://mg.pov.lt/vim/plugin/py-function-signature.vim">py-function-signature.vim</a>.
  </li>
  <li>
    Fold code into an outline so I only see names of methods or classes
    instead of their full bodies:
    <a href="http://mg.pov.lt/vim/vimrc">vimrc</a>, function PythonFoldLevel.
  </li>
  <li>
    Fold diff files so I can see whole hunks/files and can delete those with
    a single key (well, two keys -- dd).  Useful for reviewing <em>large</em>
    diffs (tens of thousands of lines):
    <a href="http://mg.pov.lt/vim/vimrc">vimrc</a>, function DiffFoldLevel.
  </li>
</ul>

<p>Some of these come from <a href="http://www.vim.org">www.vim.org</a>, some
I've written myself, some I've taken and modified a little bit to avoid an
irritating quirk or add a missing feature.  Some things I don't have (and envy
Emacs or IDE users for having -- like an integrated debugger for Python apps,
and, generally, integration with other tools, running in the background).</p>

<p>It's been my plan for a long time to polish my plugins, release them
somewhere (github?  bitbucket? launchpad?) and upload to vim.org, but as it
doesn't seem to be happening, I thought I'd at least put an <a
  href="http://mg.pov.lt/vim">svn
  export of my ~/.vim</a> on the web.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-12-08T23:23:53Z</dc:date></item><item><title>Displaying multiline text in Zope 3</title><guid isPermaLink="false">zope3-displaying-multiline-text</guid><link>http://mg.pov.lt/blog/zope3-displaying-multiline-text</link><description>zope.schema has Text and TextLine. The former is for multiline text, the latter is for a single line, as the ...</description><content:encoded><![CDATA[
<p>zope.schema has Text and TextLine.  The former is for multiline text, the
latter is for a single line, as the name suggests.  Zope 3 forms will use a
text area for Text fields and an input box for TextLine fields.  Display
widgets, however, apply no special formatting (other than HTML-quoting of
characters like &lt;, &gt; and &amp;), and since newlines are treated the same
way as spaces in HTML, your multiline text gets collapsed into a single
paragraph.</p>

<p>Here's a pattern I've been using in Zope 3 to display multiline user-entered
text as several paragraphs:</p>

<blockquote>
<pre>
<span class="keyword">import</span> <span class="name">cgi</span>

<span class="keyword">from</span> <span class="name">zope</span>.<span class="name">component</span> <span class="keyword">import</span> <span class="name">adapts</span>
<span class="keyword">from</span> <span class="name">zope</span>.<span class="name">publisher</span>.<span class="name">browser</span> <span class="keyword">import</span> <span class="name">BrowserView</span>
<span class="keyword">from</span> <span class="name">zope</span>.<span class="name">publisher</span>.<span class="name">interfaces</span> <span class="keyword">import</span> <span class="name">IRequest</span>


<span class="def">class</span> <span class="name">SplitToParagraphsView</span>(<span class="name">BrowserView</span>):
    <span class="string">"""Splits a string into paragraphs via newlines."""</span>

    <span class="name">adapts</span>(<span class="name">None</span>, <span class="name">IRequest</span>)

    <span class="def">def</span> <span class="name">paragraphs</span>(<span class="name">self</span>):
        <span class="keyword">if</span> <span class="name">self</span>.<span class="name">context</span> <span class="keyword">is</span> <span class="name">None</span>:
            <span class="keyword">return</span> []
        <span class="keyword">return</span> <span class="name">filter</span>(<span class="name">None</span>, [<span class="name">s</span>.<span class="name">strip</span>() <span class="keyword">for</span> <span class="name">s</span> <span class="keyword">in</span> <span class="name">self</span>.<span class="name">context</span>.<span class="name">splitlines</span>()])

    <span class="def">def</span> <span class="name">__call__</span>(<span class="name">self</span>):
        <span class="keyword">return</span> <span class="string">""</span>.<span class="name">join</span>(<span class="string">'&lt;p&gt;%s&lt;/p&gt;<span class="escape">\n</span>'</span> % <span class="name">cgi</span>.<span class="name">escape</span>(<span class="name">p</span>)
                        <span class="keyword">for</span> <span class="name">p</span> <span class="keyword">in</span> <span class="name">self</span>.<span class="name">paragraphs</span>())
</pre>
</blockquote>

<p>View registration</p>

<blockquote>
<pre>
&lt;<span class="def">configure</span>
    xmlns="http://namespaces.zope.org/zope"&gt;

  &lt;<span class="def">view</span>
      <span class="name">for</span>=<span class="string">"*"</span>
      <span class="name">name</span>=<span class="string">"paragraphs"</span>
      <span class="name">type</span>=<span class="string">"zope.publisher.interfaces.browser.IBrowserRequest"</span>
      <span class="name">factory</span>=<span class="string">".views.SplitToParagraphsView"</span>
      <span class="name">permission</span>=<span class="string">"zope.Public"</span>
      /&gt;

&lt;/<span class="def">configure</span>&gt;
</pre>
</blockquote>

<p>and usage</p>

<blockquote>
<pre>
&lt;<span class="def">p</span> <span class="name">tal</span>:<span class="name">replace</span>=<span class="string">"structure object/attribute/@@paragraphs"</span> /&gt;
</pre>
</blockquote>

<p><strong>Update:</strong>  The view really ought to be registered twice: once
for basestring and once for NoneType.  I was too lazy to figure out the dotted
names for those (or check if zope.interface has external interface declarations
for them), so I registered it for "*".  You should know that this makes the
view available for arbitrary objects (but won't work for most of them, since
they don't have a splitlines method), and that it is, sadly, accessible to
users who may try to hack your system by typing things like @@paragraphs in the
browser's address bar.  Ignas Mikalajūnas offers an <a
  href="http://blog.pow.lt/2009/12/02/formatting-and-processing-text-in-tal-templates/">alternative
  solution using TALES path adapters</a>.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-12-01T18:52:03Z</dc:date></item><item><title>Pylons and SQL schema migration</title><guid isPermaLink="false">pylons-and-sql-migration</guid><link>http://mg.pov.lt/blog/pylons-and-sql-migration</link><description>I'm at the point in my hobby project where I'd like to be able to change my models without losing ...</description><content:encoded><![CDATA[
<p>I'm at the point in my hobby project where I'd like to be able to change my models
without losing all my test data.  And I'm too lazy to do manual dumps and edit
the SQL in place before reimporting it.</p>

<p>I want a system</p>

<ul>
  <li>that is <em>transparent</em> to the user: if my database is at schema
      version 1, and my code is at version 3, I want it to be automatically
      upgraded to version 3 on server startup.</li>
  <li>that is <em>not too hard</em> on the programmer: dropping a numbered Python or SQL
      script in a directory ought to be sufficient to define a transition from
      schema version X to schema version X+1.</li>
  <li>that <em>handles errors gracefully</em>: makes a backup of the database
      with the old schema version; runs my script in a transaction and aborts
      that transaction if the conversion fails (while showing me enough
      information to debug the problem).</li>
  <li><em>allows prototyping</em> without having to increment the schema number for every
      little change I make to the models; I should be the one who decides that a new
      schema is ready to go out to the world.</li>
</ul>

<p>I've been glancing at <a
  href="http://code.google.com/p/sqlalchemy-migrate/">SQLAlchemy-Migrate</a>, since I've
been brought up to believe <abbr title="Not Invented Here">NIH</abbr>ing is
Bad.  But Migrate is <em>scary</em>.  I have to admit that the longer I stare
at its documentation, the less I can describe <em>why</em> I think so.  All
those shell commands&mdash;but there's an API for invoking them from Python, so maybe I can
achieve my goals.  I'll have to try and see.
</p>

]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-09-21T16:44:14Z</dc:date></item><item><title>Pylons with zc.buildout, continued</title><guid isPermaLink="false">pylons-with-buildout-2</guid><link>http://mg.pov.lt/blog/pylons-with-buildout-2</link><description>Last time I mentioned that running bin/buildout with the -N flag makes it run faster (since it skips looking for ...</description><content:encoded><![CDATA[
<p><a href="http://mg.pov.lt/blog/pylons-with-buildout.html">Last time</a> I
mentioned that running bin/buildout with the -N flag makes it run faster
(since it skips looking for newer versions to upgrade).  You can tell
buildout to do this by default by putting 'newest = false' into the [buildout]
section of buildout.cfg. We'll be running bin/buildout a lot now, since we'll
be making changes to the project environment, so this will save wear and tear
on the '-', 'N' and Shift keys.  (And, by the way, I'm not trying to soak up
Google juice by repeating the word 'buildout' a lot, honest!)</p>

<p>I will omit bzr commits from this narrative as it's getting long; you can
assume that every self-contained change was committed separately.</p>

<h4>tests</h4>

<p>  First, I want a bin/test script to run the test
suite.  Pylons uses nose, so we need to tell buildout to install the nosetests
script (under a different name, since I'm used to typing bin/test no matter
what test runner a project happens to use):</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr diff</span>
=== modified file 'buildout.cfg'
--- buildout.cfg	2009-09-15 19:49:11 +0000
+++ buildout.cfg	2009-09-15 19:49:18 +0000
@@ -8,5 +8,8 @@
 recipe = zc.recipe.egg
 eggs = Pylons
        PasteScript
+       nose
        asharing
 interpreter = python
+scripts = paster
+          nosetests=test

<span class="prompt">$</span> <span class="typing">bin/buildout</span>
...
Generated script '/tmp/AlliterationSharing/bin/paster'.
Generated script '/tmp/AlliterationSharing/bin/test'.
...
<span class="prompt">$</span> <span class="typing">bin/test</span>

----------------------------------------------------------------------
Ran 0 tests in 0.276s

OK
</pre>

<h4>ctags</h4>

<p>Documentation is good, but sometimes you want to look at the source code of
the framework.  There's a tool called <a
  href="http://ctags.sourceforge.net/">ctags</a> that builds a database of
identifiers.  The popular text editors <a href="http://www.vim.org/">Vim</a>
and <a href="http://www.gnu.org/software/emacs/">Emacs</a> can then use the
tags database to jump to a definition of any name with a single keystroke
(Ctrl-] in vim, M-. in emacs).</p>

<p>Building the tags database is complicated by each Python package being
installed into a separate directory.  There's a buildout recipe called
z3c.recipe.tag that finds those directories and lets you build a unified tags
file.  We'll also ask buildout to make sure it <em>unzips</em> any packages
distributed as .egg files, since ctags doesn't process those:</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr diff</span>
@@ -1,8 +1,9 @@
 [buildout]
 develop = .
-parts = pylons
+parts = pylons ctags
 
 newest = false
+unzip = true
 
 [pylons]
 recipe = zc.recipe.egg
@@ -13,3 +14,7 @@
 interpreter = python
 scripts = paster
           nosetests=test
+
+[ctags]
+recipe = z3c.recipe.tag:tags
+eggs = ${pylons:eggs}

<span class="prompt">$</span> <span class="typing">bin/buildout</span>
...
Generated script '/tmp/AlliterationSharing/bin/ctags'.
...
<span class="prompt">$</span> <span class="typing">bin/ctags</span>
</pre>

<h4>omelette</h4>

<p>ctags lets you find classes and functions by name; it doesn't let you find
packages or modules.  There's another recipe, collective.recipe.omelette that
creates a tree of symlinks mirroring the Python package structure (here
'unzip = true' also comes in handy):</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr diff</span>
=== modified file 'buildout.cfg'
--- buildout.cfg	2009-09-15 20:04:42 +0000
+++ buildout.cfg	2009-09-15 20:05:30 +0000
@@ -1,6 +1,6 @@
 [buildout]
 develop = .
-parts = pylons ctags
+parts = pylons ctags omelette
 
 newest = false
 unzip = true
@@ -18,3 +18,7 @@
 [ctags]
 recipe = z3c.recipe.tag:tags
 eggs = ${pylons:eggs}
+
+[omelette]
+recipe = collective.recipe.omelette
+eggs = ${pylons:eggs}

<span class="prompt">$</span> <span class="typing">bin/buildout </span>
...
<span class="prompt">$</span> <span class="typing">ls -l parts/omelette</span>
...
</pre>

<p>The symlink tree is created under parts/omelette/.  For example, if you want
to see what webhelper tags were available, you can open
parts/omelette/webhelper/html/builder.py in your editor and see.

<h4>Makefile</h4>

<p>This is getting long (and not everyone may be interested<sup
  class="footnote">1</sup>), but one long post is easier to skip than five
medium ones in a row, so I'll continue.</p>

<blockquote class="footnotes">
  <p><sup>1</sup> Sorry, <a href="http://maemo.org/news/planet-maemo/">Planet
    Maemo</a>!  There's an <a
    href="http://mg.pov.lt/blog/tag/maemo/index.rss">RSS feed of posts tagged
    'maemo'</a>, if you can figure out the URL, which is very well hidden by
    PyBlosxom, *sigh*.
  </p>
</blockquote>

<p>Wouldn't it be nice if new developers could check out your project and start
it up with just a couple of commands?  Make is a time-tested tool that works
well for this:</p>

<pre>
<span class="prompt">$</span> <span class="typing">cat Makefile</span>
# Just remember that you need to use real tabs, not spaces, in a Makefile

PYTHON = python

.PHONY: all
all: bin/paster

.PHONY: run
run: bin/paster
        bin/paster serve development.ini --reload

.PHONY: test check
test check: bin/test
        bin/test

.PHONY: tags
tags: bin/ctags
        bin/ctags

bin/paster bin/test bin/python bin/ctags: bin/buildout
        bin/buildout

bin/buildout: bootstrap.py
        $(PYTHON) bootstrap.py
</pre>

<p>Now all you need to do after checking out is run 'make' to set up a working
development environment.  'make run' or 'make test' will also do that, if
necessary, so this one-liner is sufficient to get a working Hello World
application on port 5000:</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr branch lp:~mgedmin/+junk/AlliterationSharing &amp;&amp; cd AlliterationSharing &amp;&amp; make run</span>
</pre>

<p>Try it!  You'll get a Bazaar branch with all the history of this little
blog project.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-09-15T20:31:53Z</dc:date></item><item><title>Starting a Pylons project with zc.buildout</title><guid isPermaLink="false">pylons-with-buildout</guid><link>http://mg.pov.lt/blog/pylons-with-buildout</link><description>For software development I prefer buildout to virtualenv . This is because buildout has a text file describing the state ...</description><content:encoded><![CDATA[
<p>For software development I prefer <a
  href="http://www.buildout.org">buildout</a> to <a
  href="http://pypi.python.org/pypi/virtualenv">virtualenv</a>.  This is
because buildout has a text file describing the state of your working
environent, which can be versioned and used later to recreate it, as well
as during development to modify the environment slightly.</p>

<p>To start a new Pylons project, first create an empty directory.  Let's
call our new project AlliterationSharing<sup
class="footnote">1</sup>, because everybody is sick of 'foo'
and 'bar'.</p>

<blockquote class="footnotes">
<p><sup>1</sup> Generated by randomly picking two words from
/usr/share/dict/words, then chosen over among 120 other variants that weren't
as good.</p>
</blockquote>

<pre>
<span class="prompt">$</span> <span class="typing">mkdir -p ~/src/AlliterationSharing</span>
<span class="prompt">$</span> <span class="typing">cd ~/src/AlliterationSharing</span>
</pre>

<p>Now create a file called buildout.cfg with the following content:</p>

<pre>
<span class="prompt">$</span> <span class="typing">cat buildout.cfg</span>
[buildout]
parts = pylons

[pylons]
recipe = zc.recipe.egg
eggs = Pylons
       PasteScript
interpreter = python
</pre>

<p>Download <a
  href="http://svn.zope.org/zc.buildout/trunk/bootstrap/">bootstrap.py</a> to
it and run it to get bin/buildout.  Note: you can chose which Python version you
want to use by running bootstrap.py with it.  All other scripts under bin/
will be generated by buildout and will use the same Python interpreter.</p>

<pre>
<span class="prompt">$</span> <span class="typing">wget http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py</span>
<span class="prompt">$</span> <span class="typing">python bootstrap.py</span>
Creating directory '.../AlliterationSharing/bin'.
Creating directory '.../AlliterationSharing/parts'.
Creating directory '.../AlliterationSharing/eggs'.
Creating directory '.../AlliterationSharing/develop-eggs'.
Generated script '.../AlliterationSharing/bin/buildout'.
</pre>

<p>Run bin/buildout to install Pylons into your sandbox.</p>

<pre>
<span class="prompt">$</span> <span class="typing">bin/buildout</span>
Installing pylons.
Generated script '.../AlliterationSharing/bin/paster'.
Generated interpreter '.../AlliterationSharing/bin/python'.
</pre>

<p>Aside: buildout has this very nice feature where it can share Python
packages between projects.  This will save you enormous amounts of time that
would otherwise be spent downloading and unpacking eggs.  To make use of this
facility, create a file ~/.buildout/default.cfg with</p>
<pre>
<span class="prompt">$</span> <span class="typing">cat ~/.buildout/default.cfg </span>
[buildout]
eggs-directory = /home/mg/tmp/buildout-eggs
# XXX replace /home/mg with the full path of *your* home directory
# it would be much nicer if buildout let me use ~ or $HOME
# see <a href="https://bugs.launchpad.net/zc.buildout/+bug/190260">https://bugs.launchpad.net/zc.buildout/+bug/190260</a>
</pre>

<p>Another useful trick is to pass the -N flag to bin/buildout, which will tell
it not to bother looking for newer versions of packages on the Internet when
there's already an existing version installed in your eggs directory.</p>

<p>Back to business: now you've got two new scripts: bin/python and bin/paster.
You can use the first one to play with the interactive Python console where you
can now import pylons and all the dependencies; it has no other value.</p>

<p>Now is a good point to add the files you've created into a version control
system.  I'll arbitrarily use Bazaar.</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr init .</span>
<span class="prompt">$</span> <span class="typing">bzr add bootstrap.py buildout.cfg</span>
<span class="prompt">$</span> <span class="typing">bzr ignore bin parts eggs develop-eggs .installed.cfg</span>
<span class="prompt">$</span> <span class="typing">bzr commit -m "Create AlliterationSharing project"</span>
</pre>

<p>Run bin/paster create -t pylons to create a skeleton project.</p>

<pre>
<span class="prompt">$</span> <span class="typing">bin/paster create -t pylons asharing</span>
<span class="prompt">$</span> <span class="typing">bzr ignore *.egg-info</span>
<span class="prompt">$</span> <span class="typing">bzr add asharing</span>
<span class="prompt">$</span> <span class="typing">bzr commit -m "Generated project files with paster create"</span>
</pre>

<p>Now paster creates a directory structure that I don't like:</p>

<pre>
AlliterationSharing/
  buildout.cfg
  bin/
  asharing/
    setup.py
    README.txt
    MANIFEST.in
    asharing/
      __init__.py
      config/
      controllers/
      templates/
      public/
</pre>

<p>I'd like the README and setup.py to be in the top level, and I dislike
repeating 'asharing' twice in directory names.  I'll move some files around</p>

<pre>
<span class="prompt">$</span> <span class="typing">cd asharing/</span>
<span class="prompt">$</span> <span class="typing">bzr mv development.ini docs MANIFEST.in README.txt setup.* test.ini ../</span>
<span class="prompt">$</span> <span class="typing">bzr rm ez_setup.*</span>
<span class="prompt">$</span> <span class="typing">cd ..</span>
<span class="prompt">$</span> <span class="typing">bzr mv asharing src</span>
<span class="prompt">$</span> <span class="typing">bzr ci -m "Moved some files around"</span>
</pre>

<p>Now the tree looks like this:</p>

<pre>
AlliterationSharing/
  buildout.cfg
  setup.py
  README.txt
  MANIFEST.in
  bin/
  src/
    asharing/
      __init__.py
      config/
      controllers/
      templates/
      public/
</pre>

<p>We have to tell setup.py where to find the source tree</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr diff</span>
=== modified file 'MANIFEST.in'
--- MANIFEST.in	2009-09-13 13:04:00 +0000
+++ MANIFEST.in	2009-09-13 13:05:59 +0000
@@ -1,3 +1,3 @@
-include asharing/config/deployment.ini_tmpl
-recursive-include asharing/public *
-recursive-include asharing/templates *
+include src/asharing/config/deployment.ini_tmpl
+recursive-include src/asharing/public *
+recursive-include src/asharing/templates *

=== modified file 'setup.py'
--- setup.py	2009-09-13 13:04:00 +0000
+++ setup.py	2009-09-13 13:04:40 +0000
@@ -17,7 +17,8 @@
         "SQLAlchemy&gt;=0.5",
     ],
     setup_requires=["PasteScript&gt;=1.6.3"],
-    packages=find_packages(exclude=['ez_setup']),
+    packages=find_packages('src', exclude=['ez_setup']),
+    package_dir={'': 'src'},
     include_package_data=True,
     test_suite='nose.collector',
     package_data={'asharing': ['i18n/*/LC_MESSAGES/*.mo']},
</pre>

<p>(I'm not sure if you also need to change package_data and/or setup.cfg; it's
possible that I left i18n in a broken state.  Can somebody comment on
this?)</p>

<p>And we have to tell buildout that we've got a new Python package to enable
in the project environment</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr diff buildout.cfg </span>
=== modified file 'buildout.cfg'
--- buildout.cfg	2009-09-13 12:57:21 +0000
+++ buildout.cfg	2009-09-13 13:08:05 +0000
@@ -1,8 +1,10 @@
 [buildout]
+develop = .
 parts = pylons
 
 [pylons]
 recipe = zc.recipe.egg
 eggs = Pylons
        PasteScript
+       asharing
 interpreter = python
</pre>

<p>Now you can re-run bin/buildout and start your hello-world project</p>

<pre>
<span class="prompt">$</span> <span class="typing">bzr commit -m "Include the new package in the build"</span>
<span class="prompt">$</span> <span class="typing">bin/buildout -N</span>
<span class="prompt">$</span> <span class="typing">bin/paster serve --reload development.ini</span>
</pre>

<p>Happy hacking!</p>

<p>To be continued: <a href="http://mg.pov.lt/blog/pylons-with-buildout-2.html">telling buildbot to create bin/test; using ctags and omelette</a>.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-09-13T13:13:14Z</dc:date></item><item><title>Local changes to buildout.cfg</title><guid isPermaLink="false">buildout-local-config</guid><link>http://mg.pov.lt/blog/buildout-local-config</link><description>Most of Python packages in the Zope world use Buildout : svn co svn+ssh://svn.zope.org/repos/main/plone.z3cform/trunk plone.z3cform cd plone.z3cform python2.4 bootstrap.py bin/buildout ...</description><content:encoded><![CDATA[
<p>Most of Python packages in the Zope world use <a
  href="http://www.buildout.org">Buildout</a>:</p>

<pre>
svn co svn+ssh://svn.zope.org/repos/main/plone.z3cform/trunk plone.z3cform
cd plone.z3cform
python2.4 bootstrap.py
bin/buildout
bin/test -pvc
</pre>

<p>Now suppose you want to change the buildout environment somehow, e.g.
use the current development version of zope.testing instead of whatever is
specified in buildout.cfg.  Don't edit the existing buildout.cfg (you might
accidentally commit your local debug changes), instead create a new cfg file,
e.g. test.cfg:
</p>

<pre>
[buildout]
extends = buildout.cfg
develop += ../zope.testing

[versions]
# override any existing version pins
zope.testing =
</pre>

<p>Now re-run buildout</p>

<pre>
bin/buildout -c test.cfg
bin/test -pvc
</pre>

<p>And the tests should be run with the newest zope.testing.code.</p>

<p>Only this does not work with plone.z3cform, and I have no clue why.
It generally works with other packages (at least those that use the
zc.recipe.testrunner rather than collective.recipe.z2testrunner).
Buildout is like that sometimes :(</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-08-03T17:26:22Z</dc:date></item><item><title>Python-related updates for the last couple of months</title><guid isPermaLink="false">happenings</guid><link>http://mg.pov.lt/blog/happenings</link><description>Went to EuroPython , met new people, had a great time. Updated gtkeggdeps , the interactive Python package dependency browser. ...</description><content:encoded><![CDATA[
<p>Went to <a href="http://www.europython.eu/">EuroPython</a>, met new people,
had a great time.</p>

<p>Updated <a href="https://launchpad.net/gtkeggdeps">gtkeggdeps</a>, the
interactive Python package dependency browser.  Collaborated with <a
  href="http://thomas-lotze.de/en/">Thomas Lotze</a>, who maintains the engine
(<a href="http://pypi.python.org/pypi/tl.eggdeps">tl.eggdeps</a>) that
gtkeggdeps wraps, to resolve API mismatches.  Moved the sources to <a
  href="https://code.launchpad.net/gtkeggdeps">launchpad.net</a>, added a test
suite, made it use <a href="http://buildout.org">zc.buildout</a> for convenient
development.</p>

<p>Moved the source repository of <a
  href="https://launchpad.net/gtimelog">gtimelog</a>, the simple desktop time
tracker, to <a href="https://code.launchpad.net/gtimelog">launchpad.net</a>.
Failed to do anything else with it.  <tt>:-(</tt></p>

<p>Tried to work on <a
  href="http://code.google.com/p/jrfonseca/wiki/XDot">xdot</a>, wrestled with
git-svn merges, failed abysmally.  <a
  href="http://code.google.com/p/jrfonseca/issues/detail?id=19">Asked
  upstream</a> to upload xdot to <a
  href="http://pypi.python.org/pypi">PyPI</a>.</a>

<p>Released <a href="https://launchpad.net/zodbbrowser">ZODB Browser</a>, but
this deserves a separate post.</p>

<p>Sent a bunch of <a
  href="http://www.divmod.org/trac/wiki/DivmodPyflakes">pyflakes</a> patches from
<a href="https://code.launchpad.net/~mgedmin/pyflakes/pyflakes-mg">my old
  branch</a> upstream, created <a
  href="http://www.divmod.org/trac/query?status=new&status=assigned&status=reopened&reporter=mgedmin&component=Pyflakes&order=priority">trac
  tickets</a> for the rest.  Wrestled with bzr-svn merges, failed abysmally.</p>
]]></content:encoded><category domain="http://mg.pov.lt">/home/mg/blog/data</category><dc:date>2009-07-24T23:14:12Z</dc:date></item></channel></rss>
