Good time of day. Not so long ago, we started writing a big project on Pylons and one of the main requirements was the quick connection and removal of controllers without changes to routing.py. One of our employees has already encountered similar and made this functionality through plugins. But, as it seemed to me, the decision was rather cumbersome and it was hard to transfer it from project to project in the future.
Because I’ve dealt with the Catalyst (Perl MVC framework) in the past, and I liked that it was possible to add a URL to each method. Actually I decided to write something similar.
Demand. “We need minimal functionality, with a minimum of gestures. Call the decorator from a URL (or an empty string). Also, so that the already prepared buns with the parameters in the URL are not lost. ”
')
The solution came through decorators. And it turned out that actually there wasn’t much to write and the solution was very portable.
Actually, I'll start with the fact that pylons need to be patched. Harm this patch will not bring your current projects, so you can safely patch.
--- pylons / util.py 2009-12-29 14: 28: 20.000000000 +0600
+++ dev / null 2010-02-05 03: 44: 26.000000000 +0600
@@ -122.6 +122.8 @@
'Oneword'
"" "
+ module_name = module_name.split ('.') [- 1]
words = module_name.replace ('-', '_'). split ('_')
return '' .join ([w.title () for w in words])
The patch fixes a Pylons bug that prevents you from having a substructure in the project_name / controllers / *. For some reason, they made it so that if you write the way to the method of an object with a point, then this point disappears. Actually with this patch you can make any structure in your pylons project.
Next, you need to add the library file to the project (or if you decide to do all projects on our principle, you can put it in a separate lib).
# PROJECT / lib / controllers.py
# - * - coding: utf-8 - * -
import os
def scan_folder_recurse (folder, excl_names = ['__']):
"" "Recurse search for PY files in given folder" ""
all_files = []
for root, dir, files in os.walk (folder):
filelist = \
[os.path.join (root, fi) for files in files if fi.endswith ('. py')
and not any (fi.startswith (prefix) for prefix in excl_names)]
for f in filelist:
all_files.append (f)
return all_files
This function allows you to get a recursive list of all * .py files in the desired directory. Those. if you want, you can not store controllers only in the controllers folder. It will certainly be a bad idea, but the cases are different.
Next, we modify the config / routing.py file and the make_map () function
map = Mapper (directory = config ['pylons.paths'] ['controllers'],
always_scan = config ['debug'])
map.minimization = False
proj_root = os.path.dirname (config ['pylons.paths'] ['root'])
sep = os.path.sep
"" "GETTING ALL FILES ENTIRE CONTROLLERS FOLDER" ""
all_files = scan_folder_recurse (
config ['pylons.paths'] ['controllers'],
excl_names = ['__', 'daemon', 'models'])
log.debug ("Found% d controllers"% len (all_files))
log.debug ("Building route map")
cfg = ConfigParser.RawConfigParser ()
cfg.read (config ['global_conf'] ['__ file__'])
for file in all_files:
t_controller_name = module_path.split ('.') [- 1]
controller_name = '/'.join(module_path.split('.')[2:])
"" "IMPORTING MODULE" ""
controller = __import __ (module_path)
"" "IMPORTING MODULE ENVIRONMENT" ""
controller = sys.modules [module_path]
my_list = dir (controller)
name_re = re.compile (t_controller_name, re.IGNORECASE)
"" "We need classes with methods" ""
control = None
for element in my_list:
if name_re.search (element) and controller_re.search (element):
control = getattr (controller, element)
"" "If class found" ""
if control:
"" "Searching for need property" ""
for item in control .__ dict__:
try:
attrib = control .__ dict __ [item] .__ dict __ ['method']
"" "If Class has method property" ""
if attrib == 'path':
route_path = getattr (control, item) .route_path
else:
route_path = "/% s /% s"% (controller_name, item)
route_path = route_path.rstrip ('/')
"" "Two method to create variations of path" ""
map.connect (
route_path,
controller = controller_name,
action = item)
map.connect (
"% s /"% route_path,
controller = controller_name,
action = item)
log.info ("% s ::% s ---- >>>>% s ..... connected"% \
(controller_name, item, route_path))
except:
pass
log.info ('Route map complite ...')
# The ErrorController route (handles 404/500 error pages); it should
# likely to be resolved
map.connect ('/ error / {action}', controller = 'error')
map.connect ('/ error / {action} / {id}', controller = 'error')
return map
This code snippet can replace the entire make_map () function. Now the function takes a list of * .py files recursively from the folder with the controllers. Makes a check on the existence of properties for each method in each class. If the necessary properties are present, then it is considered that the method should be present in the MAP controllers. Next, the methods found are passed to the MAP. Finally we connect the error controller. Yes, by the way, I was a little unnerved by the feature, with the prescription of 2 maps for each URL: with and without a slash. Now she fixed
So, we now have to do the decorator and apply it.
And so, the lib / decorators.py decorator itself:
# - * - coding: utf-8 - * -
"" "Decorators for project
"" "
def route_action (path = None):
def decorate (f):
if path == None:
setattr (f, 'method', 'local')
else:
setattr (f, 'method', 'path')
setattr (f, 'route_path', path)
return f
return decorate
Those. if you call the decorator without a parameter, the default path will be applied. If with parameter - then customized.
And the actual use of controllers / sample.py
from PROJECT.lib.decorators import route_action
class SampleController (BaseController):
@route_action ('/ sample / hello_world')
def hello (self):
return "This is Sample / Hello method"
@route_action ()
def hello_to_me (self):
return "This is Sample / Hello_to_me local method"
@route_action ('/ sample / hello_world / {id}')
def hello (self, id):
return "This is the Sample / Hello World with ID:% d method"% id
The principle of operation of this whole scheme is extremely simple. The decorator creates two additional properties, one of which contains your method. The second indicates exactly how to handle this method. If no arguments were given, the decorator records only that the method is local, i.e. the path must be generated. If there are no such properties (there is no decorator on the method), then the method is not considered active and there will be no calls to it.
Actually, starting the project, you will see in the console a list of what you have connected and the path to this method.
Following the link, having done everything correctly, you should see the expected result.
It seems to be all that could write. Tips, wishes?