December 2009

3

Python wanderings, part two

Posted on Thursday, 31 December 2009

2. Plugging it all in


Sometimes we get feature requests and merge proposals for features that are clearly useful for someone, but not appropriate for the general use cases. It's always unfortunate to have to say no to these folks, but we have a slim menu UI and I'm wary of cluttering it with niche features. Still, turning away legitimate users is something I don't like doing, so for a while we've been considering how to fix this.

The obvious answer is that we should support plugins, and I've been working on such a system for my epic refactoring. This is a quick wander through some thoughts I've had.

I started out by googling for python plugin systems; One if the top hits was this page by Armin Ronacher . In it he demonstrates a plugin system in under 40 lines of Python. It's simple and flexible, but there are some issues, like it makes doctest very sad.

I asked about this in #python and was politely informed that I was Doing It Wrong. I chatted for a while with the helpful residents and came away with a list of plugin frameworks to look at, namely twisted.plugin and zope.interface.

Pulling in external dependencies is a big deal for us - many of our users are on Ubuntu or similar desktops with lots of python packages already installed, but some are not using GNOME or a Linux desktop at all, so we have to be sure that we need a library before we depend on it.

After playing a little with both of the options I came to the conclusion that while they are both really well made and capable, they are far more formal than we need, and the added dependency issues continued to concern me.

I revisited Armin's plugin system and removed the use of .__subclasses__() that was breaking doctest and offending #python instead having a list in each .py file which the plugin system extracts and treats any classes mentioned in that list as plugins. I also extended it to always instantiate the plugins and look for the plugin files in both the system directories and the user's home directory.

This plugin system is currently hooked into two places in the branch, URL mangling and the context menu. This allows plugins to add support for new URL types (e.g. we just added support for Launchpad code URLs like lp:~cmsj/+junk/terminator-epic-refactor), and insert new options into the context menu. I'm not sure if we need to go further, but if you would like to hook into other parts let me know - it's pretty easy to arrange now :)

0

Random musical linkage

Posted on Wednesday, 23 December 2009

I listen to a lot of electronic music and one of the nice things about that is the intermingling of other music by way of samples.

Case in point, I'm currently watching The Ballet Boyz's production of The Rite Of Spring with Rike, and I suddenly realised that the string motif playing at the entrance of The Elders is sampled and looped to great effect by the hypnotic Physical World on Freeland's album Now & Them.

This kind of thing makes me smile :)

2

Python wanderings, part one

As mentioned in my earlier post about refactoring Terminator, I want to talk about some of the things I've learned about Python and programming in the last few months. If I were you I wouldn't place any great significance in anything I'm about to say - after all I'm a rank amateur in the field of programming.

This is going to be a multi-part post so I at least get something out there, rather than leaving it to rot forever in my drafts folder.

1. Solving global warming^W variables


I have objects that represent terminal widgets, objects that represent widgets that contain terminals, objects that contain configuration, and one master object that functions as the brains of the operation.

Inevitably these objects need to know about each other, but how to achieve that? The brain object is simply called 'Terminator' and almost every other part of the system needs to know about it, same with the config object, and Terminator needs to know about all of the terminal objects, etc. The dependencies are all over the place and one aim of the re-factor was to separate all these parts out and decouple them, but ultimately I was never going to get away from different objects needing to know about each other.

So how to go about it? As far as I know the options are:

  • pass around object references (every time you create something, pass it your references to all the bits it needs)

    • Pros: no hacks or tricks involved

    • Cons: makes every __init__() more complicated, means passing references that an object doesn't need other than to pass to its children.



  • use global variables

    • Pros: they're global

    • Cons: everyone seems to hate global variables, perhaps because it's an implicit dependency not an explicit one, or because of potential namespace collisions, or maybe other reasons.



  • use singletons

    • Pros: explicit dependency

    • Cons: often seems to involve hackery to get the singleton object reference




In my searching around I came across a fourth option that somewhat relates to singletons... the Borg pattern.

This is a very simple idea - it's a class that always instantiates to the same thing. You don't need a factory or function or something that gives you a reference to the singleton, you just instantiate a class and it's the same as all of the others you've instantiated of the same class.

Best of all, the Borg pattern is incredibly simple in Python. Like, really simple. Don't believe me? Click here. Yep, four lines of code. Technically it's probably a bit ugly, but the resulting code feels very clean.

So now I have the Borg pattern in use for the main class, a class that provides all the configuration, a class that discovers plugins and lets them be referenced, and a fairly new class I'm experimenting with that acts as a factory for all of my classes, as a way to break any possibility of circular module dependencies.

Reality has to bite though, the Borg isn't a panacea; One has to be very careful about how one creates Borg objects. I chose to create a base class called Borg which Terminator, Config, Factory and PluginRegistry all derive from, but this turns out to have been a very short sighted decision to abstract out the common 4 lines. It wasn't until I started building Config to have functions that allow it to be accessed as a dict that I realised all of my Terminator, Config, Factory and PluginRegistry instances were the same thing as opposed to each type being distinct. It's also terrifyingly important that the subclasses of Borg not use class attributes. Any attributes defined by these classes *must* be instantiated as None so they are instance variables, and *after* you've called Borg.__init__(self) in your own __init__() you can then set up your attributes however you want because they are then part of the shared state.

On the whole I am happy with the Borg pattern. I've written test code to ensure that all of the assumptions I explicitly made are guaranteed, and all of the implicit assumptions I've discovered I made are also safe. Nonetheless, it's not a completely clean solution and I find myself wishing it was somehow a primitive of the language.

2

Epic Terminator refactoring afoot

Posted on Saturday, 19 December 2009

The current bzr repository for Terminator began its life in November 2006 with the simplest possible implementation of the concept of packing multiple terminals into one window. In the 3 years since then we have expanded and extended the code in a variety of directions to produce a moderately compelling feature set, but one that is really obviously incomplete.

In the same time period we've also seen a really gratifying amount of adoption - I believe our active userbase numbers in the thousands if not tens of thousands. I am forced to largely estimate these numbers for all the usual FOSS reasons, but it's all based on one real metric - Ubuntu has about a million users reporting popcon data and over 10,000 of those have Terminator installed. I don't actually think that they all use it, but nonetheless it's the kind of number that makes you think "hey maybe I need to be doing more for these folks".

And I do think that, and I am trying to do more.

Back in August I took a serious look at where we are and came to the same old conclusions - we lack one or two headline features that people keep asking for (barely a week goes by when I don't get asked how someone can save a particular layout of terminals). These features are very subtle and deeply problematic with the existing code architecture - we've just been hacking in features as we can without any regard for architecture or future maintainability.

I decided that I'd had enough of being confused and frustrated by the status quo and so I started a side branch in my Launchpad /+junk/ folder called "epic-refactor" with the aim of refactoring all of Terminator from scratch. I'd read through every line of existing code and figure out what we were actually doing and how it could fit together more sensibly, then sketch that out in the barest form possible while I experimented with various Python techniques to arrive at an architecture that makes sense for our project, then port over the existing code feature-by-feature to the new architecture.

It's now a little over four months since I started the epic refactor and looking at where I stand today I am really happy. It's not ready to be merged into trunk yet, but the amount of work to get it there is less than the amount of work I've done on it so far. I don't want to put a timescale on it, but I hope to be calling for some wider testing in 2-3 months or less.

Once we are acceptably close to feature parity with the current releases I'll merge the epic-refactor branch over  and we can start to push forward with implementing the features that everyone wants, and finally get to the point of being able to comfortably release a 1.0 version.

I'd always thought that I'd hand over maintainership after a 1.0 version, but the last 4 months have been a whirlwind of programming discovery, so I might very well just stick around and see what people want on the road to a 2.0 release. Alternatively, the work I've been doing in the last few days on a plugin system might mean that I can kick back and watch everyone else implement crazy, awesome and sublime features I'd never thought of!

I'll be back with more when I have written a configuration subsystem for epic-refactor, because by then I'll be wanting your help to test!

I'm also going to write a separate post shortly about some of the interesting Python paradigms and ideas I've hit upon along the way. I'm sure none of it will be a revelation to anyone with serious programming chops, but for a rank amateur like me it would have been useful to have read four months ago ;)

0

Terminator 0.14 released!

Posted on Thursday, 3 December 2009

This has been another long gap release unfortunately, but we do have some goodies for you.
Stephen Boddy is back with some excellent re-working of the UI relating to grouping terminals, Kees Cook brings us some clever work relating to window geometry and myself and others have been fixing bugs for you. We hope you enjoy this release, especially those of you who hated the "I'll be back" notifications from 0.13!


Home Page: http://www.tenshu.net/terminator
Launchpad Page: http://launchpad.net/terminator
Ubuntu PPAs: https://launchpad.net/~gnome-terminator/+archive/ppa