Skip to content
This repository has been archived by the owner on Oct 3, 2019. It is now read-only.

Commit

Permalink
Merge 2c099a3 into b1df346
Browse files Browse the repository at this point in the history
  • Loading branch information
jacebrowning committed Apr 12, 2016
2 parents b1df346 + 2c099a3 commit 11023c2
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 197 deletions.
4 changes: 3 additions & 1 deletion .pep8rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[pep8]

# E501: line too long (checked by PyLint)
ignore = E501
# E711: comparison to None (checked by PyLint)
# E712: comparison to True (checked by PyLint)
ignore = E501,E711,E712
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Revision History

## 0.8 (unreleased)

- Replaced all utility functions with ORM-like tools.
- Removed the ability to check for existing files in `sync()`.
- Renamed and consolidated custom exceptions.

## 0.7.2 (2016/03/30)

- Now preserving order of `attr` decorators on `Dictionary` converters.
Expand Down
29 changes: 15 additions & 14 deletions tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,27 @@ def test_top():


def test_from_top_constants():
from yorm import UUID # filename placeholder
from yorm import UUID


def test_from_top_clases():
from yorm import Mappable # base class for mapped objects
from yorm import Converter, Container # base class for types
from yorm import Mappable
from yorm import Converter, Container


def test_from_top_decorators():
from yorm import sync # enables mapping on a class's instance objects
from yorm import sync_instances # alias for the class decorator
from yorm import attr # alternate API to identify mapped attributes


def test_from_top_functions():
from yorm import sync # enables mapping on an instance object
from yorm import sync_object # alias for the mapping function
from yorm import update # fetch (if necessary) and store a mapped object
from yorm import update_object # fetch (optional force) a mapped object
from yorm import update_file # store a mapped object
from yorm import sync
from yorm import sync_instances
from yorm import sync_object
from yorm import attr


def test_from_top_utilities():
from yorm import new
from yorm import find
from yorm import load
from yorm import save
from yorm import delete


def test_from_nested():
Expand Down
4 changes: 0 additions & 4 deletions tests/test_nested_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,6 @@ def _log_ref(name, var, ref):
assert ref == var

def test_alias_list(self):
yorm.update_object(self.sample)
var4_ref = self.sample.var4
self._log_ref('var4', self.sample.var4, var4_ref)
assert [] == self.sample.var4
Expand All @@ -308,7 +307,6 @@ def test_alias_list(self):
assert [42, 2015] == self.sample.var4

def test_alias_dict(self):
yorm.update_object(self.sample)
var5_ref = self.sample.var5
self._log_ref('var5', self.sample.var5, var5_ref)
assert {'status': False} == self.sample.var5
Expand All @@ -329,7 +327,6 @@ def test_alias_dict_in_list(self):
ref1 = top.nested_list[0]
ref2 = top.nested_list[0].nested_dict_3
ref3 = top.nested_list[0].nested_dict_3.nested_list_3
yorm.update(top)
assert id(ref1) == id(top.nested_list[0])
assert id(ref2) == id(top.nested_list[0].nested_dict_3)
assert id(ref3) == id(top.nested_list[0].nested_dict_3.nested_list_3)
Expand All @@ -341,6 +338,5 @@ def test_alias_list_in_dict(self):
logging.info("Storing refs...")
ref1 = top.nested_dict
ref2 = top.nested_dict.nested_list_2
yorm.update(top)
assert id(ref1) == id(top.nested_dict)
assert id(ref2) == id(top.nested_dict.nested_list_2)
17 changes: 2 additions & 15 deletions tests/test_persistence_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, key, name=None, root=None):

@yorm.attr(key=yorm.types.String)
@yorm.attr(name=yorm.types.String)
@yorm.sync("{self.root}/{self.key}/config.yml")
@yorm.sync("{self.root}/{self.key}/config.yml", auto=False)
class ConfigModel:

def __init__(self, key, root):
Expand All @@ -41,21 +41,9 @@ class ConfigStore:

def __init__(self, root):
self.root = root
self.path = self.root + "/{}/config.yml"
self.attrs = dict(key=yorm.types.String,
name=yorm.types.String)

def read(self, key):
instance = Config(key)
path = self.path.format(key)
attrs = self.attrs
try:
yorm.sync(instance, path, attrs, existing=True, auto=False)
except yorm.exceptions.FileMissingError:
return None
else:
yorm.update_object(instance)
return instance
return yorm.find(ConfigModel, self.root, key)


# TESTS ########################################################################
Expand Down Expand Up @@ -85,7 +73,6 @@ def test_create_dm_from_pm(self):
def test_nonmapped_attribute_is_kept(self):
model = ConfigModel('my_key', self.root)
model.unmapped = 42
yorm.update(model, force=True)
assert 42 == model.unmapped


Expand Down
4 changes: 2 additions & 2 deletions yorm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys

__project__ = 'YORM'
__version__ = '0.7.2'
__version__ = '0.8.dev1'

VERSION = __project__ + '-' + __version__

Expand All @@ -17,7 +17,7 @@
from . import bases, types
from .common import UUID
from .decorators import sync, sync_object, sync_instances, attr
from .utilities import update, update_object, update_file
from .utilities import new, find, load, save, delete
from .bases import Container, Converter, Mappable, Convertible
except ImportError: # pragma: no cover (manual test)
pass
22 changes: 10 additions & 12 deletions yorm/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from . import common
from .bases.mappable import patch_methods
from .mapper import Mapper

# TODO: remove this after refactor
from .utilities import _check_base, _check_existance
from .utilities import _ensure_mapped

log = common.logger(__name__)

Expand All @@ -30,34 +28,33 @@ def sync(*args, **kwargs):
return sync_object(*args, **kwargs)


def sync_object(instance, path, attrs=None, existing=None, **kwargs):
def sync_object(instance, path, attrs=None, **kwargs):
"""Enable YAML mapping on an object.
:param instance: object to patch with YAML mapping behavior
:param path: file path for dump/load
:param attrs: dictionary of attribute names mapped to converter classes
:param existing: indicate if file is expected to exist or not
:param auto: automatically store attributes to file
:param strict: ignore new attributes in files
"""
log.info("Mapping %r to %s...", instance, path)
_check_base(instance, mappable=False)
_ensure_mapped(instance, expected=False)

patch_methods(instance)

attrs = _ordered(attrs) or common.attrs[instance.__class__]
mapper = Mapper(instance, path, attrs, **kwargs)
common.set_mapper(instance, mapper)
_check_existance(mapper, existing)

if mapper.auto:
if not mapper.exists:
if mapper.missing:
mapper.create()
mapper.store()
mapper.fetch()

common.set_mapper(instance, mapper)
log.info("Mapped %r to %s", instance, path)

return instance


Expand All @@ -67,15 +64,16 @@ def sync_instances(path_format, format_spec=None, attrs=None, **kwargs):
:param path_format: formatting string to create file paths for dump/load
:param format_spec: dictionary to use for string formatting
:param attrs: dictionary of attribute names mapped to converter classes
:param existing: indicate if file is expected to exist or not
:param auto: automatically store attributes to file
:param strict: ignore new attributes in files
"""
format_spec = format_spec or {}
attrs = attrs or OrderedDict()

def decorator(cls):
"""Class decorator to map instances to files.."""
"""Class decorator to map instances to files."""

init = cls.__init__

Expand Down
6 changes: 3 additions & 3 deletions yorm/diskutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def load(text, path):

if not isinstance(data, dict):
msg = "Invalid file contents: {}".format(path)
raise exceptions.ContentError(msg)
raise exceptions.FileContentError(msg)

return data

Expand All @@ -114,15 +114,15 @@ def _load_json(text, path):
return json.loads(text) or {}
except json.JSONDecodeError as exc:
msg = "Invalid JSON contents: {}:\n{}".format(path, exc)
raise exceptions.ContentError(msg) from None
raise exceptions.FileContentError(msg) from None


def _load_yaml(text, path):
try:
return yaml.load(text) or {}
except yaml.error.YAMLError as exc:
msg = "Invalid YAML contents: {}:\n{}".format(path, exc)
raise exceptions.ContentError(msg) from None
raise exceptions.FileContentError(msg) from None


def dump(data, path):
Expand Down
20 changes: 4 additions & 16 deletions yorm/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,13 @@ class Error(Exception, metaclass=ABCMeta):
"""Base class for all YORM exceptions."""


class FileMissingError(Error, FileNotFoundError):
"""A file was expected to exist."""
class DuplicateMappingError(Error, FileExistsError):
"""A file path is already in use by another mapping."""


class FileAlreadyExistsError(Error, FileExistsError):
"""A file was not expected to exist."""


class FileDeletedError(Error, FileNotFoundError):
class DeletedFileError(Error, FileNotFoundError):
"""Text could not be read from a deleted file."""


class ContentError(Error, yaml.error.YAMLError, ValueError):
class FileContentError(Error, yaml.error.YAMLError, ValueError):
"""Text could not be parsed as valid YAML."""


class ConversionError(Error, ValueError):
"""Value could not be converted to the specified type."""


class MappingError(Error):
"""The API was called incorrectly."""
6 changes: 5 additions & 1 deletion yorm/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def wrapped(self, *args, **kwargs):
self.create()
else:
msg = "Cannot access deleted: {}".format(self.path)
raise exceptions.FileDeletedError(msg)
raise exceptions.DeletedFileError(msg)

return method(self, *args, **kwargs)

Expand Down Expand Up @@ -97,6 +97,10 @@ def __init__(self, obj, path, attrs, *, auto=True, strict=True):
def __str__(self):
return str(self.path)

@property
def missing(self):
return not self.exists

@property
def modified(self):
"""Determine if the file has been modified."""
Expand Down
15 changes: 1 addition & 14 deletions yorm/test/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import pytest
from expecter import expect

from yorm import exceptions
from yorm import decorators
from yorm.bases import Converter

Expand Down Expand Up @@ -55,7 +54,7 @@ def test_with_attrs(self):
def test_multiple(self):
"""Verify mapping cannot be enabled twice."""
sample = decorators.sync(self.Sample(), "sample.yml")
with pytest.raises(exceptions.MappingError):
with pytest.raises(TypeError):
decorators.sync(sample, "sample.yml")

@patch('yorm.diskutils.exists', Mock(return_value=True))
Expand All @@ -65,18 +64,6 @@ def test_init_existing(self):
sample = decorators.sync(self.Sample(), "sample.yml", strict=False)
assert 123 == sample.abc

@patch('yorm.diskutils.exists', Mock(return_value=False))
def test_exception_when_file_expected_but_missing(self):
decorators.sync(self.Sample(), "sample.yml", existing=False)
with pytest.raises(exceptions.FileMissingError):
decorators.sync(self.Sample(), "sample.yml", existing=True)

@patch('yorm.diskutils.exists', Mock(return_value=True))
def test_exception_when_file_not_expected_but_found(self):
decorators.sync(self.Sample(), "sample.yml", existing=True)
with pytest.raises(exceptions.FileAlreadyExistsError):
decorators.sync(self.Sample(), "sample.yml", existing=False)


@patch('yorm.diskutils.write', Mock())
@patch('yorm.diskutils.stamp', Mock())
Expand Down
2 changes: 1 addition & 1 deletion yorm/test/test_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def it_infers_types_on_new_attributes_when_not_strict(obj, mapper):
def it_raises_an_exception_after_delete(mapper):
mapper.delete()

with expect.raises(exceptions.FileDeletedError):
with expect.raises(exceptions.DeletedFileError):
mapper.fetch()

def describe_store():
Expand Down
Loading

0 comments on commit 11023c2

Please sign in to comment.