Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 132 lines (91 sloc) 4.731 kb
6218709 edit
anonymous authored
1 ---
2 layout: default
3 title: MetaClassAutoURLS
4 ---
5
6 # MetaClassAutoURLS
7
abcf273 edit
anonymous authored
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
53f57e5 AaronSw
anonymous authored
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
50ae59b edit
anonymous authored
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
b9531ec edit
anonymous authored
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
50ae59b edit
anonymous authored
55 * * *
56
0a896bc edit
anonymous authored
57 May be it's better to do it with decorators?
58
b9531ec edit
anonymous authored
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
0a896bc edit
anonymous authored
68 * * *
69
98f43ee edit
anonymous authored
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):
0a896bc edit
anonymous authored
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+'!'
b9531ec edit
anonymous authored
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.