mirror of
https://github.com/yaml/pyyaml.git
synced 2025-10-19 11:03:18 +00:00
Deprecate/warn usage of yaml.load(input)
The `load` and `load_all` methods will issue a warning when they are called without the 'Loader=' parameter. The warning will point to a URL that is always up to date with the latest information on the usage of `load`. There are several ways to stop the warning: * Use `full_load(input)` - sugar for `yaml.load(input, FullLoader)` * FullLoader is the new safe but complete loader class * Use `safe_load(input)` - sugar for `yaml.load(input, SafeLoader)` * Make sure your input YAML consists of the 'safe' subset * Use `unsafe_load(input)` - sugar for `yaml.load(input, UnsafeLoader)` * Make sure your input YAML consists of the 'safe' subset * Use `yaml.load(input, Loader=yaml.<loader>)` * Or shorter `yaml.load(input, yaml.<loader>)` * Where '<loader>' can be: * FullLoader - safe, complete Python YAML loading * SafeLoader - safe, partial Python YAML loading * UnsafeLoader - more explicit name for the old, unsafe 'Loader' class * yaml.warnings({'YAMLLoadWarning': False}) * Use this when you use third party modules that use `yaml.load(input)` * Only do this if input is trusted The above `load()` expressions all have `load_all()` counterparts. You can get the original unsafe behavior with: * `yaml.unsafe_load(input)` * `yaml.load(input, Loader=yaml.UnsafeLoader)` In a future release, `yaml.load(input)` will raise an exception. The new loader called FullLoader is almost entirely complete as Loader/UnsafeLoader but it does it avoids all known code execution paths. It is the preferred YAML loader, and the current default for `yaml.load(input)` when you get the warning. Here are some of the exploits that can be triggered with UnsafeLoader but not with FullLoader: ``` python -c 'import os, yaml; yaml.full_load("!!python/object/new:os.system [echo EXPLOIT!]")'` python -c 'import yaml; print yaml.full_load("!!python/object/new:abs [-5]")' python -c 'import yaml; yaml.full_load("!!python/object/new:eval [exit(5)]")' ; echo $? python -c 'import yaml; yaml.full_load("!!python/object/new:exit [5]")' ; echo $?
This commit is contained in:
parent
d13a3d0f96
commit
0cedb2a069
8 changed files with 228 additions and 64 deletions
|
@ -8,7 +8,7 @@ from nodes import *
|
|||
from loader import *
|
||||
from dumper import *
|
||||
|
||||
__version__ = '4.1'
|
||||
__version__ = '3.13'
|
||||
|
||||
try:
|
||||
from cyaml import *
|
||||
|
@ -16,6 +16,45 @@ try:
|
|||
except ImportError:
|
||||
__with_libyaml__ = False
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Warnings control
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# 'Global' warnings state:
|
||||
_warnings_enabled = {
|
||||
'YAMLLoadWarning': True,
|
||||
}
|
||||
|
||||
# Get or set global warnings' state
|
||||
def warnings(settings=None):
|
||||
if settings is None:
|
||||
return _warnings_enabled
|
||||
|
||||
if type(settings) is dict:
|
||||
for key in settings:
|
||||
if key in _warnings_enabled:
|
||||
_warnings_enabled[key] = settings[key]
|
||||
|
||||
# Warn when load() is called without Loader=...
|
||||
class YAMLLoadWarning(RuntimeWarning):
|
||||
pass
|
||||
|
||||
def load_warning(method):
|
||||
if _warnings_enabled['YAMLLoadWarning'] is False:
|
||||
return
|
||||
|
||||
import warnings
|
||||
|
||||
message = (
|
||||
"calling yaml.%s() without Loader=... is deprecated, as the "
|
||||
"default Loader is unsafe. Please read "
|
||||
"https://msg.pyyaml.org/load for full details."
|
||||
) % method
|
||||
|
||||
warnings.warn(message, YAMLLoadWarning, stacklevel=3)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def scan(stream, Loader=Loader):
|
||||
"""
|
||||
Scan a YAML stream and produce scanning tokens.
|
||||
|
@ -61,22 +100,30 @@ def compose_all(stream, Loader=Loader):
|
|||
finally:
|
||||
loader.dispose()
|
||||
|
||||
def load(stream, Loader=Loader):
|
||||
def load(stream, Loader=None):
|
||||
"""
|
||||
Parse the first YAML document in a stream
|
||||
and produce the corresponding Python object.
|
||||
"""
|
||||
if Loader is None:
|
||||
load_warning('load')
|
||||
Loader = FullLoader
|
||||
|
||||
loader = Loader(stream)
|
||||
try:
|
||||
return loader.get_single_data()
|
||||
finally:
|
||||
loader.dispose()
|
||||
|
||||
def load_all(stream, Loader=Loader):
|
||||
def load_all(stream, Loader=None):
|
||||
"""
|
||||
Parse all YAML documents in a stream
|
||||
and produce corresponding Python objects.
|
||||
"""
|
||||
if Loader is None:
|
||||
load_warning('load_all')
|
||||
Loader = FullLoader
|
||||
|
||||
loader = Loader(stream)
|
||||
try:
|
||||
while loader.check_data():
|
||||
|
@ -84,11 +131,33 @@ def load_all(stream, Loader=Loader):
|
|||
finally:
|
||||
loader.dispose()
|
||||
|
||||
def full_load(stream):
|
||||
"""
|
||||
Parse the first YAML document in a stream
|
||||
and produce the corresponding Python object.
|
||||
|
||||
Resolve all tags except those known to be
|
||||
unsafe on untrusted input.
|
||||
"""
|
||||
return load(stream, FullLoader)
|
||||
|
||||
def full_load_all(stream):
|
||||
"""
|
||||
Parse all YAML documents in a stream
|
||||
and produce corresponding Python objects.
|
||||
|
||||
Resolve all tags except those known to be
|
||||
unsafe on untrusted input.
|
||||
"""
|
||||
return load_all(stream, FullLoader)
|
||||
|
||||
def safe_load(stream):
|
||||
"""
|
||||
Parse the first YAML document in a stream
|
||||
and produce the corresponding Python object.
|
||||
Resolve only basic YAML tags.
|
||||
|
||||
Resolve only basic YAML tags. This is known
|
||||
to be safe for untrusted input.
|
||||
"""
|
||||
return load(stream, SafeLoader)
|
||||
|
||||
|
@ -96,10 +165,32 @@ def safe_load_all(stream):
|
|||
"""
|
||||
Parse all YAML documents in a stream
|
||||
and produce corresponding Python objects.
|
||||
Resolve only basic YAML tags.
|
||||
|
||||
Resolve only basic YAML tags. This is known
|
||||
to be safe for untrusted input.
|
||||
"""
|
||||
return load_all(stream, SafeLoader)
|
||||
|
||||
def unsafe_load(stream):
|
||||
"""
|
||||
Parse the first YAML document in a stream
|
||||
and produce the corresponding Python object.
|
||||
|
||||
Resolve all tags, even those known to be
|
||||
unsafe on untrusted input.
|
||||
"""
|
||||
return load(stream, UnsafeLoader)
|
||||
|
||||
def unsafe_load_all(stream):
|
||||
"""
|
||||
Parse all YAML documents in a stream
|
||||
and produce corresponding Python objects.
|
||||
|
||||
Resolve all tags, even those known to be
|
||||
unsafe on untrusted input.
|
||||
"""
|
||||
return load_all(stream, UnsafeLoader)
|
||||
|
||||
def emit(events, stream=None, Dumper=Dumper,
|
||||
canonical=None, indent=None, width=None,
|
||||
allow_unicode=None, line_break=None):
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
|
||||
__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
|
||||
'ConstructorError']
|
||||
__all__ = [
|
||||
'BaseConstructor',
|
||||
'SafeConstructor',
|
||||
'FullConstructor',
|
||||
'UnsafeConstructor',
|
||||
'Constructor',
|
||||
'ConstructorError'
|
||||
]
|
||||
|
||||
from error import *
|
||||
from nodes import *
|
||||
|
@ -464,7 +470,7 @@ SafeConstructor.add_constructor(
|
|||
SafeConstructor.add_constructor(None,
|
||||
SafeConstructor.construct_undefined)
|
||||
|
||||
class Constructor(SafeConstructor):
|
||||
class FullConstructor(SafeConstructor):
|
||||
|
||||
def construct_python_str(self, node):
|
||||
return self.construct_scalar(node).encode('utf-8')
|
||||
|
@ -481,18 +487,22 @@ class Constructor(SafeConstructor):
|
|||
def construct_python_tuple(self, node):
|
||||
return tuple(self.construct_sequence(node))
|
||||
|
||||
def find_python_module(self, name, mark):
|
||||
def find_python_module(self, name, mark, unsafe=False):
|
||||
if not name:
|
||||
raise ConstructorError("while constructing a Python module", mark,
|
||||
"expected non-empty name appended to the tag", mark)
|
||||
try:
|
||||
__import__(name)
|
||||
except ImportError, exc:
|
||||
if unsafe:
|
||||
try:
|
||||
__import__(name)
|
||||
except ImportError, exc:
|
||||
raise ConstructorError("while constructing a Python module", mark,
|
||||
"cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
|
||||
if not name in sys.modules:
|
||||
raise ConstructorError("while constructing a Python module", mark,
|
||||
"cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
|
||||
"module %r is not imported" % name.encode('utf-8'), mark)
|
||||
return sys.modules[name]
|
||||
|
||||
def find_python_name(self, name, mark):
|
||||
def find_python_name(self, name, mark, unsafe=False):
|
||||
if not name:
|
||||
raise ConstructorError("while constructing a Python object", mark,
|
||||
"expected non-empty name appended to the tag", mark)
|
||||
|
@ -501,11 +511,15 @@ class Constructor(SafeConstructor):
|
|||
else:
|
||||
module_name = '__builtin__'
|
||||
object_name = name
|
||||
try:
|
||||
__import__(module_name)
|
||||
except ImportError, exc:
|
||||
if unsafe:
|
||||
try:
|
||||
__import__(module_name)
|
||||
except ImportError, exc:
|
||||
raise ConstructorError("while constructing a Python object", mark,
|
||||
"cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
|
||||
if not module_name in sys.modules:
|
||||
raise ConstructorError("while constructing a Python object", mark,
|
||||
"cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
|
||||
"module %r is not imported" % module_name.encode('utf-8'), mark)
|
||||
module = sys.modules[module_name]
|
||||
if not hasattr(module, object_name):
|
||||
raise ConstructorError("while constructing a Python object", mark,
|
||||
|
@ -532,12 +546,16 @@ class Constructor(SafeConstructor):
|
|||
class classobj: pass
|
||||
|
||||
def make_python_instance(self, suffix, node,
|
||||
args=None, kwds=None, newobj=False):
|
||||
args=None, kwds=None, newobj=False, unsafe=False):
|
||||
if not args:
|
||||
args = []
|
||||
if not kwds:
|
||||
kwds = {}
|
||||
cls = self.find_python_name(suffix, node.start_mark)
|
||||
if not (unsafe or isinstance(cls, type) or isinstance(cls, type(self.classobj))):
|
||||
raise ConstructorError("while constructing a Python instance", node.start_mark,
|
||||
"expected a class, but found %r" % type(cls),
|
||||
node.start_mark)
|
||||
if newobj and isinstance(cls, type(self.classobj)) \
|
||||
and not args and not kwds:
|
||||
instance = self.classobj()
|
||||
|
@ -609,67 +627,83 @@ class Constructor(SafeConstructor):
|
|||
def construct_python_object_new(self, suffix, node):
|
||||
return self.construct_python_object_apply(suffix, node, newobj=True)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/none',
|
||||
Constructor.construct_yaml_null)
|
||||
FullConstructor.construct_yaml_null)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/bool',
|
||||
Constructor.construct_yaml_bool)
|
||||
FullConstructor.construct_yaml_bool)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/str',
|
||||
Constructor.construct_python_str)
|
||||
FullConstructor.construct_python_str)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/unicode',
|
||||
Constructor.construct_python_unicode)
|
||||
FullConstructor.construct_python_unicode)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/int',
|
||||
Constructor.construct_yaml_int)
|
||||
FullConstructor.construct_yaml_int)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/long',
|
||||
Constructor.construct_python_long)
|
||||
FullConstructor.construct_python_long)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/float',
|
||||
Constructor.construct_yaml_float)
|
||||
FullConstructor.construct_yaml_float)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/complex',
|
||||
Constructor.construct_python_complex)
|
||||
FullConstructor.construct_python_complex)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/list',
|
||||
Constructor.construct_yaml_seq)
|
||||
FullConstructor.construct_yaml_seq)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/tuple',
|
||||
Constructor.construct_python_tuple)
|
||||
FullConstructor.construct_python_tuple)
|
||||
|
||||
Constructor.add_constructor(
|
||||
FullConstructor.add_constructor(
|
||||
u'tag:yaml.org,2002:python/dict',
|
||||
Constructor.construct_yaml_map)
|
||||
FullConstructor.construct_yaml_map)
|
||||
|
||||
Constructor.add_multi_constructor(
|
||||
FullConstructor.add_multi_constructor(
|
||||
u'tag:yaml.org,2002:python/name:',
|
||||
Constructor.construct_python_name)
|
||||
FullConstructor.construct_python_name)
|
||||
|
||||
Constructor.add_multi_constructor(
|
||||
FullConstructor.add_multi_constructor(
|
||||
u'tag:yaml.org,2002:python/module:',
|
||||
Constructor.construct_python_module)
|
||||
FullConstructor.construct_python_module)
|
||||
|
||||
Constructor.add_multi_constructor(
|
||||
FullConstructor.add_multi_constructor(
|
||||
u'tag:yaml.org,2002:python/object:',
|
||||
Constructor.construct_python_object)
|
||||
FullConstructor.construct_python_object)
|
||||
|
||||
Constructor.add_multi_constructor(
|
||||
FullConstructor.add_multi_constructor(
|
||||
u'tag:yaml.org,2002:python/object/apply:',
|
||||
Constructor.construct_python_object_apply)
|
||||
FullConstructor.construct_python_object_apply)
|
||||
|
||||
Constructor.add_multi_constructor(
|
||||
FullConstructor.add_multi_constructor(
|
||||
u'tag:yaml.org,2002:python/object/new:',
|
||||
Constructor.construct_python_object_new)
|
||||
FullConstructor.construct_python_object_new)
|
||||
|
||||
class UnsafeConstructor(FullConstructor):
|
||||
|
||||
def find_python_module(self, name, mark):
|
||||
return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)
|
||||
|
||||
def find_python_name(self, name, mark):
|
||||
return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
|
||||
|
||||
def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
|
||||
return super(UnsafeConstructor, self).make_python_instance(
|
||||
suffix, node, args, kwds, newobj, unsafe=True)
|
||||
|
||||
# Constructor is same as UnsafeConstructor. Need to leave this in place in case
|
||||
# people have extended it directly.
|
||||
class Constructor(UnsafeConstructor):
|
||||
pass
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
|
||||
'CBaseDumper', 'CSafeDumper', 'CDumper']
|
||||
__all__ = [
|
||||
'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
|
||||
'CBaseDumper', 'CSafeDumper', 'CDumper'
|
||||
]
|
||||
|
||||
from _yaml import CParser, CEmitter
|
||||
|
||||
|
@ -25,6 +27,20 @@ class CSafeLoader(CParser, SafeConstructor, Resolver):
|
|||
SafeConstructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
||||
class CFullLoader(CParser, FullConstructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
CParser.__init__(self, stream)
|
||||
FullConstructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
||||
class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
CParser.__init__(self, stream)
|
||||
UnsafeConstructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
||||
class CLoader(CParser, Constructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
__all__ = ['BaseLoader', 'SafeLoader', 'Loader']
|
||||
__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader']
|
||||
|
||||
from reader import *
|
||||
from scanner import *
|
||||
|
@ -18,6 +18,16 @@ class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolve
|
|||
BaseConstructor.__init__(self)
|
||||
BaseResolver.__init__(self)
|
||||
|
||||
class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
Reader.__init__(self, stream)
|
||||
Scanner.__init__(self)
|
||||
Parser.__init__(self)
|
||||
Composer.__init__(self)
|
||||
FullConstructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
||||
class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
|
@ -38,3 +48,16 @@ class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
|
|||
Constructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
||||
# UnsafeLoader is the same as Loader (which is and was always unsafe on
|
||||
# untrusted input). Use of either Loader or UnsafeLoader should be rare, since
|
||||
# FullLoad should be able to load almost all YAML safely. Loader is left intact
|
||||
# to ensure backwards compatability.
|
||||
class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
|
||||
|
||||
def __init__(self, stream):
|
||||
Reader.__init__(self, stream)
|
||||
Scanner.__init__(self)
|
||||
Parser.__init__(self)
|
||||
Composer.__init__(self)
|
||||
Constructor.__init__(self)
|
||||
Resolver.__init__(self)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
|||
|
||||
NAME = 'PyYAML'
|
||||
VERSION = '4.1'
|
||||
VERSION = '3.13'
|
||||
DESCRIPTION = "YAML parser and emitter for Python"
|
||||
LONG_DESCRIPTION = """\
|
||||
YAML is a data serialization format designed for human readability
|
||||
|
|
|
@ -3,7 +3,7 @@ import yaml, test_emitter
|
|||
|
||||
def test_loader_error(error_filename, verbose=False):
|
||||
try:
|
||||
list(yaml.load_all(open(error_filename, 'rb')))
|
||||
list(yaml.load_all(open(error_filename, 'rb'), yaml.FullLoader))
|
||||
except yaml.YAMLError, exc:
|
||||
if verbose:
|
||||
print "%s:" % exc.__class__.__name__, exc
|
||||
|
@ -14,7 +14,7 @@ test_loader_error.unittest = ['.loader-error']
|
|||
|
||||
def test_loader_error_string(error_filename, verbose=False):
|
||||
try:
|
||||
list(yaml.load_all(open(error_filename, 'rb').read()))
|
||||
list(yaml.load_all(open(error_filename, 'rb').read(), yaml.FullLoader))
|
||||
except yaml.YAMLError, exc:
|
||||
if verbose:
|
||||
print "%s:" % exc.__class__.__name__, exc
|
||||
|
@ -25,7 +25,7 @@ test_loader_error_string.unittest = ['.loader-error']
|
|||
|
||||
def test_loader_error_single(error_filename, verbose=False):
|
||||
try:
|
||||
yaml.load(open(error_filename, 'rb').read())
|
||||
yaml.load(open(error_filename, 'rb').read(), yaml.FullLoader)
|
||||
except yaml.YAMLError, exc:
|
||||
if verbose:
|
||||
print "%s:" % exc.__class__.__name__, exc
|
||||
|
|
|
@ -17,7 +17,7 @@ def _unicode_open(file, encoding, errors='strict'):
|
|||
def test_unicode_input(unicode_filename, verbose=False):
|
||||
data = open(unicode_filename, 'rb').read().decode('utf-8')
|
||||
value = ' '.join(data.split())
|
||||
output = yaml.load(_unicode_open(StringIO.StringIO(data.encode('utf-8')), 'utf-8'))
|
||||
output = yaml.full_load(_unicode_open(StringIO.StringIO(data.encode('utf-8')), 'utf-8'))
|
||||
assert output == value, (output, value)
|
||||
for input in [data, data.encode('utf-8'),
|
||||
codecs.BOM_UTF8+data.encode('utf-8'),
|
||||
|
@ -25,9 +25,9 @@ def test_unicode_input(unicode_filename, verbose=False):
|
|||
codecs.BOM_UTF16_LE+data.encode('utf-16-le')]:
|
||||
if verbose:
|
||||
print "INPUT:", repr(input[:10]), "..."
|
||||
output = yaml.load(input)
|
||||
output = yaml.full_load(input)
|
||||
assert output == value, (output, value)
|
||||
output = yaml.load(StringIO.StringIO(input))
|
||||
output = yaml.full_load(StringIO.StringIO(input))
|
||||
assert output == value, (output, value)
|
||||
|
||||
test_unicode_input.unittest = ['.unicode']
|
||||
|
@ -40,14 +40,14 @@ def test_unicode_input_errors(unicode_filename, verbose=False):
|
|||
codecs.BOM_UTF8+data.encode('utf-16-le')]:
|
||||
|
||||
try:
|
||||
yaml.load(input)
|
||||
yaml.full_load(input)
|
||||
except yaml.YAMLError, exc:
|
||||
if verbose:
|
||||
print exc
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
try:
|
||||
yaml.load(StringIO.StringIO(input))
|
||||
yaml.full_load(StringIO.StringIO(input))
|
||||
except yaml.YAMLError, exc:
|
||||
if verbose:
|
||||
print exc
|
||||
|
|
|
@ -30,7 +30,7 @@ def test_recursive(recursive_filename, verbose=False):
|
|||
output2 = None
|
||||
try:
|
||||
output1 = yaml.dump(value1)
|
||||
value2 = yaml.load(output1)
|
||||
value2 = yaml.load(output1, yaml.FullLoader)
|
||||
output2 = yaml.dump(value2)
|
||||
assert output1 == output2, (output1, output2)
|
||||
finally:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue