Blogging from Sydney | Australia
Some past thoughts

Maker's Schedule, Manager's Schedule 

Having operated on a maker's schedule for so long, I really appreciated the insight into the manager's schedule:


Yep, learning the hard way

Woohoo! Lesson: 
Hi <name>, 
How's your day been?

25 odd characters of awesomeness, thanks <name>.

Got to tone down these intricate 30-second thoughts, TMI, and come back to Earth.

Fascinating how the academic world of course tends to push so hard in the other direction towards theses of hundreds of pages, but that doesn't excuse me for being an $%^hole.

I suppose I'm just happy I at least got to learn something, thanks <name>.


Can you upgrade to Python 3?

Basically it comes down to whether your dependencies are up to date and if they aren't, if you want to help and contribute back to the community to move them along. The easiest is to drop your requirements.txt file into:


Both of the following are also excellent tools to get an overview of how well the community is progressing:


Python 3 in Production at Mathspace

And Python 3 only in development.

A huge thank you to everyone on the Mathspace Team who supported, reviewed and helped out with this endeavour.

One day the Mathspace Team will put up a dedicated engineering blog, but until then this can live here.

For those who haven't heard me talk about bringing step-by-step working out, handwriting recognition, teachers never needing to mark homework again, adaptive mastery-based learning, geometry, interactive graphing, calculus, The Martian content (currently under development) and so on to High School and College education:

Love it? Want to help make it happen faster for parents, students and teachers? We're hiring.


Mathspace Overview

8 or so developers, similar number of content team members, lots of sales and support staff, moving to a bigger office soon.


86 Python Dependencies 

pip freeze | wc -l


147 Django models across two databases



~160k Python LOC

find . -name "*.py" | xargs wc -l


Driving change

Above all do it incrementally, following lean startup ideas such as reduce batch size, thanks Don Reinertsen, and thanks for coming to YOW! Sydney.

Based on:

for i in $(git log --grep="py3k" --format=format:"%H" --all); 
do git diff $i $i~1 --stat | grep "changed"; 

Some quick git stats:

  • 42+ specific merges to master between 30 August 2014 and 11 January 2016. It just wasn't quite ready for Christmas/New Year 2014 so as things go we built out more math features.
  • 1629 files changed
  • 18027 insertions(+)
  • 16885 deletions(-)
  • So around 11% of our code base changed, including the final 2to3 conversion, but excluding final few bug fixes for legacy code without unit tests.

Most of the development battle felt like it was dependencies because frankly Github is awesome but there's always higher latency than tapping a colleague on the shoulder or @colleague in Slack or Trello.

Upgraded dependencies

Switched out dependencies

  • Fabric => Fabric3 (at least until @bitprophet decides to give us Fabric2's alpha)
  • python-cloudfiles => apache-libcloud
  • suds (SOAP/WSDL) => Stripe billing gateway (might also use PySimpleSoap which claims Python 3 support, but it failed our unit tests before the Stripe switch and its master was broken with a print statement for months which is unfortunately not very confidence inspiring)

Removed dependencies

One pleasant point was upgrading Django because we have pretty good unit test coverage:

# -Wonce works too, just
# -Wall is funner to read out loud
python -Wall test

Tells you what's RemovedInDjango18, RemovedInDjango19, RemovedInDjango110, which you'll need to fix sooner or later.


Making the switch

Basically the things that broke were things with no unit tests. We fixed all of them over a weekend with a few quick deploys, thanks to David Cramer and the Sentry team.

How did we build the final branch?

0. Move anything that works under Python 2+3 off into a separate branch, reviewed and merged regularly.

1. pyenv (or homebrew) to install Python 3

brew update
# Mavericks and El Capitan come with versions of SQLite3 that
# segfault when running "./ test", so update it
brew install sqlite3
brew link --force sqlite3
brew install pyenv
# Tell pyenv to use updated SQLite3
# Takes around 2 minutes
time PYTHON_CONFIGURE_OPTS="LD_RUN_PATH=/usr/local/opt/sqlite/lib LDFLAGS=-L/usr/local/opt/sqlite/lib CPPFLAGS=-I/usr/local/include" pyenv install 3.4.4
# Test it
$ python
>>> import sqlite3
>>> sqlite3.sqlite_version
# Remember to use it when making virtualenvs:
mkvirtualenv mathspace_py3 --python=$HOME/.pyenv/versions/3.4.4/bin/python

2. Look for outdated dependencies, update them, split off upgrades of dependencies with 2+3 versions when possible.

3. Update / migrate our code base internally. As an end project, 2to3 was a better choice than six.

  1. 2to3 . --output-dir=. -n -w
  2. Fix imports 2to3 did not get right so unit tests run. DiscoverRoadRunner was invaluable saving me 4 minutes per test-driven-development cycle, I know I should spend more time on it though Django 1.9's builtin test runner does "--parallel" 🍻
  3. Fix other things 2to3 tool did not get right, e.g. Django's smart_unicode => smart_text
  4. Update or fix unit tests under Python 3 accordingly, things like tests that failed with different PYTHONHASHSEED values that were bugs under Python 2 but only detected under Python 3.
  5. Check things like the following don't throw errors, add unit tests if they did:
    ./ runserver
    ./ celery worker

4. Add new Python 3 servers - thanks Rackspace devops for getting poise-python working so supervisor can stay under Python 2.

5. Test on staging environments for any issues.

6. Deploy to production.

7. Fixed a few things fast. Thanks to Sentry we could pounce quickly. Maybe 20-30 users affected by Sentry events total, so 99.94% or more of our >50k users in that particular low-traffic week would never have noticed, anecdotally better than our last Django upgrade.

Was it overengineered? Perhaps, but to channel Django, Mathspacers can be perfectionists with deadlines, and we do think it is very important to provide the best experience we possibly can for parents, students and teachers.



  • Better unit testing
    with self.subTest()
  • Cleaner superclass calls:
    # Instead of monstrosities like
    super(MyClassName, self)
    super(MyClassName, cls)
  • Cleaner types and unicode support:
    long->int, str => bytes, unicode => str
  • Can't easily go the wrong way when using:
    .decode() or .encode()
  • No re.UNICODE flag required in regular expressions.
  • Don't need to remember
    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
  • No cryptic backtick syntax:
    repr(a) == `a`
  • Don't lose original error if another error triggered in an except block
  • Hundreds of other bugs fixes and feature improvements:

Nick Coghlan can tell the full story far better than I:


Why I used my house deposit to pay off my HECS-HELP loan

“Compound interest is the eighth wonder of the world. He who understands it, earns it ... he who doesn't ... pays it.” - Albert Einstein

So I tweeted this, and followed through today with the largest B-Pay I've ever made. Should be confirmed debt-free by the end of the week, at the cost of my house deposit.


The short version

A little more supporting reasoning

  • $40,000 is a relative drop in the bucket on a >$720,000 Sydney median house price. (And I'd be hard-pressed to save $30,000 a year just to pay 5% interest-only on $600,000 - a doubling of rates to 10% would wipe me out entirely and 17% from the Hawke/Keating era... I can only dream of earning that on a savings account if I could hold a job in such likely recessionary times).
  • CBA Goalsaver today = 3.81% total interest requiring $200 put in every month, with rates probably continuing to fall while CBA makes more record profits (built on a bubble, not touching our big four's shares with a 10 foot pole except in my super fund so I can argue that itch is scratched).
  • I'd pay a marginal tax rate of at least 38.5% (inc Medicare Levy) for FY2013-14 (39% in 2014-15, thankfully dodged the $80,000 additional deficit levy but such a shame they didn't try reforming superannuation which would have hit me, CGT discount which would have tripped me into speculating more, or negative gearing which would have been the best thing I think they could have done, I stand by my previous thoughts that it's the defining ridiculous misallocation of resources... paying people to lose money... utterly insane), leaving me with 2.34% effective interest on the CBA Goalsaver at most, about 0.5% under inflation.
  • Interest rates on all savings accounts I've looked at over the past year or so have continued to meet expectations and fall from well over 4% to the current levels.
  • Commonwealth Government decided to raise rates to the 10 year T-Bond rate, today funnily enough is about 3.81% (an instant rise of 0.9% relative to last year's indexation of 2.9%).
  • In sum, I would be paying between 1.5% to 3.5% for the optionality of holding my house deposit that is going to struggle to reach a reasonable 20% or $150,000 over the next 6 years anyway (with no price growth or rather a price crash), well up from 0.5% (though that may be long enough for some windfall rebalancing gains to come through if I get lucky with my other investments). But since it's never going to be enough anyway, I might as well seek the Australian Dream, er Farce in a country that has less red tape stopping developers building houses.
  • Retain some ethical / moral high ground, not one of these.
  • USA is looking more interesting all the time... (it's also nice when a highly-productive web/Python/Django/Machine Learning first class honours CS degree holder gets offers to be poached by certain technology companies) why should I support a broken system? (though they are at 90% debt to GDP so it looks like the boomers have raided everyone's cupboards...) 2015's budget will be very important to actually ending the age of entitlement, but I'm not holding my breath, just continuing to move bits and pieces offshore (with full reporting to the ATO of course).