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