flasklet 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flasklet
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A few nice things for Flask
5
5
  Author: Marcel Kröker
6
6
  Author-email: Marcel Kröker <kroeker.marcel@gmail.com>
@@ -9,7 +9,7 @@ def render_block(
9
9
  template_name: str,
10
10
  block_name: str,
11
11
  **context: Any,
12
- ) -> Markup:
12
+ ) -> str:
13
13
  """
14
14
  Render a specific block from a given template.
15
15
 
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from jinja2 import Template
7
+ from jinja2.nodes import Macro, Name, Output, TemplateData
8
+ from markupsafe import Markup
9
+
10
+ if TYPE_CHECKING:
11
+ from jinja2.nodes import Expr
12
+
13
+ from flasklet.render.jinja import load_jinja_env
14
+
15
+
16
+ def _extract_macro_source(template_source: str, macro_name: str) -> str:
17
+ """
18
+ Extracts the source code of a macro from the template source code.
19
+ """
20
+
21
+ # Pattern to match the macro definition, including parameters and content
22
+ macro_pattern = re.compile(
23
+ r"{%\s*macro\s+"
24
+ + re.escape(macro_name)
25
+ + r"\s*\((.*?)\)\s*%}(.*?)"
26
+ + r"{%\s*endmacro\s*%}",
27
+ re.DOTALL,
28
+ )
29
+
30
+ if not (match := macro_pattern.search(template_source)):
31
+ msg = f"Macro '{macro_name}' not found in template."
32
+ raise ValueError(msg)
33
+
34
+ # Return macro source code, including the {% macro %} and {% endmacro %} lines
35
+ return match.group(0)
36
+
37
+
38
+ def _flatten_macro_source_nodes(nodes: list[Expr]) -> str:
39
+ """Flattens the nodes of a macro's body into a single string of source code."""
40
+ res = ""
41
+ for node in nodes:
42
+ if isinstance(node, TemplateData):
43
+ res += node.data
44
+ elif isinstance(node, Name):
45
+ res += r"{{ " + node.name + r" }}"
46
+ else:
47
+ msg = f"Unsupported node type: {type(node)}"
48
+ raise TypeError(msg)
49
+ return res
50
+
51
+
52
+ def render_macro_unsafe(template_name: str, macro_name: str, **kwargs: Any) -> str:
53
+ """
54
+ Render a specific macro from a given template.
55
+ It returns unescaped HTML, meaning that it is not safe to use with user input.
56
+ """
57
+ env, env_loader = load_jinja_env()
58
+
59
+ template_source, _, _ = env_loader.get_source(env, template_name)
60
+ parsed_template = env.parse(template_source)
61
+ macros = parsed_template.find_all(Macro)
62
+
63
+ if not (macro := next((m for m in macros if m.name == macro_name), None)):
64
+ msg = f"Macro {macro_name} not found in template {template_name}"
65
+ raise ValueError(msg)
66
+
67
+ macro_source = _flatten_macro_source_nodes(cast("Output", macro.body[0]).nodes)
68
+
69
+ return Template(macro_source).render(**kwargs, g=env.globals)
70
+
71
+
72
+ def render_macro(template_name: str, macro_name: str, **kwargs: Any) -> str:
73
+ """
74
+ Render a specific macro from a given template.
75
+ """
76
+ env, env_loader = load_jinja_env()
77
+
78
+ # Load the template source code
79
+ template_source, _, _ = env_loader.get_source(env, template_name)
80
+
81
+ # Extract the macro source code
82
+ macro_source = _extract_macro_source(template_source, macro_name)
83
+
84
+ # Create a new template that includes the macro and calls it
85
+ macro_call_template_source = macro_source + "\n{{ " + macro_name + "()" + " }}"
86
+
87
+ # Compile the new template
88
+ macro_template = env.from_string(macro_call_template_source)
89
+
90
+ # Render the template with the provided arguments
91
+ rendered = macro_template.render(**kwargs)
92
+
93
+ # Return the rendered macro content
94
+ return Markup(rendered) # noqa: S704
@@ -4,7 +4,7 @@ from flask import render_template
4
4
  from markupsafe import Markup
5
5
 
6
6
 
7
- def render_partial(template_name: str, **context: Any) -> Markup:
7
+ def render_partial(template_name: str, **context: Any) -> str:
8
8
  """
9
9
  Render a full template as safe markup, for embedding inside another template.
10
10
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flasklet"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "A few nice things for Flask"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Marcel Kröker", email = "kroeker.marcel@gmail.com" }]
@@ -1,108 +0,0 @@
1
- from typing import Any
2
-
3
- from jinja2 import Template
4
- from markupsafe import Markup
5
-
6
- from flasklet.render.jinja import load_jinja_env
7
-
8
-
9
- def _get_macro(template: Template, macro_name: str, template_name: str) -> Any:
10
- """Look up a callable macro on a compiled template, or raise if absent."""
11
- macro = getattr(template.module, macro_name, None)
12
- if not callable(macro):
13
- msg = f"Macro {macro_name!r} not found in template {template_name!r}"
14
- raise TypeError(msg)
15
- return macro
16
-
17
-
18
- def render_macro(template_name: str, macro_name: str, **kwargs: Any) -> Markup:
19
- """
20
- Render a single macro from a template, calling it with the given arguments.
21
-
22
- The macro is looked up on the compiled template's module and called with
23
- `**kwargs` as its parameters, so all Jinja features (filters, expressions,
24
- control flow, autoescaping) behave exactly as they do in normal rendering.
25
- Values interpolated inside the macro are HTML-escaped, so this is safe to
26
- call with untrusted `**kwargs`.
27
-
28
- Args:
29
- template_name: Name of the template that defines the macro.
30
- macro_name: Name of the macro to call.
31
- **kwargs: Arguments passed to the macro's parameters.
32
-
33
- Returns:
34
- The rendered macro output as safe `Markup`.
35
-
36
- Raises:
37
- ValueError: If the template has no macro named `macro_name`.
38
-
39
- Example:
40
- Template "components/button.html":
41
-
42
- ```jinja
43
- {% macro button(label, kind="primary") %}
44
- <button class="{{ kind }}">{{ label }}</button>
45
- {% endmacro %}
46
- ```
47
-
48
- In Python:
49
-
50
- >>> render_macro("components/button.html", "button", label="Save")
51
- Markup('<button class="primary">Save</button>')
52
-
53
- In a Jinja template:
54
-
55
- ```jinja
56
- {{ render_macro("components/button.html", "button", label="Save") }}
57
- ```
58
- """
59
- env, _ = load_jinja_env()
60
-
61
- macro = _get_macro(env.get_template(template_name), macro_name, template_name)
62
-
63
- return Markup(macro(**kwargs)) # noqa: S704
64
-
65
-
66
- def render_macro_unsafe(template_name: str, macro_name: str, **kwargs: Any) -> Markup:
67
- """
68
- Render a single macro with autoescaping disabled.
69
-
70
- Like `render_macro`, but values interpolated inside the macro are NOT
71
- HTML-escaped, so the macro can emit raw markup passed in via `**kwargs`. The
72
- result is still returned as `Markup` so it embeds into a parent template
73
- without being re-escaped.
74
-
75
- Because interpolated values are not escaped, this is unsafe to call with
76
- untrusted `**kwargs`. Prefer `render_macro` unless you specifically need to
77
- pass through trusted HTML.
78
-
79
- Args:
80
- template_name: Name of the template that defines the macro.
81
- macro_name: Name of the macro to call.
82
- **kwargs: Arguments passed to the macro's parameters.
83
-
84
- Returns:
85
- The rendered macro output as `Markup`, with its contents left unescaped.
86
-
87
- Raises:
88
- ValueError: If the template has no macro named `macro_name`.
89
-
90
- Example:
91
- Template "components/raw.html":
92
-
93
- ```jinja
94
- {% macro raw(html) %}<div>{{ html }}</div>{% endmacro %}
95
- ```
96
-
97
- In Python:
98
-
99
- >>> render_macro_unsafe("components/raw.html", "raw", html="<b>hi</b>")
100
- Markup('<div><b>hi</b></div>')
101
- """
102
- env, env_loader = load_jinja_env()
103
-
104
- source, _, _ = env_loader.get_source(env, template_name)
105
- unsafe_template = env.overlay(autoescape=False).from_string(source)
106
- macro = _get_macro(unsafe_template, macro_name, template_name)
107
-
108
- return Markup(macro(**kwargs)) # noqa: S704
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes