Source code for monk.modeling

# -*- coding: utf-8 -*-
#
#    Monk is an unobtrusive data modeling, manipulation and validation library.
#    Copyright © 2011—2014  Andrey Mikhaylenko
#
#    This file is part of Monk.
#
#    Monk is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Lesser General Public License as published
#    by the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Monk is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public License
#    along with Monk.  If not, see <http://gnu.org/licenses/>.
"""
~~~~~~~~
Modeling
~~~~~~~~

DB-agnostic helpers to build powerful ODMs.

"""
from __future__ import unicode_literals

from .compat import text_type
from . import validate, merge_defaults


__all__ = ['DotExpandedDictMixin', 'DotExpandedDict', 'make_dot_expanded',
           'TypedDictReprMixin',
           'StructuredDictMixin']


def make_dot_expanded(data):
    if isinstance(data, DotExpandedDictMixin):
        return data
    elif isinstance(data, dict):
        pairs = []
        for key, value in data.items():
            pairs.append((key, make_dot_expanded(value)))
        return DotExpandedDict(pairs)
    elif isinstance(data, list):
        return [make_dot_expanded(x) for x in data]
    return data


[docs]class DotExpandedDictMixin(object): """ Makes the dictionary dot-expandable by exposing dictionary members via ``__getattr__`` and ``__setattr__`` in addition to ``__getitem__`` and ``__setitem__``. For example, this is the default API:: data = {'foo': {'bar': 0 } } print data['foo']['bar'] data['foo']['bar'] = 123 This mixin adds the following API:: print data.foo.bar data.foo.bar = 123 Nested dictionaries are converted to dot-expanded ones on adding. """ def _make_dot_expanded(self): for key, value in self.items(): self[key] = make_dot_expanded(value) def __getattr__(self, attr): if not attr.startswith('_') and attr in self: return self[attr] raise AttributeError('Attribute or key {0.__class__.__name__}.{1} ' 'does not exist'.format(self, attr)) def __setattr__(self, attr, value): if not attr.startswith('_') and attr in self: self[attr] = value else: # Ambigous intent: cannot tell whether user wants to create # a dictionary key or actually set an object attribute. # Assuming the last option. super(DotExpandedDictMixin, self).__setattr__(attr, value) def __setitem__(self, key, value): if isinstance(value, dict) and not isinstance(value, DotExpandedDict): value = make_dot_expanded(value) super(DotExpandedDictMixin, self).__setitem__(key, value)
class DotExpandedDict(DotExpandedDictMixin, dict): def __init__(self, *args, **kwargs): super(DotExpandedDict, self).__init__(*args, **kwargs) self._make_dot_expanded()
[docs]class TypedDictReprMixin(object): """ Makes ``repr(self)`` depend on ``unicode(self)``. """ def __repr__(self): return '<{0.__class__.__name__} {1}>'.format(self, text_type(self)) def __unicode__(self): return text_type(dict(self)) def __str__(self): return text_type(dict(self))
[docs]class StructuredDictMixin(object): """ A dictionary with structure specification and validation. .. attribute:: structure The document structure specification. For details see :func:`monk.shortcuts.validate`. """ structure = {} #defaults = {} #required = [] #validators = {} #with_skeleton = True def _insert_defaults(self): """ Inserts default values from :attr:`StructuredDictMixin.structure` to `self` by merging the two structures (see :func:`monk.manipulation.merge_defaults`). """ merged = merge_defaults(self.structure, self) self.update(merged) def validate(self): validate(self.structure, self)