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

Support the new FullLoader in PyYAML v5.1+ #16

Merged
merged 6 commits into from
Mar 13, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 3 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@ python:
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
- "nightly"
- "pypy"
- "pypy3.5"
- "pypy3"

env:
- PYYAML_VERSION="3.13"
# - PYYAML_VERSION="4.1" # this was pulled from the index (!) ..wtf, Ingy?
- PYYAML_VERSION="4.2b4"
- PYYAML_VERSION="5.1"

matrix:
fast_finish: true
allow_failures:
- python: "nightly"
include:
- python: 3.7
dist: xenial
sudo: true

install:
- pip install pyyaml~=$PYYAML_VERSION
Expand Down
24 changes: 12 additions & 12 deletions oyaml.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import platform
import sys
from collections import OrderedDict

import yaml as pyyaml


_items = 'viewitems' if sys.version_info < (3,) else 'items'
_items = "viewitems" if sys.version_info < (3,) else "items"
_std_dict_is_order_preserving = sys.version_info >= (3, 7) or (
sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
)


def map_representer(dumper, data):
Expand All @@ -17,32 +21,28 @@ def map_constructor(loader, node):


if pyyaml.safe_dump is pyyaml.dump:
# PyYAML v4.1
# PyYAML v4.x
SafeDumper = pyyaml.dumper.Dumper
DangerDumper = pyyaml.dumper.DangerDumper
SafeLoader = pyyaml.loader.Loader
DangerLoader = pyyaml.loader.DangerLoader
else:
SafeDumper = pyyaml.dumper.SafeDumper
DangerDumper = pyyaml.dumper.Dumper
SafeLoader = pyyaml.loader.SafeLoader
DangerLoader = pyyaml.loader.Loader

pyyaml.add_representer(dict, map_representer, Dumper=SafeDumper)
pyyaml.add_representer(OrderedDict, map_representer, Dumper=SafeDumper)
pyyaml.add_representer(dict, map_representer, Dumper=DangerDumper)
pyyaml.add_representer(OrderedDict, map_representer, Dumper=DangerDumper)


if sys.version_info < (3, 7):
pyyaml.add_constructor('tag:yaml.org,2002:map', map_constructor, Loader=SafeLoader)
pyyaml.add_constructor('tag:yaml.org,2002:map', map_constructor, Loader=DangerLoader)


del map_constructor, map_representer
Loader = None
if not _std_dict_is_order_preserving:
for loader_name in pyyaml.loader.__all__:
Loader = getattr(pyyaml.loader, loader_name)
pyyaml.add_constructor("tag:yaml.org,2002:map", map_constructor, Loader=Loader)


# Merge PyYAML namespace into ours.
# This allows users a drop-in replacement:
# import oyaml as yaml
del map_constructor, map_representer, SafeDumper, DangerDumper, Loader
from yaml import *
20 changes: 10 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from setuptools import setup

setup(
name='oyaml',
version='0.7',
description='Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering',
long_description=open('README.rst').read(),
author='Wim Glenn',
author_email='hey@wimglenn.com',
url='https://github.com/wimglenn/oyaml',
license='MIT',
py_modules=['oyaml'],
install_requires=['pyyaml'],
name="oyaml",
version="0.8",
description="Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering",
long_description=open("README.rst").read(),
author="Wim Glenn",
author_email="hey@wimglenn.com",
url="https://github.com/wimglenn/oyaml",
license="MIT",
py_modules=["oyaml"],
install_requires=["pyyaml"],
)
137 changes: 56 additions & 81 deletions test_oyaml.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,84 @@
import sys
from collections import OrderedDict
from types import GeneratorType

import pytest
from yaml.representer import RepresenterError

import oyaml as yaml
from oyaml import _std_dict_is_order_preserving


data = OrderedDict([('x', 1), ('z', 3), ('y', 2)])


# this release was pulled from index, but still might be seen in the wild
pyyaml_41 = yaml.pyyaml.__version__ == '4.1'
data = OrderedDict([("x", 1), ("z", 3), ("y", 2)])


def test_dump():
assert yaml.dump(data) == '{x: 1, z: 3, y: 2}\n'
assert yaml.dump(data, default_flow_style=None) == "{x: 1, z: 3, y: 2}\n"


def test_safe_dump():
assert yaml.safe_dump(data) == '{x: 1, z: 3, y: 2}\n'


@pytest.mark.skipif(not pyyaml_41, reason="requires PyYAML version == 4.1")
def test_danger_dump():
assert yaml.danger_dump(data) == '{x: 1, z: 3, y: 2}\n'
assert yaml.safe_dump(data, default_flow_style=None) == "{x: 1, z: 3, y: 2}\n"


def test_dump_all():
assert yaml.dump_all(documents=[data, {}]) == '{x: 1, z: 3, y: 2}\n--- {}\n'
assert (
yaml.dump_all(documents=[data, {}], default_flow_style=None)
== "{x: 1, z: 3, y: 2}\n--- {}\n"
)


@pytest.mark.skipif(pyyaml_41, reason="requires PyYAML version != 4.1")
def test_dump_and_safe_dump_match():
mydict = {'x': 1, 'z': 2, 'y': 3}
mydict = {"x": 1, "z": 2, "y": 3}
# don't know if mydict is ordered in the implementation or not (but don't care)
assert yaml.dump(mydict) == yaml.safe_dump(mydict)


@pytest.mark.skipif(not pyyaml_41, reason="requires PyYAML version == 4.1")
def test_danger_dump_and_safe_dump_match():
mydict = {'x': 1, 'z': 2, 'y': 3}
assert yaml.danger_dump(mydict) == yaml.safe_dump(mydict)


def test_safe_dump_all():
assert yaml.safe_dump_all(documents=[data, {}]) == '{x: 1, z: 3, y: 2}\n--- {}\n'
assert (
yaml.safe_dump_all(documents=[data, {}], default_flow_style=None)
== "{x: 1, z: 3, y: 2}\n--- {}\n"
)


def test_load():
loaded = yaml.load('{x: 1, z: 3, y: 2}')
assert loaded == {'x': 1, 'z': 3, 'y': 2}
loaded = yaml.load("{x: 1, z: 3, y: 2}")
assert loaded == {"x": 1, "z": 3, "y": 2}


def test_safe_load():
loaded = yaml.safe_load('{x: 1, z: 3, y: 2}')
assert loaded == {'x': 1, 'z': 3, 'y': 2}
loaded = yaml.safe_load("{x: 1, z: 3, y: 2}")
assert loaded == {"x": 1, "z": 3, "y": 2}


def test_load_all():
gen = yaml.load_all('{x: 1, z: 3, y: 2}\n--- {}\n')
gen = yaml.load_all("{x: 1, z: 3, y: 2}\n--- {}\n")
assert isinstance(gen, GeneratorType)
ordered_data, empty_dict = gen
assert empty_dict == {}
assert ordered_data == data


@pytest.mark.skipif(sys.version_info >= (3, 7), reason="requires python3.6-")
@pytest.mark.skipif(_std_dict_is_order_preserving, reason="requires old dict impl")
def test_loads_to_ordered_dict():
loaded = yaml.load('{x: 1, z: 3, y: 2}')
loaded = yaml.load("{x: 1, z: 3, y: 2}")
assert isinstance(loaded, OrderedDict)


@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7+")
@pytest.mark.skipif(not _std_dict_is_order_preserving, reason="requires new dict impl")
def test_loads_to_std_dict():
loaded = yaml.load('{x: 1, z: 3, y: 2}')
loaded = yaml.load("{x: 1, z: 3, y: 2}")
assert not isinstance(loaded, OrderedDict)
assert isinstance(loaded, dict)


@pytest.mark.skipif(sys.version_info >= (3, 7), reason="requires python3.6-")
@pytest.mark.skipif(_std_dict_is_order_preserving, reason="requires old dict impl")
def test_safe_loads_to_ordered_dict():
loaded = yaml.safe_load('{x: 1, z: 3, y: 2}')
loaded = yaml.safe_load("{x: 1, z: 3, y: 2}")
assert isinstance(loaded, OrderedDict)


@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7+")
@pytest.mark.skipif(not _std_dict_is_order_preserving, reason="requires new dict impl")
def test_safe_loads_to_std_dict():
loaded = yaml.safe_load('{x: 1, z: 3, y: 2}')
loaded = yaml.safe_load("{x: 1, z: 3, y: 2}")
assert not isinstance(loaded, OrderedDict)
assert isinstance(loaded, dict)

Expand All @@ -96,24 +87,15 @@ class MyOrderedDict(OrderedDict):
pass


@pytest.mark.skipif(pyyaml_41, reason="requires PyYAML version != 4.1")
def test_subclass_dump_pyyaml3():
data = MyOrderedDict([('x', 1), ('y', 2)])
assert '!!python/object/apply:test_oyaml.MyOrderedDict' in yaml.dump(data)
with pytest.raises(yaml.pyyaml.representer.RepresenterError, match='cannot represent an object') as cm:
def test_subclass_dump():
data = MyOrderedDict([("x", 1), ("y", 2)])
assert "!!python/object/apply:test_oyaml.MyOrderedDict" in yaml.dump(data)
with pytest.raises(RepresenterError, match="cannot represent an object"):
yaml.safe_dump(data)


@pytest.mark.skipif(not pyyaml_41, reason="requires PyYAML version == 4.1")
def test_subclass_dump_pyyaml4():
data = MyOrderedDict([('x', 1), ('y', 2)])
assert '!!python/object/apply:test_oyaml.MyOrderedDict' in yaml.danger_dump(data)
with pytest.raises(yaml.pyyaml.representer.RepresenterError, match='cannot represent an object') as cm:
yaml.dump(data)


def test_anchors_and_references():
text = '''
text = """
defaults:
all: &all
product: foo
Expand All @@ -125,53 +107,46 @@ def test_anchors_and_references():
platform:
<<: *development
host: baz
'''
"""
expected_load = {
'defaults': {
'all': {
'product': 'foo',
},
'development': {
'product': 'foo',
'profile': 'bar',
},
"defaults": {
"all": {"product": "foo"},
"development": {"product": "foo", "profile": "bar"},
},
'development': {
'platform': {
'host': 'baz',
'product': 'foo',
'profile': 'bar',
},
"development": {
"platform": {"host": "baz", "product": "foo", "profile": "bar"}
},
}
assert yaml.load(text) == expected_load


def test_omap():
text = '''
text = """
Bestiary: !!omap
- aardvark: African pig-like ant eater. Ugly.
- anteater: South-American ant eater. Two species.
- anaconda: South-American constrictor snake. Scaly.
'''
"""
expected_load = {
'Bestiary': ([
('aardvark', 'African pig-like ant eater. Ugly.'),
('anteater', 'South-American ant eater. Two species.'),
('anaconda', 'South-American constrictor snake. Scaly.'),
])
"Bestiary": (
[
("aardvark", "African pig-like ant eater. Ugly."),
("anteater", "South-American ant eater. Two species."),
("anaconda", "South-American constrictor snake. Scaly."),
]
)
}
assert yaml.load(text) == expected_load


def test_omap_flow_style():
text = 'Numbers: !!omap [ one: 1, two: 2, three : 3 ]'
expected_load = {'Numbers': ([('one', 1), ('two', 2), ('three', 3)])}
text = "Numbers: !!omap [ one: 1, two: 2, three : 3 ]"
expected_load = {"Numbers": ([("one", 1), ("two", 2), ("three", 3)])}
assert yaml.load(text) == expected_load


def test_merge():
text = '''
text = """
- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
Expand All @@ -198,15 +173,15 @@ def test_merge():
<< : [ *BIG, *LEFT, *SMALL ]
x: 1
label: center/big
'''
"""
data = yaml.load(text)
assert len(data) == 8
center, left, big, small, map1, map2, map3, map4 = data
assert center == {'x': 1, 'y': 2}
assert left == {'x': 0, 'y': 2}
assert big == {'r': 10}
assert small == {'r': 1}
expected = {'x': 1, 'y': 2, 'r': 10, 'label': 'center/big'}
assert center == {"x": 1, "y": 2}
assert left == {"x": 0, "y": 2}
assert big == {"r": 10}
assert small == {"r": 1}
expected = {"x": 1, "y": 2, "r": 10, "label": "center/big"}
assert map1 == expected
assert map2 == expected
assert map3 == expected
Expand Down