<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;A0ADRncyeip7ImA9WxBbFUw.&quot;"><id>tag:blogger.com,1999:blog-18508356</id><updated>2010-03-13T17:36:17.992-05:00</updated><title>Just a little Python</title><subtitle type="html">Blog about all things Python that intersect my work and hobbies</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.pythonisito.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>43</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/JustALittlePython" /><feedburner:info uri="justalittlepython" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;Dk4MRX05eip7ImA9WxBTEk8.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-3309787348248281790</id><published>2009-12-07T17:11:00.005-05:00</published><updated>2009-12-07T17:29:44.322-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-07T17:29:44.322-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ming" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="mongodb" /><title>Ming 0.1 Released - Python Library for MongoDB</title><content type="html">One of the things that's been nice about working with &lt;a href="http://sourceforge.net"&gt;SourceForge&lt;/a&gt; for the last few months is the chance I get to work with new open source technology and contribute something back.  Well, the first (in a long series, I hope) of projects we're putting out there as the result of recent work is a little library we wrote called Ming.  It's been available over git for a while, but I just finished publishing our first "official" release, with a tutorial, over at &lt;a href="http://merciless.sourceforge.net"&gt;http://merciless.sourceforge.net&lt;/a&gt; and &lt;a href="http://pypi.python.org/pypi/Ming/0.1"&gt;http://pypi.python.org/pypi/Ming/0.1&lt;/a&gt;.  Ming gives you a way of enforcing schemas on a MongoDB database in a Python application.  We also throw in automatic, lazy schema migration for good measure.   &lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;If you haven't heard of it yet, &lt;a href="http://www.mongodb.org"&gt;MongoDB&lt;/a&gt; describes itself as on its website as follows:&lt;br /&gt;&lt;blockquote&gt;MongoDB (from "humongous") is a scalable, high-performance, open source, schema-free, document-oriented database.&lt;br /&gt;&lt;/blockquote&gt;  &lt;br /&gt;There are lots of trade-offs to consider when using a non-relational database like MongoDB.  On the plus side, MongoDB is simple to develop for (no SQL), extremely fast, and models hierarchical relationships really nicely.  On the downside, we give up relational integrity constraints and transactional behavior.  &lt;br /&gt;&lt;br /&gt;With all the flexibility provided by MongoDB, however, comes some danger.  When the database is schema-free, you have to spend extra code making sure that the data you put in and the data you take out is valid for your application.  That's where Ming comes in.  A play off the character of &lt;a href="http://en.wikipedia.org/wiki/Ming_the_Merciless"&gt;Ming the Merciless&lt;/a&gt; who ruled the planet of Mongo with an iron fist in &lt;a href="http://en.wikipedia.org/wiki/Flash_Gordon"&gt;Flash Gordon&lt;/a&gt;, the Ming library provides a succinct way to specify the requirements your application has for the data it produces and consumes.  Ming also supports lazy migration of documents across schema revisions.  To give you a flavor for how easy this can be, here is a sample schema with migration from the tutorial:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;from ming.datastore import DataStore&lt;br /&gt;from ming import Session&lt;br /&gt;from ming import Document, Field, schema&lt;br /&gt;&lt;br /&gt;bind = DataStore('mongo://localhost:27017/tutorial')&lt;br /&gt;session = Session(bind)&lt;br /&gt;&lt;br /&gt;class OldWikiPage(Document):&lt;br /&gt;&lt;br /&gt;    class __mongometa__:&lt;br /&gt;        session = session&lt;br /&gt;        name = 'wiki_page'&lt;br /&gt;&lt;br /&gt;    _id = Field(schema.ObjectId)&lt;br /&gt;    title = Field(str)&lt;br /&gt;    text = Field(str, if_missing='')&lt;br /&gt;    metadata = Field(dict(&lt;br /&gt;            tags=[str],&lt;br /&gt;            categories=[str]))&lt;br /&gt;&lt;br /&gt;class WikiPage(Document):&lt;br /&gt;&lt;br /&gt;    class __mongometa__:&lt;br /&gt;        session = session&lt;br /&gt;        name = 'wiki_page'&lt;br /&gt;        version_of = OldWikiPage&lt;br /&gt;        def migrate(data):&lt;br /&gt;            result = dict(&lt;br /&gt;                data,&lt;br /&gt;                tags=data['metadata']['tags'],&lt;br /&gt;                categories=data['metadata']['categories'],&lt;br /&gt;                version=1)&lt;br /&gt;            del result['metadata']&lt;br /&gt;            return result&lt;br /&gt;&lt;br /&gt;    _id = Field(schema.ObjectId)&lt;br /&gt;    version = Field(1)&lt;br /&gt;    title = Field(str)&lt;br /&gt;    text = Field(str, if_missing='')&lt;br /&gt;    tags = Field([str])&lt;br /&gt;    categories = Field([str])&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Hopefully, that's enough to whet your appetite for now.  Rather than duplicating the entire tutorial, I'll direct you to the docs at &lt;a href="http://merciless.sourceforge.net"&gt;http://merciless.sourceforge.net&lt;/a&gt; for more information.  Let me know what you think!  &lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-3309787348248281790?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ne143MkK_r97IpkfB5bdguI9hRE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ne143MkK_r97IpkfB5bdguI9hRE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ne143MkK_r97IpkfB5bdguI9hRE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ne143MkK_r97IpkfB5bdguI9hRE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=hBKXzF0Xz90:beXXO3orJN8:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=hBKXzF0Xz90:beXXO3orJN8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=hBKXzF0Xz90:beXXO3orJN8:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=hBKXzF0Xz90:beXXO3orJN8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=hBKXzF0Xz90:beXXO3orJN8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/hBKXzF0Xz90" height="1" width="1"/&gt;</content><link rel="related" href="http://merciless.sourceforge.net" title="Ming 0.1 Released - Python Library for MongoDB" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/3309787348248281790/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=3309787348248281790" title="9 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3309787348248281790?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3309787348248281790?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/hBKXzF0Xz90/ming-01-released-python-library-for.html" title="Ming 0.1 Released - Python Library for MongoDB" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/12/ming-01-released-python-library-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE8NSXg6eCp7ImA9WxJSE0o.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-6521885783224959317</id><published>2009-05-03T13:40:00.003-04:00</published><updated>2009-05-03T13:48:18.610-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-03T13:48:18.610-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="metapython" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="hygiene" /><category scheme="http://www.blogger.com/atom/ns#" term="macros" /><title>MetaPython 0.2.2 with Hygienic Macros</title><content type="html">&lt;p&gt;In my ever-expanding quest to, as &lt;a href="http://twitter.com/jgustak"&gt;&amp;#64;jgustak&lt;/a&gt; recently tweeted, &amp;quot;introduce evil to Python to prevent even scarier evil,&amp;quot; I have released &lt;a href="http://metapython.org"&gt;MetaPython 0.2.2&lt;/a&gt;  Once again, if you aren't familiar with MetaPython, a good place to start is the &lt;a href="http://code.google.com/p/metapython/wiki"&gt;tutorial&lt;/a&gt;which walks you through the construction of a macro-ized &lt;cite&gt;collections.namedtuple&lt;/cite&gt; from the Python 2.6 standard library.  If you already know MetaPython, here's the stuff that's new in 0.2.2.&lt;/p&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;&lt;p&gt;One issue that macro implementors must eventually face is whether they want their&lt;br /&gt;macro system to be hygienic or not.  According to &lt;a href="http://en.wikipedia.org/wiki/Hygienic_macros"&gt;wikipedia&lt;/a&gt;,&lt;/p&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Hygienic macros are macros whose expansion is guaranteed not to cause collisions with existing symbol definitions. They are a feature of programming languages such as Scheme and Dylan.&lt;/blockquote&gt;&lt;br /&gt;&lt;p&gt;So what's all that mean?  The best way to explain is probably by showing the bad things unhygienic macros bring you.  Here is something you could do in MetaPython 0.2:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;def vadd(result, a, b):&lt;br /&gt;    defcode foo:&lt;br /&gt;        for i, (aa,bb) in enumerate(zip($a, $b)):&lt;br /&gt;            $&amp;lt;result&amp;gt;[i] = aa+bb&lt;br /&gt;    return foo&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;$vadd(?result, ?a, ?b)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Calling this macro &lt;cite&gt;$vadd(?z, ?x, ?y)&lt;/cite&gt; then gives you something like:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;for i,(aa,bb) in enumerate(zip(x, y)):&lt;br /&gt;    z[i] = aa+bb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Which is fine, as long as you weren't planning on using the &lt;cite&gt;i&lt;/cite&gt;, &lt;cite&gt;aa&lt;/cite&gt;, or &lt;cite&gt;bb&lt;/cite&gt; variables for anything important in the surrounding code.  This is especially bad because macros can introduce names that conflict with names in the context where they are expanded &lt;em&gt;surprisingly,&lt;/em&gt; without any indication to the macro &lt;em&gt;user&lt;/em&gt; that they are going to do so.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;In MetaPython 0.2.1 and 0.2.2, you declare the block as &lt;cite&gt;defcode foo():&lt;/cite&gt; which tells MetaPython that the &lt;cite&gt;foo&lt;/cite&gt; block should not capture any variables from its context when it is expanded.  In this case, MetaPython detects that the variables &lt;cite&gt;i&lt;/cite&gt;, &lt;cite&gt;aa&lt;/cite&gt;, and &lt;cite&gt;bb&lt;/cite&gt; are assigned in the block and replaces them with &amp;quot;known unique&amp;quot; names (names that should not exist in the surrounding block, &lt;em&gt;wherever&lt;/em&gt; they are expanded.  In MetaPython 0.2.1 and 0.2.2, you get this expansion:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;for _mpy_0,(_mpy_1,_mpy_2) in enumerate(zip(x, y)):&lt;br /&gt;    z[_mpy_0] = _mpy_1 + _mpy_2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;So as long as you avoid using names starting with &lt;cite&gt;_mpy&lt;/cite&gt;, you should be fine.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Now sometimes, you actually &lt;em&gt;want&lt;/em&gt; to capture a value from the context into which the macro is being defined.  One example is when you're writing a class factory like &lt;cite&gt;namedtuple&lt;/cite&gt;, covered in the tutorial.  Your goal is to generate a new class, and if that class has some weird &lt;cite&gt;_mpy_*&lt;/cite&gt; name, it's pretty useless.  So MetaPython lets you specify, via the arguments to &lt;cite&gt;defcode&lt;/cite&gt;, which names should be captured.  In the &lt;cite&gt;namedtuple&lt;/cite&gt; example, for instance, the &lt;cite&gt;defcode&lt;/cite&gt; declaration looks like &lt;cite&gt;defcode result(typename):&lt;/cite&gt;, where &lt;cite&gt;typename&lt;/cite&gt; is a variable containing the name of the class being created.  When MetaPython expands the code block, then, any names mentioned in the argument list will &lt;em&gt;not&lt;/em&gt; be auto-renamed by the &amp;quot;sanitizer.&amp;quot;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;MetaPython 0.2.1 and 0.2.2 both had this ability to hygienically expand macros, but 0.2.2 added the ability to use variable arguments to the &lt;cite&gt;defcode&lt;/cite&gt; block. One time when you might want to do this is in a domain-specific language context.  Say you wanted to specify that a class contained certain properties, and that those properties should be accessed via the &lt;cite&gt;property&lt;/cite&gt; builtin function.  For instance, say you wanted to have a class &lt;cite&gt;Foo&lt;/cite&gt; with properties &lt;cite&gt;a&lt;/cite&gt;, &lt;cite&gt;b&lt;/cite&gt;, &lt;cite&gt;c&lt;/cite&gt;, and &lt;cite&gt;d&lt;/cite&gt; implemented by semi-private instance variables &lt;cite&gt;_a&lt;/cite&gt;, &lt;cite&gt;_b&lt;/cite&gt;, &lt;cite&gt;_c&lt;/cite&gt;, and &lt;cite&gt;_d&lt;/cite&gt;.  You might write something like this:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;class Foo(object):&lt;br /&gt;    $has_properties(?a, ?b, ?c, ?d)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The implementation of has_properties, then, is the following:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;def has_properties(*props):&lt;br /&gt;    str_props = (str(p) for p in props)&lt;br /&gt;    gen = ( (p,&lt;br /&gt;             '_' + p,&lt;br /&gt;             '_get_' + p,&lt;br /&gt;             '_set_' + p)&lt;br /&gt;            for p in str_props )&lt;br /&gt;    defcode result(*props):&lt;br /&gt;        $for pub, pri, getter, setter in gen:&lt;br /&gt;            def $&amp;lt;getter&amp;gt;(self):&lt;br /&gt;                return self.$pri&lt;br /&gt;            def $&amp;lt;setter&amp;gt;(self, value):&lt;br /&gt;                self.$pri =  value&lt;br /&gt;            $pub = property($getter, $setter)&lt;br /&gt;    return result&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;The final expanded version of &lt;cite&gt;Foo&lt;/cite&gt; is then:&lt;/p&gt;&lt;br /&gt;&lt;pre class="literal-block"&gt;&lt;br /&gt;class Foo (object ):&lt;br /&gt;   def _mpy_3 (self ):&lt;br /&gt;     return self ._a&lt;br /&gt;&lt;br /&gt;    def _mpy_7 (self ,value ):&lt;br /&gt;        self ._a =value&lt;br /&gt;&lt;br /&gt;    a =property (_mpy_3 ,_mpy_7 )&lt;br /&gt;    def _mpy_1 (self ):&lt;br /&gt;        return self ._b&lt;br /&gt;&lt;br /&gt;    def _mpy_5 (self ,value ):&lt;br /&gt;        self ._b =value&lt;br /&gt;&lt;br /&gt;    b =property (_mpy_1 ,_mpy_5 )&lt;br /&gt;    def _mpy_2 (self ):&lt;br /&gt;        return self ._c&lt;br /&gt;&lt;br /&gt;    def _mpy_6 (self ,value ):&lt;br /&gt;        self ._c =value&lt;br /&gt;&lt;br /&gt;   c =property (_mpy_2 ,_mpy_6 )&lt;br /&gt;   def _mpy_4 (self ):&lt;br /&gt;        return self ._d&lt;br /&gt;&lt;br /&gt;   def _mpy_8 (self ,value ):&lt;br /&gt;        self ._d =value&lt;br /&gt;&lt;br /&gt;   d =property (_mpy_4 ,_mpy_8 )&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Note in particular that the getters and setters were sanitized, while the actual property names &lt;cite&gt;a&lt;/cite&gt;, &lt;cite&gt;b&lt;/cite&gt;, &lt;cite&gt;c&lt;/cite&gt;, and &lt;cite&gt;d&lt;/cite&gt; were skipped.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;So MetaPython now has what I think is a workable hygienic macro system with appropriate escapes for &amp;quot;non-hygienic&amp;quot; operation.  Any comments, questions, or criticisms are welcome, as well as ideas for how you are using or might use MetaPython.  Let me know what you think!&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-6521885783224959317?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BgzlvGihuOSo4Z-DiABGjKPsx0c/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BgzlvGihuOSo4Z-DiABGjKPsx0c/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/BgzlvGihuOSo4Z-DiABGjKPsx0c/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BgzlvGihuOSo4Z-DiABGjKPsx0c/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=us9yAK-yrJM:u2roG_zPIV4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=us9yAK-yrJM:u2roG_zPIV4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=us9yAK-yrJM:u2roG_zPIV4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=us9yAK-yrJM:u2roG_zPIV4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=us9yAK-yrJM:u2roG_zPIV4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/us9yAK-yrJM" height="1" width="1"/&gt;</content><link rel="related" href="http://metapython.org" title="MetaPython 0.2.2 with Hygienic Macros" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/6521885783224959317/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=6521885783224959317" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6521885783224959317?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6521885783224959317?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/us9yAK-yrJM/metapython-022-with-hygienic-macros.html" title="MetaPython 0.2.2 with Hygienic Macros" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/05/metapython-022-with-hygienic-macros.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08HQn8ycSp7ImA9WxJSEkQ.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-8646679290917812355</id><published>2009-04-17T09:34:00.004-04:00</published><updated>2009-05-02T16:23:53.199-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-02T16:23:53.199-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="metapython" /><category scheme="http://www.blogger.com/atom/ns#" term="python programming" /><category scheme="http://www.blogger.com/atom/ns#" term="macros" /><title>MetaPython 0.2 Release</title><content type="html">For those intrepid souls who are interested in generating Python code from the macros and code quoting facilities of &lt;a href="http://metapython.org"&gt;MetaPython&lt;/a&gt;, I have spun a new release.  If you aren't familiar with MetaPython, a good place to start is the &lt;a href="http://code.google.com/p/metapython/wiki/Tutorial"&gt;tutorial&lt;/a&gt;, which walks you through the construction of a macro for generating a macro-ized version of &lt;code&gt;collections.namedtuple&lt;/code&gt; from the Python 2.6 standard library.  If you are already familiar with MetaPython, I will try to summarize the changes and ideas in this release here.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;MetaPython 0.1 was mainly a proof of concept implementation to show that it was possible to generate a moderately useful macro facility in a short period of time.  I did, however, make one really questionable decision: to use &lt;a href="http://jinja.pocoo.org/2/"&gt;Jinja2&lt;/a&gt; as the templating language for code quotes.  &lt;br /&gt;&lt;br /&gt;Don't get me wrong; Jinja2 is an awesome templating language.  But if I am generating a language extension (MetaPython) that allows you to change the text of a module as just before it gets imported, wouldn't it be nice to also be able to change the text of a code quote using the same syntax?  Hence version 0.2.&lt;br /&gt;&lt;br /&gt;There was also the issue of ugly syntax with the &lt;code&gt;?&lt;/code&gt;,  &lt;code&gt;$&lt;/code&gt;, and &lt;code&gt;{%...%}&lt;/code&gt; operators.  Although &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; are still there, I have tried to normalize their use a bit.  &lt;code&gt;$&lt;/code&gt; always introduces a construct that should be executed or evaluated "earlier" than the surrounding code.  (Inside a &lt;code&gt;defcode...:&lt;/code&gt; block, this means at block construction time, otherwise it means at import time.)  The &lt;code&gt;?&lt;/code&gt; operator now has a much more limited use as an inline code quoting operator.&lt;br /&gt;&lt;br /&gt;Other than syntax changes, one of the main things you'll notice in MetaPython 0.2 is the introduction of import-time control statements (&lt;code&gt;$for...&lt;/code&gt;, &lt;code&gt;$if...&lt;/code&gt;, etc.)  These allow the conditional or repeated expansion of code blocks.  These constructs basically obviate the need for another template language (Jinja2) inside &lt;code&gt;defcode...&lt;/code&gt; blocks.&lt;br /&gt;&lt;br /&gt;Another big change and move toward normalization is that macro calls are now just import-time function calls.  This means that their arguments are evaluated before they are called, not passed through as code objects.  In order to get the old behavior, you can simply quote the arguments you wish to be sent through unevaluated.  For instance, in the old syntax, creating a named tuple was accomplished via &lt;code&gt;?namedtuple(Point, x, y)&lt;/code&gt;, whereas in the new syntax, you would type &lt;code&gt;$namedtuple(?Point, ?x, ?y)&lt;/code&gt;.  This makes it substantially easier to write macros which need non-code arguments, and follows the Python Zen of "Explicit is better than implicit."&lt;br /&gt;&lt;br /&gt;There have also been significant reworking of the internal MetaPython parser and code construction machinery, although this should not be user-visible.  So try it out, let me know what you think, and have fun!&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-8646679290917812355?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_WXAAB1gUatt9Vkf7ZhSzOOYf4Y/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_WXAAB1gUatt9Vkf7ZhSzOOYf4Y/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/_WXAAB1gUatt9Vkf7ZhSzOOYf4Y/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_WXAAB1gUatt9Vkf7ZhSzOOYf4Y/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=f5jLHtnEfT4:6xERlOQNRAY:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=f5jLHtnEfT4:6xERlOQNRAY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=f5jLHtnEfT4:6xERlOQNRAY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=f5jLHtnEfT4:6xERlOQNRAY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=f5jLHtnEfT4:6xERlOQNRAY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/f5jLHtnEfT4" height="1" width="1"/&gt;</content><link rel="related" href="http://metapython.org" title="MetaPython 0.2 Release" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/8646679290917812355/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=8646679290917812355" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/8646679290917812355?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/8646679290917812355?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/f5jLHtnEfT4/metapython-02-release.html" title="MetaPython 0.2 Release" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/04/metapython-02-release.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMDRXk5eSp7ImA9WxVaE0U.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-4380292888467251781</id><published>2009-04-10T13:23:00.002-04:00</published><updated>2009-04-10T13:27:54.721-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-10T13:27:54.721-04:00</app:edited><title>MetaPython Presentation</title><content type="html">Last night at the &lt;a href="http://pyatl.org"&gt;Python Atlanta&lt;/a&gt; meetup I gave a brief talk on MetaPython, including the motivations for doing something so profane as adding macros and code quoting to Python.  The video is &lt;a href="http://blip.tv/file/1979340#share"&gt;on blip.tv&lt;/a&gt; and you can find the slides on the &lt;a href="http://metapython.org/presentations.html"&gt;MetaPython.org&lt;/a&gt;  Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-4380292888467251781?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/bh2Lh65mdzHHSD1O3d3nI084WXg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bh2Lh65mdzHHSD1O3d3nI084WXg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/bh2Lh65mdzHHSD1O3d3nI084WXg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bh2Lh65mdzHHSD1O3d3nI084WXg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RAuaqsoADgk:3MIwph-j7hg:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RAuaqsoADgk:3MIwph-j7hg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RAuaqsoADgk:3MIwph-j7hg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RAuaqsoADgk:3MIwph-j7hg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RAuaqsoADgk:3MIwph-j7hg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/RAuaqsoADgk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/4380292888467251781/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=4380292888467251781" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/4380292888467251781?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/4380292888467251781?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/RAuaqsoADgk/metapython-presentation.html" title="MetaPython Presentation" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/04/metapython-presentation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ADRXo5eyp7ImA9WxVUFEU.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-2354346033423422807</id><published>2009-03-19T09:11:00.006-04:00</published><updated>2009-03-19T14:02:54.423-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-19T14:02:54.423-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="lisp" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="macros" /><title>Announcing MetaPython - Macros for Python</title><content type="html">As I mentioned in my &lt;a href="http://blog.pythonisito.com/2009/03/python-macros.html"&gt;last post&lt;/a&gt;, I have been considering writing some version of macros for Python and was looking for use cases.  Well, having gotten the use cases I so desired from my wonderful commenters, I went ahead and put together an import hook and &lt;a href="http://code.google.com/p/metapython/"&gt;Google Code project&lt;/a&gt; that I'm calling MetaPython &amp;mdash; all just in time for &lt;a href="http://us.pycon.org/2009/about/"&gt;PyCon!&lt;/a&gt; (I have no talks, but I will be there, and would love to have a MetaPython Open Space if anyone's interested.)&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;So what's all the excitement about?  MetaPython introduces some hooks to allow you to modify module code just before it is seen by the Python interpreter (at what I'm calling "import time").  The import-time syntax is pretty simple, and is (almost) all denoted by a question mark &lt;code&gt;?&lt;/code&gt; prefix (question marks are currently syntax errors in regular Python).  Here is a trivial example that defines an import-time function (which as we will see can be used as a macro) that will conditionally remove a function call (and the evaluation of its associated arguments).  Suppose the following text is saved in a file "cremove.mpy":&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def cremove(debug, expr):&lt;br /&gt;    debug = eval(str(debug))&lt;br /&gt;    defcode empty_result:&lt;br /&gt;        pass&lt;br /&gt;    if debug:&lt;br /&gt;        result = expr&lt;br /&gt;    else:&lt;br /&gt;        result = empty_result&lt;br /&gt;    return result&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The idea here is that &lt;code&gt;cremove&lt;/code&gt; will be called with two &lt;code&gt;metapython.Code&lt;/code&gt; values, &lt;code&gt;debug&lt;/code&gt; and &lt;code&gt;expr&lt;/code&gt;.  &lt;code&gt;cremove&lt;/code&gt; will convert debug to its Python code representation by calling &lt;code&gt;str()&lt;/code&gt; and then evaluate the result.  If &lt;code&gt;debug&lt;/code&gt; is true, then &lt;code&gt;expr&lt;/code&gt; will be returned.  Otherwise a &lt;code&gt;pass&lt;/code&gt; statement will be returned (defined using the MetaPython import-time construct &lt;code&gt;defcode&lt;/code&gt; which defines a code template).  To actually call cremove as a macro, we will need to define another MetaPython module, say "test_cremove.mpy":&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;import logging&lt;br /&gt;logging.basicConfig(level=logging.DEBUG)&lt;br /&gt;log = logging.getLogger(__name__)&lt;br /&gt;&lt;br /&gt;?from cremove import cremove&lt;br /&gt;&lt;br /&gt;def do_test():&lt;br /&gt;    ?cremove(True, log.debug('This statement will be logged'))&lt;br /&gt;    ?cremove(False, log.debug('This statement will not be logged'))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here, we do an import-time import (the &lt;code&gt;?from... import&lt;/code&gt; business).  This makes the module we're importing available at import-time (a regular import would be seen just as another line of Python code at import-time).  To actually call cremove as a macro, we just need to prefix it with the "?" as shown.&lt;br /&gt;&lt;br /&gt;Now, to actually test this, we'll need to install MetaPython and fire up an interpreter.  MetaPython is available from the &lt;a href="http://pypi.python.org/pypi"&gt;CheeseShop&lt;/a&gt;, so to get it just run &lt;code&gt;easy_install MetaPython&lt;/code&gt;.  Once it's installed, we can test our MetaPython code as follows:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; import metapython; metapython.install_import_hook()&lt;br /&gt;&gt;&gt;&gt; import test_cremove&lt;br /&gt;&gt;&gt;&gt; test_cremove.do_test()&lt;br /&gt;DEBUG:test_cremove:This statement will be logged&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Since macro expansion can get pretty complex and it's always tricky debugging code you've never seen, the fully-expanded module is available as the &lt;code&gt;__expanded__&lt;/code&gt; attribute of the MetaPython module:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt;&gt; print test_cremove.__expanded__&lt;br /&gt;import logging &lt;br /&gt;logging .basicConfig (level =logging .DEBUG )&lt;br /&gt;log =logging .getLogger (__name__ )&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def do_test ():&lt;br /&gt;    log .debug ('This statement will be logged')&lt;br /&gt;    pass  &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;There's a lot more to MetaPython, but hopefully this has whetted your appetite.  There's a &lt;a href="http://code.google.com/p/metapython/wiki/Tutorial"&gt;short tutorial&lt;/a&gt; available that shows how you can implement the &lt;code&gt;collections.namedtuple&lt;/code&gt; class factory using a macro.  It also shows how you can use Jinja2 syntax along with the defcode construct to dynamically produce Python code.  Have fun, and let me know what you think!&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-2354346033423422807?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/XGV5J1bI3KFBxKY6lU1Yf3Fc6pI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/XGV5J1bI3KFBxKY6lU1Yf3Fc6pI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/XGV5J1bI3KFBxKY6lU1Yf3Fc6pI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/XGV5J1bI3KFBxKY6lU1Yf3Fc6pI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y9ZDDQ_dnyE:yZmpR5sFX6A:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y9ZDDQ_dnyE:yZmpR5sFX6A:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y9ZDDQ_dnyE:yZmpR5sFX6A:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y9ZDDQ_dnyE:yZmpR5sFX6A:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y9ZDDQ_dnyE:yZmpR5sFX6A:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/y9ZDDQ_dnyE" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/metapython/" title="Announcing MetaPython - Macros for Python" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/2354346033423422807/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=2354346033423422807" title="13 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/2354346033423422807?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/2354346033423422807?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/y9ZDDQ_dnyE/announcing-metapython-macros-for-python.html" title="Announcing MetaPython - Macros for Python" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">13</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/03/announcing-metapython-macros-for-python.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYDRns_fSp7ImA9WxVUFEo.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-4718691580616017838</id><published>2009-03-12T10:17:00.005-04:00</published><updated>2009-03-19T11:56:17.545-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-19T11:56:17.545-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="lisp" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="macros" /><title>Python Macros?</title><content type="html">I've been thinking a bit about macros and what use they might be in Python.  Basically, I was contemplating writing an import hook that would allow you to use code quoting and unquoting and stuff for your Python modules.  My motive was just that Lisp people seem to rave about how awesome macros are all the time, so I figured they must be cool.  &lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;As I sat down to actually start figuring out what macro definitions and uses should &lt;i&gt;look like&lt;/i&gt; in Python, I thought, hey, I'll just throw together a use case.  But I haven't been able to come up with one (yet).&lt;br /&gt;&lt;br /&gt;Most of the examples I found on the web focused on "hey, you can implement a 'while' loop with macros in Lisp!" or "hey, look at all the cool stuff the 'setf' macro can do!"  So I started to wonder whether maybe Lisp people love macros because it allows them to extend Lisp's minimalist syntax with new constructs (like object-oriented programming with CLOS, while loops, etc.)  Python, OTOH, has pretty rich syntax.  It has a nice OOP system with syntactic support, while and for loops, generators, iterators, context managers, primitive coroutines, comprehensions, destructuring bind,.... -- What would I use macros for?  (OK, depending on the syntax, I could add a "switch" statement, but that hardly seems worth the trouble.)&lt;br /&gt;&lt;br /&gt;I should mention that I also saw some examples of people using macros for performance; you basically get rid of a function call and you can potentially make the inner loop of some critical function run really fast.  But if that's all it buys me in Python-land (well, that and a switch statement), my motivation is pretty low.  Because let's face it -- if your critical inner loop is written in pure Python, you can pretty easily throw it at &lt;a href="http://www.cython.org/"&gt;Cython&lt;/a&gt; and get better performance than Python macros could ever provide.&lt;br /&gt;&lt;br /&gt;So here's the question: does anyone out there have an idea of what macros would add to Python's power or expressiveness?  Or maybe some Lisp, Meta OCAML, or Template Haskell hackers who can enlighten me as to what macros can add to a language with already rich syntax?&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Update 2008-03-19&lt;/h2&gt;&lt;br /&gt;I have implemented MetaPython 0.1, a macro and code quoting system for Python, covered in the next &lt;a href="http://blog.pythonisito.com/2009/03/announcing-metapython-macros-for-python.html"&gt;blog post&lt;/a&gt;.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-4718691580616017838?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Scnls6kVd6Hgt2bkrcUbAYMczOE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Scnls6kVd6Hgt2bkrcUbAYMczOE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Scnls6kVd6Hgt2bkrcUbAYMczOE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Scnls6kVd6Hgt2bkrcUbAYMczOE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=FKGJ-HIVziM:vHMMCGiU8YQ:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=FKGJ-HIVziM:vHMMCGiU8YQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=FKGJ-HIVziM:vHMMCGiU8YQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=FKGJ-HIVziM:vHMMCGiU8YQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=FKGJ-HIVziM:vHMMCGiU8YQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/FKGJ-HIVziM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/4718691580616017838/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=4718691580616017838" title="17 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/4718691580616017838?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/4718691580616017838?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/FKGJ-HIVziM/python-macros.html" title="Python Macros?" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">17</thr:total><feedburner:origLink>http://blog.pythonisito.com/2009/03/python-macros.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0ACQXY8fCp7ImA9WxdaFk0.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-8975919509166303367</id><published>2008-08-22T14:53:00.005-04:00</published><updated>2008-08-24T14:16:00.874-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-24T14:16:00.874-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="decorator" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="descriptor" /><title>Lazy Descriptors</title><content type="html">Today I had a need to create a property on an object "lazily."  The Python builtin &lt;code&gt;property&lt;/code&gt; does a great job of this, but it calls the getter function &lt;em&gt;every&lt;/em&gt; time you access the property.  Here is how I ended up solving the problem:&lt;br /&gt;  &lt;span class="fullpost"&gt;&lt;br /&gt;    First of all, I had (almost) the behavior I wanted by using the following pattern:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class Foo(object):&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self._bar = None&lt;br /&gt;    @property&lt;br /&gt;    def bar(self):&lt;br /&gt;        if self._bar is None:&lt;br /&gt;            print 'Calculating self._bar'  &lt;br /&gt;            self._bar = 42    &lt;br /&gt;        return self._bar      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    There are a couple of problems with this, however.  First of all, I'm polluting my object's namespace with a &lt;code&gt;_bar&lt;/code&gt; attribute that I don't want.  Secondly, I'm using this pattern all over my codebase, and it's quite an eyesore.&lt;br /&gt;&lt;br /&gt;    Both problems can be fixed by using a descriptor.  Basically, a descriptor is an object with a &lt;code&gt;__get__&lt;/code&gt; method which is called when the descriptor is accessed as a property of a class.  The descriptor I created is below:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class LazyProperty(object):&lt;br /&gt;&lt;br /&gt;    def __init__(self, func):&lt;br /&gt;        self._func = func&lt;br /&gt;        self.__name__ = func.__name__&lt;br /&gt;        self.__doc__ = func.__doc__&lt;br /&gt;&lt;br /&gt;    def __get__(self, obj, klass=None):&lt;br /&gt;        if obj is None: return None&lt;br /&gt;        result = obj.__dict__[self.__name__] = self._func(obj)&lt;br /&gt;        return result&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    The descriptor is designed to be used as a decorator, and will save the decorated function and its name.  When the descriptor is accessed, it will calculate the value by calling the function and save the calculated value back to the object's dict.  Saving back to the object's dict has the additional benefit of preventing the descriptor from being called the next time the property is accessed.  So I can now use it in the class above:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class Foo(object):&lt;br /&gt;    @LazyProperty&lt;br /&gt;    def bar(self):&lt;br /&gt;        print 'Calculating self._bar'  &lt;br /&gt;        return 42&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    So I get a nice lazily calculated property that doesn't recalculate &lt;code&gt;bar&lt;/code&gt; every time it's accessed and doesn't bother with any memoization itself.  What do you think about it? Is this a patten you use in your code?&lt;br /&gt;  &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-8975919509166303367?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/oiL52SxY61uFxlexAuFweSQtQpU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/oiL52SxY61uFxlexAuFweSQtQpU/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/oiL52SxY61uFxlexAuFweSQtQpU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/oiL52SxY61uFxlexAuFweSQtQpU/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=Pg3qggfoLfY:RY3tzvEezsQ:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=Pg3qggfoLfY:RY3tzvEezsQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=Pg3qggfoLfY:RY3tzvEezsQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=Pg3qggfoLfY:RY3tzvEezsQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=Pg3qggfoLfY:RY3tzvEezsQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/Pg3qggfoLfY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/8975919509166303367/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=8975919509166303367" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/8975919509166303367?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/8975919509166303367?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/Pg3qggfoLfY/lazy-descriptors.html" title="Lazy Descriptors" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">12</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/08/lazy-descriptors.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4GR3oyfip7ImA9WxdaE08.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-6575313750690607502</id><published>2008-08-21T08:13:00.001-04:00</published><updated>2008-08-21T08:15:26.496-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-21T08:15:26.496-04:00</app:edited><title>New Domain blog.pythonisito.com</title><content type="html">I just wanted to let you all know that I've changed from the blogger domain to my own blog.pythonisito.com.  You should be redirected there automatically, but if you've noticed some hiccups in feeds or weird redirects from Reddit or Delicious, now you know why.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-6575313750690607502?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/g_HjO78XeArpIzBPfXoOA5is85E/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/g_HjO78XeArpIzBPfXoOA5is85E/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/g_HjO78XeArpIzBPfXoOA5is85E/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/g_HjO78XeArpIzBPfXoOA5is85E/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RGyjHdqLTOM:Sns4cHYWPN4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RGyjHdqLTOM:Sns4cHYWPN4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RGyjHdqLTOM:Sns4cHYWPN4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=RGyjHdqLTOM:Sns4cHYWPN4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=RGyjHdqLTOM:Sns4cHYWPN4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/RGyjHdqLTOM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/6575313750690607502/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=6575313750690607502" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6575313750690607502?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6575313750690607502?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/RGyjHdqLTOM/new-domain-blogpythonisitocom.html" title="New Domain blog.pythonisito.com" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/08/new-domain-blogpythonisitocom.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0EMRHg9cCp7ImA9WxdaEUg.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-1590857584426864441</id><published>2008-08-19T10:39:00.003-04:00</published><updated>2008-08-19T11:28:05.668-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-19T11:28:05.668-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="linux" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><title>A Little Command Line Love</title><content type="html">One of the things I do in my "spare" time is work on building web applications that will (hopefully) earn some spare money on the side without too much maintenance on my part.  Those who have read &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2F4-Hour-Workweek-Escape-Live-Anywhere%2Fdp%2F0307353133%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1219157009%26sr%3D1-1&amp;tag=pythonisitobl-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;The Four Hour Work Week&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=pythonisitobl-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;  will recognize this as my "muse" business.&lt;br /&gt;&lt;br /&gt;  In working on these web apps, I needed a place to host them, so I went with &lt;a href="http://www.webfaction.com/signup?affiliate=rick446"&gt;WebFaction&lt;/a&gt;  due to their excellent support for &lt;a href="http://www.turbogears.org"&gt;TurboGears&lt;/a&gt;.  I'm using a shared hosting environment with WebFaction, so my usual method of getting stuff up can't involve any scripts in /etc/init.d like I'd use at work, so I used to do the old &lt;code&gt;nohup python start-appname.py &gt;&gt; output.log 2&gt;&amp;amp;1 &amp;amp;&lt;/code&gt; to "daemonize" the process and then &lt;code&gt;ps -furick446&lt;/code&gt; to figure out which processes I needed to kill/restart when updating code.  This was irritatingly verbose, so I figured I'd build a "userspace daemonizer" which I'll describe below.&lt;br /&gt;  &lt;span class="fullpost"&gt;&lt;br /&gt;    The requirements of my daemonizer were pretty straightforward:&lt;br /&gt;    &lt;ul&gt;&lt;br /&gt;      &lt;li&gt;I should be able to add/remove services from the command line with a minimum amount of typing&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;I should be able to start/stop/restart any service just by listing it by name (no more &lt;code&gt;ps -furick446&lt;/code&gt;)&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;It should do "real" daemonization (fork, fork, dup stuff if you're familiar with it) rather than the nohup garbage I'd been using&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;It should perform some sort of verification or status check on processes to make sure that they're successfully started/stopped/running/etc.&lt;/li&gt;&lt;br /&gt;    &lt;/ul&gt;&lt;br /&gt;    The first (and most questionable) decision I made was to go with &lt;a href="http://www.sqlalchemy.org"&gt;SQLAlchemy&lt;/a&gt; as my service database.  I say this is questionable because I ended up with only one table and one client that uses the database, so a fully relational model is &lt;em&gt;really&lt;/em&gt; overkill here.  Anyway, here's the SQLAlchemy setup code I used.  It's pretty simple, and represents kind of the "lowest common denominator" of SQLAlchemy usage.  (I've also included all the imports required for the whole shebang at the top of the file.)    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;#!/usr/bin/env python2.5&lt;br /&gt;import os, sys, signal, shlex, time&lt;br /&gt;from optparse import OptionParser&lt;br /&gt;&lt;br /&gt;from sqlalchemy import *&lt;br /&gt;from sqlalchemy.orm import *&lt;br /&gt;&lt;br /&gt;HOME=os.environ.get('HOME')&lt;br /&gt;DBFILE=os.path.abspath(os.environ.get(&lt;br /&gt;    'SERVER_FILE',&lt;br /&gt;    os.path.join(HOME, 'server.sqlite')))&lt;br /&gt;&lt;br /&gt;DBURI='sqlite:///' + DBFILE&lt;br /&gt;&lt;br /&gt;engine = create_engine(DBURI)&lt;br /&gt;metadata = MetaData(engine)&lt;br /&gt;session = scoped_session(sessionmaker(bind=engine))&lt;br /&gt;&lt;br /&gt;process = Table(&lt;br /&gt;    'process', metadata,&lt;br /&gt;    Column('name', String(255), primary_key=True),&lt;br /&gt;    Column('pid', Integer),&lt;br /&gt;    Column('command_line', String(255)),&lt;br /&gt;    Column('working_directory', String(255)),&lt;br /&gt;    Column('stdin', String(255), default='/dev/null'),&lt;br /&gt;    Column('stdout', String(255), default='/dev/null'),&lt;br /&gt;    Column('stderr', String(255), default='/dev/null'))&lt;br /&gt;&lt;br /&gt;class Process(object):&lt;br /&gt;    def __repr__(self):&lt;br /&gt;        return '%s(PID %s, WD %s): %s &amp;lt; %s &gt;&gt; %s 2&gt;&gt; %s' % (&lt;br /&gt;            self.name, self.pid, self.working_directory,&lt;br /&gt;            self.command_line, self.stdin, self.stdout, self.stderr)&lt;br /&gt;session.mapper(Process, process)      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    So, for all of you out there who wonder how to use SQLAlchemy &lt;em&gt;outside&lt;/em&gt; of a web framework, there you go.  The idea here is that every service has a row in the &lt;code&gt;process&lt;/code&gt; table, and every service can optionally redirect its stdin/stdout/stderr to/from files.  If a process is running, it will have a &lt;code&gt;pid&lt;/code&gt;.  You can also specify the startup directory of each process.  That pretty much covers the data model.&lt;br /&gt;&lt;br /&gt;    My next task was to figure out how to invoke this from the command line.  I decided to make all the commands of the form &lt;code&gt;python server.py &lt;em&gt;command&lt;/em&gt; [&lt;em&gt;options&lt;/em&gt;] [&lt;em&gt;service&lt;/em&gt;]&lt;/code&gt;.  Actually, the only command that takes any options is the &lt;code&gt;add&lt;/code&gt; command, so I definitely over-engineered here, but I wanted to be able to specify a global list of options shared by all commands to be used with an &lt;code&gt;optparse&lt;/code&gt; option parser.  So I built the following dictionary:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;OPTPARSE_OPTIONS = {&lt;br /&gt;    'working-directory':(['-w', '--working-directory'],&lt;br /&gt;                         dict(dest='ws', default=HOME,&lt;br /&gt;                              help='Set working directory to DIR',&lt;br /&gt;                              metavar='DIR')),&lt;br /&gt;    'stdin':(['-i', '--stdin'],&lt;br /&gt;             dict(dest='stdin', default='/dev/null',&lt;br /&gt;                  help='Use FILE as stdin', metavar='FILE')),&lt;br /&gt;    'stdout':(['-o', '--stdout'],&lt;br /&gt;             dict(dest='stdout', default='/dev/null',&lt;br /&gt;                  help='Use FILE as stdout', metavar='FILE')),&lt;br /&gt;    'stderr':(['-e', '--stderr'],&lt;br /&gt;             dict(dest='stderr', default='/dev/null',&lt;br /&gt;                  help='Use FILE as stderr', metavar='FILE')),&lt;br /&gt;    }      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    The next task was to specify the commands.  I'm lazy and I like decorators, so I decided that new commands should be as easy as possible to write.  My &lt;code&gt;add&lt;/code&gt; command, for instance, is the most complex, and it looks like this:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;@Command&lt;br /&gt;def add(service, command, working_directory,&lt;br /&gt;        stdin, stdout, stderr):&lt;br /&gt;    p = Process(name=service,&lt;br /&gt;                command_line=command,&lt;br /&gt;                working_directory=working_directory,&lt;br /&gt;                stdin=stdin,&lt;br /&gt;                stdout=stdout,&lt;br /&gt;                stderr=stderr)&lt;br /&gt;    session.commit()      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    That's pretty simple.  Of course, as you might have guessed, the &lt;code&gt;Command&lt;/code&gt; decorator isn't all that simple.  Its responsibilities are as follows:&lt;br /&gt;    &lt;ul&gt;&lt;br /&gt;      &lt;li&gt;Add the function name to a command list so I can get a list of commands from the command line&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;Build an &lt;code&gt;optparse&lt;/code&gt; parser based on the &lt;code&gt;OPTPARSE_OPTIONS&lt;/code&gt; dict and the named arguments to the function&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;Wrap the function in a new function with a signature like &lt;code&gt;&lt;em&gt;function(args)&lt;/em&gt;&lt;/code&gt; so it can be called with &lt;code&gt;sys.argv[2:]&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;      &lt;li&gt;In the wrapper function, use the optparse parser to initialize the argument list to the function and then call with a named argument dict&lt;br /&gt;      &lt;/li&gt;&lt;br /&gt;    &lt;/ul&gt;&lt;br /&gt;    So with all that explanation, here's the code:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class Command(object):&lt;br /&gt;    commands = {}&lt;br /&gt;    def __init__(self, func):&lt;br /&gt;        Command.commands[func.__name__] = self&lt;br /&gt;        options = self.get_options(func)&lt;br /&gt;        self.func = func&lt;br /&gt;        self.optparse_options = []&lt;br /&gt;        self.optparse_option_names = []&lt;br /&gt;        self.positional_options = []&lt;br /&gt;        for o in options:&lt;br /&gt;            if o in OPTPARSE_OPTIONS:&lt;br /&gt;                self.optparse_option_names.append(o)&lt;br /&gt;                self.optparse_options.append(&lt;br /&gt;                    OPTPARSE_OPTIONS[o])&lt;br /&gt;            else:&lt;br /&gt;                self.positional_options.append(o)&lt;br /&gt;        positional_string = ' '.join(&lt;br /&gt;            '&lt;%s&gt;' % o for o in self.positional_options)&lt;br /&gt;        self.parser = OptionParser('%%prog [options] %s' % positional_string,&lt;br /&gt;                                   prog=func.__name__)&lt;br /&gt;        for args, kwargs in self.optparse_options:&lt;br /&gt;            self.parser.add_option(*args, **kwargs)&lt;br /&gt;&lt;br /&gt;    def get_options(self, func):&lt;br /&gt;        code = func.func_code&lt;br /&gt;        return [ vn for vn in code.co_varnames[:code.co_argcount] ]&lt;br /&gt;&lt;br /&gt;    @classmethod&lt;br /&gt;    def run(klass, args):&lt;br /&gt;        if args and args[0] in klass.commands:&lt;br /&gt;            return klass.commands[args[0]](args[1:])&lt;br /&gt;        else:&lt;br /&gt;            print 'Unrecognized command'&lt;br /&gt;            print 'Acceptable commands:'&lt;br /&gt;            for name in klass.commands:&lt;br /&gt;                print '  -', name&lt;br /&gt;&lt;br /&gt;    def __call__(self, args):&lt;br /&gt;        (opts,a) = self.parser.parse_args(args)&lt;br /&gt;        if len(a) != len(self.positional_options):&lt;br /&gt;            self.parser.error('Wrong number of arguments')&lt;br /&gt;        o = {}&lt;br /&gt;        for name in self.optparse_option_names:&lt;br /&gt;            o[name] = getattr(opts, name, None)&lt;br /&gt;        for name, value in zip(self.positional_options, a):&lt;br /&gt;            o[name] = value&lt;br /&gt;        return self.func(**o)      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    Once I've built the decorator, the commands are fairly straightforward (as the &lt;code&gt;add&lt;/code&gt; command shows).  Here are the "short" commands:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;@Command&lt;br /&gt;def initialize():&lt;br /&gt;    try:&lt;br /&gt;        metadata.drop_all()&lt;br /&gt;    except:&lt;br /&gt;        pass&lt;br /&gt;    metadata.create_all()&lt;br /&gt;    print 'Service database initialized'&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def list():&lt;br /&gt;    q = Process.query()&lt;br /&gt;    if q.count():&lt;br /&gt;        for p in Process.query():&lt;br /&gt;            print p&lt;br /&gt;    else:&lt;br /&gt;        print 'No processes'&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def add(service, command, working_directory,&lt;br /&gt;        stdin, stdout, stderr):&lt;br /&gt;    p = Process(name=service,&lt;br /&gt;                command_line=command,&lt;br /&gt;                working_directory=working_directory,&lt;br /&gt;                stdin=stdin,&lt;br /&gt;                stdout=stdout,&lt;br /&gt;                stderr=stderr)&lt;br /&gt;    session.commit()&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def remove(service):&lt;br /&gt;    p = Process.query.get(service)&lt;br /&gt;    session.delete(p)&lt;br /&gt;    print 'Removed %s from the service list' % service&lt;br /&gt;    session.commit()&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def status(service):&lt;br /&gt;    p = Process.query.get(service)&lt;br /&gt;    print p      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    Starting and stopping processes is a little more complex, but not too bad.  First off, I needed a way to determine if a process was running.  I didn't want to parse the &lt;code&gt;ps -furick446&lt;/code&gt; results, so I send a unix signal 0 to the PID (which doesn't do anything to the receiving process).  If there's an exception, the process is either not running or not owned by me. So here's the &lt;code&gt;is_running&lt;/code&gt; code:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;def is_running(pid):&lt;br /&gt;    try:&lt;br /&gt;        os.kill(pid, 0)&lt;br /&gt;        return True&lt;br /&gt;    except Exception, ex:&lt;br /&gt;        return False      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    I also need a daemonizer function that will start, daemonize, and set the PID of a process object:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;def daemonize(p):&lt;br /&gt;    pid = os.fork()&lt;br /&gt;    if pid: return # exit first parent&lt;br /&gt;    os.chdir(p.working_directory)&lt;br /&gt;    os.umask(0)&lt;br /&gt;    os.setsid()&lt;br /&gt;    pid = os.fork()&lt;br /&gt;    if pid:&lt;br /&gt;        Process.query.get(p.name).pid = pid&lt;br /&gt;        session.commit()&lt;br /&gt;        sys.exit(1) # exit second parent&lt;br /&gt;    si = open(p.stdin, 'r')&lt;br /&gt;    so = open(p.stdout, 'a+')&lt;br /&gt;    se = open(p.stderr, 'a+', 0)&lt;br /&gt;    os.dup2(si.fileno(), sys.stdin.fileno())&lt;br /&gt;    os.dup2(so.fileno(), sys.stdout.fileno())&lt;br /&gt;    os.dup2(se.fileno(), sys.stderr.fileno())&lt;br /&gt;    args = shlex.split(p.command_line.encode('utf-8'))&lt;br /&gt;    os.execvp(args[0], args)      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    Once these are defined, I can start, stop, and restart processes fairly simply:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;@Command&lt;br /&gt;def start(service):&lt;br /&gt;    print 'Starting %s ...' % service&lt;br /&gt;    p = Process.query.get(service)&lt;br /&gt;    if p.pid is not None:&lt;br /&gt;        if is_running(p.pid):&lt;br /&gt;            print '... %s already running with PID %s' % (&lt;br /&gt;                service, p.pid)&lt;br /&gt;            return&lt;br /&gt;    p.pid = 0&lt;br /&gt;    session.commit()&lt;br /&gt;    daemonize(p)&lt;br /&gt;    print '... started %s' % service&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def stop(service):&lt;br /&gt;    print 'Stopping %s ...' % service&lt;br /&gt;    p = Process.query.get(service)&lt;br /&gt;    if not p.pid or not is_running(p.pid):&lt;br /&gt;        print '... service %s is already stopped' % service&lt;br /&gt;        p.pid = None&lt;br /&gt;        session.commit()&lt;br /&gt;        return&lt;br /&gt;    for retry in range(5):&lt;br /&gt;        print '... sending SIGTERM to %s' % p.pid&lt;br /&gt;        os.kill(p.pid, signal.SIGTERM)&lt;br /&gt;        time.sleep(0.5)&lt;br /&gt;        if not is_running(p.pid):&lt;br /&gt;            break&lt;br /&gt;    else:&lt;br /&gt;        print '... sending SIGKILL to %s' % p.pid&lt;br /&gt;        os.kill(p.pid, signal.SIGKILL)&lt;br /&gt;        time.sleep(0.5)&lt;br /&gt;        if is_running(p.pid):&lt;br /&gt;            print '... process %s could not be killed' % p.pid&lt;br /&gt;            return&lt;br /&gt;    p.pid = None&lt;br /&gt;    session.commit()&lt;br /&gt;    print '... %s is stopped' % service&lt;br /&gt;&lt;br /&gt;@Command&lt;br /&gt;def restart(service):&lt;br /&gt;    stop([service])&lt;br /&gt;    start([service])        &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    Finally, I need to hook the script up and make sure it runs from the command line:    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;def main():&lt;br /&gt;    Command.run(sys.argv[1:])&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;    main()        &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    And there you have it!  A userspace daemonizer that lets you manage an arbitrary number of services.  It is definitely overkill in many ways, but hopefully the sharing the process of building it will be as educational to you as it was to me.  &lt;br /&gt;  &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-1590857584426864441?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/SAYoyA5n7fw_PGY1qhKmV3EDQHc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/SAYoyA5n7fw_PGY1qhKmV3EDQHc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/SAYoyA5n7fw_PGY1qhKmV3EDQHc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/SAYoyA5n7fw_PGY1qhKmV3EDQHc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gwjaC6Urj6E:Zaug6k-bTsg:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gwjaC6Urj6E:Zaug6k-bTsg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gwjaC6Urj6E:Zaug6k-bTsg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gwjaC6Urj6E:Zaug6k-bTsg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gwjaC6Urj6E:Zaug6k-bTsg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/gwjaC6Urj6E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/1590857584426864441/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=1590857584426864441" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/1590857584426864441?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/1590857584426864441?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/gwjaC6Urj6E/little-command-line-love.html" title="A Little Command Line Love" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/08/little-command-line-love.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YFSX49eCp7ImA9WxdbFkg.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-3894110025501563295</id><published>2008-08-13T12:12:00.003-04:00</published><updated>2008-08-13T14:11:58.060-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-13T14:11:58.060-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><title>Miruku - Migrations for SQLALchemy</title><content type="html">One of the painful things about working with any database-oriented project in production is that you can't just drop the database and re-create every time you have a schema change.  (Of course, you &lt;em&gt;could&lt;/em&gt; do that, but your users might get a little miffed when their data disappears.)  Rails and Django have for this reason had support for migrating from one schema version to another.  Well, now SQLAlchemy has a automatic migrations tool by the name of Miruku.  I haven't tried it, and it's an extremely early version (0.1a7), but it looks promising.  Have a look &lt;a href="http://trac.ollix.org/miruku/"&gt;here&lt;/a&gt;, and let me know what you think.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-3894110025501563295?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/XvjoOrDv77NKUwoXP9x-kgh0SUY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/XvjoOrDv77NKUwoXP9x-kgh0SUY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/XvjoOrDv77NKUwoXP9x-kgh0SUY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/XvjoOrDv77NKUwoXP9x-kgh0SUY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=VFlWnmhiOJI:-8WO6SPvy_I:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=VFlWnmhiOJI:-8WO6SPvy_I:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=VFlWnmhiOJI:-8WO6SPvy_I:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=VFlWnmhiOJI:-8WO6SPvy_I:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=VFlWnmhiOJI:-8WO6SPvy_I:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/VFlWnmhiOJI" height="1" width="1"/&gt;</content><link rel="related" href="http://trac.ollix.org/miruku/" title="Miruku - Migrations for SQLALchemy" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/3894110025501563295/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=3894110025501563295" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3894110025501563295?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3894110025501563295?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/VFlWnmhiOJI/miruku-migrations-for-sqlalchemy.html" title="Miruku - Migrations for SQLALchemy" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/08/miruku-migrations-for-sqlalchemy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQESHk6fip7ImA9WxdVFkk.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-6125057634920682046</id><published>2008-07-18T11:30:00.005-04:00</published><updated>2008-07-21T09:18:29.716-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-21T09:18:29.716-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="cherrypy" /><category scheme="http://www.blogger.com/atom/ns#" term="REST" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="turbogears" /><title>RESTfulness in TurboGears</title><content type="html">&lt;a href="http://compoundthinking.com/blog/"&gt;Mark Ramm&lt;/a&gt; and I were talking about how to differentiate GET, POST, PUT, and DELETE in &lt;a href="http://turbogears.org/2.0/docs/"&gt;TurboGears 2&lt;/a&gt; and we came up with a syntax that's pretty cool.  That's not the reason for this post, though.  This morning, I noticed that our syntax is completely compatible with TurboGears 1.x -- so here's how you do it. &lt;br /&gt;    &lt;span class="fullpost"&gt;&lt;br /&gt;      What we wanted was to expose a RESTful method like this:&lt;br /&gt;      &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class Root(controllers.RootController):&lt;br /&gt;&lt;br /&gt;    class index(RestMethod):&lt;br /&gt;        @expose('json')&lt;br /&gt;        def get(self, **kw):&lt;br /&gt;            return dict(method='GET', args=kw)&lt;br /&gt;        @expose('json')&lt;br /&gt;        def post(self, **kw):&lt;br /&gt;            return dict(method='POST', args=kw)&lt;br /&gt;        @expose('json')&lt;br /&gt;        def put(self, **kw):&lt;br /&gt;            return dict(method='PUT', args=kw)&lt;br /&gt;        # NOT exposed, for some reason&lt;br /&gt;        def delete(self, **kw):&lt;br /&gt;            return dict(method='DELETE', args=kw)&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;      The TurboGears 1 implementation relies on the way that CherryPy determines whether a given property is a valid URL controller.  It basically looks at the property and checks to make sure that:&lt;br /&gt;      &lt;ul&gt; &lt;br /&gt;        &lt;li&gt;it's callable, and&lt;/li&gt;&lt;br /&gt;        &lt;li&gt;it has a property &lt;code&gt;exposed&lt;/code&gt; which is true (or&lt;br /&gt;          "truthy")&lt;/li&gt;&lt;br /&gt;      &lt;/ul&gt;&lt;br /&gt;      The "a-ha!" moment came when realizing that:&lt;br /&gt;      &lt;ul&gt;&lt;br /&gt;        &lt;li&gt;Classes are callable in Python (calling a class == instantiating an object)&lt;/li&gt;&lt;br /&gt;        &lt;li&gt;Classes can have &lt;code&gt;exposed&lt;/code&gt; attributes&lt;/li&gt;&lt;br /&gt;      &lt;/ul&gt;&lt;br /&gt;      So with appropriate trickery behind the scenes, the above syntax should work as-is.  So here's the "appropriate trickery behind the scenes":&lt;br /&gt;      &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class RestMeta(type):&lt;br /&gt;    def __new__(meta,name,bases,dct):&lt;br /&gt;        cls = type.__new__(meta, name, bases, dct)&lt;br /&gt;        allowed_methods = cls.allowed_methods = {}&lt;br /&gt;        for name, value in dct.items():&lt;br /&gt;            if callable(value) and getattr(value, 'exposed', False):&lt;br /&gt;                allowed_methods[name] = value&lt;br /&gt;        return cls&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;      The first thing I wanted to do was create a metaclass to use for &lt;code&gt;RestMethod&lt;/code&gt; so that I could save the allowed HTTP methods.  Nothing &lt;em&gt;too&lt;/em&gt; complicated here.&lt;br /&gt;      &lt;code&gt;&lt;pre&gt;&lt;br /&gt;import cherrypy as cp&lt;br /&gt;class ExposedDescriptor(object):&lt;br /&gt;    def __get__(self, obj, cls=None):&lt;br /&gt;        if cls is None: cls = obj&lt;br /&gt;        allowed_methods = cls.allowed_methods&lt;br /&gt;        cp_methodname = cp.request.method&lt;br /&gt;        methodname = cp_methodname.lower()&lt;br /&gt;        if methodname not in allowed_methods:&lt;br /&gt;            raise cp.HTTPError(405, '%s not allowed on %s' % (&lt;br /&gt;                cp_methodname, cp.request.browser_url))&lt;br /&gt;        return True&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;      This next thing is tricky.  If you don't understand what a "descriptor" is, I suggest the very nice description &lt;a href="http://users.rcn.com/python/download/Descriptor.htm"&gt;here&lt;/a&gt;.   The basic thing I get here is the ability to intercept a reference to a class attribute the same way the property() builtin intercepts references to object attributes.  &lt;br /&gt;&lt;br /&gt;      The idea here is to use this descriptor as the &lt;code&gt;exposed&lt;/code&gt; attribute on the &lt;code&gt;RestMethod&lt;/code&gt; class.  When CherryPy tries to figure out if the method is exposed, it calls &lt;code&gt;ExposedDescriptor.__get__&lt;/code&gt; and uses the result as the value of &lt;code&gt;exposed&lt;/code&gt;.  If the HTTP method in question is not exposed, then the code raises a nice HTTP 405 error, which is the correct response to sending, say, a POST to a method expecting only GETs.&lt;br /&gt;&lt;br /&gt;      The final part of the solution, the actual RestMethod, is actually pretty simple:&lt;br /&gt;      &lt;code&gt;&lt;pre&gt;&lt;br /&gt;class RestMethod(object):&lt;br /&gt;    __metaclass__ = RestMeta&lt;br /&gt;&lt;br /&gt;    exposed = ExposedDescriptor()&lt;br /&gt;&lt;br /&gt;    def __init__(self, *l, **kw):&lt;br /&gt;        methodname = cp.request.method.lower()&lt;br /&gt;        method = self.allowed_methods[methodname]&lt;br /&gt;        self.result = method(self, *l, **kw)&lt;br /&gt;&lt;br /&gt;    def __iter__(self):&lt;br /&gt;        return iter(self.result)&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;      The sequence of things is now this:&lt;br /&gt;      &lt;ul&gt;&lt;br /&gt;        &lt;li&gt;CherryPy traverses along to the root.index class and looks up root.index.exposed&lt;/li&gt;&lt;br /&gt;        &lt;li&gt;root.index.exposed is intercepted by the descriptor which checks the CherryPy request method to see if it's valid for this controller, and if it is, returns True&lt;/li&gt;&lt;br /&gt;        &lt;li&gt;CherryPy says, "Great! root.index is exposed.  So now I'll call root.index(...)."  This calls &lt;code&gt;index&lt;/code&gt;'s constructor, which in turn calls the appropriate method and saves the result in self.result&lt;/li&gt;&lt;br /&gt;        &lt;li&gt;CherryPy says "Cool!  root.index returned me an iterable object.  I'll iterate over it to get the text to send back to the browser."  This calls root.index(...).__iter__, which is just delegated to the result that the real controller gave.&lt;br /&gt;        &lt;/li&gt;&lt;br /&gt;      &lt;/ul&gt;&lt;br /&gt;      At then end, we get a fully REST compliant controller with a nice (I think) syntax.&lt;br /&gt;&lt;br /&gt;      &lt;em&gt;&lt;strong&gt;Update 2008-07-21:&lt;/strong&gt;&lt;/em&gt; After some more thinking, I realized that the metaclass isn't necessary.  The descriptor is more important, but also not strictly necessary.  My original design did everything in the constructor of RestMethod, but this runs &lt;b&gt;after&lt;/b&gt; all of TurboGears' validation and identity checking happens, so it's pretty inefficient.  If you want the implementation with the descriptor but without the metaclass, you can do this:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;import cherrypy as cp&lt;br /&gt;class ExposedDescriptor(object):&lt;br /&gt;    def __get__(self, obj, cls=None):&lt;br /&gt;        if cls is None: cls = obj&lt;br /&gt;        cp_methodname = cp.request.method&lt;br /&gt;        methodname = cp_methodname.lower()&lt;br /&gt;        method = getattr(cls, methodname, None)&lt;br /&gt;        if callable(method) and getattr(method, 'exposed', False):&lt;br /&gt;            return True&lt;br /&gt;        raise cp.HTTPError(405, '%s not allowed on %s' % (&lt;br /&gt;            cp_methodname, cp.request.browser_url))&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;The benefit to using a metaclass is that the check for the existence, "callability", and exposed-ness of the method happens up front rather than on each request.  Also, I don't like the fact that the metaclass pollutes the class namespace with the "allowed_methods" attribute.  There's probably a way to clean that up and put the descriptor in a closure, but I haven't had time to look at it.  Maybe that will be a future post....&lt;br /&gt;    &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-6125057634920682046?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Hygml04kjpz7_JyOXe_eKC7DAhs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Hygml04kjpz7_JyOXe_eKC7DAhs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Hygml04kjpz7_JyOXe_eKC7DAhs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Hygml04kjpz7_JyOXe_eKC7DAhs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=eg1WlZIjzho:G-2Q3ekC9s4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=eg1WlZIjzho:G-2Q3ekC9s4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=eg1WlZIjzho:G-2Q3ekC9s4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=eg1WlZIjzho:G-2Q3ekC9s4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=eg1WlZIjzho:G-2Q3ekC9s4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/eg1WlZIjzho" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/6125057634920682046/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=6125057634920682046" title="13 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6125057634920682046?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6125057634920682046?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/eg1WlZIjzho/restfulness-in-turbogears.html" title="RESTfulness in TurboGears" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">13</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/07/restfulness-in-turbogears.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4MQn09fip7ImA9WxdWGEo.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-2493185480699047090</id><published>2008-07-11T11:35:00.004-04:00</published><updated>2008-07-12T09:56:23.366-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-12T09:56:23.366-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="cascade" /><title>Cascade Rules in SQLAlchemy</title><content type="html">Last night at the PyAtl meeting, there was a question about how you define your cascade rules in SQLAlchemy mappers.  I'll confess that it confused me at first, too, but here's all you need to know:&lt;br /&gt;  &lt;span class="fullpost"&gt;&lt;br /&gt;    What's "cascading" in the mapper is session-based operations.  This includes putting an object into the session (saving it), deleting an object from the session, etc.  Generally, &lt;em&gt;you don't care&lt;/em&gt; about all that stuff, because it Just Works most of the time, as long as you specify &lt;code&gt;cascade="all"&lt;/code&gt; on your relation() properties in your mappers.  What this means is "whatever session operation you do to the mapped class, do it to the related class as well".&lt;br /&gt;    &lt;br /&gt;    One little confusing thing is that there's another thing you'll often want to specify in your cascade rules, and that's the "delete-orphan".  In fact, most of my 1:N relation()s look like:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;mapper(ParentClass, parent, properties=dict(&lt;br /&gt;    children=relation(ChildClass, backref='parent', &lt;br /&gt;                      cascade='all,delete-orphan')&lt;br /&gt;    )&lt;br /&gt;)&lt;br /&gt;      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    The "delete-orphan" specifies that if you ever have a ChildClass instance that is "orphaned", that is, not connected to some ParentClass, go ahead and delete that ChildClass.  You want to specify this whenever you &lt;em&gt;don't&lt;/em&gt; want ChildClass instances hanging out with null ParentClass references.  Note that even if you don't specify "delete-orphan", deletes on the ParentClass instance will still cascade to related ChildClass instances.  An example is probably best.  Say you have the following schema and mapper setup:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;photo = Table(&lt;br /&gt;    'photo', metadata,&lt;br /&gt;    Column('id', Integer, primary_key=True))&lt;br /&gt;tag = Table(&lt;br /&gt;    'tag', metadata,&lt;br /&gt;    Column('id', Integer, primary_key=True),&lt;br /&gt;    Column('photo_id', None, ForeignKey('photo.id')),&lt;br /&gt;    Column('tag', String(80)))&lt;br /&gt;&lt;br /&gt;class Photo(object): pass&lt;br /&gt;        &lt;br /&gt;class Tag(object): pass&lt;br /&gt;        &lt;br /&gt;session.mapper(Photo, photo, properties=dict(&lt;br /&gt;    tags=relation(Tag, backref='photo', cascade="all"),&lt;br /&gt;    session.mapper(Tag, tag)&lt;br /&gt;      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    I'll go ahead and create some photos and tags:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;p1 = Photo(tags=[&lt;br /&gt;    Tag(tag='foo'),&lt;br /&gt;    Tag(tag='bar'),&lt;br /&gt;    Tag(tag='baz') ])&lt;br /&gt;p2 = Photo(tags=[&lt;br /&gt;    Tag(tag='foo'),&lt;br /&gt;    Tag(tag='bar'),&lt;br /&gt;    Tag(tag='baz') ])&lt;br /&gt;session.flush()&lt;br /&gt;session.clear()&lt;br /&gt;      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    Now if I delete one of the photos, I'll delete the tags associated&lt;br /&gt;    with it, as well:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; for t in Tag.query():&lt;br /&gt;...     print t.id, t.photo_id, t.tag&lt;br /&gt;... &lt;br /&gt;1 1 foo&lt;br /&gt;2 1 bar&lt;br /&gt;3 1 baz&lt;br /&gt;4 2 foo&lt;br /&gt;5 2 bar&lt;br /&gt;6 2 baz&lt;br /&gt;&gt;&gt;&gt; session.delete(Photo.query.get(1))&lt;br /&gt;&gt;&gt;&gt; session.flush()&lt;br /&gt;&gt;&gt;&gt; for t in Tag.query():&lt;br /&gt;...     print t.id, t.photo_id, t.tag&lt;br /&gt;... &lt;br /&gt;4 2 foo&lt;br /&gt;5 2 bar&lt;br /&gt;6 2 baz&lt;br /&gt;      &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    At this point, everything is the same whether I specify&lt;br /&gt;    "delete-orphan" or not.  The difference is in what happens when I&lt;br /&gt;    just remove an item from a photo's "tags" collection:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; p2 = Photo.query.get(2)&lt;br /&gt;&gt;&gt;&gt; del p2.tags[0]&lt;br /&gt;&gt;&gt;&gt; session.flush()&lt;br /&gt;&gt;&gt;&gt; for t in Tag.query():&lt;br /&gt;...     print t.id, t.photo_id, t.tag&lt;br /&gt;... &lt;br /&gt;4 None foo&lt;br /&gt;5 2 bar&lt;br /&gt;6 2 baz&lt;br /&gt;       &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    See how the "foo" tag is just hanging out there with no photo?&lt;br /&gt;    That's what "delete-orphan" is designed to prevent.  If we'd&lt;br /&gt;    specified "delete-orphan", we'd have the following result:&lt;br /&gt;    &lt;code&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; p2 = Photo.query.get(2)&lt;br /&gt;&gt;&gt;&gt; del p2.tags[0]&lt;br /&gt;&gt;&gt;&gt; session.flush()&lt;br /&gt;&gt;&gt;&gt; for t in Tag.query():&lt;br /&gt;...     print t.id, t.photo_id, t.tag&lt;br /&gt;... &lt;br /&gt;5 2 bar&lt;br /&gt;6 2 baz&lt;br /&gt;       &lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;    So there you go.  If you don't mind orphans, then use&lt;br /&gt;    &lt;code&gt;cascade="all"&lt;/code&gt; and leave off the&lt;br /&gt;    "delete-orphan".  If you'd rather have them disappear when&lt;br /&gt;    disconnected from their parent, use&lt;br /&gt;    &lt;code&gt;cascade="all,delete-orphan"&lt;/code&gt;.&lt;br /&gt;  &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-2493185480699047090?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ZpTZQm9B_B-LQNMYdwJdhoqPU84/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZpTZQm9B_B-LQNMYdwJdhoqPU84/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ZpTZQm9B_B-LQNMYdwJdhoqPU84/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZpTZQm9B_B-LQNMYdwJdhoqPU84/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y-XZHRjKYtg:WbbxO_9kj5M:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y-XZHRjKYtg:WbbxO_9kj5M:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y-XZHRjKYtg:WbbxO_9kj5M:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=y-XZHRjKYtg:WbbxO_9kj5M:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=y-XZHRjKYtg:WbbxO_9kj5M:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/y-XZHRjKYtg" height="1" width="1"/&gt;</content><link rel="related" href="http://www.sqlalchemy.org" title="Cascade Rules in SQLAlchemy" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/2493185480699047090/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=2493185480699047090" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/2493185480699047090?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/2493185480699047090?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/y-XZHRjKYtg/cascade-rules-in-sqlalchemy.html" title="Cascade Rules in SQLAlchemy" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/07/cascade-rules-in-sqlalchemy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cAQns-eyp7ImA9WxdWGEo.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-6640564863057558848</id><published>2008-07-11T11:04:00.016-04:00</published><updated>2008-07-12T09:57:23.553-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-12T09:57:23.553-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="wxpython" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="wxwindows" /><title>PyAtl: SQLAlchemy Theme Night</title><content type="html">Well, last night was the Python Atlanta user group meeting (PyAtl).  It had been a while since I've been, and I'd forgotten how fun it can be.  The theme was &lt;a href="http://www.sqlalchemy.org"&gt;SQLAlchemy&lt;/a&gt;, and the speaker lineup was me, Brandon Craig Rhodes, and James Fowler.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;The meeting started off with "shooting the breeze" as usual, and then moved into my presentation "Essential SQLAlchemy", which gives a 30 minute overview of the basics of SQLAlchemy.  Here are the usual links to &lt;a href="http://files.meetup.com/127119/EssentialSQLAlchemy.pdf"&gt;slides&lt;/a&gt; and the video:&lt;br /&gt;&lt;br /&gt;&lt;embed id="VideoPlayback" style="width:400px;height:326px" allowFullScreen="true" src="http://video.google.com/googleplayer.swf?docid=2139688260328269384&amp;hl=en&amp;fs=true" type="application/x-shockwave-flash"&gt; &lt;/embed&gt;&lt;br /&gt;&lt;br /&gt;After my talk, &lt;a href="http://rhodesmill.org/brandon/software/"&gt;Brandon Craig Rhodes&lt;/a&gt; (who is, by the way, an incredibly lively presenter, using nothing but emacs!) gave a talk "SQLAlchemy Advanced Mappings" that focused on using the ORM layer in SQLAlchemy.  It really was more of a mini-tutorial that took you through basic mappings all the way through relations, backrefs, and more.  SQLAlchemy is an amazingly rich library, and it's &lt;em&gt;hard&lt;/em&gt; to squeeze a talk into half an hour.  Here's the video:&lt;br /&gt;&lt;br /&gt;&lt;embed id="VideoPlayback" style="width:400px;height:326px" allowFullScreen="true" src="http://video.google.com/googleplayer.swf?docid=7582038483043089057&amp;hl=en&amp;fs=true" type="application/x-shockwave-flash"&gt; &lt;/embed&gt;&lt;br /&gt;&lt;br /&gt;After Brandon, &lt;a href="http://python.meetup.com/46/members/7430331/"&gt;James Fowler&lt;/a&gt; did a "now for something completely different" kind of talk on wxPython, "WxPython Quick Bite", focusing on how you can make &lt;a href="http://www.wxpython.org"&gt;wxPython&lt;/a&gt; (designed to be event-driven and single-threaded) play nicely in a multi-threaded environment.  Unfortunately the start of the video was cut off as I feverishly tried to download the other two videos to make room for James's talk.  I'll post the video as soon as it gets uploaded.&lt;br /&gt;&lt;br /&gt;I'd be remiss if I didn't thank O'Reilly for "sponsoring" the meetup with a giveaway of a number of books (including 9 copies of &lt;a href="http://www.amazon.com/dp/0596516142?tag=pythonisitobl-20&amp;camp=14573&amp;creative=327641&amp;linkCode=as1&amp;creativeASIN=0596516142&amp;adid=00MW7R31EJM36T7RDQY9&amp;"&gt;Essential SQLAlchemy&lt;/a&gt;, which I stuck around afterwards to sign).  We also had a couple of copies of &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FBeautiful-Code-Leading-Programmers-Practice%2Fdp%2F0596510047%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1215789909%26sr%3D1-1&amp;tag=pythonisitobl-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;Beautiful Code&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=pythonisitobl-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;, &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FBeginning-Game-Development-Python-Pygame%2Fdp%2F1590598725%2F&amp;tag=pythonisitobl-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;Beginning Development with Python Gaming&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=pythonisitobl-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt;, and &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FHackerteen-1-Internet-Blackout%2Fdp%2F0596516479%3Fie%3DUTF8%26s%3Dbooks%26qid%3D1215790248%26sr%3D1-1&amp;tag=pythonisitobl-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325"&gt;Hackerteen&lt;/a&gt;&lt;img src="http://www.assoc-amazon.com/e/ir?t=pythonisitobl-20&amp;amp;l=ur2&amp;amp;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /&gt; to give away.  A great time was had by all!&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-6640564863057558848?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/T8oRnvOHCkM-RhJ1ypGn6qZva1E/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/T8oRnvOHCkM-RhJ1ypGn6qZva1E/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/T8oRnvOHCkM-RhJ1ypGn6qZva1E/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/T8oRnvOHCkM-RhJ1ypGn6qZva1E/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=cZTLxw5X6uc:YCcGoAIe4eI:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=cZTLxw5X6uc:YCcGoAIe4eI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=cZTLxw5X6uc:YCcGoAIe4eI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=cZTLxw5X6uc:YCcGoAIe4eI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=cZTLxw5X6uc:YCcGoAIe4eI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/cZTLxw5X6uc" height="1" width="1"/&gt;</content><link rel="related" href="http://pyatl.org/presentations" title="PyAtl: SQLAlchemy Theme Night" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/6640564863057558848/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=6640564863057558848" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6640564863057558848?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6640564863057558848?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/cZTLxw5X6uc/pyatl-sqlalchemy-theme-night.html" title="PyAtl: SQLAlchemy Theme Night" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/07/pyatl-sqlalchemy-theme-night.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMMQX4-eCp7ImA9WxdRF0Q.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-6163096943415979498</id><published>2008-06-06T17:05:00.004-04:00</published><updated>2008-06-06T19:21:20.050-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-06T19:21:20.050-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="python programming" /><title>Essential SQLAlchemy Ships</title><content type="html">Well, I finally got my hands on the first (that I know of) copy of my book Essential SQLAlchemy.  You can get it from Amazon using the link below.  (Disclaimer: this is my affiliate link.  Hey, it's my blog after all, right? ;-) )&lt;br /&gt;&lt;iframe src="http://rcm.amazon.com/e/cm?t=pythonisitobl-20&amp;o=1&amp;p=8&amp;l=as1&amp;asins=0596516142&amp;fc1=000000&amp;IS2=1&amp;lt1=_blank&amp;lc1=0000FF&amp;bc1=000000&amp;bg1=FFFFFF&amp;f=ifr" style="width:120px;float:left;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;I think the book turned out well, although I was a little surprised at how thin it was.  200 or so pages seems like a lot more when you're writing (or reading) it.  Many thanks go out to all who put up with me during the writing of the book, including my coworkers, editors, reviewers, and my amazing wife Nancy.  (Not to mention my son Matthew, who was born during the proofreading phase of the book.  I wisely delayed my proofreading until we all got home from the hospital.)&lt;br /&gt;&lt;br /&gt;Anyway, here is a picture of me with the book at work taken by my coworker Jenny Walsh on her iPhone:&lt;br /&gt;&lt;img src="http://farm4.static.flickr.com/3029/2556400008_34d13c43b7.jpg?v=0" style="float:right"/&gt;&lt;br /&gt;&lt;br /&gt;I'll try to post some more about SQLAlchemy at some point.  It really is a fantastic library.  Thanks go to Mike Bayer and all the other contributors for writing it!&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Edit 7:16pm June 6, 2008&lt;/em&gt;&lt;br /&gt;As I have been reminded by Noah, I will be presenting a talk on SQLAlchemy for the July 10th Python Atlanta user group PyAtl, followed by a book signing.  I should have a few books available there to sell, or you can (obviously) bring our own.  You can RSVP for the meeting at the &lt;a href="http://python.meetup.com/46/"&gt;PyAtl Meetup Site&lt;/a&gt;.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-6163096943415979498?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/283Mylh8xuA8FDj8KjmdJdd4F9M/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/283Mylh8xuA8FDj8KjmdJdd4F9M/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/283Mylh8xuA8FDj8KjmdJdd4F9M/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/283Mylh8xuA8FDj8KjmdJdd4F9M/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=6r6Woo0EBdE:rB8-OnfgU_Y:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=6r6Woo0EBdE:rB8-OnfgU_Y:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=6r6Woo0EBdE:rB8-OnfgU_Y:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=6r6Woo0EBdE:rB8-OnfgU_Y:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=6r6Woo0EBdE:rB8-OnfgU_Y:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/6r6Woo0EBdE" height="1" width="1"/&gt;</content><link rel="related" href="http://www.amazon.com/gp/product/0596516142?ie=UTF8&amp;tag=pythonisitobl-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0596516142" title="Essential SQLAlchemy Ships" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/6163096943415979498/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=6163096943415979498" title="9 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6163096943415979498?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/6163096943415979498?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/6r6Woo0EBdE/essential-sqlalchemy-ships.html" title="Essential SQLAlchemy Ships" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/06/essential-sqlalchemy-ships.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cCQn89fSp7ImA9WxZWE0g.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-3401947638375035974</id><published>2008-03-12T15:36:00.002-04:00</published><updated>2008-03-12T15:44:23.165-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-12T15:44:23.165-04:00</app:edited><title>Essential SQLAlchemy Available on Amazon</title><content type="html">I thought I'd give a heads up to the readers of this blog that my book on SQLAlchemy, &lt;a href="http://www.amazon.com/Essential-SQLAlchemy-Rick-Copeland/dp/0596516142/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1205350633&amp;sr=8-1"&gt;Essential SQLAlchemy&lt;/a&gt;, being published by O'Reilly, is now available for pre-order on Amazon.com.  Amazon lists the availability date as June 15, and I have no information beyond that.&lt;br /&gt;&lt;br /&gt;P.S. In other news, I also thought I'd mention that I would have loved to give a talk at PyCon (on SQLAlchemy or something else), but I am reluctant to travel for a few weeks due to the upcoming arrival of my first child, just in case he comes early (he is due on April 9th).  So maybe next year -- to everyone who's going, have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-3401947638375035974?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/F3HEVtpvj2oE_3YsL0CBIIJRaQg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/F3HEVtpvj2oE_3YsL0CBIIJRaQg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/F3HEVtpvj2oE_3YsL0CBIIJRaQg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/F3HEVtpvj2oE_3YsL0CBIIJRaQg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=zaDlEt0uj_E:ZKnkYVfI430:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=zaDlEt0uj_E:ZKnkYVfI430:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=zaDlEt0uj_E:ZKnkYVfI430:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=zaDlEt0uj_E:ZKnkYVfI430:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=zaDlEt0uj_E:ZKnkYVfI430:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/zaDlEt0uj_E" height="1" width="1"/&gt;</content><link rel="related" href="http://www.amazon.com/Essential-SQLAlchemy-Rick-Copeland/dp/0596516142/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1205350633&amp;sr=8-1" title="Essential SQLAlchemy Available on Amazon" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/3401947638375035974/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=3401947638375035974" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3401947638375035974?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3401947638375035974?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/zaDlEt0uj_E/essential-sqlalchemy-available-on.html" title="Essential SQLAlchemy Available on Amazon" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/03/essential-sqlalchemy-available-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMDR3ozfyp7ImA9WxZQEk0.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-3111510033512412943</id><published>2008-02-14T11:07:00.003-05:00</published><updated>2008-02-16T18:11:16.487-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-16T18:11:16.487-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="datalog" /><title>PyAtl BloxAlchemy Talk</title><content type="html">I gave a talk at the &lt;a href="http://www.pyatl.org"&gt;Atlanta Python user group&lt;/a&gt; last night on a project I've been working on called BloxAlchemy.  BloxAlchemy is bascially a SQLAlchemy-inspired library that targets a logical database known as LogicBlox instead of a SQL database.  I've uploaded the slides &lt;a href="http://files.meetup.com/127119/BloxAlchemy.pdf"&gt;here&lt;/a&gt;, if you're interested.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;Update 2/16/08&lt;/b&gt;&lt;/i&gt;: The video of the talk is now available on Google video &lt;a href="http://video.google.com/videoplay?docid=2130832537579149296"&gt;here&lt;/a&gt;.  If you're going to watch the talk, I'd recommend keeping the slides opened in a separate window while watching, since I make a lot of references to code on the slides that isn't in the video.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-3111510033512412943?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/gA8ZPk4x8C3f-_tIWaEZTm-jPVw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/gA8ZPk4x8C3f-_tIWaEZTm-jPVw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/gA8ZPk4x8C3f-_tIWaEZTm-jPVw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/gA8ZPk4x8C3f-_tIWaEZTm-jPVw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=O6gxgaHzftc:US26tedv0IU:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=O6gxgaHzftc:US26tedv0IU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=O6gxgaHzftc:US26tedv0IU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=O6gxgaHzftc:US26tedv0IU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=O6gxgaHzftc:US26tedv0IU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/O6gxgaHzftc" height="1" width="1"/&gt;</content><link rel="related" href="http://files.meetup.com/127119/BloxAlchemy.pdf" title="PyAtl BloxAlchemy Talk" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/3111510033512412943/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=3111510033512412943" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3111510033512412943?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3111510033512412943?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/O6gxgaHzftc/pyatl-bloxalchemy-talk.html" title="PyAtl BloxAlchemy Talk" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/02/pyatl-bloxalchemy-talk.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQGRH84eCp7ImA9WB9aF04.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-5536162456422957367</id><published>2008-01-07T14:51:00.000-05:00</published><updated>2008-01-07T15:05:25.130-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-07T15:05:25.130-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="posgresql" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="sqlalchemy" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><title>Cascading DROP TABLE with SQLAlchemy</title><content type="html">A little quirk that can get you if you're using SQLAlchemy to create and drop your database is that PostgreSQL doesn't allow you to drop a table that has other tables referring to it via FOREIGN KEY constraints.  PostgreSQL has a DROP TABLE ... CASCADE command that supports this (and drops all the dependent tables) but there's no easy way to use the DROP TABLE ... CASCADE statement.  It's not too hard to make it available, though.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;It turns out, however, that SQLAlchemy has a nice, pluggable database dialect system that is fairly simple to update.  One part of this dialect system is a "SchemaDropper".  So to cascade the DROP TABLE statements, I just created the following SchemaDropper (derived from the existing PostgreSQL PGSchemaDropper) and installed it as the default PostgreSQL dialect schemadropper.  (Most of the code is copied from the base SchemaDropper class in sqlalchemy.sql.compiler)&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from sqlalchemy.databases import postgres&lt;br /&gt;&lt;br /&gt;class PGCascadeSchemaDropper(postgres.PGSchemaDropper):&lt;br /&gt;     def visit_table(self, table):&lt;br /&gt;        for column in table.columns:&lt;br /&gt;            if column.default is not None:&lt;br /&gt;                self.traverse_single(column.default)&lt;br /&gt;        self.append("\nDROP TABLE " +&lt;br /&gt;                    self.preparer.format_table(table) +&lt;br /&gt;                    " CASCADE")&lt;br /&gt;        self.execute()&lt;br /&gt;&lt;br /&gt;postgres.dialect.schemadropper = PGCascadeSchemaDropper&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And that's it!&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-5536162456422957367?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ZFeEG3bH8nnt3AGlKLFcm6EOd2U/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZFeEG3bH8nnt3AGlKLFcm6EOd2U/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ZFeEG3bH8nnt3AGlKLFcm6EOd2U/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZFeEG3bH8nnt3AGlKLFcm6EOd2U/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=_tg_7EqG64g:1kKQkh7P_mM:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=_tg_7EqG64g:1kKQkh7P_mM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=_tg_7EqG64g:1kKQkh7P_mM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=_tg_7EqG64g:1kKQkh7P_mM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=_tg_7EqG64g:1kKQkh7P_mM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/_tg_7EqG64g" height="1" width="1"/&gt;</content><link rel="related" href="http://www.luckydonkey.com/2007/11/23/postgresql-sqlalchemy-dropping-all-tables-and-sequences/" title="Cascading DROP TABLE with SQLAlchemy" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/5536162456422957367/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=5536162456422957367" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/5536162456422957367?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/5536162456422957367?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/_tg_7EqG64g/cascading-drop-table-with-sqlalchemy.html" title="Cascading DROP TABLE with SQLAlchemy" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.pythonisito.com/2008/01/cascading-drop-table-with-sqlalchemy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYMSX0-eyp7ImA9WBFWE0g.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-1598803137041896794</id><published>2007-03-31T11:00:00.000-04:00</published><updated>2007-03-31T11:16:28.353-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-03-31T11:16:28.353-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python programming" /><title>Five things I hate about Python</title><content type="html">It's been a meme of late to blog about 5 things you hate about your favorite programming language, in order to qualify you to complain about other languages.  (It proves your "objectivity.")  What I really wanted to do is blog about things I like about Python, but to go along with the current theme, here is my list of the top 5 things I hate about Python.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;Lack of support for blocks&lt;/em&gt; - I have a bit of an esoteric desire here: I want to use Python-like syntax to build a DSL for compiling into hardware description language, essentially using Python as a macro language on top of the DSL.  I want to be able to create my own control structures (hardware_if:...., etc.).  Python doesn't let me do this.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;Stdlib organization (or lack thereof)&lt;/em&gt; - Maybe this is inevitable, as the stdlib is made up of all sorts of things integrated from other packages, but it would be nice to have something closer to what the C# and Java people rave about in their respective languages.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;Inefficient tail calls (tail recursion)&lt;/em&gt; - Maybe not important to too many people since Python has such cool iteration support, but it would be nice to be able to write a tail-recursive function and know that it will be optimized into a loop.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;No "nonlocal" rebinding of functions&lt;/em&gt; - This will be fixed in Python 3, which is cool, but it's not there right now.  Basically, you can read variables from enclosing scopes right now, but you can't "rebind them", that is, assign the name to a new object.  All assignments are either in the local scope (default) or module scope (if you declare the name "global").  And honestly, the main reason I hate this is that it gives Paul Graham one more negative thing to say about Python relative to LISP.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;em&gt;No dictionary comprehensions&lt;/em&gt; - And it's &lt;a href="http://www.python.org/dev/peps/pep-0274/"&gt;not gonna happen&lt;/a&gt; any time soon.  List comprehensions are &lt;em&gt;wonderful&lt;/em&gt;, and I'd like to build a dict in the same way.  I know I can do dict((k,v) for k,v in seq).  But I don't like the double-left-paren there.  OK, so it's a nit.  But wouldn't it be nicer just to say {k:v for k,v in seq}?&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Well, there it is.  5 things I hate.  Now I can get on to writing about the things I &lt;em&gt;like&lt;/em&gt;.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-1598803137041896794?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/tn0PzjXENAw9SuLvNRAfG_-M3Fs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/tn0PzjXENAw9SuLvNRAfG_-M3Fs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/tn0PzjXENAw9SuLvNRAfG_-M3Fs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/tn0PzjXENAw9SuLvNRAfG_-M3Fs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=rw7qU9yk44w:RYzaWN5cKls:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=rw7qU9yk44w:RYzaWN5cKls:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=rw7qU9yk44w:RYzaWN5cKls:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=rw7qU9yk44w:RYzaWN5cKls:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=rw7qU9yk44w:RYzaWN5cKls:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/rw7qU9yk44w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/1598803137041896794/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=1598803137041896794" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/1598803137041896794?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/1598803137041896794?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/rw7qU9yk44w/five-things-i-hate-about-python.html" title="Five things I hate about Python" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total><feedburner:origLink>http://blog.pythonisito.com/2007/03/five-things-i-hate-about-python.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YDQnc6fyp7ImA9WBFWGEw.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-9195679220757917605</id><published>2007-03-25T20:46:00.000-04:00</published><updated>2007-04-05T18:12:53.917-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-04-05T18:12:53.917-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="dynamic" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><title>Dynamic Language Weenies?</title><content type="html">I saw the linked article and couldn't help but get irritated.  I know I shouldn't get this worked up about programming languages, but I did anyway.  (Perhaps it was the unnecessarily abrasive tone of the author....)  Rather than making you wade through the rather misinformed article, let me summarize and refute some of the author's main points here.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;&lt;br /&gt;[Edit 3/31/07 @11:58pm - It looks like some of the points I complain about in this article have been changed in the original post.  I haven't gone over the article line-by-line, but the article I am responding to was posted on 3/25/07, while the one currently posted is dated 3/26/07, so please take that into account when reading this (3/25/07) article.  Some of the main points that were changed in the original post had to do with the use of the confusing terms "weak" and "strong" typing.  There may be others.]&lt;br /&gt;&lt;br /&gt;[Edit 4/5/07 @5:55pm - I should make it clear that when I refer to "static languages" in the article below, I do so in the same sense that the original author refers to "static languages" -- languages in the style of C, C++, Java, C#, Pascal, etc.  I am aware of statically languages such as Haskell and ML which convey many of the productivity benefits of "dynamic" languages in a statically typed environment.  ]&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Brief aside #1: Non-weenie credentials&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I have worked as a commercial hardware and software developer in Real Jobs now for about 15 years.  I have used, in production scenarios, C, C++, C#, Dynamic C (embedded programming), VHDL, Verilog, SQL, Javascript, and Python.  I have written embedded microcontroller programs, C compilers targeting FPGAs and exotic dataflow (MONARCH) architectures, multiprocessor simulators, enterprise workflow applications, and high-volume web sites.  I have implemented digital logic for the StarCore SC140s DSP core, as well as designed various IP digital logic cores, including a Viterbi decoder and an SHA-1 hashing engine.  I am not a weenie.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Somewhat less brief aside #2: Muddled thinking about typing&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Next, and this gets me every time, the author confuses the (at least) three axes of typing.  The first axis is the strong/weak axis, and generally puts languages such as C, perl and rexx on the weak end and most everything else on the strong end.  The deciding question here is whether the language implicitly converts unrelated types without warning, allowing you to add the integer 0 to the string "10" and arrive at the result "010" (or is it 11?  I forget.).  Strongly typed languages will cry foul, where weakly typed will simply do what they think you mean and continue.&lt;br /&gt;&lt;br /&gt;The second axis is static versus dynamic typing [Edit 4/3/07]&lt;span style="text-decoration:line-through"&gt;, also known as early versus late binding&lt;/span&gt;.  This has entirely to do with how names (variables) are resolved in the language.  In statically typed languages, a name (variable) is associated with a type, and that name (variable) can never reference a value of any other type.  In dynamically typed languages, a name may be associated with values of different types over its lifetime.  Languages such as C/C++, Java, Pascal, Haskell, OCAML, etc. fall into the static category (with some dynamic capabilities in C++ and Java through runtime dynamic type casting), while languages such as Ruby, Python, etc. fall into the dynamic category.  Many languages have support for both, including Lisp and the aforementioned Java and C++.&lt;br /&gt;&lt;br /&gt;The third axis is manifest versus implicit typing, and it is a fascinating axis.  (Note that this axis is really only applicable to statically typed languages, so it might not really even be an axis in its own right, but I think it's worth looking at here.)  Implicitly typed languages such as OCAML, although they are most definitely statically typed and compiled, actually perform quite advanced type inference on the code to determine what types you intended your variables to be, generally by analyzing which operations they participate in.  Remarkably, an OCAML compiler is able to produce extremely optimized, statically and strongly typed code, even in the absence of explicit type declarations.  RPython (part of the PyPy project) is example of an implicitly typed subset of Python whose compiler is able to produce highly optimized, statically typed code.&lt;br /&gt;&lt;br /&gt;The author of the "weenies" article conflates all three axes into strong versus weak, and puts C/C++ and Java on the "strong" side, with Ruby, Python, etc. on the "weak" side, while ignoring other languages such as Haskell, LISP, OCAML, etc.  Which hopefully you can see is a gross oversimplification.  If you're interested, my current language of choice, Python, is a strongly, dynamically typed language.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Aside #3: Ignorance of other &lt;span style="font-style: italic;"&gt;strong&lt;/span&gt; advantages of dynamic languages&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The author left out what I consider to be two of the most important features, productivity-wise, of my current chosen language, Python: built-in polymorphic containers and high-order functions.  (These are also present in most dynamic languages, but I'll discuss them in the context of Python, because that's what I'm familiar with.)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Built-in polymorphic containers&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Python has, built into the language, the following containers: lists, tuples, dictionaries ('dicts'), and strings.  And when I say that they are built-in, I mean not merely that the language includes facilities to manipulate the structures, but that it also includes a convenient literal syntax to &lt;span style="font-style: italic;"&gt;define&lt;/span&gt; them.  A list of the integers from 1 to 10, for instance, is represented as [1,2,3,4,5,6,7,8,9,10].  Lists have methods that mimic the union of arrays and linked lists in other languages, and even support recursive definition (not that I've used it):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&gt;&gt;&gt; lst = [1,2,3]&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&gt;&gt;&gt; lst.append(lst)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&gt;&gt;&gt; lst&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;[1,2,3,[...]]&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&gt;&gt;&gt; lst[3]&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;[1,2,3,[...]]&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&gt;&gt;&gt; lst[3][3]&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;[1,2,3,[...]]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Tuples are similar to "immutable lists", and are often used to return multiple values from a function:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;a,b,c = foo(d,e,f)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This also illustrates a corrolary property of tuples and lists, the "destructuring assignment", that allows you to "unpack" structured data in a very succinct way.&lt;br /&gt;&lt;br /&gt;Dictionaries are nice syntactic sugar for hashtables, and allow you to use any "hashable" object as a key to index any Python object:  {1:'foo', 2:'bar', 'the third item':'baz', (1,2):'baff'}  This also illustrates that all these containers are polymorphic (though in practice the types in a container are usually restricted).  Strings need little explanation, except to say that they are built in, unlike C/C++ strings.&lt;br /&gt;&lt;br /&gt;Why do all these types make things easier?  Mainly because they're already included.  To use a list in C++, you have to #include the right header and declare the type of things that go in the list.  To use a vector in C++, you also have to #include the right header (a different header, if I remember correctly) and declare the type of things that go in the vector.  And good luck if you want to use a hash table.  For that, you not only have to #include the header and declare the types of key and object, but you also have to provide a hashing function.  Wouldn't it be nice if the language took care of all that for you?  (Yes, it is very nice.)  If you're using C++, you're also stuck with no literal syntax for specifying anything but the simplest structures, and the enormous pitfall of confusing STL string&lt;&gt;s with char[]s.  Dynamic languages (like Python) so significantly lower the bar on creating data structures that you'll often see lists of dicts or tuples where C++ or Java would be littered with utility classes, type declarations (and in pre-generics Java, typecasts).  I mean, come on -- do I really need to declare a pair&lt;t1,t2&gt; class if I want a list of points?  And a separate RGB class if I want color values?  Give me a break.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;High-order functions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Simply put, this is the ability to treat a function as an object in your code, and the utility of this feature is difficult to overstate.  C++ really tries to do this with templates, and Boost::Lambda gets 95% of the way there, but wouldn't it be nice if you didn't have to jump through so many hoops?  Python includes features such as map (apply a function to every element in a container), reduce (apply a 2-argument function to every pair of elements in a container until it's reduced to one element), filter (find all elements in a container for which a predicate function returns true), and lambda (define an anonymous function inline).  C++ has support for these, but you have to create a functor object (which Boost::Lambda makes mercifully simpler).  Actual C++ functions are thus second-class citizens.  If you are a C++ or Java language programmer, it may never have occurred to you to write a function that takes a function as a parameter and returns a function as its result.  If you are a dynamic language programmer, you probably wrote three of these last week.  It's higher-order thinking, and it's simply not supported as well in most static languages.&lt;br /&gt;&lt;br /&gt;I should probably pause for a moment and make the point that the C++ templating system is a Turing-complete, dynamically typed, functional programming language that happens to be interpreted at compile time.  (I have a compile-time factorial program that I can show you if you don't believe me.)  Its syntax leaves much to be desired, but it's semantically much closer to the dynamic language camp than the language onto which it was bolted on, C++.&lt;br /&gt;&lt;br /&gt;OK, on to the author's main points, which he presents in a claim/reality format:&lt;br /&gt;&lt;br /&gt;&lt;/t1,t2&gt;&lt;blockquote&gt;&lt;br /&gt;Claim: Weak Typing, Interpretation and Reduced Code Volume Increase Development Speed&lt;br /&gt;&lt;br /&gt;Reality: No they don't, either individually or together. ....&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;My reality check: &lt;span style="font-style: italic;"&gt;Dynamic&lt;/span&gt; typing, interpretation, and reduced code volume do indeed increase development speed.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Dynamic Typing&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Have you ever tried to write a &lt;span style="font-style: italic;"&gt;really&lt;/span&gt; static C++ program?  You know, where you actually declare all the methods that &lt;span style="font-weight: bold;"&gt;don't&lt;/span&gt; modify your class as "const" methods?  I tried it.  Once.  Might have tried it again if I didn't have to work with other people.  Dynamically typed languages &lt;span style="font-weight: bold;"&gt;do&lt;/span&gt; increase development speed, although their impact is somewhat mitigated in larger projects where enforcement of interfaces becomes more important.  Where they really shine, however, is in their "genericity."  C++ tried to do generics with templates, and it succeeded to some extent.  I'm sure there are other examples in other languages.  Dynamic languages give you what are essentially C++ templated functions &lt;span style="font-style: italic;"&gt;for every function you write&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Interpretation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Interpretation helps, not so much because compile time is prohibitive in static projects, but because the REPL (read-eval-print loop) is so freaking easy.  Want to try out something quickly?  Paste it into your interactive shell.  Static languages are beginning to understand this, with some IDEs providing a little interactive shell.  But how long did it take to "invent" this feature (which was present in Lisp in the 1960s)?  Interpretation also facilitates the exploration of new language features in a way that statically compiled languages have a really hard time keeping up with.  Take it from someone who has written both interpreters and compilers: it is easier to add a feature to an interpreter than it is to a compiler.  OCAML does some amazing things in their compiler.  You're going to have a hard time convincing me they can extend the language easier than the PyPy team, however.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Reduced Code Volume&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Reduced code volume certainly does reduce development time trivially -- less typing.  More importantly, however, it allows you to fit larger concepts onto one screenful of code.  The units at which you are programming are larger.  Also important to note is the correlation of bug count with source lines of code, independent of language used.  That means that, roughly, 1000 lines of assembly has the same bug count as 1000 lines of Lisp.  Which one do you think accomplishes more?  Reduced code volume is easier and faster to code, debug, and maintain.  I can't understand how the author could even imagine this not to be true.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Claim: Support From Major Companies Legitimizes DLs&lt;br /&gt;&lt;br /&gt;Reality: No it doesn't. Companies know that fan boys like you are easy marks - an enthusiastic and indiscriminate market segment ripe for exploitation. They also know that you might spread your naive enthusiasms into your workplaces, opening up a corporate market for supporting tools.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;My reality check: OK, fine.  Companies are driven by profit, so I can accept that corporate profit chasing has little to do with the quality of a language.  But this cuts both ways.  Java has been pushed by Sun, and C# by Microsoft.  Neither would have anywhere &lt;span style="font-weight: bold;"&gt;near&lt;/span&gt; the market share they currently have without their corporate backers.&lt;br /&gt;&lt;br /&gt;But let's leave aside corporations "supporting" the languages.  Let's look at those who actually get things done.  Yahoo! stores was originally written in Lisp.  BitTorrent in Python.  Google and NASA use Python extensively.  The OLPC project is using Python as their core O/S language.  37signals uses (and invented) Ruby on Rails.  Reddit is Python (was Lisp).  YouTube runs on Python.  And tell me, how many thin-client applications use Java applets (static language) versus Javascript (dynamic language)?  And that's even &lt;span style="font-weight: bold;"&gt;with&lt;/span&gt; the hellish problem of browser inconsistency.&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Claim: As the Problems Change, People Use New Languages&lt;br /&gt;&lt;br /&gt;Reality: As languages change, people remain the same. Software development is now, and always has been, driven by an obsession with novelty, and that is what drives language adoption. If there is a new problem to solve, that will simply make for a convenient excuse. Your misplaced enthusiasm simply perpetuates a cycle of self-defeating behaviour that prevents software development maturing into a true profession.&lt;br /&gt;&lt;/blockquote&gt;My reality check: Yes, people remain the same.  However, the resources we use do not.  CPU cycles and memory are relatively cheap today.  That's why no one (except some embedded developers) can get away with saying they need to program in assembly language.  Runtime performance is objectively less constraining now than it was 10 years ago for the same problems.  Which means that all the things we &lt;span style="font-weight: bold;"&gt;wish&lt;/span&gt; we could have done in 1997 are available now.  Like dynamic, interpreted languages.&lt;br /&gt;&lt;br /&gt;Language is a tool for expressing ideas.  Some languages express different ideas more easily, or with greater difficulty, than others.  Try saying ninety-nine in French, if you don't believe me (&lt;span style="font-style: italic;"&gt;quatre-vingt-dix-neuf&lt;/span&gt;, literally four twenty ten nine).  Programming languages are no different.  Things have been learned about better ways to express yourself since C++, Java, and C# were invented.  C++, Java , and C# also chose to ignore certain things that were well-known in programming language research when they were invented due to design decisions that were made in a technological context dissimilar to today.&lt;br /&gt;&lt;br /&gt;And for one further reality check, no, language adoption is not driven by novelty.  Java introduced nothing whatsoever that was new.  It started with known features of C++, removed a bunch of stuff, added a garbage collector that had been understood since the days of the VAX, and threw an ungodly amount of marketing behind it.  Java is no longer new, but it is still widespread.  C is certainly not new, and its popularity remains astonishingly high.  Language adoption is driven by a wide range of factors, including but by no means dominated by novelty.&lt;br /&gt;&lt;blockquote&gt;Claim: You Can Assess Productivity By Feel&lt;br /&gt;&lt;br /&gt;Reality: No you can't. You're just trying to justify personal preference by hiding it behind a legitimate but definitionally complex term. If you've never taken measurements, you have approximately no idea what your productivity is like either with or without your favorite dynamic language. You certainly can't assess the difference between the two.&lt;br /&gt;&lt;/blockquote&gt;My reality check: The article's author hasn't taken measurements, either.  But Lutz Prechelt at least has &lt;span style="font-weight: bold;"&gt;some&lt;/span&gt; data, where the linked article presents none.  In fact, &lt;span style="text-decoration:line-through"&gt;without exception&lt;/span&gt;, all studies &lt;span style="text-decoration:underline"&gt;of which I am aware&lt;/span&gt; [Ed: 4/5/07] which have compared productivity in languages between compiled, manifestly, statically typed languages and interpreted, dynamically typed languages have the dynamic languages easily winning out.&lt;br /&gt;&lt;br /&gt;But what the author ignores is that the "feel" of a language, while not providing objective &lt;span style="font-style: italic;"&gt;evidence&lt;/span&gt; of its productivity, is an influencing &lt;span style="font-style: italic;"&gt;factor&lt;/span&gt; in its productivity due to the increased motivation to work in a dynamic language.  If I like how a language "feels", I will use it more.  I will be a more motivated employee, producing more.  If I do open source work, I will work more on it, producing more libraries and facilitating code reuse, which even the most jaded non-weenie must admit is a Good Thing.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Claim: Syntax Can Be Natural&lt;br /&gt;&lt;br /&gt;Reality: All programming languages are arcane and cryptic, in different ways and to varying degrees. What is perceived as "natural" varies tremendously between individuals, depending upon their experience and background. Your mischaracterisation of a syntax as "natural" is just an attempt to retro-fit a philosophy to your personal preferences.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;My reality check:  No syntax is completely natural, but some have more in common with non-programming languages than others.  For instance, Haskell invented a wonderful syntax for specifying lists: the "list comprehension":&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;[x + 2*x + x/2 | x &lt;- [1,2,3,4]]  &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;OK, that looks weird if you've never seen it before.  But does it have an analogue outside of programming?  How about&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;{ x + 2*x + x/2 | x in {1,2,3,4} }  &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's just about pure mathematical notation for a set.  Python took a compromise approach and writes the more "verbal":&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;[ x + 2*x + x/2 for x in [1,2,3,4] ]  &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;How do I say this in C++?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#include&amp;lt;list&gt;&lt;br /&gt;std::list&amp;lt;int&gt; mklist() {&lt;br /&gt; std::list&amp;lt;int&gt; l;&lt;br /&gt; for(int x = 1; x&lt;= 4; x++)    &lt;br /&gt;   l.push_back(x + 2*x + x/2);  &lt;br /&gt; return l;&lt;br /&gt;} &lt;/pre&gt;&lt;br /&gt;Which feels more "natural" to you?  I see in programming language design two big threads, of equal power but radically different approach, which I will name by certain scientists who inspired the respective approaches.  One is the "Chuch" thread, where languages express mathematics.  The other is the "Turing" thread, where languages command machines to accomplish tasks.  Roughly, this puts languages into "declarative" and "imperative" camps.  Dynamic languages pull ideas from both camps.  Static languages (at least C/C++, Java, and C#) are heavily imperative, and have little support for declarative concepts.  Sure, neither is particularly "natural," but dynamic languages have more expressive capabilities and can approach a "natural" syntax more easily than manifestly static languages.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Claim: A Strength Of My Language Is Its Community&lt;br /&gt;&lt;br /&gt;Reality: If it it[sic], then you are in deep trouble, for your community appears to be dominated by juveniles who only take time out from self-gratification long enough to wipe the byproducts off their keyboard, then mindlessly flame anyone who does not share their adolescent enthusiasms.&lt;br /&gt;&lt;/blockquote&gt;My Reality Check: The community is a strength, no doubt about it.  But communities are made up of all types.  For every blithering idiot, there may be five or ten solid programmers pounding out production-quality code.  I had to make a choice tonight -- write this article or work on my application.  Maybe I made the wrong choice.  Many of the juveniles of which you write don't have that choice, being without the requisite skills to create an application.  Of course, that raises the question of what you were doing writing the article....&lt;br /&gt;&lt;blockquote&gt;Claim: No Harm, No Foul&lt;br /&gt;&lt;br /&gt;Reality: No Brain, No Pain.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;And the &lt;span style="font-style: italic;"&gt;dynamic&lt;/span&gt; languages people are juvenile?....&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-9195679220757917605?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ZbXO6eCMGZDdQWX4-pLNASqRnHQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZbXO6eCMGZDdQWX4-pLNASqRnHQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ZbXO6eCMGZDdQWX4-pLNASqRnHQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZbXO6eCMGZDdQWX4-pLNASqRnHQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=lyTDUNGH7Gw:xLTljDJkhT4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=lyTDUNGH7Gw:xLTljDJkhT4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=lyTDUNGH7Gw:xLTljDJkhT4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=lyTDUNGH7Gw:xLTljDJkhT4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=lyTDUNGH7Gw:xLTljDJkhT4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/lyTDUNGH7Gw" height="1" width="1"/&gt;</content><link rel="related" href="http://www.hacknot.info/hacknot/action/showEntry?eid=93" title="Dynamic Language Weenies?" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/9195679220757917605/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=9195679220757917605" title="31 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/9195679220757917605?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/9195679220757917605?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/lyTDUNGH7Gw/dynamic-language-weenies.html" title="Dynamic Language Weenies?" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">31</thr:total><feedburner:origLink>http://blog.pythonisito.com/2007/03/dynamic-language-weenies.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYNSXY9eyp7ImA9WBFREko.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-3030224528928827758</id><published>2007-02-23T17:43:00.000-05:00</published><updated>2007-02-23T18:43:18.863-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-02-23T18:43:18.863-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="ajax" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="compiler" /><title>Templates in Javascript</title><content type="html">For my own use, and now also my employer's, I have created a simplistic Javascript templating library "JST".  Basically, it compiles a simple templating language into an executable Javascript function which generates HTML.  It's mainly useful for replacing DOM generation in Javascript (which is &lt;em&gt;slow&lt;/em&gt;) with a fast function which builds a string that you can use to set the .innerHTML property of certain DOM nodes (which is &lt;em&gt;fast&lt;/em&gt;).&lt;br /&gt;&lt;br /&gt;If you're interested, I'm at PyCon in Dallas this weekend and would be glad to show off the code if you're here, too.  (I may end up doing a 5-minute lightning talk about it).  If this interests you, or if you want a copy of the JST =&gt; Javascript compiler, send me an email or leave a comment.  Just to whet your appetite, here's a sample template that makes three really big nested tables (outer tables are 5x5, each cell of which is another 5x5 table).  Using JST, these tables can be rendered by Firefox in less than 2 seconds.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;    &amp;lt;head&gt;&lt;br /&gt;        &amp;lt;title&gt;Hi there&amp;lt;/title&gt;&lt;br /&gt;    &amp;lt;/head&gt;&lt;br /&gt;    &amp;lt;body&gt;&lt;br /&gt;        &amp;lt;h1&gt;This is my template test: $v.name, ${[1,2,3].length} ${[1].length}&amp;lt;/h1&gt;&lt;br /&gt;        &amp;lt;? function foo() { ?&gt;&lt;br /&gt;        &amp;lt;table border="1"&gt;&lt;br /&gt;            &amp;lt;? for(var i = 0; i &amp;lt; 5; i++) { ?&gt;&lt;br /&gt;            &amp;lt;tr&gt;&lt;br /&gt;                &amp;lt;? for(var j = 0; j &amp;lt; 5; j++) { ?&gt;&lt;br /&gt;                &amp;lt;td&gt;&lt;br /&gt;                    &amp;lt;table&gt;&lt;br /&gt;                        &amp;lt;? for(var k = 0; k &amp;lt; 5; k++) { ?&gt;&lt;br /&gt;                        &amp;lt;tr&gt;&lt;br /&gt;                            &amp;lt;? for(var l = 0; l &amp;lt; 5; l++) { ?&gt;&lt;br /&gt;                            &amp;lt;td&gt;($i,$j,$k,$l,${{a: '1',b: '2',c: '3'\}.c})&amp;lt;/td&gt;&lt;br /&gt;                            &amp;lt;? } ?&gt;&lt;br /&gt;                        &amp;lt;/tr&gt;&lt;br /&gt;                        &amp;lt;? } ?&gt;&lt;br /&gt;                    &amp;lt;/table&gt;&lt;br /&gt;                &amp;lt;/td&gt;&lt;br /&gt;                &amp;lt;? } ?&gt;&lt;br /&gt;            &amp;lt;/tr&gt;&lt;br /&gt;            &amp;lt;? } ?&gt;&lt;br /&gt;        &amp;lt;/table&gt;&lt;br /&gt;        &amp;lt;? } ?&gt;&lt;br /&gt;        &amp;lt;? foo(); foo(); foo(); ?&gt;&lt;br /&gt;    &amp;lt;/body&gt;&lt;br /&gt;&amp;lt;/html&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-3030224528928827758?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/RRtjXIvQvL7BqGBNsaDJfXrddUg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RRtjXIvQvL7BqGBNsaDJfXrddUg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/RRtjXIvQvL7BqGBNsaDJfXrddUg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RRtjXIvQvL7BqGBNsaDJfXrddUg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gBQsqixymJI:5Fgvz6No3SI:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gBQsqixymJI:5Fgvz6No3SI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gBQsqixymJI:5Fgvz6No3SI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gBQsqixymJI:5Fgvz6No3SI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gBQsqixymJI:5Fgvz6No3SI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/gBQsqixymJI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/3030224528928827758/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=3030224528928827758" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3030224528928827758?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/3030224528928827758?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/gBQsqixymJI/templates-in-javascript.html" title="Templates in Javascript" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.pythonisito.com/2007/02/templates-in-javascript.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08NR30_eyp7ImA9WBFSFEw.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-117141189629433701</id><published>2007-02-13T19:03:00.000-05:00</published><updated>2007-02-13T19:11:36.343-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-02-13T19:11:36.343-05:00</app:edited><title>Python's Files</title><content type="html">I was reading the following comment:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;What I didn't like about Python was the fact that OOP seems bolted on. For example, to open a file you use "f = open("text.txt")" but to close it you do "f.close()". Ruby is much more elegant in that everything is an object.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;and I thought I'd explain Python files a little just in case someone else makes the same mistake.  (And by the way, everything in Python is an object, too.)&lt;br /&gt;&lt;br /&gt;In Python, the "open()" function is actually an alias for the "file()" &lt;i&gt;constructor&lt;/i&gt;.  Yes, that's right -- what the commenter thought pointed out a non-object-oriented aspect of Python actually illustrates Python's object-oriented nature.  So you create a file &lt;i&gt;object&lt;/i&gt; 'f' using 'f = file("text.txt")', and you close it you do "f.close()".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-117141189629433701?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/lOkptnb_Q-DbLTMcKrtufkEtIoI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/lOkptnb_Q-DbLTMcKrtufkEtIoI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/lOkptnb_Q-DbLTMcKrtufkEtIoI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/lOkptnb_Q-DbLTMcKrtufkEtIoI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=5WiuuQ_x9GA:FkVyrL5T4Lo:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=5WiuuQ_x9GA:FkVyrL5T4Lo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=5WiuuQ_x9GA:FkVyrL5T4Lo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=5WiuuQ_x9GA:FkVyrL5T4Lo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=5WiuuQ_x9GA:FkVyrL5T4Lo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/5WiuuQ_x9GA" height="1" width="1"/&gt;</content><link rel="related" href="http://jeremy.zawodny.com/blog/archives/007085.html" title="Python's Files" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/117141189629433701/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=117141189629433701" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/117141189629433701?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/117141189629433701?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/5WiuuQ_x9GA/pythons-files.html" title="Python's Files" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2007/02/pythons-files.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU8NQHYyeyp7ImA9WBJWEEw.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-114501789187454898</id><published>2006-04-14T08:28:00.000-04:00</published><updated>2006-04-14T08:31:31.893-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2006-04-14T08:31:31.893-04:00</app:edited><title>Papr.info</title><content type="html">I have just finished getting my new project to the "usable" stage, so I thought I'd go ahead and let you know about it.  It's an academic paper sharing/tagging site (slightly similar to &lt;a href="http://del.icio.us"&gt;del.icio.us&lt;/a&gt;, but for papers).  I guess it's kind of a "mash-up" since it uses at least 3 different sites' APIs (Google, CiteSeer, and Citidel).  Take a look and tell me what you think!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-114501789187454898?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/nzniOvcFQpxTzO7l9PjOML1owr0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/nzniOvcFQpxTzO7l9PjOML1owr0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/nzniOvcFQpxTzO7l9PjOML1owr0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/nzniOvcFQpxTzO7l9PjOML1owr0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=nXoAZleyOes:LtTKJfRNPsk:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=nXoAZleyOes:LtTKJfRNPsk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=nXoAZleyOes:LtTKJfRNPsk:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=nXoAZleyOes:LtTKJfRNPsk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=nXoAZleyOes:LtTKJfRNPsk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/nXoAZleyOes" height="1" width="1"/&gt;</content><link rel="related" href="http://papr.info" title="Papr.info" /><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/114501789187454898/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=114501789187454898" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114501789187454898?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114501789187454898?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/nXoAZleyOes/paprinfo.html" title="Papr.info" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.pythonisito.com/2006/04/paprinfo.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYMSXgzfyp7ImA9WBFWFEs.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-114256454929098529</id><published>2006-03-16T21:37:00.001-05:00</published><updated>2007-04-01T18:23:08.687-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-04-01T18:23:08.687-04:00</app:edited><title>A hypothesis on why Pythonistas reinvent wheels</title><content type="html">Well, maybe the title's more inflammatory than need be.  This post is kind of a follow-up to &lt;a href="http://pythonisito.blogspot.com/2006/01/three-reasons-why-you-shouldnt-write.html"&gt;Three Reasons You Shouldn't Write Your Own Web Framework&lt;/a&gt;, but it's a little broader than that.&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;My hypothesis is that Python has two "things" that appear together in few (if any!) other languages right now, and that the combination of these ingredients makes the proliferation of web frameworks (and libraries, and RSS readers, and wikis, etc....) nearly unavoidable.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Python has power&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;First off, Python is a powerful, expressive language.  Sure, the Lispers out there deride its lack of macros, Rubyists bemoan its "impure OO", and so on.  But Python is expressive &lt;em&gt;enough.&lt;/em&gt;  Enough to lower the amount of pain it inflicts upon programmers to where some would rather build a framework/library that's &lt;em&gt;just right&lt;/em&gt; instead of grabbing something else that's out there.  It's just so stinkin' &lt;em&gt;easy&lt;/em&gt; to code up something &lt;em&gt;quickly&lt;/em&gt; that will do exactly what you want.  (Well, &lt;em&gt;almost&lt;/em&gt; what you want, and you know how to avoid those bugs, anyway. ;-) )&lt;br /&gt;&lt;br /&gt;But there are other languages that are more expressive, concise, "powerful."  Why have they, too, not embarked upon the path of infinite diversity?&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Python has programmers&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Python is one of the most widely-used dynamic languages in the world today.  Yes, Perl is more widely used.  [Edit 4/1/07: In my completely uninformed opinion as someone who has never used Perl for anything even remotely serious,] Perl also makes it very easy to get yourself in trouble very quickly when building a large system.  Not that it can't be done.  Just that it kind of fails the "powerful" criterion above.  Ruby is "getting there," or it may already have surpassed Python's popularity (hard to know for sure with such scientific measurements as "# of Google search results", "# of new books", "# of SourceForge/FreshMeat projects", etc.  But Ruby has not had the time to develop the same critical mass of programmers with a hankering to build their own framework.  Haskell, ML, Smalltalk, and so forth just don't have the base, either.  Lispers built quite a few frameworks/libraries &lt;em&gt;back in the day&lt;/em&gt;, but these were mainly in the realm of AI, so we won't hear much about them until AI comes back in vogue.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What to do?&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;I can't say I really know.  After seeing Ian's tutorial on &lt;a href="http://blog.ianbicking.org/do-it-yourself-already.html"&gt;building your own&lt;/a&gt; WSGI framework, I see the allure.  Working in academia, I also feel the urge to build upon prior research.  Maybe it's the nature of the beast (having a widely-used, expressive language).  What do you think?&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-114256454929098529?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Dan627iBfgW1h90r4SaCvmCZj3s/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Dan627iBfgW1h90r4SaCvmCZj3s/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Dan627iBfgW1h90r4SaCvmCZj3s/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Dan627iBfgW1h90r4SaCvmCZj3s/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=a-CF1ph6ICA:KQza0g-RTWg:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=a-CF1ph6ICA:KQza0g-RTWg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=a-CF1ph6ICA:KQza0g-RTWg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=a-CF1ph6ICA:KQza0g-RTWg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=a-CF1ph6ICA:KQza0g-RTWg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/a-CF1ph6ICA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/114256454929098529/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=114256454929098529" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114256454929098529?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114256454929098529?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/a-CF1ph6ICA/hypothesis-on-why-pythonistas-reinvent_16.html" title="A hypothesis on why Pythonistas reinvent wheels" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.pythonisito.com/2006/03/hypothesis-on-why-pythonistas-reinvent_16.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkACQngzfip7ImA9WBJSFkg.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-114165839168025985</id><published>2006-03-06T09:58:00.000-05:00</published><updated>2006-03-06T10:26:03.686-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2006-03-06T10:26:03.686-05:00</app:edited><title>TG Admin Interface (part II)</title><content type="html">Well, the week was less-than-productive on the automated TG admin pages front.  I did manage to extract the CRUD code from the model class, however.  Here is the (new) code to create an admin page for a given SQLObject model:&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;import model&lt;br /&gt;from crud import crudbase, fields&lt;br /&gt;&lt;br /&gt;class DistributionCenterView(crudbase):&lt;br /&gt;    modelClass=model.DistributionCenter&lt;br /&gt;    displayName='Distribution Center'&lt;br /&gt;    fields=fields('name', 'note',&lt;br /&gt;                  'address.street1', 'address.street2',&lt;br /&gt;                  'address.city', 'address.state', 'address.zip',&lt;br /&gt;                  'stores', 'trucks')&lt;br /&gt;    key='name'&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;this is for the following SQLObject model:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class DistributionCenter(SQLObject):&lt;br /&gt;    def destroySelf(self):&lt;br /&gt;        self.address.destroySelf()&lt;br /&gt;        SQLObject.destroySelf(self)&lt;br /&gt;&lt;br /&gt;    name=StringCol(default=uniqueValue('DistributionCenter', 'name', 'ctr'),&lt;br /&gt;                   alternateID=True)&lt;br /&gt;    note=StringCol(default='')&lt;br /&gt;    address=ForeignKey('Address', default=newAddressID, cascade=False)&lt;br /&gt;    stores=MultipleJoin('Store')&lt;br /&gt;    trucks=MultipleJoin('Truck')&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The resulting list view looks a little like this (simplified a bit for blogger):&lt;br /&gt;&lt;table border="1"&gt;&lt;br /&gt;  &lt;thead&gt;&lt;br /&gt;    &lt;tr&gt;&lt;br /&gt;      &lt;th&gt;&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Name&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Note&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Street1&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Street2&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;City&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;State&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Zip&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Stores&lt;/th&gt;&lt;br /&gt;      &lt;th&gt;Trucks&lt;/th&gt;&lt;br /&gt;    &lt;/tr&gt;&lt;br /&gt;  &lt;/thead&gt;&lt;br /&gt;  &lt;tbody&gt;&lt;br /&gt;    &lt;tr&gt;&lt;br /&gt;      &lt;td&gt;&lt;br /&gt; &lt;a href="#"&gt;Edit&lt;/a&gt; &lt;br /&gt; &lt;a href="#"&gt;Delete&lt;/a&gt;&lt;br /&gt;      &lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Dist Ctr A&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;A note on dca&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;123 Main Street&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Marietta&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;GA&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;30062&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Home Dep 3&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;SXG 456,ABC 123&lt;/td&gt;&lt;br /&gt;    &lt;/tr&gt;&lt;br /&gt;    &lt;tr&gt;&lt;br /&gt;      &lt;td&gt;&lt;br /&gt; &lt;a href="#"&gt;Edit&lt;/a&gt;&lt;br /&gt; &lt;a href="#"&gt;Delete&lt;/a&gt;&lt;br /&gt;      &lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Center 2&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Another Center&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;456 Beechwood Ave.&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Marietta&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;GA&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;30067&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;Store Num 1,store_2&lt;/td&gt;&lt;br /&gt;      &lt;td&gt;333 HFF&lt;/td&gt;&lt;br /&gt;    &lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;As you can see, foreign keys and multiple joins are handled, as well as columns from foreign tables (via the "address.street1..." stuff).  I'm still working on cleaning things up, but if this code would be a useful starting point for you, drop me a comment and I'll email you a copy.  Alternatively, if there are several people interested, we'll set up a project on SourceForge.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-114165839168025985?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/saqgfx760nf1Uuach4-zOYk3bVQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/saqgfx760nf1Uuach4-zOYk3bVQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/saqgfx760nf1Uuach4-zOYk3bVQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/saqgfx760nf1Uuach4-zOYk3bVQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=42MSZjBUYf8:bpm6K5CPfTg:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=42MSZjBUYf8:bpm6K5CPfTg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=42MSZjBUYf8:bpm6K5CPfTg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=42MSZjBUYf8:bpm6K5CPfTg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=42MSZjBUYf8:bpm6K5CPfTg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/42MSZjBUYf8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/114165839168025985/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=114165839168025985" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114165839168025985?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114165839168025985?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/42MSZjBUYf8/tg-admin-interface-part-ii.html" title="TG Admin Interface (part II)" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.pythonisito.com/2006/03/tg-admin-interface-part-ii.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak4DQno-fip7ImA9WBJSE0Q.&quot;"><id>tag:blogger.com,1999:blog-18508356.post-114108030613661688</id><published>2006-02-27T17:45:00.000-05:00</published><updated>2006-03-03T10:16:13.456-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2006-03-03T10:16:13.456-05:00</app:edited><title>Django-like automated admin interface</title><content type="html">&lt;span class="technoratitag"&gt;Categories: &lt;a href="http://del.icio.us/rick446/python" rel="tag"&gt;python&lt;/a&gt;, &lt;a href="http://del.icio.us/rick446/programming" rel="tag"&gt;programming&lt;/a&gt;, &lt;a href="http://del.icio.us/rick446/turbogears" rel="tag"&gt;turbogears&lt;/a&gt;, &lt;a href="http://del.icio.us/rick446/" rel="tag"&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;As I continue to upgrade &lt;a href="http://www.consultracker.com"&gt;ConsulTracker&lt;/a&gt; and work on my new (not-yet announced) project, I often find myself wanting a nice &lt;span class="caps"&gt;CRUD&lt;/span&gt;-like interface, something akin to what I&amp;#8217;ve heard &lt;a href="http://www.djangoproject.com"&gt;Django&lt;/a&gt; has.  While &lt;a href="http://www.oreillynet.com/onlamp/blog/2006/01/shifting_gears_switching_to_dj.html"&gt;less masochistic&lt;/a&gt; developers might go for the &amp;#8220;real deal&amp;#8221; of Django, I chose to begin adding admin-style &lt;span class="caps"&gt;CRUD&lt;/span&gt; pages for &lt;a href="http://www.turbogears.org"&gt;TurboGears&lt;/a&gt;, the Python web framework with which I&amp;#8217;m most familiar.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="fullpost"&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Requirements&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Now, TurboGears already has something called &amp;#8220;FastData&amp;#8221;, but I&amp;#8217;ve found it somewhat limiting.  What I started with, then, is a set of requirements:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Table View&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;There should be a (HTML) table view of the (SQL) table which shows all rows of the (SQL) table.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The columns shown for each table must be customizable with a minimum of custom code.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When a column contains a foreign key, something &amp;#8220;useful&amp;#8221; must show up in that column (e.g. not the numeric &amp;#8220;id&amp;#8221; column of the foreign table)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The table should have the capability of displaying columns from foreign-key&amp;#8217;d tables as native columns.  (This is kind of like creating an &lt;span class="caps"&gt;SQL&lt;/span&gt; view containing columns from multiple tables.)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The table should have a link for editing each row, a link for marking a row for deletion, and a &lt;em&gt;button&lt;/em&gt; for actually deleting the marked items.  This is really a pet peeve of mine.  &lt;span class="caps"&gt;GET&lt;/span&gt;-style links should not modify data in a well-designed web application.  That can lead to all sorts of nastiness, especially when dealing with web spiders that follow all links.  So I&amp;#8217;ll keep all the data-modifying stuff in buttons.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;From these requirements, I determined that I needed some kind of information, call it &amp;#8220;CRUD metadata&amp;#8221; or crudmeta for short, which contained the following information for each SQLObject model I want to view:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The fields to include in the table (note that these can be sub-fields, as in &amp;#8220;address.street1&amp;#8221;, etc.)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The &amp;#8220;name&amp;#8221; of each row in the table&amp;#8212;this is for displaying the row as a &amp;#8220;foreign key&amp;#8221; in another table.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Edit View&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;There should be an &amp;#8220;edit&amp;#8221; view which allows for the following functionality:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Each SQLObject property refrenced in &amp;#8220;crudmeta&amp;#8221; should have a place on the form.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The fields in the form should make sense for their data type.  This means:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;TextFields for StringCol, IntCol, FloatCol, etc.  (with data validation)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;CalendarDate[Time]Picker for Date[Time]Col&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Select fields for ForeignKey &amp;#8211; populated with the &amp;#8220;name&amp;#8221; from crudmeta, of course!&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Tables for MultiJoin fields with the similar functionality as the &amp;#8220;Table View&amp;#8221; above.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;span class="caps"&gt;AJAX&lt;/span&gt;-y goodness to allow you to &amp;#8220;drill down&amp;#8221; in the object hierarchy without losing your context.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Note that some (most) of these fields have TurboGears 0.9 widgets-style names.  Not a coincidince.  I want to reuse as much as possible from existing TurboGears development so as to avoid &amp;#8220;re-inventing the wheel.&amp;#8221;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Status &amp;amp; next steps&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;As of today (2006&amp;#8211;02-27), the basic functionality is there.  It&amp;#8217;s not pretty, it doesn&amp;#8217;t handle errors well, it needs refactoring badly, and it mixes model and view a bit from &lt;span class="caps"&gt;MVC&lt;/span&gt;, but it exists.  My current plan is to clean it up a bit this week and (hopefully) make it available by next weekend.  To whet your appetite, here is an (abbreviated) model from my new application with the &lt;span class="caps"&gt;CRUD&lt;/span&gt; annotations present.  (Currently, this is all the custom code you need in order to create the above-described interface.)&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  class Store(SQLObject):&lt;br /&gt;      class crudmeta:&lt;br /&gt;          fields=crud.fields(&amp;#8216;name&amp;#8217;, &amp;#8216;note&amp;#8217;,&lt;br /&gt;                             &amp;#8216;address.street1&amp;#8217;, &amp;#8216;address.street2&amp;#8217;,&lt;br /&gt;                             &amp;#8216;address.city&amp;#8217;, &amp;#8216;address.state&amp;#8217;, &amp;#8216;address.zip&amp;#8217;,&lt;br /&gt;                             (&amp;#8216;distributionCenter&amp;#8217;, &amp;#8216;Distribution Center&amp;#8217;), &lt;br /&gt;                             &amp;#8216;orders&amp;#8217;)&lt;br /&gt;          key=&amp;#8216;name&amp;#8217;&lt;br /&gt;  &lt;br /&gt;      name=StringCol(default=uniqueValue(&amp;#8216;Store&amp;#8217;, &amp;#8216;name&amp;#8217;, &amp;#8216;store&amp;#8217;),&lt;br /&gt;                     alternateID=True)&lt;br /&gt;      note=StringCol(default=&amp;#8217;&amp;#8217;)&lt;br /&gt;      distributionCenter=ForeignKey(&amp;#8216;DistributionCenter&amp;#8217;,default=None, &lt;br /&gt;                                    cascade=None)&lt;br /&gt;      address=ForeignKey(&amp;#8216;Address&amp;#8217;,default=newAddressID)&lt;br /&gt;      orders=MultipleJoin(&amp;#8216;StoreOrder&amp;#8217;)&lt;br /&gt;      deliveries=MultipleJoin(&amp;#8216;Delivery&amp;#8217;)&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/18508356-114108030613661688?l=blog.pythonisito.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/KBaJx1-S4zWHWdbr0NwNyrDmPVQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KBaJx1-S4zWHWdbr0NwNyrDmPVQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/KBaJx1-S4zWHWdbr0NwNyrDmPVQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KBaJx1-S4zWHWdbr0NwNyrDmPVQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gdnHiRSVkW0:5RKYAKYAmAw:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gdnHiRSVkW0:5RKYAKYAmAw:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gdnHiRSVkW0:5RKYAKYAmAw:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:l6gmwiTKsz0"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?d=l6gmwiTKsz0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/JustALittlePython?a=gdnHiRSVkW0:5RKYAKYAmAw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/JustALittlePython?i=gdnHiRSVkW0:5RKYAKYAmAw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/JustALittlePython/~4/gdnHiRSVkW0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.pythonisito.com/feeds/114108030613661688/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=18508356&amp;postID=114108030613661688" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114108030613661688?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/18508356/posts/default/114108030613661688?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/JustALittlePython/~3/gdnHiRSVkW0/django-like-automated-admin-interface.html" title="Django-like automated admin interface" /><author><name>Rick Copeland</name><uri>http://www.blogger.com/profile/11612114223288841087</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="03926169927015009454" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://blog.pythonisito.com/2006/02/django-like-automated-admin-interface.html</feedburner:origLink></entry></feed>
