Skip to content

Commit

Permalink
initial jinja rendering in Python (#34)
Browse files Browse the repository at this point in the history
Co-authored-by: nichmor <[email protected]>
  • Loading branch information
wolfv and nichmor authored Jul 30, 2024
1 parent a214770 commit f96f7c5
Show file tree
Hide file tree
Showing 9 changed files with 1,152 additions and 1,944 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/type-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: prefix-dev/[email protected]
with:
pixi-version: "latest"
environments: lint
environments: type-checking

- name: type check
run: |
Expand Down
2,958 changes: 1,016 additions & 1,942 deletions pixi.lock

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ conda = ">=4.2"
pygithub = ">=2,<3"
tomli = ">=2.0.1,<3"
typing-extensions = ">=4.12.2,<4.13"
jinja2 = ">=3.0.2,<4"
types-PyYAML = ">=6.0.12.20240311,<6.0.13"

[pypi-dependencies]
rattler-build-conda-compat = { path = ".", editable = true }
Expand All @@ -28,7 +30,7 @@ pytest = ">=8.2.2,<9"
syrupy = ">=4.6.1,<5"

[feature.tests.tasks]
tests = "pytest tests"
tests = "pytest --doctest-modules"
snapshot_update = "pytest --snapshot-update tests"

[feature.lint.dependencies]
Expand All @@ -42,6 +44,11 @@ ruff = ">=0.5.0,<0.6"
[feature.lint.tasks]
pre-commit-install = "pre-commit-install"
pre-commit-run = "pre-commit run"

[feature.type-checking.dependencies]
mypy = ">=1.10.1,<2"

[feature.type-checking.tasks]
type-check = "mypy src"

[feature.py312.dependencies]
Expand All @@ -66,3 +73,4 @@ py310 = ["py310", "tests"]
py39 = ["py39", "tests"]
py38 = ["py38", "tests"]
lint = { features = ["lint"], no-default-feature = true }
type-checking = { features = ["type-checking"] }
80 changes: 80 additions & 0 deletions src/rattler_build_conda_compat/jinja.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from __future__ import annotations

from typing import Any, TypedDict

import jinja2
import yaml
from jinja2 import DebugUndefined

from rattler_build_conda_compat.loader import load_yaml


class RecipeWithContext(TypedDict, total=False):
context: dict[str, str]


class _MissingUndefined(DebugUndefined):
def __str__(self) -> str:
"""
By default, `DebugUndefined` return values in the form `{{ value }}`.
`rattler-build` has a different syntax, so we need to override this method,
and return the value in the form `${{ value }}`.
"""
return f"${super().__str__()}"


def jinja_env() -> jinja2.Environment:
"""
Create a `rattler-build` specific Jinja2 environment with modified syntax.
"""
return jinja2.Environment(
variable_start_string="${{",
variable_end_string="}}",
trim_blocks=True,
lstrip_blocks=True,
autoescape=True,
undefined=_MissingUndefined,
)


def load_recipe_context(context: dict[str, str], jinja_env: jinja2.Environment) -> dict[str, str]:
"""
Load all string values from the context dictionary as Jinja2 templates.
"""
# Process each key-value pair in the dictionary
for key, value in context.items():
# If the value is a string, render it as a template
if isinstance(value, str):
template = jinja_env.from_string(value)
rendered_value = template.render(context)
context[key] = rendered_value

return context


def render_recipe_with_context(recipe_content: RecipeWithContext) -> dict[str, Any]:
"""
Render the recipe using known values from context section.
Unknown values are not evaluated and are kept as it is.
Examples:
---
```python
>>> from pathlib import Path
>>> from rattler_build_conda_compat.loader import load_yaml
>>> recipe_content = load_yaml((Path().resolve() / "tests" / "data" / "eval_recipe_using_context.yaml").read_text())
>>> evaluated_context = render_recipe_with_context(recipe_content)
>>> assert "my_value-${{ not_present_value }}" == evaluated_context["build"]["string"]
>>>
```
"""
env = jinja_env()
context = recipe_content.get("context", {})
# load all context templates
context_templates = load_recipe_context(context, env)

# render the rest of the document with the values from the context
# and keep undefined expressions _as is_.
template = env.from_string(yaml.dump(recipe_content))
rendered_content = template.render(context_templates)
return load_yaml(rendered_content)
15 changes: 15 additions & 0 deletions tests/__snapshots__/test_jinja.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# serializer version: 1
# name: test_render_context
'''
build:
string: ${{ blas_variant }}${{ hash }}_foo-bla
context:
name: foo
name_version: foo-bla
version: bla
package:
name: foo
version: bla

'''
# ---
11 changes: 11 additions & 0 deletions tests/data/context.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
context:
name: "foo"
version: "bla"
name_version: ${{ name }}-${{ version }}

package:
name: ${{ name }}
version: ${{ version }}

build:
string: ${{ blas_variant }}${{ hash }}_${{ name_version }}
4 changes: 4 additions & 0 deletions tests/data/eval_recipe_using_context.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
context:
name: "my_value"
build:
string: ${{ name }}-${{ not_present_value }}
15 changes: 15 additions & 0 deletions tests/test_jinja.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path

import yaml
from rattler_build_conda_compat.jinja import load_yaml, render_recipe_with_context


def test_render_recipe_with_context(snapshot) -> None:
recipe = Path("tests/data/context.yaml")
with recipe.open() as f:
recipe_yaml = load_yaml(f)

rendered = render_recipe_with_context(recipe_yaml)
into_yaml = yaml.dump(rendered)

assert into_yaml == snapshot
1 change: 1 addition & 0 deletions tests/test_rattler_render.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
from pathlib import Path
from typing import TYPE_CHECKING, Any

from rattler_build_conda_compat.loader import parse_recipe_config_file
Expand Down

0 comments on commit f96f7c5

Please sign in to comment.