Skip to content

Commit

Permalink
Merge branch 'poc-flask-views' into poc-flask-views.common-url_for
Browse files Browse the repository at this point in the history
Conflicts:
	ckan/config/middleware.py
	ckan/views/api.py
  • Loading branch information
amercader committed Jun 6, 2016
2 parents 36f5a32 + 9f07da4 commit e64a6e0
Show file tree
Hide file tree
Showing 20 changed files with 1,128 additions and 69 deletions.
25 changes: 24 additions & 1 deletion ckan/common.py
Expand Up @@ -14,7 +14,7 @@
from flask.ext.babel import gettext as flask_gettext
from pylons.i18n import _ as pylons_gettext, ungettext

from pylons import g, session, response
from pylons import g, response
import simplejson as json

try:
Expand Down Expand Up @@ -96,3 +96,26 @@ def __delattr__(self, name):


c = PylonsStyleContext()


class Session():

def __getattr__(self, name):
if is_flask():
return getattr(flask.session, name, None)
else:
return getattr(pylons.session, name, None)

def __setattr__(self, name, value):
if is_flask():
return setattr(flask.session, name, value)
else:
return setattr(pylons.session, name, value)

def __delattr__(self, name):
if is_flask():
return delattr(flask.session, name, None)
else:
return delattr(pylons.session, name, None)

session = Session()
168 changes: 168 additions & 0 deletions ckan/config/middleware/__init__.py
@@ -0,0 +1,168 @@
# encoding: utf-8

"""WSGI app initialization"""

import urlparse

import webob

from pylons import config
from routes import request_config as routes_request_config

from werkzeug.test import create_environ, run_wsgi_app
from wsgi_party import WSGIParty

from ckan.config.middleware.flask_app import make_flask_stack
from ckan.config.middleware.pylons_app import make_pylons_stack
from ckan.config.middleware.common_middleware import I18nMiddleware

import logging
log = logging.getLogger(__name__)

# This monkey-patches the webob request object because of the way it messes
# with the WSGI environ.

# Start of webob.requests.BaseRequest monkey patch
original_charset__set = webob.request.BaseRequest._charset__set


def custom_charset__set(self, charset):
original_charset__set(self, charset)
if self.environ.get('CONTENT_TYPE', '').startswith(';'):
self.environ['CONTENT_TYPE'] = ''

webob.request.BaseRequest._charset__set = custom_charset__set

webob.request.BaseRequest.charset = property(
webob.request.BaseRequest._charset__get,
custom_charset__set,
webob.request.BaseRequest._charset__del,
webob.request.BaseRequest._charset__get.__doc__)

# End of webob.requests.BaseRequest monkey patch


def make_app(conf, full_stack=True, static_files=True, **app_conf):
'''
Initialise both the pylons and flask apps, and wrap them in dispatcher
middleware.
'''

pylons_app = make_pylons_stack(conf, full_stack, static_files, **app_conf)
flask_app = make_flask_stack(conf, **app_conf)

app = AskAppDispatcherMiddleware({'pylons_app': pylons_app,
'flask_app': flask_app})

return app


class AskAppDispatcherMiddleware(WSGIParty):

'''
Establish a 'partyline' to each provided app. Select which app to call
by asking each if they can handle the requested path at PATH_INFO.
Used to help transition from Pylons to Flask, and should be removed once
Pylons has been deprecated and all app requests are handled by Flask.
Each app should handle a call to 'can_handle_request(environ)', responding
with a tuple:
(<bool>, <app>, [<origin>])
where:
`bool` is True if the app can handle the payload url,
`app` is the wsgi app returning the answer
`origin` is an optional string to determine where in the app the url
will be handled, e.g. 'core' or 'extension'.
Order of precedence if more than one app can handle a url:
Flask Extension > Pylons Extension > Flask Core > Pylons Core
'''

def __init__(self, apps=None, invites=(), ignore_missing_services=False):
# Dict of apps managed by this middleware {<app_name>: <app_obj>, ...}
self.apps = apps or {}

# A dict of service name => handler mappings.
self.handlers = {}

# If True, suppress :class:`NoSuchServiceName` errors. Default: False.
self.ignore_missing_services = ignore_missing_services

self.send_invitations(apps)

self.i18n_middleware = I18nMiddleware()

def send_invitations(self, apps):
'''Call each app at the invite route to establish a partyline. Called
on init.'''
PATH = '/__invite__/'
# We need to send an environ tailored to `ckan.site_url`, otherwise
# Flask will return a 404 for the invite path (as we are using
# SERVER_NAME). Existance of `ckan.site_url` in config has already
# been checked.
parts = urlparse.urlparse(config.get('ckan.site_url'))
environ_overrides = {
'HTTP_HOST': parts.netloc,
}
for app_name, app in apps.items():
environ = create_environ(PATH, environ_overrides=environ_overrides)
environ[self.partyline_key] = self.operator_class(self)
# A reference to the handling app. Used to id the app when
# responding to a handling request.
environ['partyline_handling_app'] = app_name
run_wsgi_app(app, environ)

def __call__(self, environ, start_response):
'''Determine which app to call by asking each app if it can handle the
url and method defined on the eviron'''

# Handle the i18n first, otherwise localized URLs (eg `/jp/about`)
# won't get recognized by the app route mappers
self.i18n_middleware(environ, start_response)

app_name = 'pylons_app' # currently defaulting to pylons app
answers = self.ask_around('can_handle_request', environ)
log.debug('Route support answers for {0} {1}: {2}'.format(
environ.get('REQUEST_METHOD'), environ.get('PATH_INFO'),
answers))
available_handlers = []
for answer in answers:
if len(answer) == 2:
can_handle, asked_app = answer
origin = 'core'
else:
can_handle, asked_app, origin = answer
if can_handle:
available_handlers.append('{0}_{1}'.format(asked_app, origin))

# Enforce order of precedence:
# Flask Extension > Pylons Extension > Flask Core > Pylons Core
if available_handlers:
if 'flask_app_extension' in available_handlers:
app_name = 'flask_app'
elif 'pylons_app_extension' in available_handlers:
app_name = 'pylons_app'
elif 'flask_app_core' in available_handlers:
app_name = 'flask_app'

log.debug('Serving request via {0} app'.format(app_name))
environ['ckan.app'] = app_name
if app_name == 'flask_app':
# This request will be served by Flask, but we still need the
# Pylons URL builder (Routes) to work
parts = urlparse.urlparse(config.get('ckan.site_url',
'http://0.0.0.0:5000'))
request_config = routes_request_config()
request_config.host = str(parts.netloc + parts.path)
request_config.protocol = str(parts.scheme)
request_config.mapper = config['routes.map']
return self.apps[app_name](environ, start_response)
else:
# Although this request will be served by Pylons we still
# need a request context (wich will create an app context) in order
# for the Flask URL builder to work
flask_app = self.apps['flask_app']._flask_app

with flask_app.test_request_context(environ_overrides=environ):
return self.apps[app_name](environ, start_response)

0 comments on commit e64a6e0

Please sign in to comment.