-
-
Notifications
You must be signed in to change notification settings - Fork 230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Working Jinja2 url_for with test #1004
base: main
Are you sure you want to change the base?
Changes from all commits
14461b2
6b907b2
68d4f2e
d0d8e7b
560dcd2
2a02a6a
8433c59
25df6bc
39dac34
c54238a
11f198a
beae5ee
032c247
e9df6ab
620fb62
a24d8e2
bfb3ca8
6a5168c
79318e9
32836ff
b655eba
3d93d85
c87a201
3829ab3
f489ee2
f6193ea
67c535b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,83 @@ | ||
from abc import ABC, abstractmethod | ||
from typing import Callable, List | ||
|
||
from jinja2 import Environment, FileSystemLoader | ||
|
||
from robyn import status_codes | ||
from robyn import Robyn, status_codes | ||
from robyn.router import Route | ||
|
||
from .robyn import Headers, Response | ||
|
||
|
||
def get_param_filled_url(url: str, kwdict: dict | None = None) -> str: | ||
"""fill the :params in the url | ||
|
||
Args: | ||
url (str): typically comes from the route | ||
kwdict (dict): the **kwargs as a dict | ||
|
||
Returns: | ||
str: _description_modified url (if there are elements in kwdict, otherwise unchanged) | ||
""" | ||
if kwdict is not None: | ||
for k, v in zip(kwdict.keys(), kwdict.values()): | ||
url = url.replace(f":{k}", f"{v}") | ||
|
||
return url | ||
|
||
|
||
class TemplateInterface(ABC): | ||
def __init__(self): ... | ||
|
||
@abstractmethod | ||
def render_template(self, *args, **kwargs) -> Response: ... | ||
|
||
@abstractmethod | ||
def set_robyn(self, robyn: Robyn) -> None: ... | ||
|
||
@abstractmethod | ||
def get_function_url(self, function_name: str, route_type: str = "GET", **kwargs) -> str: ... | ||
|
||
|
||
class JinjaTemplate(TemplateInterface): | ||
def __init__(self, directory, encoding="utf-8", followlinks=False): | ||
self.env = Environment(loader=FileSystemLoader(searchpath=directory, encoding=encoding, followlinks=followlinks)) | ||
def __init__(self, directory, encoding="utf-8", followlinks=False) -> None: | ||
self.env: Environment = Environment(loader=FileSystemLoader(searchpath=directory, encoding=encoding, followlinks=followlinks)) | ||
self.add_function_to_globals("get_function_url", self.get_function_url) | ||
self.robyn: Robyn | None = None | ||
|
||
def add_function_to_globals(self, name: str, func: Callable): | ||
""" | ||
Add a global function to a Jinja environment. | ||
""" | ||
self.env.globals[name] = func | ||
|
||
def set_robyn(self, robyn: Robyn) -> None: | ||
""" | ||
The get_function_url needs to have access to the list of routes stored in the apps Robyn object | ||
|
||
Args: | ||
robyn (Robyn): The top instance of the Robyn class for this app. | ||
""" | ||
Comment on lines
+54
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am sorry if I am being a bit daft but why is this function needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we are rendering a template that includes a call into the TemplateInterface I couldn't see a way to get hold of the Robyn instance automagically. So it was either a set method or the constructor. I decided to go for a set method as the constructor for JiujaTemplate already has 3 arguments plus self (I figured it will impact the existing code less and be easier to add additions templating packages) If projects don't use Long term I want to find a way to get to the routes via a router singleton and to have the router do the work to return the URL. But I don't think we have that possibility yet. And it won't change the templates (only thing would be that set_robyn would not be needed but we can make it do nothing and depreciate it over a couple of releases) |
||
self.robyn = robyn | ||
|
||
def get_function_url(self, function_name: str, route_type: str = "GET", **kwargs) -> str: | ||
"""Creates a link to an endpoint function name | ||
|
||
Returns: | ||
str: the url for the function | ||
""" | ||
|
||
if self.robyn is None: | ||
return "get_function_url needs set_robyn" | ||
|
||
routes: List[Route] = self.robyn.router.get_routes() | ||
for r in routes: | ||
if r.function.handler.__name__ == function_name and str(r.route_type) == f"HttpMethod.{route_type}": | ||
if len(kwargs) > 0: | ||
return get_param_filled_url(r.route, kwargs) | ||
return r.route | ||
|
||
return "route not found in Robyn router" | ||
|
||
def render_template(self, template_name, **kwargs) -> Response: | ||
rendered_template = self.env.get_template(template_name).render(**kwargs) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from robyn import Robyn | ||
from robyn.templating import JinjaTemplate | ||
|
||
|
||
def h(request): | ||
return "Hello" | ||
|
||
|
||
def get_hello(request): | ||
return "get_Hello" | ||
|
||
|
||
def post_hello(request): | ||
return "post_Hello" | ||
|
||
|
||
def put_hello(request): | ||
return "put_Hello" | ||
|
||
|
||
def delete_hello(request): | ||
return "delete_Hello" | ||
|
||
|
||
def patch_hello(request): | ||
return "patch_Hello" | ||
|
||
|
||
def options_hello(request): | ||
return "options_Hello" | ||
|
||
|
||
def head_hello(request): | ||
return "head_Hello" | ||
|
||
|
||
def test_get_function_url(): | ||
app = Robyn(__file__) | ||
app.add_route("GET", "/", h) | ||
app.add_route("GET", "/get_hello", get_hello) | ||
app.add_route("POST", "/post_hello", post_hello) | ||
app.add_route("PUT", "/put_hello", put_hello) | ||
app.add_route("DELETE", "/delete_hello", delete_hello) | ||
app.add_route("PATCH", "/patch_hello", patch_hello) | ||
app.add_route("OPTIONS", "/options_hello", options_hello) | ||
app.add_route("HEAD", "/head_hello", head_hello) | ||
|
||
jinja_template = JinjaTemplate(".", "templates", "utf-8") | ||
jinja_template.set_robyn(app) | ||
|
||
assert jinja_template.get_function_url("h") == "/" | ||
assert jinja_template.get_function_url("get_hello") == "/get_hello" | ||
assert jinja_template.get_function_url("get_hello", "GET") == "/get_hello" | ||
assert jinja_template.get_function_url("post_hello", "POST") == "/post_hello" | ||
assert jinja_template.get_function_url("put_hello", "PUT") == "/put_hello" | ||
assert jinja_template.get_function_url("delete_hello", "DELETE") == "/delete_hello" | ||
assert jinja_template.get_function_url("patch_hello", "PATCH") == "/patch_hello" | ||
assert jinja_template.get_function_url("options_hello", "OPTIONS") == "/options_hello" | ||
assert jinja_template.get_function_url("head_hello", "HEAD") == "/head_hello" | ||
|
||
|
||
def get_hello_param(request): | ||
return "get_Hello_param" | ||
|
||
|
||
def test_get_function_url_with_params() -> None: | ||
app = Robyn(__file__) | ||
app.add_route("GET", "/get_hello/:id", get_hello_param) | ||
|
||
jinja_template = JinjaTemplate(".", "templates", "utf-8") | ||
jinja_template.set_robyn(app) | ||
|
||
url: str = jinja_template.get_function_url("get_hello_param", "GET", id=42) | ||
assert url == "/get_hello/42", f"Param filled url|{url}|" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from robyn.templating import get_param_filled_url | ||
|
||
|
||
def test_get_param_filled_url_42() -> None: | ||
d42: dict = {"id": 42} | ||
assert get_param_filled_url("/get_hello/:id", d42) == "/get_hello/42" | ||
assert get_param_filled_url("/:id", d42) == "/42" | ||
assert get_param_filled_url("/get_hello/:idmore", d42) == "/get_hello/42more" | ||
assert get_param_filled_url("/get_hello/:id/", d42) == "/get_hello/42/" | ||
assert get_param_filled_url("/get_hello/:id/more", d42) == "/get_hello/42/more" | ||
|
||
|
||
def test_get_param_filled_url_2s() -> None: | ||
d42: dict = {"id": 42, "s": "str"} | ||
assert get_param_filled_url("/get_hello/:id/:s", d42) == "/get_hello/42/str" | ||
assert get_param_filled_url("/:id/:s", d42) == "/42/str" | ||
assert get_param_filled_url("/get_hello/:id:smore", d42) == "/get_hello/42strmore" | ||
assert get_param_filled_url("/get_hello/:id/:s/", d42) == "/get_hello/42/str/" | ||
assert get_param_filled_url("/get_hello/:id/:s/more", d42) == "/get_hello/42/str/more" | ||
assert get_param_filled_url("/get_hello/:s/:id/:s/more", d42) == "/get_hello/str/42/str/more" | ||
|
||
|
||
def test_get_param_filled_url() -> None: | ||
assert get_param_filled_url("/get_hello/:id/:s") == "/get_hello/:id/:s" | ||
assert get_param_filled_url("/:id/:s") == "/:id/:s" | ||
assert get_param_filled_url("/get_hello/:id:smore") == "/get_hello/:id:smore" | ||
assert get_param_filled_url("/get_hello/:id/:s/") == "/get_hello/:id/:s/" | ||
assert get_param_filled_url("/get_hello/:id/:s/more") == "/get_hello/:id/:s/more" | ||
assert get_param_filled_url("/get_hello/:s/:id/:s/more") == "/get_hello/:s/:id/:s/more" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dave42w , should it be a part of the
ABC
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put it in the ABC because I assume that if you switched from Jinja2 to a different templating engine you would still want to be able to call this function so you can have less brittle templates (ones that change if you change a subrouter prefix or the route for a function.