summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthon van der Neut <anthon@mnt.org>2017-06-04 14:27:02 +0200
committerAnthon van der Neut <anthon@mnt.org>2017-06-04 14:27:02 +0200
commita0ff58af5242fddc39236168b18aafb7c8b324da (patch)
treeb7ba0da53b720dde7be6e3b32ad927456c6f5dcf
parent6ba66a44af41d072f5ceddfcdf2c21611c2a7cd0 (diff)
downloadruamel.yaml-a0ff58af5242fddc39236168b18aafb7c8b324da.tar.gz
initial version with YAML class0.15.0
Some new tests are still required
-rw-r--r--CHANGES16
-rw-r--r--README.rst17
-rw-r--r--__init__.py4
-rw-r--r--_doc/api.rst123
-rw-r--r--_doc/basicuse.rst56
-rw-r--r--_doc/detail.rst6
-rw-r--r--_doc/index.rst7
-rw-r--r--_test/data/duplicate-anchor-1.loader-warning (renamed from _test/data/duplicate-anchor-1.loader-error)0
-rw-r--r--_test/data/duplicate-anchor-2.loader-warning (renamed from _test/data/duplicate-anchor-2.loader-error)0
-rw-r--r--_test/lib/test_errors.py2
-rw-r--r--_test/test_z_data.py5
-rw-r--r--composer.py6
-rw-r--r--constructor.py6
-rw-r--r--cyaml.py3
-rw-r--r--emitter.py50
-rw-r--r--error.py1
-rw-r--r--main.py380
-rw-r--r--parser.py6
-rw-r--r--reader.py42
-rw-r--r--representer.py2
-rw-r--r--resolver.py11
-rw-r--r--scanner.py4
-rw-r--r--serializer.py4
23 files changed, 699 insertions, 52 deletions
diff --git a/CHANGES b/CHANGES
index 26efc3e..d6d49d4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,19 @@
+[0, 15, 0]: 2017-06-04
+ - it is no allowed to pass in a ``pathlib.Path`` as "stream" parameter to all
+ load/dump functions
+ - passing in a non-supported object (e.g. a string) as "stream" will result in a
+ much more meaningful YAMLStreamError.
+ - assigning a normal string value to an existing CommentedMap key or CommentedSeq
+ element will result in a value cast to the previous value's type if possible.
+
+[0, 15, 0]: 2017-06-04
+ - it is no allowed to pass in a ``pathlib.Path`` as "stream" parameter to all
+ load/dump functions
+ - passing in a non-supported object (e.g. a string) as "stream" will result in a
+ much more meaningful YAMLStreamError.
+ - assigning a normal string value to an existing CommentedMap key or CommentedSeq
+ element will result in a value cast to the previous value's type if possible.
+
[0, 14, 12]: 2017-05-14
- fix for issue 119, deepcopy not returning subclasses (reported and PR by
Constantine Evans <cevans@evanslabs.org>)
diff --git a/README.rst b/README.rst
index e97288e..a5aa8d9 100644
--- a/README.rst
+++ b/README.rst
@@ -4,10 +4,24 @@ ruamel.yaml
``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python.
+Starting with version 0.15.0 the way YAML files are loaded and dumped
+is changing. See the API doc for details. Currently existing
+functionality will throw a warning before being changed/removed.
+**For production systems you should pin the version being used with
+``ruamel.yaml<=0.15``**. There might be bug fixes in the 0.14 series,
+but new functionality is likely only to be available via the new API.
+
+If your package uses ``ruamel.yaml`` and is not listed on PyPI, drop
+me an email, preferably with some infomormation on how you use the
+package (or a link to bitbucket/github) and I'll keep you informed
+when the status of the API is stable enough to make the transition.
+
* `Overview <http://yaml.readthedocs.org/en/latest/overview.html>`_
* `Installing <http://yaml.readthedocs.org/en/latest/install.html>`_
+* `Basic Usage <http://yaml.readthedocs.org/en/latest/basicuse.html>`_
* `Details <http://yaml.readthedocs.org/en/latest/detail.html>`_
* `Examples <http://yaml.readthedocs.org/en/latest/example.html>`_
+* `API <http://yaml.readthedocs.org/en/latest/api.html>`_
* `Differences with PyYAML <http://yaml.readthedocs.org/en/latest/pyyaml.html>`_
.. image:: https://readthedocs.org/projects/yaml/badge/?version=stable
@@ -18,13 +32,14 @@ ChangeLog
.. should insert NEXT: at the beginning of line for next key
-NEXT:
+0.15.0 (2017-06-04):
- it is no allowed to pass in a ``pathlib.Path`` as "stream" parameter to all
load/dump functions
- passing in a non-supported object (e.g. a string) as "stream" will result in a
much more meaningful YAMLStreamError.
- assigning a normal string value to an existing CommentedMap key or CommentedSeq
element will result in a value cast to the previous value's type if possible.
+ - added ``YAML`` class for new API
0.14.12 (2017-05-14):
- fix for issue 119, deepcopy not returning subclasses (reported and PR by
diff --git a/__init__.py b/__init__.py
index e33f19e..14cf4e2 100644
--- a/__init__.py
+++ b/__init__.py
@@ -11,8 +11,8 @@ if False: # MYPY
_package_data = dict(
full_package_name='ruamel.yaml',
- version_info=(0, 14, 13, 'dev'),
- __version__='0.14.13.dev',
+ version_info=(0, 15, 0),
+ __version__='0.15.0',
author='Anthon van der Neut',
author_email='a.van.der.neut@ruamel.eu',
description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order', # NOQA
diff --git a/_doc/api.rst b/_doc/api.rst
new file mode 100644
index 0000000..61d74a1
--- /dev/null
+++ b/_doc/api.rst
@@ -0,0 +1,123 @@
+
+
+Departure from previous API
+---------------------------
+
+With version 0.15.0 `ruamel.yaml` starts to depart from the previous (PyYAML) way
+of loading and dumping. During a transition period the original
+`load()` and `dump()` in its various formats will still be supported,
+but this is not guaranteed to be so with the transition to 1.0.
+
+At the latest with 1.0, but possible earlier transition error and
+warning messages will be issued, so any packages depending on
+ruamel.yaml should pin the version with which they are testing.
+
+Up to 0.15.0, the loaders (``load()``, ``safe_load()``,
+``round_trip_load()``, ``load_all``, etc.) took, apart from the input
+stream, a ``version`` argument to allow downgrading to YAML 1.1,
+sometimes needed for
+documents without directive. When round-tripping, there was an option to
+preserve quotes.
+
+Up to 0.15.0, the dumpers (``dump()``, ``safe_dump``,
+``round_trip_dump()``, ``dump_all()``, etc.) had a plethora of
+arguments, some inhereted from ``PyYAML``, some added in
+``ruamel.yaml``. The only required argument is the ``data`` to be
+dumped. If the stream argument is not provided to the dumper, then a
+string representation is build up in memory and returned to the
+caller.
+
+Starting with 0.15.0 ``load()`` and ``dump()`` are methods on a
+``YAML`` instance and only take the stream,
+resp. the data and stram argument. All other parameters are set on the instance
+of ``YAML`` before calling ``load()`` or ``dump()``
+
+Before 0.15.0::
+
+ from pathlib import Path
+ from ruamel import yaml
+
+ data = yaml.safe_load("abc: 1")
+ out = path('/tmp/out.yaml')
+ with out.open('w') as fp:
+ yaml.safe_dump(data, fp, default_flow_style=False)
+
+after:
+
+ from ruamel.yaml import YAML
+
+ yaml = YAML(typ='safe')
+ yaml.default_flow_style = False
+ data = yaml.load("abc: 1")
+ out = path('/tmp/out.yaml')
+ yaml.dump(data, out)
+
+If you previously used an keyword argument `explicit_start=True` you
+now do ``yaml.explicit_start = True`` before calling ``dump()``. The
+``Loader`` and ``Dumper`` keyword arguments are not supported that
+way. You can provide the `typ` keyword to `rt` (default),
+`safe`, `unsafe` or `base` (for round-trip load/dump, safe_load/dump,
+load/dump resp. using the BaseLoader / BaseDumper. More fine-control
+is possible by setting the attributes `.Parser`, `.Constructor`,
+`.Emitter`, etc., to the class of the type to create for that stage
+(typically a subclass of an existing class implementing that).
+
+All data is dumped (not just for round-trip-mode) with `.allow_unicode
+= True`
+
+You can of course have multiple YAML instances active at the same
+time, with different load and/or dump behaviour.
+
+Initially only the typical operations are supported, but in principle
+all functionality of the old interface will be available via
+``YAML`` instances (if you are using something that isn't let me know).
+
+Reason for API change
+---------------------
+
+``ruamel.yaml`` inherited the way of doing things from ``PyYAML``. In
+particular when calling the function ``load()`` or ``dump()`` a
+temporary instances of ``Loader()`` resp. ``Dumper()`` were
+created that were discarded on termination of the function.
+
+This way of doing things leads to several problems:
+
+- it is impossible to return information to the caller apart from the
+ constructed data structure. E.g. if you would get a YAML document
+ version number from a directive, there is no way to let the caller
+ know apart from handing back special data structures. The same
+ problem exists when trying to do on the fly
+ analysis of a document for indentation width.
+
+- these instances were composites of the various load/dump steps and
+ if you wanted to enhance one of the steps, you needed e.g. subclass
+ the emitter and make a new composite (dumper) as well, providing all
+ of the parameters (i.e. copy paste
+
+- many routines (like ``add_representer()``) have a direct global
+ impact on all of the following calls to ``dump()`` and those are
+ difficult if not impossible to turn back. This forces the need to
+ subclass ``Loaders`` and ``Dumpers``, a long time problem in PyYAML
+ as some attributes were not `deep_copied`` although a bug-report
+ (and fix) had been available a long time.
+
+- If you want to set an attribute, e.g. to control whether literal
+ block style scalars are allowed to have trailing spaces on a line
+ instead of being dumped as double quoted scalars, you have to change
+ the ``dump()`` family of routines, all of the ``Dumpers()`` as well
+ as the actual functionality change in `emitter.Emitter()`. The
+ functionality change takes changing 4 (four!) lines in one file, and being able
+ to enable that another 50+ line changes (non-contiguous) in 3 more files resulting
+ in diff that is far over 200 lines long.
+
+- replacing libyaml with something that doesn't both support `0o52`
+ and `052` for the integer ``42`` (instead of ``52`` as per YAML 1.2)
+ is difficult
+
+
+With ``ruamel.yaml>=0.15.0`` the various steps "know" about the
+``YAML`` instance and can pick up setting, as well as report back
+information via that instance. Representers, etc., are added to a
+reusable instance and different YAML instances can co-exists.
+
+This change eases development and helps prevent regressions.
diff --git a/_doc/basicuse.rst b/_doc/basicuse.rst
new file mode 100644
index 0000000..935e83a
--- /dev/null
+++ b/_doc/basicuse.rst
@@ -0,0 +1,56 @@
+Basic Usage
+===========
+
+*This is the new (0.15+) interface for ``ruamel.yaml``, it is still in
+the process of being fleshed out*. Please pin your dependency to
+``ruamel.yaml<0.15`` for production software.
+
+------
+
+You load a YAML document using::
+
+ from ruamel.yaml import YAML
+
+ yaml=YAML(typ='safe') # default if not specfied is round-trip
+
+ yaml.load(doc)
+
+in this ``doc`` can be a file pointer (i.e. an object that has the
+`.read()` method, a string or a ``pathlib.Path()``. `typ='safe'`
+accomplishes the same as what ``safe_load()`` did before: loading of a
+document without resolving unknow tags.
+
+Dumping works in the same way::
+
+ from ruamel.yaml import YAML
+
+ yaml=YAML()
+ yaml.default_flow_style = False
+ yaml.dump({a: [1, 2], s)
+
+in this ``s`` can be a file pointer (i.e. an object that has the
+`.write()` method, a ``pathlib.Path()`` or ``None`` (the default, which causes the
+YAML documented to be returned as a string.
+
+*If you have `yaml.dump()`
+return the YAML doc as string, do not just ``print`` that returned
+value*. In that case use `yaml.dump(data, sys.stdout)`, which is more
+efficient (and shows that you know what you are doing).
+
+More examples
+-------------
+
+Using the C based SafeLoader (at this time is inherited from
+libyaml/PyYAML and e.g. loads ``0o52`` as well as ``052`` load as integer ``42``)::
+
+ from ruamel.yaml import YAML
+
+ yaml=YAML(typ="safe")
+ yaml.load("""a:\n b: 2\n c: 3\n""")
+
+Using the Python based SafeLoader (YAML 1.2 support, ``052`` loads as ``52``)::
+
+ from ruamel.yaml import YAML
+
+ yaml=YAML(typ="safe", pure=True)
+ yaml.load("""a:\n b: 2\n c: 3\n""")
diff --git a/_doc/detail.rst b/_doc/detail.rst
index 3df8fff..272b70c 100644
--- a/_doc/detail.rst
+++ b/_doc/detail.rst
@@ -72,9 +72,9 @@ for calculating the cumulative indent for deeper levels and specifying
``indent=3`` resp. ``block_seq_indent=2``, migth give correct, but counter
intuitive results.
-**It is best to always have** `indent >= block_seq_indent + 2` **but
- this is not enforced**. Depending on your structure, not following
- this advice **might lead to invalid output**.
+**It is best to always have** ``indent >= block_seq_indent + 2``
+**but this is not enforced**. Depending on your structure, not following
+this advice **might lead to invalid output**.
Positioning ':' in top level mappings, prefix in ':'
----------------------------------------------------
diff --git a/_doc/index.rst b/_doc/index.rst
index 6bfbdd3..28e3cdc 100644
--- a/_doc/index.rst
+++ b/_doc/index.rst
@@ -13,11 +13,8 @@ Contents:
overview
install
+ basicuse
detail
example
+ api
pyyaml
-
-
-
-
-
diff --git a/_test/data/duplicate-anchor-1.loader-error b/_test/data/duplicate-anchor-1.loader-warning
index 906cf29..906cf29 100644
--- a/_test/data/duplicate-anchor-1.loader-error
+++ b/_test/data/duplicate-anchor-1.loader-warning
diff --git a/_test/data/duplicate-anchor-2.loader-error b/_test/data/duplicate-anchor-2.loader-warning
index 62b4389..62b4389 100644
--- a/_test/data/duplicate-anchor-2.loader-error
+++ b/_test/data/duplicate-anchor-2.loader-warning
diff --git a/_test/lib/test_errors.py b/_test/lib/test_errors.py
index f89392c..9576c90 100644
--- a/_test/lib/test_errors.py
+++ b/_test/lib/test_errors.py
@@ -3,6 +3,8 @@ from __future__ import print_function
import ruamel.yaml as yaml
import test_emitter
+import warnings
+warnings.simplefilter('ignore', yaml.error.UnsafeLoaderWarning)
def test_loader_error(error_filename, verbose=False):
diff --git a/_test/test_z_data.py b/_test/test_z_data.py
index 3b72618..d85ba28 100644
--- a/_test/test_z_data.py
+++ b/_test/test_z_data.py
@@ -9,8 +9,11 @@ import platform # NOQA
sys.path.insert(0, os.path.dirname(__file__) + '/lib')
-import ruamel.yaml # NOQA
+import ruamel.yaml # NOQA
import test_appliance # NOQA
+import warnings # NOQA
+warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
+
args = []
diff --git a/composer.py b/composer.py
index e706145..2d84dd7 100644
--- a/composer.py
+++ b/composer.py
@@ -30,19 +30,23 @@ class Composer(object):
def __init__(self, loader=None):
# type: (Any) -> None
self.loader = loader
- if self.loader is not None:
+ if self.loader is not None and getattr(self.loader, '_composer', None) is None:
self.loader._composer = self
self.anchors = {} # type: Dict[Any, Any]
@property
def parser(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ self.loader.parser
return self.loader._parser
@property
def resolver(self):
# type: () -> Any
# assert self.loader._resolver is not None
+ if hasattr(self.loader, 'typ'):
+ self.loader.resolver
return self.loader._resolver
def check_node(self):
diff --git a/constructor.py b/constructor.py
index fd37ddd..4a52b27 100644
--- a/constructor.py
+++ b/constructor.py
@@ -44,7 +44,7 @@ class BaseConstructor(object):
def __init__(self, preserve_quotes=None, loader=None):
# type: (bool, Any) -> None
self.loader = loader
- if self.loader is not None:
+ if self.loader is not None and getattr(self.loader, '_constructor', None) is None:
self.loader._constructor = self
self.loader = loader
self.constructed_objects = {} # type: Dict[Any, Any]
@@ -56,6 +56,8 @@ class BaseConstructor(object):
@property
def composer(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ return self.loader.composer
try:
return self.loader._composer
except AttributeError:
@@ -67,6 +69,8 @@ class BaseConstructor(object):
@property
def resolver(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ return self.loader.resolver
return self.loader._resolver
def check_data(self):
diff --git a/cyaml.py b/cyaml.py
index 02ed6aa..78438ff 100644
--- a/cyaml.py
+++ b/cyaml.py
@@ -5,7 +5,6 @@ from __future__ import absolute_import
from _ruamel_yaml import CParser, CEmitter # type: ignore
from ruamel.yaml.constructor import Constructor, BaseConstructor, SafeConstructor
-from ruamel.yaml.serializer import Serializer
from ruamel.yaml.representer import Representer, SafeRepresenter, BaseRepresenter
from ruamel.yaml.resolver import Resolver, BaseResolver
@@ -98,7 +97,7 @@ class CSafeDumper(CEmitter, SafeRepresenter, Resolver): # type: ignore
Resolver.__init__(self)
-class CDumper(CEmitter, Serializer, Representer, Resolver): # type: ignore
+class CDumper(CEmitter, Representer, Resolver): # type: ignore
def __init__(self, stream,
default_style=None, default_flow_style=None,
canonical=None, indent=None, width=None,
diff --git a/emitter.py b/emitter.py
index 773058a..70407bd 100644
--- a/emitter.py
+++ b/emitter.py
@@ -55,18 +55,13 @@ class Emitter(object):
top_level_colon_align=None, prefix_colon=None, dumper=None):
# type: (StreamType, Any, int, int, bool, Any, int, bool, Any, Any) -> None
self.dumper = dumper
- if self.dumper is not None:
+ if self.dumper is not None and getattr(self.dumper, '_emitter', None) is None:
self.dumper._emitter = self
- # The stream should have the methods `write` and possibly `flush`.
- if not hasattr(stream, 'write') and hasattr(stream, 'open'):
- self.stream = stream.open('w') # pathlib.Path() instance
- else:
- if not hasattr(stream, 'write'):
- raise YAMLStreamError('stream argument needs to have a write() method')
- self.stream = stream
+ self.stream = stream
# Encoding can be overriden by STREAM-START.
self.encoding = None # type: Union[None, Text]
+ self.allow_space_break = None
# Emitter is a state machine with a stack of states to handle nested
# structures.
@@ -136,6 +131,23 @@ class Emitter(object):
self.analysis = None # type: Any
self.style = None # type: Any
+ @property
+ def stream(self):
+ # type: () -> Any
+ try:
+ return self._stream
+ except AttributeError:
+ raise YAMLStreamError('output stream needs to specified')
+
+ @stream.setter
+ def stream(self, val):
+ # type: (Any) -> None
+ if val is None:
+ return
+ if not hasattr(val, 'write'):
+ raise YAMLStreamError('stream argument needs to have a write() method')
+ self._stream = val
+
def dispose(self):
# type: () -> None
# Reset the state attributes (to clear self-references)
@@ -632,6 +644,7 @@ class Emitter(object):
(self.flow_level and self.analysis.allow_flow_plain or
(not self.flow_level and self.analysis.allow_block_plain))):
return ''
+ self.analysis.allow_block = True
if self.event.style and self.event.style in '|>':
if (not self.flow_level and not self.simple_key_context and
self.analysis.allow_block):
@@ -902,9 +915,12 @@ class Emitter(object):
# Spaces followed by breaks, as well as special character are only
# allowed for double quoted scalars.
- if space_break or special_characters:
- allow_flow_plain = allow_block_plain = \
- allow_single_quoted = allow_block = False
+ if special_characters:
+ allow_flow_plain = allow_block_plain = allow_single_quoted = allow_block = False
+ elif space_break:
+ allow_flow_plain = allow_block_plain = allow_single_quoted = False
+ if not self.allow_space_break:
+ allow_block = False
# Although the plain scalar writer supports breaks, we never emit
# multiline plain scalars.
@@ -974,7 +990,7 @@ class Emitter(object):
self.column = indent
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
def write_line_break(self, data=None):
# type: (Any) -> None
@@ -993,7 +1009,7 @@ class Emitter(object):
data = u'%%YAML %s' % version_text
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
self.write_line_break()
def write_tag_directive(self, handle_text, prefix_text):
@@ -1001,7 +1017,7 @@ class Emitter(object):
data = u'%%TAG %s %s' % (handle_text, prefix_text)
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
self.write_line_break()
# Scalar streams.
@@ -1247,7 +1263,7 @@ class Emitter(object):
self.column += len(data)
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
self.whitespace = False
self.indention = False
spaces = False
@@ -1269,7 +1285,7 @@ class Emitter(object):
self.column += len(data)
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
start = end
elif breaks:
if ch not in u'\n\x85\u2028\u2029':
@@ -1290,7 +1306,7 @@ class Emitter(object):
self.column += len(data)
if self.encoding: # type: ignore
data = data.encode(self.encoding)
- self.stream.write(data) # type: ignore
+ self.stream.write(data)
start = end
if ch is not None:
spaces = (ch == u' ')
diff --git a/error.py b/error.py
index 910e13b..96872cc 100644
--- a/error.py
+++ b/error.py
@@ -129,6 +129,7 @@ class MarkedYAMLError(YAMLError):
class YAMLStreamError(Exception):
pass
+
class ReusedAnchorWarning(Warning):
pass
diff --git a/main.py b/main.py
index dc6ed92..65f933a 100644
--- a/main.py
+++ b/main.py
@@ -2,6 +2,7 @@
from __future__ import absolute_import, unicode_literals
+import sys
import warnings
import ruamel.yaml
@@ -25,8 +26,387 @@ if False: # MYPY
from typing import List, Set, Dict, Union, Any # NOQA
from ruamel.yaml.compat import StreamType, StreamTextType, VersionType # NOQA
+try:
+ from _ruamel_yaml import CParser, CEmitter # type: ignore
+except:
+ CParser = CEmitter = None
+
# import io
+enforce = object()
+
+
+# YAML is an acronym, i.e. spoken: rhymes with "camel". And thus a
+# subset of abbreviations, which should all caps according to PEP8
+
+class YAML(object):
+ def __init__(self, _kw=enforce, typ=None, pure=False):
+ # type: (Any, Any, Any) -> None
+ """
+ _kw: not used, forces keyword arguments in 2.7 (in 3 you can do (*, safe_load=..)
+ typ: 'rt'/None -> RoundTripLoader/RoundTripDumper, (default)
+ 'safe' -> SafeLoader/SafeDumper,
+ 'unsafe' -> normal/unsafe Loader/Dumper
+ 'base' -> baseloader
+ pure: if True only use Python modules
+ """
+ if _kw is not enforce:
+ raise TypeError("{}.__init__() takes no positional argument but at least "
+ "one was given ({!r})".format(self.__class__.__name__, _kw))
+
+ self.typ = 'rt' if typ is None else typ
+ self.Resolver = ruamel.yaml.resolver.VersionedResolver
+ self.allow_unicode = True
+ self.Reader = None # type: Any
+ self.Scanner = None # type: Any
+ self.Serializer = None # type: Any
+ if self.typ == 'rt':
+ # no optimized rt-dumper yet
+ self.Emitter = ruamel.yaml.emitter.Emitter # type: Any
+ self.Serializer = ruamel.yaml.serializer.Serializer # type: Any
+ self.Representer = ruamel.yaml.representer.RoundTripRepresenter # type: Any
+ self.Scanner = ruamel.yaml.scanner.RoundTripScanner # type: Any
+ # no optimized rt-parser yet
+ self.Parser = ruamel.yaml.parser.RoundTripParser # type: Any
+ self.Composer = ruamel.yaml.composer.Composer # type: Any
+ self.Constructor = ruamel.yaml.constructor.RoundTripConstructor # type: Any
+ elif self.typ == 'safe':
+ self.Emitter = ruamel.yaml.emitter.Emitter if pure or CEmitter is None \
+ else CEmitter
+ self.Representer = ruamel.yaml.representer.SafeRepresenter
+ self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
+ self.Composer = ruamel.yaml.composer.Composer
+ self.Constructor = ruamel.yaml.constructor.SafeConstructor
+ elif self.typ == 'base':
+ self.Emitter = ruamel.yaml.emitter.Emitter
+ self.Representer = ruamel.yaml.representer.BaseRepresenter
+ self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
+ self.Composer = ruamel.yaml.composer.Composer
+ self.Constructor = ruamel.yaml.constructor.BaseConstructor
+ elif self.typ == 'unsafe':
+ self.Emitter = ruamel.yaml.emitter.Emitter
+ self.Representer = ruamel.yaml.representer.Representer
+ self.Parser = ruamel.yaml.parser.Parser if pure or CParser is None else CParser
+ self.Composer = ruamel.yaml.composer.Composer
+ self.Constructor = ruamel.yaml.constructor.Constructor
+ self.stream = None
+ self.canonical = None
+ self.indent = None
+ self.width = None
+ self.line_break = None
+ self.block_seq_indent = None
+ self.top_level_colon_align = None
+ self.prefix_colon = None
+ self.version = None
+ self.preserve_quotes = None
+ self.encoding = None
+ self.explicit_start = None
+ self.explicit_end = None
+ self.tags = None
+ self.default_style = None
+ self.default_flow_style = None
+
+ @property
+ def reader(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Reader(None, loader=self))
+ return getattr(self, attr)
+
+ @property
+ def scanner(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Scanner(loader=self))
+ return getattr(self, attr)
+
+ @property
+ def parser(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ if self.Parser is not CParser:
+ setattr(self, attr, self.Parser(loader=self))
+ else:
+ if getattr(self, '_stream', None) is None:
+ # wait for the stream
+ return None
+ else:
+ # if not hasattr(self._stream, 'read') and hasattr(self._stream, 'open'):
+ # # pathlib.Path() instance
+ # setattr(self, attr, CParser(self._stream))
+ # else:
+ setattr(self, attr, CParser(self._stream)) # type: ignore
+ # self._parser = self._composer = self
+ # print('scanner', self.loader.scanner)
+
+ return getattr(self, attr)
+
+ @property
+ def composer(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Composer(loader=self))
+ return getattr(self, attr)
+
+ @property
+ def constructor(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Constructor(
+ preserve_quotes=self.preserve_quotes, loader=self))
+ return getattr(self, attr)
+
+ @property
+ def resolver(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Resolver(
+ version=self.version, loader=self))
+ return getattr(self, attr)
+
+ @property
+ def emitter(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ if self.Emitter is not CEmitter:
+ setattr(self, attr, self.Emitter(
+ None, canonical=self.canonical,
+ indent=self.indent, width=self.width,
+ allow_unicode=self.allow_unicode, line_break=self.line_break,
+ block_seq_indent=self.block_seq_indent,
+ dumper=self))
+ else:
+ if getattr(self, '_stream', None) is None:
+ # wait for the stream
+ return None
+ return None
+ return getattr(self, attr)
+
+ @property
+ def serializer(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Serializer(
+ encoding=self.encoding,
+ explicit_start=self.explicit_start, explicit_end=self.explicit_end,
+ version=self.version, tags=self.tags, dumper=self))
+ return getattr(self, attr)
+
+ @property
+ def representer(self):
+ # type: () -> Any
+ attr = '_' + sys._getframe().f_code.co_name
+ if not hasattr(self, attr):
+ setattr(self, attr, self.Representer(
+ default_style=self.default_style,
+ default_flow_style=self.default_flow_style,
+ dumper=self))
+ return getattr(self, attr)
+
+ # separate output resolver?
+
+ def load(self, stream):
+ # type: (StreamTextType) -> Any
+ """
+ at this point you either have the non-pure Parser (which has its own reader and
+ scanner) or you have the pure Parser.
+ If the pure Parser is set, then set the Reader and Scanner, if not already set.
+ If either the Scanner or Reader are set, you cannot use the non-pure Parser,
+ so reset it to the pure parser and set the Reader resp. Scanner if necessary
+ """
+ if not hasattr(stream, 'read') and hasattr(stream, 'open'):
+ # pathlib.Path() instance
+ with stream.open('r') as fp: # type: ignore
+ return self.load(fp)
+ constructor, parser = self.get_constructor_parser(stream)
+ try:
+ return constructor.get_single_data()
+ finally:
+ parser.dispose()
+
+ def load_all(self, stream, _kw=enforce): # , skip=None):
+ # type: (StreamTextType, Any) -> Any
+ if _kw is not enforce:
+ raise TypeError("{}.__init__() takes no positional argument but at least "
+ "one was given ({!r})".format(self.__class__.__name__, _kw))
+ if not hasattr(stream, 'read') and hasattr(stream, 'open'):
+ # pathlib.Path() instance
+ with stream.open('r') as fp: # type: ignore
+ yield self.load_all(fp, _kw=enforce)
+ # if skip is None:
+ # skip = []
+ # elif isinstance(skip, int):
+ # skip = [skip]
+ constructor, parser = self.get_constructor_parser(stream)
+ try:
+ while constructor.check_data():
+ yield constructor.get_data()
+ finally:
+ parser.dispose()
+
+ def get_constructor_parser(self, stream):
+ # type: (StreamTextType) -> Any
+ """
+ the old cyaml needs special setup, and therefore the stream
+ """
+ if self.Parser is not CParser:
+ if self.Reader is None:
+ self.Reader = ruamel.yaml.reader.Reader
+ if self.Scanner is None:
+ self.Scanner = ruamel.yaml.scanner.Scanner
+ self.reader.stream = stream
+ else:
+ if self.Reader is not None:
+ if self.Scanner is None:
+ self.Scanner = ruamel.yaml.scanner.Scanner
+ self.Parser = ruamel.yaml.parser.Parser
+ self.reader.stream = stream
+ elif self.Scanner is not None:
+ if self.Reader is None:
+ self.Reader = ruamel.yaml.reader.Reader
+ self.Parser = ruamel.yaml.parser.Parser
+ self.reader.stream = stream
+ else:
+ # combined C level reader>scanner>parser
+ # does some calls to the resolver, e.g. BaseResolver.descend_resolver
+ # if you just initialise the CParser, to much of resolver.py
+ # is actually used
+ rslvr = self.Resolver
+ if rslvr is ruamel.yaml.resolver.VersionedResolver:
+ rslvr = ruamel.yaml.resolver.Resolver
+
+ class XLoader(self.Parser, self.Constructor, rslvr): # type: ignore
+ def __init__(selfx, stream, version=None, preserve_quotes=None):
+ # type: (StreamTextType, VersionType, bool) -> None
+ CParser.__init__(selfx, stream)
+ selfx._parser = selfx._composer = selfx
+ self.Constructor.__init__(selfx, loader=selfx)
+ rslvr.__init__(selfx, loadumper=selfx)
+ self._stream = stream
+ loader = XLoader(stream)
+ return loader, loader
+ return self.constructor, self.parser
+
+ def dump(self, data, stream=None):
+ # type: (Any, StreamType) -> Any
+ return self.dump_all([data], stream)
+
+ def dump_all(self, documents, stream=None):
+ # type: (Any, StreamType) -> Any
+ """
+ Serialize a sequence of Python objects into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ # The stream should have the methods `write` and possibly `flush`.
+ if not hasattr(stream, 'write') and hasattr(stream, 'open'):
+ # pathlib.Path() instance
+ with stream.open('w') as fp: # type: ignore
+ return self.dump_all(documents, fp)
+ getvalue = None
+ if self.top_level_colon_align is True:
+ tlca = max([len(str(x)) for x in documents[0]]) # type: Any
+ else:
+ tlca = self.top_level_colon_align
+ if stream is None:
+ if self.encoding is None:
+ stream = StringIO()
+ else:
+ stream = BytesIO()
+ getvalue = stream.getvalue
+ serializer, representer, emitter = \
+ self.get_serializer_representer_emitter(stream, tlca)
+ try:
+ self.serializer.open()
+ for data in documents:
+ try:
+ self.representer.represent(data)
+ except AttributeError:
+ # print(dir(dumper._representer))
+ raise
+ self.serializer.close()
+ finally:
+ try:
+ self.emitter.dispose()
+ except AttributeError:
+ raise
+ # self.dumper.dispose() # cyaml
+ delattr(self, "_serializer")
+ delattr(self, "_emitter")
+ if getvalue is not None:
+ return getvalue()
+ return None
+
+ def get_serializer_representer_emitter(self, stream, tlca):
+ # type: (StreamType, Any) -> Any
+ # we have only .Serializer to deal with (vs .Reader & .Scanner), much simpler
+ if self.Emitter is not CEmitter:
+ if self.Serializer is None:
+ self.Serializer = ruamel.yaml.serializer.Serializer
+ self.emitter.stream = stream
+ self.emitter.top_level_colon_align = tlca
+ return self.serializer, self.representer, self.emitter
+ if self.Serializer is not None:
+ # cannot set serializer with CEmitter
+ self.Emitter = ruamel.yaml.emitter.Emitter
+ self.emitter.stream = stream
+ self.emitter.top_level_colon_align = tlca
+ return self.serializer, self.representer, self.emitter
+ # C routines
+
+ rslvr = ruamel.yaml.resolver.BaseResolver if self.typ == 'base' \
+ else ruamel.yaml.resolver.Resolver
+
+ class XDumper(CEmitter, self.Representer, rslvr): # type: ignore
+ def __init__(selfx, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None, block_seq_indent=None,
+ top_level_colon_align=None, prefix_colon=None):
+ # type: (StreamType, Any, Any, Any, bool, Union[None, int], Union[None, int], bool, Any, Any, Union[None, bool], Union[None, bool], Any, Any, Any, Any, Any) -> None # NOQA
+ CEmitter.__init__(selfx, stream, canonical=canonical,
+ indent=indent, width=width, encoding=encoding,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start,
+ explicit_end=explicit_end,
+ version=version, tags=tags)
+ selfx._emitter = selfx._serializer = selfx._representer = selfx
+ Representer.__init__(selfx, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(selfx)
+ self._stream = stream
+ dumper = XDumper(stream)
+ self._emitter = self._serializer = dumper
+ return dumper, dumper, dumper
+
+ # basic types
+ def map(self, **kw):
+ # type: (Any) -> Any
+ if self.typ == 'rt':
+ from ruamel.yaml.comments import CommentedMap
+ return CommentedMap(**kw)
+ else:
+ return dict(**kw)
+
+ def seq(self, *args):
+ # type: (Any) -> Any
+ if self.typ == 'rt':
+ from ruamel.yaml.comments import CommentedSeq
+ return CommentedSeq(*args)
+ else:
+ return list(*args)
+
+
+########################################################################################
def scan(stream, Loader=Loader):
# type: (StreamTextType, Any) -> Any
diff --git a/parser.py b/parser.py
index c4c997e..410ae3b 100644
--- a/parser.py
+++ b/parser.py
@@ -104,7 +104,7 @@ class Parser(object):
def __init__(self, loader):
# type: (Any) -> None
self.loader = loader
- if self.loader is not None:
+ if self.loader is not None and getattr(self.loader, '_parser', None) is None:
self.loader._parser = self
self.current_event = None
self.yaml_version = None
@@ -116,11 +116,15 @@ class Parser(object):
@property
def scanner(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ self.loader.scanner
return self.loader._scanner
@property
def resolver(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ self.loader.resolver
return self.loader._resolver
def dispose(self):
diff --git a/reader.py b/reader.py
index 4cc67b8..210470c 100644
--- a/reader.py
+++ b/reader.py
@@ -74,10 +74,9 @@ class Reader(object):
def __init__(self, stream, loader=None):
# type: (StreamTextType, Any) -> None
self.loader = loader
- if self.loader is not None:
+ if self.loader is not None and getattr(self.loader, '_reader', None) is None:
self.loader._reader = self
- self.name = None
- self.stream = None # type: Any # as .read is called
+ self.name = None # type: Any
self.stream_pointer = 0
self.eof = True
self.buffer = u''
@@ -88,22 +87,35 @@ class Reader(object):
self.index = 0
self.line = 0
self.column = 0
- if isinstance(stream, text_type):
+ self.stream = stream # type: Any # as .read is called
+
+ @property
+ def stream(self):
+ # type: () -> Any
+ try:
+ return self._stream
+ except AttributeError:
+ raise YAMLStreamError('input stream needs to specified')
+
+ @stream.setter
+ def stream(self, val):
+ # type: (Any) -> None
+ if val is None:
+ return
+ self._stream = None
+ if isinstance(val, text_type):
self.name = "<unicode string>"
- self.check_printable(stream)
- self.buffer = stream+u'\0'
- elif isinstance(stream, binary_type):
+ self.check_printable(val)
+ self.buffer = val + u'\0'
+ elif isinstance(val, binary_type):
self.name = "<byte string>"
- self.raw_buffer = stream
+ self.raw_buffer = val
self.determine_encoding()
else:
- if not hasattr(stream, 'read') and hasattr(stream, 'open'):
- self.stream = stream.open('r')
- else:
- if not hasattr(stream, 'read'):
- raise YAMLStreamError('stream argument needs to have a read() method')
- self.stream = stream
- self.name = getattr(stream, 'name', "<file>")
+ if not hasattr(val, 'read'):
+ raise YAMLStreamError('stream argument needs to have a read() method')
+ self._stream = val
+ self.name = getattr(self.stream, 'name', "<file>")
self.eof = False
self.raw_buffer = None
self.determine_encoding()
diff --git a/representer.py b/representer.py
index a9e229c..f6eef1b 100644
--- a/representer.py
+++ b/representer.py
@@ -60,6 +60,8 @@ class BaseRepresenter(object):
def serializer(self):
# type: () -> Any
try:
+ if hasattr(self.dumper, 'typ'):
+ return self.dumper.serializer
return self.dumper._serializer
except AttributeError:
return self # cyaml
diff --git a/resolver.py b/resolver.py
index 15fb55f..83656d1 100644
--- a/resolver.py
+++ b/resolver.py
@@ -104,7 +104,7 @@ class BaseResolver(object):
def __init__(self, loadumper=None):
# type: (Any, Any) -> None
self.loadumper = loadumper
- if self.loadumper is not None:
+ if self.loadumper is not None and getattr(self.loadumper, '_resolver', None) is None:
self.loadumper._resolver = self.loadumper
self._loader_version = None # type: Any
self.resolver_exact_paths = [] # type: List[Any]
@@ -114,6 +114,8 @@ class BaseResolver(object):
def parser(self):
# type: () -> Any
if self.loadumper is not None:
+ if hasattr(self.loadumper, 'typ'):
+ return self.loadumper.parser
return self.loadumper._parser
return None
@@ -204,6 +206,8 @@ class BaseResolver(object):
def descend_resolver(self, current_node, current_index):
# type: (Any, Any) -> None
+ # print('xx2', self)
+ # sys.exit(0)
if not self.yaml_path_resolvers:
return
exact_paths = {}
@@ -429,7 +433,10 @@ class VersionedResolver(BaseResolver):
try:
version = self.parser.yaml_version
except AttributeError:
- version = self.loadumper._serializer.use_version # dumping
+ if hasattr(self.loadumper, 'typ'):
+ version = self.loadumper.version
+ else:
+ version = self.loadumper._serializer.use_version # dumping
if version is None:
version = self._loader_version
if version is None:
diff --git a/scanner.py b/scanner.py
index 2f083f5..de7a92a 100644
--- a/scanner.py
+++ b/scanner.py
@@ -72,7 +72,7 @@ class Scanner(object):
# self.forward(l=1) # read the next l characters and move the pointer
self.loader = loader
- if self.loader is not None:
+ if self.loader is not None and getattr(self.loader, '_scanner', None) is None:
self.loader._scanner = self
# Had we reached the end of the stream?
@@ -130,6 +130,8 @@ class Scanner(object):
@property
def reader(self):
# type: () -> Any
+ if hasattr(self.loader, 'typ'):
+ self.loader.reader
return self.loader._reader
# Public methods.
diff --git a/serializer.py b/serializer.py
index bda778f..f224e96 100644
--- a/serializer.py
+++ b/serializer.py
@@ -56,11 +56,15 @@ class Serializer(object):
@property
def emitter(self):
# type: () -> Any
+ if hasattr(self.dumper, 'typ'):
+ return self.dumper.emitter
return self.dumper._emitter
@property
def resolver(self):
# type: () -> Any
+ if hasattr(self.dumper, 'typ'):
+ self.dumper.resolver
return self.dumper._resolver
def open(self):