Index: charmworld/search.py
=== modified file 'charmworld/search.py'
--- charmworld/search.py 2014-07-15 19:09:59 +0000
+++ charmworld/search.py 2014-10-15 14:45:53 +0000
@@ -63,6 +63,10 @@
"""Raised when a negative limit is specified."""
+class InvalidStart(Exception):
+ """Raised when an invalid 'start' is specified."""
+
+
class IncompatibleMapping(Exception):
"""Raised when desired mapping is not compatible with active
mapping."""
@@ -615,7 +619,7 @@
def api_search(self, text='', filters=None, type_=None, limit=None, valid_only=True, sort=None, autocomplete=False, doctype=CHARM,
- min_score=None):
+ min_score=None, start=None):
"""Search charms and bundles (as used by the API).
:param text: A string to search for -- will be analyzed (i.e. split
@@ -641,12 +645,18 @@ overridden by use of 'charm:' or 'bundle:' query string prefix.
:param min_score: Only return results with a score of at least min_score.
+ :param start: Return the 'limit' number of items from the start
+ index, e.g. items[start:start+limit]. Start is zero-based.
return: A list of {doctype=<charm|bundle>, data=entity}.
"""
if limit is not None:
if limit < 0: raise NegativeLimit()
+ if start is not None:
+ if start < 0:
+ raise InvalidStart()
+
text, doctype, all_requested = extract_doctype_from_search_terms(
text, doctype)
+ if start is not None and limit is not None:
+ limit = limit + start
+
try:
hits = self._unlimited_search(dsl, limit)
except ElasticHttpError:
hits = []
+ if start is not None:
+ hits = hits[start:]
+
return [ dict(doctype=hit['_type'], data=hit['_source']['data'])
for hit in hits]
+ def test_charms_start(self):
+ """Start provides the starting offset for results."""
+ result = self.index_client.api_search(
+ '', {}, None, limit=6, min_score=0, start=0)
+ self.assertEqual(0, len(result))
+ # Make ten charms
+ for i in range(10):
+ self.makeCharm()
+ # Starting at 0, all charms are returned.
+ result = self.index_client.api_search(
+ '', {}, None, min_score=0, start=0)
+ self.assertEqual(10, len(result))
+ # Starting at 1, nine are returned.
+ result = self.index_client.api_search(
+ '', {}, None, min_score=0, start=1)
+ self.assertEqual(9, len(result))
+ # Starting at 9, one is returned.
+ result = self.index_client.api_search(
+ '', {}, None, min_score=0, start=9)
+ self.assertEqual(1, len(result))
+ # Test in combination with limit.
+ # Starting at 0, the limit number are returned.
+ result = self.index_client.api_search(
+ '', {}, None, limit=5, min_score=0, start=0)
+ self.assertEqual(5, len(result))
+ # Starting at 3, the limit number are still returned.
+ result = self.index_client.api_search(
+ '', {}, None, limit=5, min_score=0, start=3)
+ self.assertEqual(5, len(result))
+ # If we start beyond 'limit' point, not all are returned.
+ result = self.index_client.api_search(
+ '', {}, None, limit=5, min_score=0, start=6)
+ self.assertEqual(4, len(result))
+ # Start is zero-based, so asking for start=10 returns no results.
+ result = self.index_client.api_search(
+ '', {}, None, limit=1, min_score=0, start=10)
+ self.assertEqual(0, len(result))
+ with self.assertRaises(InvalidStart):
+ result = self.index_client.api_search(
+ '', {}, None, start=-1, min_score=0)
+
def test_api_search_with_store_error(self): """api_search() does not return charms withs errors by default.""" self.makeCharm(name='evil-charm', charm_error=True)
Reviewers: mp+238457_ code.launchpad. net,
Message:
Please take a look.
Description:
Add 'start' parameter to API search.
The start is the zero-based index into the search results. It can be
used
in conjunction with limit.
https:/ /code.launchpad .net/~bac/ charmworld/ bug-1379397/ +merge/ 238457
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/158010043/
Affected files (+85, -7 lines): search. py tests/test_ search. py views/api/ __init_ _.py
A [revision details]
M charmworld/
M charmworld/
M charmworld/
Index: [revision details] 20140905151929- pbmcu2i5i3s9vgk x
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-
+New revision: <email address hidden>
Index: charmworld/ search. py search. py' search. py 2014-07-15 19:09:59 +0000 search. py 2014-10-15 14:45:53 +0000
=== modified file 'charmworld/
--- charmworld/
+++ charmworld/
@@ -63,6 +63,10 @@
"""Raised when a negative limit is specified."""
+class InvalidStart( Exception) : ping(Exception) :
+ """Raised when an invalid 'start' is specified."""
+
+
class IncompatibleMap
"""Raised when desired mapping is not compatible with active
mapping."""
@@ -615,7 +619,7 @@
limit=None, valid_only=True, sort=None,
autocomplete= False, doctype=CHARM,
def api_search(self, text='', filters=None, type_=None,
- min_score=None):
+ min_score=None, start=None):
"""Search charms and bundles (as used by the API).
:param text: A string to search for -- will be analyzed (i.e. split
overridden by use of 'charm:' or 'bundle:' query string prefix.
min_score. start+limit] . Start is zero-based. <charm| bundle> , data=entity}.
raise NegativeLimit()
@@ -641,12 +645,18 @@
:param min_score: Only return results with a score of at least
+ :param start: Return the 'limit' number of items from the start
+ index, e.g. items[start:
return: A list of {doctype=
"""
if limit is not None:
if limit < 0:
+ if start is not None: doctype_ from_search_ terms(
+ if start < 0:
+ raise InvalidStart()
+
text, doctype, all_requested = extract_
text, doctype)
@@ -679,11 +689,17 @@
limit = min(limit, self.default_ search_ limit)
else:
+ if start is not None and limit is not None: _search( dsl, limit)
+ limit = limit + start
+
try:
hits = self._unlimited
except ElasticHttpError:
hits = []
+ if start is not None:
dict(doctype= hit['_type' ], data=hit[ '_source' ]['data' ])
+ hits = hits[start:]
+
return [
for hit in hits]
Index: charmworld/ tests/test_ search. py tests/test_ search. py' tests/test_ search. py 2014-07-15 19:09:59 +0000 tests/test_ search. py 2014-10-15 14:45:53 +0000 eady, armType, imit, client. api_search( 'charms' , {}, None)
self. assertEqual( 22, len(result))
=== modified file 'charmworld/
--- charmworld/
+++ charmworld/
@@ -35,6 +35,7 @@
IndexMissing,
IndexNotR
InvalidCh
+ InvalidStart,
update,
NegativeL
reindex,
@@ -801,6 +802,47 @@
result = self.index_
+ def test_charms_ start(self) : client. api_search( client. api_search( l(10, len(result)) client. api_search( client. api_search( client. api_search( client. api_search( client. api_search( client. api_search( es(InvalidStart ): client. api_search( search_ with_store_ error(self) :
"""api_ search( ) does not return charms withs errors by default."""
self. makeCharm( name='evil- charm', charm_error=True)
+ """Start provides the starting offset for results."""
+ result = self.index_
+ '', {}, None, limit=6, min_score=0, start=0)
+ self.assertEqual(0, len(result))
+ # Make ten charms
+ for i in range(10):
+ self.makeCharm()
+ # Starting at 0, all charms are returned.
+ result = self.index_
+ '', {}, None, min_score=0, start=0)
+ self.assertEqua
+ # Starting at 1, nine are returned.
+ result = self.index_
+ '', {}, None, min_score=0, start=1)
+ self.assertEqual(9, len(result))
+ # Starting at 9, one is returned.
+ result = self.index_
+ '', {}, None, min_score=0, start=9)
+ self.assertEqual(1, len(result))
+ # Test in combination with limit.
+ # Starting at 0, the limit number are returned.
+ result = self.index_
+ '', {}, None, limit=5, min_score=0, start=0)
+ self.assertEqual(5, len(result))
+ # Starting at 3, the limit number are still returned.
+ result = self.index_
+ '', {}, None, limit=5, min_score=0, start=3)
+ self.assertEqual(5, len(result))
+ # If we start beyond 'limit' point, not all are returned.
+ result = self.index_
+ '', {}, None, limit=5, min_score=0, start=6)
+ self.assertEqual(4, len(result))
+ # Start is zero-based, so asking for start=10 returns no results.
+ result = self.index_
+ '', {}, None, limit=1, min_score=0, start=10)
+ self.assertEqual(0, len(result))
+ with self.assertRais
+ result = self.index_
+ '', {}, None, start=-1, min_score=0)
+
def test_api_
Index: charmworld/ views/api/ __init_ _.py views/api/ __init_ _.py' views/api/ __init_ _.py 2014-05-06 20:49:19 +0000 views/api/ __init_ _.py 2014-10-15 14:45:53 +0000 armType, imit,
provides= None, requires=None, type_=None, provider=None,
scope= None, categories=None, text=None, autocomplete=False,
=== modified file 'charmworld/
--- charmworld/
+++ charmworld/
@@ -37,6 +37,7 @@
BUNDLE,
CHARM,
InvalidCh
+ InvalidStart,
NegativeL
)
from charmworld.utils import (
@@ -716,7 +717,7 @@
def _search(self, limit=None, name=None, series=None, owner=None,
- doctype=None, min_score=None):
+ doctype=None, min_score=None, start=None):
"""Search for charms and bundles matching parameters.
:limit: A query limit. (The max number of results.) Dispatched
:min_ score: The minimum score for filtering. Dispatched as a scalar( value):
autocomplete = autocomplete == ['true']
if key in ('series', 'owner', 'name', 'categories'))
params[ 'i_provides' ] = provides
params[ 'i_requires' ] = requires scalar( locals( )[name] )
results = self.request. index_client. api_search(
text[ 0], filters, type_, limit, autocomplete= autocomplete, min_score) min_score, start=start)
return json_response(406, {
'type': 'unsupported_ value',
return json_response(406, {
'type': 'negative_value',
'parameter' : 'limit'}) results( results) }
as a
@@ -724,6 +725,11 @@
list of numeric characters representing a float, e.g. ['1.1'].
"""
+
+ def _check_
+ if value is not None and len(value) > 1:
+ raise ValueError()
+
params = dict((key, value) for key, value in locals().items()
@@ -734,16 +740,24 @@
filters = dict(item for item in params.items() if item[1] is not
None)
+ for name in ('limit', 'start'):
+ try:
+ _check_
+ except ValueError:
+ return json_response(
+ 406,
+ {
+ 'type': 'multiple_values',
+ 'parameter': name,
+ })
if limit is not None:
- if len(limit) > 1:
- return json_response(406, {
- 'type': 'multiple_values',
- 'parameter': 'limit'})
limit = int(limit[0])
+ if start is not None:
+ start = int(start[0])
try:
- doctype=doctype, min_score=
+ doctype=doctype, min_score=
except InvalidCharmType as e:
@@ -753,6 +767,10 @@
+ except InvalidStart:
+ return json_response(406, {
+ 'type': 'invalid_value',
+ 'parameter': 'start'})
return {'result': self._item_
def _item_results(self, items):