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

Commit

Permalink
Move all I/O to diskutils
Browse files Browse the repository at this point in the history
  • Loading branch information
jacebrowning committed Feb 22, 2016
1 parent dec8f0c commit e5f2247
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 66 deletions.
65 changes: 48 additions & 17 deletions yorm/diskutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
log = common.logger(__name__)


def exists(path):
"""Determine if a path exists."""
return os.path.exists(path)


def touch(path):
"""Ensure a file path exists."""
if not os.path.exists(path):
Expand Down Expand Up @@ -79,43 +84,69 @@ def delete(path):
os.remove(path)


def load(text, path, ext='yml'):
def load(text, path):
"""Parse a dictionary a data from formatted text.
:param text: string containing dumped data
:param path: file path for error messages
:param path: file path to specify formatting
:return: dictionary of data
"""
data = {}
ext = _get_ext(path)
if ext in ['json']:
data = _load_json(text, path)
elif ext in ['yml', 'yaml']:
data = _load_yaml(text, path)
else:
log.warning("Unrecognized file extension: %s", ext)
data = _load_yaml(text, path)

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

return data


def _load_json(text, path):
try:
if ext in ['yml', 'yaml']:
data = yaml.load(text) or {}
elif ext in ['json']:
data = json.loads(text) or {}
except yaml.error.YAMLError as exc:
msg = "Invalid YAML contents: {}:\n{}".format(path, exc)
raise exceptions.ContentError(msg) from None
return json.loads(text) or {}
except json.JSONDecodeError as exc:
msg = "Invalid JSON contents: {}:\n{}".format(path, exc)
raise exceptions.ContentError(msg) from None

# Ensure data is a dictionary
if not isinstance(data, dict):
msg = "Invalid file contents: {}".format(path)
raise exceptions.ContentError(msg)

return data
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


def dump(data, ext):
"""Format a dictionary into a serialization format."""
def dump(data, path):
"""Format a dictionary into a serialization format.
:param text: dictionary of data to format
:param path: file path to specify formatting
:return: string of formatted data
"""
ext = _get_ext(path)

if ext in ['json']:
return json.dumps(data, indent=4, sort_keys=True)

if ext not in ['yml', 'yaml']:
log.warning("Unrecognized file extension: %s", ext)

return yaml.dump(data, default_flow_style=False, allow_unicode=True)


def _get_ext(path):
if '.' in path:
return path.split('.')[-1].lower()
else:
return 'yml'
52 changes: 22 additions & 30 deletions yorm/mapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Core object-file mapping functionality."""

import os
import functools
from pprint import pformat

Expand Down Expand Up @@ -105,7 +104,7 @@ def __init__(self, obj, path, attrs, auto=True):
self.auto = auto

self.auto_store = False
self.exists = self.path and os.path.isfile(self.path)
self.exists = diskutils.exists(self.path)
self.deleted = False
self._activity = False
self._timestamp = 0
Expand All @@ -114,28 +113,6 @@ def __init__(self, obj, path, attrs, auto=True):
def __str__(self):
return str(self.path)

@property
def text(self):
"""Get file contents."""
log.info("Getting contents of %s...", prefix(self))
if settings.fake:
text = self._fake
else:
text = self._read()
log.trace("Text read: \n%s", text[:-1])
return text

@text.setter
def text(self, text):
"""Set file contents."""
log.info("Setting contents of %s...", prefix(self))
if settings.fake:
self._fake = text
else:
self._write(text)
log.trace("Text wrote: \n%s", text[:-1])
self.modified = True

@property
def modified(self):
"""Determine if the file has been modified."""
Expand Down Expand Up @@ -164,11 +141,26 @@ def modified(self, changes):
log.debug("Marked %s as unmodified", prefix(self))

@property
def ext(self):
if '.' in self.path:
return self.path.split('.')[-1]
def text(self):
"""Get file contents."""
log.info("Getting contents of %s...", prefix(self))
if settings.fake:
text = self._fake
else:
text = self._read()
log.trace("Text read: \n%s", text[:-1])
return text

@text.setter
def text(self, text):
"""Set file contents."""
log.info("Setting contents of %s...", prefix(self))
if settings.fake:
self._fake = text
else:
return 'yml'
self._write(text)
log.trace("Text wrote: \n%s", text[:-1])
self.modified = True

def create(self):
"""Create a new file for the object."""
Expand All @@ -189,7 +181,7 @@ def fetch(self):

# Parse data from file
text = self._read()
data = diskutils.load(text=text, path=self.path, ext=self.ext)
data = diskutils.load(text=text, path=self.path)
log.trace("Loaded data: \n%s", pformat(data))

# Update all attributes
Expand Down Expand Up @@ -251,7 +243,7 @@ def store(self):
data[name] = data2

# Dump data to file
text = diskutils.dump(data=data, ext=self.ext)
text = diskutils.dump(data=data, path=self.path)
self._write(text)

# Set meta attributes
Expand Down
30 changes: 15 additions & 15 deletions yorm/test/test_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,6 @@ def it_creates_the_file_automatically(mapper_real):

expect(mapper_real.path).exists()

def describe_text():

def can_get_the_file_contents(obj, mapper):
obj.var3 = 42
mapper.store()

expect(mapper.text) == "var2: 0\nvar3: 42\n"

def can_set_the_file_contents(obj, mapper):
mapper.create()
mapper.text = "var2: 42\n"
mapper.fetch()

expect(obj.var2) == 42

def describe_modified():

def is_true_initially(mapper):
Expand Down Expand Up @@ -157,3 +142,18 @@ def can_be_set_true(mapper):
mapper.modified = True

expect(mapper.modified).is_true()

def describe_text():

def can_get_the_file_contents(obj, mapper):
obj.var3 = 42
mapper.store()

expect(mapper.text) == "var2: 0\nvar3: 42\n"

def can_set_the_file_contents(obj, mapper):
mapper.create()
mapper.text = "var2: 42\n"
mapper.fetch()

expect(obj.var2) == 42
8 changes: 4 additions & 4 deletions yorm/test/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,20 @@ def test_multiple(self):
with pytest.raises(exceptions.MappingError):
utilities.sync(sample, "sample.yml")

@patch('os.path.isfile', Mock(return_value=True))
@patch('yorm.diskutils.exists', Mock(return_value=True))
def test_init_existing(self):
"""Verify an existing file is read."""
with patch('yorm.diskutils.read', Mock(return_value="abc: 123")):
sample = utilities.sync(self.Sample(), "sample.yml")
assert 123 == sample.abc

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

@patch('os.path.isfile', Mock(return_value=True))
@patch('yorm.diskutils.exists', Mock(return_value=True))
def test_exception_when_file_not_expected_but_found(self):
utilities.sync(self.Sample(), "sample.yml", existing=True)
with pytest.raises(exceptions.FileAlreadyExistsError):
Expand Down Expand Up @@ -197,7 +197,7 @@ def test_with_attrs(self):
assert "sample.yml" == sample.__mapper__.path
assert ['var1'] == list(sample.__mapper__.attrs.keys())

@patch('os.path.isfile', Mock(return_value=True))
@patch('yorm.diskutils.exists', Mock(return_value=True))
def test_init_existing(self):
"""Verify an existing file is read."""
with patch('yorm.diskutils.read', Mock(return_value="abc: 123")):
Expand Down

0 comments on commit e5f2247

Please sign in to comment.