SaltyCrane Blog — Notes on Python and web development on Ubuntu Linux

Switching to OS X and front end development

After 7 years, I've yielded to the Dark Side and switched from Ubuntu to OS X on my work laptop. I've also switched from Python and back end web development to Javascript and front end development. The former is mostly to support the latter.

Linux is rare1, especially among front end developers, and I want to make it easy to collaborate as I learn new things. I've had problems working with Photoshop files in GIMP and I couldn't run the iOS simulator. Issues with Linux device drivers don't help.

I'm choosing front end development because I want to code closer to the end user.2 In small part like Ian Bicking wrote last year, I feel unexcited about back end development and really excited about Javascript and front end development. I'm excited about ES 2015 and React and React Native and CSS transitions. I'm even coming around to Node.js. Javascript is uglier than Python, but it's getting better and there are things Python can't do that Javascript can. If only beauty mattered, I'd use something like Scheme.3

I'm sure I will hate OS X at first, but hopefully it will be good in the long run. If anyone can recommend a tiling window manager like Qtile for OS X, please let me know.

(I will continue using Emacs because Emacs rocks!)

  1. I think I was the last person at my company running Linux.
  2. I've been trying to do front end work for years now, but I finally got a sensible chance to switch as my company is changing it's technology stack from Python to Ruby and Backbone/Angular to React.
  3. Speaking of functional languages and Javascript, Elm sounds pretty cool.

Logging the actual host (instead of localhost) with the Python elasticsearch client

The Elasticsearch Python library has a cool feature that allows you to log the equivalent curl command of the query you are making. To use it you just need to configure the elasticsearch.trace logger. There is one annoyance-- by design, "localhost:9200" is logged even if you have configured another host. It is more useful to log the actual host I am using so I can copy and paste the command to co-workers without changing the hostname. At first I thought I'd monkeypatch the library to do what I wanted. Then I remembered I could use a logging.Filter to rewrite the log message.

I am using the high level elasticsearch-dsl library in my example, but the logging is actually done by the low level official elasticsearch library which elasticsearch-dsl wraps.

$ pip install elasticsearch-dsl
import logging

import elasticsearch_dsl as esdsl  # high level elasticsearch library
from elasticsearch import Elasticsearch  # low level official elasticsearch library


class ReplaceHostFilter(logging.Filter):
    Replace "localhost:9200" with real host
    def __init__(self, real_host, *args, **kwargs):
        self.real_host = real_host
        super(ReplaceHostFilter, self).__init__(*args, **kwargs)

    def filter(self, record):
        record.msg = record.msg.replace('http://localhost:9200', self.real_host)
        record.msg = record.msg.replace('curl', 'curl -sS')
        record.msg += ' | python -mjson.tool'
        return True

# Set up logging
host_replacer = ReplaceHostFilter(ELASTICSEARCH_HOSTS[0])
formatter = logging.Formatter("%(levelname)s %(asctime)s %(name)s - %(message)s")
sh = logging.StreamHandler()
eslogger = logging.getLogger('elasticsearch.trace')

# Make a query
esclient = Elasticsearch(hosts=ELASTICSEARCH_HOSTS)
search = esdsl.Search(using=esclient, index='my_index')
filt = esdsl.F('term', myTerm=1234)
search = search.filter(filt)
response = search.execute()

Log output showing the actual host instead of localhost:

INFO 2015-07-28 13:48:54,464 elasticsearch.trace - curl -sS -XGET '' -d '{
  "query": {
    "filtered": {
      "filter": {
        "term": {
          "myTerm": 1234
      "query": {
        "match_all": {}
}' | python -mjson.tool

Customizing Bootstrap (Sass version) using Grunt and Bower

Update 2015-02-22: My co-workers informed me that Grunt is so last year and Gulp is the new hotness. You wish this post covered Gulp, but instead it covers Grunt.

I recently converted this blog to use Twitter's Bootstrap CSS framework to make it responsive. (In particular, I wanted to read it on my phone.) I had been using Blueprint CSS from 2008 so it was about time for an update. Unfortunately my blog design is also from 2008 but I'm not updating that. (Web design is hard.) Update: I ended up refreshing the design a little. (Web design is hard.)

My problem was that 2014 Bootrap's 200px gutter width didn't match my 2008 design's 2px gutter. So I wanted to customize the gutter width.

There are a few ways to customize Bootstrap. One option is creating a custom version on Bootstrap's website and downloading it for use on your site. However this doesn't allow you to change things quickly as you develop. Another option is overriding Bootstrap's style in your site stylesheet. However, it's hacky to write compilcated selectors to override something in multiple places when it is set in a single variable in Bootstrap. For example, to change the gutter width, here is the single variable that needs to be changed.


Here is the approach I took to customize Bootstrap. I'm running on Ubuntu 14.10 Utopic Unicorn 64-bit.

  • Use the Sass version of Bootstrap. The standard Bootstrap project uses Less but my limited knowledge of frontend technology tells me Sass (specifically SCSS) is a better choice.
  • Install Bootstrap using Bower. Bower is a tool used to install frontend packages.
    "What's bower?" "A package manager, install it with npm." "What's npm?" "A package manager, you can install it with APT" "What's APT?" - adapted from Stefan Baumgartner's tweet
    An alternative is to just download Bootstrap and add it to your project. I chose Bower because I wanted to be like the cool kids from 2 years ago and it would be nice to upgrade my libraries with a simple command.

    Update 2015-03-22: Another alternative is to use npm to install pacakges. Bower is optimized for frontend components while npm historically is used for backend packages[1] but there is discussion about merging functionality[2].

  • Commit the bower_components directory to git. Alternatively you can leave bower_components out of git and use something like grunt-bower-task or grunt-bower-copy to copy stuff from bower_components into your project. I chose to commit bower_components to git so that I wouldn't need an extra grunt task. The downside is that I have big long ugly paths in my bootstrap.scss file.
  • Override Bootstrap's Sass variables, remove unneeded components, and combine Bootstrap with my site's stylesheet using Sass @imports.
  • Compile the Sass SCSS files to CSS using grunt-contrib-sass.
  • Commit the compiled Sass files to git. An alternative is to compile the files as part of the deploy process. At work, it is a pain keeping these files in git because there are many merge conflicts and differences in build tool versions and platforms between developers. For my blog, I am the only committer so I won't run into this.

Project directory structure

Here is what my project directory structure looks like:

├── Gruntfile.js
├── bower.json
├── bower_components
│   ├── bootstrap-sass-official
│   └── jquery
├── node_modules
│   ├── grunt
│   ├── grunt-contrib-sass
│   └── grunt-contrib-watch
├── package.json
├── sass
│   ├── _bootstrap-variables-override.scss
│   ├── _bootstrap.scss
│   └── mystyle.scss
└── static
    └── css

Install Sass

Sass is installed using RubyGems, the Ruby package manager.

$ sudo apt-get install ruby 
$ sudo gem install sass 
$ sass --version 
Sass 3.4.13 (Selective Steve)

Install grunt-cli, and bower

Both Grunt and Bower depend on npm, the Node.js package manager.

$ sudo add-apt-repository ppa:chris-lea/node.js 
$ sudo apt-get update 
$ sudo apt-get install nodejs 
$ npm --version
$ sudo npm install -g grunt-cli 
$ sudo npm install -g bower 
$ grunt --version 
grunt-cli v0.1.13
$ bower --version 

Install bootstrap-sass using Bower

bootstrap-sass is the official Sass port of Bootstrap. First create a bower.json file, /tmp/my-project/bower.json. For more information about bower.json files and installing packages, see the Bower documentation.

  "name": "my-project",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "bootstrap-sass-official": "3.3.1"

Then I ran bower install to download the files. The packages are stored in the bower_components directory.

$ cd /tmp/my-project 
$ bower install 

Customize Boostrap

To customize Boostrap, I copied the bower_components/bootstrap-sass-official/assets/stylesheets/boostrap.scss file to my sass directory, changed the paths, deleted the components that I didn't need, and added a line for my custom variable overrides. If anyone knows how to put that long path into a single place, I'd appreciate the tip. Here is my final file, named /tmp/my-project/sass/_bootstrap.scss:

Update 2015-03-18: To answer my own question, I think setting the loadPath option with grunt-contrib-sass will solve the long path problem.

// Core variables and mixins
// variable overrides come first (without the !default)
// bootstrap default variables come second because they use sass !default (won't get set if set already')
// and some variables in here depend on our overrides.
@import "bootstrap-variables-override";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/variables";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/mixins";

// Reset and dependencies
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/normalize";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/print";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/glyphicons";

// Core CSS
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/scaffolding";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/type";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/code";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/grid";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/tables";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/forms";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/buttons";

// Utility classes
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/utilities";
@import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/responsive-utilities";

The Boostrap variables are located in bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/_variables.scss I copied the ones that I was interested in into a _bootstrap-variables-override.scss file. Be sure to remove the !default flag and import this file before the default bootstrap variables. For more information about !default, see the Sass documentation. Here is my /tmp/my-project/sass/_bootstrap-variables-override.scss file:

// overrides here (do not use !default here)
$grid-gutter-width: 10px;
$font-size-base: 16px;
$headings-font-weight: 800;

In my site stylesheet, I @import the _bootstrap.scss file. Here is my /tmp/my-project/sass/mystyle.scss file:

@import "bootstrap";
/* add site style here */

Install grunt plugins

To compile the Sass files to CSS, I am using grunt-contrib-sass. I created this package.json file, /tmp/my-project/package.json. For more information about the package.json file and installing plugins, see the Grunt documentation.

  "name": "my-project",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "0.4.5",
    "grunt-contrib-sass": "0.8.1",
    "grunt-contrib-watch": "0.6.1"

Then I ran npm install to install grunt, grunt-contrib-sass, and grunt-contrib-watch.

$ cd /tmp/my-project 
$ npm install 

Create Gruntfile.js

My Gruntfile.js uses the grunt-contrib-sass plugin to compile the Sass to CSS. My input file is sass/mystyle.scss and my output file is static/css/mystyle.css. I am also using the grunt-contrib-watch plugin during development to automatically run the compilation whenever a SCSS file changes. For more information on configuring tasks, see the Grunt documentation. Here is my Gruntfile, /tmp/my-project/Gruntfile.js:

sassFiles = {
    'static/css/mystyle.css': 'sass/mystyle.scss'

module.exports = function(grunt) {
        sass: {
            dev: {
                options: {
                    style: 'expanded'
                files: sassFiles
            dist: {
                options: {
                    style: 'compressed'
                files: sassFiles
        watch: {
            sass: {
                files: 'sass/*.scss',
                tasks: ['sass:dev']
    grunt.registerTask('buildcss', ['sass:dist']);

Run grunt

To compile the Sass to CSS for production, I run grunt buildcss:

$ cd /tmp/my-project 
$ grunt buildcss 

During development, I use grunt watch to build whenever a SCSS (Sass) file changes.

$ cd /tmp/my-project 
$ grunt watch 

See Also / References

Ordered a Lenovo X1 Carbon for 40% off

I ordered a ~$1200 2.8 lb. 14" Lenovo Thinkpad X1 Carbon (2014, 2nd generation, 20A7) today at 40% off for the Black Friday / Cyber Monday sale. I hope I like the keyboard. I splurged for an Intel Core i7-4600U, 8GB RAM, 256GB SSD, and the WQHD 2560x1440 display. I have enjoyed 7 months with my ~$200 2.8 lb. 11" Acer C720 Chromebook. It's a pleasant contrast to my work-issued 5.9 lb. 17" Lenovo Thinkpad W510. Crouton worked well for running Linux on the Chromebook but I wanted a dedicated Linux laptop and a better screen. I will bequeath the C720 to my wife since she currently uses my 7+ year old Dell Inspiron E1405. I considered the 3.5 lb. 14" Lenovo Thinkpad T440s due to better battery/keyboard/flexibility, but the X1 Carbon was ~$200 cheaper for a similarly spec'd model due to a bigger discount and I liked the thinner and lighter design and better display. Many reviews complained about the new keyboard layout and adaptive function row. I hope key remapping will reduce the pain enough. editor, Jonathan Corbet mentioned he bought an X1 Carbon in High-DPI displays and Linux, so it can't be too bad, right?

Here is the full 2014 2nd Gen ThinkPad X1 Carbon (Type 20A7, 20A8) spec sheet.

Update 2015-03-14: Lenovo released the 3rd generation of the X1 Carbon which fixes the keyboard and improves battery life. Too bad I didn't wait a few months. Here is the new 2015 3rd Gen spec sheet.

How to install grunt on Ubuntu 14.04

Grunt is a Javascript task runner that can be used to compile Sass, run JSHint, or run many other plugins. It depends on the Node.js package manager, npm.

If you use the standard Ubuntu Trusty Tahr repository to install nodejs/npm, you will get this error when running grunt:

/usr/bin/env: node: No such file or directory
Instead, use the chris-lea nodejs repository.

Install nodejs, npm, and grunt-cli

$ sudo add-apt-repository ppa:chris-lea/node.js 
$ sudo apt-get update 
$ sudo apt-get install nodejs 
$ sudo npm install -g grunt-cli 

Install grunt in your project directory

$ cd ~/myproject 
$ echo "{}" > package.json 
$ npm install grunt --save-dev 

Verify grunt is installed

$ nodejs --version 
$ npm --version 
$ grunt --version 
grunt-cli v0.1.13
grunt v0.4.5

Run a simple grunt task

  1. $ cd ~/myproject
  2. Create a package.json file:
      "name": "my-project-name",
      "version": "0.1.0",
      "devDependencies": {
        "grunt": "~0.4.5",
        "grunt-contrib-uglify": "~0.5.0"
  3. Install grunt-contrib-uglify
    $ npm install 
    npm WARN package.json my-project-name@0.1.0 No description
    npm WARN package.json my-project-name@0.1.0 No repository field.
    npm WARN package.json my-project-name@0.1.0 No README data
    grunt-contrib-uglify@0.5.1 node_modules/grunt-contrib-uglify
    ├── chalk@0.5.1 (ansi-styles@1.1.0, escape-string-regexp@1.0.2, supports-color@0.2.0, strip-ansi@0.3.0, has-ansi@0.1.0)
    ├── lodash@2.4.1
    ├── maxmin@0.2.2 (figures@1.3.5, pretty-bytes@0.1.2, gzip-size@0.2.0)
    └── uglify-js@2.4.15 (uglify-to-browserify@1.0.2, async@0.2.10, optimist@0.3.7, source-map@0.1.34)
  4. Get an example unminified JS file:
    $ wget 
    --2014-11-22 00:47:31--
    Resolving (,
    Connecting to (||:80... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 247351 (242K) [application/x-javascript]
    Saving to: ‘jquery-2.1.1.js’
    100%[================================================================================================================>] 247,351     --.-K/s   in 0.1s    
    2014-11-22 00:47:31 (1.71 MB/s) - ‘jquery-2.1.1.js’ saved [247351/247351]
  5. Create a Gruntfile.js file:
    module.exports = function(grunt) {
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
          build: {
            src: 'jquery-2.1.1.js',
            dest: 'jquery-2.1.1.min.js'
      grunt.registerTask('default', ['uglify']);
  6. Run the grunt task:
    $ grunt 
    Running "uglify:build" (uglify) task
    Done, without errors.
  7. You should now have a minified file, jquery-2.1.1.min.js
    $ ls -gG jquery* 
    -rw-rw-r-- 1 247351 2014 10/23 17:16 jquery-2.1.1.js
    -rw-rw-r-- 1  84113 2014 11/22 00:48 jquery-2.1.1.min.js


An example using groupby and defaultdict to do the same task

Here is some data that I want to group by model:

    {'model': u'Yaris', 'some_value': 11202, 'trim_name': u'3-Door L Manual'},
    {'model': u'Yaris', 'some_value': 19269, 'trim_name': u'3-Door LE Automatic'},
    {'model': u'Corolla', 'some_value': 27119, 'trim_name': u'L Automatic'},
    {'model': u'Corolla', 'some_value': 32262, 'trim_name': u'LE'},
    {'model': u'Corolla', 'some_value': 37976, 'trim_name': u'S Premium'},
    {'model': u'Camry', 'some_value': 39730, 'trim_name': u'LE 4-Cyl'},
    {'model': u'Camry', 'some_value': 45761, 'trim_name': u'XSE 4-Cyl'},
    {'model': u'Yaris', 'some_value': 48412, 'trim_name': u'3-Door L Automatic'},
    {'model': u'Camry', 'some_value': 55423, 'trim_name': u'XLE 4-Cyl'},
    {'model': u'Corolla', 'some_value': 57055, 'trim_name': u'ECO Premium'},
    {'model': u'Corolla', 'some_value': 61296, 'trim_name': u'ECO Plus'},
    {'model': u'Camry', 'some_value': 63660, 'trim_name': u'XSE V6'},
    {'model': u'Yaris', 'some_value': 65570, 'trim_name': u'5-Door LE Automatic'},
    {'model': u'Camry', 'some_value': 67461, 'trim_name': u'XLE V6'},
    {'model': u'Corolla', 'some_value': 73602, 'trim_name': u'S'},
    {'model': u'Yaris', 'some_value': 74158, 'trim_name': u'5-Door SE Manual'},
    {'model': u'Corolla', 'some_value': 74249, 'trim_name': u'LE Plus'},
    {'model': u'Corolla', 'some_value': 78386, 'trim_name': u'ECO'},
    {'model': u'Camry', 'some_value': 82747, 'trim_name': u'SE 4-Cyl'},
    {'model': u'Corolla', 'some_value': 83162, 'trim_name': u'LE Premium'},
    {'model': u'Corolla', 'some_value': 84863, 'trim_name': u'S Plus Manual'},
    {'model': u'Yaris', 'some_value': 90313, 'trim_name': u'5-Door L Automatic'},
    {'model': u'Corolla', 'some_value': 90452, 'trim_name': u'L Manual'},
    {'model': u'Yaris', 'some_value': 93152, 'trim_name': u'5-Door SE Automatic'},
    {'model': u'Corolla', 'some_value': 94973, 'trim_name': u'S Plus CVT'},

This can be done using defaultdict from the collections module.

import collections

grouped = collections.defaultdict(list)
for item in SOME_DATA:

for model, group in grouped.items():
    print model
    pprint(group, width=150)

Here are the results:

[{'model': u'Yaris', 'some_value': 27065, 'trim_name': u'5-Door L Automatic'},
 {'model': u'Yaris', 'some_value': 32757, 'trim_name': u'5-Door SE Automatic'},
 {'model': u'Yaris', 'some_value': 57344, 'trim_name': u'3-Door L Manual'},
 {'model': u'Yaris', 'some_value': 64002, 'trim_name': u'5-Door SE Manual'},
 {'model': u'Yaris', 'some_value': 77974, 'trim_name': u'3-Door L Automatic'},
 {'model': u'Yaris', 'some_value': 92658, 'trim_name': u'3-Door LE Automatic'},
 {'model': u'Yaris', 'some_value': 98769, 'trim_name': u'5-Door LE Automatic'}]

[{'model': u'Camry', 'some_value': 30247, 'trim_name': u'XSE 4-Cyl'},
 {'model': u'Camry', 'some_value': 33809, 'trim_name': u'XSE V6'},
 {'model': u'Camry', 'some_value': 65637, 'trim_name': u'LE 4-Cyl'},
 {'model': u'Camry', 'some_value': 67329, 'trim_name': u'SE 4-Cyl'},
 {'model': u'Camry', 'some_value': 76269, 'trim_name': u'XLE 4-Cyl'},
 {'model': u'Camry', 'some_value': 87438, 'trim_name': u'XLE V6'}]

[{'model': u'Corolla', 'some_value': 11239, 'trim_name': u'S'},
 {'model': u'Corolla', 'some_value': 27356, 'trim_name': u'S Plus Manual'},
 {'model': u'Corolla', 'some_value': 44792, 'trim_name': u'L Manual'},
 {'model': u'Corolla', 'some_value': 56252, 'trim_name': u'ECO Premium'},
 {'model': u'Corolla', 'some_value': 78570, 'trim_name': u'S Plus CVT'},
 {'model': u'Corolla', 'some_value': 78964, 'trim_name': u'LE Premium'},
 {'model': u'Corolla', 'some_value': 82116, 'trim_name': u'ECO'},
 {'model': u'Corolla', 'some_value': 85467, 'trim_name': u'S Premium'},
 {'model': u'Corolla', 'some_value': 87099, 'trim_name': u'L Automatic'},
 {'model': u'Corolla', 'some_value': 91974, 'trim_name': u'LE Plus'},
 {'model': u'Corolla', 'some_value': 94862, 'trim_name': u'LE'},
 {'model': u'Corolla', 'some_value': 97625, 'trim_name': u'ECO Plus'}]

This can also be done using itertools.groupby. This method is probably better when working with large datasets because groupby returns the group as an iterator. (This is the reason I convert it to a list before printing.)

import itertools

def keyfunc(x):
    return x['model']

SOME_DATA = sorted(SOME_DATA, key=keyfunc)
for model, group in itertools.groupby(SOME_DATA, keyfunc):
    print model
    pprint(list(group), width=150)

Here are the results:

[{'model': u'Camry', 'some_value': 36776, 'trim_name': u'SE 4-Cyl'},
 {'model': u'Camry', 'some_value': 56569, 'trim_name': u'LE 4-Cyl'},
 {'model': u'Camry', 'some_value': 57052, 'trim_name': u'XSE 4-Cyl'},
 {'model': u'Camry', 'some_value': 92360, 'trim_name': u'XLE V6'},
 {'model': u'Camry', 'some_value': 92756, 'trim_name': u'XSE V6'},
 {'model': u'Camry', 'some_value': 94413, 'trim_name': u'XLE 4-Cyl'}]

[{'model': u'Corolla', 'some_value': 13307, 'trim_name': u'L Automatic'},
 {'model': u'Corolla', 'some_value': 15726, 'trim_name': u'ECO Plus'},
 {'model': u'Corolla', 'some_value': 25579, 'trim_name': u'S'},
 {'model': u'Corolla', 'some_value': 31920, 'trim_name': u'ECO Premium'},
 {'model': u'Corolla', 'some_value': 34480, 'trim_name': u'LE'},
 {'model': u'Corolla', 'some_value': 44958, 'trim_name': u'S Plus Manual'},
 {'model': u'Corolla', 'some_value': 49606, 'trim_name': u'LE Premium'},
 {'model': u'Corolla', 'some_value': 59629, 'trim_name': u'LE Plus'},
 {'model': u'Corolla', 'some_value': 74226, 'trim_name': u'S Plus CVT'},
 {'model': u'Corolla', 'some_value': 75725, 'trim_name': u'L Manual'},
 {'model': u'Corolla', 'some_value': 82382, 'trim_name': u'ECO'},
 {'model': u'Corolla', 'some_value': 95633, 'trim_name': u'S Premium'}]

[{'model': u'Yaris', 'some_value': 16789, 'trim_name': u'3-Door L Manual'},
 {'model': u'Yaris', 'some_value': 20349, 'trim_name': u'5-Door LE Automatic'},
 {'model': u'Yaris', 'some_value': 42897, 'trim_name': u'5-Door L Automatic'},
 {'model': u'Yaris', 'some_value': 62045, 'trim_name': u'5-Door SE Automatic'},
 {'model': u'Yaris', 'some_value': 91913, 'trim_name': u'3-Door L Automatic'},
 {'model': u'Yaris', 'some_value': 94218, 'trim_name': u'5-Door SE Manual'},
 {'model': u'Yaris', 'some_value': 97979, 'trim_name': u'3-Door LE Automatic'}]

A bank style session timeout example using jQuery, Bootstrap, and Flask

This is an example that uses Javascript to display a session timeout warning modal 10 minutes before session expiration. It also resets the session expiration whenever the user clicks the mouse. It uses Javascript, jQuery, and Boostrap on the frontend and Python, Flask, Flask-Login, and WTForms on the backend.

  • Mouse clicks anywhere on the page ping the server at a maximum frequecy of once per minute and reset the session expiration.
  • 10 minutes before the session expiration, a warning modal is displayed with two buttons: "Log out" and "Stay Logged In".
  • If the user clicks "Stay Logged In" the session expiration is reset.
  • If the user clicks "Log out", the user is logged out.
  • If the user does nothing for 10 minutes, the user is logged out and displayed a message that the session timed out.

Here is the Javascript (session-monitor.js):

sessionMonitor = function(options) {
    "use strict";

    var defaults = {
            // Session lifetime (milliseconds)
            sessionLifetime: 60 * 60 * 1000,
            // Amount of time before session expiration when the warning is shown (milliseconds)
            timeBeforeWarning: 10 * 60 * 1000,
            // Minimum time between pings to the server (milliseconds)
            minPingInterval: 1 * 60 * 1000,
            // Space-separated list of events passed to $(document).on() that indicate a user is active
            activityEvents: 'mouseup',
            // URL to ping the server using HTTP POST to extend the session
            pingUrl: '/ping',
            // URL used to log out when the user clicks a "Log out" button
            logoutUrl: '/logout',
            // URL used to log out when the session times out
            timeoutUrl: '/logout?timeout=1',
            ping: function() {
                // Ping the server to extend the session expiration using a POST request.
                    type: 'POST',
                    url: self.pingUrl
            logout: function() {
                // Go to the logout page.
                window.location.href = self.logoutUrl;
            onwarning: function() {
                // Below is example code to demonstrate basic functionality. Use this to warn
                // the user that the session will expire and allow the user to take action.
                // Override this method to customize the warning.
                var warningMinutes = Math.round(self.timeBeforeWarning / 60 / 1000),
                    $alert = $('<div id="jqsm-warning">Your session will expire in ' + warningMinutes + ' minutes. ' +
                               '<button id="jqsm-stay-logged-in">Stay Logged In</button>' +
                               '<button id="jqsm-log-out">Log Out</button>' +

                if (!$('body').children('div#jqsm-warning').length) {
                $('button#jqsm-stay-logged-in').on('click', self.extendsess)
                    .on('click', function() { $alert.hide(); });
                $('button#jqsm-log-out').on('click', self.logout);
            onbeforetimeout: function() {
                // By default this does nothing. Override this method to perform actions
                // (such as saving draft data) before the user is automatically logged out.
                // This may optionally return a jQuery Deferred object, in which case
                // ontimeout will be executed when the deferred is resolved or rejected.
            ontimeout: function() {
                // Go to the timeout page.
                window.location.href = self.timeoutUrl;
        self = {},
        // The time of the last ping to the server.
        _lastPingTime = 0;

    function extendsess() {
        // Extend the session expiration. Ping the server and reset the timers if
        // the minimum interval has passed since the last ping.
        var now = $.now(),
            timeSinceLastPing = now - _lastPingTime;

        if (timeSinceLastPing > self.minPingInterval) {
            _lastPingTime = now;

    function _resetTimers() {
        // Reset the session warning and session expiration timers.
        var warningTimeout = self.sessionLifetime - self.timeBeforeWarning;

        _warningTimeoutID = window.setTimeout(self.onwarning, warningTimeout);
        _expirationTimeoutID = window.setTimeout(_onTimeout, self.sessionLifetime);

    function _onTimeout() {
        // A wrapper that calls onbeforetimeout and ontimeout and supports asynchronous code.

    // Add default variables and methods, user specified options, and non-overridable
    // public methods to the session monitor instance.
    $.extend(self, defaults, options, {
        extendsess: extendsess
    // Set an event handler to extend the session upon user activity (e.g. mouseup).
    $(document).on(self.activityEvents, extendsess);
    // Start the timers and ping the server to ensure they are in sync with the backend session expiration.

    return self;

Here is the important HTML:

<!DOCTYPE html>
<html lang="en">
        <meta charset="utf-8" />
        <link rel="stylesheet" href="" />
        <div id="session-warning-modal" class="modal">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title" id="sessWarnLabel">Your session is about to expire<a class="sectionlink" title="Section permalink" href="#sessWarnLabel"></a></h4>
                    <div class="modal-body">
                        Your session will expire in <span id="remaining-time"></span> minutes due to inactivity.
                    <div class="modal-footer">
                        <button id="log-out" class="btn btn-default" type="button" data-dismiss="modal">Log Out</button>
                        <button id="stay-logged-in" class="btn btn-warning" type="button" data-dismiss="modal">Stay Logged In</button>
        <script src="//"></script>
        <script src="//"></script>
        <script src="{{ url_for('static', filename='session-monitor.js')}}"></script>
        <script type="text/javascript">
            // Configure and start the session timeout monitor
            sessMon = sessionMonitor({
                // Subtract 1 minute to ensure the backend doesn't expire the session first
                sessionLifetime: {{ PERMANENT_SESSION_LIFETIME_MS }} - (1 * 60 * 1000),
                timeBeforeWarning: 10 * 60 * 1000,  // 10 minutes
                minPingInterval: 1 * 60 * 1000,  // 1 minute
                pingUrl: '/ping',
                logoutUrl: '/logout',
                timeoutUrl: '/logged-out?timeout=1&next=' + encodeURIComponent(
                    window.location.pathname + + window.location.hash),
                // The "mouseup" event was used instead of "click" because some of the
                // inner elements on some pages have click event handlers that stop propagation.
                activityEvents: 'mouseup',
                onwarning: function() {
            $(document).ready( function() {
                // Configure the session timeout warning modal
                        "backdrop": "static",
                        "keyboard": false,
                        "show": false
                    .on("click", "#stay-logged-in", sessMon.extendsess)
                    .on("click", "#log-out", sessMon.logout)
                    .find("#remaining-time").text(Math.round(sessMon.timeBeforeWarning / 60 / 1000));
            window.sessMon = sessMon;

Here is the Python Flask app (

import collections
import datetime

from flask import Flask, request, render_template, redirect, url_for, session
from flask.ext.login import (
    LoginManager, login_user, logout_user,  UserMixin, login_required)
from wtforms.fields import PasswordField, StringField
from wtforms.form import Form

UserRow = collections.namedtuple('UserRow', ['id', 'password'])
    'george': UserRow(id=1, password='george'),

# settings ###############################################################
# Set a secret key to sign the session (Flask config value)
SECRET_KEY = 'insert secret key here'

# The amount of time after which the user's session expires
# (this is a Flask setting and is also used by the Javascript)
PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=60)

# init ###############################################################
app = Flask(__name__)
login_manager = LoginManager()
login_manager.login_view = '.login'

def load_user(userid):
    return User(userid)

def add_session_config():
    """Add current_app.permanent_session_lifetime converted to milliseconds
    to context. The config variable PERMANENT_SESSION_LIFETIME is not
    used because it could be either a timedelta object or an integer
    representing seconds.
    return {
            app.permanent_session_lifetime.seconds * 1000),

# models ###############################################################
class User(UserMixin):
    def __init__(self, id): = id

# forms ###############################################################
class LoginForm(Form):
    username = StringField()
    password = PasswordField()

# views ###############################################################
@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm(request.form)
    message = ''

    if request.method == 'POST' and form.validate():
        db_user = TOY_USER_DATABASE.get(
        if == db_user.password:
            user = User(
            return redirect(url_for('.home'))
            message = 'Login failed.'

    context = {
        'form': form,
        'message': message,
    return render_template('login.html', **context)

def home():
    return render_template('home.html')

def another_page():
    return render_template('another_page.html')

def logout():
    return redirect(url_for('.logged_out') + '?' + request.query_string)

def logged_out():
    timed_out = request.args.get('timeout')
    return render_template('logged_out.html', timed_out=timed_out)

@app.route("/ping", methods=['POST'])
def ping():
    session.modified = True
    return 'OK'

if __name__ == "__main__":

The full example is also on github at:

Initial ideas were taken from but, from what I could tell, it pinged the server whether the user was active or not.

The old "%" string formatting and the new string .format() method handle unicode differently

Today I learned that the old style "%" string formatting and the new string .format() method behave differently when interpolating unicode strings. I was suprised to find out that one of these lines raised an error while one did not:

'%s' % u'O\u2019Connor'

The old style "%" formatting operation returns a unicode string if one of the values is a unicode string even when the format string is a non-unicode string:

Python 2.7.3 (default, Feb 27 2014, 19:58:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%s' % u'O\u2019Connor'

The new string .format() method called on a non-unicode string with a unicode string argument tries to encode the unicode string to a non-unicode string (bytestring) possibly raising a UnicodeEncodeError:

Python 2.7.3 (default, Feb 27 2014, 19:58:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '{}'.format(u'O\u2019Connor')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 1: ordinal not in range(128)</module></stdin>

I guess the correct thing to do is to start with a unicode format string:

Python 2.7.3 (default, Feb 27 2014, 19:58:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> u'{}'.format(u'O\u2019Connor')

See also

Python logging filters do not propagate like handlers and levels do

Loggers are organized in a hierarchical fashion. A logger named '' is a child of a logger named 'foo'.

getLogger() returns a reference to a logger instance with the specified name if it is provided, or root if not. The names are period-separated hierarchical structures. Multiple calls to getLogger() with the same name will return a reference to the same logger object. Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name of foo, loggers with names of,, and foo.bam are all descendants of foo. - Loggers documentation

If the level is not set on a logger, the level of the parent is used.

Loggers have a concept of effective level. If a level is not explicitly set on a logger, the level of its parent is used instead as its effective level. If the parent has no explicit level set, its parent is examined, and so on - all ancestors are searched until an explicitly set level is found. The root logger always has an explicit level set (WARNING by default). When deciding whether to process an event, the effective level of the logger is used to determine whether the event is passed to the logger’s handlers. - Loggers documentation

import logging

foo_logger = logging.getLogger('foo')

foo_bar_logger = logging.getLogger('')

print foo_logger.getEffectiveLevel()
print foo_bar_logger.getEffectiveLevel()

Similarly, if a handler is not defined for a logger, the handler of the parent is used.

Child loggers propagate messages up to the handlers associated with their ancestor loggers. Because of this, it is unnecessary to define and configure handlers for all the loggers an application uses. It is sufficient to configure handlers for a top-level logger and create child loggers as needed. (You can, however, turn off propagation by setting the propagate attribute of a logger to False.) - Loggers documentation

import logging

myformatter = logging.Formatter("MY HANDLER: %(name)s - %(message)s")

myhandler = logging.StreamHandler()

foo_logger = logging.getLogger('foo')

foo_bar_logger = logging.getLogger('')

MY HANDLER: 40 foo - asdfasdf
MY HANDLER: 40 - zxcvzxcv

However, filters, unlike levels and handlers, do not propagate. If a filter is not defined for a logger, the filter of the parent is NOT used.

Note that filters attached to handlers are consulted before an event is emitted by the handler, whereas filters attached to loggers are consulted whenever an event is logged (using debug(), info(), etc.), before sending an event to handlers. This means that events which have been generated by descendant loggers will not be filtered by a logger’s filter setting, unless the filter has also been applied to those descendant loggers. - Filter Objects documentation
See also the logging flowchart.

import logging

class MyFilter(logging.Filter):
    def filter(self, record):
        record.msg = 'MY FILTER: ' + record.msg
        return 1

myfilter = MyFilter()

myformatter = logging.Formatter("MY HANDLER: %(name)s - %(message)s")

myhandler = logging.StreamHandler()

foo_logger = logging.getLogger('foo')

foo_bar_logger = logging.getLogger('')

MY HANDLER: foo - MY FILTER: asdfasdf
MY HANDLER: - zxcvzxcv

I guess I'll attach it to the handler instead...

import logging

class MyFilter(logging.Filter):
    def filter(self, record):
        record.msg = 'MY FILTER: ' + record.msg
        return 1

myfilter = MyFilter()

myformatter = logging.Formatter("MY HANDLER: %(name)s - %(message)s")

myhandler = logging.StreamHandler()

foo_logger = logging.getLogger('foo')

foo_bar_logger = logging.getLogger('')

MY HANDLER: foo - MY FILTER: asdfasdf

Subdomain-based configuration for a Flask local development server

This example shows how to set up a Flask local development server to use a different configuration based on the subdomain of the request. The project I work on has several environments (dev, qa, staging, etc). Each environment has different database and API hostnames. I use this to switch between database and API environments quickly while using my local development server.

This assumes a create_app function is used to create the Flask application instance as described in the Application Factories Flask documentation.


Modify the create_app function to take a configobj argument and use it to override the default configuration.

def create_app(configobj=None):
    app = Flask(__name__)

    # Default configuration

    # Override configuration using config passed into create_app
    if configobj:

    return app


The SubdomainDispatcher is taken from the Application Dispatching Flask documentation. It is WSGI middleware that looks at the subdomain of the request and returns a different application instance for each subdomain. It calls the create_app function above and passes it the appropriate configuration object for the subdomain.

class SubdomainDispatcher(object):

    def __init__(self, create_app, domain=''):
        :param create_app: a function that returns a `flask.Flask` instance
        :param domain: str - used to determine the subdomain
        self.create_app = create_app
        self.domain = domain
        self.lock = Lock()
        self.instances = {}

    def __call__(self, environ, start_response):
        app = self._get_application(environ['HTTP_HOST'])
        return app(environ, start_response)

    def _get_application(self, host):
        host = host.split(':')[0]
        assert host.endswith(self.domain), 'Configuration error'
        subdomain = host[:-len(self.domain)].rstrip('.')
        with self.lock:
            app = self.instances.get(subdomain)
            if app is None:
                configobj = self._get_subdomain_based_config(subdomain)
                app = self.create_app(configobj=configobj)
                self.instances[subdomain] = app
            return app

    def _get_subdomain_based_config(subdomain):

        class Config(object):
        config = Config()

        if subdomain == 'dev':
            config.API_HOST = 'dev-host'
            config.DB_SERVER = 'dev-db-server'
        elif subdomain == 'qa':
            config.API_HOST = 'qa-host'
            config.DB_SERVER = 'qa-db-server'

        return config


rundevserver is similar to but uses the SubdomainDispatcher middleware before calling werkzeug.serving.run_simple.

def rundevserver(host=None, port=None, domain='', debug=True, **options):
    Modified from ``

    Runs the application on a local development server.

    :param host: the hostname to listen on. Set this to ``''`` to
                 have the server available externally as well. Defaults to
    :param port: the port of the webserver. Defaults to ``5000``
    :param domain: used to determine the subdomain
    :param debug: if given, enable or disable debug mode.
                  See :attr:`debug`.
    :param options: the options to be forwarded to the underlying
                    Werkzeug server. See
                    :func:`werkzeug.serving.run_simple` for more
    from werkzeug.serving import run_simple

    if host is None:
        host = ''
    if port is None:
        port = 5000
    options.setdefault('use_reloader', debug)
    options.setdefault('use_debugger', debug)

    app = SubdomainDispatcher(create_app, domain, debug=debug)

    run_simple(host, port, app, **options)


  1. Add the following to your hosts file (/etc/hosts on Ubuntu): dev.localhost qa.localhost
  2. Run the local dev server:
    if __name__ == '__main__':
        rundevserver(host='', port=5000, domain='localhost')
  3. Use the following URLs to get different app configurations:

Github code

A full working example is located on github: flask-subdomain-dispatcher-example An updated version of the code is here: flask-subdomaindevserver.