root/galaxy-central/eggs/Routes-1.12.3-py2.6.egg/routes/mapper.py

リビジョン 3, 45.9 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1"""Mapper and Sub-Mapper"""
2import re
3import sys
4import threading
5
6from routes import request_config
7from routes.lru import LRUCache
8from routes.util import controller_scan, MatchException, RoutesException
9from routes.route import Route
10
11
12COLLECTION_ACTIONS = ['index', 'create', 'new']
13MEMBER_ACTIONS = ['show', 'update', 'delete', 'edit']
14
15
16def strip_slashes(name):
17    """Remove slashes from the beginning and end of a part/URL."""
18    if name.startswith('/'):
19        name = name[1:]
20    if name.endswith('/'):
21        name = name[:-1]
22    return name
23
24
25class SubMapperParent(object):
26    """Base class for Mapper and SubMapper, both of which may be the parent
27    of SubMapper objects
28    """
29   
30    def submapper(self, **kargs):
31        """Create a partial version of the Mapper with the designated
32        options set
33       
34        This results in a :class:`routes.mapper.SubMapper` object.
35       
36        If keyword arguments provided to this method also exist in the
37        keyword arguments provided to the submapper, their values will
38        be merged with the saved options going first.
39       
40        In addition to :class:`routes.route.Route` arguments, submapper
41        can also take a ``path_prefix`` argument which will be
42        prepended to the path of all routes that are connected.
43       
44        Example::
45           
46            >>> map = Mapper(controller_scan=None)
47            >>> map.connect('home', '/', controller='home', action='splash')
48            >>> map.matchlist[0].name == 'home'
49            True
50            >>> m = map.submapper(controller='home')
51            >>> m.connect('index', '/index', action='index')
52            >>> map.matchlist[1].name == 'index'
53            True
54            >>> map.matchlist[1].defaults['controller'] == 'home'
55            True
56       
57        Optional ``collection_name`` and ``resource_name`` arguments are
58        used in the generation of route names by the ``action`` and
59        ``link`` methods.  These in turn are used by the ``index``,
60        ``new``, ``create``, ``show``, ``edit``, ``update`` and
61        ``delete`` methods which may be invoked indirectly by listing
62        them in the ``actions`` argument.  If the ``formatted`` argument
63        is set to ``True`` (the default), generated paths are given the
64        suffix '{.format}' which matches or generates an optional format
65        extension.
66       
67        Example::
68       
69            >>> from routes.util import url_for
70            >>> map = Mapper(controller_scan=None)
71            >>> m = map.submapper(path_prefix='/entries', collection_name='entries', resource_name='entry', actions=['index', 'new'])
72            >>> url_for('entries') == '/entries'
73            True
74            >>> url_for('new_entry', format='xml') == '/entries/new.xml'
75            True
76
77        """
78        return SubMapper(self, **kargs)
79
80    def collection(self, collection_name, resource_name, path_prefix=None,
81                   member_prefix='/{id}', controller=None,
82                   collection_actions=COLLECTION_ACTIONS,
83                   member_actions = MEMBER_ACTIONS, member_options=None,
84                   **kwargs):
85        """Create a submapper that represents a collection.
86
87        This results in a :class:`routes.mapper.SubMapper` object, with a
88        ``member`` property of the same type that represents the collection's
89        member resources.
90       
91        Its interface is the same as the ``submapper`` together with
92        ``member_prefix``, ``member_actions`` and ``member_options``
93        which are passed to the ``member` submatter as ``path_prefix``,
94        ``actions`` and keyword arguments respectively.
95       
96        Example::
97       
98            >>> from routes.util import url_for
99            >>> map = Mapper(controller_scan=None)
100            >>> c = map.collection('entries', 'entry')
101            >>> c.member.link('ping', method='POST')
102            >>> url_for('entries') == '/entries'
103            True
104            >>> url_for('edit_entry', id=1) == '/entries/1/edit'
105            True
106            >>> url_for('ping_entry', id=1) == '/entries/1/ping'
107            True
108
109        """
110        if controller is None:
111            controller = resource_name or collection_name
112       
113        if path_prefix is None:
114            path_prefix = '/' + collection_name
115
116        collection = SubMapper(self, collection_name=collection_name,
117                               resource_name=resource_name,
118                               path_prefix=path_prefix, controller=controller,
119                               actions=collection_actions, **kwargs)
120       
121        collection.member = SubMapper(collection, path_prefix=member_prefix,
122                                      actions=member_actions,
123                                      **(member_options or {}))
124
125        return collection
126
127
128class SubMapper(SubMapperParent):
129    """Partial mapper for use with_options"""
130    def __init__(self, obj, resource_name=None, collection_name=None,
131                 actions=None, formatted=None, **kwargs):
132        self.kwargs = kwargs
133        self.obj = obj
134        self.collection_name = collection_name
135        self.member = None
136        self.resource_name = resource_name \
137                            or getattr(obj, 'resource_name', None) \
138                            or kwargs.get('controller', None) \
139                            or getattr(obj, 'controller', None)
140        if formatted is not None:
141            self.formatted = formatted
142        else:
143            self.formatted = getattr(obj, 'formatted', None)
144            if self.formatted is None:
145                self.formatted = True
146
147        self.add_actions(actions or [])
148       
149    def connect(self, *args, **kwargs):
150        newkargs = {}
151        newargs = args
152        for key, value in self.kwargs.items():
153            if key == 'path_prefix':
154                if len(args) > 1:
155                    newargs = (args[0], self.kwargs[key] + args[1])
156                else:
157                    newargs = (self.kwargs[key] + args[0],)
158            elif key in kwargs:
159                if isinstance(value, dict):
160                    newkargs[key] = dict(value, **kwargs[key]) # merge dicts
161                else:
162                    newkargs[key] = value + kwargs[key]
163            else:
164                newkargs[key] = self.kwargs[key]
165        for key in kwargs:
166            if key not in self.kwargs:
167                newkargs[key] = kwargs[key]
168        return self.obj.connect(*newargs, **newkargs)
169
170    def link(self, rel=None, name=None, action=None, method='GET',
171             formatted=None, **kwargs):
172        """Generates a named route for a subresource.
173
174        Example::
175       
176            >>> from routes.util import url_for
177            >>> map = Mapper(controller_scan=None)
178            >>> c = map.collection('entries', 'entry')
179            >>> c.link('recent', name='recent_entries')
180            >>> c.member.link('ping', method='POST', formatted=True)
181            >>> url_for('entries') == '/entries'
182            True
183            >>> url_for('recent_entries') == '/entries/recent'
184            True
185            >>> url_for('ping_entry', id=1) == '/entries/1/ping'
186            True
187            >>> url_for('ping_entry', id=1, format='xml') == '/entries/1/ping.xml'
188            True
189
190        """
191        if formatted or (formatted is None and self.formatted):
192            suffix = '{.format}'
193        else:
194            suffix = ''
195
196        return self.connect(name or (rel + '_' + self.resource_name),
197                            '/' + (rel or name) + suffix,
198                            action=action or rel or name,
199                            **_kwargs_with_conditions(kwargs, method))
200
201    def new(self, **kwargs):
202        """Generates the "new" link for a collection submapper."""
203        return self.link(rel='new', **kwargs)
204
205    def edit(self, **kwargs):
206        """Generates the "edit" link for a collection member submapper."""
207        return self.link(rel='edit', **kwargs)
208
209    def action(self, name=None, action=None, method='GET', formatted=None,
210               **kwargs):
211        """Generates a named route at the base path of a submapper.
212
213        Example::
214       
215            >>> from routes import url_for
216            >>> map = Mapper(controller_scan=None)
217            >>> c = map.submapper(path_prefix='/entries', controller='entry')
218            >>> c.action(action='index', name='entries', formatted=True)
219            >>> c.action(action='create', method='POST')
220            >>> url_for(controller='entry', action='index', method='GET') == '/entries'
221            True
222            >>> url_for(controller='entry', action='index', method='GET', format='xml') == '/entries.xml'
223            True
224            >>> url_for(controller='entry', action='create', method='POST') == '/entries'
225            True
226
227        """
228        if formatted or (formatted is None and self.formatted):
229            suffix = '{.format}'
230        else:
231            suffix = ''
232        return self.connect(name or (action + '_' + self.resource_name),
233                            suffix,
234                            action=action or name,
235                            **_kwargs_with_conditions(kwargs, method))
236           
237    def index(self, name=None, **kwargs):
238        """Generates the "index" action for a collection submapper."""
239        return self.action(name=name or self.collection_name,
240                           action='index', method='GET', **kwargs)
241
242    def show(self, name = None, **kwargs):
243        """Generates the "show" action for a collection member submapper."""
244        return self.action(name=name or self.resource_name,
245                           action='show', method='GET', **kwargs)
246
247    def create(self, **kwargs):
248        """Generates the "create" action for a collection submapper."""
249        return self.action(action='create', method='POST', **kwargs)
250       
251    def update(self, **kwargs):
252        """Generates the "update" action for a collection member submapper."""
253        return self.action(action='update', method='PUT', **kwargs)
254
255    def delete(self, **kwargs):
256        """Generates the "delete" action for a collection member submapper."""
257        return self.action(action='delete', method='DELETE', **kwargs)
258
259    def add_actions(self, actions):
260        [getattr(self, action)() for action in actions]
261
262    # Provided for those who prefer using the 'with' syntax in Python 2.5+
263    def __enter__(self):
264        return self
265   
266    def __exit__(self, type, value, tb):
267        pass
268
269# Create kwargs with a 'conditions' member generated for the given method
270def _kwargs_with_conditions(kwargs, method):
271    if method and 'conditions' not in kwargs:
272        newkwargs = kwargs.copy()
273        newkwargs['conditions'] = {'method': method}               
274        return newkwargs             
275    else:
276        return kwargs
277
278
279
280class Mapper(SubMapperParent):
281    """Mapper handles URL generation and URL recognition in a web
282    application.
283   
284    Mapper is built handling dictionary's. It is assumed that the web
285    application will handle the dictionary returned by URL recognition
286    to dispatch appropriately.
287   
288    URL generation is done by passing keyword parameters into the
289    generate function, a URL is then returned.
290   
291    """
292    def __init__(self, controller_scan=controller_scan, directory=None,
293                 always_scan=False, register=True, explicit=True):
294        """Create a new Mapper instance
295       
296        All keyword arguments are optional.
297       
298        ``controller_scan``
299            Function reference that will be used to return a list of
300            valid controllers used during URL matching. If
301            ``directory`` keyword arg is present, it will be passed
302            into the function during its call. This option defaults to
303            a function that will scan a directory for controllers.
304           
305            Alternatively, a list of controllers or None can be passed
306            in which are assumed to be the definitive list of
307            controller names valid when matching 'controller'.
308       
309        ``directory``
310            Passed into controller_scan for the directory to scan. It
311            should be an absolute path if using the default
312            ``controller_scan`` function.
313       
314        ``always_scan``
315            Whether or not the ``controller_scan`` function should be
316            run during every URL match. This is typically a good idea
317            during development so the server won't need to be restarted
318            anytime a controller is added.
319       
320        ``register``
321            Boolean used to determine if the Mapper should use
322            ``request_config`` to register itself as the mapper. Since
323            it's done on a thread-local basis, this is typically best
324            used during testing though it won't hurt in other cases.
325       
326        ``explicit``
327            Boolean used to determine if routes should be connected
328            with implicit defaults of::
329               
330                {'controller':'content','action':'index','id':None}
331           
332            When set to True, these defaults will not be added to route
333            connections and ``url_for`` will not use Route memory.
334               
335        Additional attributes that may be set after mapper
336        initialization (ie, map.ATTRIBUTE = 'something'):
337       
338        ``encoding``
339            Used to indicate alternative encoding/decoding systems to
340            use with both incoming URL's, and during Route generation
341            when passed a Unicode string. Defaults to 'utf-8'.
342       
343        ``decode_errors``
344            How to handle errors in the encoding, generally ignoring
345            any chars that don't convert should be sufficient. Defaults
346            to 'ignore'.
347       
348        ``minimization``
349            Boolean used to indicate whether or not Routes should
350            minimize URL's and the generated URL's, or require every
351            part where it appears in the path. Defaults to True.
352       
353        ``hardcode_names``
354            Whether or not Named Routes result in the default options
355            for the route being used *or* if they actually force url
356            generation to use the route. Defaults to False.
357       
358        """
359        self.matchlist = []
360        self.maxkeys = {}
361        self.minkeys = {}
362        self.urlcache = LRUCache(1600)
363        self._created_regs = False
364        self._created_gens = False
365        self._master_regexp = None
366        self.prefix = None
367        self.req_data = threading.local()
368        self.directory = directory
369        self.always_scan = always_scan
370        self.controller_scan = controller_scan
371        self._regprefix = None
372        self._routenames = {}
373        self.debug = False
374        self.append_slash = False
375        self.sub_domains = False
376        self.sub_domains_ignore = []
377        self.domain_match = '[^\.\/]+?\.[^\.\/]+'
378        self.explicit = explicit
379        self.encoding = 'utf-8'
380        self.decode_errors = 'ignore'
381        self.hardcode_names = True
382        self.minimization = False
383        self.create_regs_lock = threading.Lock()
384        if register:
385            config = request_config()
386            config.mapper = self
387   
388    def __str__(self):
389        """Generates a tabular string representation."""
390        def format_methods(r):
391            if r.conditions:
392                method = r.conditions.get('method', '')
393                return type(method) is str and method or ', '.join(method)
394            else:
395                return ''
396
397        table = [('Route name', 'Methods', 'Path')] + \
398                [(r.name or '', format_methods(r), r.routepath or '')
399                 for r in self.matchlist]
400           
401        widths = [max(len(row[col]) for row in table)
402                  for col in range(len(table[0]))]
403       
404        return '\n'.join(
405            ' '.join(row[col].ljust(widths[col])
406                     for col in range(len(widths)))
407            for row in table)
408
409    def _envget(self):
410        try:
411            return self.req_data.environ
412        except AttributeError:
413            return None
414    def _envset(self, env):
415        self.req_data.environ = env
416    def _envdel(self):
417        del self.req_data.environ
418    environ = property(_envget, _envset, _envdel)
419   
420    def extend(self, routes, path_prefix=''):
421        """Extends the mapper routes with a list of Route objects
422       
423        If a path_prefix is provided, all the routes will have their
424        path prepended with the path_prefix.
425       
426        Example::
427           
428            >>> map = Mapper(controller_scan=None)
429            >>> map.connect('home', '/', controller='home', action='splash')
430            >>> map.matchlist[0].name == 'home'
431            True
432            >>> routes = [Route('index', '/index.htm', controller='home',
433            ...                 action='index')]
434            >>> map.extend(routes)
435            >>> len(map.matchlist) == 2
436            True
437            >>> map.extend(routes, path_prefix='/subapp')
438            >>> len(map.matchlist) == 3
439            True
440            >>> map.matchlist[2].routepath == '/subapp/index.htm'
441            True
442       
443        .. note::
444           
445            This function does not merely extend the mapper with the
446            given list of routes, it actually creates new routes with
447            identical calling arguments.
448       
449        """
450        for route in routes:
451            if path_prefix and route.minimization:
452                routepath = '/'.join([path_prefix, route.routepath])
453            elif path_prefix:
454                routepath = path_prefix + route.routepath
455            else:
456                routepath = route.routepath
457            self.connect(route.name, routepath, **route._kargs)
458               
459    def connect(self, *args, **kargs):
460        """Create and connect a new Route to the Mapper.
461       
462        Usage:
463       
464        .. code-block:: python
465       
466            m = Mapper()
467            m.connect(':controller/:action/:id')
468            m.connect('date/:year/:month/:day', controller="blog", action="view")
469            m.connect('archives/:page', controller="blog", action="by_page",
470            requirements = { 'page':'\d{1,2}' })
471            m.connect('category_list', 'archives/category/:section', controller='blog', action='category',
472            section='home', type='list')
473            m.connect('home', '', controller='blog', action='view', section='home')
474       
475        """
476        routename = None
477        if len(args) > 1:
478            routename = args[0]
479        else:
480            args = (None,) + args
481        if '_explicit' not in kargs:
482            kargs['_explicit'] = self.explicit
483        if '_minimize' not in kargs:
484            kargs['_minimize'] = self.minimization
485        route = Route(*args, **kargs)
486               
487        # Apply encoding and errors if its not the defaults and the route
488        # didn't have one passed in.
489        if (self.encoding != 'utf-8' or self.decode_errors != 'ignore') and \
490           '_encoding' not in kargs:
491            route.encoding = self.encoding
492            route.decode_errors = self.decode_errors
493       
494        if not route.static:
495            self.matchlist.append(route)
496       
497        if routename:
498            self._routenames[routename] = route
499            route.name = routename
500        if route.static:
501            return
502        exists = False
503        for key in self.maxkeys:
504            if key == route.maxkeys:
505                self.maxkeys[key].append(route)
506                exists = True
507                break
508        if not exists:
509            self.maxkeys[route.maxkeys] = [route]
510        self._created_gens = False
511   
512    def _create_gens(self):
513        """Create the generation hashes for route lookups"""
514        # Use keys temporailly to assemble the list to avoid excessive
515        # list iteration testing with "in"
516        controllerlist = {}
517        actionlist = {}
518       
519        # Assemble all the hardcoded/defaulted actions/controllers used
520        for route in self.matchlist:
521            if route.static:
522                continue
523            if route.defaults.has_key('controller'):
524                controllerlist[route.defaults['controller']] = True
525            if route.defaults.has_key('action'):
526                actionlist[route.defaults['action']] = True
527       
528        # Setup the lists of all controllers/actions we'll add each route
529        # to. We include the '*' in the case that a generate contains a
530        # controller/action that has no hardcodes
531        controllerlist = controllerlist.keys() + ['*']
532        actionlist = actionlist.keys() + ['*']
533       
534        # Go through our list again, assemble the controllers/actions we'll
535        # add each route to. If its hardcoded, we only add it to that dict key.
536        # Otherwise we add it to every hardcode since it can be changed.
537        gendict = {} # Our generated two-deep hash
538        for route in self.matchlist:
539            if route.static:
540                continue
541            clist = controllerlist
542            alist = actionlist
543            if 'controller' in route.hardcoded:
544                clist = [route.defaults['controller']]
545            if 'action' in route.hardcoded:
546                alist = [unicode(route.defaults['action'])]
547            for controller in clist:
548                for action in alist:
549                    actiondict = gendict.setdefault(controller, {})
550                    actiondict.setdefault(action, ([], {}))[0].append(route)
551        self._gendict = gendict
552        self._created_gens = True
553
554    def create_regs(self, *args, **kwargs):
555        """Atomically creates regular expressions for all connected
556        routes
557        """
558        self.create_regs_lock.acquire()
559        try:
560            self._create_regs(*args, **kwargs)
561        finally:
562            self.create_regs_lock.release()
563   
564    def _create_regs(self, clist=None):
565        """Creates regular expressions for all connected routes"""
566        if clist is None:
567            if self.directory:
568                clist = self.controller_scan(self.directory)
569            elif callable(self.controller_scan):
570                clist = self.controller_scan()
571            elif not self.controller_scan:
572                clist = []
573            else:
574                clist = self.controller_scan
575       
576        for key, val in self.maxkeys.iteritems():
577            for route in val:
578                route.makeregexp(clist)
579       
580        regexps = []
581        routematches = []
582        for route in self.matchlist:
583            if not route.static:
584                routematches.append(route)
585                regexps.append(route.makeregexp(clist, include_names=False))
586        self._routematches = routematches
587       
588        # Create our regexp to strip the prefix
589        if self.prefix:
590            self._regprefix = re.compile(self.prefix + '(.*)')
591       
592        # Save the master regexp
593        regexp = '|'.join(['(?:%s)' % x for x in regexps])
594        self._master_reg = regexp
595        self._master_regexp = re.compile(regexp)
596        self._created_regs = True
597   
598    def _match(self, url, environ):
599        """Internal Route matcher
600       
601        Matches a URL against a route, and returns a tuple of the match
602        dict and the route object if a match is successfull, otherwise
603        it returns empty.
604       
605        For internal use only.
606       
607        """
608        if not self._created_regs and self.controller_scan:
609            self.create_regs()
610        elif not self._created_regs:
611            raise RoutesException("You must generate the regular expressions"
612                                 " before matching.")
613       
614        if self.always_scan:
615            self.create_regs()
616       
617        matchlog = []
618        if self.prefix:
619            if re.match(self._regprefix, url):
620                url = re.sub(self._regprefix, r'\1', url)
621                if not url:
622                    url = '/'
623            else:
624                return (None, None, matchlog)
625               
626        environ = environ or self.environ
627        sub_domains = self.sub_domains
628        sub_domains_ignore = self.sub_domains_ignore
629        domain_match = self.domain_match
630        debug = self.debug
631       
632        # Check to see if its a valid url against the main regexp
633        # Done for faster invalid URL elimination
634        valid_url = re.match(self._master_regexp, url)
635        if not valid_url:
636            return (None, None, matchlog)
637       
638        for route in self.matchlist:
639            if route.static:
640                if debug:
641                    matchlog.append(dict(route=route, static=True))
642                continue
643            match = route.match(url, environ, sub_domains, sub_domains_ignore,
644                                domain_match)
645            if debug:
646                matchlog.append(dict(route=route, regexp=bool(match)))
647            if isinstance(match, dict) or match:
648                return (match, route, matchlog)
649        return (None, None, matchlog)
650   
651    def match(self, url=None, environ=None):
652        """Match a URL against against one of the routes contained.
653       
654        Will return None if no valid match is found.
655       
656        .. code-block:: python
657           
658            resultdict = m.match('/joe/sixpack')
659       
660        """
661        if not url and not environ:
662            raise RoutesException('URL or environ must be provided')
663       
664        if not url:
665            url = environ['PATH_INFO']
666               
667        result = self._match(url, environ)
668        if self.debug:
669            return result[0], result[1], result[2]
670        if isinstance(result[0], dict) or result[0]:
671            return result[0]
672        return None
673   
674    def routematch(self, url=None, environ=None):
675        """Match a URL against against one of the routes contained.
676       
677        Will return None if no valid match is found, otherwise a
678        result dict and a route object is returned.
679       
680        .. code-block:: python
681       
682            resultdict, route_obj = m.match('/joe/sixpack')
683       
684        """
685        if not url and not environ:
686            raise RoutesException('URL or environ must be provided')
687       
688        if not url:
689            url = environ['PATH_INFO']
690        result = self._match(url, environ)
691        if self.debug:
692            return result[0], result[1], result[2]
693        if isinstance(result[0], dict) or result[0]:
694            return result[0], result[1]
695        return None
696   
697    def generate(self, *args, **kargs):
698        """Generate a route from a set of keywords
699       
700        Returns the url text, or None if no URL could be generated.
701       
702        .. code-block:: python
703           
704            m.generate(controller='content',action='view',id=10)
705       
706        """
707        # Generate ourself if we haven't already
708        if not self._created_gens:
709            self._create_gens()
710       
711        if self.append_slash:
712            kargs['_append_slash'] = True
713       
714        if not self.explicit:
715            if 'controller' not in kargs:
716                kargs['controller'] = 'content'
717            if 'action' not in kargs:
718                kargs['action'] = 'index'
719       
720        environ = kargs.pop('_environ', self.environ)
721        controller = kargs.get('controller', None)
722        action = kargs.get('action', None)
723
724        # If the URL didn't depend on the SCRIPT_NAME, we'll cache it
725        # keyed by just by kargs; otherwise we need to cache it with
726        # both SCRIPT_NAME and kargs:
727        cache_key = unicode(args).encode('utf8') + \
728            unicode(kargs).encode('utf8')
729       
730        if self.urlcache is not None:
731            if self.environ:
732                cache_key_script_name = '%s:%s' % (
733                    environ.get('SCRIPT_NAME', ''), cache_key)
734            else:
735                cache_key_script_name = cache_key
736       
737            # Check the url cache to see if it exists, use it if it does
738            for key in [cache_key, cache_key_script_name]:
739                if key in self.urlcache:
740                    return self.urlcache[key]
741       
742        actionlist = self._gendict.get(controller) or self._gendict.get('*', {})
743        if not actionlist and not args:
744            return None
745        (keylist, sortcache) = actionlist.get(action) or \
746                               actionlist.get('*', (None, {}))
747        if not keylist and not args:
748            return None
749
750        keys = frozenset(kargs.keys())
751        cacheset = False
752        cachekey = unicode(keys)
753        cachelist = sortcache.get(cachekey)
754        if args:
755            keylist = args
756        elif cachelist:
757            keylist = cachelist
758        else:
759            cacheset = True
760            newlist = []
761            for route in keylist:
762                if len(route.minkeys - route.dotkeys - keys) == 0:
763                    newlist.append(route)
764            keylist = newlist
765           
766            def keysort(a, b):
767                """Sorts two sets of sets, to order them ideally for
768                matching."""
769                am = a.minkeys
770                a = a.maxkeys
771                b = b.maxkeys
772               
773                lendiffa = len(keys^a)
774                lendiffb = len(keys^b)
775                # If they both match, don't switch them
776                if lendiffa == 0 and lendiffb == 0:
777                    return 0
778               
779                # First, if a matches exactly, use it
780                if lendiffa == 0:
781                    return -1
782               
783                # Or b matches exactly, use it
784                if lendiffb == 0:
785                    return 1
786               
787                # Neither matches exactly, return the one with the most in
788                # common
789                if cmp(lendiffa, lendiffb) != 0:
790                    return cmp(lendiffa, lendiffb)
791               
792                # Neither matches exactly, but if they both have just as much
793                # in common
794                if len(keys&b) == len(keys&a):
795                    # Then we return the shortest of the two
796                    return cmp(len(a), len(b))
797               
798                # Otherwise, we return the one that has the most in common
799                else:
800                    return cmp(len(keys&b), len(keys&a))
801           
802            keylist.sort(keysort)
803            if cacheset:
804                sortcache[cachekey] = keylist
805               
806        # Iterate through the keylist of sorted routes (or a single route if
807        # it was passed in explicitly for hardcoded named routes)
808        for route in keylist:
809            fail = False
810            for key in route.hardcoded:
811                kval = kargs.get(key)
812                if not kval:
813                    continue
814                if isinstance(kval, str):
815                    kval = kval.decode(self.encoding)
816                else:
817                    kval = unicode(kval)
818                if kval != route.defaults[key] and not callable(route.defaults[key]):
819                    fail = True
820                    break
821            if fail:
822                continue
823            path = route.generate(**kargs)
824            if path:
825                if self.prefix:
826                    path = self.prefix + path
827                external_static = route.static and route.external
828                if environ and environ.get('SCRIPT_NAME', '') != ''\
829                    and not route.absolute and not external_static:
830                    path = environ['SCRIPT_NAME'] + path
831                    key = cache_key_script_name
832                else:
833                    key = cache_key
834                if self.urlcache is not None:
835                    self.urlcache[key] = str(path)
836                return str(path)
837            else:
838                continue
839        return None
840   
841    def resource(self, member_name, collection_name, **kwargs):
842        """Generate routes for a controller resource
843       
844        The member_name name should be the appropriate singular version
845        of the resource given your locale and used with members of the
846        collection. The collection_name name will be used to refer to
847        the resource collection methods and should be a plural version
848        of the member_name argument. By default, the member_name name
849        will also be assumed to map to a controller you create.
850       
851        The concept of a web resource maps somewhat directly to 'CRUD'
852        operations. The overlying things to keep in mind is that
853        mapping a resource is about handling creating, viewing, and
854        editing that resource.
855       
856        All keyword arguments are optional.
857       
858        ``controller``
859            If specified in the keyword args, the controller will be
860            the actual controller used, but the rest of the naming
861            conventions used for the route names and URL paths are
862            unchanged.
863       
864        ``collection``
865            Additional action mappings used to manipulate/view the
866            entire set of resources provided by the controller.
867           
868            Example::
869               
870                map.resource('message', 'messages', collection={'rss':'GET'})
871                # GET /message/rss (maps to the rss action)
872                # also adds named route "rss_message"
873       
874        ``member``
875            Additional action mappings used to access an individual
876            'member' of this controllers resources.
877           
878            Example::
879               
880                map.resource('message', 'messages', member={'mark':'POST'})
881                # POST /message/1/mark (maps to the mark action)
882                # also adds named route "mark_message"
883       
884        ``new``
885            Action mappings that involve dealing with a new member in
886            the controller resources.
887           
888            Example::
889               
890                map.resource('message', 'messages', new={'preview':'POST'})
891                # POST /message/new/preview (maps to the preview action)
892                # also adds a url named "preview_new_message"
893       
894        ``path_prefix``
895            Prepends the URL path for the Route with the path_prefix
896            given. This is most useful for cases where you want to mix
897            resources or relations between resources.
898       
899        ``name_prefix``
900            Perpends the route names that are generated with the
901            name_prefix given. Combined with the path_prefix option,
902            it's easy to generate route names and paths that represent
903            resources that are in relations.
904           
905            Example::
906               
907                map.resource('message', 'messages', controller='categories',
908                    path_prefix='/category/:category_id',
909                    name_prefix="category_")
910                # GET /category/7/message/1
911                # has named route "category_message"
912               
913        ``parent_resource``
914            A ``dict`` containing information about the parent
915            resource, for creating a nested resource. It should contain
916            the ``member_name`` and ``collection_name`` of the parent
917            resource. This ``dict`` will
918            be available via the associated ``Route`` object which can
919            be accessed during a request via
920            ``request.environ['routes.route']``
921 
922            If ``parent_resource`` is supplied and ``path_prefix``
923            isn't, ``path_prefix`` will be generated from
924            ``parent_resource`` as
925            "<parent collection name>/:<parent member name>_id".
926
927            If ``parent_resource`` is supplied and ``name_prefix``
928            isn't, ``name_prefix`` will be generated from
929            ``parent_resource`` as  "<parent member name>_".
930 
931            Example::
932 
933                >>> from routes.util import url_for
934                >>> m = Mapper()
935                >>> m.resource('location', 'locations',
936                ...            parent_resource=dict(member_name='region',
937                ...                                 collection_name='regions'))
938                >>> # path_prefix is "regions/:region_id"
939                >>> # name prefix is "region_" 
940                >>> url_for('region_locations', region_id=13)
941                '/regions/13/locations'
942                >>> url_for('region_new_location', region_id=13)
943                '/regions/13/locations/new'
944                >>> url_for('region_location', region_id=13, id=60)
945                '/regions/13/locations/60'
946                >>> url_for('region_edit_location', region_id=13, id=60)
947                '/regions/13/locations/60/edit'
948
949            Overriding generated ``path_prefix``::
950
951                >>> m = Mapper()
952                >>> m.resource('location', 'locations',
953                ...            parent_resource=dict(member_name='region',
954                ...                                 collection_name='regions'),
955                ...            path_prefix='areas/:area_id')
956                >>> # name prefix is "region_"
957                >>> url_for('region_locations', area_id=51)
958                '/areas/51/locations'
959
960            Overriding generated ``name_prefix``::
961
962                >>> m = Mapper()
963                >>> m.resource('location', 'locations',
964                ...            parent_resource=dict(member_name='region',
965                ...                                 collection_name='regions'),
966                ...            name_prefix='')
967                >>> # path_prefix is "regions/:region_id"
968                >>> url_for('locations', region_id=51)
969                '/regions/51/locations'
970
971        """
972        collection = kwargs.pop('collection', {})
973        member = kwargs.pop('member', {})
974        new = kwargs.pop('new', {})
975        path_prefix = kwargs.pop('path_prefix', None)
976        name_prefix = kwargs.pop('name_prefix', None)
977        parent_resource = kwargs.pop('parent_resource', None)
978       
979        # Generate ``path_prefix`` if ``path_prefix`` wasn't specified and
980        # ``parent_resource`` was. Likewise for ``name_prefix``. Make sure
981        # that ``path_prefix`` and ``name_prefix`` *always* take precedence if
982        # they are specified--in particular, we need to be careful when they
983        # are explicitly set to "".
984        if parent_resource is not None:
985            if path_prefix is None:
986                path_prefix = '%s/:%s_id' % (parent_resource['collection_name'],
987                                             parent_resource['member_name'])
988            if name_prefix is None:
989                name_prefix = '%s_' % parent_resource['member_name']
990        else:
991            if path_prefix is None: path_prefix = ''
992            if name_prefix is None: name_prefix = ''
993       
994        # Ensure the edit and new actions are in and GET
995        member['edit'] = 'GET'
996        new.update({'new': 'GET'})
997       
998        # Make new dict's based off the old, except the old values become keys,
999        # and the old keys become items in a list as the value
1000        def swap(dct, newdct):
1001            """Swap the keys and values in the dict, and uppercase the values
1002            from the dict during the swap."""
1003            for key, val in dct.iteritems():
1004                newdct.setdefault(val.upper(), []).append(key)
1005            return newdct
1006        collection_methods = swap(collection, {})
1007        member_methods = swap(member, {})
1008        new_methods = swap(new, {})
1009       
1010        # Insert create, update, and destroy methods
1011        collection_methods.setdefault('POST', []).insert(0, 'create')
1012        member_methods.setdefault('PUT', []).insert(0, 'update')
1013        member_methods.setdefault('DELETE', []).insert(0, 'delete')
1014       
1015        # If there's a path prefix option, use it with the controller
1016        controller = strip_slashes(collection_name)
1017        path_prefix = strip_slashes(path_prefix)
1018        path_prefix = '/' + path_prefix
1019        if path_prefix and path_prefix != '/':
1020            path = path_prefix + '/' + controller
1021        else:
1022            path = '/' + controller
1023        collection_path = path
1024        new_path = path + "/new"
1025        member_path = path + "/:(id)"
1026       
1027        options = {
1028            'controller': kwargs.get('controller', controller),
1029            '_member_name': member_name,
1030            '_collection_name': collection_name,
1031            '_parent_resource': parent_resource,
1032            '_filter': kwargs.get('_filter')
1033        }
1034       
1035        def requirements_for(meth):
1036            """Returns a new dict to be used for all route creation as the
1037            route options"""
1038            opts = options.copy()
1039            if method != 'any':
1040                opts['conditions'] = {'method':[meth.upper()]}
1041            return opts
1042       
1043        # Add the routes for handling collection methods
1044        for method, lst in collection_methods.iteritems():
1045            primary = (method != 'GET' and lst.pop(0)) or None
1046            route_options = requirements_for(method)
1047            for action in lst:
1048                route_options['action'] = action
1049                route_name = "%s%s_%s" % (name_prefix, action, collection_name)
1050                self.connect("formatted_" + route_name, "%s/%s.:(format)" % \
1051                             (collection_path, action), **route_options)
1052                self.connect(route_name, "%s/%s" % (collection_path, action),
1053                                                    **route_options)
1054            if primary:
1055                route_options['action'] = primary
1056                self.connect("%s.:(format)" % collection_path, **route_options)
1057                self.connect(collection_path, **route_options)
1058       
1059        # Specifically add in the built-in 'index' collection method and its
1060        # formatted version
1061        self.connect("formatted_" + name_prefix + collection_name,
1062            collection_path + ".:(format)", action='index',
1063            conditions={'method':['GET']}, **options)
1064        self.connect(name_prefix + collection_name, collection_path,
1065                     action='index', conditions={'method':['GET']}, **options)
1066       
1067        # Add the routes that deal with new resource methods
1068        for method, lst in new_methods.iteritems():
1069            route_options = requirements_for(method)
1070            for action in lst:
1071                path = (action == 'new' and new_path) or "%s/%s" % (new_path,
1072                                                                    action)
1073                name = "new_" + member_name
1074                if action != 'new':
1075                    name = action + "_" + name
1076                route_options['action'] = action
1077                formatted_path = (action == 'new' and new_path + '.:(format)') or \
1078                    "%s/%s.:(format)" % (new_path, action)
1079                self.connect("formatted_" + name_prefix + name, formatted_path,
1080                             **route_options)
1081                self.connect(name_prefix + name, path, **route_options)
1082       
1083        requirements_regexp = '[^\/]+'
1084
1085        # Add the routes that deal with member methods of a resource
1086        for method, lst in member_methods.iteritems():
1087            route_options = requirements_for(method)
1088            route_options['requirements'] = {'id':requirements_regexp}
1089            if method not in ['POST', 'GET', 'any']:
1090                primary = lst.pop(0)
1091            else:
1092                primary = None
1093            for action in lst:
1094                route_options['action'] = action
1095                self.connect("formatted_%s%s_%s" % (name_prefix, action,
1096                                                    member_name),
1097                    "%s/%s.:(format)" % (member_path, action), **route_options)
1098                self.connect("%s%s_%s" % (name_prefix, action, member_name),
1099                    "%s/%s" % (member_path, action), **route_options)
1100            if primary:
1101                route_options['action'] = primary
1102                self.connect("%s.:(format)" % member_path, **route_options)
1103                self.connect(member_path, **route_options)
1104       
1105        # Specifically add the member 'show' method
1106        route_options = requirements_for('GET')
1107        route_options['action'] = 'show'
1108        route_options['requirements'] = {'id':requirements_regexp}
1109        self.connect("formatted_" + name_prefix + member_name,
1110                     member_path + ".:(format)", **route_options)
1111        self.connect(name_prefix + member_name, member_path, **route_options)
1112   
1113    def redirect(self, match_path, destination_path, *args, **kwargs):
1114        """Add a redirect route to the mapper
1115       
1116        Redirect routes bypass the wrapped WSGI application and instead
1117        result in a redirect being issued by the RoutesMiddleware. As
1118        such, this method is only meaningful when using
1119        RoutesMiddleware.
1120       
1121        By default, a 302 Found status code is used, this can be
1122        changed by providing a ``_redirect_code`` keyword argument
1123        which will then be used instead. Note that the entire status
1124        code string needs to be present.
1125       
1126        When using keyword arguments, all arguments that apply to
1127        matching will be used for the match, while generation specific
1128        options will be used during generation. Thus all options
1129        normally available to connected Routes may be used with
1130        redirect routes as well.
1131       
1132        Example::
1133           
1134            map = Mapper()
1135            map.redirect('/legacyapp/archives/{url:.*}, '/archives/{url})
1136            map.redirect('/home/index', '/', _redirect_code='301 Moved Permanently')
1137       
1138        """
1139        both_args = ['_encoding', '_explicit', '_minimize']
1140        gen_args = ['_filter']
1141       
1142        status_code = kwargs.pop('_redirect_code', '302 Found')
1143        gen_dict, match_dict = {}, {}
1144       
1145        # Create the dict of args for the generation route
1146        for key in both_args + gen_args:
1147            if key in kwargs:
1148                gen_dict[key] = kwargs[key]
1149        gen_dict['_static'] = True
1150       
1151        # Create the dict of args for the matching route
1152        for key in kwargs:
1153            if key not in gen_args:
1154                match_dict[key] = kwargs[key]
1155       
1156        self.connect(match_path, **match_dict)
1157        match_route = self.matchlist[-1]
1158       
1159        self.connect('_redirect_%s' % id(match_route), destination_path,
1160                     **gen_dict)
1161        match_route.redirect = True
1162        match_route.redirect_status = status_code
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。