Justin Wood I am a software engineer currently working at Rackspace Inc., and loving every second of it. My interests include automotive restoration, music production, drumming, embedded electronics, and code golf. I currently live in beautiful Austin Texas.
Published

Wed 12 April 2017

←Home

Bugginess in bottle.py plugins and middleware.

Recently, while working on a personal project using the bottle.py web framework, I came across a very strange and difficult to trace bug, that was virtually undocumented across the web. The problem was this: Whenever I attempted to deploy my backend code, upon hitting an endpoint, the app would throw a 500 and complain about not recieving the correct amount of arguments in the function that was being called to fulfill the request.

Some facts about the app:

Bottle.py<https://bottlepy.org> Cork Authentication and Authorization Module<https://github.com/FedericoCeratto/bottle-cork> Beaker Session Management Plugin<https://github.com/bottlepy/bottle-beaker> Bottle-SQLAlchemy Plugin<https://github.com/iurisilvio/bottle-sqlalchemy>

Hosted on Heroku<https://www.heroku.com>, backed by PostgreSQL<https://www.postgresql.org/>

When running locally, the app worked as expected. The only difference being that I was using SQLite in memory instead of a persistent PostgreSQL database. The app is first initialized:

    import os

    import bottle
    from bottle.ext import sqlalchemy
    from sqlalchemy import create_engine
    from beaker.middleware import SessionMiddleware
    from cork import Cork, AAAException

    from backend import CustomBackend
    from models import Base

    # Other imports not relevant to this post.

    # Define Globals
    DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite://')
    SMTP_URL = os.getenv('SMTP_URL', None)
    EMAIL_SENDER = os.getenv('EMAIL_SENDER', None)
    PORT = os.getenv('PORT', 8080)

    # Init SQLAlchemy engine and Cork Backend
    engine = create_engine(DATABASE_URL)
    b = CustomBackend(engine)
    aaa = Cork(backend=b, email_sender=EMAIL_SENDER, smtp_url=SMTP_URL)

    # Grab bottle stack and install sqlalchemy plugin
    app = bottle.app()
    plugin = sqlalchemy.Plugin(engine, Base.metadata, create=True, keyword='db')
    app.install(plugin)

    # Define Beaker Middleware session variables, wrap app.
    session_opts = {
        'session.cookie_expires': True,
        'session.encrypt_key': '<secret_key_here>,
        'session.httponly': True,
        'session.timeout': 3600 * 24,  # 1 day
        'session.type': 'cookie',
        'session.validate_key': True,
    }
    app = SessionMiddleware(app, session_opts)

Pay special attention to the use of the Bottle-sqlalchemy plugin. As it is supposed to work, it handles SQLAlchemy session management and passes a session connection object to each request handling function, by default in a parameter called 'db'. This saves a lot of boilerplate code in the functions. So in standard bottle framework style, my controller functions had a route decorator, a view decorator (if the return was intended to be passed to a template), and then the function itself.

    @bottle.route('/profile')
    @bottle.view('profile')
    def show_current_user_role(db):
        """Show current user profile"""
        aaa.require(role='user', fail_redirect='/login')
        user = db.query(User).filter_by(username=aaa.current_user.username).first()

        return dict(username = user.username,
                    email = user.email_addr,
                    posts = [dict(id=post.id,
                                  title=post.title,
                                  summary=post.summary,
                                  date=post.publish_date) for post in user.posts])

In bottle parlance, this hypothetical function would execute whenever someone requests http://someservice.tld/profile. It would determine who the user is (redirect to login if no user), and then grab that user's email and post information. Finally it would pass that info on to the view to be rendered as variable data against the template named profile.tpl.

All was working well locally, but when I pushed the code to heroku, the application would crash whenever I made a request, complaining that the function wasn't recieving the db parameter. Naturally this was a bit consternating, as the logs were not showing any obvious changes in the execution pattern from local to heroku. The environment was mirrored as closely as possible, and I started chasing a ghost, trying to find a problem caused by the session middleware screwing with the application context and affecting the ability of the plugin to inject its parameter in the request calls.

After various attempts at initializing the bottle application object through different mechanisms and doing the plugin installation and middleware wrapping in different sequences, I stumbled upon the fact that the error was only occuring when hitting an endpoint that had both a route decorator and a view decorator -JW

Go Top
comments powered by Disqus