diff --git a/README.md b/README.md index d1feeaa..f4c37fd 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ - Automatic Ping / Pong Support - Per Socket Data - Middlewares +- Templates Support (examples with [Mako](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_mako.py) and [Jinja2)](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_jinja2.py)) ## :mag_right: Upcoming Features - Fetch like API powered by libuv - Async file IO powered by libuv diff --git a/examples/helpers/templates.py b/examples/helpers/templates.py new file mode 100644 index 0000000..698069d --- /dev/null +++ b/examples/helpers/templates.py @@ -0,0 +1,31 @@ +#Simple example of mako and jinja2 template plugin for socketify.py +from mako.template import Template +from mako.lookup import TemplateLookup +from mako import exceptions + + +from jinja2 import Environment, FileSystemLoader + +class Jinja2Template: + def __init__(self, searchpath, encoding='utf-8', followlinks=False): + self.env = Environment(loader=FileSystemLoader(searchpath, encoding, followlinks)) + + #You can also add caching and logging strategy here if you want ;) + def render(self, templatename, **kwargs): + try: + template = self.env.get_template(templatename) + return template.render(**kwargs) + except Exception as err: + return str(err) + +class MakoTemplate: + def __init__(self, **options): + self.lookup = TemplateLookup(**options) + + #You can also add caching and logging strategy here if you want ;) + def render(self, templatename, **kwargs): + try: + template = self.lookup.get_template(templatename) + return template.render(**kwargs) + except Exception as err: + return exceptions.html_error_template().render() \ No newline at end of file diff --git a/examples/requeriments.txt b/examples/requeriments.txt index aeca5c7..a5df884 100644 --- a/examples/requeriments.txt +++ b/examples/requeriments.txt @@ -3,4 +3,5 @@ aiofiles aiofile redis strawberry-graphql +mako git+https://github.com/cirospaciari/socketify.py.git@main#socketify --global-option="build_ext" \ No newline at end of file diff --git a/examples/template_jinja2.py b/examples/template_jinja2.py new file mode 100644 index 0000000..c91ba23 --- /dev/null +++ b/examples/template_jinja2.py @@ -0,0 +1,14 @@ +from socketify import App +#see helper/templates.py for plugin implementation +from helpers.templates import Jinja2Template + + +app = App() +app.template(Jinja2Template("./templates", encoding='utf-8', followlinks=False)) + +async def home(res, req): + res.render("jinja2_home.html", title="Hello", message="Hello, World") + +app.get("/", home) +app.listen(3000, lambda config: print("Listening on port http://localhost:%s now\n" % str(config.port))) +app.run() \ No newline at end of file diff --git a/examples/template_mako.py b/examples/template_mako.py new file mode 100644 index 0000000..7cabb24 --- /dev/null +++ b/examples/template_mako.py @@ -0,0 +1,14 @@ +from socketify import App +#see helper/templates.py for plugin implementation +from helpers.templates import MakoTemplate + + +app = App() +app.template(MakoTemplate(directories=['./templates'], output_encoding='utf-8', encoding_errors='replace')) + +async def home(res, req): + res.render("mako_home.html", message="Hello, World") + +app.get("/", home) +app.listen(3000, lambda config: print("Listening on port http://localhost:%s now\n" % str(config.port))) +app.run() \ No newline at end of file diff --git a/examples/templates/jinja2_base.html b/examples/templates/jinja2_base.html new file mode 100644 index 0000000..2fe47ef --- /dev/null +++ b/examples/templates/jinja2_base.html @@ -0,0 +1,20 @@ + + + + {{ title }} + + + + + +{%- block body %}{%- endblock %} + + + + + \ No newline at end of file diff --git a/examples/templates/jinja2_home.html b/examples/templates/jinja2_home.html new file mode 100644 index 0000000..2518aac --- /dev/null +++ b/examples/templates/jinja2_home.html @@ -0,0 +1,4 @@ +{% extends "jinja2_base.html" %} +{% block body %} +

this is the body content. {{ message }}

+{% endblock body %} \ No newline at end of file diff --git a/examples/templates/mako_base.html b/examples/templates/mako_base.html new file mode 100644 index 0000000..215ced0 --- /dev/null +++ b/examples/templates/mako_base.html @@ -0,0 +1,15 @@ + + +
+ <%block name="header"/> +
+ + ${self.body()} + + + + \ No newline at end of file diff --git a/examples/templates/mako_home.html b/examples/templates/mako_home.html new file mode 100644 index 0000000..682f00b --- /dev/null +++ b/examples/templates/mako_home.html @@ -0,0 +1,7 @@ +<%inherit file="mako_base.html"/> + +<%block name="header"> + this is some header content + + +this is the body content. ${ message } \ No newline at end of file diff --git a/src/socketify/socketify.py b/src/socketify/socketify.py index df58832..b6e0f5d 100644 --- a/src/socketify/socketify.py +++ b/src/socketify/socketify.py @@ -422,7 +422,7 @@ def uws_websocket_upgrade_handler(res, req, context, user_data): if not user_data == ffi.NULL: try: (handlers, app) = ffi.from_handle(user_data) - response = AppResponse(res, app.loop, app.SSL) + response = AppResponse(res, app.loop, app.SSL, app._template) request = AppRequest(req) handler = handlers.upgrade if inspect.iscoroutinefunction(handler): @@ -463,7 +463,7 @@ def uws_generic_method_handler(res, req, user_data): if not user_data == ffi.NULL: try: (handler, app) = ffi.from_handle(user_data) - response = AppResponse(res, app.loop, app.SSL) + response = AppResponse(res, app.loop, app.SSL, app._template) request = AppRequest(req) if inspect.iscoroutinefunction(handler): response.grab_aborted_handler() @@ -1044,7 +1044,7 @@ class AppRequest: self._ptr = ffi.NULL class AppResponse: - def __init__(self, response, loop, ssl): + def __init__(self, response, loop, ssl, render=None): self.res = response self.SSL = ssl self.aborted = False @@ -1060,6 +1060,7 @@ class AppResponse: self._chunkFuture = None self._dataFuture = None self._data = None + self._render = render def cork(self, callback): if not self.aborted: @@ -1253,6 +1254,12 @@ class AppResponse: self.cork(lambda res: res.end(message, end_connection)) return self + def render(self, *args, **kwargs): + if self._render: + self.cork_end(self._render.render(*args, **kwargs)) + return self + raise RuntimeError("No registered templated engine") + def get_remote_address_bytes(self): buffer = ffi.new("char**") length = lib.uws_res_get_remote_address(self.SSL, self.res, buffer) @@ -1467,6 +1474,7 @@ class App: socket_options_ptr = ffi.new("struct us_socket_context_options_t *") socket_options = socket_options_ptr[0] self.options = options + self._template = None if options != None: self.is_ssl = True self.SSL = ffi.cast("int", 1) @@ -1498,7 +1506,9 @@ class App: self._missing_server_handler = None - + def template(self, template_engine): + self._template = template_engine + def static(self, route, directory): static_route(self, route, directory) return self