annotate roundup/web/router.py @ 4999:e9077def7678 routing

router: Add interactive mode and iterator over urlmap
author anatoly techtonik <techtonik@gmail.com>
date Thu, 10 Sep 2015 12:33:12 +0300
parents d8e0af01543b
children ce06c665932a
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
4943
7920d700e580 Add support for extensions to provide custom pages to Roundup
anatoly techtonik <techtonik@gmail.com>
parents: 4925
diff changeset
2 # This Router component was written by techtonik@gmail.com and it's been
7920d700e580 Add support for extensions to provide custom pages to Roundup
anatoly techtonik <techtonik@gmail.com>
parents: 4925
diff changeset
3 # placed in the Public Domain. Copy and modify to your heart's content.
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 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
6 The purpose of router is to make Roundup URL scheme configurable
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
7 and allow extensions add their own handlers and URLs to tracker.
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
8 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
9
4924
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
10 DEBUG = False
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
11
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
12
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
13 import re
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
14
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
15 # --- Example URL mapping
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
16
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
17 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
18 """Object that outputs its name when printed"""
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
19 def __init__(self, name):
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
20 self.name = name
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
21 def __repr__(self):
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
22 return self.name
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
23
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
24 ExampleHandler = NamedObject('ExampleHandler')
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
25 ExampleFileHandler = NamedObject('ExampleFileHandler')
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
26
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
27
4918
35dc9191394d routing: Use Router in cgi.client.main
anatoly techtonik <techtonik@gmail.com>
parents: 4910
diff changeset
28 EXAMPLE_URL_MAP = (
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
29 'static/(.*)', ExampleFileHandler,
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
30 'index', ExampleHandler
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
31 )
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
32
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
33 # --- Helper functions
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
34
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
35 def entry(prompt='> '):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
36 """Just get text for interactive mode"""
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
37 import sys
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
38 if sys.version_info[0] < 3:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
39 return raw_input(prompt)
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
40 else:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
41 return input(prompt)
4906
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
42
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
43 # --- Regexp based router
b860ede03056 routing: Add example URL map
anatoly techtonik <techtonik@gmail.com>
parents: 4905
diff changeset
44
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
45 class Router(object):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
46
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
47 urlmap = []
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
48
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
49 def __init__(self, urlmap=[]):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
50 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
51 `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
52 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
53 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
54 self.urlmap = urlmap
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
55
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
56 def get_handler(self, urlpath):
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
57 """
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
58 `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
59 (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
60
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
61 returns tuple (handler, arguments) or (None, ())
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
62 """
4909
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
63 # strip leading slashes before matching
f31c93abedf6 routing: No leading slash in URL map patterns
anatoly techtonik <techtonik@gmail.com>
parents: 4907
diff changeset
64 path = urlpath.lstrip('/')
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
65 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
66 pattern = pattern.lstrip('/')
4924
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
67 if DEBUG:
6ee1844019d5 routing: Add DEBUG flag for troubleshooting
anatoly techtonik <techtonik@gmail.com>
parents: 4918
diff changeset
68 print('router: matching %s' % pattern)
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
69 match = re.match(pattern, path)
4905
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
70 if match:
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
71 return handler, match.groups()
6e313bdf6b69 routing: Add new roundup.web namespace with router component
anatoly techtonik <techtonik@gmail.com>
parents:
diff changeset
72 return (None, ())
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
73
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
74 def iter_urlmap(self):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
75 """
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
76 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
77 """
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
78 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
79 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
80
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
81 def interactive(self):
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
82 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
83 url = entry('url: ')
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
84 while url != '':
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
85 if url == 'l':
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
86 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
87 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
88 print(self.urlmap[i:i+2])
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
89 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
90 url = entry('url: ')
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
91
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 # [ ] 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
94 # (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
95
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
96 if __name__ == '__main__':
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
97
4999
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
98 import sys
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
99 if '-i' in sys.argv:
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
100 router = Router(EXAMPLE_URL_MAP)
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
101 router.interactive()
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
102 sys.exit()
e9077def7678 router: Add interactive mode and iterator over urlmap
anatoly techtonik <techtonik@gmail.com>
parents: 4998
diff changeset
103
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
104 import unittest
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
105 class test_Router(unittest.TestCase):
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
106 def test_example_routes(self):
4918
35dc9191394d routing: Use Router in cgi.client.main
anatoly techtonik <techtonik@gmail.com>
parents: 4910
diff changeset
107 router = Router(EXAMPLE_URL_MAP)
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
108 self.assertEqual(router.get_handler(''), (None, ()))
4910
e5f90a69f660 routing: Fix router test
anatoly techtonik <techtonik@gmail.com>
parents: 4909
diff changeset
109 handler, params = router.get_handler('/index')
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
110 self.assertEqual(handler, ExampleHandler)
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
111 self.assertEqual(params, tuple())
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
112
4925
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
113 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
114 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
115 return args
997fa47c92d5 routing: Strip leading slash from both path and pattern, add test
anatoly techtonik <techtonik@gmail.com>
parents: 4924
diff changeset
116 router = Router(('/files/(.*)', echo_handler))
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
117 self.assertEqual(router.get_handler(''), (None, ()))
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
118 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
119 handler, params = router.get_handler('/files/filename')
4998
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
120 self.assertEqual(handler, echo_handler)
d8e0af01543b Fix unittest warning about deprecated assertEquals
anatoly techtonik <techtonik@gmail.com>
parents: 4943
diff changeset
121 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
122
4907
c37069a99cec routing: Add self-test to router.py
anatoly techtonik <techtonik@gmail.com>
parents: 4906
diff changeset
123 unittest.main()

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