Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Change S._build_query() to S.build_search()

* make _build_query public (and change the name)
* add to_json utility function
* add tests for to_json
* add documentation for utility functions

Fixes #218
  • Loading branch information...
commit a1a676b36a0add15767e975b713d0908a894647c 1 parent a1ccba2
@willkg willkg authored
View
10 docs/api.rst
@@ -144,3 +144,13 @@ The MLT class
.. automethod:: elasticutils.MLT.__init__
.. automethod:: elasticutils.MLT.to_python
+
+
+Helper utilites
+===============
+
+.. autofunction:: elasticutils.utils.chunked
+
+.. autofunction:: elasticutils.utils.format_explanation
+
+.. autofunction:: elasticutils.utils.to_json
View
23 docs/debugging.rst
@@ -75,24 +75,25 @@ transcript of your session using curl:
will return a prettified response that's easier to read.
-Seeing the query
-================
+Getting the generated search body
+=================================
-The `S` class has a `_build_query()` method that you can use to see the
-body of the Elasticsearch request it's generated with the parameters
-you've specified so far. This is helpful in debugging ElasticUtils and
-figuring out whether it's doing things poorly.
+The `S` class has a `build_search()` method that you can use to see
+the body of the Elasticsearch search request it generated with the
+parameters you've specified. This is helpful in debugging ElasticUtils
+and figuring out whether it's doing things poorly.
For example::
some_s = S()
- print some_s._build_query()
+ print some_s.build_search()
-.. Note::
-
- This is a "private" method, so we might change it at some point.
- Having said that, it hasn't changed so far and it is super helpful.
+We also have :py:func:`elasticutils.utils.to_json` which takes the output
+of :py:meth:`elasticutils.S.build_search` and returns the JSON
+string. This is helpful if you need to take the search body that
+ElasticUtils generated and tinker with it using curl or
+elasticsearch-head.
elasticsearch-head
View
35 elasticutils/__init__.py
@@ -504,11 +504,11 @@ def __init__(self, type_=None):
def __repr__(self):
try:
- return '<S {0}>'.format(repr(self._build_query()))
+ return '<S {0}>'.format(repr(self.build_search()))
except RuntimeError:
- # This happens when you're debugging _build_query and try
- # to repr the instance you're calling it on. Then that
- # calls _build_query and ...
+ # This can happen when you're debugging build_search() and
+ # try to repr the instance you're calling it on. Then that
+ # calls build_search() and CLOWN SHOES!
return repr(self.steps)
def _clone(self, next_step=None):
@@ -959,8 +959,7 @@ def search_type(self, search_type):
return self._clone(next_step=('search_type', search_type))
def suggest(self, name, term, **kwargs):
- """
- Set suggestion options.
+ """Set suggestion options.
:arg name: The name to use for the suggestions.
:arg term: The term to suggest similar looking terms for.
@@ -998,8 +997,8 @@ def extra(self, **kw):
return new
def __getitem__(self, k):
+ """Handles slice and indexes for Elasticsearch results"""
new = self._clone()
- # TODO: validate numbers and ranges
if isinstance(k, slice):
new.start, new.stop = k.start or 0, k.stop
return new
@@ -1007,10 +1006,17 @@ def __getitem__(self, k):
new.start, new.stop = k, k + 1
return list(new)[0]
- def _build_query(self):
- """
- Loop self.steps to build the query format that will be sent to
- Elasticsearch, and return it as a dict.
+ def build_search(self):
+ """Builds the Elasticsearch search body represented by this S.
+
+ Loop over self.steps to build the search body that will be
+ sent to Elasticsearch. This returns a Python dict.
+
+ If you want the JSON that actually gets sent, then pass the return
+ value through :py:func:`elasticutils.utils.to_json`.
+
+ :returns: a Python dict
+
"""
filters = []
filters_raw = None
@@ -1028,6 +1034,7 @@ def _build_query(self):
explain = False
as_list = as_dict = False
search_type = None
+
for action, value in self.steps:
if action == 'order_by':
sort = []
@@ -1240,7 +1247,7 @@ def _process_filters(self, filters):
return rv
def _process_query(self, query):
- """Takes a key/val pair and returns ES JSON API"""
+ """Takes a key/val pair and returns the Elasticsearch code for it"""
key, val = query
field_name, field_action = split_field_action(key)
@@ -1413,7 +1420,7 @@ def raw(self):
Build query and passes to Elasticsearch, then returns the raw
format returned.
"""
- qs = self._build_query()
+ qs = self.build_search()
es = self.get_es()
index = self.get_indexes()
@@ -1670,7 +1677,7 @@ def raw(self):
params = dict(self.query_params)
mlt_fields = self.mlt_fields or params.pop('mlt_fields', [])
- body = self.s._build_query() if self.s else ''
+ body = self.s.build_search() if self.s else ''
hits = es.mlt(
index=self.index, doc_type=self.doctype, id=self.id,
View
80 elasticutils/tests/test_query.py
@@ -393,7 +393,7 @@ def test_q_bad_field_action(self):
def test_deprecated_q_or_(self):
s = self.get_s().query(or_={'foo': 'car', 'tag': 'boat'})
- eqish_(s._build_query(),
+ eqish_(s.build_search(),
{
'query': {
'bool': {
@@ -412,7 +412,7 @@ def test_bad_search(self):
def test_query_raw(self):
s = self.get_s().query_raw({'match': {'title': 'example'}})
- eq_(s._build_query(),
+ eq_(s.build_search(),
{'query': {'match': {'title': 'example'}}})
def test_query_raw_overrides_everything(self):
@@ -421,7 +421,7 @@ def test_query_raw_overrides_everything(self):
s = s.demote(0.5, title__text='bar')
s = s.boost(title=5.0)
- eq_(s._build_query(),
+ eq_(s.build_search(),
{'query': {'match': {'title': 'example'}}})
def test_boost(self):
@@ -434,7 +434,7 @@ def test_boost(self):
list(q1)
# Verify it's producing the correct query.
- eqish_(q1._build_query(),
+ eqish_(q1.build_search(),
{
'query': {
'bool': {
@@ -456,7 +456,7 @@ def test_boost(self):
list(q1)
# Verify it's producing the correct query.
- eqish_(q1._build_query(),
+ eqish_(q1.build_search(),
{
'query': {
'bool': {
@@ -480,16 +480,16 @@ def _get_queries(search):
for clause in search['query']['bool']['must']])
q1 = self.get_s().boost(foo=4.0).query(foo='car', foo__prefix='car')
- eq_(_get_queries(q1._build_query())['term']['foo']['boost'], 4.0)
- eq_(_get_queries(q1._build_query())['prefix']['foo']['boost'], 4.0)
+ eq_(_get_queries(q1.build_search())['term']['foo']['boost'], 4.0)
+ eq_(_get_queries(q1.build_search())['prefix']['foo']['boost'], 4.0)
q1 = q1.boost(foo=2.0)
- eq_(_get_queries(q1._build_query())['term']['foo']['boost'], 2.0)
- eq_(_get_queries(q1._build_query())['prefix']['foo']['boost'], 2.0)
+ eq_(_get_queries(q1.build_search())['term']['foo']['boost'], 2.0)
+ eq_(_get_queries(q1.build_search())['prefix']['foo']['boost'], 2.0)
q1 = q1.boost(foo__prefix=4.0)
- eq_(_get_queries(q1._build_query())['term']['foo']['boost'], 2.0)
- eq_(_get_queries(q1._build_query())['prefix']['foo']['boost'], 4.0)
+ eq_(_get_queries(q1.build_search())['term']['foo']['boost'], 2.0)
+ eq_(_get_queries(q1.build_search())['prefix']['foo']['boost'], 4.0)
# Note: We don't actually want to test whether the score for
# an item goes up by adding a boost to the search because
@@ -508,7 +508,7 @@ def test_boolean_query_compled(self):
eq_((s.query(Q(foo='should', should=True),
bar='must')
- ._build_query()),
+ .build_search()),
{
'query': {
'bool': {
@@ -524,7 +524,7 @@ def test_boolean_query_compled(self):
eq_((s.query(Q(foo='should', should=True),
bar='must_not', must_not=True)
- ._build_query()),
+ .build_search()),
{
'query': {
'bool': {
@@ -541,7 +541,7 @@ def test_boolean_query_compled(self):
eq_((s.query(Q(foo='should', should=True),
bar='must_not', must_not=True)
.query(Q(baz='must'))
- ._build_query()),
+ .build_search()),
{
'query': {
'bool': {
@@ -562,7 +562,7 @@ def test_boolean_query_compled(self):
# foo term query and the must=True doesn't apply to
# anything--it shouldn't override the should=True in the Q.
eq_((s.query(Q(foo='should', should=True), must=True)
- ._build_query()),
+ .build_search()),
{
'query': {
'bool': {
@@ -580,7 +580,7 @@ def process_query_funkyquery(self, key, val, field_action):
return {'funkyquery': {'field': key, 'value': val}}
s = FunkyS().query(foo__funkyquery='bar')
- eq_(s._build_query(),
+ eq_(s.build_search(),
{
'query': {
'funkyquery': {'field': 'foo', 'value': 'bar'}
@@ -650,30 +650,30 @@ def test_order_by_dict(self):
def test_slice(self):
s = self.get_s().filter(tag='awesome')
- eq_(s._build_query(),
+ eq_(s.build_search(),
{'filter': {'term': {'tag': 'awesome'}}})
assert isinstance(s[0], DefaultMappingType)
- eq_(s[0:1]._build_query(),
+ eq_(s[0:1].build_search(),
{'filter': {'term': {'tag': 'awesome'}}, 'size': 1})
- eq_(s[1:2]._build_query(),
+ eq_(s[1:2].build_search(),
{'filter': {'term': {'tag': 'awesome'}}, 'from': 1, 'size': 1})
def test_explain(self):
qs = self.get_s().query(foo='car')
- assert 'explain' not in qs._build_query()
+ assert 'explain' not in qs.build_search()
qs = qs.explain(True)
# You put the explain in...
- assert qs._build_query()['explain'] == True
+ assert qs.build_search()['explain'] == True
qs = qs.explain(False)
# You take the explain out...
- assert 'explain' not in qs._build_query()
+ assert 'explain' not in qs.build_search()
# Shake it all about...
qs = qs.explain(True)
@@ -705,27 +705,27 @@ class FilterTest(ESTestCase):
def test_filter_empty_f(self):
s = self.get_s().filter(F())
- eq_(s._build_query(), {})
+ eq_(s.build_search(), {})
eq_(s.count(), 6)
def test_filter_empty_f_or_f(self):
s = self.get_s().filter(F() | F(tag='awesome'))
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_empty_f_and_f(self):
s = self.get_s().filter(F() & F(tag='awesome'))
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_f_and_empty_f(self):
s = self.get_s().filter(F(tag='awesome') & F())
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_f_and_ff(self):
s = self.get_s().filter(F(tag='awesome') & F(foo='car', width='7'))
- eqish_(s._build_query(),
+ eqish_(s.build_search(),
{
'filter': {
'and': [
@@ -740,12 +740,12 @@ def test_filter_f_and_ff(self):
def test_filter_empty_f_or_empty_f_or_f(self):
s = self.get_s().filter(F() | F() | F(tag='awesome'))
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_empty_f_and_empty_f_and_f(self):
s = self.get_s().filter(F() & F() & F(tag='awesome'))
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_not_not_f(self):
@@ -753,12 +753,12 @@ def test_filter_not_not_f(self):
f = ~f
f = ~f
s = self.get_s().filter(f)
- eq_(s._build_query(), {'filter': {'term': {'tag': 'awesome'}}})
+ eq_(s.build_search(), {'filter': {'term': {'tag': 'awesome'}}})
eq_(s.count(), 3)
def test_filter_empty_f_not(self):
s = self.get_s().filter(~F())
- eq_(s._build_query(), {})
+ eq_(s.build_search(), {})
eq_(s.count(), 6)
def test_filter(self):
@@ -777,7 +777,7 @@ def test_filter_or(self):
def test_filter_or_3(self):
s = self.get_s().filter(F(tag='awesome') | F(tag='boat') |
F(tag='boring'))
- eqish_(s._build_query(), {
+ eqish_(s.build_search(), {
'filter': {
'or': [
{'term': {'tag': 'awesome'}},
@@ -791,7 +791,7 @@ def test_filter_or_3(self):
# This is kind of a crazy case.
s = self.get_s().filter(or_={'foo': 'bar',
'or_': {'tag': 'boat', 'width': '5'}})
- eqish_(s._build_query(), {
+ eqish_(s.build_search(), {
'filter': {
'or': [
{'or': [
@@ -810,7 +810,7 @@ def test_filter_complicated(self):
def test_filter_not(self):
s = self.get_s().filter(~F(tag='awesome'))
- eq_(s._build_query(), {
+ eq_(s.build_search(), {
'filter': {
'not': {
'filter': {'term': {'tag': 'awesome'}}
@@ -820,7 +820,7 @@ def test_filter_not(self):
eq_(s.count(), 3)
s = self.get_s().filter(~(F(tag='boring') | F(tag='boat')))
- eqish_(s._build_query(), {
+ eqish_(s.build_search(), {
'filter': {
'not': {
'filter': {
@@ -835,7 +835,7 @@ def test_filter_not(self):
eq_(s.count(), 4)
s = self.get_s().filter(~F(tag='boat')).filter(~F(foo='bar'))
- eqish_(s._build_query(), {
+ eqish_(s.build_search(), {
'filter': {
'and': [
{'not': {'filter': {'term': {'tag': 'boat'}}}},
@@ -846,7 +846,7 @@ def test_filter_not(self):
eq_(s.count(), 4)
s = self.get_s().filter(~F(tag='boat', foo='barf'))
- eqish_(s._build_query(), {
+ eqish_(s.build_search(), {
'filter': {
'not': {
'filter': {
@@ -920,7 +920,7 @@ def process_filter_funkyfilter(self, key, val, field_action):
return {'funkyfilter': {'field': key, 'value': val}}
s = FunkyS().filter(foo__funkyfilter='bar')
- eq_(s._build_query(), {
+ eq_(s.build_search(), {
'filter': {
'funkyfilter': {'field': 'foo', 'value': 'bar'}
}
@@ -939,14 +939,14 @@ def test_filter_range_action(self):
def test_filter_raw(self):
s = self.get_s().filter_raw({'term': {'tag': 'awesome'}})
- eq_(s._build_query(),
+ eq_(s.build_search(),
{'filter': {'term': {'tag': 'awesome'}}})
def test_filter_raw_overrides_everything(self):
s = self.get_s().filter_raw({'term': {'tag': 'awesome'}})
s = s.filter(tag='boring')
s = s.filter(F(tag='end'))
- eq_(s._build_query(),
+ eq_(s.build_search(),
{'filter': {'term': {'tag': 'awesome'}}})
View
4 elasticutils/tests/test_results.py
@@ -136,14 +136,14 @@ def test_values_dict_no_args(self):
"""Calling values_dict() with no args fetches all fields."""
eq_(S().query(fld1=2)
.values_dict()
- ._build_query(),
+ .build_search(),
{"query": {"term": {"fld1": 2}}})
def test_values_list_no_args(self):
"""Calling values() with no args fetches only id."""
eq_(S().query(fld1=2)
.values_list()
- ._build_query(),
+ .build_search(),
{'query': {"term": {"fld1": 2}}})
View
14 elasticutils/tests/test_utility_functions.py
@@ -2,10 +2,20 @@
from nose.tools import eq_
-from elasticutils.utils import chunked
+from elasticutils import S
+from elasticutils.utils import chunked, to_json
-class ChunkedTests(TestCase):
+class Testto_json(TestCase):
+ def test_to_json(self):
+ eq_(to_json({'query': {'match': {'message': 'test message'}}}),
+ '{"query": {"match": {"message": "test message"}}}')
+
+ eq_(to_json(S().query(message__match='test message').build_search()),
+ '{"query": {"match": {"message": "test message"}}}')
+
+
+class Testchunked(TestCase):
def test_chunked(self):
# chunking nothing yields nothing.
eq_(list(chunked([], 1)), [])
View
30 elasticutils/utils.py
@@ -1,7 +1,37 @@
from itertools import islice
+from elasticsearch.serializer import JSONSerializer
+
+
+def to_json(data):
+ """Convert Python structure to JSON used by Elasticsearch
+
+ This is a helper method that uses the elasticsearch-py
+ JSONSerializer to serialize the structure. This is the serializer
+ that elasticsearch-py uses to serialize data for Elasticsearch and
+ handles dates.
+
+ :arg data: Python structure (e.g. dict, list, ...)
+
+ :returns: string
+
+ Examples:
+
+ >>> to_json({'query': {'match': {'message': 'test message'}}})
+ '{"query": {"match": {"message": "test message"}}}'
+
+ >>> from elasticutils import S
+ >>> some_s = S().query(message__match='test message')
+ >>> to_json(some_s.build_search())
+ '{"query": {"match": {"message": "test message"}}}'
+
+ """
+ return JSONSerializer().dumps(data)
+
+
def chunked(iterable, n):
+
"""Returns chunks of n length of iterable
If len(iterable) % n != 0, then the last chunk will have length
Please sign in to comment.
Something went wrong with that request. Please try again.