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 overriding anchors #394

Open
wants to merge 3 commits into
base: release/6.0
Choose a base branch
from
Open
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
24 changes: 12 additions & 12 deletions lib/yaml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,68 +26,68 @@ def warnings(settings=None):
return {}

#------------------------------------------------------------------------------
def scan(stream, Loader=Loader):
def scan(stream, Loader=Loader, reuse_anchors=False):
"""
Scan a YAML stream and produce scanning tokens.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_token():
yield loader.get_token()
finally:
loader.dispose()

def parse(stream, Loader=Loader):
def parse(stream, Loader=Loader, reuse_anchors=False):
"""
Parse a YAML stream and produce parsing events.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_event():
yield loader.get_event()
finally:
loader.dispose()

def compose(stream, Loader=Loader):
def compose(stream, Loader=Loader, reuse_anchors=False):
"""
Parse the first YAML document in a stream
and produce the corresponding representation tree.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
return loader.get_single_node()
finally:
loader.dispose()

def compose_all(stream, Loader=Loader):
def compose_all(stream, Loader=Loader, reuse_anchors=False):
"""
Parse all YAML documents in a stream
and produce corresponding representation trees.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_node():
yield loader.get_node()
finally:
loader.dispose()

def load(stream, Loader):
def load(stream, Loader, reuse_anchors=False):
"""
Parse the first YAML document in a stream
and produce the corresponding Python object.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
return loader.get_single_data()
finally:
loader.dispose()

def load_all(stream, Loader):
def load_all(stream, Loader, reuse_anchors=False):
"""
Parse all YAML documents in a stream
and produce corresponding Python objects.
"""
loader = Loader(stream)
loader = Loader(stream, reuse_anchors=reuse_anchors)
try:
while loader.check_data():
yield loader.get_data()
Expand Down
5 changes: 3 additions & 2 deletions lib/yaml/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class ComposerError(MarkedYAMLError):

class Composer:

def __init__(self):
def __init__(self, reuse_anchors=False):
self.anchors = {}
self.reuse_anchors=reuse_anchors

def check_node(self):
# Drop the STREAM-START event.
Expand Down Expand Up @@ -71,7 +72,7 @@ def compose_node(self, parent, index):
event = self.peek_event()
anchor = event.anchor
if anchor is not None:
if anchor in self.anchors:
if anchor in self.anchors and not self.reuse_anchors:
raise ComposerError("found duplicate anchor %r; first occurrence"
% anchor, self.anchors[anchor].start_mark,
"second occurrence", event.start_mark)
Expand Down
20 changes: 10 additions & 10 deletions lib/yaml/cyaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,36 @@

class CBaseLoader(CParser, BaseConstructor, BaseResolver):

def __init__(self, stream):
CParser.__init__(self, stream)
def __init__(self, stream, reuse_anchors=False):
CParser.__init__(self, stream, reuse_anchors=reuse_anchors)
BaseConstructor.__init__(self)
BaseResolver.__init__(self)

class CSafeLoader(CParser, SafeConstructor, Resolver):

def __init__(self, stream):
CParser.__init__(self, stream)
def __init__(self, stream, reuse_anchors=False):
CParser.__init__(self, stream, reuse_anchors=reuse_anchors)
SafeConstructor.__init__(self)
Resolver.__init__(self)

class CFullLoader(CParser, FullConstructor, Resolver):

def __init__(self, stream):
CParser.__init__(self, stream)
def __init__(self, stream, reuse_anchors=False):
CParser.__init__(self, stream, reuse_anchors=reuse_anchors)
FullConstructor.__init__(self)
Resolver.__init__(self)

class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):

def __init__(self, stream):
CParser.__init__(self, stream)
def __init__(self, stream, reuse_anchors=False):
CParser.__init__(self, stream, reuse_anchors=reuse_anchors)
UnsafeConstructor.__init__(self)
Resolver.__init__(self)

class CLoader(CParser, Constructor, Resolver):

def __init__(self, stream):
CParser.__init__(self, stream)
def __init__(self, stream, reuse_anchors=False):
CParser.__init__(self, stream, reuse_anchors=reuse_anchors)
Constructor.__init__(self)
Resolver.__init__(self)

Expand Down
20 changes: 10 additions & 10 deletions lib/yaml/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,41 @@

class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
BaseConstructor.__init__(self)
BaseResolver.__init__(self)

class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
FullConstructor.__init__(self)
Resolver.__init__(self)

class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
SafeConstructor.__init__(self)
Resolver.__init__(self)

class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
Constructor.__init__(self)
Resolver.__init__(self)

Expand All @@ -54,10 +54,10 @@ def __init__(self, stream):
# to ensure backwards compatibility.
class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Composer.__init__(self, reuse_anchors=reuse_anchors)
Constructor.__init__(self)
Resolver.__init__(self)
1 change: 1 addition & 0 deletions tests/data/override-anchor-1.reuse-anchors-code
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(['bar', 'bar', 'baz', 'baz'])
4 changes: 4 additions & 0 deletions tests/data/override-anchor-1.reuse-anchors-data
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- &foo bar
- *foo
- &foo baz
- *foo
1 change: 1 addition & 0 deletions tests/data/override-anchor-2.reuse-anchors-code
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1, 2, 3, 4]
4 changes: 2 additions & 2 deletions tests/lib/canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,12 @@ def peek_event(self):
class CanonicalLoader(CanonicalScanner, CanonicalParser,
yaml.composer.Composer, yaml.constructor.Constructor, yaml.resolver.Resolver):

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
if hasattr(stream, 'read'):
stream = stream.read()
CanonicalScanner.__init__(self, stream)
CanonicalParser.__init__(self)
yaml.composer.Composer.__init__(self)
yaml.composer.Composer.__init__(self, reuse_anchors=reuse_anchors)
yaml.constructor.Constructor.__init__(self)
yaml.resolver.Resolver.__init__(self)

Expand Down
28 changes: 28 additions & 0 deletions tests/lib/test_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,34 @@ def test_subclass_blacklist_types(data_filename, verbose=False):

test_subclass_blacklist_types.unittest = ['.subclass_blacklist']

def test_reuse_anchors(data_filename, code_filename, verbose=False):
try:
with open(data_filename, 'rb') as file:
native1 = list(yaml.load_all(file, Loader=yaml.SafeLoader, reuse_anchors=True))
if len(native1) == 1:
native1 = native1[0]
with open(code_filename, 'rb') as file:
native2 = _load_code(file.read())
try:
if native1 == native2:
return
except TypeError:
pass
if verbose:
print("SERIALIZED NATIVE1:")
print(_serialize_value(native1))
print("SERIALIZED NATIVE2:")
print(_serialize_value(native2))
assert _serialize_value(native1) == _serialize_value(native2), (native1, native2)
finally:
if verbose:
print("NATIVE1:")
pprint.pprint(native1)
print("NATIVE2:")
pprint.pprint(native2)

test_reuse_anchors.unittest = ['.reuse-anchors-data', '.reuse-anchors-code']

if __name__ == '__main__':
import sys, test_constructor
sys.modules['test_constructor'] = sys.modules['__main__']
Expand Down
24 changes: 12 additions & 12 deletions tests/lib/test_yaml_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ def new_parse(stream, Loader=yaml.CLoader):
return old_parse(stream, Loader)

old_compose = yaml.compose
def new_compose(stream, Loader=yaml.CLoader):
return old_compose(stream, Loader)
def new_compose(stream, Loader=yaml.CLoader, reuse_anchors=False):
return old_compose(stream, Loader, reuse_anchors=reuse_anchors)

old_compose_all = yaml.compose_all
def new_compose_all(stream, Loader=yaml.CLoader):
return old_compose_all(stream, Loader)
def new_compose_all(stream, Loader=yaml.CLoader, reuse_anchors=False):
return old_compose_all(stream, Loader, reuse_anchors=reuse_anchors)

old_load = yaml.load
def new_load(stream, Loader=yaml.CLoader):
return old_load(stream, Loader)
def new_load(stream, Loader=yaml.CLoader, reuse_anchors=False):
return old_load(stream, Loader, reuse_anchors=reuse_anchors)

old_load_all = yaml.load_all
def new_load_all(stream, Loader=yaml.CLoader):
return old_load_all(stream, Loader)
def new_load_all(stream, Loader=yaml.CLoader, reuse_anchors=False):
return old_load_all(stream, Loader, reuse_anchors=reuse_anchors)

old_safe_load = yaml.safe_load
def new_safe_load(stream):
return old_load(stream, yaml.CSafeLoader)
def new_safe_load(stream, reuse_anchors=False):
return old_load(stream, yaml.CSafeLoader, reuse_anchors=reuse_anchors)

old_safe_load_all = yaml.safe_load_all
def new_safe_load_all(stream):
return old_load_all(stream, yaml.CSafeLoader)
def new_safe_load_all(stream, reuse_anchors=False):
return old_load_all(stream, yaml.CSafeLoader, reuse_anchors=reuse_anchors)

old_emit = yaml.emit
def new_emit(events, stream=None, Dumper=yaml.CDumper, **kwds):
Expand Down
5 changes: 3 additions & 2 deletions yaml/_yaml.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ cdef class CParser:
cdef int stream_cache_pos
cdef int unicode_source

def __init__(self, stream):
def __init__(self, stream, reuse_anchors=False):
self.reuse_anchors=reuse_anchors
cdef is_readable
if yaml_parser_initialize(&self.parser) == 0:
raise MemoryError
Expand Down Expand Up @@ -714,7 +715,7 @@ cdef class CParser:
and self.parsed_event.data.mapping_start.anchor != NULL:
anchor = PyUnicode_FromYamlString(self.parsed_event.data.mapping_start.anchor)
if anchor is not None:
if anchor in self.anchors:
if anchor in self.anchors and not self.reuse_anchors:
mark = Mark(self.stream_name,
self.parsed_event.start_mark.index,
self.parsed_event.start_mark.line,
Expand Down