source: indico/indico/MaKaC/common/fossilize.py @ a217dc

burotelhello-world-walkthroughipv6new-webexv0.97-seriesv0.98-seriesv0.98.2v0.98.3v0.98b1v0.98b2v0.99v1.0v1.1
Last change on this file since a217dc was a217dc, checked in by Jose Benito <jose.benito.gonzalez@…>, 3 years ago

[OPT] Optimization of the timetable display

  • fixes #307
  • Replaced pickling by fossilization for the generation of the data structures needed for the display of the timetable. The fossils contain the minimal amount of information needed to display the timetable, which is less than what the pickling was retrieving
  • New minimal fossils were added for the managment timetable too.
  • Optimized the loop that is grouping entries by day
  • Removed dead code used for the management timetable generation
  • Added a argument to the Fossils.fossilize(), ScheduleToJson?.process() and ScheduleToJson?.processEntry() method in order to use the attributes cache, useful for fossils that have fields that are repeated.
  • Displaying the 'view event' timetable of a conference like 'CHEP 2009' takes now less than 8 seconds
  • Displaying the 'managment' timetable of a conference like 'CHEP 2009' takes now less than 4 seconds
  • Property mode set to 100644
File size: 12.3 KB
Line 
1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20
21"""
22`fossilize` allows us to "serialize" complex python objects into dictionaries
23and lists. Such operation is very useful for generating JSON data structures
24from business objects. It works as a wrapper around `zope.interface`.
25
26Some of the features are:
27 * Different "fossil" types for the same source class;
28 * Built-in inheritance support;
29"""
30
31import inspect
32import re
33import zope.interface
34from types import NoneType, ClassType, TypeType
35
36
37def fossilizes(*classList):
38    """
39    Simple wrapper around 'implements'
40    """
41    zope.interface.declarations._implements("fossilizes",
42                                            classList,
43                                            zope.interface.classImplements)
44
45def addFossil(klazz, fossils):
46    """
47    Declares fossils for a class
48
49    :param klazz: a class object
50    :type klass: class object
51    :param fossils: a fossil class (or a list of fossil classes)
52    """
53    if not type(fossils) is list:
54        fossils = [fossils]
55
56    for fossil in fossils:
57        zope.interface.classImplements(klazz, fossil)
58
59class NonFossilizableException(Exception):
60    """
61    Object is not fossilizable (doesn't implement Fossilizable)
62    """
63
64class WrongFossilTypeException(Exception):
65    """
66    Fossil type doesn't apply to target object
67    """
68
69class InvalidFossilException(Exception):
70    """
71    The fossil name doesn't follow the convention I(\w+)Fossil
72    or has an invalid method name and did not declare a .name tag for it
73    """
74
75class IFossil(zope.interface.Interface):
76    """
77    Fossil base interface. All fossil classes should derive from this one.
78    """
79
80class Fossilizable:
81    """
82    Base class for all the objects that can be fossilized
83    """
84
85    __fossilNameRE = re.compile('^I(\w+)Fossil$')
86    __methodNameRE = re.compile('^get(\w+)|(has\w+)|(is\w+)$')
87    __methodNameCache = {}
88    __fossilNameCache = {}
89    __fossilAttrsCache = {} # Attribute Cache for Fossils with
90                            # fields that are repeated
91
92    @classmethod
93    def __extractName(cls, name):
94        """
95        'De-camelcase' the name
96        """
97
98        if name in cls.__methodNameCache:
99            return cls.__methodNameCache[name]
100        else:
101            nmatch = cls.__methodNameRE.match(name)
102
103            if not nmatch:
104                raise InvalidFossilException("method name '%s' is not valid! "
105                                             "has to start by 'get', 'has', 'is' "
106                                             "or use 'name' tag" % name)
107            else:
108                group = nmatch.group(1) or nmatch.group(2) or nmatch.group(3)
109                extractedName = group[0:1].lower() + group[1:]
110                cls.__methodNameCache[name] = extractedName
111                return extractedName
112
113    @classmethod
114    def __extractFossilName(cls, name):
115        """
116        Extracts the fossil name from a I(.*)Fossil
117        class name.
118        IMyObjectBasicFossil -> myObjectBasic
119        """
120
121        if name in cls.__fossilNameCache:
122            fossilName = cls.__fossilNameCache[name]
123        else:
124            fossilNameMatch = Fossilizable.__fossilNameRE.match(name)
125            if fossilNameMatch is None:
126                raise InvalidFossilException("Invalid fossil name: %s."
127                                             " A fossil name should follow the"
128                                             " pattern: I(\w+)Fossil." % name)
129            else:
130                fossilName = fossilNameMatch.group(1)[0].lower() + fossilNameMatch.group(1)[1:]
131                cls.__fossilNameCache[name] = fossilName
132        return fossilName
133
134
135    def __obtainInterface(self, interfaceArg):
136        """
137        Obtains the appropriate interface for this object.
138
139        :param interfaceArg: the target fossile type
140        :type interfaceArg: IFossil, NoneType, or dict
141
142            -If IFossil, we will use it.
143            -If None, we will take the default fossil (the first one of this class's "fossilizes" list)
144            -If a dict, we will use the object's class, class name, or full class name as key.
145
146        Also verifies that the interface obtained through these 3 methods is effectively provided by the object.
147        """
148        if interfaceArg is None:
149            #we try to take the 1st interface declared with fossilizes
150            implementedInterfaces = list(i for i in zope.interface.implementedBy(self.__class__) if i.extends(IFossil))
151            if not implementedInterfaces:
152                raise NonFossilizableException("Object %s of class %s cannot be fossilized,"
153                                               "no fossils were declared for it" %
154                                               (str(self),
155                                                self.__class__.__name__))
156            else:
157                interface = implementedInterfaces[0]
158
159        elif type(interfaceArg) is dict:
160
161            className = self.__class__.__module__ + '.' + \
162                        self.__class__.__name__
163
164            # interfaceArg is a dictionary of class:Fossil pairs
165            if className in interfaceArg:
166                interface = interfaceArg[className]
167            else:
168                raise NonFossilizableException("Object %s of class %s cannot be fossilized; "
169                                               "its class was not a key in the provided fossils dictionary" %
170                                               (str(self),
171                                                self.__class__.__name__))
172        else:
173            interface = interfaceArg
174
175        if not interface.providedBy(self):
176
177            raise WrongFossilTypeException("Interface '%s' not provided"
178                                           " by '%s'" %
179                                           (interface.__name__,
180                                            self.__class__.__name__))
181
182        return interface
183
184
185    @classmethod
186    def _fossilizeIterable(cls, target, interface, useAttrCache = False, **kwargs):
187        """
188        Fossilizes an object, be it a 'direct' fossilizable
189        object, or an iterable (dict, list, set);
190        """
191
192        if isinstance(target, Fossilizable):
193            return target.fossilize(interface, useAttrCache, **kwargs)
194        else:
195            ttype = type(target)
196            if ttype in [int, str, float, NoneType]:
197                return target
198            elif ttype in [list, set, tuple]:
199                container = [] #we turn sets and tuples into lists since JSON does not have sets / tuples
200                for elem in target:
201                    container.append(fossilize(elem, interface, useAttrCache, **kwargs))
202                return container
203            elif ttype is dict:
204                container = {}
205                for key, value in target.iteritems():
206                    container[key] = fossilize(value, interface, useAttrCache, **kwargs)
207                return container
208            else:
209                raise NonFossilizableException()
210
211            return fossilize(target, interface, useAttrCache)
212
213    def fossilize(self, interfaceArg = None, useAttrCache = False, **kwargs):
214        """
215        Fossilizes the object, using the fossil provided by `interface`.
216
217        :param interfaceArg: the target fossile type
218        :type interfaceArg: IFossil, NoneType, or dict
219        :param useAttrCache: use caching of attributes if same fields are
220            repeated for a fossil
221        :type useAttrCache: boolean
222        """
223
224        interface = self.__obtainInterface(interfaceArg)
225
226        name = interface.getName()
227        fossilName = self.__extractFossilName(name)
228
229        result = {}
230
231        for method in interface:
232
233            tags = interface[method].getTaggedValueTags()
234
235            # In some cases it is better to use the attribute cache to
236            # speed up the fossilization
237            cacheUsed = False
238            if useAttrCache:
239                try:
240                    methodResult = self.__fossilAttrsCache[self._p_oid][method]
241                    cacheUsed = True
242                except KeyError:
243                    pass
244            if not cacheUsed:
245                #Please use 'produce' as little as possible; there is almost always a more elegant and modular solution!
246                if 'produce' in tags:
247                    methodResult = interface[method].getTaggedValue('produce')(self)
248                else:
249                    methodResult = getattr(self, method)()
250
251                if hasattr(self, "_p_oid"):
252                    try:
253                        self.__fossilAttrsCache[self._p_oid]
254                    except KeyError:
255                        self.__fossilAttrsCache[self._p_oid] = {}
256                    self.__fossilAttrsCache[self._p_oid][method] = methodResult
257
258            # Result conversion
259            if 'result' in tags:
260                targetInterface = interface[method].getTaggedValue('result')
261                #targetInterface = globals()[targetInterfaceName]
262
263                methodResult = Fossilizable._fossilizeIterable(
264                    methodResult, targetInterface, **kwargs)
265
266            # Conversion function
267            if 'convert' in tags:
268                convertFunction = interface[method].getTaggedValue('convert')
269                converterArgNames = inspect.getargspec(convertFunction)[0]
270                converterArgs = dict((name, kwargs[name])
271                                     for name in converterArgNames
272                                     if name in kwargs)
273                methodResult = convertFunction(methodResult, **converterArgs)
274
275            # Re-name the attribute produced by the method
276            if 'name' in tags:
277                attrName = interface[method].getTaggedValue('name')
278            else:
279                attrName = self.__extractName(method)
280
281            # In case the name contains dots, each of the 'domains' but the
282            # last one are translated into nested dictionnaries. For example,
283            # if we want to re-name an attribute into "foo.bar.tofu", the
284            # corresponding fossilized attribute will be of the form:
285            # {"foo":{"bar":{"tofu": res,...},...},...}
286            # instead of:
287            # {"foo.bar.tofu": res, ...}
288
289            current = result
290            attrList = attrName.split('.')
291
292            while len(attrList) > 1:
293                attr = attrList.pop(0)
294                try:
295                    current = current[attr]
296                except KeyError:
297                    current[attr] = {}
298                    current = current[attr]
299
300            # For the last attribute level
301            current[attrList[0]] = methodResult
302
303        if "_type" in result or "_fossil" in result:
304            raise InvalidFossilException('"_type" or "_fossil"'
305                                         ' cannot be a fossil attribute  name')
306        else:
307            result["_type"] = self.__class__.__name__
308            if fossilName: #we check that it's not an empty string
309                result["_fossil"] = fossilName
310            else:
311                result["_fossil"] = ""
312
313        return result
314
315
316def fossilize(target, interfaceArg = None, useAttrCache = False, **kwargs):
317    """
318    Method that allows the "fossilization" process to
319    be called on data structures (lists, dictionaries
320    and sets) as well as normal `Fossilizable` objects.
321
322    :param target: target object to be fossilized
323    :type target: Fossilizable
324    :param interfaceArg: target fossil type
325    :type interfaceArg: IFossil, NoneType, or dict
326    :param useAttrCache: use the attribute caching
327    :type useAttrCache: boolean
328    """
329    return Fossilizable._fossilizeIterable(target, interfaceArg, useAttrCache, **kwargs)
Note: See TracBrowser for help on using the repository browser.