NTLM Authentication using IIS, ISAPI-WSGI and cherrypy

(How’s that for a snappy post title?). This is really an aide-memoire for myself since I always make this kind of thing way more complicated than it needs to be. My mission (whether or not I choose to accept it) is to have a web interface to one of our internal apps which should support transparent browser-based authentication but should fail back gracefully to anonymous access. The app in question is a Helpdesk app and some years ago I wrote a cherrypy-based web interface to it since its own desktop interface is woeful. The cherrypy app’s been running fine for some years, with incremental improvements but I’m trying to push it out to a wider (internal) audience and it could do with the speed and stability boost which a fully-fledged webserver can give it.

The machine it’s running on (via the standard cherrypy server on port 8080) is already running IIS and the pywin32 isapi module, nicely extended by the ISAPI-WSGI project, makes transferring the app fairly plain sailing. But one of the benefits of transferring to IIS was to get as smooth a passthrough authentication as possible. I had been using a fairly crude http basic auth with an authentication check at the server end, but that’s far from ideal. I hoped that by switching to IIS (and given that the official company browser is IE) I could just flick a switch and get the browser’s identity transparently.

Well you can, but I put two stumbling blocks in my own path: when it didn’t work immediately I tried to make it far more complicated than I need have rather than looking for a simple solution; and identity isn’t the same as an access token. So, for my future self trying to remember how to do this, and for anyone else looking for this solution, here’s the easy bit. (I’m assuming you’ve installed some kind of ISAPI-WSGI app, altho’ the same pretty much applies elsewhere).

  • In the IIS MMC snap-in find the isapi-wsgi app you’ve installed. Right-click Properties. [Directory Security] tab. Anonymous access and authentication control [Edit]. Leave [Anonymous Access] ticked. Tick [Integrated Windows Authentication]. [Ok] all the way out.
  • In your WSGI app, look for the REMOTE_USER env var. If it’s set, you’ve got a remote user. If it’s not, call start_response (”401 Unauthorized”, [(”www-authenticate”, “NTLM”)]) and return []

That’s the really brief version, and is based around the IIS 5.1 which comes free with WinXP. IE users need do no more. FF users will probably already know that they need to add the web server to the about:config param with the unmemorable name (it’s got “ntlm” and “trusted-uris” in it). Don’t know if Chrome handles this.

For my purposes, I was using cherrypy so — when I get back to work — all I should have to do is: check cherrypy.request.login which keeps track of incoming REMOTE_USER / LOGIN_USER env vars for me; and do the “401 Unauthorized” dance if I need to possibly by means of a simple pre-request cherrypy tool. The graceful anonymity will be implemented by not displaying and/or allowing certain actions if there is no remote user. Not sure yet whether I need to fail back to basic or digest auth.

The trickier bit — and the bit I haven’t yet solved — is translating the “Authorization:” header which the browser sends into something which I can persuade Windows to use to give me a session token. With the session token, I can determine whether my user’s in certain security groups or not. (I can do something from the username alone by querying AD and that’s my fallback plan). The python-ntlm project looks like it’s done all the spadework here, and especially in the clientserver branch but they also talk about the pywin32 sspi module which I’ve so far managed to avoid having anything to do with.

IronPython in Action

I received my copy of IronPython in Action (henceforth IPiA) in the middle of the week, and I’ve spent the last few days going through it on the Tube. What that means, in practice, is that I’ve simply skimmed sections which are code-heavy: I’ve not had my laptop with me and trying to read even a modest one-page explanation of a Windows Form app I find daunting unless I can tap the code in as I go. But I’ve gone through everything else. And I like what I see.

By way of disclosure, I’m given a couple of blushingly generous footnote credits by Michael which naturally leave me feeling well-disposed towards the book as a whole. But even without those, I’d be giving it the thumbs-up. As the authors note early on, a book of this sort is trying to fulfil two expectations: to inform existing .NET users about Python; and to inform existing Python users about .NET. With a very few exceptions I believe it manages at least the second of those two. (I’m not competent to judge the first).

As I noted in my review of Tarek Ziade’s Expert Python Programming, people want different things from their programming books. What suits me in IPiA might not suit someone else. But I find the style to be lightweight enough to avoid pomposity (my own cardinal fault in writing) while not descending into jokey asides so often as to distract. It also keeps the code examples fairly short. Altho’ I personally do prefer self-contained examples every time, I recognise that this isn’t always easy or even possible. And that some people like one growing example app to run through a book.

But the most important win, I think, is managing to write a book about IronPython, not about Python or .NET. Naturally there is an element of explanation involved in both directions when some feature is being introduced or compared. But for the most part you can refer to the appendices which give summaries of Python/.NET if an unfamiliar term arises. For me, this achievement is key to the success of a book like this. If I want to learn Python or .NET I’ll turn to another book or website. Here I want to know what IronPython can do which Python can’t (or better, or faster, or worse) and what IronPython can do which C# can’t (or better etc.). I don’t want to know what .NET can offer as such, altho’ real-word examples are obviously great illustrations. The one place I believe IPiA falls down just a little in this regard is in the chapter on Databases and Web Services. The problem is that IronPython doesn’t seem to bring enough to the table here to distinguish it from the equivalent C# code.

As a long-time user of Python on a Windows platform, I’ve obviously been umming-and-ahing about IronPython for a while. I’m happy with CPython, familiar with it; I’ve written no few lines of code around the Win32 API, all of which disappears when you enter the world of IronPython/.NET. That said, it’s clear that .NET is the future of Windows. As it happens, it looks like we’re about to undertake a project at work based around the Juggernaut Sharepoint and I hope this will provide the incentive for me to have a go with IronPython and see what it can do.

Any version bdist_msi for pure Python packages

A double whammy, courtesy of recent commits by Stephen Bethard and Tarek Ziade: not only is the available-but-undocumented bdist_msi packager now available-and-documented, but it also offers installation into multiple Python installations for pure Python packages. This means that, say, an installer for my Python-only WMI module can now be installed to any Python installation found in the registry. Not only that (which the bdist_wininst installer already offers), but it also offers you the chance to install to an arbitrary location (say if you have Python built under a Subversion checkout).

UPDATE: Brad Allen points out that bdist_msi already allows installation to an arbitrary location (which I didn’t think it did).

Questions and Answers on the Python lists

Over the years of my involvement with the Python community, I have seen many questions asked and answered on the Python lists. I’m a fairly regular watcher on python-list, python-win32 and python-tutor and an occasional on a couple of other lists, including python-dev. For my part, I nearly always access them via their email interface. But other people view them through gmane, Usenet, Google Groups and a handful of web-style mirror sites.

Many people have commented on the generally friendly and helpful atmosphere which prevails on all the Python lists, and justifiably. You get the occasional ding-dong thread or a see-sawing to-and-fro between opposing parties, neither of whom can bring himself to relinquish his own position or to accept the other’s. But even that rarely ends in bloodshed.

But my entirely anecdotal impression is that there is a class of questioner who asks a question and fails to respond when an answer is given. And, as someone who’s provided one or two answers in his time, I’m moved to wonder: why? Is it because the OP feels that no thanks or response is necessary — that the list has done its duty and the matter is closed? Is it because the response took more than a few minutes to come back, and the OP wanted a quick answer or none at all? Is it because the answer doesn’t help but the OP doesn’t feel entitled to come back and ask for more? Is it because the OP expects any response to come to him personally and not to the list as a whole, and so doesn’t keep checking back?

I’ve no idea, and I doubt that one case meets all. As it happens, in answer to help I have offered, I have had many responses on and off-list thanking me, or asking for more information, occasionally offering to buy me a drink! And for all those I am very grateful: this is a friendly community built around the creative art of programming, and it’s nice to know that someone’s benefitted from your help and is grateful in turn.

But it would be a shame if there were a class of user who came, not as a potential contributor to the wider community, but as a mercenary who wants merely to get something from Python and walk away.


I don’t know if you follow the Python issue tracker especially. For some particular reason I subscribed to the Python Bugs List a while ago, which emails you with updates to all issues on the Python tracker. Obviously it’s quite a moderate volume list, but I’m quite good at pressing the delete key :). And what it’s shown lately is that there’s an enormous amount of activity around the tracker.

This falls into three broad categories: issue assessment; post-PyCon work; and general issue activity.

The first is due to a series of energetic stints by Daniel Diniz (aka ajaksu2) who seems to have undertaken to go through pretty much every single issue still open and to assess its continuing validity (does it still apply?), to set the relevant flags (Python 2.6? 3.0? interested developers?), and to work out a slightly more robust set of workflow tags (needs patch, needs test) to indicate what state the issue’s in. Looks like Tennessee Leeuwenburg is also keen on pepping-up the workflow and statuses. (Not sure who’s exactly driving, so apologies if I’ve miscredited).

The upshot is that a whole slew of outdated bugs have been closed or tagged as likely to close in the absence of confirmation from their original submitters or other interested parties. In addition, many more issues have been looked at with a view to their current state: patches, tests, docs etc. and flagged appropriately. I suspect that there will be some things closed a bit over-zealously but it’s great to see some people with a sense of responsibility for the state of things. We all know there are too few developers to cope with even assessing let alone critiqueing or implementing every issue, but it really helps to have a Spring Clean.

The other thing which is evident is a post-PyCon rush of issues being dealt with (and in some cases, raised) as a whole bunch of developers sprint during or after the conference. Jack Diederich has taken it upon himself to deal with all the outstanding telnetlib issues. Likewise, Michael Foord (of IronPython fame) seems to be heading up an initiative to give the unittest module a much-discussed overhaul. The indefatigable Georg Brandl has continued his maintenance of the docs and the Sphinx system which builds it. And obviously, many other people working on particular issues.

And of course there’s the ongoing work by everyone in the community to report bugs, suggest improvements and help to reproduce problems and post patches. I’m not sure whether the nett number of open calls has increased, decreased or remained stable, but the very activity itself gives a clear sense that the Python community is alive and kicking and concerned about improving things.

GROUP BY in Python

When it comes to a certain class of data problem, my mind reaches for SQL… which is a problem when the data’s in Python. Obviously I could create an in-memory sqlite database just for the purpose of storing the data and then retrieving it with SQL. But that would be mild overkill. One such example is grouping data by, say, the first letter. I’ve known about the itertools.groupby function for a while but for some reason whenever I came to look at it, it never quite seemed to find my brain. Having now made the breakthrough I’m reminding myself here for future purposes:

  LEFT (words.word, 1),
  COUNT (*)
    word = LOWER (w.word)
    words AS w
) AS words
  LEN (words.word) >= 2
  LEFT (words.word, 1)

translates to

import os, sys
import itertools
import operator
import re

first_letter = operator.itemgetter (0)

text = open (os.path.join (sys.prefix, "LICENSE.txt")).read ()
words = set (w.lower () for w in re.findall (r"\w{2,}", text))
groups = itertools.groupby (sorted (words), first_letter)

for k, v in groups:
  print k, "=>", len (list (v))

Expert Python Programming

(Review copy from Packt Publishing)

Preamble: re-reading this review I realise that it comes across as rather negative. Unfortunately, the things about this book which put me off put me off sufficiently that they outweighed the many parts which I did find useful. Since I know that there are quite a few other reviews around the Python community which are more positive, I don’t feel the need to recast anything in particular: prospective purchasers who aren’t deterred by the aspects which deterred me will find enough useful material in other reviews. My sincere compliments to the author on producing the book at all: I hope he considers my comments constructive and not merely negative or nit-picking.

When I first saw it being noised about, this book’s title (and its subtitle: “Best practices for designing, coding… “) suggested to me that its target audience was people who already had an amount of experience of Python and who wanted to learn some nifty tips and tricks and some good design practices in general. And I thought to myself that it was high time for such a book. It seems to me, without an exhaustive survey, that the majority of Python books out there are either in the “learn from scratch” camp or are aimed at a particular segment of the market: either a particular toolkit, such as Twisted or sqlalchemy; or a particular discipline, such as Bioinformatics. It’s almost as though, having launched you out into the mainstream of Python programming, you’re left to trawl the internet for resources.

The book falls into three main sections: the first few chapters covering various syntax choices, especially those which have been introduced more recently; the middle chapters covering a sort of project-management approach focusing a lot on the use of eggs and buildout; and a final section covering aspects of optimization and useful patterns. For reasons I’ll explain below, I find the first and last of the these sections the most useful and have less time for the central section.

To start a hare on python-list / python-tutor, you only need to ask a question like “What book should I read to learn Python?”. You’ll get as many answers as people who answer. Obviously there are a few favourites, but even these will have a detractor: someone who found it didn’t suit them at all. And that’s the way with books. One person likes every piece of illustrating code to be completely self contained; another prefers an example to run all the way through the book. One person prefers the code to be spelt out completely in the course of the book; another prefers a downloadable .zip or a CD. One person prefers a cookbook-style set of recipes; another prefers a more narrative approach punctuated with examples. One person prefers a jokey manner, littered with witty allusions and community in-jokes; another prefers a more straightforward just-tell-me-what-I-need-to-know style.

Expert Python Programming’s visual layout, font choices,etc. seemed to me a little pedestrian. The section headings are just a little too big, and the occasional pull-out [Notes] paragraphs remind me of late 1980s DTP more than I’d have expected for a professional publication. That the author’s first language isn’t English is apparent from the — extremely few — errors and very occasional awkwardness of style. (That his English is very good is equally apparent). None of this is showstopping, but it all forms part of the effect. I was surprised that the opening chapter detailed installing Python. I would have expected anyone reading a book on Expert Python Programming to have reached the point already where they could install Python and use it. Perhaps this is my misreading of the target audience. Likewise the advice on configuring a text editor (a notoriously personal issue).

The next few chapters cover newish syntax choices such as iterators, generators, decorators and context managers (contextors, anyone?). This is nicely pitched although I feel that the author is sometimes too didactic in his advice. For example: “Every time a loop is run to massage the contents of a sequence, try to replace it with a list comprehension”. Certainly, the “try” in that recommendation softens the effect slightly but he does the same thing a little later with generators which “should be considered every time you deal with a function that returns a sequence or works in a loop” (emphasised in the original). Again, the “considered” obviously doesn’t make it a mandate, but it does seem a little less nuanced that it might be. There are certainly places where you’ll trip yourself up if you just throw a generator at a sequence/loop.

This section includes a lot of examples, most small and self-contained and following the layout of an interpreter session. This is useful, although I feel that showing the entire interpreter session tends to introduce a lot of noise into the example (including all the “>>>” and “…” prompts. It also tends to result in quite vertically long code segments which will more easily cross over pages, making it more difficult to follow. Might work better if the font size was a point size smaller.

There follows a set of chapters covering syntax choices around classes, including subclassing built-in types, using super, slots and metaclasses. Possibly because this is an area where my own ignorance is more exposed I found the narrative and explanations here mostly useful. Unfortunately, stemming possibly from that same ignorance, I found the clutter in the code examples more noticeable and distracting. Try making sense, for example, of the explanation of a descriptor on page 75. Not sure what I’d have done to improve it, although removing the inline comments is probably not a bad idea especially as most of them are simple restatements of the next line of code.

Chapter 4 covers good naming and module / package organisation. And introduces the two things which I like perhaps most and least about this book: reference to useful external packages; and eggs. I like this reference to exernal tools because the stdlib doesn’t have everything – and nor should it, I say – and it’s always good to find out about tools other people have found useful. On the other side of the coin, I’m one of those people for whom setuptools / eggs and the subcommunity which has built around them are solving a problem I don’t have. That could be because I’ve never built a package with sufficient sophistication. But chapter 5 on “Writing a Package” plunges straight in with “Therefore, all packages can be built using egg structures” which I’m afraid put me off. Personally, I plan to go back and re-read this section when I’ve found myself trying to manage a package which is something more than a set of useful modules. As far as it goes, for those who are interested in learning about eggs and their interaction with PyPI, this chapter seems to give lots of practical advice. I would have preferred it to have started off slimline, showing a straightforward distutils setup.py without setuptools, without Paster.

Chapter 6 starts an application which runs through the rest of the book on-and-off: Atomisator, a feed aggregation toolkit. The author walks through creating the package, introduces sqlalchemy as a way to model the data and then discusses the API before talking about using a setup.py to declare interdependencies between subpackages and upload to PyPI. Chapter 7 extends this simple start by introducing zc.buildout, a build/release mechanism the author favours.

The next couple of chapters cover code management via version control systems – the author cannily focuses on Mercurial which has just been selected by GvR as Python’s VCS of the future. And speaks about continuous integration, showcasing buildbot, the Python-based system used by the Python core development among others. The chapters interleave higher-level explanations with low-level step-by-step installations. I feel that the latter actually detracts from the book a little: for one thing, projects move sufficiently fast that anyone reading this in a year’s time will very likely be seeing very different prompts and responses to those which the author illustrates. In addition, it leaves less space for real discussion of some key pros and cons or tips and tricks or Python-specific hints. Credit to the author, certainly, for being thorough, but I fear it could be counterproductive.

The next chapter neatly covers possible lifecycle models and illustrates the use of Trac as a project management tool: this is a nice choice as it’s a bit of a poster-child for Python and does lots of useful things well. Chapter 10 on Documenting is the free online chapter and I imagine many people had, like myself, read it before coming to the complete book. If one again cuts out the slightly over-explanatory example sections, this is the author at his best: there are concise and clear guidelines (for which he gives due credit to the author of Agile Documenting). More useful tools are introduced, including the invaluable Sphinx. Chapter 11 covers test-driven development and is nicely split into an “I Don’t Test” section followed by an “I do test” for new or existing converts, running over stdlib tools and nose / py.test. The chapter concludes with Fakes & Mocks and I think gives just the right amount of space to all of its sections and recommended tools: enough to give you a flavour of the tool without weighing you down with an extended example which might be better hosted on the tool’s own site.

The final section covers optimisation techniques, and covers approaches, data structures, caching, and multi-whatever programming touching on each one for long enough to give good enough feel of the pros and cons. More examples of useful tools which help to analyse the results of profiling and memory use. Here again I think the author’s approach shines: he’s doing a kind of question-and-answer session by himself and the motivation behind a number of the recommendations becomes a lot clearer. The very last chapter covers a number of the common Design Patterns, and I’m afraid I’ve never found the famous Patterns a particularly digestible topic so I’ll skip any commentary here.

In conclusion “Expert Python Programming” is definitely worth reading. If, like me, you’re not a fan of eggs and the like, then borrow it from a friend or the library lest you feel that you’ve wasted half the price. If you are a fan of eggs or are neutral, then buy the book. The best parts for my money are those where the author is covering speedily several different approaches or tools or issues and illustrating each one briefly.

As a postscript: the author’s examples are geared to Unix. Early on, he recommends installing MSYS and if you are a Windows user and a sufficient novice not to know what to substitute where, that might be your best solution. Personally I give MSYS & Cygwin the go-by as much as I can, unless forced (eg when trying build the ffmpeg libraries). Just one of those things.

Even if you do need it…

This is a response of sorts to Fuzzyman’s post “You don’t really need it”. In any community discussion, people whose views are passionately held and who feel strongly enough to persevere in a discussion will tend to dominate any thread and will eventually be remembered as “the community thinks”. Their views might be representative or not, reasonable or not. But they will be heard. In addition, it’s clear that some commentators come along to the discussion forum with an attitude of “I know what’s wrong with Python…” and no matter what the relative merits of their proposal, this approach will tend to provoke more or less hostility. We’re all human, after all. Tempers can rise, things can get heated. Sometimes the best thing is just to back off before posting for a bit. I’m sure I’m not the only person who’s penned a strong response to a post just to get it off my chest, and then deleted it without sending.

By and large I agree with Michael in the points he makes. “You don’t really need it” can be a valid put-off, especially for inexperienced developers, but it can’t be a universal answer. The flip-side, however, is: ok, you might need it, but should it become part of Python-the-language or even Python-the-stdlib?

The latter is slightly easier to answer: create a module which does what you want; post it on PyPI, advertise it through your blog and the lists; and see what the uptake is. For an example, the pyprocessing module is just making it into the Python stdlib as “multiprocessing” after someone (not its author) proposed it, wrote a PEP, garnered some support — and some opposition — from core developers, and finally got the thumbs-up from the BDFL. Likewise, simplejson I think. Other suggestions haven’t made it in, and sometimes there’s no clear divide except what takes the fancy of the relevant people. This isn’t a democracy, and life isn’t always fair :)

The former — changes to the core language — are of course far more tricky. And I get the impression that people coming fairly fresh to the language imagine that adding some keyword or syntax change is basically just as easy as updating a docstring. They find the use case that supports a (possibly reasonable) change or addition and don’t consider: existing code which might break; wider technical or technosocial[*] ramifications of the change; or simply the amount of work needed to add a new keyword or construct, add tests for it, and make sure it doesn’t do what it shouldn’t. And that’s assuming that enough people agree that it’s a good idea.

A lot of discussion goes on around even approved changes to the language (some of it bikeshedding, certainly) and this can bewilder people who thought that, on the surface, they were proposing a simple and useful thing.


[*] A word I’ve just invented to mean the kind of things technies discuss among themselves.

Paean to pysvn

pysvn is one of those packages I use only occasionally but every time I need it, it comes up to scratch. And the simplicity of the API combined with the helpful examples covering all the major uses means that I’m up-and-running in minutes. Seconds, even.

The latest bacon-salvation need arose from code.google.com’s apparent inability to accept large commits to its Subversion repositories. I was getting spurious 502 errors again and again. So, one 10-line script later and we’re committing 100 changes at a go. Took a while but it worked.

import pysvn

svn = pysvn.Client ()
while True:
  adds = [f.path for f in svn.status (".") 
    if f.text_status == pysvn.wc_status_kind.added]
  if not adds: break
  added = adds[:100]
  print "Added", added[0], "to", added[-1], 
    "leaving", len (adds) - len (added)
  svn.checkin (added, "Suitable message")

Brilliant! Thanks, pysvn.

London Python Meetup May 2008

Thanks as always to Simon Brunning for organising another meetup at Thoughtworks, who generously funded the beer & pizza as well. Whether by planning or force of circumstances I’m not sure, but we started with half-a-dozen lightning talks before moving on to the main speaker of the evening. Simon made it clear up-front that overrunning was not an option and indeed we had six quite different brief presentations: Michael demoed the Google App Engine; Tim Couper put in a plea for involvement with PyConUK 2008 while Trent Nelson did the same for the Worldwide Bug Sprint this weekend; Peter, fresh from Grokkerdam, abandoned his laptop in favour of an explanation of how Grok makes the power of Zope3 easily available; Julius gave a humorous but heartfelt moral tale from his own experience as a Python programmers among Java-ites: don’t be too happy; I finished off with the entirely non-scientific micro-survey whose results are below.

The key speaker of the evening was Jacob Kaplan-Moss of Django fame who very generously took a bite out of his holiday over here to come and talk to us. As his vacation is decidedly low-tech, he didn’t bring a laptop but instead spoke to us about the history of Django and what their plans are for the future. (For my money this is a far better approach to presentation: watching someone generate code on a screen using a tool you’re unfamiliar with can range from bewildering to simply boring!) Even though I was aware of at least some of what Jacob was talking about, it was all the more fascinating to hear it from someone really on the inside of Django.

So, with about 50 people there yesterday, I decided to conduct an absolutely non-scientific mini-survey. When I spoke about WMI (a Windows-specific technology) at the first Thoughtworks Python meetup, I had anticipated that I’d be looking at, say, 30% Windows developers and the rest on various flavours of Unix. In fact, there were about two other Windows developers! This time I thought I’d find out where a few other loyalties lay.

The numbers below are out of 50 people and are rough estimates from the number of hands waved. I only counted exactly where there were less than 10 hands.

Where do you principally use Python? (Only put your hand up once, please)

Commercially: 30
Hobby: 15
Education: (handful)
Playing around / Don’t use Python: (handful)

On what platform do you principally use Python?

Windows: 12
Unix (not Mac): 25
Mac: 12
Other: (handful)

What version of Python do you principally use?

2.2: (none)
2.3: (handful)
2.4: 15
2.5: 30
2.6: (none)

What’s your approach to Python 3? (can answer more than one)

Following what’s happening? 20
Looking forward to it? 20
Python 3.what? (none)

Which of these Python techniques do you naturally reach for? (as opposed to simply knowing about them)

Generator expressions: 40
New-style classes: 40
Metaclasses: 10
Subprocess module: 20
Lambda: 30
Nested functions: 15
Decorators: 25
With: (handful)

Which web framework do you principally work with?

Django: 10
Turbogears: 4
Pylons: 6
Zope: 1
Raw or Cooked WSGI: 5
Other (JonPy, CherryPy): 4

After I’d finished, someone called for a vote on IDEs, but I think Simon preferred not to have a free-for-all break out in his company’s offices. I did wish I’d thought to ask how many people there use Python as their principal language (as opposed to, say, Java) and how many work in the City / Canary Wharf, ie in Financial Institutions.

Overall, I don’t know that the results indicate very much. There were more Windows developers this time than the when I gave my talk, but I doubt that represents a growth of any sort, merely the shift around of people who turn up to these things on any given month. I was surprised that there were even a handful of people still using Python 2.2/2.3 and I’d be interested in knowing what the story is there. Other than that, the fact that Django’s out on top of the Webservers isn’t surprising but — in this extremely limited survey — it’s still only got 30% of the market which is still quite fragmented.

Anyway, there it is. Thanks to everyone who came last night and made for a nice atmosphere. Hope to see you all around the next time.