Wandering Thoughts archives

2011-04-06

Monkey subclassing for fun and profit

Monkey subclassing is my own term for something that I wind up doing every so often, formed by analogy to monkey patching. Where monkey patching is changing the class itself, monkey subclassing is changing the class by subclassing it and then overriding internal method functions (generally undocumented ones, or at least ones not officially exposed as public interfaces). Monkey subclassing is different from regular subclassing in that I am playing fast and loose with the internals of someone else's code and in fact these classes may never have been designed to be subclassed to start with.

Monkey subclassing has the advantage that it only changes the class for you, not for all code in your program that's using the class. It has the potential disadvantage that you may need to force other bits of the program to use your new subclass instead of the original class, forcing you to monkey subclass them in turn and soon it is monkeys all the way up.

(This depends on what sort of a change you want; sometimes you're happy with a private version of the class with a particular feature that you need.)

All of this is abstract, so let me make it concrete. Django has a few options for sending email, but for our purposes none of them were ideal; we could either send via SMTP with its problems or invoke sendmail directly, which didn't offer enough control of headers and suchlike. What we wanted to do was talk SMTP to the local mailer over a pipe, which would give us the full control we needed while meaning that we didn't have to worry about handling retries and queueing ourselves, but there is no canned solution for that right now in Django (or at least none that I saw when I went looking).

Talking SMTP over a pipe to a local process is very similar to talking SMTP over the network to a remote SMTP server, and Django mostly uses standard Python things to do the latter. So I made two monkey subclasses. First, I subclassed the standard Python smtplib's SMTP class to have a connect() method that invoked the local mailer with the right arguments in a subprocess instead of making a network connection. Second, I monkey subclassed Django's standard SMTP email backend to have an open() method that used my new monkey 'local SMTP' subclass instead of smtplib.SMTP to create its 'SMTP connection' that it would send mail over. The net result was that with two relatively short routines I had just what I wanted: our Django application sends mail using the local mailer but with full control over the message envelopes and headers.

I don't think that either class considers the methods that I overrode in my subclasses to be public interfaces. In fact, I'm not sure that either class is intended to be subclassed by outside people. And certainly a code change in either module could easily break my subclassing; monkey subclassing is the fast way, not necessarily the morally correct way.

(The morally correct way to solve my problem would be to write my own Django email backend from scratch in order to do this, and never mind the code duplication and time involved.)

python/MonkeySubclassing written at 23:30:33; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.