Changeset d2f974 in indico


Ignore:
Timestamp:
08/23/11 15:38:51 (21 months ago)
Author:
Jose Benito <jose.benito.gonzalez@…>
Branches:
master, hello-world-walkthrough, ipv6, v0.98-series, v0.98.2, v0.98.3, v0.98b2, v0.99, 051b2622c51afb171a1dedb46a0df4fbb0cbd02e, 0da0c1403bae8e51d8229f460181c71b9e6dda72
Children:
b18213
Parents:
6ce88b
git-author:
Adrian Moennich <jerome.ernst.monnich@…> (05/16/11 16:39:40)
git-committer:
Jose Benito <jose.benito.gonzalez@…> (08/23/11 15:38:51)
Message:

[IMP] Support plugin exporters and refactor code

  • plugins can now define exporters
  • exporters are classes now instead of functions
Location:
indico
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • indico/MaKaC/plugins/base.py

    r70cbfb rd2f974  
    488488        self.__options = {} 
    489489        self.__actions = {} 
     490        self.__exporters = [] 
    490491 
    491492        self.__usable = False 
     
    706707    ############## end of actions related ############### 
    707708 
     709    ############## exporters related ############### 
     710    def updateAllExporters(self, retrievedPluginExporters): 
     711        self.__exporters = [] 
     712        if retrievedPluginExporters is not None: 
     713            self.__exporters = retrievedPluginExporters 
     714        self._notifyModification() 
     715 
     716    def getExporterList(self): 
     717        try: 
     718            return self.__exporters 
     719        except: 
     720            self.__exporters = [] 
     721            return self.__exporters 
     722 
     723    ############## end of exporters related ############### 
     724 
    708725    def _notifyModification(self): 
    709726        self._p_changed = 1 
     
    786803            p.updateAllActions(pluginModule.actions.pluginActions) 
    787804 
     805        if hasattr(pluginModule, "export") and \ 
     806               hasattr(pluginModule.options, "globalExporters"): 
     807            p.updateAllExporters(pluginModule.export.globalExporters) 
     808 
    788809        self._updateComponentInfo(p, pluginModule) 
    789810        self._updateHandlerInfo(p, pluginModule) 
     
    823844        self.__visible = ptypeMetadata['visible'] 
    824845 
    825         # components, handlers, options and actions 
     846        # components, handlers, options, actions and exporters 
    826847        self._updateComponentInfo(self, ptypeModule) 
    827848        self._updateRHMapInfo(self, ptypeModule) 
     
    829850        self.updateAllOptions(self._retrievePluginTypeOptions()) 
    830851        self.updateAllActions(self._retrievePluginTypeActions()) 
     852        self.updateAllExporters(self._retrievePluginTypeExporters()) 
    831853 
    832854 
     
    893915        if hasActionsModule and hasPluginTypeActions: 
    894916            return self.getModule().actions.pluginTypeActions 
     917        else: 
     918            return None 
     919 
     920    def _retrievePluginTypeExporters(self): 
     921 
     922        hasExportModule = hasattr(self.getModule(), "export") 
     923        hasGlobalExportersVariable = hasExportModule and hasattr(self.getModule().export, "globalExporters") 
     924        if hasExportModule and hasGlobalExportersVariable: 
     925            return self.getModule().export.globalExporters 
    895926        else: 
    896927            return None 
  • indico/web/http_api/__init__.py

    r276f7e rd2f974  
    2323""" 
    2424 
    25 from indico.web.http_api.export import ExportInterface, LimitExceededException 
     25from indico.web.http_api.export import ExportInterface, LimitExceededException, Exporter 
    2626 
    2727API_MODE_KEY            = 0 # public requests without API key, authenticated requests with api key 
  • indico/web/http_api/export.py

    r6ce88b rd2f974  
    2626import fnmatch 
    2727import itertools 
     28import pytz 
    2829import re 
    2930from zope.interface import Interface, implements 
     
    5253from MaKaC.common.info import HelperMaKaCInfo 
    5354from MaKaC.conference import ConferenceHolder 
     55from MaKaC.plugins.base import PluginsHolder 
    5456 
    5557from indico.web.http_api.util import get_query_parameter, remove_lists 
     
    6870 
    6971 
    70 class IExport(Interface): 
    71  
    72     def category(cls, idlist, fromDT, toDT, location=None, limit=None, 
    73                orderBy=None, descending=False, detail="events"): 
    74         """ 
    75         TODO: Document this 
    76         """ 
    77  
    78     def event(cls, idlist, orderBy=None, descending=False, detail="events"): 
    79         """ 
    80         TODO: Document this 
    81         """ 
     72class Exporter(object): 
     73    EXPORTER_LIST = [] 
     74    TYPES = None # abstract 
     75    RE = None # abstract 
     76    DEFAULT_DETAIL = None # abstract 
     77    MAX_RECORDS = None # abstract 
     78 
     79    @classmethod 
     80    def parseRequest(cls, path, qdata): 
     81        """Parse a request path and return an exporter and the requested data type.""" 
     82        exporters = itertools.chain(cls.EXPORTER_LIST, cls._getPluginExporters()) 
     83        for expCls in exporters: 
     84            m = expCls._matchPath(path) 
     85            if m: 
     86                gd = m.groupdict() 
     87                g = m.groups() 
     88                type = g[0] 
     89                format = g[-1] 
     90                if format not in ExportInterface.getAllowedFormats(): 
     91                    return None, None 
     92                return expCls(qdata, type, gd), format 
     93        return None, None 
     94 
     95    @staticmethod 
     96    def register(cls): 
     97        """Register an exporter that is not part of a plugin. 
     98 
     99        To use it, simply decorate the exporter class with this method.""" 
     100        assert cls.RE is not None 
     101        Exporter.EXPORTER_LIST.append(cls) 
     102        return cls 
     103 
     104    @classmethod 
     105    def _matchPath(cls, path): 
     106        if not hasattr(cls, '_RE'): 
     107            types = '|'.join(cls.TYPES) 
     108            cls._RE = re.compile(r'/export/(' + types + r')/' + cls.RE + r'\.(\w+)$') 
     109        return cls._RE.match(path) 
     110 
     111    @classmethod 
     112    def _getPluginExporters(cls): 
     113        for plugin in PluginsHolder().getPluginTypes(): 
     114            for expClsName in plugin.getExporterList(): 
     115                yield getattr(plugin.getModule().export, expClsName) 
     116 
     117    def __init__(self, qdata, type, urlParams): 
     118        self._qdata = qdata 
     119        self._type = type 
     120        self._urlParams = urlParams 
     121 
     122    def _getParams(self): 
     123        self._offset = get_query_parameter(self._qdata, ['O', 'offset'], 0, integer=True) 
     124        self._orderBy = get_query_parameter(self._qdata, ['o', 'order'], 'start') 
     125        self._descending = get_query_parameter(self._qdata, ['c', 'descending'], False) 
     126        self._detail = get_query_parameter(self._qdata, ['d', 'detail'], self.DEFAULT_DETAIL) 
     127        tzName = get_query_parameter(self._qdata, ['tz'], None) 
     128        if tzName is None: 
     129            info = HelperMaKaCInfo.getMaKaCInfoInstance() 
     130            tzName = info.getTimezone() 
     131        self._tz = pytz.timezone(tzName) 
     132        max = self.MAX_RECORDS.get(self._detail, 10000) 
     133        self._userLimit = get_query_parameter(self._qdata, ['n', 'limit'], 0, integer=True) 
     134        if self._userLimit > max: 
     135            raise HTTPAPIError("You can only request up to %d records per request with the detail level '%s'" % 
     136                (max, self._detail), apache.HTTP_BAD_REQUEST) 
     137        self._limit = self._userLimit if self._userLimit > 0 else max 
     138 
     139    def __call__(self, aw): 
     140        """Perform the actual exporting""" 
     141        self._getParams() 
     142        resultList = [] 
     143        complete = True 
     144 
     145        func = getattr(self, 'export_' + self._type, None) 
     146        if not func: 
     147            raise NotImplementedError('export_' + self._type) 
     148 
     149        try: 
     150            for obj in func(aw): 
     151                resultList.append(obj) 
     152        except LimitExceededException: 
     153            complete = (self._limit == self._userLimit) 
     154 
     155        return resultList, complete 
    82156 
    83157 
    84158class ExportInterface(object): 
    85     implements(IExport) 
    86  
    87159    _deltas =  {'yesterday': timedelta(-1), 
    88160                'tomorrow': timedelta(1)} 
     
    166238            if counter >= limit: 
    167239                raise LimitExceededException() 
    168             if obj not in exclude and obj.canAccess(self._aw): 
     240            if obj not in exclude and (not hasattr(obj, 'canAccess') or obj.canAccess(self._aw)): 
    169241                self._intermediateResults.append(obj) 
    170242                yield obj 
     
    201273    @classmethod 
    202274    def _getDetailInterface(cls, detail): 
     275        raise HTTPAPIError('Invalid detail level: %s' % detail, apache.HTTP_BAD_REQUEST) 
     276 
     277    def _iterateOver(self, iterator, offset, limit, orderBy, descending, filter=None): 
     278        """ 
     279        Iterates over a maximum of `limit` elements, starting at the 
     280        element number `offset`. The elements will be ordered according 
     281        to `orderby` and `descending` (slooooow) and filtered by the 
     282        callable `filter`: 
     283        """ 
     284 
     285        if filter: 
     286            iterator = itertools.ifilter(filter, iterator) 
     287        sortedIterator = self._sortedIterator(iterator, limit, orderBy, descending) 
     288        # Skip offset elements - http://docs.python.org/library/itertools.html#recipes 
     289        next(itertools.islice(sortedIterator, offset, offset), None) 
     290        return sortedIterator 
     291 
     292 
     293@Exporter.register 
     294class CategoryEventExporter(Exporter): 
     295    TYPES = ('event', 'categ') 
     296    RE = r'(?P<idlist>\w+(?:-\w+)*)' 
     297    DEFAULT_DETAIL = 'events' 
     298    MAX_RECORDS = { 
     299        'events': 10000, 
     300        'contributions': 500, 
     301        'subcontributions': 500, 
     302        'sessions': 100, 
     303    } 
     304 
     305    def _getParams(self): 
     306        super(CategoryEventExporter, self)._getParams() 
     307        self._idList = self._urlParams['idlist'].split('-') 
     308 
     309    def export_categ(self, aw): 
     310        expInt = CategoryEventExportInterface(aw) 
     311        return expInt.category(self._idList, self._tz, self._offset, self._limit, self._detail, self._orderBy, self._descending, self._qdata) 
     312 
     313    def export_event(self, aw): 
     314        expInt = CategoryEventExportInterface(aw) 
     315        return expInt.event(self._idList, self._tz, self._offset, self._limit, self._detail, self._orderBy, self._descending, self._qdata) 
     316 
     317 
     318class CategoryEventExportInterface(ExportInterface): 
     319    @classmethod 
     320    def _getDetailInterface(cls, detail): 
    203321        if detail == 'events': 
    204322            return IConferenceMetadataFossil 
     
    211329        raise HTTPAPIError('Invalid detail level: %s' % detail, apache.HTTP_BAD_REQUEST) 
    212330 
    213     def _iterateOver(self, iterator, offset, limit, orderBy, descending, filter=None): 
    214         """ 
    215         Iterates over a maximum of `limit` elements, starting at the 
    216         element number `offset`. The elements will be ordered according 
    217         to `orderby` and `descending` (slooooow) and filtered by the 
    218         callable `filter`: 
    219         """ 
    220  
    221         if filter: 
    222             iterator = itertools.ifilter(filter, iterator) 
    223         sortedIterator = self._sortedIterator(iterator, limit, orderBy, descending) 
    224         # Skip offset elements - http://docs.python.org/library/itertools.html#recipes 
    225         next(itertools.islice(sortedIterator, offset, offset), None) 
    226         return sortedIterator 
    227  
    228331    def category(self, idlist, tz, offset, limit, detail, orderBy, descending, qdata): 
    229  
    230332        fromDT = get_query_parameter(qdata, ['f', 'from']) 
    231333        toDT = get_query_parameter(qdata, ['t', 'to']) 
     
    257359 
    258360    def event(self, idlist, tz, offset, limit, detail, orderBy, descending, qdata): 
    259         # TODO: use iterators 
    260  
    261361        ch = ConferenceHolder() 
    262362 
     
    271371        for event in self._iterateOver(_iterate_objs(idlist), offset, limit, orderBy, descending): 
    272372            yield fossilize(event, iface, tz=tz) 
    273  
    274373 
    275374Serializer.register('html', HTML4Serializer) 
  • indico/web/http_api/handlers.py

    r6ce88b rd2f974  
    2626import hashlib 
    2727import hmac 
     28import itertools 
    2829import re 
    2930import time 
     
    3435 
    3536# indico imports 
    36 from indico.web.http_api import ExportInterface, LimitExceededException 
     37from indico.web.http_api import ExportInterface, LimitExceededException, Exporter 
    3738from indico.web.http_api.auth import APIKeyHolder 
    3839from indico.web.http_api.cache import RequestCache 
     
    4849from MaKaC.accessControl import AccessWrapper 
    4950from MaKaC.common.info import HelperMaKaCInfo 
     51from MaKaC.plugins.base import PluginsHolder 
    5052 
    5153# Maximum number of records that will get exported for each detail level 
     
    130132    return aw 
    131133 
    132  
    133 def getExportHandler(path): 
    134     """Get the export handler, handler args and return type from a path""" 
    135     func = None 
    136     match = None 
    137     for pathRe, handlerFunc in EXPORT_URL_MAP.iteritems(): 
    138         match = pathRe.match(path) 
    139         if match: 
    140             func = handlerFunc 
    141             break 
    142  
    143     groups = match and match.groups() 
    144     if not match or groups[-1] not in ExportInterface.getAllowedFormats(): 
    145         return None, None, None 
    146     return globals()[func], groups[:-1], groups[-1] 
    147  
    148  
    149 def handler_event_categ(aw, qdata, dtype, idlist): 
    150     idlist = idlist.split('-') 
    151  
    152     expInt = ExportInterface(aw) 
    153     tzName = get_query_parameter(qdata, ['tz'], None) 
    154     detail = get_query_parameter(qdata, ['d', 'detail'], 'events') 
    155     userLimit = get_query_parameter(qdata, ['n', 'limit'], 0, integer=True) 
    156     offset = get_query_parameter(qdata, ['O', 'offset'], 0, integer=True) 
    157     orderBy = get_query_parameter(qdata, ['o', 'order'], 'start') 
    158     descending = get_query_parameter(qdata, ['c', 'descending'], False) 
    159  
    160     if tzName is None: 
    161         info = HelperMaKaCInfo.getMaKaCInfoInstance() 
    162         tzName = info.getTimezone() 
    163  
    164     tz = pytz.timezone(tzName) 
    165  
    166     max = MAX_RECORDS.get(detail, 10000) 
    167     if userLimit > max: 
    168         raise HTTPAPIError("You can only request up to %d records per request with the detail level '%s" % 
    169             (max, detail), apache.HTTP_BAD_REQUEST) 
    170  
    171     # impose a hard limit 
    172     limit = userLimit if userLimit > 0 else max 
    173  
    174     if dtype == 'categ': 
    175         iterator = expInt.category(idlist, tz, offset, limit, detail, orderBy, descending, qdata) 
    176     elif dtype == 'event': 
    177         iterator = expInt.event(idlist, tz, offset, limit, detail, orderBy, descending, qdata) 
    178  
    179     resultList = [] 
    180     complete = True 
    181  
    182     try: 
    183         for obj in iterator: 
    184             resultList.append(obj) 
    185     except LimitExceededException: 
    186         complete = (limit == userLimit) 
    187  
    188     return resultList, complete 
    189  
    190134def handler(req, **params): 
    191135    path, query = req.URLFields['PATH_INFO'], req.URLFields['QUERY_STRING'] 
     
    206150 
    207151    # Get our handler function and its argument and response type 
    208     func, args, dformat = getExportHandler(path) 
     152    func, dformat = Exporter.parseRequest(path, qdata) 
    209153    if func is None or dformat is None: 
    210154        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND 
     
    236180        if result is None: 
    237181            # Perform the actual exporting 
    238             result, complete = func(aw, qdata, *args) 
     182            result, complete = func(aw) 
    239183        if result is not None and add_to_cache: 
    240184            cache.cacheObject(cache_key, (result, complete)) 
Note: See TracChangeset for help on using the changeset viewer.