Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace number stringification hack with custom YAML loader #4183

Merged
merged 3 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 32 additions & 23 deletions conda_build/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations

import contextlib
import copy
import hashlib
import json
Expand Down Expand Up @@ -41,9 +40,37 @@
)

try:
loader = yaml.CLoader
except:
loader = yaml.Loader
Loader = yaml.CLoader
except AttributeError:
Loader = yaml.Loader


class StringifyNumbersLoader(Loader):
@classmethod
def remove_implicit_resolver(cls, tag):
if "yaml_implicit_resolvers" not in cls.__dict__:
cls.yaml_implicit_resolvers = {
k: v[:] for k, v in cls.yaml_implicit_resolvers.items()
}
for ch in tuple(cls.yaml_implicit_resolvers):
resolvers = [(t, r) for t, r in cls.yaml_implicit_resolvers[ch] if t != tag]
if resolvers:
cls.yaml_implicit_resolvers[ch] = resolvers
else:
del cls.yaml_implicit_resolvers[ch]

@classmethod
def remove_constructor(cls, tag):
if "yaml_constructors" not in cls.__dict__:
cls.yaml_constructors = cls.yaml_constructors.copy()
if tag in cls.yaml_constructors:
del cls.yaml_constructors[tag]


StringifyNumbersLoader.remove_implicit_resolver("tag:yaml.org,2002:float")
StringifyNumbersLoader.remove_implicit_resolver("tag:yaml.org,2002:int")
StringifyNumbersLoader.remove_constructor("tag:yaml.org,2002:float")
StringifyNumbersLoader.remove_constructor("tag:yaml.org,2002:int")

on_win = sys.platform == "win32"

Expand Down Expand Up @@ -261,9 +288,7 @@ def select_lines(data, namespace, variants_in_place):

def yamlize(data):
try:
with stringify_numbers():
loaded_data = yaml.load(data, Loader=loader)
return loaded_data
return yaml.load(data, Loader=StringifyNumbersLoader)
except yaml.error.YAMLError as e:
if "{{" in data:
try:
Expand Down Expand Up @@ -1056,23 +1081,7 @@ def _hash_dependencies(hashing_dependencies, hash_length):
return f"h{hash_.hexdigest()}"[: hash_length + 1]


@contextlib.contextmanager
def stringify_numbers():
# ensure that numbers are not interpreted as ints or floats. That trips up versions
# with trailing zeros.
implicit_resolver_backup = loader.yaml_implicit_resolvers.copy()
for ch in list("0123456789"):
if ch in loader.yaml_implicit_resolvers:
del loader.yaml_implicit_resolvers[ch]
yield
for ch in list("0123456789"):
if ch in implicit_resolver_backup:
loader.yaml_implicit_resolvers[ch] = implicit_resolver_backup[ch]


class MetaData:
__hash__ = None # declare as non-hashable to avoid its use with memoization

def __init__(self, path, config=None, variant=None):
self.undefined_jinja_vars = []
self.config = get_or_merge_config(config, variant=variant)
Expand Down
65 changes: 64 additions & 1 deletion tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest

from conda_build import api
from conda_build.metadata import MetaData, _hash_dependencies, select_lines
from conda_build.metadata import MetaData, _hash_dependencies, select_lines, yamlize
from conda_build.utils import DEFAULT_SUBDIRS

from .utils import metadata_dir, thisdir
Expand Down Expand Up @@ -260,3 +260,66 @@ def test_config_member_decoupling(testing_metadata):
b = testing_metadata.copy()
b.config.some_member = "123"
assert b.config.some_member != testing_metadata.config.some_member


# ensure that numbers are not interpreted as ints or floats, doing so trips up versions
# with trailing zeros
def test_yamlize_zero():
yml = yamlize(
"""
- 0
- 0.
- 0.0
- .0
"""
)

assert yml == ["0", "0.", "0.0", ".0"]


def test_yamlize_positive():
yml = yamlize(
"""
- +1
- +1.
- +1.2
- +.2
"""
)

assert yml == ["+1", "+1.", "+1.2", "+.2"]


def test_yamlize_negative():
yml = yamlize(
"""
- -1
- -1.
- -1.2
- -.2
"""
)

assert yml == ["-1", "-1.", "-1.2", "-.2"]


def test_yamlize_numbers():
yml = yamlize(
"""
- 1
- 1.2
"""
)

assert yml == ["1", "1.2"]


def test_yamlize_versions():
yml = yamlize(
"""
- 1.2.3
- 1.2.3.4
"""
)

assert yml == ["1.2.3", "1.2.3.4"]