server/image-hash: improve exception handling

This commit is contained in:
rr- 2017-02-02 18:21:21 +01:00
parent f42fbbdc56
commit aa1faa3ccb
5 changed files with 74 additions and 30 deletions

View file

@ -1,5 +1,5 @@
import datetime import datetime
from szurubooru import search, db from szurubooru import search, db, errors
from szurubooru.rest import routes from szurubooru.rest import routes
from szurubooru.func import ( from szurubooru.func import (
auth, tags, posts, snapshots, favorites, scores, util, versions) auth, tags, posts, snapshots, favorites, scores, util, versions)
@ -211,6 +211,12 @@ def get_posts_around(ctx, params):
def get_posts_by_image(ctx, _params=None): def get_posts_by_image(ctx, _params=None):
auth.verify_privilege(ctx.user, 'posts:reverse_search') auth.verify_privilege(ctx.user, 'posts:reverse_search')
content = ctx.get_file('content', required=True) content = ctx.get_file('content', required=True)
try:
lookalikes = posts.search_by_image(content)
except (errors.ThirdPartyError, errors.ProcessingError):
lookalikes = []
return { return {
'exactPost': 'exactPost':
_serialize_post(ctx, posts.search_by_image_exact(content)), _serialize_post(ctx, posts.search_by_image_exact(content)),
@ -220,6 +226,6 @@ def get_posts_by_image(ctx, _params=None):
'distance': lookalike.distance, 'distance': lookalike.distance,
'post': _serialize_post(ctx, lookalike.post), 'post': _serialize_post(ctx, lookalike.post),
} }
for lookalike in posts.search_by_image(content) for lookalike in lookalikes
], ],
} }

View file

@ -46,3 +46,7 @@ class MissingRequiredParameterError(ValidationError):
class InvalidParameterError(ValidationError): class InvalidParameterError(ValidationError):
pass pass
class ThirdPartyError(BaseError):
pass

View file

@ -44,6 +44,13 @@ def _on_processing_error(ex):
raise _map_error(ex, rest.errors.HttpBadRequest, 'Processing error') raise _map_error(ex, rest.errors.HttpBadRequest, 'Processing error')
def _on_third_party_error(ex):
raise _map_error(
ex,
rest.errors.HttpInternalServerError,
'Server configuration error')
def _on_stale_data_error(_ex): def _on_stale_data_error(_ex):
raise rest.errors.HttpConflict( raise rest.errors.HttpConflict(
name='IntegrityError', name='IntegrityError',
@ -110,6 +117,7 @@ def create_app():
rest.errors.handle(errors.IntegrityError, _on_integrity_error) rest.errors.handle(errors.IntegrityError, _on_integrity_error)
rest.errors.handle(errors.NotFoundError, _on_not_found_error) rest.errors.handle(errors.NotFoundError, _on_not_found_error)
rest.errors.handle(errors.ProcessingError, _on_processing_error) rest.errors.handle(errors.ProcessingError, _on_processing_error)
rest.errors.handle(errors.ThirdPartyError, _on_third_party_error)
rest.errors.handle(sqlalchemy.orm.exc.StaleDataError, _on_stale_data_error) rest.errors.handle(sqlalchemy.orm.exc.StaleDataError, _on_stale_data_error)
return rest.application return rest.application

View file

@ -1,10 +1,13 @@
import logging
import elasticsearch import elasticsearch
import elasticsearch_dsl import elasticsearch_dsl
import xml.etree
from image_match.elasticsearch_driver import SignatureES from image_match.elasticsearch_driver import SignatureES
from szurubooru import config from szurubooru import config, errors
# pylint: disable=invalid-name # pylint: disable=invalid-name
logger = logging.getLogger(__name__)
es = elasticsearch.Elasticsearch([{ es = elasticsearch.Elasticsearch([{
'host': config.config['elasticsearch']['host'], 'host': config.config['elasticsearch']['host'],
'port': config.config['elasticsearch']['port'], 'port': config.config['elasticsearch']['port'],
@ -12,6 +15,28 @@ es = elasticsearch.Elasticsearch([{
session = SignatureES(es, index='szurubooru') session = SignatureES(es, index='szurubooru')
def _safe_blanket(default_param_factory):
def wrapper_outer(target_function):
def wrapper_inner(*args, **kwargs):
try:
return target_function(*args, **kwargs)
except elasticsearch.exceptions.NotFoundError:
# index not yet created, will be created dynamically by
# add_image()
return default_param_factory()
except elasticsearch.exceptions.ElasticsearchException as ex:
logger.warning('Problem with elastic search: %s' % ex)
raise errors.ThirdPartyError(
'Error connecting to elastic search.')
except xml.etree.ElementTree.ParseError as ex:
# image-match issue #60
raise errors.ProcessingError('Not an image.')
except Exception as ex:
raise errors.ThirdPartyError('Unknown error (%s).' % ex)
return wrapper_inner
return wrapper_outer
class Lookalike: class Lookalike:
def __init__(self, score, distance, path): def __init__(self, score, distance, path):
self.score = score self.score = score
@ -19,39 +44,37 @@ class Lookalike:
self.path = path self.path = path
@_safe_blanket(lambda: None)
def add_image(path, image_content): def add_image(path, image_content):
if not path or not image_content: if not path or not image_content:
return return
session.add_image(path=path, img=image_content, bytestream=True) session.add_image(path=path, img=image_content, bytestream=True)
@_safe_blanket(lambda: None)
def delete_image(path): def delete_image(path):
if not path: if not path:
return return
try: es.delete_by_query(
es.delete_by_query( index=session.index,
index=session.index, doc_type=session.doc_type,
doc_type=session.doc_type, body={'query': {'term': {'path': path}}})
body={'query': {'term': {'path': path}}})
except elasticsearch.exceptions.NotFoundError:
pass
@_safe_blanket(lambda: [])
def search_by_image(image_content): def search_by_image(image_content):
try: ret = []
for result in session.search_image( for result in session.search_image(
path=image_content, # sic path=image_content, # sic
bytestream=True): bytestream=True):
yield Lookalike( ret.append(Lookalike(
score=result['score'], score=result['score'],
distance=result['dist'], distance=result['dist'],
path=result['path']) path=result['path']))
except elasticsearch.exceptions.ElasticsearchException: return ret
raise
except Exception:
yield from []
@_safe_blanket(lambda: None)
def purge(): def purge():
es.delete_by_query( es.delete_by_query(
index=session.index, index=session.index,
@ -59,12 +82,10 @@ def purge():
body={'query': {'match_all': {}}}) body={'query': {'match_all': {}}})
@_safe_blanket(lambda: set())
def get_all_paths(): def get_all_paths():
try: search = (
search = ( elasticsearch_dsl.Search(
elasticsearch_dsl.Search( using=es, index=session.index, doc_type=session.doc_type)
using=es, index=session.index, doc_type=session.doc_type) .source(['path']))
.source(['path'])) return set(h.path for h in search.scan())
return set(h.path for h in search.scan())
except elasticsearch.exceptions.NotFoundError:
return set()

View file

@ -47,5 +47,10 @@ class HttpMethodNotAllowed(BaseHttpError):
reason = 'Method Not Allowed' reason = 'Method Not Allowed'
class HttpInternalServerError(BaseHttpError):
code = 500
reason = 'Internal Server Error'
def handle(exception_type, handler): def handle(exception_type, handler):
error_handlers[exception_type] = handler error_handlers[exception_type] = handler