1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2009-2011, Nicolas Clairon
4# All rights reserved.
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are met:
7#
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above copyright
11#       notice, this list of conditions and the following disclaimer in the
12#       documentation and/or other materials provided with the distribution.
13#     * Neither the name of the University of California, Berkeley nor the
14#       names of its contributors may be used to endorse or promote products
15#       derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
18# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
21# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28import datetime
29import logging
30log = logging.getLogger(__name__)
31
32
33def totimestamp(value):
34    """
35    convert a datetime into a float since epoch
36    """
37    import calendar
38    return int(calendar.timegm(value.timetuple()) * 1000 + value.microsecond / 1000)
39
40
41def fromtimestamp(epoch_date):
42    """
43    convert a float since epoch to a datetime object
44    """
45    seconds = float(epoch_date) / 1000.0
46    return datetime.datetime.utcfromtimestamp(seconds)
47
48from copy import deepcopy
49
50
51class i18nDotedDict(dict):
52    """
53    Dot notation dictionary access with i18n support
54    """
55    def __init__(self, dic, doc):
56        super(i18nDotedDict, self).__init__(dic)
57        self._doc = doc
58
59    def __setattr__(self, key, value):
60        from mongokit.schema_document import i18n
61        if key in self:
62            if isinstance(self[key], i18n):
63                self[key][self._doc._current_lang] = value
64            else:
65                self[key] = value
66        else:
67            dict.__setattr__(self, key, value)
68
69    def __getattr__(self, key):
70        from mongokit.schema_document import i18n
71        if key in self:
72            if isinstance(self[key], i18n):
73                if self._doc._current_lang not in self[key]:
74                    return self[key].get(self._doc._fallback_lang)
75                return self[key][self._doc._current_lang]
76            return self[key]
77
78    def __deepcopy__(self, memo={}):
79        obj = dict(self)
80        return deepcopy(obj, memo)
81
82    def __getstate__(self):
83        return self.__dict__
84
85    def __setstate__(self, d):
86        self.__dict__.update(d)
87
88
89class DotedDict(dict):
90    """
91    Dot notation dictionary access
92    """
93    def __init__(self, doc=None, warning=False):
94        self._dot_notation_warning = warning
95        if doc is None:
96            doc = {}
97        super(DotedDict, self).__init__(doc)
98        self.__dotify_dict(self)
99
100    def __dotify_dict(self, doc):
101        for k, v in doc.iteritems():
102            if isinstance(v, dict):
103                doc[k] = DotedDict(v)
104                self.__dotify_dict(v)
105
106    def __setattr__(self, key, value):
107        if key in self:
108            self[key] = value
109        else:
110            if self._dot_notation_warning and not key.startswith('_') and\
111               key not in ['db', 'collection', 'versioning_collection', 'connection', 'fs']:
112                log.warning("dot notation: %s was not found in structure. Add it as attribute instead" % key)
113            dict.__setattr__(self, key, value)
114
115    def __getattr__(self, key):
116        if key in self:
117            return self[key]
118
119    def __deepcopy__(self, memo={}):
120        obj = dict(self)
121        return deepcopy(obj, memo)
122
123    def __getstate__(self):
124        return self.__dict__
125
126    def __setstate__(self, d):
127        self.__dict__.update(d)
128
129
130class EvalException(Exception):
131    pass
132
133
134class DotExpandedDict(dict):
135    """
136    A special dictionary constructor that takes a dictionary in which the keys
137    may contain dots to specify inner dictionaries. It's confusing, but this
138    example should make sense.
139
140    >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
141          'person.1.lastname': ['Willison'], \
142          'person.2.firstname': ['Adrian'], \
143          'person.2.lastname': ['Holovaty']})
144    >>> d
145    {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']},
146    '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
147    >>> d['person']
148    {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
149    >>> d['person']['1']
150    {'lastname': ['Willison'], 'firstname': ['Simon']}
151
152    # Gotcha: Results are unpredictable if the dots are "uneven":
153    >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
154    {'c': 1}
155    """
156    # code taken from Django source code http://code.djangoproject.com/
157    def __init__(self, key_to_list_mapping):
158        for k, v in key_to_list_mapping.items():
159            current = self
160            bits = k.split('.')
161            for bit in bits[:-1]:
162                if bit.startswith('$'):
163                    try:
164                        bit = eval(bit[1:])
165                    except:
166                        raise EvalException('%s is not a python type' % bit[:1])
167                current = current.setdefault(bit, {})
168            # Now assign value to current position
169            last_bit = bits[-1]
170            if last_bit.startswith('$'):
171                try:
172                    last_bit = eval(last_bit[1:])
173                except:
174                    raise EvalException('%s is not a python type' % last_bit)
175            try:
176                current[last_bit] = v
177            except TypeError:  # Special-case if current isn't a dict.
178                current = {last_bit: v}
179
180
181class DotCollapsedDict(dict):
182    """
183    A special dictionary constructor that take a dict and provides
184    a dot collapsed dict:
185
186    >>> DotCollapsedDict({'a':{'b':{'c':{'d':3}, 'e':5}, "g":2}, 'f':6})
187    {'a.b.c.d': 3, 'a.b.e': 5, 'a.g': 2, 'f': 6}
188
189    >>> DotCollapsedDict({'bla':{'foo':{unicode:{"bla":3}}, 'bar':'egg'}})
190    {'bla.foo.$unicode.bla': 3, 'bla.bar': "egg"}
191
192    >>> DotCollapsedDict({'bla':{'foo':{unicode:{"bla":3}}, 'bar':'egg'}}, remove_under_type=True)
193    {'bla.foo':{}, 'bla.bar':unicode}
194
195    >>> dic = {'bar':{'foo':3}, 'bla':{'g':2, 'h':3}}
196    >>> DotCollapsedDict(dic, reference={'bar.foo':None, 'bla':{'g':None, 'h':None}})
197    {'bar.foo':3, 'bla':{'g':2, 'h':3}}
198
199    """
200    def __init__(self, passed_dict, remove_under_type=False, reference=None):
201        self._remove_under_type = remove_under_type
202        assert isinstance(passed_dict, dict), "you must pass a dict instance"
203        final_dict = {}
204        self._reference = reference
205        self._make_dotation(passed_dict, final_dict)
206        self.update(final_dict)
207
208    def _make_dotation(self, d, final_dict, key=""):
209        for k, v in d.iteritems():
210            if isinstance(k, type):
211                k = "$%s" % k.__name__
212            if isinstance(v, dict) and v != {}:
213                if key:
214                    _key = "%s.%s" % (key, k)
215                else:
216                    _key = k
217                if self._reference and _key in self._reference:
218                    final_dict[_key] = v
219                if self._remove_under_type:
220                    if [1 for i in v.keys() if isinstance(i, type)]:
221                        v = v.__class__()
222                        if not key:
223                            final_dict[k] = v
224                        else:
225                            final_dict["%s.%s" % (key, k)] = v
226                    else:
227                        self._make_dotation(v, final_dict, _key)
228                else:
229                    self._make_dotation(v, final_dict, _key)
230            else:
231                if not key:
232                    final_dict[k] = v
233                else:
234                    if not self._reference:
235                        final_dict["%s.%s" % (key, k)] = v
236                    elif "%s.%s" % (key, k) in self._reference:
237                        final_dict["%s.%s" % (key, k)] = v
238                    #else:
239                    #    final_dict[key] = {k: v}
240                    #    print "+++", {k:v}
241