Skip to content

Commit

Permalink
Merge pull request #160 from ael-code/api
Browse files Browse the repository at this point in the history
Api introduction
  • Loading branch information
leonaard committed Jun 14, 2015
2 parents 32d35b8 + d1fcab1 commit 7059036
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 14 deletions.
8 changes: 4 additions & 4 deletions libreantdb/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ def setup_db(self, wait_for_ready=True):
def __len__(self):
return self.es.count(index=self.index_name)['count']

def _search(self, body, size=30):
return self.es.search(index=self.index_name, body=body, size=size)
def _search(self, body, **kargs):
return self.es.search(index=self.index_name, body=body, **kargs)

def _get_search_field(self, field, value):
return {'query':
Expand Down Expand Up @@ -179,9 +179,9 @@ def get_books_by_actor(self, authorname):
def get_book_by_id(self, id):
return self.es.get(index=self.index_name, id=id)

def get_books_querystring(self, query):
def get_books_querystring(self, query, **kargs):
q = {'query': query, 'fields': ['_text_*']}
return self._search({'query': dict(query_string=q)})
return self._search({'query': dict(query_string=q)}, **kargs)

def user_search(self, query):
'''
Expand Down
Empty file added webant/api/__init__.py
Empty file.
98 changes: 98 additions & 0 deletions webant/api/blueprint_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from flask import Blueprint, current_app, jsonify, request, url_for

from archivant.archivant import Archivant
from archivant.exceptions import NotFoundException
from webant.utils import send_attachment_file


class ApiError(Exception):
def __init__(self, message, http_code, err_code=None, details=None):
Exception.__init__(self, http_code, err_code, message, details)
self.http_code = http_code
self.err_code = err_code
self.message = message
self.details = details

def __str__(self):
return "http_code: {}, err_code: {}, message: '{}', details: '{}'".format(self.http_code, self.err_code, self.message, self.details)


api = Blueprint('api', __name__)

@api.errorhandler(ApiError)
def apiErrorHandler(apiErr):
error = {}
error['code'] = apiErr.err_code if (apiErr.err_code is not None) else apiErr.http_code
error['message'] = apiErr.message
error['details'] = apiErr.details if (apiErr.details is not None) else ""

response = jsonify({'error': error})
response.status_code = apiErr.http_code
return response

# workaround for "https://github.com/mitsuhiko/flask/issues/1498"
@api.route("/<path:invalid_path>", methods=['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'])
def apiNotFound(invalid_path):
raise ApiError("invalid URI", 404)

@api.errorhandler(Exception)
def exceptionHandler(e):
current_app.logger.exception(e)
return apiErrorHandler(ApiError("internal server error", 500))


@api.route('/volumes/')
def get_volumes():
q = request.args.get('q', "*:*")
try:
from_ = int(request.args.get('from', 0))
except ValueError, e:
raise ApiError("Bad Request", 400, details="could not covert 'from' parameter to number")
try:
size = int(request.args.get('size', 10))
except ValueError, e:
raise ApiError("Bad Request", 400, details="could not covert 'size' parameter to number")
if size > current_app.config.get('MAX_RESULTS_PER_PAGE', 50):
raise ApiError("Request Entity Too Large", 413, details="'size' parameter is too high")

q_res = current_app.archivant._db.get_books_querystring(query=q, from_=from_, size=size)
volumes = map(Archivant.normalize_volume, q_res['hits']['hits'])
next_args = "?q={}&from={}&size={}".format(q, from_ + size, size)
prev_args = "?q={}&from={}&size={}".format(q, from_ - size if ((from_ - size) > -1) else 0, size)
base_url = url_for('.get_volumes', _external=True)
res = {'link_prev': base_url + prev_args,
'link_next': base_url + next_args,
'total': q_res['hits']['total'],
'data': volumes}
return jsonify(res)

@api.route('/volumes/<volumeID>', methods=['GET'])
def get_volume(volumeID):
try:
volume = current_app.archivant.get_volume(volumeID)
except NotFoundException, e:
raise ApiError("volume not found", 404, details=str(e))
return jsonify({'data': volume})

@api.route('/volumes/<volumeID>/attachments/', methods=['GET'])
def get_attachments(volumeID):
try:
atts = current_app.archivant.get_volume(volumeID)['attachments']
except NotFoundException, e:
raise ApiError("volume not found", 404, details=str(e))
return jsonify({'data': atts})

@api.route('/volumes/<volumeID>/attachments/<attachmentID>', methods=['GET'])
def get_attachment(volumeID, attachmentID):
try:
att = current_app.archivant.get_attachment(volumeID, attachmentID)
except NotFoundException, e:
raise ApiError("attachment not found", 404, details=str(e))
return jsonify({'data': att})

@api.route('/volumes/<volumeID>/attachments/<attachmentID>/file', methods=['GET'])
def get_file(volumeID, attachmentID):
try:
return send_attachment_file(current_app.archivant, volumeID, attachmentID)
except NotFoundException, e:
raise ApiError("file not found", 404, details=str(e))
11 changes: 10 additions & 1 deletion webant/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import functools

from flask import send_file

def memoize(obj):
'''decorator to memoize things'''
Expand Down Expand Up @@ -36,3 +36,12 @@ def requestedFormat(request,acceptedFormat):
return fieldFormat
else:
return request.accept_mimetypes.best_match(acceptedFormat)

def send_attachment_file(archivant, volumeID, attachmentID):
f = archivant.get_file(volumeID, attachmentID)
attachment = archivant.get_attachment(volumeID, attachmentID)
archivant._db.increment_download_count(volumeID, attachmentID)
return send_file(f,
mimetype=attachment['metadata']['mime'],
attachment_filename=attachment['metadata']['name'],
as_attachment=True)
14 changes: 5 additions & 9 deletions webant/webant.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging
import os

from flask import Flask, render_template, request, abort, Response, redirect, url_for, send_file, make_response
from flask import Flask, render_template, request, abort, Response, redirect, url_for, make_response
from werkzeug import secure_filename
from utils import requestedFormat
from utils import requestedFormat, send_attachment_file
from flask_bootstrap import Bootstrap
from elasticsearch import exceptions as es_exceptions
from flask.ext.babel import Babel, gettext
Expand All @@ -17,6 +17,7 @@
from archivant import Archivant
from archivant.exceptions import NotFoundException
from agherant import agherant
from api.blueprint_api import api
from webserver_utils import gevent_run
import config_utils

Expand Down Expand Up @@ -53,6 +54,7 @@ def __init__(self, import_name, conf={}):
super(LibreantViewApp, self).__init__(import_name, defaults)
if self.config['AGHERANT_DESCRIPTIONS']:
self.register_blueprint(agherant, url_prefix='/agherant')
self.register_blueprint(api, url_prefix='/api/v1')
Bootstrap(self)
self.babel = Babel(self)

Expand Down Expand Up @@ -181,13 +183,7 @@ def view_volume(volumeID):
@app.route('/download/<volumeID>/<attachmentID>')
def download_attachment(volumeID, attachmentID):
try:
attachment = app.archivant.get_attachment(volumeID, attachmentID)
file = app.archivant.get_file(volumeID, attachmentID)
app.archivant._db.increment_download_count(volumeID, attachmentID)
return send_file(file,
mimetype=attachment['metadata']['mime'],
attachment_filename=attachment['metadata']['name'],
as_attachment=True)
return send_attachment_file(app.archivant, volumeID, attachmentID)
except NotFoundException:
# no attachment found with the given id
return renderErrorPage(message='no attachment found with id "{}" on volume "{}"'.format(attachmentID, volumeID), httpCode=404)
Expand Down

0 comments on commit 7059036

Please sign in to comment.