Skip to content

Integrating with Flask

Basic integration

Rendering markupy elements or components in flask is as easy as return a stringified instance from your routes.

from flask import Flask
from markupy import Component, Node
from markupy.tag import H1

app = Flask(__name__)

@app.route("/page")
def page():
    return str(H1["Hello!"])

class MyComponent(Component):
    def render(self) -> Node:
        return H1["Hello!"]

@app.route("/component")
def component():
    return str(MyComponent())

Avoid casting to str by subclassing Flask

As you saw previously, since Flask doesn't know about our elements or components, we need to convert them to str before returning them.

You can avoid that by subclassing Flask and overriding the make_response method:

from flask import Flask
from markupy import View

class MarkupyFlask(Flask):
    # Here we override make_response to be able to return View instances
    # from our routes directly without having to cast them to str()
    def make_response(self, rv):
        if isinstance(rv, View):
            rv = str(rv)
        return super().make_response(rv)

Note

Here we check if our object to be rendered is a subclass of markupy.View, which is the base class for all markupy Element, Fragment and Component.

And then our previous example becomes like this (basically we instantiate MarkupyFlask instead of Flask previously and do not need the calls to str anymore):

from my_flask import MarkupyFlask
from markupy import Component, Node
from markupy.tag import H1

app = MarkupyFlask(__name__)

@app.route("/page")
def page():
    return H1["Hello!"]

class MyComponent(Component):
    def render(self) -> Node:
        return H1["Hello!"]

@app.route("/component")
def component():
    return MyComponent()

Streaming HTML

Given that markupy elements and components are iterables, you can leverage the power of python generators to stream the response instead of sending it all at once.

Flask supports streaming out of the box (see docs).

Note

The examples below are returning very small and simple content, please be aware that you will only benefit from streaming for large contents.

Streaming by returning a generator

from flask import Flask, stream_with_context
from markupy import Component, Node
from markupy.tag import H1

app = Flask(__name__)

@app.route("/page")
def page():
    my_element = H1["Hello!"]
    return stream_with_context(chunk for chunk in my_element)

class MyComponent(Component):
    def render(self) -> Node:
        return H1["Hello!"]

@app.route("/component")
def component():
    return stream_with_context(chunk for chunk in MyComponent())

Streaming by subclassing Flask

Same as above, if you prefer a cleaner syntax that will apply streaming to all your routes, we can adapt our Flask subclass:

from flask import Flask, stream_with_context
from markupy import View

class MarkupyStreamFlask(Flask):
    # Here we override make_response to be able to stream View instances
    # from our routes directly when returning them
    def make_response(self, rv):
        if isinstance(rv, View):
            rv = stream_with_context(chunk for chunk in rv)
        return super().make_response(rv)

And then in your routes:

@app.route("/page")
def page():
    return H1["Hello!"]

Streaming with route decorators

If you prefer to apply this change on a route per route basis, you could also create a decorator that will take care of the component to stream conversion:

from functools import wraps
from flask import stream_with_context
from markupy import View

def markupy_stream(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        rv = f(*args, **kwargs)
        if isinstance(rv, View):
            return stream_with_context(chunk for chunk in rv)
        return rv

    return wrapper

And then in your routes:

@app.route("/page")
@markupy_stream
def page():
    return H1["Hello!"]