Skip to content

Commit

Permalink
Merge pull request #36 from conda-incubator/meta-yaml-improvements
Browse files Browse the repository at this point in the history
Harden up undefined vars jinja support a bit
  • Loading branch information
mariusvniekerk authored Sep 8, 2020
2 parents 2a85a62 + db45848 commit 76b69ce
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 55 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: check-ast

- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.1
rev: 3.8.3
hooks:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
rev: v5.4.2
hooks:
- id: isort
args: [--multi-line=3, --trailing-comma, --force-grid-wrap=0, --use-parentheses, --line-width=88]

- repo: https://github.com/psf/black
rev: 19.10b0
rev: 20.8b1
hooks:
- id: black
language_version: python3

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.760
rev: v0.782
hooks:
- id: mypy
4 changes: 4 additions & 0 deletions conda_lock/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .conda_lock import main


main()
6 changes: 4 additions & 2 deletions conda_lock/conda_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ def install_micromamba_exe() -> str:
url = f"https://micromamba.snakepit.net/api/micromamba/{p}/latest"
resp = requests.get(url, allow_redirects=True)
resp.raise_for_status()
import tarfile
import io
import tarfile

tarball = io.BytesIO(resp.content)
tarball.seek(0)
Expand Down Expand Up @@ -264,7 +264,9 @@ def fn_to_dist_name(fn: str) -> str:


def make_lock_files(
conda: PathLike, platforms: List[str], src_file: pathlib.Path,
conda: PathLike,
platforms: List[str],
src_file: pathlib.Path,
):
for plat in platforms:
print(f"generating lockfile for {plat}", file=sys.stderr)
Expand Down
111 changes: 63 additions & 48 deletions conda_lock/src_parser/meta_yaml.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,84 @@
import pathlib

from typing import List

import jinja2
import yaml

from conda_lock.src_parser import LockSpecification
from conda_lock.src_parser.selectors import filter_platform_selectors


class NullUndefined(jinja2.Undefined):
def __getattr__(self, key):
return ""
class UndefinedNeverFail(jinja2.Undefined):
"""
Copied from https://github.com/conda/conda-build/blob/master/conda_build/jinja_context.py
A class for Undefined jinja variables.
This is even less strict than the default jinja2.Undefined class,
because it permits things like {{ MY_UNDEFINED_VAR[:2] }} and
{{ MY_UNDEFINED_VAR|int }}. This can mask lots of errors in jinja templates, so it
should only be used for a first-pass parse, when you plan on running a 'strict'
second pass later.
Note:
When using this class, any usage of an undefined variable in a jinja template is recorded
in the (global) all_undefined_names class member. Therefore, after jinja rendering,
you can detect which undefined names were used by inspecting that list.
Be sure to clear the all_undefined_names list before calling template.render().
"""

all_undefined_names: List[str] = []

def __init__(
self,
hint=None,
obj=jinja2.runtime.missing,
name=None,
exc=jinja2.exceptions.UndefinedError,
):
jinja2.Undefined.__init__(self, hint, obj, name, exc)

# Using any of these methods on an Undefined variable
# results in another Undefined variable.
__add__ = (
__radd__
) = (
__mul__
) = (
__rmul__
) = (
__div__
) = (
__rdiv__
) = (
__truediv__
) = (
__rtruediv__
) = (
__floordiv__
) = (
__rfloordiv__
) = (
__mod__
) = (
__rmod__
) = (
__pos__
) = (
__neg__
) = (
__call__
) = (
__getitem__
) = (
__lt__
) = (
__le__
) = (
__gt__
) = (
__ge__
) = (
__complex__
) = __pow__ = __rpow__ = lambda self, *args, **kwargs: self._return_undefined(
self._undefined_name
)
# fmt: off
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
__complex__ = __pow__ = __rpow__ = \
lambda self, *args, **kwargs: self._return_undefined(self._undefined_name) # noqa: E122
# fmt: on

# Accessing an attribute of an Undefined variable
# results in another Undefined variable.
def __getattr__(self, k):
try:
return object.__getattr__(self, k)
except AttributeError:
self._return_undefined(self._undefined_name + "." + k)

# Unlike the methods above, Python requires that these
# few methods must always return the correct type
__str__ = __repr__ = lambda self: self._return_value(str()) # type: ignore # noqa: E731
__unicode__ = lambda self: self._return_value("") # noqa: E731
__int__ = lambda self: self._return_value(0) # noqa: E731
__float__ = lambda self: self._return_value(0.0) # noqa: E731
__nonzero__ = lambda self: self._return_value(False) # noqa: E731

def _return_undefined(self, result_name):
# Record that this undefined variable was actually used.
return NullUndefined(
UndefinedNeverFail.all_undefined_names.append(self._undefined_name)
return UndefinedNeverFail(
hint=self._undefined_hint,
obj=self._undefined_obj,
name=result_name,
exc=self._undefined_exception,
)

def _return_value(self, value=None):
# Record that this undefined variable was actually used.
UndefinedNeverFail.all_undefined_names.append(self._undefined_name)
return value


def parse_meta_yaml_file(
meta_yaml_file: pathlib.Path, platform: str
Expand All @@ -81,9 +94,11 @@ def parse_meta_yaml_file(
filtered_recipe = "\n".join(
filter_platform_selectors(fo.read(), platform=platform)
)
t = jinja2.Template(filtered_recipe, undefined=NullUndefined)
t = jinja2.Template(filtered_recipe, undefined=UndefinedNeverFail)
rendered = t.render()

meta_yaml_data = yaml.safe_load(rendered)

channels = meta_yaml_data.get("extra", {}).get("channels", [])
specs = []

Expand Down

0 comments on commit 76b69ce

Please sign in to comment.