Beispiel #1
0
    def test_routes(self):
        """
        L{getRoutes} returns all the defined routes and their attributes.
        """
        app = Klein()

        def f():
            pass

        f.attr = "attr"

        def g():
            pass

        g.attr = "G"
        app.route(b"/", methods=[b"GET"])(f)
        app.route(b"/g", methods=[b"PUT", b"OPTIONS"])(g)

        routes = sorted(getRoutes(app))
        self.assertEqual(routes, [
            KleinRoute(methods={b"GET"},
                       path=b"/",
                       endpoint="f",
                       attributes={'attr': 'attr'}),
            KleinRoute(methods={b'PUT', b'OPTIONS'},
                       path=b'/g',
                       endpoint='g',
                       attributes={'attr': 'G'})
        ])
Beispiel #2
0
    def test_private_not_visible(self):
        """
        When an endpoint is decorated with ``@private_api`` it is
        omitted from result of ``makeRst``.
        """
        app = Klein()

        @private_api
        def g():
            pass

        app.route(b"/g", methods=[b"GET"])(g)
        rest = list(makeRst(b"/", "section", app, None, {}))
        self.assertEqual(rest, [])
Beispiel #3
0
    def test_private_not_visible(self):
        """
        When an endpoint is decorated with ``@private_api`` it is
        omitted from result of ``makeRst``.
        """
        app = Klein()

        @private_api
        def g():
            pass

        app.route(b"/g", methods=[b"GET"])(g)
        rest = list(makeRst(b"/", "section", app, None, {}))
        self.assertEqual(rest, [])
Beispiel #4
0
def simpleSessionRouter():
    # type: () -> Tuple[sessions, errors, str, str, StubTreq]
    """
    Construct a simple router.
    """
    sessions = []
    exceptions = []
    mss = MemorySessionStore.fromAuthorizers([memoryAuthorizer])
    router = Klein()
    token = "X-Test-Session-Token"
    cookie = "X-Test-Session-Cookie"
    sproc = SessionProcurer(
        mss,
        secureTokenHeader=b"X-Test-Session-Token",
        secureCookie=b"X-Test-Session-Cookie",
    )

    @router.route("/")
    @inlineCallbacks
    def route(request):
        # type: (IRequest) -> Deferred
        try:
            sessions.append((yield sproc.procureSession(request)))
        except NoSuchSession as nss:
            exceptions.append(nss)
        returnValue(b"ok")

    requirer = Requirer()

    @requirer.prerequisite([ISession])
    def procure(request):
        # type: (IRequest) -> Deferred
        return sproc.procureSession(request)

    @requirer.require(router.route("/test"), simple=Authorization(ISimpleTest))
    def testRoute(simple):
        # type: (SimpleTest) -> str
        return "ok: " + str(simple.doTest() + 4)

    @requirer.require(router.route("/denied"), nope=Authorization(IDenyMe))
    def testDenied(nope):
        # type: (IDenyMe) -> str
        return "bad"

    treq = StubTreq(router.resource())
    return sessions, exceptions, token, cookie, treq
Beispiel #5
0
    def test_routes(self):
        """
        L{getRoutes} returns all the defined routes and their attributes.
        """
        app = Klein()

        def f():
            pass
        f.attr = "attr"

        def g():
            pass
        g.attr = "G"
        app.route(b"/", methods=[b"GET"])(f)
        app.route(b"/g", methods=[b"PUT", b"OPTIONS"])(g)

        routes = sorted(getRoutes(app))
        self.assertEqual(routes, [
            KleinRoute(methods={b"GET"}, path=b"/", endpoint="f",
                       attributes={'attr': 'attr'}),
            KleinRoute(methods={b'PUT', b'OPTIONS'}, path=b'/g', endpoint='g',
                       attributes={'attr': 'G'})])
Beispiel #6
0
def simpleFormRouter() -> Tuple[Klein, List[Tuple[str, int]]]:
    """
    Create a simple router hooked up to a field handler.
    """
    router = Klein()
    requirer = Requirer()
    calls = []

    @requirer.require(
        router.route("/getme", methods=["GET"]),
        name=Field.text(),
        value=Field.number(),
        custom=Field(formInputType="number", converter=int, required=False),
    )
    def justGet(name: str, value: int, custom: int) -> bytes:
        calls.append((name, value or custom))
        return b"got"

    return router, calls
Beispiel #7
0
def simpleFormRouter():
    # type: () -> Tuple[Klein, List[Tuple[str, int]]]
    """
    Create a simple router hooked up to a field handler.
    """
    router = Klein()
    requirer = Requirer()
    calls = []

    @requirer.require(
        router.route("/getme", methods=['GET']),
        name=Field.text(), value=Field.number(),
        custom=Field(formInputType='number', converter=int, required=False),
    )
    def justGet(name, value, custom):
        # type: (str, int, int) -> bytes
        calls.append((name, value or custom))
        return b'got'

    return router, calls
Beispiel #8
0
requirer = Requirer()


@requirer.prerequisite([ISession])
def procurer(request):
    return SessionProcurer(sessions).procureSession(request)


style = Plating(tags=tags.html(tags.head(tags.title("yay")),
                               tags.body(tags.div(slot(Plating.CONTENT)))))


@requirer.require(
    style.routed(
        app.route("/", methods=["POST"]),
        tags.h1("u did it: ", slot("an-form-arg")),
    ),
    foo=Field.number(minimum=3, maximum=10),
    bar=Field.text(),
)
def postHandler(foo, bar):
    return {"an-form-arg": foo}


@requirer.require(
    style.routed(app.route("/", methods=["GET"]), tags.div(slot("anForm"))),
    theForm=Form.rendererFor(postHandler, action="/?post=yes"),
)
def formRenderer(theForm):
    return {"anForm": theForm}
                api,
                "execute",
                capability_version,
                capability_hash, '')

            links.append({"href": capability_url, "rel": "capability"})

        return links
    else:
        return url


def transaction_id(request):
    """
    Extract the transaction id from the given request.

    :param IRequest request: The request we are trying to get the
        transaction id for.

    :returns: A string transaction id.
    """
    return request.responseHeaders.getRawHeaders('X-Response-Id')[0]


app = Klein()
app.route = partial(app.route, strict_slashes=False)

root = Resource()
root.putChild('v1.0', app.resource())
root.putChild('', Data('', 'text/plain'))
Beispiel #10
0
    """
    def getAllRawHeaders(self):
        # type: () -> Iterable[Tuple[bytes, List[bytes]]]
        """
        Don't return a host header.
        """
        for key, values in super(BadlyBehavedHeaders, self).getAllRawHeaders():
            if key != b"Host":
                yield (key, values)


router = Klein()
requirer = Requirer()


@requirer.require(router.route("/hello/world", methods=["GET"]),
                  url=RequestURL())
def requiresURL(url):
    # type: (DecodedURL) -> Text
    """
    This is a route that requires a URL.
    """
    return url.child(u"hello/ world").asText()


class ISample(Interface):
    """
    Interface for testing.
    """

Beispiel #11
0
        if capability_hash is not None:
            capability_url = append_segments(get_url_root(), api, "execute",
                                             capability_version,
                                             capability_hash, '')

            links.append({"href": capability_url, "rel": "capability"})

        return links
    else:
        return url


def transaction_id(request):
    """
    Extract the transaction id from the given request.

    :param IRequest request: The request we are trying to get the
        transaction id for.

    :returns: A string transaction id.
    """
    return request.responseHeaders.getRawHeaders('X-Response-Id')[0]


app = Klein()
app.route = partial(app.route, strict_slashes=False)

root = Resource()
root.putChild('v1.0', app.resource())
root.putChild('', Data('', 'text/plain'))
Beispiel #12
0
requirer = Requirer()

@requirer.prerequisite([ISession])
def procurer(request):
    return SessionProcurer(sessions).procureSession(request)


style = Plating(tags=tags.html(
    tags.head(tags.title("yay")),
    tags.body(tags.div(slot(Plating.CONTENT))))
)

@style.routed(
    requirer.require(
        app.route("/", methods=["POST"]),
        foo=Field.integer(minimum=3, maximum=10), bar=Field.text(),
    ),
    tags.h1('u did it: ', slot("an-form-arg"))
)
def postHandler(foo, bar):
    return {"an-form-arg": foo}

@requirer.require(
    style.routed(
        app.route("/", methods=["GET"]),
        tags.div(slot("anForm"))
    ),
    theForm=Form.rendererFor(postHandler, action=u"/?post=yes")
)
def formRenderer(theForm):
Beispiel #13
0
class PlatingTests(TestCase):
    """
    Tests for L{Plating}.
    """

    def setUp(self):
        """
        Create an app and a resource wrapping that app for this test.
        """
        self.app = Klein()
        self.kr = self.app.resource()

    def get(self, uri):
        """
        Issue a virtual GET request to the given path that is expected to
        succeed synchronously, and return the generated request object and
        written bytes.
        """
        request = requestMock(uri)
        d = _render(self.kr, request)
        self.successResultOf(d)
        return request, request.getWrittenData()

    def test_template_html(self):
        """
        Rendering a L{Plating.routed} decorated route results in templated
        HTML.
        """
        @page.routed(self.app.route("/"),
                     tags.span(slot("ok")))
        def plateMe(request):
            return {"ok": "test-data-present"}
        request, written = self.get(b"/")
        self.assertIn(b'<span>test-data-present</span>', written)
        self.assertIn(b'<title>default title unchanged</title>', written)

    def test_template_json(self):
        """
        Rendering a L{Plating.routed} decorated route with a query parameter
        asking for JSON will yield JSON instead.
        """
        @page.routed(self.app.route("/"),
                     tags.span(slot("ok")))
        def plateMe(request):
            return {"ok": "an-plating-test"}
        request, written = self.get(b"/?json=true")
        self.assertEqual(
            request.responseHeaders.getRawHeaders(b'content-type')[0],
            b'text/json; charset=utf-8'
        )
        self.assertEquals({"ok": "an-plating-test",
                           "title": "default title unchanged"},
                          json.loads(written.decode('utf-8')))

    def test_template_numbers(self):
        """
        Data returned from a plated method may include numeric types (integers,
        floats, and possibly longs), which although they are not normally
        serializable by twisted.web.template, will be converted by plating into
        their decimal representation.
        """
        @page.routed(self.app.route("/"),
                     tags.div(tags.span(slot("anInteger")),
                              tags.i(slot("anFloat")),
                              tags.b(slot("anLong")),
                     ))
        def plateMe(result):
            return {"anInteger": 7,
                    "anFloat": 3.2,
                    "anLong": 0x10000000000000001}
        request, written = self.get(b"/")
        self.assertIn(b"<span>7</span>", written)
        self.assertIn(b"<i>3.2</i>", written)
        self.assertIn(b"<b>18446744073709551617</b>", written)

    def test_render_list(self):
        """
        The C{:list} renderer suffix will render the slot named by the renderer
        as a list, filling each slot.
        """
        @page.routed(self.app.route("/"),
                     tags.ul(tags.li(slot("item"),
                                     render="subplating:list")))
        def rsrc(request):
            return {"subplating": [1, 2, 3]}
        request, written = self.get(b"/")
        self.assertIn(b'<ul><li>1</li><li>2</li><li>3</li></ul>', written)
        self.assertIn(b'<title>default title unchanged</title>', written)

    def test_widget_html(self):
        """
        When L{Plating.widgeted} is applied as a decorator, it gives the
        decorated function a C{widget} attribute which is a version of the
        function with a modified return type that turns it into a renderable
        HTML sub-element that may fill a slot.
        """
        @page.routed(self.app.route("/"),
                     tags.div(slot("widget")))
        def rsrc(request):
            return {"widget": enwidget.widget(3, 4)}
        request, written = self.get(b"/")
        self.assertIn(b"<span>a: 3</span>", written)
        self.assertIn(b"<span>b: 4</span>", written)

    def test_widget_json(self):
        """
        When L{Plating.widgeted} is applied as a decorator, and the result is
        serialized to JSON, it appears the same as the returned value despite
        the HTML-friendly wrapping described above.
        """
        @page.routed(self.app.route("/"),
                     tags.div(slot("widget")))
        def rsrc(request):
            return {"widget": enwidget.widget(3, 4)}
        request, written = self.get(b"/?json=1")
        self.assertEqual(json.loads(written.decode('utf-8')),
                         {"widget": {"a": 3, "b": 4},
                          "title": "default title unchanged"})

    def test_prime_directive_return(self):
        """
        Nothing within these Articles Of Federation shall authorize the United
        Federation of Planets to alter the return value of a callable by
        applying a decorator to it...
        """
        exact_result = {"ok": "some nonsense value"}
        @page.routed(self.app.route("/"),
                     tags.span(slot("ok")))
        def plateMe(request):
            return exact_result
        self.assertIdentical(plateMe(None), exact_result)

    def test_prime_directive_arguments(self):
        """
        ... or shall require the function to modify its signature under these
        Articles Of Federation.
        """
        @page.routed(self.app.route("/"),
                     tags.span(slot("ok")))
        def plateMe(request, one, two, three):
            return (one, two, three)
        exact_one = {"one": "and"}
        exact_two = {"two": "and"}
        exact_three = {"three": "and"}
        result_one, result_two, result_three = plateMe(
            None, exact_one, exact_two, three=exact_three
        )
        self.assertIdentical(result_one, exact_one)
        self.assertIdentical(result_two, exact_two)
        self.assertIdentical(result_three, exact_three)

    def test_presentation_only_json(self):
        """
        Slots marked as "presentation only" will not be reflected in the
        output.
        """
        plating = Plating(tags=tags.span(slot("title")),
                          presentation_slots={"title"})
        @plating.routed(self.app.route("/"),
                        tags.span(slot("data")))
        def justJson(request):
            return {"title": "uninteresting", "data": "interesting"}
        request, written = self.get(b"/?json=1")
        self.assertEqual(json.loads(written.decode("utf-8")),
                         {"data": "interesting"})

    def test_missing_renderer(self):
        """
        Missing renderers will result in an exception during rendering.
        """
        def test(missing):
            plating = Plating(tags=tags.span(slot(Plating.CONTENT)))
            @plating.routed(self.app.route("/"),
                            tags.span(tags.span(render=missing)))
            def no(request):
                return {}
            self.get(b"/")
            [fe] = self.flushLoggedErrors(FlattenerError)
            self.assertIsInstance(fe.value.args[0], MissingRenderMethod)
        test("garbage")
        test("garbage:missing")

    def test_json_serialize_unknown_type(self):
        """
        The JSON serializer will raise a L{TypeError} when it can't find an
        appropriate type.
        """
        from klein._plating import json_serialize
        class reprish(object):
            def __repr__(self):
                return '<blub>'
        te = self.assertRaises(TypeError, json_serialize, {"an": reprish()})
        self.assertIn("<blub>", str(te))
Beispiel #14
0
sessions = MemorySessionStore()

requirer = Requirer()


@requirer.prerequisite([ISession])
def procurer(request):
    return SessionProcurer(sessions).procureSession(request)


style = Plating(tags=tags.html(tags.head(tags.title("yay")),
                               tags.body(tags.div(slot(Plating.CONTENT)))))


@requirer.require(
    style.routed(app.route("/", methods=["POST"]),
                 tags.h1('u did it: ', slot("an-form-arg"))),
    foo=Field.number(minimum=3, maximum=10),
    bar=Field.text(),
)
def postHandler(foo, bar):
    return {"an-form-arg": foo}


@requirer.require(style.routed(app.route("/", methods=["GET"]),
                               tags.div(slot("anForm"))),
                  theForm=Form.rendererFor(postHandler, action=u"/?post=yes"))
def formRenderer(theForm):
    return {"anForm": theForm}

Beispiel #15
0
from twisted.web.template import tags, slot
from klein import Klein, Plating
app = Klein()

myStyle = Plating(
    tags=tags.html(
        tags.head(tags.title(slot("pageTitle"))),
        tags.body(tags.h1(slot("pageTitle"), Class="titleHeading"),
                  tags.div(slot(Plating.CONTENT)))
    ),
    defaults={"pageTitle": "Places & Foods"}
)

@myStyle.routed(
    app.route("/"),
    tags.div(
        tags.h2("Sample Places:"),
        tags.ul([tags.li(tags.a(href=["/places/", place])(place))
                 for place in ["new york", "san francisco", "shanghai"]]),
        tags.h2("Sample Foods:"),
        tags.ul([tags.li(tags.a(href=["/foods/", food])(food))
                 for food in ["hamburgers", "cheeseburgers", "hot dogs"]]),
    ))
def root(request):
    return {}

@myStyle.routed(app.route("/foods/<food>"),
              tags.table(border="2", style="color: blue")(
                  tags.tr(tags.td("Name:"), tags.td(slot("name"))),
                  tags.tr(tags.td("Deliciousness:"),
Beispiel #16
0
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from klein import run, route
from twisted.web.template import tags, slot
from klein import Klein, Plating

from input_handler import handle_input

app = Klein()

myStyle = Plating(
    tags=tags.html(
        tags.head(tags.title(slot("pageTitle"))),
        tags.body(tags.h1(slot("pageTitle"), Class="titleHeading"),
                  tags.div(slot(Plating.CONTENT)))
    )
)

@myStyle.routed(
    app.route("/input/"),
    tags.div())
def input(request):
	return {"r":['Hello, world!']}



if __name__ == '__main__':
	app.run("localhost", 8080)
    
Beispiel #17
0
    return Random(unpack("!I", sha256(string.encode("utf-8")).digest()[:4])[0])


from twisted.web.template import tags, slot
from klein import Klein, Plating
app = Klein()

myStyle = Plating(tags=tags.html(
    tags.head(tags.title(slot("pageTitle"))),
    tags.body(tags.h1(slot("pageTitle"), Class="titleHeading"),
              tags.div(slot(Plating.CONTENT)))),
                  defaults={"pageTitle": "Places & Foods"})


@myStyle.routed(
    app.route("/"),
    tags.div(
        tags.h2("Sample Places:"),
        tags.ul([
            tags.li(tags.a(href=["/places/", place])(place))
            for place in ["new york", "san francisco", "shanghai"]
        ]),
        tags.h2("Sample Foods:"),
        tags.ul([
            tags.li(tags.a(href=["/foods/", food])(food))
            for food in ["hamburgers", "cheeseburgers", "hot dogs"]
        ]),
    ))
def root(request):
    return {}
Beispiel #18
0
class PlatingTests(TestCase):
    """
    Tests for L{Plating}.
    """
    def setUp(self):
        """
        Create an app and a resource wrapping that app for this test.
        """
        self.app = Klein()
        self.kr = self.app.resource()

    def get(self, uri):
        """
        Issue a virtual GET request to the given path that is expected to
        succeed synchronously, and return the generated request object and
        written bytes.
        """
        request = requestMock(uri)
        d = _render(self.kr, request)
        self.successResultOf(d)
        return request, request.getWrittenData()

    def test_template_html(self):
        """
        Rendering a L{Plating.routed} decorated route results in templated
        HTML.
        """
        @page.routed(self.app.route("/"), tags.span(slot("ok")))
        def plateMe(request):
            return {"ok": "test-data-present"}

        request, written = self.get(b"/")
        self.assertIn(b'<span>test-data-present</span>', written)
        self.assertIn(b'<title>default title unchanged</title>', written)

    def test_template_json(self):
        """
        Rendering a L{Plating.routed} decorated route with a query parameter
        asking for JSON will yield JSON instead.
        """
        @page.routed(self.app.route("/"), tags.span(slot("ok")))
        def plateMe(request):
            return {"ok": "an-plating-test"}

        request, written = self.get(b"/?json=true")
        self.assertEqual(
            request.responseHeaders.getRawHeaders(b'content-type')[0],
            b'text/json; charset=utf-8')
        self.assertEquals(
            {
                "ok": "an-plating-test",
                "title": "default title unchanged"
            }, json.loads(written.decode('utf-8')))

    def test_template_numbers(self):
        """
        Data returned from a plated method may include numeric types (integers,
        floats, and possibly longs), which although they are not normally
        serializable by twisted.web.template, will be converted by plating into
        their decimal representation.
        """
        @page.routed(self.app.route("/"),
                     tags.div(
                         tags.span(slot("anInteger")),
                         tags.i(slot("anFloat")),
                         tags.b(slot("anLong")),
                     ))
        def plateMe(result):
            return {
                "anInteger": 7,
                "anFloat": 3.2,
                "anLong": 0x10000000000000001
            }

        request, written = self.get(b"/")
        self.assertIn(b"<span>7</span>", written)
        self.assertIn(b"<i>3.2</i>", written)
        self.assertIn(b"<b>18446744073709551617</b>", written)

    def test_render_list(self):
        """
        The C{:list} renderer suffix will render the slot named by the renderer
        as a list, filling each slot.
        """
        @page.routed(self.app.route("/"),
                     tags.ul(tags.li(slot("item"), render="subplating:list")))
        def rsrc(request):
            return {"subplating": [1, 2, 3]}

        request, written = self.get(b"/")
        self.assertIn(b'<ul><li>1</li><li>2</li><li>3</li></ul>', written)
        self.assertIn(b'<title>default title unchanged</title>', written)

    def test_widget_html(self):
        """
        When L{Plating.widgeted} is applied as a decorator, it gives the
        decorated function a C{widget} attribute which is a version of the
        function with a modified return type that turns it into a renderable
        HTML sub-element that may fill a slot.
        """
        @page.routed(self.app.route("/"), tags.div(slot("widget")))
        def rsrc(request):
            return {"widget": enwidget.widget(3, 4)}

        request, written = self.get(b"/")
        self.assertIn(b"<span>a: 3</span>", written)
        self.assertIn(b"<span>b: 4</span>", written)

    def test_widget_json(self):
        """
        When L{Plating.widgeted} is applied as a decorator, and the result is
        serialized to JSON, it appears the same as the returned value despite
        the HTML-friendly wrapping described above.
        """
        @page.routed(self.app.route("/"), tags.div(slot("widget")))
        def rsrc(request):
            return {"widget": enwidget.widget(3, 4)}

        request, written = self.get(b"/?json=1")
        self.assertEqual(json.loads(written.decode('utf-8')), {
            "widget": {
                "a": 3,
                "b": 4
            },
            "title": "default title unchanged"
        })

    def test_prime_directive_return(self):
        """
        Nothing within these Articles Of Federation shall authorize the United
        Federation of Planets to alter the return value of a callable by
        applying a decorator to it...
        """
        exact_result = {"ok": "some nonsense value"}

        @page.routed(self.app.route("/"), tags.span(slot("ok")))
        def plateMe(request):
            return exact_result

        self.assertIdentical(plateMe(None), exact_result)

    def test_prime_directive_arguments(self):
        """
        ... or shall require the function to modify its signature under these
        Articles Of Federation.
        """
        @page.routed(self.app.route("/"), tags.span(slot("ok")))
        def plateMe(request, one, two, three):
            return (one, two, three)

        exact_one = {"one": "and"}
        exact_two = {"two": "and"}
        exact_three = {"three": "and"}
        result_one, result_two, result_three = plateMe(None,
                                                       exact_one,
                                                       exact_two,
                                                       three=exact_three)
        self.assertIdentical(result_one, exact_one)
        self.assertIdentical(result_two, exact_two)
        self.assertIdentical(result_three, exact_three)

    def test_presentation_only_json(self):
        """
        Slots marked as "presentation only" will not be reflected in the
        output.
        """
        plating = Plating(tags=tags.span(slot("title")),
                          presentation_slots={"title"})

        @plating.routed(self.app.route("/"), tags.span(slot("data")))
        def justJson(request):
            return {"title": "uninteresting", "data": "interesting"}

        request, written = self.get(b"/?json=1")
        self.assertEqual(json.loads(written.decode("utf-8")),
                         {"data": "interesting"})

    def test_missing_renderer(self):
        """
        Missing renderers will result in an exception during rendering.
        """
        def test(missing):
            plating = Plating(tags=tags.span(slot(Plating.CONTENT)))

            @plating.routed(self.app.route("/"),
                            tags.span(tags.span(render=missing)))
            def no(request):
                return {}

            self.get(b"/")
            [fe] = self.flushLoggedErrors(FlattenerError)
            self.assertIsInstance(fe.value.args[0], MissingRenderMethod)

        test("garbage")
        test("garbage:missing")

    def test_json_serialize_unknown_type(self):
        """
        The JSON serializer will raise a L{TypeError} when it can't find an
        appropriate type.
        """
        from klein._plating import json_serialize

        class reprish(object):
            def __repr__(self):
                return '<blub>'

        te = self.assertRaises(TypeError, json_serialize, {"an": reprish()})
        self.assertIn("<blub>", str(te))
Beispiel #19
0
app = Klein()

myStyle = Plating(
    tags=tags.html(
        tags.head(tags.title(slot("pageTitle"))),
        tags.body(
            tags.h1(slot("pageTitle"), Class="titleHeading"),
            tags.div(slot(Plating.CONTENT)),
        ),
    ),
    defaults={"pageTitle": "Places & Foods"},
)


@myStyle.routed(
    app.route("/"),
    tags.div(
        tags.h2("Sample Places:"),
        tags.ul([
            tags.li(tags.a(href=["/places/", place])(place))
            for place in ["new york", "san francisco", "shanghai"]
        ]),
        tags.h2("Sample Foods:"),
        tags.ul([
            tags.li(tags.a(href=["/foods/", food])(food))
            for food in ["hamburgers", "cheeseburgers", "hot dogs"]
        ]),
    ),
)
def root(request):
    return {}