Skip to content

Commit

Permalink
[#3196] Register core blueprints on the Flask app
Browse files Browse the repository at this point in the history
Automatically go through the `views` folder and register all instances
of Flask's Blueprint. This is done by inspecting all modules on the
folder for the relevant members.

As an example, a new api blueprint has been added, which for now only
handles the root request (`/api`).

See
https://github.com/ckan/ckan/wiki/Migration-from-Pylons-to-Flask#flask-views-blueprints-and-routing
for details on how we will use Blueprints
  • Loading branch information
amercader committed Aug 16, 2016
1 parent 7b0cbd6 commit ee12473
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 2 deletions.
29 changes: 28 additions & 1 deletion ckan/config/middleware/flask_app.py
@@ -1,7 +1,12 @@
# encoding: utf-8

from flask import Flask
import os
import importlib
import inspect

from flask import Flask, Blueprint
from flask.ctx import _AppCtxGlobals

from werkzeug.exceptions import HTTPException

from ckan.common import config, g
Expand Down Expand Up @@ -42,6 +47,9 @@ def hello_world():
def hello_world_post():
return 'Hello World, this was posted to Flask'

# Auto-register all blueprints defined in the `views` folder
_register_core_blueprints(app)

# Add a reference to the actual Flask app so it's easier to access
app._wsgi_app = flask_app

Expand Down Expand Up @@ -88,3 +96,22 @@ def can_handle_request(self, environ):
return (True, self.app_name)
except HTTPException:
return (False, self.app_name)


def _register_core_blueprints(app):
u'''Register all blueprints defined in the `views` folder
'''
views_path = os.path.join(os.path.dirname(__file__),
u'..', u'..', u'views')
module_names = [f.rstrip(u'.py')
for f in os.listdir(views_path)
if f.endswith(u'.py') and not f.startswith(u'_')]
blueprints = []
for name in module_names:
module = importlib.import_module(u'ckan.views.{0}'.format(name))
blueprints.extend([m for m in inspect.getmembers(module)
if isinstance(m[1], Blueprint)])
if blueprints:
for blueprint in blueprints:
app.register_blueprint(blueprint[1])
log.debug(u'Registered core blueprint: {0}'.format(blueprint[0]))
1 change: 0 additions & 1 deletion ckan/config/routing.py
Expand Up @@ -140,7 +140,6 @@ def make_map():
# /api ver 1, 2, 3 or none
with SubMapper(map, controller='api', path_prefix='/api{ver:/1|/2|/3|}',
ver='/1') as m:
m.connect('', action='get_api')
m.connect('/search/{register}', action='search')

# /api ver 1, 2 or none
Expand Down
Empty file added ckan/views/__init__.py
Empty file.
101 changes: 101 additions & 0 deletions ckan/views/api.py
@@ -0,0 +1,101 @@
# encoding: utf-8

import cgi
import logging

from flask import Blueprint, request, make_response

from ckan.common import json


log = logging.getLogger(__name__)

CONTENT_TYPES = {
u'text': u'text/plain;charset=utf-8',
u'html': u'text/html;charset=utf-8',
u'json': u'application/json;charset=utf-8',
}


API_DEFAULT_VERSION = 3
API_MAX_VERSION = 3


# Blueprint definition

api = Blueprint(u'api', __name__, url_prefix=u'/api')


# Private methods

def _finish(status_int, response_data=None,
content_type=u'text', headers=None):
u'''When a controller method has completed, call this method
to prepare the response.
@return response message - return this value from the controller
method
e.g. return _finish(404, 'Package not found')
'''
assert(isinstance(status_int, int))
response_msg = u''
if headers is None:
headers = {}
if response_data is not None:
headers[u'Content-Type'] = CONTENT_TYPES[content_type]
if content_type == u'json':
response_msg = json.dumps(
response_data,
for_json=True) # handle objects with for_json methods
else:
response_msg = response_data
# Support "JSONP" callback.
if (status_int == 200 and u'callback' in request.args and
request.method == u'GET'):
# escape callback to remove '<', '&', '>' chars
callback = cgi.escape(request.args[u'callback'])
response_msg = _wrap_jsonp(callback, response_msg)
return make_response((response_msg, status_int, headers))


def _finish_ok(response_data=None,
content_type=u'json',
resource_location=None):
u'''If a controller method has completed successfully then
calling this method will prepare the response.
@param resource_location - specify this if a new
resource has just been created.
@return response message - return this value from the controller
method
e.g. return _finish_ok(pkg_dict)
'''
status_int = 200
headers = None
if resource_location:
status_int = 201
try:
resource_location = str(resource_location)
except Exception, inst:
msg = \
u"Couldn't convert '%s' header value '%s' to string: %s" % \
(u'Location', resource_location, inst)
raise Exception(msg)
headers = {u'Location': resource_location}

return _finish(status_int, response_data, content_type, headers)


def _wrap_jsonp(callback, response_msg):
return u'{0}({1});'.format(callback, response_msg)


# View functions

def get_api(ver=1):
response_data = {
u'version': ver
}
return _finish_ok(response_data)


# Routing
api.add_url_rule(u'/', view_func=get_api, strict_slashes=False)

0 comments on commit ee12473

Please sign in to comment.