Skip to content

Commit daf8f31

Browse files
turbaszekolchasfeluelle
authored
Add template fields renderers for better UI rendering (#11061)
This PR adds possibility to define template_fields_renderers for an operator. In this way users will be able to provide information what lexer should be used for rendering a particular field. This is super useful for custom operator and gives more flexibility than predefined keywords. Co-authored-by: Kamil Olszewski <[email protected]> Co-authored-by: Felix Uellendall <[email protected]>
1 parent 0edc3dd commit daf8f31

File tree

7 files changed

+52
-6
lines changed

7 files changed

+52
-6
lines changed

airflow/models/baseoperator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ class derived from this one results in the creation of a task object,
276276
template_fields: Iterable[str] = ()
277277
# Defines which files extensions to look for in the templated fields
278278
template_ext: Iterable[str] = ()
279+
# Template field renderers indicating type of the field, for example sql, json, bash
280+
template_fields_renderers: Dict[str, str] = {}
281+
279282
# Defines the color in the UI
280283
ui_color = '#fff' # type: str
281284
ui_fgcolor = '#000' # type: str
@@ -1311,7 +1314,8 @@ def get_serialized_fields(cls):
13111314
vars(BaseOperator(task_id='test')).keys() - {
13121315
'inlets', 'outlets', '_upstream_task_ids', 'default_args', 'dag', '_dag',
13131316
'_BaseOperator__instantiated',
1314-
} | {'_task_type', 'subdag', 'ui_color', 'ui_fgcolor', 'template_fields'})
1317+
} | {'_task_type', 'subdag', 'ui_color', 'ui_fgcolor',
1318+
'template_fields', 'template_fields_renderers'})
13151319

13161320
return cls.__serialized_fields
13171321

airflow/operators/bash.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class BashOperator(BaseOperator):
9595
"""
9696

9797
template_fields = ('bash_command', 'env')
98+
template_fields_renderers = {'bash_command': 'bash', 'env': 'json'}
9899
template_ext = ('.sh', '.bash',)
99100
ui_color = '#f0ede4'
100101

airflow/providers/google/cloud/operators/dataproc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ class DataprocCreateClusterOperator(BaseOperator):
460460
'labels',
461461
'impersonation_chain',
462462
)
463+
template_fields_renderers = {'cluster_config': 'json'}
463464

464465
@apply_defaults
465466
def __init__( # pylint: disable=too-many-arguments

airflow/www/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,12 @@ def wrapped_markdown(s, css_class=None):
333333
'<div class="rich_doc {css_class}" >'.format(css_class=css_class) + markdown.markdown(s) + "</div>"
334334
)
335335

336-
# pylint: disable=no-member
337-
338336

337+
# pylint: disable=no-member
339338
def get_attr_renderer():
340339
"""Return Dictionary containing different Pygments Lexers for Rendering & Highlighting"""
341340
return {
341+
'bash': lambda x: render(x, lexers.BashLexer),
342342
'bash_command': lambda x: render(x, lexers.BashLexer),
343343
'hql': lambda x: render(x, lexers.SqlLexer),
344344
'sql': lambda x: render(x, lexers.SqlLexer),
@@ -347,9 +347,13 @@ def get_attr_renderer():
347347
'doc_rst': lambda x: render(x, lexers.RstLexer),
348348
'doc_yaml': lambda x: render(x, lexers.YamlLexer),
349349
'doc_md': wrapped_markdown,
350+
'json': lambda x: render(x, lexers.JsonLexer),
351+
'md': wrapped_markdown,
352+
'py': lambda x: render(get_python_source(x), lexers.PythonLexer),
350353
'python_callable': lambda x: render(get_python_source(x), lexers.PythonLexer),
354+
'rst': lambda x: render(x, lexers.RstLexer),
355+
'yaml': lambda x: render(x, lexers.YamlLexer),
351356
}
352-
353357
# pylint: enable=no-member
354358

355359

airflow/www/views.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -823,10 +823,15 @@ def rendered(self):
823823
flash("Error rendering template: " + str(e), "error")
824824
title = "Rendered Template"
825825
html_dict = {}
826+
renderers = wwwutils.get_attr_renderer()
827+
826828
for template_field in task.template_fields:
827829
content = getattr(task, template_field)
828-
if template_field in wwwutils.get_attr_renderer():
829-
html_dict[template_field] = wwwutils.get_attr_renderer()[template_field](content)
830+
renderer = task.template_fields_renderers.get(template_field, template_field)
831+
if renderer in renderers:
832+
if isinstance(content, (dict, list)):
833+
content = json.dumps(content, sort_keys=True, indent=4)
834+
html_dict[template_field] = renderers[renderer](content)
830835
else:
831836
html_dict[template_field] = \
832837
Markup("<pre><code>{}</pre></code>").format(pformat(content)) # noqa

docs/howto/custom-operator.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,35 @@ with actual value. Note that Jinja substitutes the operator attributes and not t
200200
201201
In the example, the ``template_fields`` should be ``['guest_name']`` and not ``['name']``
202202

203+
Additionally you may provide ``template_fields_renderers`` dictionary which defines in what style the value
204+
from template field renders in Web UI. For example:
205+
206+
.. code-block:: python
207+
208+
class MyRequestOperator(BaseOperator):
209+
template_fields = ['request_body']
210+
template_fields_renderers = {'request_body': 'json'}
211+
212+
@apply_defaults
213+
def __init__(
214+
self,
215+
request_body: str,
216+
**kwargs) -> None:
217+
super().__init__(**kwargs)
218+
self.request_body = request_body
219+
220+
Currently available lexers:
221+
222+
- bash
223+
- doc
224+
- json
225+
- md
226+
- py
227+
- rst
228+
- sql
229+
- yaml
230+
231+
If you use a non existing lexer then the value of the template field will be rendered as a pretty printed object.
203232

204233
Define an operator extra link
205234
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/serialization/test_dag_serialization.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"ui_color": "#f0ede4",
9494
"ui_fgcolor": "#000",
9595
"template_fields": ['bash_command', 'env'],
96+
"template_fields_renderers": {'bash_command': 'bash', 'env': 'json'},
9697
"bash_command": "echo {{ task.task_id }}",
9798
'label': 'bash_task',
9899
"_task_type": "BashOperator",
@@ -116,6 +117,7 @@
116117
"ui_color": "#fff",
117118
"ui_fgcolor": "#000",
118119
"template_fields": ['bash_command'],
120+
"template_fields_renderers": {},
119121
"_task_type": "CustomOperator",
120122
"_task_module": "tests.test_utils.mock_operators",
121123
"pool": "default_pool",

0 commit comments

Comments
 (0)