Skip to content
This repository
Newer
Older
100644 132 lines (91 sloc) 4.731 kb
6218709f » anonymous
2006-06-06 edit
1 ---
2 layout: default
3 title: MetaClassAutoURLS
4 ---
5
6 # MetaClassAutoURLS
7
abcf2730 » anonymous
2006-06-06 edit
8 web.py maps URLs regexes to classes via a list like so:
9
10 class Default(object):
11 ''' Action for / ''' def GET(self):
12 pass
13
14 class Login(object):
15 ''' Action for /login ''' def GET(self):
16 pass
17
18 def POST(self):
19 pass
20
21 urls = ["/", "Default", "/login", "Login"]
22
23 One of the disadvantages of this approach is that for large applications, the urls list can get very long and tedious to maintain. Things get worse during long refactorings/reorgnanizations because it's easy to typo a class or module name or just forget to update the urls list. After spending a couple hours debugging just this problem and then smacking myself on the forehead for missing such a simple mistake, I thought to myself, _There's got to be a way to automate this!_ After some research, I found that metaclasses can do exactly what I want:
24
25 urls = [ ]
26
27 class ActionMetaClass(type):
28 def __init__(klass, name, bases, attrs):
29 urls.append(attrs["url"])
30 urls.append("%s.%s" % (klass.__module__, name))
31
32 class Default(object):
33 __metaclass__ = ActionMetaClass
34 url = "/"
35 def GET(self):
36 pass
37
38 class Login(object):
39 __metaclass__ = ActionMetaClass
40 url = "/login"
41 def GET(self):
42 pass
43
44 def POST(self):
45 pass
46
53f57e58 » anonymous
2006-07-11 AaronSw
47 Of course, this is a simple example that leaves a lot to be desired (handling attrs lacking a url key, for instance), but it should get you on your way.
48
49 * * *
50
50ae59b4 » anonymous
2006-07-11 edit
51 **AaronSw writes:** That's a clever idea. One improvement might be to have the classes inherit from a class with the metaclass set, so you don't have that unsightly `__metaclass__` every time.
52
b9531ecf » anonymous
2006-07-17 edit
53 **xunil writes:** Yeah, I went w/ this approach in the end since I found that I wanted each action class to inherit some common functionality anyway. You can [view the source](http://www.theanykey.com/svn/) for [my incomplete site](http://dev.theanykey.com) which uses a metaclass and decorators.
54
50ae59b4 » anonymous
2006-07-11 edit
55 * * *
56
0a896bc3 » anonymous
2006-07-17 edit
57 May be it's better to do it with decorators?
58
b9531ecf » anonymous
2006-07-17 edit
59 **xunil writes:** I originally tried to use decorators to do this since I was already familiar w/ them, but unless I'm missing something, decorators don't work on classes eg.
60
61 @action("/")
62 class Default:
63 def GET(self):
64 pass
65
66 That won't work. [The PEP for decorators](http://www.python.org/dev/peps/pep-0318/) describes it, but apparently it was never incorporated into the language (perhaps b/c metaclasses are available and do the same thing).
67
0a896bc3 » anonymous
2006-07-17 edit
68 * * *
69
98f43eea » anonymous
2006-07-17 edit
70 I couldn't figure out how to get this to work with URL classes in separate files, so I did something a little different, based on [this Python Cookbook recipe](http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/436873):
0a896bc3 » anonymous
2006-07-17 edit
71
72 import os, web
73
74 urls = [ ]
75
76 for aaa in os.listdir(os.getcwd()):
77 module_name, ext = os.path.splitext(aaa)
78 if module_name.startswith('cgi_') and ext == '.py':
79 module = __import__(module_name)
80 urls.append(module.url[0])
81 urls.append(module_name + "." + module.url[1])
82
83 if __name__ == "__main__":
84 web.run(urls)
85
86 And then e.g. cgi_hello.py (in the same directory) would be:
87
88 import web
89 url = ('/(.*)', 'hello')
90
91 class hello:
92 def GET(self, name):
93 i = web.input(times=1)
94 if not name: name = 'world' for c in xrange(int(i.times)): print 'Hello,', name+'!'
b9531ecf » anonymous
2006-07-17 edit
95 Is this a terrible way to do it?
96
97 * * *
98
99 **xunil writes:** This is not altogether a bad idea, but it again decouples the URL information from the class, making it a module-level tuple or list. You could combine your importing logic w/ my metaclass idea, actually, and achieve the same thing as me eg.
100
101 import os, web
102
103 for aaa in os.listdir(os.getcwd()):
104 module_name, ext = os.path.splitext(aaa)
105 if module_name.startswith('cgi_') and ext == '.py':
106 module = __import__(module_name)
107
108 if __name__ == "__main__":
109 import metaclass
110 web.run(metaclass.urls)
111
112 metaclass.py:
113
114 urls = [ ]
115
116 class ActionMetaClass(type):
117 def __init__(klass, name, bases, attrs):
118 urls.append(attrs["url"])
119 urls.append("%s.%s" % (klass.__module__, name))
120
121 cgi_hello.py:
122
123 import web
124 from metaclass import ActionMetaClass
125
126 class hello:
127 __metaclass__ = ActionMetaClass
128 url = '/(.*)'
129 def GET(self, name):
130 i = web.input(times=1)
131 if not name: name = 'world' for c in xrange(int(i.times)): print 'Hello,', name+'!'
132 * * *
Something went wrong with that request. Please try again.