Feat/1462/text search http api (#1471)

* Add assets text search endpoint

* Filter out assets from invalid transactions.

- Added the limit argument to limit the returned results
- Created and updated tests

* Added documentation for the assets endpoint.

- Added some docstrings

* Removed unnecessary fixtures
This commit is contained in:
Tim Daubenschütz 2017-05-30 11:21:26 +02:00 committed by Rodolphe Marques
parent ac2d65d23d
commit 8b7f86b63d
6 changed files with 260 additions and 0 deletions

View File

@ -381,6 +381,9 @@ def text_search(conn, search, *, language='english', case_sensitive=False,
Returns:
:obj:`list` of :obj:`dict`: a list of assets
Raises:
OperationError: If the backend does not support text search
"""
raise OperationError('This query is only supported when running '

View File

@ -621,6 +621,16 @@ class Bigchain(object):
return backend.query.write_assets(self.connection, assets)
def text_search(self, search, *, limit=0):
"""
Return an iterator of assets that match the text search
Args:
search (str): Text search string to query the text index
limit (int, optional): Limit the number of returned documents.
Returns:
iter: An iterator of assets that match the text search.
"""
assets = backend.query.text_search(self.connection, search, limit=limit)
# TODO: This is not efficient. There may be a more efficient way to

View File

@ -1,6 +1,7 @@
""" API routes definition """
from flask_restful import Api
from bigchaindb.web.views import (
assets,
blocks,
info,
statuses,
@ -25,6 +26,7 @@ def r(*args, **kwargs):
ROUTES_API_V1 = [
r('/', info.ApiV1Index),
r('assets/', assets.AssetListApi),
r('blocks/<string:block_id>', blocks.BlockApi),
r('blocks/', blocks.BlockListApi),
r('statuses/', statuses.StatusApi),

View File

@ -0,0 +1,50 @@
"""This module provides the blueprint for some basic API endpoints.
For more information please refer to the documentation: http://bigchaindb.com/http-api
"""
import logging
from flask_restful import reqparse, Resource
from flask import current_app
from bigchaindb.backend.exceptions import OperationError
from bigchaindb.web.views.base import make_error
logger = logging.getLogger(__name__)
class AssetListApi(Resource):
def get(self):
"""API endpoint to perform a text search on the assets.
Args:
search (str): Text search string to query the text index
limit (int, optional): Limit the number of returned documents.
Return:
A list of assets that match the query.
"""
parser = reqparse.RequestParser()
parser.add_argument('search', type=str, required=True)
parser.add_argument('limit', type=int)
args = parser.parse_args()
if not args['search']:
return make_error(400, 'text_search cannot be empty')
if not args['limit']:
# if the limit is not specified do not pass None to `text_search`
del args['limit']
pool = current_app.config['bigchain_pool']
with pool() as bigchain:
assets = bigchain.text_search(**args)
try:
# This only works with MongoDB as the backend
return list(assets)
except OperationError as e:
return make_error(
400,
'({}): {}'.format(type(e).__name__, e)
)

View File

@ -270,6 +270,118 @@ Statuses
:statuscode 404: A block with that ID was not found.
Assets
--------------------------------
.. http:get:: /api/v1/assets
Return all the assets that match a given text search.
:query string text search: Text search string to query.
:query int limit: (Optional) Limit the number of returned assets. Defaults
to ``0`` meaning return all matching assets.
.. note::
Currently this enpoint is only supported if the server is running
MongoDB as the backend.
.. http:get:: /api/v1/assets?search={text_search}
Return all assets that match a given text search. The asset is returned
with the ``id`` of the transaction that created the asset.
If no assets match the text search it returns an empty list.
If the text string is empty or the server does not support text search,
a ``400`` is returned.
The results are sorted by text score.
For more information about the behavior of text search see `MongoDB text
search behavior <https://docs.mongodb.com/manual/reference/operator/query/text/#behavior>`_
**Example request**:
.. sourcecode:: http
GET /api/v1/assets/?search=bigchaindb HTTP/1.1
Host: example.com
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-type: application/json
[
{
"data": {"msg": "Hello BigchainDB 1!"},
"id": "51ce82a14ca274d43e4992bbce41f6fdeb755f846e48e710a3bbb3b0cf8e4204"
},
{
"data": {"msg": "Hello BigchainDB 2!"},
"id": "b4e9005fa494d20e503d916fa87b74fe61c079afccd6e084260674159795ee31"
},
{
"data": {"msg": "Hello BigchainDB 3!"},
"id": "fa6bcb6a8fdea3dc2a860fcdc0e0c63c9cf5b25da8b02a4db4fb6a2d36d27791"
}
]
:resheader Content-Type: ``application/json``
:statuscode 200: The query was executed successfully.
:statuscode 400: The query was not executed successfully. Returned if the
text string is empty or the server does not support
text search.
.. http:get:: /api/v1/assets?search={text_search}&limit={n_documents}
Return at most ``n`` assets that match a given text search.
If no assets match the text search it returns an empty list.
If the text string is empty or the server does not support text search,
a ``400`` is returned.
The results are sorted by text score.
For more information about the behavior of text search see `MongoDB text
search behavior <https://docs.mongodb.com/manual/reference/operator/query/text/#behavior>`_
**Example request**:
.. sourcecode:: http
GET /api/v1/assets/?search=bigchaindb&limit=2 HTTP/1.1
Host: example.com
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-type: application/json
[
{
"data": {"msg": "Hello BigchainDB 1!"},
"id": "51ce82a14ca274d43e4992bbce41f6fdeb755f846e48e710a3bbb3b0cf8e4204"
},
{
"data": {"msg": "Hello BigchainDB 2!"},
"id": "b4e9005fa494d20e503d916fa87b74fe61c079afccd6e084260674159795ee31"
},
]
:resheader Content-Type: ``application/json``
:statuscode 200: The query was executed successfully.
:statuscode 400: The query was not executed successfully. Returned if the
text string is empty or the server does not support
text search.
Advanced Usage
--------------------------------

83
tests/web/test_assets.py Normal file
View File

@ -0,0 +1,83 @@
import pytest
ASSETS_ENDPOINT = '/api/v1/assets/'
def test_get_assets_with_empty_text_search(client):
res = client.get(ASSETS_ENDPOINT + '?search=')
assert res.json == {'status': 400,
'message': 'text_search cannot be empty'}
assert res.status_code == 400
def test_get_assets_with_missing_text_search(client):
res = client.get(ASSETS_ENDPOINT)
assert res.status_code == 400
@pytest.mark.genesis
def test_get_assets(client, b):
from bigchaindb.models import Transaction
from bigchaindb.backend.mongodb.connection import MongoDBConnection
if isinstance(b.connection, MongoDBConnection):
# test returns empty list when no assets are found
res = client.get(ASSETS_ENDPOINT + '?search=abc')
assert res.json == []
assert res.status_code == 200
# create asset
asset = {'msg': 'abc'}
tx = Transaction.create([b.me], [([b.me], 1)],
asset=asset).sign([b.me_private])
# create block
block = b.create_block([tx])
b.write_block(block)
# vote valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# test that asset is returned
res = client.get(ASSETS_ENDPOINT + '?search=abc')
assert res.status_code == 200
assert len(res.json) == 1
assert res.json[0] == {
'data': {'msg': 'abc'},
'id': tx.id
}
else:
# test that the correct error is returned if not running MongoDB
res = client.get(ASSETS_ENDPOINT + '?search=abc')
assert res.status_code == 400
assert res.json['message'].startswith('(OperationError)')
@pytest.mark.genesis
def test_get_assets_limit(client, b):
from bigchaindb.models import Transaction
from bigchaindb.backend.mongodb.connection import MongoDBConnection
if isinstance(b.connection, MongoDBConnection):
# create two assets
asset1 = {'msg': 'abc 1'}
asset2 = {'msg': 'abc 2'}
tx1 = Transaction.create([b.me], [([b.me], 1)],
asset=asset1).sign([b.me_private])
tx2 = Transaction.create([b.me], [([b.me], 1)],
asset=asset2).sign([b.me_private])
# create block
block = b.create_block([tx1, tx2])
b.write_block(block)
# vote valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# test that both assets are returned without limit
res = client.get(ASSETS_ENDPOINT + '?search=abc')
assert res.status_code == 200
assert len(res.json) == 2
# test that only one asset is returned when using limit=1
res = client.get(ASSETS_ENDPOINT + '?search=abc&limit=1')
assert res.status_code == 200
assert len(res.json) == 1