flasklet 0.1.0__py3-none-any.whl
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.
- flasklet/__init__.py +10 -0
- flasklet/blueprints.py +74 -0
- flasklet/flashing.py +37 -0
- flasklet/imports.py +29 -0
- flasklet/py.typed +0 -0
- flasklet/render/__init__.py +22 -0
- flasklet/render/block.py +60 -0
- flasklet/render/jinja.py +136 -0
- flasklet/render/macro.py +94 -0
- flasklet/render/partial.py +16 -0
- flasklet-0.1.0.dist-info/METADATA +14 -0
- flasklet-0.1.0.dist-info/RECORD +13 -0
- flasklet-0.1.0.dist-info/WHEEL +4 -0
flasklet/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from flasklet.blueprints import register_blueprints
|
|
2
|
+
from flasklet.flashing import FlashMessage, parse_flashed_messages
|
|
3
|
+
from flasklet.imports import import_routes
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"FlashMessage",
|
|
7
|
+
"import_routes",
|
|
8
|
+
"parse_flashed_messages",
|
|
9
|
+
"register_blueprints",
|
|
10
|
+
]
|
flasklet/blueprints.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from pkgutil import iter_modules
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from flask import Blueprint, Flask
|
|
9
|
+
|
|
10
|
+
BlueprintTree = tuple[Blueprint, list[Any]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _find_blueprints(module: ModuleType) -> list[BlueprintTree]:
|
|
14
|
+
"""
|
|
15
|
+
Recursively find blueprints in the given module and its sub-modules.
|
|
16
|
+
"""
|
|
17
|
+
if module.__file__ is None: # path of the __init__.py file
|
|
18
|
+
msg = f"Module {module.__name__} does not have an __init__.py file"
|
|
19
|
+
raise ValueError(msg)
|
|
20
|
+
|
|
21
|
+
found_blueprints: list[BlueprintTree] = []
|
|
22
|
+
for _, sub_package_name, is_package in iter_modules([Path(module.__file__).parent]):
|
|
23
|
+
if not is_package:
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
sub_module = import_module(f"{module.__name__}.{sub_package_name}")
|
|
27
|
+
sub_blueprint = getattr(sub_module, "bp", None)
|
|
28
|
+
|
|
29
|
+
if not isinstance(sub_blueprint, Blueprint):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
found_blueprints += [(sub_blueprint, _find_blueprints(sub_module))]
|
|
33
|
+
|
|
34
|
+
return found_blueprints
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _register_blueprints(blueprint: Blueprint, blueprint_trees: list[BlueprintTree]) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Recursively register blueprints on the given root (app or blueprint), using a
|
|
40
|
+
depth-first traversal.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Avoid registering the same blueprint multiple times
|
|
44
|
+
if blueprint._got_registered_once: # noqa: SLF001
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
for sub_bp, sub_trees in blueprint_trees:
|
|
48
|
+
_register_blueprints(sub_bp, sub_trees) # Depth first
|
|
49
|
+
|
|
50
|
+
blueprint.register_blueprint(sub_bp)
|
|
51
|
+
print(f"๐ {sub_bp.name} registered on {blueprint.name}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def register_blueprints(app: Flask) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Find and register all blueprints starting from the root module.
|
|
57
|
+
|
|
58
|
+
Recursively walks the sub-packages of `app`'s import module. Any sub-package
|
|
59
|
+
exposing a module-level `bp: Blueprint` attribute is registered on its parent
|
|
60
|
+
package's blueprint (or on `app` itself for top-level packages), depth-first.
|
|
61
|
+
Sub-packages without a `bp` attribute are skipped, along with their children.
|
|
62
|
+
"""
|
|
63
|
+
t0 = time.time()
|
|
64
|
+
|
|
65
|
+
print("๐ Registering Blueprints ...")
|
|
66
|
+
|
|
67
|
+
finded_blueprints = _find_blueprints(import_module(app.name))
|
|
68
|
+
for blueprint, sub_trees in finded_blueprints:
|
|
69
|
+
_register_blueprints(blueprint, sub_trees) # Depth first
|
|
70
|
+
|
|
71
|
+
app.register_blueprint(blueprint)
|
|
72
|
+
print(f"๐ {blueprint.name} registered on {app.name}")
|
|
73
|
+
|
|
74
|
+
print(f"๐ done in {time.time() - t0:.3f}s")
|
flasklet/flashing.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from flask import get_flashed_messages
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class FlashMessage:
|
|
8
|
+
message: str
|
|
9
|
+
category: str
|
|
10
|
+
type: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_flashed_messages() -> list[FlashMessage]:
|
|
14
|
+
"""
|
|
15
|
+
From Flask's "get_flashed_messages", parse the messages into a
|
|
16
|
+
list of FlashMessage objects.
|
|
17
|
+
|
|
18
|
+
The message type is derived from the flash category: a category containing
|
|
19
|
+
"toast", "modal", or "oob_swap" sets `type` accordingly (and that keyword is
|
|
20
|
+
removed from `category`); otherwise `type` defaults to "message".
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
messages = [x for x in get_flashed_messages(with_categories=True) if isinstance(x, tuple)]
|
|
24
|
+
|
|
25
|
+
res = []
|
|
26
|
+
for category, message in messages:
|
|
27
|
+
message_type = "message"
|
|
28
|
+
if "toast" in category:
|
|
29
|
+
message_type = "toast"
|
|
30
|
+
elif "modal" in category:
|
|
31
|
+
message_type = "modal"
|
|
32
|
+
elif "oob_swap" in category:
|
|
33
|
+
message_type = "oob_swap"
|
|
34
|
+
|
|
35
|
+
category_no_type = " ".join(c for c in category.split(" ") if c != message_type)
|
|
36
|
+
res.append(FlashMessage(message, category_no_type, message_type))
|
|
37
|
+
return res
|
flasklet/imports.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def import_routes(file: str, module_name: str) -> None:
|
|
6
|
+
"""
|
|
7
|
+
Call with: `import_routes(__file__, __name__)`
|
|
8
|
+
|
|
9
|
+
Imports all python files in:
|
|
10
|
+
- All .py files in the `./routes` directory relative to the calling file
|
|
11
|
+
- All .py files in the current directory relative to the calling file
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Import all routes in the ./routes directory
|
|
15
|
+
|
|
16
|
+
if (routes_dir := Path(file).parent / "routes").is_dir():
|
|
17
|
+
for path in routes_dir.iterdir():
|
|
18
|
+
if not path.is_file() or path.suffix != ".py" or path.stem == "__init__":
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
importlib.import_module(f"{module_name}.routes.{path.stem}")
|
|
22
|
+
|
|
23
|
+
# Import all routes in the current directory
|
|
24
|
+
|
|
25
|
+
for path in Path(file).parent.iterdir():
|
|
26
|
+
if not path.is_file() or path.suffix != ".py" or path.stem == "__init__":
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
importlib.import_module(f"{module_name}.{path.stem}")
|
flasklet/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from flasklet.render.block import render_block
|
|
2
|
+
from flasklet.render.jinja import (
|
|
3
|
+
HighlightBlockExtension,
|
|
4
|
+
IncludeRawExtension,
|
|
5
|
+
JinjaTemplateMetadata,
|
|
6
|
+
get_template_metadata,
|
|
7
|
+
load_jinja_env,
|
|
8
|
+
)
|
|
9
|
+
from flasklet.render.macro import render_macro, render_macro_unsafe
|
|
10
|
+
from flasklet.render.partial import render_partial
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"HighlightBlockExtension",
|
|
14
|
+
"IncludeRawExtension",
|
|
15
|
+
"JinjaTemplateMetadata",
|
|
16
|
+
"get_template_metadata",
|
|
17
|
+
"load_jinja_env",
|
|
18
|
+
"render_block",
|
|
19
|
+
"render_macro",
|
|
20
|
+
"render_macro_unsafe",
|
|
21
|
+
"render_partial",
|
|
22
|
+
]
|
flasklet/render/block.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from markupsafe import Markup
|
|
4
|
+
|
|
5
|
+
from flasklet.render.jinja import get_template_metadata
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def render_block(
|
|
9
|
+
template_name: str,
|
|
10
|
+
block_name: str,
|
|
11
|
+
**context: dict[str, Any],
|
|
12
|
+
) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Render a specific block from a given template.
|
|
15
|
+
|
|
16
|
+
This function extracts and renders a named block from a Jinja2 template,
|
|
17
|
+
similar to how template inheritance works, but without requiring extension.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If the specified block is not found in the template
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
Template file "components/card.html":
|
|
24
|
+
```
|
|
25
|
+
{% block header %}
|
|
26
|
+
<h2>{{ title }}</h2>
|
|
27
|
+
{% endblock %}
|
|
28
|
+
|
|
29
|
+
{% block content %}
|
|
30
|
+
<p>{{ description }}</p>
|
|
31
|
+
{% endblock %}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Usage in Python:
|
|
35
|
+
```python
|
|
36
|
+
rendered = render_block(
|
|
37
|
+
"components/card.html",
|
|
38
|
+
"header",
|
|
39
|
+
title="Welcome"
|
|
40
|
+
)
|
|
41
|
+
# Returns: <h2>Welcome</h2>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Usage in Jinja template:
|
|
45
|
+
```
|
|
46
|
+
{{ render_block("components/card.html", "content", description="Hello World") }}
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
template = get_template_metadata(template_name).template
|
|
51
|
+
rendered_blocks = template.blocks.get(block_name)
|
|
52
|
+
|
|
53
|
+
if not rendered_blocks:
|
|
54
|
+
msg = f"Block {block_name} not found in template {template_name}"
|
|
55
|
+
raise ValueError(msg)
|
|
56
|
+
|
|
57
|
+
# Render the block
|
|
58
|
+
rendered_content = "".join(rendered_blocks(template.new_context(context)))
|
|
59
|
+
|
|
60
|
+
return Markup(rendered_content) # noqa: S704
|
flasklet/render/jinja.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, override
|
|
5
|
+
|
|
6
|
+
from flask import current_app
|
|
7
|
+
from jinja2 import BaseLoader, Environment, Template, nodes
|
|
8
|
+
from jinja2.ext import Extension
|
|
9
|
+
from markupsafe import Markup
|
|
10
|
+
from pygments import highlight
|
|
11
|
+
from pygments.formatters import HtmlFormatter
|
|
12
|
+
from pygments.lexers import get_lexer_by_name
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
|
|
17
|
+
from jinja2.parser import Parser
|
|
18
|
+
|
|
19
|
+
MSG_JINJA_ENV_MUST_HAVE_LOADER = "Jinja environment must have a loader"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
########################################################################################
|
|
23
|
+
#### MARK: Environment and templates
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class JinjaTemplateMetadata:
|
|
28
|
+
template: Template
|
|
29
|
+
name: str
|
|
30
|
+
source: str
|
|
31
|
+
ast: nodes.Template
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_jinja_env() -> tuple[Environment, BaseLoader]:
|
|
35
|
+
env = current_app.jinja_env
|
|
36
|
+
if env.loader is None:
|
|
37
|
+
raise ValueError(MSG_JINJA_ENV_MUST_HAVE_LOADER)
|
|
38
|
+
return env, env.loader
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_template_metadata(template_name_or_list: str) -> JinjaTemplateMetadata:
|
|
42
|
+
env, env_loader = load_jinja_env()
|
|
43
|
+
|
|
44
|
+
template = env.get_or_select_template(template_name_or_list)
|
|
45
|
+
|
|
46
|
+
if template.name is None:
|
|
47
|
+
msg = "Template name not found"
|
|
48
|
+
raise ValueError(msg)
|
|
49
|
+
|
|
50
|
+
template_source, _, _ = env_loader.get_source(env, template.name)
|
|
51
|
+
ast = env.parse(template_source)
|
|
52
|
+
|
|
53
|
+
return JinjaTemplateMetadata(
|
|
54
|
+
template=template,
|
|
55
|
+
name=template.name,
|
|
56
|
+
source=template_source,
|
|
57
|
+
ast=ast,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
########################################################################################
|
|
62
|
+
#### MARK: Include raw file without Jinja processing extension
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class IncludeRawExtension(Extension):
|
|
66
|
+
tags = {"include_raw"} # noqa: RUF012
|
|
67
|
+
|
|
68
|
+
@override
|
|
69
|
+
def parse(self, parser: Parser) -> nodes.Output:
|
|
70
|
+
lineno = parser.stream.expect("name:include_raw").lineno
|
|
71
|
+
template = parser.parse_expression()
|
|
72
|
+
result = self.call_method("_render", [template], lineno=lineno)
|
|
73
|
+
return nodes.Output([result], lineno=lineno)
|
|
74
|
+
|
|
75
|
+
def _render(self, filename: str) -> Markup:
|
|
76
|
+
if not self.environment.loader:
|
|
77
|
+
raise ValueError(MSG_JINJA_ENV_MUST_HAVE_LOADER)
|
|
78
|
+
|
|
79
|
+
template_source, _, _ = self.environment.loader.get_source(self.environment, filename)
|
|
80
|
+
return Markup(template_source) # noqa: S704
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
########################################################################################
|
|
84
|
+
#### MARK: Highlight code blocks extension
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class HighlightBlockExtension(Extension):
|
|
88
|
+
tags = {"highlight"} # noqa: RUF012
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
def parse(self, parser: Parser) -> nodes.Node:
|
|
92
|
+
# Parse the block
|
|
93
|
+
lineno = next(parser.stream).lineno
|
|
94
|
+
args = [parser.parse_expression()] # Language argument
|
|
95
|
+
body = parser.parse_statements(("name:endhighlight",), drop_needle=True)
|
|
96
|
+
call_block = nodes.CallBlock(self.call_method("_render", args), [], [], body)
|
|
97
|
+
return call_block.set_lineno(lineno)
|
|
98
|
+
|
|
99
|
+
def _render(self, language: str, caller: Callable[[], str]) -> Markup:
|
|
100
|
+
def unindent(code: str) -> str:
|
|
101
|
+
lines = code.splitlines()
|
|
102
|
+
|
|
103
|
+
while all(x.startswith(" ") for x in lines if x != "") or all(
|
|
104
|
+
x.startswith("\t") for x in lines if x != ""
|
|
105
|
+
):
|
|
106
|
+
lines = [x[1:] if x.startswith((" ", "\t")) else x for x in lines]
|
|
107
|
+
|
|
108
|
+
return "\n".join(lines)
|
|
109
|
+
|
|
110
|
+
# Get the block content
|
|
111
|
+
|
|
112
|
+
code = unindent(caller())
|
|
113
|
+
|
|
114
|
+
# Apply Pygments highlighting
|
|
115
|
+
|
|
116
|
+
lexer = get_lexer_by_name(language)
|
|
117
|
+
formatter = HtmlFormatter(style="monokai")
|
|
118
|
+
highlighted_code = highlight(code, lexer, formatter)
|
|
119
|
+
|
|
120
|
+
# Adjust CSS
|
|
121
|
+
|
|
122
|
+
css = formatter.get_style_defs(".highlight")
|
|
123
|
+
css += """
|
|
124
|
+
pre {
|
|
125
|
+
background-color: unset; /* Remove host stylesheet's background color */
|
|
126
|
+
color: unset; /* Remove host stylesheet's text color */
|
|
127
|
+
margin: 0;
|
|
128
|
+
}
|
|
129
|
+
.highlight {
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
padding: 14px;
|
|
132
|
+
margin: 14px 0;
|
|
133
|
+
}
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
return Markup(f"<style>{css}</style>{highlighted_code}") # noqa: S704
|
flasklet/render/macro.py
ADDED
|
@@ -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: dict[str, 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: dict[str, 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
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from flask import render_template
|
|
4
|
+
from markupsafe import Markup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def render_partial(template_name: str, **context: dict[str, Any]) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Render a template to be used as a partial within another template.
|
|
10
|
+
|
|
11
|
+
Example usage:
|
|
12
|
+
In a Jinja template, you can use:
|
|
13
|
+
{{ render_partial("my_template.html", var1=value1, var2=value2) }}
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
return Markup(render_template(template_name, **context)) # noqa: S704
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: flasklet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A few nice things for Flask
|
|
5
|
+
Author: Marcel Krรถker
|
|
6
|
+
Requires-Dist: flask>=3.1
|
|
7
|
+
Requires-Dist: jinja2>=3.1
|
|
8
|
+
Requires-Dist: markupsafe>=2.1
|
|
9
|
+
Requires-Dist: pygments>=2.18
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# flasklet
|
|
14
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
flasklet/__init__.py,sha256=Iuk8Qs4kyaihC8WPDSG3wePKdjwW70qeKTNFnzXpPLk,263
|
|
2
|
+
flasklet/blueprints.py,sha256=CCOYii8QfoT5-1nRKA9DUSRwf191weaLXHNr397612Y,2329
|
|
3
|
+
flasklet/flashing.py,sha256=DA9Fjaw0ILRiYFQYyIcrhaxrzIBOSFDUUoViaAPaDZk,1050
|
|
4
|
+
flasklet/imports.py,sha256=9PGOFAIgDQXItq6RCB4ZG5pBUGIJ5789M5FfqXXAy1Q,868
|
|
5
|
+
flasklet/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
flasklet/render/__init__.py,sha256=trVaqTVg5xFiuw4-V_vpA5ORARwOHcCCwwKJyr3ckxo,532
|
|
7
|
+
flasklet/render/block.py,sha256=uLfMxH6TEKEzWgN5OecdwNiRQxYX3NvwwmPDR1NlI0o,1341
|
|
8
|
+
flasklet/render/jinja.py,sha256=pjPhE2CUTnH6fu-Q3JB0ShQ_xlW8dEaZjxC_rWUdqy4,3840
|
|
9
|
+
flasklet/render/macro.py,sha256=idWxt5WWE1TPbnCQrJ3F47LG0TgoUNyzRnodAurPqs0,2852
|
|
10
|
+
flasklet/render/partial.py,sha256=swJ7nzmnBPaZjSgKV0jXjK3fkqMRSVJrBz6oEp7f2ho,436
|
|
11
|
+
flasklet-0.1.0.dist-info/WHEEL,sha256=l3MmIxu8qaet7ng2J9fFoJnYGj8IREj7jXTbgsuzmy4,81
|
|
12
|
+
flasklet-0.1.0.dist-info/METADATA,sha256=fmjeouAHNyb0uT6q5l_YBeC0aygmPC4ZS58CB3LOVbA,303
|
|
13
|
+
flasklet-0.1.0.dist-info/RECORD,,
|