Skip to content

Commit

Permalink
Merge pull request #245 from jacebrowning/handle-comment-blocks
Browse files Browse the repository at this point in the history
Handle commented blocks in YAML
  • Loading branch information
jacebrowning committed Nov 20, 2021
2 parents 07704c5 + 8b0e53e commit d99dda0
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 116 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 1.1 (beta)

- Added support for Python 3.10's builtin optional types (e.g. `int | None`).
- Fixed handling of commented blocks in YAML files.

# 1.0 (2021-10-04)

Expand Down
6 changes: 6 additions & 0 deletions datafiles/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from typing import Any, Dict, Mapping, Optional, Union

import log
from ruamel.yaml.comments import CommentedMap
from ruamel.yaml.scalarfloat import ScalarFloat
from ruamel.yaml.scalarstring import DoubleQuotedScalarString

from ..utils import cached, subclasses
from ._bases import Converter
Expand All @@ -31,6 +33,10 @@ def register(cls: Union[type, str], converter: type):
register(ScalarFloat, Float)
register(Boolean.TYPE, Boolean)
register(String.TYPE, String)
register(DoubleQuotedScalarString, String)
register(CommentedMap, Dictionary)
register(list, List)
register(dict, Dictionary)


def resolve(annotation, obj=None):
Expand Down
10 changes: 7 additions & 3 deletions datafiles/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import dataclasses
import inspect
import os
from contextlib import suppress
from pathlib import Path
from typing import Any, Dict, Optional

Expand Down Expand Up @@ -98,7 +99,8 @@ def infer(self) -> bool:

@property
def data(self) -> Dict:
return self._get_data()
with hooks.disabled():
return self._get_data()

def _get_data(self, include_default_values: Trilean = None) -> Dict:
log.debug(f'Preserializing object to data: {self._instance!r}')
Expand Down Expand Up @@ -188,7 +190,8 @@ def load(self, *, _log=True, _first_load=False) -> None:
def _infer_attr(name, value):
cls: Any = type(value)
if issubclass(cls, list):
cls.__origin__ = list
with suppress(TypeError):
cls.__origin__ = list
if value:
item_cls = type(value[0])
for item in value:
Expand All @@ -203,7 +206,8 @@ def _infer_attr(name, value):
return map_type(cls, name=name, item_cls=item_cls) # type: ignore

if issubclass(cls, dict):
cls.__origin__ = dict
with suppress(TypeError):
cls.__origin__ = dict
log.debug(f'Inferring {name!r} type: {cls}')
return map_type(cls, name=name, item_cls=Converter)

Expand Down
10 changes: 10 additions & 0 deletions datafiles/tests/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ def it_handles_dataclasses(expect):
'flag': converters.Boolean,
}

@pytest.mark.parametrize(
("cls", "converter"),
[
(list, converters.List),
(dict, converters.Dictionary),
],
)
def it_handles_container_literals(expect, cls, converter):
expect(converters.map_type(cls)) == converter

def it_handles_enums(expect):
converter = converters.map_type(Color)
expect(converter.__name__) == 'ColorConverter'
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

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

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]

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

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

# Formats
"ruamel.yaml" = "^0.17.16"
"ruamel.yaml" = "^0.17.17"
tomlkit = "^0.7.2"

# ORM
Expand Down
233 changes: 126 additions & 107 deletions tests/test_file_inference.py
Original file line number Diff line number Diff line change
@@ -1,112 +1,131 @@
from datafiles import auto
from datafiles.utils import dedent, logbreak, read, write


def test_auto_with_sample_file(expect):
write(
'tmp/sample.yml',
"""
homogeneous_list:
- 1
- 2
heterogeneous_list:
- 1
- 'abc'
empty_list: []
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Reading attributes")
expect(sample.homogeneous_list) == [1, 2]
expect(sample.heterogeneous_list) == [1, 'abc']
expect(sample.empty_list) == []

logbreak("Updating attribute")
sample.homogeneous_list.append(3.4)
sample.heterogeneous_list.append(5.6)
sample.empty_list.append(7.8)

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
homogeneous_list:
- 1
- 2
- 3
heterogeneous_list:
- 1
- 'abc'
- 5.6
empty_list:
- 7.8
"""
)
# pylint: disable=unused-variable

import pytest

def test_float_inference(expect):
write(
'tmp/sample.yml',
"""
language: python
python:
- 3.7
- 3.8
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Updating attribute")
sample.python.append(4)

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
language: python
python:
- 3.7
- 3.8
- 4.0
"""
)

from datafiles import auto
from datafiles.utils import dedent, logbreak, read, write

def test_nested_mutables(expect):
write(
'tmp/sample.yml',
"""
name: Test
roles:
category1:
- value1
- value2
category2:
- something
- else
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Updating attributes")
sample.roles['category1'].append('value3')

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
name: Test
roles:
category1:
- value1
- value2
- value3
category2:
- something
- else
"""
def describe_auto():
@pytest.mark.parametrize(
('filename', 'count'),
[
('.appveyor.yml', 5),
('.travis.yml', 9),
('mkdocs.yml', 8),
('pyproject.toml', 2),
],
)
def with_real_file(expect, filename, count):
logbreak("Inferring object")
sample = auto(filename)

logbreak("Reading attributes")
expect(len(sample.datafile.data)) == count

def with_sample_file(expect):
write(
'tmp/sample.yml',
"""
homogeneous_list:
- 1
- 2
heterogeneous_list:
- 1
- 'abc'
empty_list: []
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Reading attributes")
expect(sample.homogeneous_list) == [1, 2]
expect(sample.heterogeneous_list) == [1, 'abc']
expect(sample.empty_list) == []

logbreak("Updating attribute")
sample.homogeneous_list.append(3.4)
sample.heterogeneous_list.append(5.6)
sample.empty_list.append(7.8)

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
homogeneous_list:
- 1
- 2
- 3
heterogeneous_list:
- 1
- 'abc'
- 5.6
empty_list:
- 7.8
"""
)

def with_floats(expect):
write(
'tmp/sample.yml',
"""
language: python
python:
- 3.7
- 3.8
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Updating attribute")
sample.python.append(4)

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
language: python
python:
- 3.7
- 3.8
- 4.0
"""
)

def with_nested_mutables(expect):
write(
'tmp/sample.yml',
"""
name: Test
roles:
category1:
- value1
- value2
category2:
- something
- else
""",
)

logbreak("Inferring object")
sample = auto('tmp/sample.yml')

logbreak("Updating attributes")
sample.roles['category1'].append('value3')

logbreak("Reading file")
expect(read('tmp/sample.yml')) == dedent(
"""
name: Test
roles:
category1:
- value1
- value2
- value3
category2:
- something
- else
"""
)

0 comments on commit d99dda0

Please sign in to comment.