📜 ⬆️ ⬇️

Django do-it-yourself part 3: Routes, debugging, middleware, bottle, beaker

Part 1.
Part 2.

Introduction


This article will focus on the intricacies of implementing a wsgi application. We will not consider writing an application server from scratch, since there are a lot of microframes that already do this, not to mention large ones. For these purposes, was selected bottle. Mostly for its minimalism. We will also talk about routs, statics, and sessions that will be beaker in charge.

Debugging


One of the main problems with microframes is that when errors occur in the application, most of the errors that wsgi gives us should be caught in the logs. And we would like to see all this on the page in the browser, or in its console in case some post request returned an error.

To do this, we simply integrate into the processing chain and intercept all exceptions that the application itself did not intercept, and display it as a beautifully designed page.
try: #  bottle        ,    . @bottle.hook('before_request') def pre_init(): #       , ini_environment() #       #  options_session #   bottle app = bottle.app() app.catchall = False #       . debugger = DebuggerM(app) #     . session = SessionMiddleware(debugger, options_session) #   application    wsgi' application = session except Exception as e: #            #     . exc = sys.exc_info() def error_apps(environ, start_response): start_response('500 INTERNAL SERVER ERROR', [('Content-Type', 'text/html; charset=utf-8')]) return view_error(exc, environ, e) application = error_apps 

')
The class that directly serves our middleware.

 class DebuggerM(object): """       """ __app = None def __init__ (self, app): #      bottle # app-    ,  application     middleware self.__app = app def __call__(self, environ, start_response): try: #      (   ) app_iter = self.__app(environ, start_response) for item in app_iter: #      #          yield item except Exception as e: #         . start_response('500 INTERNAL SERVER ERROR', [('Content-Type', 'text/html; charset=utf-8')]) yield view_error(sys.exc_info(), environ, e) 


The function itself that draws our error:
 def view_error(exc, environ, e): import cgi, traceback ee = dict(environ) text= '' text='<h1>ERROR "%s"</h1>' % str(e) text+='<style>pre{border:red solid 1px; max-height:240px; overflow:auto;}</style>' text+='<h2>Stack trace</h2><pre>%s</pre>' % ''.join(reversed(traceback.format_exception(*exc))) text+='<h2>Env</h2><pre">%s </pre>' % ' '.join(['%s=%s' %(k, ee[k])for k in ee]) text+='</pre>' return text 


Work with routs


By default, we already have routes in the bootle as a decorator that can be hooked to any function:
 @route('/hello/:name') def index(name='World'): return '<b>Hello %s</b>' % name 


but it is convenient for relatively small projects. And we would like to expand this moment so that we could create a file of this type in any component.
 from app1 import * routes = { 'function': ('/hello/', hello, 'GET'), 'function2':('/hello/<_id>', hello_post, 'POST') } 

Accordingly, after that, it is possible in any place, preferably in this component, to call the hello function. To do this, we write in the file which contains the description of the core core functionality.
 all_routes = {} def union_routes(dir): routes = {} #               . if __builtin__.__import__('routes', globals=globals()): #    module = sys.modules['routes'] #     routes routes.update(module.routes) #     for name in os.listdir(dir): path = os.path.join(dir, name) #       routes.py if os.path.isdir(path) and os.path.isfile(path+'/routes.py'): #           name = 'app.'+path[len(dir)+1:]+'.routes' if __builtin__.__import__(name, globals=globals()): module = sys.modules[name] routes.update(module.routes) return routes def setup_routes(routes): #           bottle        . all_routes.clear() all_routes.update(routes) for name in routes: path, func, method = routes[name] route(path, method=method, name=name)(func) 


Now we are replacing the comment that we left in the first part of the debugging devoted to the call initialization of the routes, so that they are immediately connected to us.
 routes = {} #      routes.update(union_routes(lib_path)) #      routes.update(union_routes(app_path)) setup_routes(routes) 

All now we have full-fledged routes, we can optionally wrap any function in the decorator, or set out a list of routes with their assigned functions in the file route.py and, if desired, pass the necessary parameters to them through lambda.
 from app1 import * routes = { 'function2': ('/hello/<_id>', hello_post, 'POST'), 'function3': ('/base/1', lambda: test(True, u''), 'GET') } 

Also now using the key of our routes dictionary in the given case of function2, we can form links by the name of the route without knowing exactly which parameters will be passed to the link itself, for example, if you take '/ hello / <_ id>' then <_id> will be set dynamically.
 def make_link(name, params, full=False): """     <>     """ #     templ_link = all_routes[name][0][1:] #      <> r = re.compile(r'<([^:>]+)(:[^>]+)?>') #       ,       . while True: rs = r.search(templ_link) if not rs: break sub = rs.group(0) name = rs.group(1) templ_link = re.sub(sub, params[name], teml_link) link = os.path.sep+ templ_link #        ,     . if full: link = 'http://'+get_name_host()+link return link 

Now we write anywhere in the template or just in the code.
 link = make_link('test', {'id':id, 'doc_id':doc_id}, True) 

It’s often easier to admit to writing just a link, but sometimes you can’t do without this function.


Work with sessions


Working with sessions is to use beaker. This wonderful module can work with sessions, save them in a selected folder or in the database. You can configure it to save them for example in postgresql or in memcached. The first thing we do is import:
 from beaker.middleware import SessionMiddleware 

Next, we replace the comment with the initialization immediately after the initialization of the routes.
  options_session = { 'session.cookie_expires': 30000, 'session.timeout': 30000, 'session.type': 'file', #     'session.data_dir': './s_data' #      } 

And in the pre_init () function, we add a line for dynamic, if so you can tell the definition for which domain we store sessions.
 session.options['session.cookie_domain'] = get_name_host() 

get_name_host () - is engaged in obtaining the name of our site.
After that, all we need is a simple function with which you can use the sessions.
 def session(): #    s = request.environ.get('beaker.session') return s 

Now anywhere:
 s = session() s['test'] = '123' s.save() 


Statics


We will also deal with bottle static, only we, in turn, will expand its capabilities for our needs.

 #  bottle          ,           . @route('/static/<component>/<fname:re:.*>') def st_file(component, fname): #              ,     bottle path = os.path.join( settings.lib_path, component, 'static') + os.path.sep if not os.path.exists(path + fname): path = os.path.join( settings.lib_path, 'app', component,'static')+ os.path.sep if not os.path.exists( path + fname): path = os.path.join( os.getcwd(), 'app', component, 'static')+ os.path.sep if not os.path.exists(path + fname) and component == 'static': path = os.path.join( os.getcwd(), 'static')+ os.path.sep return static_file(fname, root=path) 


Now it remains to learn how to connect statics if desired from the module scripts if such a module exists, and not all without parsing in the main template.
 def add_resourse(name, append=True): env = get_environment(); t = '' if name.endswith('.css'): t = 'css' if name.endswith('.js'): t = 'js' if not t: return if not name in env['res_list'][t]: if append: env['res_list'][t].append(name) else: env['res_list'][t].prepend(name) 


We call this function in any module and pass the right path as the first argument to the static file of the type '/ static / <component> / <fname: re:. *>':
 add_resourse(/static/base/base.js, True) 


And in the main template, just call:
 {% for res in env.res_list['js'] %} <script type="text/javascript" src="{{res}}"></script> {% endfor %} {% for res in env.res_list['css'] %} <link rel="stylesheet" type="text/css" href="{{res}}" /> {% endfor %} 


Hello world


Now a simple Hello world would look something like this.
The route.py route file will be placed in / app / app_one of the project:
 from app.app_one.view import * routes = { 'hello': ('/', hello, 'GET') } 

In the same place, we place the file view.py next to it, although the name here is no longer fundamentally except in terms of logic:
 def hello(): text = 'It works' return templ('hello', text=text) 

And in the same directory in the folder / templ put a template called hello.tpl.
 {{text}} 

All go to the root of the site and see the greeting.

Summary


Actually the main frame is ready. Some frameworks in general, to one degree or another, implement what we have considered in this series of lessons. But it seems to me that it was interesting to look at one of the options as it can be implemented. The next part will be devoted to the creation of the admin as well as the presentation of data.
For now. All success.

Used and useful materials


Bottle
Beaker and Caching
Beaker
Introduction to the web for python
Python Web Server Gateway Interface

Source: https://habr.com/ru/post/148261/


All Articles