Using Yeoman with Flask

Thoughts on using Flask (a Python web framework) as a server for a Yeoman powered application. I previously write an article on using Yeoman with a custom develoment server, but I didn't share my Python code.

Flask

While developing, Flask must serve both the app directory and the .tmp directory (used for js templates, et compass). If the file is not present in app, it must check in the .tmp directory as fallback.

In production, the server should serve the dist directory only.

Here is a basic Flask server that do the job, it rely on the FLASK_YEOMAN_DEBUG environment variable to switch in dev/production mode.

from flask import Flask, current_app, send_file, render_template_string
from werkzeug.exceptions import NotFound

import os

app = Flask(__name__)

@app.route('/', defaults={'path': 'index.html'})
@app.route('/<path:path>')
def serve_index(path):
    flask_yeoman_debug = int(os.environ.get('FLASK_YEOMAN_DEBUG', False))
    fpath = 'dist'
    # While developing, we serve the app directory
    if flask_yeoman_debug:
        fpath = 'app'

    root_path = current_app.root_path
    default_path = os.path.join(root_path, fpath)

    default_path_abs = os.path.join(default_path, path)

    if os.path.isfile(default_path_abs):
        if path == 'index.html':
            # If index.html is requested, we inject the Flask current_app config
            return render_template_string(open(default_path_abs).read().decode('utf-8'),
                                          config=current_app.config)
        return send_file(default_path_abs)

    # While development, we must check the .tmp dir as fallback
    if flask_yeoman_debug:
        # The .tmp dir is used by compass and for the template file
        alt_path = os.path.join(root_path, '.tmp')
        alt_path_abs = os.path.join(alt_path, path)
        if os.path.isfile(alt_path_abs):
            return send_file(alt_path_abs)

    raise NotFound()

Grunt watch

Yeoman use Grunt as a task runner to automate tasks.

In the Gruntfile.js, let's create a new flask task to spawn our flask server, and add it to the server task.

The child_process node module.

First we create a new task:

// New task for flask server
grunt.registerTask('flask', 'Run flask server.', function() {
   var spawn = require('child_process').spawn;
   grunt.log.writeln('Starting Flak development server.');
   // stdio: 'inherit' let us see flask output in grunt
   var PIPE = {stdio: 'inherit'};
   spawn('python', ['server.py'], PIPE);
});

Then we can replace the connect:livereload by flask:

grunt.registerTask('server', function (target) {
    if (target === 'dist') {
        return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
    }

    grunt.task.run([
        'clean:server',
        'coffee:dist',
        'createDefaultTemplate',
        'jst',
        'compass:server',
        'flask',
        'open',
        'watch',
    ]);
});

Live reloading support

To keep the live reloading working even with a custom webserver, you need to add this snippet manually in your index.html.

<!-- livereload script -->
<script>document.write('<script src="http://'
 + (location.host || 'localhost').split(':')[0]
 + ':35729/livereload.js?snipver=1" type="text/javascript"><\/script>')
</script>

Share Flask config with your Javascript app

In your index.html (the one in your app directory), you have access to Flask config directly, so you can share configuration between Flask and you javascript application and do something like this:

<script>
window.myapp.config = {var1: {{ config.VAR1|tojson }},
                       var2: {{ config.VAR2|tojson }}};
</script>

{% if config.FLASK_YEOMAN_DEBUG %}
<!-- livereload script -->
<script>document.write('<script src="http://'
 + (location.host || 'localhost').split(':')[0]
 + ':35729/livereload.js?snipver=1" type="text/javascript"><\/script>')
</script>
{% endif %}

Introducing Flask-Yeoman

I created a Flask Blueprint: Flask-Yeoman that allows you to quickly get up and running:

from flask import Flask, jsonify
from flask_yeoman import flask_yeoman

app = Flask(__name__)
app.register_blueprint(flask_yeoman)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)

Check out the README on GitHub.

Feedback

Please don't hesitate if you have any feedback/question/suggestion !

Share this article

Tip with Bitcoin

Tip me with Bitcoin and vote for this post!

1FKdaZ75Ck8Bfc3LgQ8cKA8W7B86fzZBe2

© Thomas Sileo. Powered by Pelican.