Armin Ronacher

PHP Developer Quits

written by Armin Ronacher, on Sunday, July 30, 2006 0:00.

Some days ago sniper aka Jani Taskinen quit his work for the PHP project. At first I had to laugh because I thought be quit because PHP sucks ;-). But then I read the comments on slashdot...

mkavanagh2 posted on slashdot this irclog:

<_sniper_> hehehehe..
<_sniper_> all other members of the UN security council wanted to
           condemn Israel for attacking the UN post but USA (freedom and
           democracy) vetoed it....Israel says the resolution was fair.
<_sniper_> hell yeah..
<_sniper_> NUKE ISRAEL!
<_sniper_> I'm so full of that fucking country..
<Shai-Tan> indeed
<_sniper_> Eye for an eye..I'll kill one Israel officer for one of ours,
           is that fair?
<_sniper_> I bet I'll be hanged for that.
<_sniper_> They kill one of my brother-in-arms-for-peace..I think I'm 
           entitled to kill one of their nazis.
<_sniper_> Hezbollah, where can I enlist?
<_sniper_> FYI: I don't care at all what anybody thinks about me. I'm 
           going to be openly anti-Israel from now on. This was the last
           straw for me. Fuck you jews.
<_sniper_> I will also quit this project. As long as it's backed by some
           Israel company, I don't want to have anything to do with it.
<_sniper_> Good bye.
<-- _sniper_ (~jani@a88-112-115-63.elisa-laajakaista.fi) has left #php.pecl

And from another comment i know that:

[...] Jani himself has served as a UN peace keeper in Southern Lebanon and has been at the receiving end of both Israel and Hezbollah fire. This may entitle him to have strong personal feelings about the recent death of a Finnish peace keeper at the same location, along ones from other countries.

Why can't humans stop wars? I really hate that. Now there is another person in that world that can't stand Jews just because of a stupid war.

Setuptools Plugins

written by Armin Ronacher, on Sunday, July 30, 2006 0:00.

Welcome to part #2 of the Python Plugin System Tutorial. In this part of the tutorial I'll explain how to use setuptools to create a plugin system.

It really looks like eggs are the most hyped python related thing next to WSGI at the moment. Many applications and Frameworks like TurboGears, Trac, Paste and Pylons use eggs for their plugins. This isn't surprising since eggs are very easy to distribute.

The core of setuptools are the two modules setuptools and pkg_resources. The former is required for the setup.py which is the core of each egg. The latter allows us to query those created eggs when they export entrypoints.

WTF are entrypoints?

The peak wiki explains entry points in this manner:

setuptools supports creating libraries that "plug in" to extensible applications and frameworks, by letting you register "entry points" in your project that can be imported by the application or framework.

For example, suppose that a blogging tool wants to support plugins that provide translation for various file types to the blog's output format. The framework might define an "entry point group" called blogtool.parsers, and then allow plugins to register entry points for the file extensions they support.

This would allow people to create distributions that contain one or more parsers for different file types, and then the blogging tool would be able to find the parsers at runtime by looking up an entry point for the file extension (or mime type, or however it wants to).

So if we look back to the first part of the tutorial we used a list of capabilities. Basically we could Now create an entrypoint of each of those capabilitities. But we won't do that. Why? Because of three reasons:

  • Each time you change one entrypoint you have to rebuild the egg-info folder.
  • When the developer adds an feature to the plugin he would have to edit the setup.py file too.
  • It's more complex to implement

You still want to use more than one entry point you can do so but this tutorial won't cover that case.

In this example the application is called myapplication.py and all plugins lay in an subfolder called plugins. We will call the entrypoint for the plugins myapplication.plugins.

So we should create a folder structure like that:

myapplication/
  myapplication.py      an empty file by now
  plugins/              an empty folder for all plugins

The first plugin

At first we have to create a new plugin, in this case we call it foo. Therefore we create a new folder structure below myapplication/plugins:

foo/
  foo/
    __init__.py         an empty file
  setup.py              an empty file

The next step will be adding content to the setup.py file:

# -*- coding: utf-8 -*-
"""
A small example plugin
"""
from setuptools import setup

__author__ = 'Your Name Here'

setup(
    name='Foo',
    version='1.0',
    description=__doc__,
    author=__author__,
    packages=['foo'],
    entry_points='''
    [myapplication.plugins]
    Foo = foo:FooPlugin
    '''
)

This file should be simple to understand, basically it's a normal distutils setup file. The difference is that we use distutils over setuptools and that he have defined an entrypoint. This entrypoint basically tells the application that the "Foo" plugin is a class called FooPlugin and located inside of the "foo" package.

Now we open the empty __init__.py file and add the following content:

# -*- coding: utf-8 -*-

class FooPlugin(object):
    capabilities = ['foo_capability']

    def do_something(self):
        return 'Hello from %s' % self.__class__.__name__

This file looks like the plugins from the first part of the tutorial but it doesn't have to inherit from a base class. Here we just use the generic object baseclass which is the parent of all new style classes.

Now we have a folder structure for an egg. but not an egg. Since we don't want to rebuild an egg each time we change something on the sourcecode we use the develop option of the setup.py file. Therefore we open a shell and go to the folder with the setup.py file and execute the following command:

$ python setup.py develop --install-dir .. -m

The -m option is required, otherwise setuptools will fail because it can't find the ".." folder in the PYTHONPATH.

The Main Application

Now we have an egg in development mode. Basically it's a file called Foo.egg-link with the path to the sources in it.

Time to create the application:

# -*- coding: utf-8 -*-
import os
import sys
import pkg_resources
sys.modules['myapplication'] = __import__(__name__)

ENTRYPOINT = 'myapplication.plugins'
PLUGIN_DIR = os.path.join(os.path.dirname(__file__), 'plugins')

def init_plugins():
    pkg_resources.working_set.add_entry(PLUGIN_DIR)
    pkg_env = pkg_resources.Environment([PLUGIN_DIR])
    plugins = {}
    for name in pkg_env:
        egg = pkg_env[name][0]
        egg.activate()
        modules = []
        for name in egg.get_entry_map(ENTRYPOINT):
            entry_point = egg.get_entry_info(ENTRYPOINT, name)
            cls = entry_point.load()
            if not hasattr(cls, 'capabilities'):
                cls.capabilities = []
            instance = cls()
            for c in cls.capabilities:
                plugins.setdefault(c, []).append(instance)
    return plugins

plugins = init_plugins()

That looks complex. But it isn't. The sys.modules thingy is not needed in real applications, but since this application is a one file application it won't appear in sys.modules and plugins would have problems importing it. So we put the current file into sys.modules.

The init_plugins function basically scans the plugins folder for eggs whose entry point is myapplication.plugins, load that egg and use the returned class to look up the capabilities. After that it instanciates the class and creates a dict in the following structure:

{'capability': [list, of, plugins]}

Basically this now allows you to select all plugins of a given capability by doing this:

for plugin in plugins.get('my_capability', []):

For the sake of simplicity we can create two methods for querying plugins:

def get_plugins_by_capability(capability):
    return plugins.get(capability, [])

def get_all_plugins():
    result = set()
    for p in plugins.itervalues():
        for plugin in p:
            result.add(plugin)
    return list(result)

Other plugins now could query plugins on their own by importing those methods from the myapplication module. For example here a Bar plugin:

# -*- coding: utf-8 -*-
import myapplication

class BarPlugin(object):
    capabilities = ['foo_capability']

    def do_something(self):
        plugins = myapplication.get_all_plugins()
        return 'Number of plugins: %d' % len(plugins)

The main application now can gain a main method:

def main():
    for plugin in get_plugins_by_capability('foo_capability'):
        print '%s: %s' % (plugin, plugin.do_something())

if __name__ == '__main__':
    main()

And the output would look like this:

$ python myapplication.py
<foo.FooPlugin object at 0xb7ca59ec>: Hello from FooPlugin
<bar.BarPlugin object at 0xb7ca5cec>: Number of plugins: 2

I personally don't use egg files beside for working on trac plugins so I'm not sure if my method of loading egg files is the best. If someone has a better way for loading egg files from a given plugin folder drop me a line :)

You can download an archive of all files here: setuptools_plugins.tar.gz

trac rocks

written by Armin Ronacher, on Monday, July 24, 2006 0:00.

I'm started using trac short after the 0.8 release. And damn, it rocks. the pocoo trac is currently running version 0.10dev with 10 plugins. I never saw a web application with such a great plugin interface before. hope the pocoo plugin interface isn't worse ;). In the following post I'll cover some of the great plugins we use for our trac

I think everybody knows trac. trac is the whizkid under all bugtrackers. It features a flexible plugin interface, support for multiple versioning systems and has the far most initiutive user interface among all OpenSource trackers I know.

For the pocoo trac I installed many plugins. Here the list of plugins we use and why we use them ;-)

trac 0.10dev

The trac core in version 0.10dev. Why not the stable version? Because I havn't found any bigger bugs in the trunk version we're running and some plugins depend on this version.

TracSpamFilter

Everybody hates Spam. And in the last months many tracs suffered under spambot attacks. This plugins supports akismet and a ip blacklist for catching spam. But also the moin antitrac system which means it helds a central BadContent page with a number of regexes. If one of those regexes matches the user can't save the page. Moin automatically syncs that page with moinmaster, trac doesn't. Because of this I've written a small plugin that automatically syncs the BadContent page with an other wiki or trac defined in the trac config:

spamsync

Like mentioned before this plugin allows the trac to sync the BadContent page with a master wiki. It's not stable, use it at your own risk, but it seems to work in the pocoo trac.

TracTicketDelete

If a spammer was successfull (yeah. even with a antispam plugin this can happen) you can't delete a ticket using tracs techiques. This plugin gives you a "ticket delete" button in the webadmin interface which you can use to delete tickets including all comments on it. Very useful.

WebAdmin

Required by the TicketDeletePlugin and very usefull you don't want to ssh into the server to update components, milestones or other settings. Also the only way if you want all TRAC_ADMINs to edit the settings without giving them ssh access.

XmlRpcPlugin

Allows you to connect using xmlrpc to your trac and query the database or update tickets. Very nice when you want to write some cleanup scripts for the trac etc.

CodeTagsPlugin

Everybody knows those XXX, TODO, etc. tags in the sourcecode. This plugin allows you to display those tags on one page. IMHO very useful. But note: It's not supported by me ;-) But if you found a bug, query me on irc.freenode.net (nickname mitsuhiko) or send me a mail.

IrcLogsPlugin

This plugin parses the logfiles of a supybot and displays them in the trac: #pocoo logs. Also not supported by now ;)

The other plugins we use: trac blog and trac repo search very useful too.

Last but not least: thank you trac team ;-)

my holidays

written by Armin Ronacher, on Wednesday, July 19, 2006 0:00.

There are news in mitsuhiko land. I've now more time for my projects and am angry about the current situation in Libanon, start writing on my pypy "Fachbereichsarbeit" try start driving cars and much more.

First the positive aspects of this blog posts. Since two weeks I have summer holidays. That means no school until the September 9th. I'll use my spare time for starting writing on the "Fachbereichsarbeit" for my school leaving examination which topic will be "pypy". In Austria you have to posibillity to write a 30 page long article about a topic you can choose as replacement for one final examination. So I'll write about pypy (at least I hope I can write about it, the teacher has to approve it).

The second great thing about the holidays: I'll spend two weeks in Ireland. The funny thing about it: Alexander Schremmer informed me that one week after I'll leave Ireland there will be a pypy sprint. Maybe I manage it to extend it somehow. At least I'll print out the pypy docs and read it.

Since Monday I'm also participating in the lessons of the local driving school. I hope I can use my holydays to drive at least 2000 kilometers. I need 3000 too be able to gain the driving license before I'm 18. (this is called L17 here in Austria. You drive 3000km and gain the driving license with 17)

I'm also working again with the rest of the pocoo team on... pocoo ^^. A more or less up to date version is online at area51.pocoo.org. beewee thinks that we can do an early feature freezy in three months. Hopefully he is right. I'm really sick of our phpbb.

And now to something that currently makes me really angry. It's about the situation in Libanon. Beside that I really hate war at all (I hated the war against Iraq, Afghanistan as well as any other war I heard of since I'm able to switch on the television or read the newspaper) I'm especially angered about this one. When you read the newspaper you just read everything from the view of Israel. It really looks like there is a country (Israel) and a anticountry (Libanon) which just brews terrorists. When I found the blog codedemigod by a programmer from Libanon it was the first chance for me to see the situation form the other side too. Goddamn. Those are people too. And they don't do anything wrong. In my oppinion the wholeh situation down there is just crazy. And after reading the response to my comment I really think about informing myself about the situation down there and not just to trust the newspapers and there all-mighty-point-of-view.

why I can't stand thread.local (and others)

written by Armin Ronacher, on Monday, July 10, 2006 0:00.

I had a discussion about that thingy on #pythonpaste a few hours ago. Looks like I'm the only person hating thread.local. Maybe I'm ultraconservative but I think I've some good argument agaist the use of this magic type.

preamble: This is my point of view. I know that thread local objects are a nice way to fix the problem with not passing request/session/or any other objects to methods but I don't like them. I'm not fighting against pylons or any other application using thread locals nor do I want the pylons developers to remove thread local objects.

In this particular case I want to talk about the thread local object in pylons. Pylons uses some magic thread local objects named m, g, h, c, cache, session, request, response, params and buffet. You can get those object by importing them from the yourapplication.lib.base package.

Some of them are deprecated and exist just because of backwards compatiblitity.

I was told that pylons uses paste.registry to be able to have more than one application in the same thread without getting threading issues. So it should be technically possible to write something to m without touching the m of another pylons application.

Looks like (beside the fact that the variable names are not that easy to understand when first looking at the code) pylons doesn't have any problems with thread.local since they workaround it with paste.registry

But what about web.py for example. It overwrites sys.stdout to redirect printed data directly to the request of the current thread. Nice idea but using this technique it's only possible to have one running web.py application in the same thread. *grmbl*

django doesn't use thread.local objects (not true, afair it uses thread.local objects somewhere in the core) but lacks a deployable system. You can't mix up django application since they mostly end up in name(space) clashes of either models, templatetags, templates or other magic tags. It's not possible to run two django installations in the same process since they relay on os.environ['DJANGO_SETTINGS_MODULE'] or a directly passed settings module.

I really like django, but never try to write an application you want to distribute.

Same for SQLObject. Imagine MoinMoin and pocoo would both use SQLobject and a pocoo plugin imports the moinmoin package, create a moinmoin wikiconfig and try to parse a wikipage. If you overwrite the global sqlhub the later importer moinmoin would overwrite the connection settings of pocoo. Someone on #python.de also reported stupid threading issues with sqlobject which appeared from time to time. It ended up that he switched to sqlalchemy which doesn't use thread locals any more (sice 0.2). (You can still use the magic technologie, but you have to enable it first)

As and programmer and admin I really like to have applications you can just-deploy (tm).

As just deploy i see something you can put into a debian repository and update using the normal update procedure (apt-get update, aptitute, synaptic...) And I don't want a site-packages for apps like pocoo, moinmoin since they arn't libs but applications. And I want to mix up applications in the same process like I do on ubuntuusers to be able to share user accounts, user settings, the same mailing system etc. between many applications.

And thread.local as such breaks this. paste.registry fixes it again I was told, but one thing is still left:

It's magic, hard to understand for newcommers, breaks my dir() and is slower then passing the given objects to the app

Okay. One of this points is premature optimization ;-)

  • magic / hard for newcommers - immagine that you have a project using paste.registry for a global request and response variable. You also have a globals variable to put request variables in. How do I tell a PHP developer switching to this project (this currently happens at ubuntuusers.de where it was impossible to find any python devs with web experience) that accessing myapp.request, myapp.resposne and myapp.globals is safe whereas accessing any other global namespace, object etc. isn't. It does not make sence at all ^^ Why not pass a request object to the view? Seven chars more to type? or three in case of just writing req.
  • dir() broken - i like dir(), it is the best to debug ^^ Just raise an exception, use a middleware with eval() support and navigate to the frame with the problem. dir() the object and see what's broken. Works as long as you don't use a magic thread local object which breaks dir()
  • slower - it actually is premature optimization but fetching current thread and looking up an object in a dict each time you access it is not very fast.

And here an example of how pocoo defines its views:

from pocoo.pkg.core.models import Forum
from pocoo.application import RequestHandler
from pocoo.template import PagePublisher
from pocoo.http import TemplateResponse


class IndexPage(RequestHandler, PagePublisher):

    def get_page_name(self):
        return 'index'

    def get_relative_url(self):
        return ''

    def get_handler_regexes(self):
        yield u'$'

    def handle_request(self, req):
        categories = req.db.select(Forum, Forum.c.parent_id == None)
        return TemplateResponse('index.html',
            categories=categories
        )

Oki. Maybe the imports suck. But everyone who is reading that source the first time knows where the objects are comming from.

And here a pylons/thread.local version of the above code:

from pocoo.pkg.core.models import Forum
from pocoo.template import PagePublisher
from pocoo.http import *


class IndexController(RegexController, PagePublisher):

    def get_page_name(self):
        return 'index'
        
    def get_relative_url(self):
        return ''

    def get_handler_regexes(self):
        yield u'$'

    def handle_request(self):
        c.categories = d.select(Forum, Forum.c.parent_id == None)
        m.subexec('/index.myt')

This isn't valid "pylons" :-) But it uses the ideas of pylons. Those global c, m etc objects. Of course it's less to write. But don't tell me it's easier to understand