Tidbits | July 12, 2014

Python Dev Tip: DRY your shell with PYTHONSTARTUP

by Frank Wiles

Do you find yourself re-doing the same Python shell stuff?

I know I do. I'm constantly doing it and replaying some of my IPython history isn't cutting it for me anymore. IPython has profiles but they're not terribly easy to get working with Django and your typical manage.py shell command.

Site wide is easy, but what about per project?

You can load things and execute code for ALL your Python shells system wide pretty easily, but what about when you need different things loaded depending on the project?

Turns out it's pretty easy to do with an environment variable and virtualenvwrapper. If you aren't already using virtualenvwrapper you should really check it out. I use it even outside of Python projects just to have an easy mechanism for different "shell environments" to have certain things executed when I start and finish working on a project.

The situation I was in...

While the issue that lead me to figure this out was a Django project, nothing about this technique is Django specific. I have a pretty common workflow when doing green field Django development where I setup Fabric tasks to rebuild my local dev database and repopulate it with certain database objects I need to make the site functional enough for testing. For example, I typically setup a User object with username 'frank' (I know big surprise there) that is a superuser. So if I launch a shell 9 out of 10 times I probably need my frank User object for something.

This is complicated however by the fact that on some projects I work on the User model is customized to not have a username so in some projects I need to run:

frank = User.objects.get(username='frank')

And in others I need:

frank = User.objects.get(email='frank@revsys.com')

What I realized was Python honors and executes the code in any file you specify in the PYTHONSTARTUP environment variable in your shell. So if you adjust your virtualenv's postactivate and postdeactivate hooks to set the path to code you want run on a per project basis. For example:

In ~/.virtualenvs/projectX/bin/postactivate

  #!/bin/bash
  export PYTHONSTARTUP=/Users/frank/work/src/projectX/startup.py
  export DJANGO_SETTINGS_MODULE="projectX.settings.dev"
  cd /Users/frank/work/src/projectX

In ~/.virtualenvs/projectX/bin/postdeactivate

  #!/bin/bash
  unset $DJANGO_SETTINGS_MODULE
  unset $PYTHONSTARTUP

So what can you do with this?

Well you can execute any arbitrary Python code so you could could do a lot, even go so far as to hit your time tracking software's API to log the time, but realize anything you run will slow down the startup time of each Python shell you launch so keep it to what you really need. On this particular project I kept it to a few small things:

  # Import datetime and some of my commonly used Models 
  import datetime
  from django.contrib.auth import get_user_model
  from app1.models import ThatOneModel
  from app2.models import TheOtherCommonModel

  try:
      User = get_user_model()
      frank = User.objects.get(username='frank')
  except User.DoesNotExist:
      # Frank not being in the DB shouldn't raise an error
      pass

Hope this helps you stay in flow more and delay your carpal tunnel a few days at least!

Simple way to use virtaualenvwrapper's postactivate and deactivate hooks to execute arbitrary Python code for any of your Python shells while working on that project.

2014-07-12T15:08:55 2018-04-18T16:25:12.851917 2014 python,django,Featured Posts