Add context generator: first step towards removing globals from the application
authorChristopher Allan Webber <cwebber@dustycloud.org>
Sat, 29 Nov 2014 19:56:41 +0000 (13:56 -0600)
committerChristopher Allan Webber <cwebber@dustycloud.org>
Wed, 3 Dec 2014 21:40:56 +0000 (15:40 -0600)
This allows you to generate a "context" object that gets threaded
throughout the application... this object should keep track of the same
things that currently we use global variables for.

mediagoblin/app.py

index 9014bf474c9e92f69cc57b3effa6542f39ae005d..8b231163e9ea4f7920c2af9623c0c695b22e4eda 100644 (file)
@@ -46,6 +46,19 @@ from mediagoblin.auth.tools import check_auth_enabled, no_auth_logout
 _log = logging.getLogger(__name__)
 
 
+class Context(object):
+    """
+    MediaGoblin context object.
+
+    If a web request is being used, a Flask Request object is used
+    instead, otherwise (celery tasks, etc), attach things to this
+    object.
+
+    Usually appears as "ctx" in utilities as first argument.
+    """
+    pass
+
+
 class MediaGoblinApp(object):
     """
     WSGI application of MediaGoblin
@@ -149,67 +162,104 @@ class MediaGoblinApp(object):
         self.meddleware = [common.import_component(m)(self)
                            for m in meddleware.ENABLED_MEDDLEWARE]
 
-    def call_backend(self, environ, start_response):
-        request = Request(environ)
-
-        # Compatibility with django, use request.args preferrably
-        request.GET = request.args
+    def gen_context(self, ctx=None):
+        """
+        Attach contextual information to request, or generate a context object
 
-        ## Routing / controller loading stuff
-        map_adapter = self.url_map.bind_to_environ(request.environ)
+        This avoids global variables; various utilities and contextual
+        information (current translation, etc) are attached to this
+        object.
+        """
+        # Set up context
+        # --------------
 
-        # By using fcgi, mediagoblin can run under a base path
-        # like /mediagoblin/. request.path_info contains the
-        # path inside mediagoblin. If the something needs the
-        # full path of the current page, that should include
-        # the basepath.
-        # Note: urlgen and routes are fine!
-        request.full_path = environ["SCRIPT_NAME"] + request.path
-        # python-routes uses SCRIPT_NAME. So let's use that too.
-        # The other option would be:
-        # request.full_path = environ["SCRIPT_URL"]
+        # Is a context provided?
+        if ctx is not None:
+            # Do special things if this is a request
+            if isinstance(ctx, Request):
+                ctx = self._request_only_gen_context(ctx)
 
-        # Fix up environ for urlgen
-        # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
-        if environ.get('HTTPS', '').lower() == 'off':
-            environ.pop('HTTPS')
+        else:
+            ctx = Context()
+        
+        # Attach utilities
+        # ----------------
 
-        ## Attach utilities to the request object
-        # Do we really want to load this via middleware?  Maybe?
-        session_manager = self.session_manager
-        request.session = session_manager.load_session_from_cookie(request)
         # Attach self as request.app
         # Also attach a few utilities from request.app for convenience?
-        request.app = self
+        ctx.app = self
 
-        request.db = self.db
-        request.staticdirect = self.staticdirector
+        ctx.db = self.db
+        ctx.staticdirect = self.staticdirector
+
+        return ctx
+
+    def _request_only_gen_context(self, request):
+        """
+        Requests get some extra stuff attached to them that's not relevant
+        otherwise.
+        """
+        # Do we really want to load this via middleware?  Maybe?
+        request.session = self.session_manager.load_session_from_cookie(request)
 
         request.locale = translate.get_locale_from_request(request)
+
+        # This should be moved over for certain, but how to deal with
+        # request.locale?
         request.template_env = template.get_jinja_env(
             self.template_loader, request.locale)
 
+        mg_request.setup_user_in_request(request)
+
+        ## Routing / controller loading stuff
+        request.map_adapter = self.url_map.bind_to_environ(request.environ)
+
         def build_proxy(endpoint, **kw):
             try:
                 qualified = kw.pop('qualified')
             except KeyError:
                 qualified = False
 
-            return map_adapter.build(
+            return request.map_adapter.build(
                     endpoint,
                     values=dict(**kw),
                     force_external=qualified)
 
         request.urlgen = build_proxy
 
+        return request
+
+    def call_backend(self, environ, start_response):
+        request = Request(environ)
+
+        # Compatibility with django, use request.args preferrably
+        request.GET = request.args
+
+        # By using fcgi, mediagoblin can run under a base path
+        # like /mediagoblin/. request.path_info contains the
+        # path inside mediagoblin. If the something needs the
+        # full path of the current page, that should include
+        # the basepath.
+        # Note: urlgen and routes are fine!
+        request.full_path = environ["SCRIPT_NAME"] + request.path
+        # python-routes uses SCRIPT_NAME. So let's use that too.
+        # The other option would be:
+        # request.full_path = environ["SCRIPT_URL"]
+
+        # Fix up environ for urlgen
+        # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
+        if environ.get('HTTPS', '').lower() == 'off':
+            environ.pop('HTTPS')
+
+        ## Attach utilities to the request object
+        request = self.gen_context(request)
+
         # Log user out if authentication_disabled
         no_auth_logout(request)
 
-        mg_request.setup_user_in_request(request)
-
         request.controller_name = None
         try:
-            found_rule, url_values = map_adapter.match(return_rule=True)
+            found_rule, url_values = request.map_adapter.match(return_rule=True)
             request.matchdict = url_values
         except RequestRedirect as response:
             # Deal with 301 responses eg due to missing final slash
@@ -225,6 +275,7 @@ class MediaGoblinApp(object):
         # used for lazy context modification
         request.controller_name = found_rule.endpoint
 
+        ## TODO: get rid of meddleware, turn it into hooks only
         # pass the request through our meddleware classes
         try:
             for m in self.meddleware:
@@ -255,8 +306,9 @@ class MediaGoblinApp(object):
             response = render_http_exception(
                 request, e, e.get_description(environ))
 
-        session_manager.save_session_to_cookie(request.session,
-                                               request, response)
+        self.session_manager.save_session_to_cookie(
+            request.session,
+            request, response)
 
         return response(environ, start_response)