Skip to content

Commit c6a7426

Browse files
committed
Refactor spec parser and move utility to template engine
1 parent c6ffc6f commit c6a7426

2 files changed

Lines changed: 73 additions & 29 deletions

File tree

cli/polyaxon/_polyaxonfile/specs/libs/engine.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import jinja2
22

3-
from typing import Optional
3+
from typing import Any, Dict, Optional
44

55
from markupsafe import soft_str
66

7+
from polyaxon.exceptions import PolyaxonSchemaError
8+
79

810
def map_format(value, pattern, variable_name: Optional[str] = None):
911
if variable_name:
@@ -15,3 +17,69 @@ def get_engine():
1517
env = jinja2.Environment()
1618
env.filters["map_format"] = map_format
1719
return env
20+
21+
22+
def render_template(template_str: str, context: Dict[str, Any]) -> str:
23+
"""
24+
Render a Jinja2 template string with context.
25+
26+
Args:
27+
template_str: Template string with Jinja2 syntax
28+
context: Dictionary of variables for template rendering
29+
30+
Returns:
31+
Rendered string
32+
33+
Raises:
34+
PolyaxonSchemaError: If template rendering fails
35+
"""
36+
if not template_str or not isinstance(template_str, str):
37+
return template_str
38+
39+
if "{{" not in template_str and "{%" not in template_str:
40+
return template_str # No template syntax, return as-is
41+
42+
try:
43+
engine = get_engine()
44+
template = engine.from_string(template_str)
45+
return template.render(**context)
46+
except (jinja2.exceptions.TemplateError, ValueError, TypeError) as e:
47+
raise PolyaxonSchemaError(
48+
f"Encountered a problem rendering the template, "
49+
f"please make sure your variables are resolvable. "
50+
f"Error: {repr(e)}"
51+
)
52+
53+
54+
def render_config(config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
55+
"""
56+
Recursively render all string values in a config dict.
57+
58+
Args:
59+
config: Configuration dictionary with potential template strings
60+
context: Dictionary of variables for template rendering
61+
62+
Returns:
63+
Config with all template strings rendered
64+
"""
65+
if not config:
66+
return config
67+
68+
rendered = {}
69+
for key, value in config.items():
70+
if isinstance(value, str):
71+
rendered[key] = render_template(value, context)
72+
elif isinstance(value, dict):
73+
rendered[key] = render_config(value, context)
74+
elif isinstance(value, list):
75+
rendered[key] = [
76+
render_config(item, context)
77+
if isinstance(item, dict)
78+
else render_template(item, context)
79+
if isinstance(item, str)
80+
else item
81+
for item in value
82+
]
83+
else:
84+
rendered[key] = value
85+
return rendered

cli/polyaxon/_polyaxonfile/specs/libs/parser.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ast
2-
import jinja2
32

43
from collections.abc import Mapping
54
from datetime import date, datetime, timedelta
@@ -15,9 +14,8 @@
1514
)
1615

1716
from polyaxon._flow import ParamSpec
18-
from polyaxon._polyaxonfile.specs.libs.engine import get_engine
17+
from polyaxon._polyaxonfile.specs.libs.engine import render_template
1918
from polyaxon._polyaxonfile.specs.sections import Sections
20-
from polyaxon.exceptions import PolyaxonSchemaError
2119

2220
try:
2321
import numpy as np
@@ -28,8 +26,6 @@
2826
class PolyaxonfileParser:
2927
"""Parses the Polyaxonfile."""
3028

31-
engine = get_engine()
32-
3329
@staticmethod
3430
def _get_section_data(section_data):
3531
if hasattr(section_data, "to_dict"):
@@ -193,19 +189,6 @@ def parse_io(
193189
@classmethod
194190
def parse_expression( # pylint:disable=too-many-branches
195191
cls, expression, params: Dict, check_operators: bool = False
196-
):
197-
try:
198-
return cls._parse_expression(expression, params, check_operators)
199-
except jinja2.exceptions.TemplateError as e:
200-
raise PolyaxonSchemaError(
201-
"Encountered a problem parsing the template, "
202-
"please make sure your variables are resolvable. "
203-
"Error: {}".format(repr(e))
204-
)
205-
206-
@classmethod
207-
def _parse_expression( # pylint:disable=too-many-branches
208-
cls, expression, params: Dict, check_operators: bool = False
209192
):
210193
if isinstance(expression, (int, float, complex, type(None))):
211194
return expression
@@ -227,7 +210,7 @@ def _parse_expression( # pylint:disable=too-many-branches
227210
if len(expression) == 1:
228211
old_key, value = list(expression.items())[0]
229212
# always parse the keys, they must be base object or evaluate to base objects
230-
key = cls._parse_expression(old_key, params)
213+
key = cls.parse_expression(old_key, params)
231214
if check_operators and cls.is_operator(key):
232215
return cls._parse_operator({key: value}, params)
233216
else:
@@ -236,7 +219,7 @@ def _parse_expression( # pylint:disable=too-many-branches
236219
new_expression = {}
237220
for k, v in expression.items():
238221
new_expression.update(
239-
cls._parse_expression({k: v}, params, check_operators)
222+
cls.parse_expression({k: v}, params, check_operators)
240223
)
241224
return new_expression
242225

@@ -253,14 +236,7 @@ def _parse_expression( # pylint:disable=too-many-branches
253236

254237
@classmethod
255238
def _evaluate_expression(cls, expression, params, check_operators):
256-
try:
257-
result = cls.engine.from_string(expression).render(**params)
258-
except (ValueError, TypeError) as e:
259-
raise PolyaxonSchemaError(
260-
"Encountered a problem parsing the template, "
261-
"please make sure your variables are resolvable. "
262-
"Error: {}".format(repr(e))
263-
)
239+
result = render_template(expression, params)
264240
if result == expression:
265241
try:
266242
return ast.literal_eval(result)

0 commit comments

Comments
 (0)