annotate roundup/web/router.py @ 5002:ce06c665932a routing

Update router docstring to reference usage example
author anatoly techtonik <techtonik@gmail.com>
date Fri, 11 Sep 2015 11:08:34 +0300
parents e9077def7678
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
1 #!/usr/bin/env python
5002
ce06c665932a Update router docstring to reference usage example
anatoly techtonik <techtonik@gmail.com>
parents: 4999
diff changeset
2 # The Router component is placed into public domain by
ce06c665932a Update router docstring to reference usage example
anatoly techtonik <techtonik@gmail.com>
parents: 4999
diff changeset
3 # anatoly techtonik <techtonik@gmail.com>
4943
7920d700e580 Add support for extensions to provide custom pages to Roundup
anatoly techtonik <techtonik@gmail.com>
parents: 4925
diff changeset
4
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
5 """
5002
ce06c665932a Update router docstring to reference usage example
anatoly techtonik <techtonik@gmail.com>
parents: 4999
diff changeset
6 Router allows extensions to add handlers for their own URLs to
ce06c665932a Update router docstring to reference usage example
anatoly techtonik <techtonik@gmail.com>
parents: 4999
diff changeset
7 tracker. To test how it works, copy
ce06c665932a Update router docstring to reference usage example
anatoly techtonik <techtonik@gmail.com>
parents: 4999
diff changeset
8 plugins/extensions/custompage.py module to your tracker.
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
9 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
10
4924
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
11 DEBUG = False
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
12
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
13
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
14 import re
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
15
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
16 # --- Example URL mapping
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
17
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
18 class NamedObject(object):
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
19 """Object that outputs its name when printed"""
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
20 def __init__(self, name):
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
21 self.name = name
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
22 def __repr__(self):
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
23 return self.name
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
24
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
25 ExampleHandler = NamedObject('ExampleHandler')
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
26 ExampleFileHandler = NamedObject('ExampleFileHandler')
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
27
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
28
4918
35dc9191394d routing: Use Router in cgi.client.main
anatoly techtonik <techtonik@gmail.com>
parents: 4910
diff changeset
29 EXAMPLE_URL_MAP = (
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
30 'static/(.*)', ExampleFileHandler,
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
31 'index', ExampleHandler
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
32 )
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
33
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
34 # --- Helper functions
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
35
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
36 def entry(prompt='> '):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
37 """Just get text for interactive mode"""
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
38 import sys
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
39 if sys.version_info[0] < 3:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
40 return raw_input(prompt)
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
41 else:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
42 return input(prompt)
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
43
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
44 # --- Regexp based router
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
45
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
46 class Router(object):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
47
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
48 urlmap = []
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
49
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
50 def __init__(self, urlmap=[]):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
51 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
52 `urlmap` is a list (pattern, handler, pattern, ...)
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
53 leading slash in pattern is stripped
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
54 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
55 self.urlmap = urlmap
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
56
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
57 def get_handler(self, urlpath):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
58 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
59 `urlpath` is a part of url /that/looks?like=this
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
60 (leading slash is optional, will be stripped anyway)
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
61
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
62 returns tuple (handler, arguments) or (None, ())
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
63 """
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
64 # strip leading slashes before matching
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
65 path = urlpath.lstrip('/')
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
66 for pattern, handler in self.iter_urlmap():
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
67 pattern = pattern.lstrip('/')
4924
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
68 if DEBUG:
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
69 print('router: matching %s' % pattern)
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
70 match = re.match(pattern, path)
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
71 if match:
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
72 return handler, match.groups()
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
73 return (None, ())
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
74
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
75 def iter_urlmap(self):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
76 """
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
77 iterate over self.urlmap returning (pattern, handler) pairs
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
78 """
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
79 for i in range(0, len(self.urlmap), 2):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
80 yield self.urlmap[i], self.urlmap[i+1]
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
81
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
82 def interactive(self):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
83 print('enter url to test, [l] to list rules, empty line exits')
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
84 url = entry('url: ')
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
85 while url != '':
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
86 if url == 'l':
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
87 for i in range(0, len(self.urlmap), 2):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
88 pattern, handler = self.urlmap[i], self.urlmap[i+1]
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
89 print(self.urlmap[i:i+2])
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
90 print('matched ' + str(self.get_handler(url)))
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
91 url = entry('url: ')
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
92
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
93
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
94 # [ ] len(urlmap) should be even to avoid errors
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
95 # (find a way to explain this to users)
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
96
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
97 if __name__ == '__main__':
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
98
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
99 import sys
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
100 if '-i' in sys.argv:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
101 router = Router(EXAMPLE_URL_MAP)
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
102 router.interactive()
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
103 sys.exit()
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
104
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
105 import unittest
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
106 class test_Router(unittest.TestCase):
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
107 def test_example_routes(self):
4918
35dc9191394d routing: Use Router in cgi.client.main
anatoly techtonik <techtonik@gmail.com>
parents: 4910
diff changeset
108 router = Router(EXAMPLE_URL_MAP)
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
109 self.assertEqual(router.get_handler(''), (None, ()))
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
110 handler, params = router.get_handler('/index')
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
111 self.assertEqual(handler, ExampleHandler)
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
112 self.assertEqual(params, tuple())
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
113
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
114 def test_route_param(self):
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
115 def echo_handler(args):
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
116 return args
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
117 router = Router(('/files/(.*)', echo_handler))
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
118 self.assertEqual(router.get_handler(''), (None, ()))
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
119 self.assertEqual(router.get_handler('/files'), (None, ()))
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
120 handler, params = router.get_handler('/files/filename')
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
121 self.assertEqual(handler, echo_handler)
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
122 self.assertEqual(params, ('filename',))
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
123
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
124 unittest.main()

Roundup Issue Tracker: http://roundup-tracker.org/