diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 00:33:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 00:33:55 +0000 |
commit | cbbc936ed9811bdb5dd480bc2c5e10c3062532be (patch) | |
tree | ec1783c0aaa2ee6eaa6d6362f2bed4392943de8e /_doc | |
parent | Releasing progress-linux version 0.18.5-1~exp1~progress7.99u1. (diff) | |
download | ruamel.yaml-cbbc936ed9811bdb5dd480bc2c5e10c3062532be.tar.xz ruamel.yaml-cbbc936ed9811bdb5dd480bc2c5e10c3062532be.zip |
Merging upstream version 0.18.6.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | _doc/Makefile | 216 | ||||
-rw-r--r-- | _doc/README.ryd | 120 | ||||
-rw-r--r-- | _doc/_static/license.svg | 1 | ||||
-rw-r--r-- | _doc/_static/pypi.svg | 1 | ||||
-rw-r--r-- | _doc/api.ryd | 308 | ||||
-rw-r--r-- | _doc/basicuse.ryd | 77 | ||||
-rw-r--r-- | _doc/conf.py | 317 | ||||
-rw-r--r-- | _doc/contributing.ryd | 139 | ||||
-rw-r--r-- | _doc/detail.ryd | 298 | ||||
-rw-r--r-- | _doc/dumpcls.ryd | 315 | ||||
-rw-r--r-- | _doc/example.ryd | 255 | ||||
-rw-r--r-- | _doc/install.ryd | 47 | ||||
-rw-r--r-- | _doc/links.rydinc | 7 | ||||
-rw-r--r-- | _doc/overview.ryd | 47 | ||||
-rw-r--r-- | _doc/pyyaml.ryd | 72 |
15 files changed, 2220 insertions, 0 deletions
diff --git a/_doc/Makefile b/_doc/Makefile new file mode 100644 index 0000000..c5d1aa0 --- /dev/null +++ b/_doc/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = a4 +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/yaml.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/yaml.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/yaml" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/yaml" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/_doc/README.ryd b/_doc/README.ryd new file mode 100644 index 0000000..f728607 --- /dev/null +++ b/_doc/README.ryd @@ -0,0 +1,120 @@ +version: 0.2 +text: md +pdf: false +order: +- overview.ryd +- install.ryd +- basicuse.ryd +- dumpcls.ryd +- detail.ryd +- example.ryd +- api.ryd +- pyyaml.ryd +- contributing.ryd +toc: False # don't index this file or put in mkdocs.nav +mkdocs: + site_name: yaml + docs_dir: _doc + site_author: Anthon van der Neut + + nav: + - overview.md + - install.md + - basicuse.md + - dumpcls.md + - detail.md + - example.md + - api.md + - pyyaml.md + - contributing.md + + theme: + name: readthedocs + + exclude_docs: | + *.ryd + *.rst + + markdown_extensions: + - toc: + permalink: true +--- | +# ruamel.yaml + +`ruamel.yaml` is a YAML 1.2 loader/dumper package for Python. + +--- !table +version: !Env version +updated: !Env date +documentation: https://yaml.readthedocs.io +repository: https://sourceforge.net/projects/ruamel-yaml +pypi: https://pypi.org/project/ruamel.yaml +--- | + +As announced, in 0.18.0, the old PyYAML functions have been deprecated. +(`scan`, `parse`, `compose`, `load`, `emit`, `serialize`, `dump` and their variants +(`_all`, `safe_`, `round_trip_`, etc)). If you only read this after your program has +stopped working: I am sorry to hear that, but that also means you, or the person +developing your program, has not tested with warnings on (which is the recommendation +in PEP 565, and e.g. defaultin when using `pytest`). If you have troubles, explicitly use +``` +pip install "ruamel.yaml<0.18.0" +``` +or put something to that effects in your requirments, to give yourself +some time to solve the issue. + +There will be at least one more potentially breaking change in the 0.18 series: `YAML(typ='unsafe')` +now has a pending deprecation warning and is going to be deprecated, probably before the end of 2023. +If you only use it to dump, please use the new `YAML(typ='full')`, the result of that can be *safely* +loaded with a default instance `YAML()`, as that will get you inspectable, tagged, scalars, instead of +executed Python functions/classes. (You should probably add constructors for what you actually need, +but I do consider adding a `ruamel.yaml.unsafe` package that will re-add the `typ='unsafe'` option. +*Please adjust/pin your dependencies accordingly if necessary.* + + +There seems to be a CVE on `ruamel.yaml`, stating that the `load()` function could be abused +because of unchecked input. `load()` was never the default function (that was `round_trip_load()` +before the new API came into existence`. So the creator of that CVE was ill informed and +probably lazily assumed that since `ruamel.yaml` is a derivative of PyYAML (for which +a similar CVE exists), the same problem would still exist, without checking. +So the CVE was always inappriate, now just more so, as the call +to the function `load()` with any input will terminate your program with an error message. If you +(have to) care about such things as this CVE, my recommendation is to stop using Python +completely, as `pickle.load()` can be abused in the same way as `load()` (and like unlike `load()` +is only documented to be unsafe, without development-time warning. + +Version 0.17.21 was the last one tested to be working on Python 3.5 and 3.6<BR> +The 0.16.13 release was the last that was tested to be working on Python 2.7. + + +There are two extra plug-in packages +(`ruamel.yaml.bytes` and `ruamel.yaml.string`) +for those not wanting to do the streaming to a +`io.BytesIO/StringIO` buffer themselves. + +If your package uses `ruamel.yaml` and is not listed on PyPI, drop me an +email, preferably with some information on how you use the package (or a +link to the repository) and I'll keep you informed when the status of +the API is stable enough to make the transition. + +--- !toc +level: 3 +# prefix: http://yaml.readthedocs.io/en/latest/ +--- | + +[![image](https://readthedocs.org/projects/yaml/badge/?version=latest)](https://yaml.readthedocs.org/en/latest?badge=latest)[![image](https://bestpractices.coreinfrastructure.org/projects/1128/badge)](https://bestpractices.coreinfrastructure.org/projects/1128) +[![image](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/_doc/_static/license.svg?format=raw)](https://opensource.org/licenses/MIT) +[![image](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/_doc/_static/pypi.svg?format=raw)](https://pypi.org/project/ruamel.yaml/) +[![image](https://sourceforge.net/p/oitnb/code/ci/default/tree/_doc/_static/oitnb.svg?format=raw)](https://pypi.org/project/oitnb/) +[![image](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) + +# ChangeLog + +--- !changelog +CHANGES +--- | + +------------------------------------------------------------------------ + +For older changes see the file +[CHANGES](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/CHANGES) diff --git a/_doc/_static/license.svg b/_doc/_static/license.svg new file mode 100644 index 0000000..43dbd86 --- /dev/null +++ b/_doc/_static/license.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="82" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="82" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h51v20H0z"/><path fill="#007ec6" d="M51 0h31v20H51z"/><path fill="url(#b)" d="M0 0h82v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="265" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="410">License</text><text x="265" y="140" transform="scale(.1)" textLength="410">License</text><text x="655" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">MIT</text><text x="655" y="140" transform="scale(.1)" textLength="210">MIT</text></g> </svg> diff --git a/_doc/_static/pypi.svg b/_doc/_static/pypi.svg new file mode 100644 index 0000000..28c535d --- /dev/null +++ b/_doc/_static/pypi.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="86" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="86" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h33v20H0z"/><path fill="#007ec6" d="M33 0h53v20H33z"/><path fill="url(#b)" d="M0 0h86v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="175" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">pypi</text><text x="175" y="140" transform="scale(.1)" textLength="230">pypi</text><text x="585" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">0.18.6</text><text x="585" y="140" transform="scale(.1)" textLength="430">0.18.6</text></g> </svg> diff --git a/_doc/api.ryd b/_doc/api.ryd new file mode 100644 index 0000000..c0e1a7d --- /dev/null +++ b/_doc/api.ryd @@ -0,0 +1,308 @@ +version: 0.2 +text: md +pdf: false +--- !python-pre | +import sys +from io import StringIO +import ruamel.yaml +from ruamel.yaml import YAML +yaml=YAML() +ostream = s = StringIO() +istream = stream = doc = "a: 1" +data = dict(a=1) +from pathlib import Path # or: from ruamel.std.pathlib import Path +--- | +# 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 inherited 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 stream argument. +All other parameters are set on the instance of `YAML` before calling +`load()` or `dump()` + +Before 0.15.0 you could do: + +``` python +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: +--- !python | +from pathlib import Path +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 a 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). + +The default loader (`typ='rt'`) is a direct derivative of the safe +loader, without the methods to construct arbitrary Python objects that +make the `unsafe` loader unsafe, but with the changes needed for +round-trip preservation of comments, etc.. For trusted Python classes a +constructor can of course be added to the round-trip or safe-loader, but +this has to be done explicitly (`add_constructor`). + +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). + +If a parse or dump fails, and throws and exception, the state of the +`YAML()` instance is not guaranteed to be able to handle further +processing. You should, at that point to recreate the YAML instance +before proceeding. + +## Loading + +### Duplicate keys + +In JSON mapping keys should be unique, in YAML they must be unique. +PyYAML never enforced this although the YAML 1.1 specification already +required this. + +In the new API (starting 0.15.1) duplicate keys in mappings are no +longer allowed by default. To allow duplicate keys in mappings: +--- !python | +yaml = ruamel.yaml.YAML() +yaml.allow_duplicate_keys = True +yaml.load(stream) +--- | +In the old API this is a warning starting with 0.15.2 and an error in +0.16.0. + +When a duplicate key is found it and its value are discarded, as should +be done according to the [YAML 1.1 +specification](http://yaml.org/spec/1.1/#id932806). + +## Dumping a multi-document YAML stream + +The \"normal\" `dump_all` expected as first element a list of documents, +or something else the internals of the method can iterate over. To read +and write a multi-document you would either make a `list`: +--- !code | + yaml = YAML() + data = list(yaml.load_all(in_path)) + # do something on data[0], data[1], etc. + yaml.dump_all(data, out_path) +--- | +or create some function/object that would yield the `data` values. + +What you now can do is create `YAML()` as an context manager. This works +for output (dumping) only, requires you to specify the output (file, +buffer, `Path`) at creation time, and doesn\'t support `transform` +(yet). + +: +--- !code | + with YAML(output=sys.stdout) as yaml: + yaml.explicit_start = True + for data in yaml.load_all(Path(multi_document_filename)): + # do something on data + yaml.dump(data) +--- | +Within the context manager, you cannot use the `dump()` with a second +(stream) argument, nor can you use `dump_all()`. The `dump()` within the +context of the `YAML()` automatically creates multi-document if called +more than once. + +To combine multiple YAML documents from multiple files: + +: +--- !code | + list_of_filenames = ['x.yaml', 'y.yaml', ] + with YAML(output=sys.stdout) as yaml: + yaml.explicit_start = True + for path in list_of_filename: + with open(path) as fp: + yaml.dump(yaml.load(fp)) +--- | +The output will be a valid, uniformly indented YAML file. Doing +`cat {x,y}.yaml` might result in a single document if there is not +document start marker at the beginning of `y.yaml` + +## Dumping + +### Controls + +On your `YAML()` instance you can set attributes e.g with: + + yaml = YAML(typ='safe', pure=True) + yaml.allow_unicode = False + +available attributes include: + +`unicode_supplementary` + +: Defaults to `True` if Python\'s Unicode size is larger than 2 bytes. + Set to `False` to enforce output of the form `\U0001f601` (ignored + if `allow_unicode` is `False`) + +## Transparent usage of new and old API + +With 0.18 the entry functions for the old API has been removed, so the +following now only makes sense if you use the old API on a pinned +old version or `ruamel.yaml`. + +If you have multiple packages depending on `ruamel.yaml`, or install +your utility together with other packages not under your control, then +fixing your `install_requires` might not be so easy. + +Depending on your usage you might be able to \"version\" your usage to +be compatible with both the old and the new. The following are some +examples all assuming `from ruamel import yaml` somewhere at the top of +your file and some `istream` and `ostream` apropriately opened for +reading resp. writing. + +Loading and dumping using the `SafeLoader`: + + if ruamel.yaml.version_info < (0, 15): + data = yaml.safe_load(istream) + yaml.safe_dump(data, ostream) + else: + yml = ruamel.yaml.YAML(typ='safe', pure=True) # 'safe' load and dump + data = yml.load(istream) + yml.dump(data, ostream) + +Loading with the `CSafeLoader`, dumping with `RoundTripLoader`. You need +two `YAML` instances, but each of them can be re-used: +--- !python | +if ruamel.yaml.version_info < (0, 15): + data = yaml.load(istream, Loader=yaml.CSafeLoader) + yaml.round_trip_dump(data, ostream, width=1000, explicit_start=True) +else: + yml = ruamel.yaml.YAML(typ='safe') + data = yml.load(istream) + ymlo = ruamel.yaml.YAML() # or yaml.YAML(typ='rt') + ymlo.width = 1000 + ymlo.explicit_start = True + ymlo.dump(data, ostream) +--- | +Loading and dumping from `pathlib.Path` instances using the +round-trip-loader: +--- !code | +# in myyaml.py +if ruamel.yaml.version_info < (0, 15): + class MyYAML(yaml.YAML): + def __init__(self): + yaml.YAML.__init__(self) + self.preserve_quotes = True + self.indent(mapping=4, sequence=4, offset=2) +# in your code +try: + from myyaml import MyYAML +except (ModuleNotFoundError, ImportError): + if ruamel.yaml.version_info >= (0, 15): + raise + +# some pathlib.Path +from pathlib import Path +inf = Path('/tmp/in.yaml') +outf = Path('/tmp/out.yaml') + +if ruamel.yaml.version_info < (0, 15): + with inf.open() as ifp: + data = yaml.round_trip_load(ifp, preserve_quotes=True) + with outf.open('w') as ofp: + yaml.round_trip_dump(data, ofp, indent=4, block_seq_indent=2) +else: + yml = MyYAML() + # no need for with statement when using pathlib.Path instances + data = yml.load(inf) + yml.dump(data, outf) +--- | +## Reason for API change + +`ruamel.yaml` inherited the way of doing things from `PyYAML`. In +particular when calling the function `load()` or `dump()` 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 virtually 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) + + Alternatives, like making a class that returned a `Dumper` when + called and sets attributes before doing so, is cumbersome for + day-to-day use. + +- 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.ryd b/_doc/basicuse.ryd new file mode 100644 index 0000000..cce50d8 --- /dev/null +++ b/_doc/basicuse.ryd @@ -0,0 +1,77 @@ +version: 0.2 +text: md +pdf: false +--- !python-pre | +import sys +from io import StringIO +from ruamel.yaml import YAML +yaml=YAML() +s = StringIO() +doc = "a: 1" +data = dict(a=1) +--- | +# Basic Usage +## Load and dump + +You load a YAML document using: +--- !python | +from ruamel.yaml import YAML + +yaml=YAML(typ='safe') # default, if not specfied, is 'rt' (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 unknown tags. Provide `pure=True` to enforce +using the pure Python implementation, otherwise the faster C libraries +will be used when possible/available but these behave slightly different +(and sometimes more like a YAML 1.1 loader). + +Dumping works in the same way: +--- !code | +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, or a `pathlib.Path()`. If you want to display your +output, just stream to `sys.stdout`. + +If you need to transform a string representation of the output provide a +function that takes a string as input and returns one: +--- !python | +def tr(s): + return s.replace('\n', '<\n') # such output is not valid YAML! + +yaml.dump(data, sys.stdout, transform=tr) + +--- | +## More examples + +Using the C based SafeLoader (at this time is inherited from +libyaml/PyYAML and e.g. loads `0o52` as well as `052` as integer +`42`): +--- !python | + 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`): +--- !python | + from ruamel.yaml import YAML + + yaml=YAML(typ="safe", pure=True) + yaml.load("""a:\n b: 2\n c: 3\n""") + +--- | + +Restrictions when using the C based SafeLoader/SafeDumper: + +- yaml.indent will set the same value for mappings and sequences. (Issue 471) diff --git a/_doc/conf.py b/_doc/conf.py new file mode 100644 index 0000000..3b4f6c8 --- /dev/null +++ b/_doc/conf.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +# +# yaml documentation build configuration file, created by +# sphinx-quickstart on Mon Feb 29 12:03:00 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys # NOQA +import os # NOQA + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] # type: ignore + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = ['.rst'] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'yaml' +copyright = '2017-2021, Anthon van der Neut, Ruamel bvba' +author = 'Anthon van der Neut' + +# The version info for the project you are documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +try: + from ruamel.yaml import __version__, version_info # NOQA + + # The short X.Y version. + version = '.'.join([str(ch) for ch in version_info[:3]]) + # The full version, including alpha/beta/rc tags. + release = version # = __version__ +except Exception as e: + print('exception', e) + version = release = 'dev' +print('ruamel.yaml version', version) +# print('cwd:', os.getcwd()) +# current working directory is the one with `conf.py` ! + + +class ryd2rst: + pass + + +if True: + try: + from ryd.__main__ import main + from pathlib import Path + + oldargv = sys.argv + for fn in Path('.').glob('*.ryd'): + sys.argv = ['ryd', 'convert', '--no-pdf', str(fn)] + main(sys.argv) + sys.argv = oldargv + + except Exception as e: + print('ryd exception', e) + raise + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +html_title = 'Python YAML package documentation' + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'yamldoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + 'papersize': 'a4paper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + 'yaml.tex', + 'Python YAML package documentation', + 'Anthon van der Neut', + 'manual', + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, 'yaml', 'yaml Documentation', [author], 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + 'yaml', + 'yaml Documentation', + author, + 'yaml', + 'One line description of project.', + 'Miscellaneous', + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False diff --git a/_doc/contributing.ryd b/_doc/contributing.ryd new file mode 100644 index 0000000..24e351b --- /dev/null +++ b/_doc/contributing.ryd @@ -0,0 +1,139 @@ +version: 0.2 +text: md +pdf: false +--- | +# Contributing + +Any contribution to `ruamel.yaml` is welcome, be it in the form of an +email, a question on stackoverflow (I\'ll get notified of that when you +tag it with `ruamel.yaml`), an issue or pull-request (PR) on +sourceforge. + +Contributing via stackoverflow is, for most, easy to do. When I answer +your question there and the answer warrants an extension to the +documentation or code, I will include it in a documentation update +and/or future (normally the next) release of `ruamel.yaml`. + +Please don\'t post support questions as an issue on sourceforge. + +## Documentation + +The documentation for `ruamel.yaml` is in YAML, more specifically in +[ryd](https://pypi.python.org/pypi/ryd) ( /rɑɪt/, pronounced like the +verb "write" ). This is Markdown (previously reStructuredText) +mixed with Python, each in +separate YAML documents within a single file. If you know a bit of YAML, +Python and Markdown, it will be clear how that works. + +If you want to contribute to the documentation, you can send me a clear +description of the needed changes, e.g. as a unified diff. If the +changes encompass multiple documents in a `.ryd` file, it is best to +install `ryd` (use a virtualenv!), clone the `ruamel.yaml` repository on +sourceforge, edit documentation, run `ryd`: + + ryd --pdf '**/*.ryd' + +(quoting might not be necessary depending on your shell), and once the +PDF(s) look acceptable, submit a pull-request. + +`ryd` will check your file for single backquotes (my most common mistake +going back and forth between reStructuredText and other mark up). + +If you contribute example programs, note that `ryd` will automatically +run your program (so it should be correct) and can include the output of +the program in the resulting `.rst` (and PDF) file. + +## Code + +Code changes are welcome as well, but anything beyond a minor change +should be tested (`tox`/`pytest`), checked for typing conformance +(`mypy`) and pass pep8 conformance (`flake8`). + +In my experience it is best to use two `virtualenv` environments, one +with the latest Python version currently supported, the other with +the oldest supported version. +In the site-packages directory of each virtualenv make a soft link to +the ruamel directory of your (cloned and checked out) copy of the +repository. Do not under any circumstances run `pip install -e .` or +`python setup.py -e .` it will not work (at least not until these +commands are fixed to support packages with namespaces). + +You can install `tox`, `pytest`, `mypy` and `flake8` in the Python3 +`virtualenv`, or in a `virtualenv` of their own. If all of these +commands pass without warning/error, you can create your pull-request. + +### Flake + +My `~/.config/flake8` file: + + [flake8] + show-source = True + max-line-length = 95 + ignore = F405 + +The suppress of F405 is necessary to allow `from xxx import *`, which I +have not removed in all places (yet). + +First make sure your checked out source passes `flake8` without test (it +should). Then make your changes pass without any warnings/errors. + +### Tox/pytest + +Whether you add something or fix some bug with your code changes, first +add one or more tests that fail in the unmodified source when running +`tox`. Once that is in place add your code, which should have as a +result that your added test(s) no longer fail, and neither should any +other existing tests. + +### Typing/mypy + +If you add methods or functions to `ruamel.yaml`, you will need to add +Python 2.7 compatible typing information in order for `mypy` to pass +without error. + +I run `mypy` from the directory where the (link to) ruamel directory is +using: + + mypy --py2 --strict --follow-imports silent ruamel/yaml/*.py + +This should give no errors or warnings + +## Generated files + +I use a minimal environment when developing, void of most artifacts +needed for packaging, testing etc. These artifact files are *generated*, +just before committing to sourceforge and pushing to PyPI, with nuances +coming from the `_package_data` information in `__init__.py`. Included +changes in these files will automatically be reverted, even assuming +your PR is accepted as is. + +Consider the following files **read-only** (if you think changes need to +be made to these, contact me): + + setup.py + tox.ini + LICENSE + _ryd/conf.py + -ryd/Makefile + +## Vulnerabilities + +If you find a vulnerability in `ruamel.yaml` (e.g. that would show the +`safe` and `rt` loader are not safe due to a bug in the software)), +please contact me directly via email, or by leaving a comment on +StackOverflow (below any of my posts), without going into the details +about the vulnerability. After contact is estabilished I will work to +eliminate the vulnerability in a timely fashion. After the vulnerability +is removed, and affected parties haven been notified to allow them to +update versions, the vulnerability will be published, and your role in +finding/resolving this properly attributed. + +Please note that there is a CVE out there against `ruamel.yaml`, that states +that the input of the function `load()` is not checked. As the +use of `ruamel.yaml.load()` was never the default, was documented to potentially +cause problems when specific parameters were provided, and issued a +warning, this was always an inappropriate statement. +(To compare: no such CVE was given for the use of the Python standard library +function `pickle.load`, which only documents which is default function +to use and only documented to potentially dangerious). The whole CVE is moot, +with the removal of the `load()` function 0.18. diff --git a/_doc/detail.ryd b/_doc/detail.ryd new file mode 100644 index 0000000..26ce7f0 --- /dev/null +++ b/_doc/detail.ryd @@ -0,0 +1,298 @@ +version: 0.2 +text: md +pdf: false +--- | +# Details + +- support for simple lists as mapping keys by transforming these to + tuples +- `!!omap` generates ordereddict (C) on Python 2, + collections.OrderedDict on Python 3, and `!!omap` is generated for + these types. +- Tests whether the C yaml library is installed as well as the header + files. That library doesn\'t generate CommentTokens, so it cannot be + used to do round trip editing on comments. It can be used to speed + up normal processing (so you don\'t need to install `ruamel.yaml` + and `PyYaml`). See the section *Optional requirements*. +- Basic support for multiline strings with preserved newlines and + chomping ( \'`|`\', \'`|+`\', \'`|-`\' ). As this subclasses the + string type the information is lost on reassignment. (This might be + changed in the future so that the preservation/folding/chomping is + part of the parent container, like comments). +- anchors names that are hand-crafted (not of the form`idNNN`) are + preserved +- [merges](http://yaml.org/type/merge.html) in dictionaries are + preserved +- adding/replacing comments on block-style sequences and mappings with + smart column positioning +- collection objects (when read in via RoundTripParser) have an `lc` + property that contains line and column info `lc.line` and `lc.col`. + Individual positions for mappings and sequences can also be + retrieved (`lc.key('a')`, `lc.value('a')` resp. `lc.item(3)`) +- preservation of whitelines after block scalars. Contributed by Sam + Thursfield. + +*In the following examples it is assumed you have done something like:*: + + from ruamel.yaml import YAML + yaml = YAML() + +*if not explicitly specified.* + +## Indentation of block sequences + +Although ruamel.yaml doesn\'t preserve individual indentations of block +sequence items, it does properly dump: + + x: + - b: 1 + - 2 + +back to: + + x: + - b: 1 + - 2 + +if you specify `yaml.indent(sequence=4)` (indentation is counted to the +beginning of the sequence element). + +PyYAML (and older versions of ruamel.yaml) gives you non-indented +scalars (when specifying default_flow_style=False): + + x: + - b: 1 + - 2 + +You can use `mapping=4` to also have the mappings values indented. The +dump also observes an additional `offset=2` setting that can be used to +push the dash inwards, *within the space defined by* `sequence`. + +The above example with the often seen +`yaml.indent(mapping=2, sequence=4, offset=2)` indentation: + + x: + y: + - b: 1 + - 2 + +The defaults are as if you specified +`yaml.indent(mapping=2, sequence=2, offset=0)`. + +If the `offset` equals `sequence`, there is not enough room for the dash +and the space that has to follow it. In that case the element itself +would normally be pushed to the next line (and older versions of +`ruamel.yaml` did so). But this is prevented from happening. However the +`indent` level is what is used for calculating the cumulative indent for +deeper levels and specifying `sequence=3` resp. `offset=2`, might give +correct, but counter-intuitive results. + +**It is best to always have** `sequence >= offset + 2` **but this is not +enforced**. Depending on your structure, not following this advice +**might lead to invalid output**. + +### Inconsistently indented YAML + +If your input is inconsistently indented, such indentation cannot be +preserved. The first round-trip will make it consistent/normalize it. +Here are some inconsistently indented YAML examples. + +`b` indented 3, `c` indented 4 positions: + + a: + b: + c: 1 + +Top level sequence is indented 2 without offset, the other sequence 4 +(with offset 2): + + - key: + - foo + - bar + +### Indenting using `typ="safe"` + +The C based emitter doesn't have the fine control, distinguishing between +block mappings and sequences. Do only use the `pure` Python versions +of the dumper if you want to have that sort of control. + + +## Positioning ':' in top level mappings, prefixing ':' + +If you want your toplevel mappings to look like: + + library version: 1 + comment : | + this is just a first try + +then set `yaml.top_level_colon_align = True` (and `yaml.indent = 4`). +`True` causes calculation based on the longest key, but you can also +explicitly set a number. + +If you want an extra space between a mapping key and the colon specify +`yaml.prefix_colon = ' '`: + + - https://myurl/abc.tar.xz : 23445 + # ^ extra space here + - https://myurl/def.tar.xz : 944 + +If you combine `prefix_colon` with `top_level_colon_align`, the top +level mapping doesn\'t get the extra prefix. If you want that anyway, +specify `yaml.top_level_colon_align = 12` where `12` has to be an +integer that is one more than length of the widest key. + +### Document version support + +In YAML a document version can be explicitly set by using: + + %YAML 1.x + +before the document start (at the top or before a `---`). For +`ruamel.yaml` x has to be 1 or 2. If no explicit version is set [version +1.2](http://www.yaml.org/spec/1.2/spec.html) is assumed (which has been +released in 2009). + +The 1.2 version does **not** support: + +- sexagesimals like `12:34:56` +- octals that start with 0 only: like `012` for number 10 (`0o12` + **is** supported by YAML 1.2) +- Unquoted Yes and On as alternatives for True and No and Off for + False. + +If you cannot change your YAML files and you need them to load as 1.1 +you can load with `yaml.version = (1, 1)`, or the equivalent (version +can be a tuple, list or string) `yaml.version = "1.1"` + +*If you cannot change your code, stick with ruamel.yaml==0.10.23 and let +me know if it would help to be able to set an environment variable.* + +This does not affect dump as ruamel.yaml never emitted sexagesimals, nor +octal numbers, and emitted booleans always as true resp. false + +### Round trip including comments + +The major motivation for this fork is the round-trip capability for +comments. The integration of the sources was just an initial step to +make this easier. + +#### adding/replacing comments + +Starting with version 0.8, you can add/replace comments on block style +collections (mappings/sequences resuting in Python dict/list). The basic +for for this is: +--- !python | + from __future__ import print_function + + import sys + import ruamel.yaml + + yaml = ruamel.yaml.YAML() # defaults to round-trip + + inp = """\ + abc: + - a # comment 1 + xyz: + a: 1 # comment 2 + b: 2 + c: 3 + d: 4 + e: 5 + f: 6 # comment 3 + """ + + data = yaml.load(inp) + data['abc'].append('b') + data['abc'].yaml_add_eol_comment('comment 4', 1) # takes column of comment 1 + data['xyz'].yaml_add_eol_comment('comment 5', 'c') # takes column of comment 2 + data['xyz'].yaml_add_eol_comment('comment 6', 'e') # takes column of comment 3 + data['xyz'].yaml_add_eol_comment('comment 7\n\n# that\'s all folks', 'd', column=20) + + yaml.dump(data, sys.stdout) +--- !stdout | +Resulting in:: +--- !comment | + abc: + - a # comment 1 + - b # comment 4 + xyz: + a: 1 # comment 2 + b: 2 + c: 3 # comment 5 + d: 4 # comment 7 + e: 5 # comment 6 + f: 6 # comment 3 + +--- | +If the comment doesn\'t start with \'#\', this will be added. The key is +the element index for list, the actual key for dictionaries. As can be +seen from the example, the column to choose for a comment is derived +from the previous, next or preceding comment column (picking the first +one found). + +Make sure that the added comment is correct, in the sense that when it +contains newlines, the following is either an empty line or a line with +only spaces, or the first non-space is a `#`. + +# Config file formats + +There are only a few configuration file formats that are easily readable +and editable: JSON, INI/ConfigParser, YAML (XML is to cluttered to be +called easily readable). + +Unfortunately [JSON](http://www.json.org/) doesn\'t support comments, +and although there are some solutions with pre-processed filtering of +comments, there are no libraries that support round trip updating of +such commented files. + +INI files support comments, and the excellent +[ConfigObj](http://www.voidspace.org.uk/python/configobj.html) library +by Foord and Larosa even supports round trip editing with comment +preservation, nesting of sections and limited lists (within a value). +Retrieval of particular value format is explicit (and extensible). + +YAML has basic mapping and sequence structures as well as support for +ordered mappings and sets. It supports scalars various types including +dates and datetimes (missing in JSON). YAML has comments, but these are +normally thrown away. + +Block structured YAML is a clean and very human readable format. By +extending the Python YAML parser to support round trip preservation of +comments, it makes YAML a very good choice for configuration files that +are human readable and editable while at the same time interpretable and +modifiable by a program. + +# Extending + +There are normally six files involved when extending the roundtrip +capabilities: the reader, parser, composer and constructor to go from +YAML to Python and the resolver, representer, serializer and emitter to +go the other way. + +Extending involves keeping extra data around for the next process step, +eventuallly resulting in a different Python object (subclass or +alternative), that should behave like the original, but on the way from +Python to YAML generates the original (or at least something much +closer). + +# Smartening + +When you use round-tripping, then the complex data you get are already +subclasses of the built-in types. So you can patch in extra methods or +override existing ones. Some methods are already included and you can +do: + + yaml_str = """\ + a: + - b: + c: 42 + - d: + f: 196 + e: + g: 3.14 + """ + + + data = yaml.load(yaml_str) + + assert data.mlget(['a', 1, 'd', 'f'], list_ok=True) == 196 diff --git a/_doc/dumpcls.ryd b/_doc/dumpcls.ryd new file mode 100644 index 0000000..048cdeb --- /dev/null +++ b/_doc/dumpcls.ryd @@ -0,0 +1,315 @@ +version: 0.2 +text: md +pdf: false +# code_directory: ../_example +--- | +# Working with Python classes + +## Dumping Python classes + +Only `yaml = YAML(typ='unsafe')` loads and dumps Python objects +out-of-the-box. And since it loads **any** Python object, this can be +unsafe, so don't use it. + +If you have instances of some class(es) that you want to dump or load, +it is easy to allow the YAML instance to do that explicitly. You can +either register the class with the `YAML` instance or decorate the +class. + +Registering is done with `YAML.register_class()`: +--- !python | + +import sys +import ruamel.yaml + + +class User: + def __init__(self, name, age): + self.name = name + self.age = age + + +yaml = ruamel.yaml.YAML() +yaml.register_class(User) +yaml.dump([User('Anthon', 18)], sys.stdout) +--- !stdout | +which gives as output:: +--- | +The tag `!User` originates from the name of the class. + +You can specify a different tag by adding the attribute `yaml_tag`, and +explicitly specify dump and/or load *classmethods* which have to be +named `to_yaml` resp. `from_yaml`: +--- !python | +import sys +import ruamel.yaml + + +class User: + yaml_tag = u'!user' + + def __init__(self, name, age): + self.name = name + self.age = age + + @classmethod + def to_yaml(cls, representer, node): + return representer.represent_scalar(cls.yaml_tag, + u'{.name}-{.age}'.format(node, node)) + + @classmethod + def from_yaml(cls, constructor, node): + return cls(*node.value.split('-')) + + +yaml = ruamel.yaml.YAML() +yaml.register_class(User) +yaml.dump([User('Anthon', 18)], sys.stdout) +--- !stdout | +which gives as output:: + +--- | +When using the decorator, which takes the `YAML()` instance as a +parameter, the `yaml = YAML()` line needs to be moved up in the file: +--- !python | +import sys +from ruamel.yaml import YAML, yaml_object + +yaml = YAML() + + +@yaml_object(yaml) +class User: + yaml_tag = u'!user' + + def __init__(self, name, age): + self.name = name + self.age = age + + @classmethod + def to_yaml(cls, representer, node): + return representer.represent_scalar(cls.yaml_tag, + u'{.name}-{.age}'.format(node, node)) + + @classmethod + def from_yaml(cls, constructor, node): + return cls(*node.value.split('-')) + + +yaml.dump([User('Anthon', 18)], sys.stdout) + +--- | +The `yaml_tag`, `from_yaml` and `to_yaml` work in the same way as when +using `.register_class()`. + +Alternatively you can use the `register_class()` method as decorator, +This also requires you have the yaml instance available: +--- !python | +import sys +import ruamel.yaml + +yaml = ruamel.yaml.YAML() + +@yaml.register_class +class User: + yaml_tag = u'!user' + + def __init__(self, name, age): + self.name = name + self.age = age + + @classmethod + def to_yaml(cls, representer, node): + return representer.represent_scalar(cls.yaml_tag, + u'{.name}-{.age}'.format(node, node)) + + @classmethod + def from_yaml(cls, constructor, node): + return cls(*node.value.split('-')) + + +yaml.dump([User('Anthon', 18)], sys.stdout) + +--- !stdout | + +This also gives: + +--- | + +If your class is dumped as a YAML mapping or sequence, there might be an (indirect) +reference to the object itself in one or more of the mapping keys (in YAML these +don't have to be simple scalars), mapping values or sequence entries. + +That means that re-creating an object in `to_yaml` cannot generally just create +a `dict`/`list` from the `node` parameter and then create and return a complete +object. The solution for this is to create an empty object and yield that +and then fill in the content data afterwards. That way, if there is a self +reference, and the same node is encountered *while creating the content for the +object*, there is an `id` (from the yielded object) created for that node which +can be assigned. + +--- !python | + +from pathlib import Path +import ruamel.yaml + +class Person: + def __init__(self, name, siblings=None): + self.name = name + self.siblings = [] if siblings is None else siblings + +arya = Person('Arya') +sansa = Person('Sansa') +arya.siblings.append(sansa) # there are better ways to represent this +sansa.siblings.append(arya) + +yaml = ruamel.yaml.YAML() +yaml.register_class(Person) + +path = Path('/tmp/arya.yaml') +yaml.dump(arya, path) +print(path.read_text()) + +--- !stdout | + +dumping as: + +--- | + +And you can load the output: + +--- !python | + +from pathlib import Path +import ruamel.yaml + +class Person: + def __init__(self, name, siblings=None): + self.name = name + self.siblings = [] if siblings is None else siblings + + def __repr__(self): + return f'Person(name: {self.name}, siblings: {self.siblings})' + +path = Path('/tmp/arya.yaml') +yaml = ruamel.yaml.YAML() +yaml.register_class(Person) +data = yaml.load(path) + +print(data) + +--- !stdout | + +giving: +--- | + +But if you provide a (to) simple loader: + +--- !python | + +from pathlib import Path +import ruamel.yaml + +class Person: + def __init__(self, name, siblings=None): + self.name = name + self.siblings = [] if siblings is None else siblings + + def __repr__(self): + return f'Person(name: {self.name}, siblings: {self.siblings})' + + @classmethod + def from_yaml(cls, constructor, node): + data = ruamel.yaml.CommentedMap() + constructor.construct_mapping(node, maptyp=data, deep=True) + return cls(**data) + + +path = Path('/tmp/arya.yaml') +yaml = ruamel.yaml.YAML() +yaml.register_class(Person) +data = yaml.load(path) +print(data) + +--- !stdout | + +giving: + +--- | +As you can see, Sansa has no normal siblings after this load. + +What you need to do is yield the empty Person instance and fill it in +afterwards: + +--- !python | + +from pathlib import Path +import ruamel.yaml + +class Person: + def __init__(self, name, siblings=None): + self.name = name + self.siblings = [] if siblings is None else siblings + + def __repr__(self): + return f'Person(name: {self.name}, siblings: {self.siblings})' + + @classmethod + def from_yaml(cls, constructor, node): + person = Person(name='') + yield person + data = ruamel.yaml.CommentedMap() + constructor.construct_mapping(node, maptyp=data, deep=True) + for k, v in data.items(): + setattr(person, k, v) + + +path = Path('/tmp/arya.yaml') +yaml = ruamel.yaml.YAML() +yaml.register_class(Person) +data = yaml.load(path) +print(data) + +--- !stdout | + +giving: + +--- | + +## Dataclass + +Although you could always register dataclasses, in 0.17.34 support was added to +call `__post_init__()` on these classes, if available. + + +--- !python | + +from typing import ClassVar +from dataclasses import dataclass +import ruamel.yaml + +@dataclass +class DC: + yaml_tag: ClassVar = '!dc_example' # if you don't want !DC as tag + abc: int + klm: int + xyz: int = 0 + + def __post_init__(self) -> None: + self.xyz = self.abc + self.klm + +yaml = ruamel.yaml.YAML() +yaml.register_class(DC) +dc = DC(abc=5, klm=42) +assert dc.xyz == 47 + +yaml_str = """\ +!dc_example +abc: 13 +klm: 37 +""" +dc2 = yaml.load(yaml_str) +print(f'{dc2.xyz=}') + +--- !stdout | +printing: diff --git a/_doc/example.ryd b/_doc/example.ryd new file mode 100644 index 0000000..4b431cd --- /dev/null +++ b/_doc/example.ryd @@ -0,0 +1,255 @@ +version: 0.2 +text: md +pdf: false +--- | +# Examples + +Basic round trip of parsing YAML to Python objects, modifying and +generating YAML: +--- !python | + import sys + from ruamel.yaml import YAML + + inp = """\ + # example + name: + # details + family: Smith # very common + given: Alice # one of the siblings + """ + + yaml = YAML() + code = yaml.load(inp) + code['name']['given'] = 'Bob' + + yaml.dump(code, sys.stdout) + +--- !stdout | +Resulting in:: +--- | +------------------------------------------------------------------------ + +YAML handcrafted anchors and references as well as key merging are +preserved. The merged keys can transparently be accessed using `[]` and +`.get()`: +--- !python | + from ruamel.yaml import YAML + + inp = """\ + - &CENTER {x: 1, y: 2} + - &LEFT {x: 0, y: 2} + - &BIG {r: 10} + - &SMALL {r: 1} + # All the following maps are equal: + # Explicit keys + - x: 1 + y: 2 + r: 10 + label: center/big + # Merge one map + - <<: *CENTER + r: 10 + label: center/big + # Merge multiple maps + - <<: [*CENTER, *BIG] + label: center/big + # Override + - <<: [*BIG, *LEFT, *SMALL] + x: 1 + label: center/big + """ + + yaml = YAML() + data = yaml.load(inp) + assert data[7]['y'] == 2 +--- | +The `CommentedMap`, which is the `dict` like construct one gets when +round-trip loading, supports insertion of a key into a particular +position, while optionally adding a comment: +--- !python | + import sys + from ruamel.yaml import YAML + + yaml_str = """\ + first_name: Art + occupation: Architect # This is an occupation comment + about: Art Vandelay is a fictional character that George invents... + """ + + yaml = YAML() + data = yaml.load(yaml_str) + data.insert(1, 'last name', 'Vandelay', comment="new key") + yaml.dump(data, sys.stdout) + +--- !stdout | +gives:: +--- | +Please note that the comment is aligned with that of its neighbour (if +available). + +The above was inspired by a +[question](http://stackoverflow.com/a/36970608/1307905) posted by +*demux* on StackOverflow. + +------------------------------------------------------------------------ + +By default `ruamel.yaml` indents with two positions in block style, for +both mappings and sequences. For sequences the indent is counted to the +beginning of the scalar, with the dash taking the first position of the +indented \"space\". + +You can change this default indentation by e.g. using `yaml.indent()`: +--- !python | + +import sys +from ruamel.yaml import YAML + +d = dict(a=dict(b=2),c=[3, 4]) +yaml = YAML() +yaml.dump(d, sys.stdout) +print('0123456789') +yaml = YAML() +yaml.indent(mapping=4, sequence=6, offset=3) +yaml.dump(d, sys.stdout) +print('0123456789') + + +--- !stdout | + +giving:: + + +--- | +If a block sequence or block mapping is the element of a sequence, the +are, by default, displayed +[compact](http://yaml.org/spec/1.2/spec.html#id2797686) notation. This +means that the dash of the \"parent\" sequence is on the same line as +the first element resp. first key/value pair of the child collection. + +If you want either or both of these (sequence within sequence, mapping +within sequence) to begin on the next line use `yaml.compact()`: +--- !python | + +import sys +from ruamel.yaml import YAML + +d = [dict(b=2), [3, 4]] +yaml = YAML() +yaml.dump(d, sys.stdout) +print('='*15) +yaml = YAML() +yaml.compact(seq_seq=False, seq_map=False) +yaml.dump(d, sys.stdout) + + +--- !stdout | + +giving:: + + +--- | +------------------------------------------------------------------------ + +The following program uses three dumps on the same data, resulting in a +stream with three documents: +--- !python | +import sys +from ruamel.yaml import YAML + +data = {1: {1: [{1: 1, 2: 2}, {1: 1, 2: 2}], 2: 2}, 2: 42} + +yaml = YAML() +yaml.explicit_start = True +yaml.dump(data, sys.stdout) +yaml.indent(sequence=4, offset=2) +yaml.dump(data, sys.stdout) + + +def sequence_indent_four(s): + # this will fail on direclty nested lists: {1; [[2, 3], 4]} + levels = [] + ret_val = '' + for line in s.splitlines(True): + ls = line.lstrip() + indent = len(line) - len(ls) + if ls.startswith('- '): + if not levels or indent > levels[-1]: + levels.append(indent) + elif levels: + if indent < levels[-1]: + levels = levels[:-1] + # same -> do nothing + else: + if levels: + if indent <= levels[-1]: + while levels and indent <= levels[-1]: + levels = levels[:-1] + ret_val += ' ' * len(levels) + line + return ret_val + +yaml = YAML() +yaml.explicit_start = True +yaml.dump(data, sys.stdout, transform=sequence_indent_four) + +--- !stdout | +gives as output:: + +--- | +The transform example, in the last document, was inspired by a [question +posted by \*nowox\*](https://stackoverflow.com/q/44388701/1307905) on +StackOverflow. + +------------------------------------------------------------------------ + +## Output of `dump()` as a string + +The single most abused "feature" of the old API is not providing the +(second) stream parameter to one of the `dump()` variants, in order to +get a monolithic string representation of the stream back. + +Apart from being memory inefficient and slow, quite often people using +this did not realise that `print(round_trip_dump(dict(a=1, b=2)))` gets +you an extra, empty, line after `b: 2`. + +The real question is why this functionality, which is seldom really +necessary, is available in the old API (and in PyYAML) in the first +place. One explanation you get by looking at what someone would need to +do to make this available if it weren\'t there already. Apart from +subclassing the `Serializer` and providing a new `dump` method, which +would ten or so lines, another **hundred** lines, essentially the whole +`dumper.py` file, would need to be copied and to make use of this +serializer. + +The fact is that one should normally be doing +`round_trip_dump(dict(a=1, b=2)), sys.stdout)` and do away with 90% of +the cases for returning the string, and that all post-processing YAML, +before writing to stream, can be handled by using the `transform=` +parameter of dump, being able to handle most of the rest. But it is also +much easier in the new API to provide that YAML output as a string if +you really need to have it (or think you do): +--- !python | +import sys +from ruamel.yaml import YAML +from ruamel.yaml.compat import StringIO + +class MyYAML(YAML): + def dump(self, data, stream=None, **kw): + inefficient = False + if stream is None: + inefficient = True + stream = StringIO() + YAML.dump(self, data, stream, **kw) + if inefficient: + return stream.getvalue() + +yaml = MyYAML() # or typ='safe'/'unsafe' etc +--- | +with about one tenth of the lines needed for the old interface, you can +once more do: +--- !code | +print(yaml.dump(dict(a=1, b=2))) +--- | +instead of: +--- !code | +yaml.dump((dict(a=1, b=2)), sys.stdout) +print() # or sys.stdout.write('\n') diff --git a/_doc/install.ryd b/_doc/install.ryd new file mode 100644 index 0000000..9edfc70 --- /dev/null +++ b/_doc/install.ryd @@ -0,0 +1,47 @@ +version: 0.2 +text: md +pdf: false +--- | +# Installing + +Make sure you have a recent version of `pip` and `setuptools` installed. +The later needs environment marker support (`setuptools>=20.6.8`) and +that is e.g. bundled with Python 3.4.6 but not with 3.4.4. It is +probably best to do: + + pip install -U pip setuptools wheel + +in your environment (`virtualenv`, (Docker) container, etc) before +installing `ruamel.yaml`. + +`ruamel.yaml` itself should be installed from [PyPI]() using: + + pip install ruamel.yaml + +If you want to process jinja2/YAML templates (which are not valid YAML +with the default jinja2 markers), do `pip install ruamel.yaml[jinja2]` +(you might need to quote the last argument because of the `[]`) + +There also is a commandline utility `yaml` available after installing: + + pip install ruamel.yaml.cmd + +that allows for round-trip testing/re-indenting and conversion of YAML +files (JSON,INI,HTML tables) + +## Optional requirements + +If you have the the header files for your Python executables installed +then you can use the (non-roundtrip), but faster, C loader and emitter. + +On Debian systems you should use: + + sudo apt-get install python3-dev + +you can leave out `python3-dev` if you don\'t use python3 + +For CentOS (7) based systems you should do: + + sudo yum install python-devel +--- !inc-raw | +links.rydinc diff --git a/_doc/links.rydinc b/_doc/links.rydinc new file mode 100644 index 0000000..7e840c7 --- /dev/null +++ b/_doc/links.rydinc @@ -0,0 +1,7 @@ + +.. _tox: https://pypi.python.org/pypi/tox +.. _py.test: http://pytest.org/latest/ +.. _YAML 1.1: http://www.yaml.org/spec/1.1/spec.html +.. _YAML 1.2: http://www.yaml.org/spec/1.2/spec.html +.. _PyPI: https://pypi.python.org/pypi +.. _ruamel.yaml: https://pypi.python.org/pypi/ruamel.yaml diff --git a/_doc/overview.ryd b/_doc/overview.ryd new file mode 100644 index 0000000..b10dc78 --- /dev/null +++ b/_doc/overview.ryd @@ -0,0 +1,47 @@ +version: 0.2 +text: md +pdf: false +--- | +# Overview + +`ruamel.yaml` is a YAML 1.2 loader/dumper package for Python. It is a +derivative of Kirill Simonov\'s [PyYAML +3.11](https://bitbucket.org/xi/pyyaml). + +`ruamel.yaml` supports [YAML 1.2]() and has round-trip loaders and +dumpers. A round-trip is a YAML load-modify-save sequence and +ruamel.yaml tries to preserve, among others: + +- comments +- block style and key ordering are kept, so you can diff the + round-tripped source +- flow style sequences ( \'a: b, c, d\') (based on request and test by + Anthony Sottile) +- anchor names that are hand-crafted (i.e. not of the form`idNNN`) +- [merges](http://yaml.org/type/merge.html) in dictionaries are + preserved + +This preservation is normally not broken unless you severely alter the +structure of a component (delete a key in a dict, remove list entries). +Reassigning values or replacing list items, etc., is fine. + +For the specific 1.2 differences see +`yaml-1-2-support`{.interpreted-text role="ref"} + +Although individual indentation of lines is not preserved, you can +specify separate indentation levels for mappings and sequences (counting +for sequences does **not** include the dash for a sequence element) and +specific offset of block sequence dashes within that indentation. + +Although `ruamel.yaml` still allows most of the PyYAML way of doing +things, adding features required a different API then the transient +nature of PyYAML\'s `Loader` and `Dumper`. Starting with `ruamel.yaml` +version 0.15.0 this new API gets introduced. Old ways that get in the +way will be removed, after first generating warnings on use, then +generating an error. In general a warning in version 0.N.x will become +an error in 0.N+1.0 + +Many of the bugs filed against PyYAML, but that were never acted upon, +have been fixed in `ruamel.yaml` +--- !inc-raw | +links.rydinc diff --git a/_doc/pyyaml.ryd b/_doc/pyyaml.ryd new file mode 100644 index 0000000..f670237 --- /dev/null +++ b/_doc/pyyaml.ryd @@ -0,0 +1,72 @@ +version: 0.2 +text: md +pdf: false +--- | +# Differences with PyYAML + +::: parsed-literal + +*If I have seen further, it is by standing on the shoulders of giants*. + +: Isaac Newton (1676) +::: + +`ruamel.yaml` is a derivative of Kirill Simonov\'s [PyYAML +3.11](https://bitbucket.org/xi/pyyaml) and would not exist without that +excellent base to start from. + +The following a summary of the major differences with PyYAML 3.11 + +## Defaulting to YAML 1.2 support + +PyYAML supports the [YAML 1.1]() standard, `ruamel.yaml` supports [YAML +1.2]() as released in 2009. + +- YAML 1.2 dropped support for several features unquoted `Yes`, `No`, + `On`, `Off` +- YAML 1.2 no longer accepts strings that start with a `0` and solely + consist of number characters as octal, you need to specify such + strings with `0o[0-7]+` (zero + lower-case o for octal + one or more + octal characters). +- YAML 1.2 no longer supports + [sexagesimals](https://en.wikipedia.org/wiki/Sexagesimal), so the + string scalar `12:34:56` doesn\'t need quoting. +- `\/` escape for JSON compatibility +- correct parsing of floating point scalars with exponentials + +unless the YAML document is loaded with an explicit `version==1.1` or +the document starts with: + + % YAML 1.1 + +, `ruamel.yaml` will load the document as version 1.2. + +## PY2/PY3 reintegration + +`ruamel.yaml` re-integrates the Python 2 and 3 sources, running on +Python 2.7 (CPython, PyPy), 3.3, 3.4, 3.5 and 3.6 (support for 2.6 has +been dropped mid 2016). It is more easy to extend and maintain as only a +miniscule part of the code is Python version specific. + +## Fixes + +- `ruamel.yaml` follows the `indent` keyword argument on scalars when + dumping. +- `ruamel.yaml` allows `:` in plain scalars, as long as these are not + followed by a space (as per the specification) + +## Testing + +`ruamel.yaml` is tested using [tox]() and [py.test](). In addition to +new tests, the original PyYAML test framework is called from within +`tox` runs. + +Before versions are pushed to PyPI, `tox` is invoked, and has to pass, +on all supported Python versions, on PyPI as well as flake8/pep8 + +## API + +Starting with 0.15 the API for using `ruamel.yaml` has diverged allowing +easier addition of new features. +--- !inc-raw +- links.rydinc |