Extensions

weppy extensions extend the functionality of weppy in various different ways.

Extensions are listed on the Extension Registry and can be downloaded with easy_install or pip. When adding extensions to your application, it is a good habit to declare them as dependencies in your requirements.txt or setup.py file: this way, they can be installed with a simple command or when your application installs.

Using extensions

An extension typically has accompanying documentation that shows how to use it correctly. In general, weppy extensions should be named with the format weppy-foo and have a package-name like weppy_foo, replacing foo with the desired name. If the extension is written according to the suggested pattern, using it in your application will be quite easy:

from weppy import App
from weppy_foo import Foo

app = App(__name__)

# configure the extension
app.config.Foo.someparam = "something"
# add the extension to our app
app.use_extension(Foo)
# access extension attributes and methods
app.ext.Foo.bar()

That's all.

As you can see, extensions have a namespace that accesses your app's configuration, and after you have added the extension to your application using the use_extension() method, you can access the extension instance at app.ext.<extension_name>.

Building extensions

The first step in creating a new extension for weppy is writing an Extension subclass:

from weppy.extensions import Extension

class Awesomeness(Extension):
    default_config = {}

    def on_load(self):
        # pass

As you can see, the Extension class in actually quite simple, since you just have to write down the default configuration (if needed) and override the on_load method, that will be called by the framework when the extension will be initialised.

You can access three attributes of the extension instance, that are injected by weppy before calling the on_load method, in particular you will have:

attribute description
app the application on which the extension is loaded
config a sdict containing the configuration
env a sdict reserved to the extension where data can be stored

The config attribute will contain the configuration defined by the developer using the extension, with the default attributes you defined in the default_config if not specified differently.

The developer will access app.config.Awesomeness in order configure the extension, in fact weppy uses the name of your class as namespace for the configuration and environment objects. If you want to specify a different namespace, you can use the relevant attribute:

class Awesomeness(Extension):
    namespace = "Foobar"

just remember to update the documentation of your extension accordingly.

Since the extension can access the application object, it can easily do anything, for example it may define a route:

class Awesomeness(Extension):
    def on_load(self):
        self.app.route('/awesome')(awesome_route)

def awesome_route():
    return {'message': 'Awesome!'}

or add a custom handler:

class AwesomeHandler(Handler):
    # some code

class Awesomeness(Extension):
    def on_load(self):
        self.app.common_handlers.append(
            AwesomeHandler()
        )

– dude, what if I need to access the database?
ask to the developer to add it to the extension configuration

In fact, if you state you need the database in the extension documentation, the developer can just write:

app.config.Awesomeness.db = db

Then you can write a check inside your load method in order to ensure the database presence:

class Awesomeness(Extension):
    def on_load(self):
        if not self.config.db:
            raise RuntimeError('Awesomeness extension needs the database')

Template extensions

Whenever you want to extend something related to the weppy templating system, you can take advantage of another class provided by the framework: the TemplateExtension one.

This class provides some useful methods when dealing with templates, and has to be used on conjunction with the Extension one. Let's see this with an example.

Let's say we want to build an extension that adds Haml support to weppy. Then we need to write a template extension that interact with templates with an .haml file extension and that provides the compiled html source in order to let weppy understand the templates. We can start by writing:

from weppy.extensions import TemplateExtension

class HamlTemplates(TemplateExtension):
    file_extension = '.haml' 

Then we can use three different methods provided by the TemplateExtension class:

  • the preload method, that should accept a path and a filename and return the same tuple, and is useful to alter the standard template names weppy looks for;
  • the preprocess method, that should accept the source code and file name variables and return the source code that should be used by weppy;
  • the inject method, that should accept a context dictionary and can add methods and variable to it.

Let's say we want to compile the haml templates in html ones on application start, then just tell weppy to use the generated html files one. The simplest way to do that is to override the preload method in order to change the extension of the file:

from weppy.extensions import TemplateExtension

class HamlTemplates(TemplateExtension):
    file_extension = '.haml'

    def preload(self, path, file_name):
        # file_name will be like "somefile.haml"
        return path, file_name + ".html" 

Then we should write the code that compiles the templates:

import os

class Haml(Extension):
    def on_load(self):
        for path, dirs, files in os.walk(self.app.template_path):
            for fname in files:
                if os.path.splitext(fname)[1] == ".haml":
                    self.compile(path, fname)

where compile method will be the one responsible to parse the haml code and produce compatible html for weppy.

Given this, we also want to register the template extension when the user activate the main extension, and to share the same context, so that the final code will look like this:

class Haml(Extension):
    def on_load(self):
        self.app.add_template_extension(HamlTemplates)
        for path, dirs, files in os.walk(self.app.template_path):
            for fname in files:
                if os.path.splitext(fname)[1] == ".haml":
                    self.compile(path, fname)


class HamlTemplates(TemplateExtension):
    namespace = 'Haml'
    file_extension = '.haml'

    def preload(self, path, file_name):
        # file_name will be like "somefile.haml"
        return path, file_name + ".html" 

Sharing the namespace between the Extension and the TemplateExtension is useful, because you will have the same config object for the two, and you can pass variables between them using the env object:

class Haml(Extension):
    def on_load(self):
        self.env.debug = True


class HamlTemplates(TemplateExtension):
    namespace = 'Haml'

    def preload(self, path, file_name):
        if self.env.debug:
            # some code

Template extensions can also register lexers, which are the keyword used by weppy in templates to render specific contents. For example, the standard include_static keyword is a lexer that produce the appropriate <link> or <script> html objects.

In order to create a new lexer, you have to use the TemplateLexer class provided by weppy. Let's say we want to create a shortcut to include images from the static folder using this notation:

<div>
    {{img 'foo.png'}}
</div>

To do this, we first need a method that produce the final html code and add it to the template context so we can invoke it:

from weppy import url

class ImgTemplateExtension(TemplateExtension):
    def gen_img_string(self, name):
        return '<img src="{}" />'.format(
            url("static", "images/" + name)
        )

    def inject(self, context):
        context['_img_lexer_'] = self.gen_img_string

then we should write a lexer that converts the img notation to a call to our method and add it as a template node to the template tree:

from weppy.extensions import TemplateLexer

class ImgLexer(TemplateLexer):
    def process(self, value):
        s = '_img_lexer_("{}")'.format(value)
        node = self.parser.create_node(
            s, pre_extend=False, writer_escape=False
        )
        self.top.append(node)

The above code tells the template parser to add a python node to the current template tree, so that the _img_lexer_ method will be invoked. The self.top property is the actual level of the template tree, so if you are, for example, inside a div or a specific block, that will be used to inject the code.

The last things we need is to register the lexer and the template extension, so the final code will look like this:

from weppy import url
from weppy.extensions import Extension, TemplateExtension, TemplateLexer

class ImgExtension(Extension):
    namespace = "TemplateImg"

    def on_load(self):
        self.app.add_template_extension(ImgTemplateExtension)

class ImgLexer(TemplateLexer):
    def process(self, value):
        s = '_img_lexer_("{}")'.format(value)
        node = self.parser.create_node(
            s, pre_extend=False, writer_escape=False
        )
        self.top.append(node)


class ImgTemplateExtension(TemplateExtension):
    namespace = "TemplateImg"
    lexers = {'img': ImgLexer}

    def gen_img_string(self, name):
        return '<img src="{}" />'.format(
            url("static", "images/" + name)
        )

    def inject(self, context):
        context['_img_lexer_'] = self.gen_img_string