Skip to content

Commit

Permalink
Merge pull request #152 from jacebrowning/pyyaml-support
Browse files Browse the repository at this point in the history
Add support for using PyYAML as the formatter
  • Loading branch information
jacebrowning committed Feb 9, 2020
2 parents af6494b + c85701d commit b3dd417
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 78 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
# 0.7 (unreleased)

- Added a `YAML_LIBRARY` setting to control the underlying YAML library.

# 0.6 (2020-01-25)

- Added a registration system for custom formatter classes.
Expand Down
57 changes: 53 additions & 4 deletions datafiles/formats.py
@@ -1,3 +1,5 @@
# pylint: disable=import-outside-toplevel

import json
from abc import ABCMeta, abstractmethod
from contextlib import suppress
Expand All @@ -6,8 +8,6 @@
from typing import IO, Any, Dict, List

import log
import tomlkit
from ruamel import yaml

from . import settings

Expand Down Expand Up @@ -64,22 +64,28 @@ def extensions(cls):

@classmethod
def deserialize(cls, file_object):
import tomlkit

return tomlkit.loads(file_object.read()) or {}

@classmethod
def serialize(cls, data):
import tomlkit

return tomlkit.dumps(data)


class YAML(Formatter):
"""Formatter for (safe, round-trip) YAML Ain't Markup Language."""
class RuamelYAML(Formatter):
"""Formatter for (round-trip) YAML Ain't Markup Language."""

@classmethod
def extensions(cls):
return {'.yml', '.yaml'}

@classmethod
def deserialize(cls, file_object):
from ruamel import yaml

try:
return yaml.YAML(typ='rt').load(file_object) or {}
except NotImplementedError as e:
Expand All @@ -88,6 +94,8 @@ def deserialize(cls, file_object):

@classmethod
def serialize(cls, data):
from ruamel import yaml

if settings.INDENT_YAML_BLOCKS:
f = StringIO()
y = yaml.YAML()
Expand All @@ -100,6 +108,44 @@ def serialize(cls, data):
return "" if text == "{}\n" else text


class PyYAML(Formatter):
"""Formatter for YAML Ain't Markup Language."""

@classmethod
def extensions(cls):
return {'.yml', '.yaml'}

@classmethod
def deserialize(cls, file_object):
import yaml

data = yaml.safe_load(file_object)

return data

@classmethod
def serialize(cls, data):
import yaml

def represent_none(self, _):
return self.represent_scalar('tag:yaml.org,2002:null', '')

yaml.add_representer(type(None), represent_none)

class Dumper(yaml.Dumper):
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(
flow=flow,
indentless=False if settings.INDENT_YAML_BLOCKS else indentless,
)

text = yaml.dump( # type: ignore
data, Dumper=Dumper, sort_keys=False, default_flow_style=False
)

return text


def deserialize(path: Path, extension: str) -> Dict:
formatter = _get_formatter(extension)
with path.open('r') as file_object:
Expand All @@ -112,6 +158,9 @@ def serialize(data: Dict, extension: str = '.yml') -> str:


def _get_formatter(extension: str):
if settings.YAML_LIBRARY == 'PyYAML':
register('.yml', PyYAML)

with suppress(KeyError):
return _REGISTRY[extension]

Expand Down
4 changes: 4 additions & 0 deletions datafiles/settings.py
@@ -1,5 +1,9 @@
"""Shared configuration flags."""

HIDE_TRACEBACK_IN_HOOKS = True

HOOKS_ENABLED = True

INDENT_YAML_BLOCKS = True

YAML_LIBRARY = 'ruamel.yaml'
32 changes: 29 additions & 3 deletions datafiles/tests/test_formats.py
Expand Up @@ -11,9 +11,9 @@ def describe_serialize():
def data():
return {'key': "value", 'items': [1, 'a', None]}

def describe_yaml():
def describe_ruamel_yaml():
def it_indents_blocks_by_default(expect, data):
text = formats.YAML.serialize(data)
text = formats.RuamelYAML.serialize(data)
expect(text) == dedent(
"""
key: value
Expand All @@ -26,7 +26,33 @@ def it_indents_blocks_by_default(expect, data):

def it_can_render_lists_inline(expect, data, monkeypatch):
monkeypatch.setattr(settings, 'INDENT_YAML_BLOCKS', False)
text = formats.YAML.serialize(data)
text = formats.RuamelYAML.serialize(data)
expect(text) == dedent(
"""
key: value
items:
- 1
- a
-
"""
)

def describe_pyyaml():
def it_indents_blocks_by_default(expect, data):
text = formats.PyYAML.serialize(data)
expect(text) == dedent(
"""
key: value
items:
- 1
- a
-
"""
)

def it_can_render_lists_inline(expect, data, monkeypatch):
monkeypatch.setattr(settings, 'INDENT_YAML_BLOCKS', False)
text = formats.PyYAML.serialize(data)
expect(text) == dedent(
"""
key: value
Expand Down
7 changes: 7 additions & 0 deletions docs/settings.md
Expand Up @@ -52,3 +52,10 @@ items:
- 2
- 3
```

# `YAML_LIBRARY`

This setting controls the underlying YAML library used to read and write files. The following options are available:

- `'ruamel.yaml'` (default)
- `'PyYAML'`
84 changes: 15 additions & 69 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
@@ -1,7 +1,7 @@
[tool.poetry]

name = "datafiles"
version = "0.7b1"
version = "0.7b2"
description = "File-based ORM for dataclasses."

license = "MIT"
Expand Down Expand Up @@ -42,6 +42,7 @@ classifiers = [
python = "^3.7"

# Formats
PyYAML = "^5.3"
"ruamel.yaml" = "^0.16.7"
tomlkit = "^0.5.3"

Expand Down Expand Up @@ -70,7 +71,7 @@ pydocstyle = "*"
# Testing
pytest = "^5.3.2"
pytest-describe = "*"
pytest-expecter = "*"
pytest-expecter = "^2.0"
pytest-mock = "*"
pytest-random = "*"
pytest-repeat = "*"
Expand Down

0 comments on commit b3dd417

Please sign in to comment.