Skip to content

tags

Class info

Classes

Name Children Inherits
BaseTemplateTag
jinjarope.tags
Base class for template tag extensions providing common functionality.
ContainerTag
jinjarope.tags
Tag that wraps content and processes it.
    InclusionTag
    jinjarope.tags
    Tag that includes other templates with context.
      StandaloneTag
      jinjarope.tags
      Tag that renders to a single output without content block.

      🛈 DocStrings

      Jinja2 template tag extensions.

      BaseTemplateTag

      Bases: Extension

      Base class for template tag extensions providing common functionality.

      Source code in src/jinjarope/tags.py
       24
       25
       26
       27
       28
       29
       30
       31
       32
       33
       34
       35
       36
       37
       38
       39
       40
       41
       42
       43
       44
       45
       46
       47
       48
       49
       50
       51
       52
       53
       54
       55
       56
       57
       58
       59
       60
       61
       62
       63
       64
       65
       66
       67
       68
       69
       70
       71
       72
       73
       74
       75
       76
       77
       78
       79
       80
       81
       82
       83
       84
       85
       86
       87
       88
       89
       90
       91
       92
       93
       94
       95
       96
       97
       98
       99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      class BaseTemplateTag(Extension):
          """Base class for template tag extensions providing common functionality."""
      
          def __init__(self, environment: Environment) -> None:
              super().__init__(environment)
              self.context: Context | None = None
              self.template: str | None = None
              self.lineno: int | None = None
              self.tag_name: str | None = None
      
          def parse(self, parser: Parser) -> nodes.Node:
              """Parse the tag definition and return a Node."""
              lineno = parser.stream.current.lineno
              tag_name = parser.stream.current.value
              meta_kwargs = [
                  nodes.Keyword("_context", nodes.ContextReference()),
                  nodes.Keyword("_template", nodes.Const(parser.name)),
                  nodes.Keyword("_lineno", nodes.Const(lineno)),
                  nodes.Keyword("_tag_name", nodes.Const(tag_name)),
              ]
      
              self.init_parser(parser)
              args, kwargs, options = self.parse_args(parser)
              kwargs.extend(meta_kwargs)
              options["tag_name"] = tag_name
              return self.create_node(parser, args, kwargs, lineno=lineno, **options)
      
          def init_parser(self, parser: Parser) -> None:
              """Initialize parser by skipping the tag name."""
              parser.stream.skip(1)
      
          def parse_args(
              self,
              parser: Parser,
          ) -> tuple[list[nodes.Expr], list[nodes.Keyword], dict[str, Any]]:
              """Parse arguments from the tag definition."""
              args: list[nodes.Expr] = []
              kwargs: list[nodes.Keyword] = []
              options: dict[str, str | None] = {"target": None}
              require_comma = False
              arguments_finished = False
      
              while parser.stream.current.type != "block_end":
                  if parser.stream.current.test("name:as"):
                      parser.stream.skip(1)
                      options["target"] = parser.stream.expect("name").value
                      arguments_finished = True
      
                  if arguments_finished:
                      if not parser.stream.current.test("block_end"):
                          desc = describe_token(parser.stream.current)
                          parser.fail(
                              f"Expected 'block_end', got {desc!r}",
                              parser.stream.current.lineno,
                          )
                      break
      
                  if require_comma:
                      parser.stream.expect("comma")
      
                      # support for trailing comma
                      if parser.stream.current.type == "block_end":
                          break
      
                  token = parser.stream.current
                  if token.type == "name" and parser.stream.look().type == "assign":
                      key = token.value
                      parser.stream.skip(2)  # Skip name and assign tokens
                      value = parser.parse_expression()
                      kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
                  else:
                      if kwargs:
                          parser.fail("Invalid argument syntax", token.lineno)
                      args.append(parser.parse_expression())
      
                  require_comma = True
      
              return args, kwargs, options
      
          def create_node(
              self,
              parser: Parser,
              args: list[nodes.Expr],
              kwargs: list[nodes.Keyword],
              *,
              lineno: int,
              **options: Any,
          ) -> nodes.Node:
              raise NotImplementedError
      

      init_parser

      init_parser(parser: Parser) -> None
      

      Initialize parser by skipping the tag name.

      Source code in src/jinjarope/tags.py
      51
      52
      53
      def init_parser(self, parser: Parser) -> None:
          """Initialize parser by skipping the tag name."""
          parser.stream.skip(1)
      

      parse

      parse(parser: Parser) -> Node
      

      Parse the tag definition and return a Node.

      Source code in src/jinjarope/tags.py
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      def parse(self, parser: Parser) -> nodes.Node:
          """Parse the tag definition and return a Node."""
          lineno = parser.stream.current.lineno
          tag_name = parser.stream.current.value
          meta_kwargs = [
              nodes.Keyword("_context", nodes.ContextReference()),
              nodes.Keyword("_template", nodes.Const(parser.name)),
              nodes.Keyword("_lineno", nodes.Const(lineno)),
              nodes.Keyword("_tag_name", nodes.Const(tag_name)),
          ]
      
          self.init_parser(parser)
          args, kwargs, options = self.parse_args(parser)
          kwargs.extend(meta_kwargs)
          options["tag_name"] = tag_name
          return self.create_node(parser, args, kwargs, lineno=lineno, **options)
      

      parse_args

      parse_args(parser: Parser) -> tuple[list[Expr], list[Keyword], dict[str, Any]]
      

      Parse arguments from the tag definition.

      Source code in src/jinjarope/tags.py
       55
       56
       57
       58
       59
       60
       61
       62
       63
       64
       65
       66
       67
       68
       69
       70
       71
       72
       73
       74
       75
       76
       77
       78
       79
       80
       81
       82
       83
       84
       85
       86
       87
       88
       89
       90
       91
       92
       93
       94
       95
       96
       97
       98
       99
      100
      101
      def parse_args(
          self,
          parser: Parser,
      ) -> tuple[list[nodes.Expr], list[nodes.Keyword], dict[str, Any]]:
          """Parse arguments from the tag definition."""
          args: list[nodes.Expr] = []
          kwargs: list[nodes.Keyword] = []
          options: dict[str, str | None] = {"target": None}
          require_comma = False
          arguments_finished = False
      
          while parser.stream.current.type != "block_end":
              if parser.stream.current.test("name:as"):
                  parser.stream.skip(1)
                  options["target"] = parser.stream.expect("name").value
                  arguments_finished = True
      
              if arguments_finished:
                  if not parser.stream.current.test("block_end"):
                      desc = describe_token(parser.stream.current)
                      parser.fail(
                          f"Expected 'block_end', got {desc!r}",
                          parser.stream.current.lineno,
                      )
                  break
      
              if require_comma:
                  parser.stream.expect("comma")
      
                  # support for trailing comma
                  if parser.stream.current.type == "block_end":
                      break
      
              token = parser.stream.current
              if token.type == "name" and parser.stream.look().type == "assign":
                  key = token.value
                  parser.stream.skip(2)  # Skip name and assign tokens
                  value = parser.parse_expression()
                  kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
              else:
                  if kwargs:
                      parser.fail("Invalid argument syntax", token.lineno)
                  args.append(parser.parse_expression())
      
              require_comma = True
      
          return args, kwargs, options
      

      ContainerTag

      Bases: BaseTemplateTag

      Tag that wraps content and processes it.

      Source code in src/jinjarope/tags.py
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      class ContainerTag(BaseTemplateTag):
          """Tag that wraps content and processes it."""
      
          def create_node(
              self,
              parser: Parser,
              args: list[nodes.Expr],
              kwargs: list[nodes.Keyword],
              *,
              lineno: int,
              **options: Any,
          ) -> nodes.Node:
              """Create a node that processes wrapped content."""
              call_node = self.call_method("render_wrapper", args, kwargs, lineno=lineno)
              body = parser.parse_statements(
                  (f"name:end{options['tag_name']}",),
                  drop_needle=True,
              )
              call_block = nodes.CallBlock(call_node, [], [], body).set_lineno(lineno)
      
              if target := options.get("target"):
                  target_node = nodes.Name(target, "store", lineno=lineno)
                  return nodes.AssignBlock(target_node, None, [call_block], lineno=lineno)
              return call_block
      
          def render_wrapper(self, *args: Any, **kwargs: Any) -> Any:
              self.context = kwargs.pop("_context")
              self.template = kwargs.pop("_template")
              self.lineno = kwargs.pop("_lineno")
              self.tag_name = kwargs.pop("_tag_name")
              return self.render(*args, **kwargs)
      
          def render(self, *args: Any, **kwargs: Any) -> Any:
              raise NotImplementedError
      

      create_node

      create_node(
          parser: Parser,
          args: list[Expr],
          kwargs: list[Keyword],
          *,
          lineno: int,
          **options: Any
      ) -> Node
      

      Create a node that processes wrapped content.

      Source code in src/jinjarope/tags.py
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      def create_node(
          self,
          parser: Parser,
          args: list[nodes.Expr],
          kwargs: list[nodes.Keyword],
          *,
          lineno: int,
          **options: Any,
      ) -> nodes.Node:
          """Create a node that processes wrapped content."""
          call_node = self.call_method("render_wrapper", args, kwargs, lineno=lineno)
          body = parser.parse_statements(
              (f"name:end{options['tag_name']}",),
              drop_needle=True,
          )
          call_block = nodes.CallBlock(call_node, [], [], body).set_lineno(lineno)
      
          if target := options.get("target"):
              target_node = nodes.Name(target, "store", lineno=lineno)
              return nodes.AssignBlock(target_node, None, [call_block], lineno=lineno)
          return call_block
      

      InclusionTag

      Bases: StandaloneTag

      Tag that includes other templates with context.

      Source code in src/jinjarope/tags.py
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      class InclusionTag(StandaloneTag):
          """Tag that includes other templates with context."""
      
          template_name: str | None = None
          safe_output: ClassVar[bool] = True
      
          def render(self, *args: Any, **kwargs: Any) -> str:
              """Render included template with context."""
              template_names = self.get_template_names(*args, **kwargs)
              template = (
                  self.environment.get_template(template_names)
                  if isinstance(template_names, str)
                  else self.environment.select_template(template_names)
              )
      
              if not self.context:
                  msg = "Context not available"
                  raise RuntimeError(msg)
      
              context = template.new_context(
                  {**self.context.get_all(), **self.get_context(*args, **kwargs)},
                  shared=True,
              )
              return template.render(context)
      
          def get_context(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
              """Get additional context for template."""
              return {}
      
          def get_template_names(self, *args: Any, **kwargs: Any) -> str | Sequence[str]:
              """Get template name(s) to include."""
              if not self.template_name:
                  msg = "InclusionTag requires 'template_name' or 'get_template_names()'"
                  raise RuntimeError(msg)
              return self.template_name
      

      get_context

      get_context(*args: Any, **kwargs: Any) -> dict[str, Any]
      

      Get additional context for template.

      Source code in src/jinjarope/tags.py
      216
      217
      218
      def get_context(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
          """Get additional context for template."""
          return {}
      

      get_template_names

      get_template_names(*args: Any, **kwargs: Any) -> str | Sequence[str]
      

      Get template name(s) to include.

      Source code in src/jinjarope/tags.py
      220
      221
      222
      223
      224
      225
      def get_template_names(self, *args: Any, **kwargs: Any) -> str | Sequence[str]:
          """Get template name(s) to include."""
          if not self.template_name:
              msg = "InclusionTag requires 'template_name' or 'get_template_names()'"
              raise RuntimeError(msg)
          return self.template_name
      

      render

      render(*args: Any, **kwargs: Any) -> str
      

      Render included template with context.

      Source code in src/jinjarope/tags.py
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      def render(self, *args: Any, **kwargs: Any) -> str:
          """Render included template with context."""
          template_names = self.get_template_names(*args, **kwargs)
          template = (
              self.environment.get_template(template_names)
              if isinstance(template_names, str)
              else self.environment.select_template(template_names)
          )
      
          if not self.context:
              msg = "Context not available"
              raise RuntimeError(msg)
      
          context = template.new_context(
              {**self.context.get_all(), **self.get_context(*args, **kwargs)},
              shared=True,
          )
          return template.render(context)
      

      StandaloneTag

      Bases: BaseTemplateTag

      Tag that renders to a single output without content block.

      Source code in src/jinjarope/tags.py
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      class StandaloneTag(BaseTemplateTag):
          """Tag that renders to a single output without content block."""
      
          safe_output: ClassVar[bool] = False
      
          def create_node(
              self,
              parser: Parser,
              args: list[nodes.Expr],
              kwargs: list[nodes.Keyword],
              *,
              lineno: int,
              **options: Any,
          ) -> nodes.Node:
              call_node: nodes.Call | nodes.MarkSafeIfAutoescape = self.call_method(
                  "render_wrapper",
                  args,
                  kwargs,
                  lineno=lineno,
              )
              if self.safe_output:
                  call_node = nodes.MarkSafeIfAutoescape(call_node, lineno=lineno)
      
              if target := options.get("target"):
                  target_node = nodes.Name(target, "store", lineno=lineno)
                  return nodes.Assign(target_node, call_node, lineno=lineno)
      
              return nodes.Output([call_node], lineno=lineno)
      
          def render_wrapper(self, *args: Any, **kwargs: Any) -> Any:
              self.context = kwargs.pop("_context")
              self.template = kwargs.pop("_template")
              self.lineno = kwargs.pop("_lineno")
              self.tag_name = kwargs.pop("_tag_name")
              return self.render(*args, **kwargs)
      
          def render(self, *args: Any, **kwargs: Any) -> Any:
              raise NotImplementedError
      

      create_tag_extension

      create_tag_extension(
          typ: Literal["standalone", "container", "inclusion"],
          tag: str | list[str] | set[str],
          render_fn: Callable[..., str],
      )
      

      Create a Jinja2 extension from a render function.

      Source code in src/jinjarope/tags.py
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      def create_tag_extension(
          typ: Literal["standalone", "container", "inclusion"],
          tag: str | list[str] | set[str],
          render_fn: Callable[..., str],
      ):
          """Create a Jinja2 extension from a render function."""
          jinja_tag = {tag} if isinstance(tag, str) else set(tag)
          match typ:
              case "standalone":
                  base_cls: type[BaseTemplateTag] = StandaloneTag
              case "container":
                  base_cls = ContainerTag
              case "inclusion":
                  base_cls = InclusionTag
              case _:
                  msg = f"Invalid extension type: {typ!r}"
                  raise ValueError(msg)
      
          class Extension(base_cls):  # type: ignore
              tags = jinja_tag
              render = staticmethod(render_fn)
      
          return Extension