Skip to content

Commit

Permalink
Make backend implementations not depends on container class.
Browse files Browse the repository at this point in the history
- Parser class of backends just process dict or OrderedDict objects now.
- Conversion from dict or OrderedDict to m9dicts's dict object to
  support recursive merge operations are done in .api.*load explicitly.
- .backend.base.to_container_fn was deprecated and replaced with
  .backend.base.Parser._container_fn which selects dict class used for
  loaded results from dict and OrderedDict based on Parser.ordered()
  (Parser._ordered) and ac_ordered argument passed to Parser.*load*.
  • Loading branch information
ssato committed Feb 27, 2017
1 parent 2522955 commit b89757d
Show file tree
Hide file tree
Showing 13 changed files with 51 additions and 26 deletions.
4 changes: 3 additions & 1 deletion anyconfig/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def single_load(path_or_stream, ac_parser=None, ac_template=False,
LOGGER.warning("Failed to compile %s, fallback to no template "
"mode, exc=%r", path_or_stream, exc)

cnf = psr.load(path_or_stream, **options)
cnf = to_container(psr.load(path_or_stream, **options), **options)
return _maybe_validated(cnf, schema, **options)


Expand Down Expand Up @@ -321,6 +321,8 @@ def multi_load(paths, ac_parser=None, ac_template=False, ac_context=None,
if cups:
cnf.update(cups)

# Disabled for a while: convert to normal dicts, dict or OrderedDict.
# cnf = anyconfig.mdicts.convert_to(cnf, **options)
return _maybe_validated(cnf, schema, **options)


Expand Down
48 changes: 31 additions & 17 deletions anyconfig/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# License: MIT
#
# pylint: disable=unused-argument
"""Abstract implementation of backend modules.
r"""Abstract implementation of backend modules:
Backend module must implement a parser class inherits :class:`Parser` or its
children classes of this module and override all or some of the methods as
Expand All @@ -16,12 +16,18 @@
- :meth:`dump_to_stream`: Dump config to a file or file-like object
- :meth:`dump_to_path`: Dump config to a file of given path
History:
Changelog:
.. versionchanged:: 0.8.3
- Add `_ordered` membmer and a class method :meth:` ordered to the class
:class:`Parser`.
.. versionchanged:: 0.2
The methods :meth:`load_impl`, :meth:`dump_impl` are deprecated and replaced
with :meth:`load_from_stream` and :meth:`load_from_path`,
:meth:`dump_to_string` and :meth:`dump_to_path` respectively.
- The methods :meth:`load_impl`, :meth:`dump_impl` are deprecated and
replaced with :meth:`load_from_stream` and :meth:`load_from_path`,
:meth:`dump_to_string` and :meth:`dump_to_path` respectively.
"""
from __future__ import absolute_import

Expand Down Expand Up @@ -81,16 +87,6 @@ def wrapper(*args, **kwargs):
return wrapper


def to_container_fn(**options):
"""
:param options:
Keyword options will be passed to :func:`to_container` in
:mod:`anyconfig.mdicts` to decide which merge-able dict to
wrap configurations.
"""
return functools.partial(anyconfig.mdicts.to_container, **options)


class Parser(object):
"""
Abstract parser to provide basic implementation of some methods, interfaces
Expand All @@ -102,6 +98,7 @@ class Parser(object):
_load_opts = []
_dump_opts = []
_open_flags = ('r', 'w')
_ordered = False

@classmethod
def type(cls):
Expand All @@ -124,6 +121,13 @@ def extensions(cls):
"""
return cls._extensions

@classmethod
def ordered(cls):
"""
:return: True if parser can keep the order of keys else False.
"""
return cls._ordered

@classmethod
def ropen(cls, filepath, **kwargs):
"""
Expand All @@ -144,6 +148,16 @@ def _load_options(self, **kwargs):
"""
return mk_opt_args(self._load_opts, kwargs)

def _container_fn(self, **options):
"""
:param options: Keyword options may contain 'ac_ordered'.
:return: Factory (class or function) to make an container.
"""
if self.ordered() and options.get("ac_ordered", False):
return anyconfig.compat.OrderedDict
else:
return dict

def load_from_string(self, content, to_container, **kwargs):
"""
Load config from given string `content`.
Expand Down Expand Up @@ -192,7 +206,7 @@ def loads(self, content, **options):
:return: dict or dict-like object holding configurations
"""
to_container = to_container_fn(**options)
to_container = self._container_fn(**options)
if not content or content is None:
return to_container()

Expand All @@ -216,7 +230,7 @@ def load(self, path_or_stream, ignore_missing=False, **options):
:return: dict or dict-like object holding configurations
"""
to_container = to_container_fn(**options)
to_container = self._container_fn(**options)
options = self._load_options(**options)

if isinstance(path_or_stream, anyconfig.compat.STR_TYPES):
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/bson.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Parser(anyconfig.backend.base.FromStringLoader,
_load_opts = [] if bson.has_c() else ["tz_aware", "uuid_subtype"]
_dump_opts = ["check_keys", "uuid_subtype"]
_open_flags = ('rb', 'wb')
_ordered = not bson.has_c()

dump_to_string = anyconfig.backend.base.to_method(bson.BSON.encode)

Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/configobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
_load_opts = _LOAD_OPTS # options on dump will be just ignored.
_dump_opts = _LOAD_OPTS # Likewise.
_open_flags = ('rb', 'wb')
_ordered = True

load_from_path = load_from_stream = anyconfig.backend.base.to_method(load)

Expand Down
3 changes: 2 additions & 1 deletion anyconfig/backend/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
_extensions = ["json", "jsn", "js"]
_load_opts = _LOAD_OPTS
_dump_opts = _DUMP_OPTS
_ordered = True

dump_to_string = anyconfig.backend.base.to_method(json.dumps)
dump_to_stream = anyconfig.backend.base.to_method(json.dump)
Expand All @@ -73,7 +74,7 @@ def _load(self, load_fn, content_or_strm, to_container, **opts):
:return: Dict-like object holding configuration
"""
if "object_pairs_hook" in self._load_opts:
opts["object_pairs_hook"] = anyconfig.compat.OrderedDict
opts["object_pairs_hook"] = self._container_fn(**opts)
return to_container(load_fn(content_or_strm, **opts))
else:
return load_fn(content_or_strm, object_hook=to_container, **opts)
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/msgpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
_dump_opts = ["default", "encoding", "unicode_errors", "use_single_float",
"autoreset", "use_bin_type"]
_open_flags = ('rb', 'wb')
_ordered = True

dump_to_string = to_method(msgpack.packb)
dump_to_stream = to_method(msgpack.pack)
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
"""
_type = "properties"
_extensions = ["properties"]
_ordered = True

def load_from_stream(self, stream, to_container, **kwargs):
"""
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/shellvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
Parser for Shell variable definition files.
"""
_type = "shellvars"
_ordered = True

def load_from_stream(self, stream, to_container, **kwargs):
"""
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Parser(anyconfig.backend.base.FromStreamLoader,
"""
_type = "toml"
_extensions = ["toml"]
_ordered = True

dump_to_string = to_method(toml.dumps)
dump_to_stream = to_method(toml.dump)
Expand Down
1 change: 1 addition & 0 deletions anyconfig/backend/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ class Parser(anyconfig.backend.base.ToStreamDumper):
_extensions = ["xml"]
_open_flags = ('rb', 'wb')
_load_opts = _dump_opts = ["tags", "merge_attrs", "ac_parse_value"]
_ordered = True

def load_from_string(self, content, to_container, **opts):
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import tests.common


MZERO = TT.to_container_fn()()
MZERO = TT.Parser()._container_fn()()


class Test00(unittest.TestCase):
Expand Down
1 change: 0 additions & 1 deletion tests/backend/cbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class Test10(tests.backend.ini.Test10):
cnf = dict(a=0, b="bbb", c=5, sect0=dict(d=["x", "y", "z"]))
cnf_s = TT.cbor.dumps(cnf)
load_options = dump_options = dict(sort_keys=False)
is_order_kept = False

def setUp(self):
self.psr = TT.Parser()
Expand Down
12 changes: 7 additions & 5 deletions tests/backend/ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import tests.common

from anyconfig.compat import OrderedDict as ODict
from anyconfig.mdicts import UpdateWithReplaceDict
from tests.common import dicts_equal


Expand All @@ -39,7 +38,7 @@ class Test10(unittest.TestCase):
cnf_s = CNF_0_S
load_options = dict(allow_no_value=False, defaults=None)
dump_options = dict()
is_order_kept = True
is_order_kept = TT.Parser.ordered()

def setUp(self):
self.psr = TT.Parser()
Expand All @@ -48,7 +47,8 @@ def _assert_dicts_equal(self, cnf, ordered=False, instance_check=False):
self.assertTrue(dicts_equal(cnf, self.cnf, ordered=ordered),
"\n %r\nvs.\n %r" % (cnf, self.cnf))
if instance_check:
self.assertTrue(isinstance(cnf, UpdateWithReplaceDict))
cls = ODict if self.is_order_kept and ordered else dict
self.assertTrue(isinstance(cnf, cls))

def test_10_loads(self):
cnf = self.psr.loads(self.cnf_s)
Expand Down Expand Up @@ -136,10 +136,12 @@ def setUp(self):
def tearDown(self):
tests.common.cleanup_workdir(self.workdir)

def _assert_dicts_equal(self, cnf):
def _assert_dicts_equal(self, cnf, ordered=False, instance_check=False):
self.assertTrue(dicts_equal(cnf, self.cnf),
"\n %r\nvs.\n %r" % (cnf, self.cnf))
self.assertTrue(isinstance(cnf, UpdateWithReplaceDict))
if instance_check:
cls = ODict if self.is_order_kept and ordered else dict
self.assertTrue(isinstance(cnf, cls))

def test_10_load(self):
cnf = self.psr.load(self.cpath)
Expand Down

0 comments on commit b89757d

Please sign in to comment.