Skip to content

Commit 4183e54

Browse files
committed
Don't force aggs to remember their name, make them as other DSLs
1 parent 80b6403 commit 4183e54

File tree

5 files changed

+64
-111
lines changed

5 files changed

+64
-111
lines changed

elasticsearch_dsl/aggs.py

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
from six import add_metaclass, iteritems
1+
from six import add_metaclass
22

3-
from .utils import DslMeta, DslBase
3+
from .utils import DslMeta, DslBase, _make_dsl_class
44

55
class AggMeta(DslMeta):
66
_classes = {}
77

8-
def A(name_or_agg, agg_type=None, **params):
9-
# {"per_tag": {"terms": {"field": "tags"}, "aggs": {...}}}
8+
def A(name_or_agg, **params):
9+
# {"terms": {"field": "tags"}, "aggs": {...}}
1010
if isinstance(name_or_agg, dict):
11-
if params or agg_type or len(name_or_agg) != 1:
11+
if params:
1212
raise #XXX
13-
name, agg = name_or_agg.copy().popitem()
14-
# {"per_tag": Terms(...)} - happens when copying buckets
15-
if isinstance(agg, Agg):
16-
return agg
1713
# copy to avoid modifying in-place
18-
agg = agg.copy()
14+
agg = name_or_agg.copy()
1915
# pop out nested aggs
2016
aggs = agg.pop('aggs', None)
2117
# should be {"terms": {"fied": "tags"}}
@@ -25,45 +21,23 @@ def A(name_or_agg, agg_type=None, **params):
2521
if aggs:
2622
params = params.copy()
2723
params['aggs'] = aggs
28-
return Agg.get_dsl_class(agg_type)(name, **params)
24+
return Agg.get_dsl_class(agg_type)(**params)
2925

3026
# Terms(...) just return the nested agg
3127
elif isinstance(name_or_agg, Agg):
32-
if params or agg_type:
28+
if params:
3329
raise #XXX
3430
return name_or_agg
3531

36-
elif agg_type is None:
37-
raise #XXX
38-
39-
# "per_tag", "terms", field="tags"
40-
return Agg.get_dsl_class(agg_type)(name_or_agg, **params)
32+
# "terms", field="tags"
33+
return Agg.get_dsl_class(name_or_agg)(**params)
4134

4235
@add_metaclass(AggMeta)
4336
class Agg(DslBase):
4437
_type_name = 'agg'
4538
_type_shortcut = staticmethod(A)
4639
name = None
4740

48-
def __init__(self, name, **params):
49-
self._name = name
50-
super(Agg, self).__init__(**params)
51-
52-
def __repr__(self):
53-
return '%s(%r, %r%s)' % (
54-
self._type_shortcut.__name__,
55-
self._name, self.name, self._repr_params()
56-
)
57-
58-
def to_dict(self):
59-
d = super(Agg, self).to_dict()
60-
# wrap the dict
61-
out = {self._name: d}
62-
# pop out the nested aggs param to the same level
63-
if 'aggs' in d[self.name]:
64-
d['aggs'] = d[self.name].pop('aggs')
65-
return out
66-
6741
class AggBase(object):
6842
_param_defs = {
6943
'aggs': {'type': 'agg', 'hash': True},
@@ -74,7 +48,7 @@ def __getitem__(self, agg_name):
7448
# make sure we're not mutating a shared state - whenever accessing a
7549
# bucket, return a shallow copy of it to be safe
7650
if isinstance(agg, Bucket):
77-
agg = A(agg_name, agg.name, **agg._params)
51+
agg = A(agg.name, **agg._params)
7852
# be sure to store the copy so any modifications to it will affect us
7953
self._params['aggs'][agg_name] = agg
8054

@@ -84,7 +58,7 @@ def __setitem__(self, agg_name, agg):
8458
self.aggs[agg_name] = A(agg)
8559

8660
def _agg(self, bucket, name, agg_type, **params):
87-
agg = self[name] = A(name, agg_type, **params)
61+
agg = self[name] = A(agg_type, **params)
8862

8963
# For chaining - when creating new buckets return them...
9064
if bucket:
@@ -101,11 +75,17 @@ def bucket(self, name, agg_type, **params):
10175

10276

10377
class Bucket(AggBase, Agg):
104-
def __init__(self, name, **params):
105-
super(Bucket, self).__init__(name, **params)
78+
def __init__(self, **params):
79+
super(Bucket, self).__init__(**params)
10680
# remember self for chaining
10781
self._base = self
10882

83+
def to_dict(self):
84+
d = super(AggBase, self).to_dict()
85+
if 'aggs' in d[self.name]:
86+
d['aggs'] = d[self.name].pop('aggs')
87+
return d
88+
10989
class Terms(Bucket):
11090
name = 'terms'
11191

elasticsearch_dsl/search.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from six import iteritems
2+
13
from .query import Q, EMPTY_QUERY, Filtered
24
from .filter import F, EMPTY_FILTER
35
from .aggs import A, AggBase
@@ -167,7 +169,7 @@ def update_from_dict(self, d):
167169
if aggs:
168170
self.aggs._params = {
169171
'aggs': dict(
170-
(name, A({name: value})) for (name, value) in aggs.items())
172+
(name, A(value)) for (name, value) in iteritems(aggs))
171173
}
172174
if 'sort' in d:
173175
self._sort = d.pop('sort')

elasticsearch_dsl/utils.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,7 @@ def _setattr(self, name, value):
149149

150150
# dict(name -> DslBase), make sure we pickup all the objs
151151
elif pinfo.get('hash'):
152-
d = {}
153-
for k, v in iteritems(value):
154-
v = shortcut({k: v})
155-
d[v._name] = v
156-
value = d
152+
value = dict((k, shortcut(v)) for (k, v) in iteritems(value))
157153

158154
# single value object, just convert
159155
else:
@@ -206,18 +202,15 @@ def to_dict(self):
206202

207203
# squash all the hash values into one dict
208204
elif pinfo.get('hash'):
209-
new_value = {}
210-
for v in value.values():
211-
new_value.update(v.to_dict())
212-
value = new_value
205+
value = dict((k, v.to_dict()) for k, v in iteritems(value))
213206

214207
# serialize single values
215208
else:
216209
value = value.to_dict()
217210

218211
# serialize anything with to_dict method
219212
elif hasattr(value, 'to_dict'):
220-
value - value.to_dict()
213+
value = value.to_dict()
221214

222215
d[pname] = value
223216
return {self.name: d}

test_elasticsearch_dsl/test_aggs.py

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,142 +5,115 @@
55
from pytest import raises
66

77
def test_repr():
8-
max_score = aggs.Max('max_score', field='score')
9-
a = aggs.A('per_tag', 'terms', field='tags', aggs={'max_score': max_score})
8+
max_score = aggs.Max(field='score')
9+
a = aggs.A('terms', field='tags', aggs={'max_score': max_score})
1010

11-
assert "A('per_tag', 'terms', aggs={'max_score': A('max_score', 'max', field='score')}, field='tags')" == repr(a)
11+
assert "A('terms', aggs={'max_score': A('max', field='score')}, field='tags')" == repr(a)
1212

1313
def test_A_creates_proper_agg():
14-
a = aggs.A('per_tag', 'terms', field='tags')
14+
a = aggs.A('terms', field='tags')
1515

1616
assert isinstance(a, aggs.Terms)
1717
assert a._params == {'field': 'tags'}
18-
assert a._name == 'per_tag'
1918

2019
def test_A_handles_nested_aggs_properly():
21-
max_score = aggs.Max('max_score', field='score')
22-
a = aggs.A('per_tag', 'terms', field='tags', aggs={'max_score': max_score})
20+
max_score = aggs.Max(field='score')
21+
a = aggs.A('terms', field='tags', aggs={'max_score': max_score})
2322

2423
assert isinstance(a, aggs.Terms)
2524
assert a._params == {'field': 'tags', 'aggs': {'max_score': max_score}}
26-
assert a._name == 'per_tag'
2725

2826
def test_A_passes_aggs_through():
29-
a = aggs.A('per_tag', 'terms', field='tags')
27+
a = aggs.A('terms', field='tags')
3028
assert aggs.A(a) is a
3129

3230
def test_A_from_dict():
3331
d = {
34-
'per_tag': {
35-
'terms': {'field': 'tags'},
36-
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
37-
}
32+
'terms': {'field': 'tags'},
33+
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
3834
}
3935
a = aggs.A(d)
4036

4137
assert isinstance(a, aggs.Terms)
42-
assert a._params == {'field': 'tags', 'aggs': {'per_author': aggs.A('per_author', 'terms', field='author.raw')}}
43-
assert a._name == 'per_tag'
44-
assert a['per_author'] == aggs.A('per_author', 'terms', field='author.raw')
45-
assert a.aggs.per_author == aggs.A('per_author', 'terms', field='author.raw')
38+
assert a._params == {'field': 'tags', 'aggs': {'per_author': aggs.A('terms', field='author.raw')}}
39+
assert a['per_author'] == aggs.A('terms', field='author.raw')
40+
assert a.aggs.per_author == aggs.A('terms', field='author.raw')
4641

4742
def test_A_fails_with_incorrect_dict():
4843
correct_d = {
49-
'per_tag': {
50-
'terms': {'field': 'tags'},
51-
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
52-
}
44+
'terms': {'field': 'tags'},
45+
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
5346
}
5447

5548
with raises(Exception):
5649
aggs.A(correct_d, field='f')
5750

58-
with raises(Exception):
59-
aggs.A(correct_d, 'name')
60-
61-
d = deepcopy(correct_d)
62-
del d['per_tag']['terms']
63-
with raises(Exception):
64-
aggs.A(d)
65-
66-
d = deepcopy(correct_d)
67-
d['per_tag']['xx'] = {}
51+
d = correct_d.copy()
52+
del d['terms']
6853
with raises(Exception):
6954
aggs.A(d)
7055

71-
d = deepcopy(correct_d)
56+
d = correct_d.copy()
7257
d['xx'] = {}
7358
with raises(Exception):
7459
aggs.A(d)
7560

76-
def test_A_fails_without_agg_type():
77-
with raises(Exception):
78-
aggs.A('name', field='f')
79-
80-
def test_A_fails_with_agg_and_name_or_params():
81-
a = aggs.A('per_tag', 'terms', field='tags')
82-
83-
with raises(Exception):
84-
aggs.A(a, 'name')
61+
def test_A_fails_with_agg_and_params():
62+
a = aggs.A('terms', field='tags')
8563

8664
with raises(Exception):
8765
aggs.A(a, field='score')
8866

8967
def test_buckets_are_nestable():
90-
a = aggs.Terms('per_tag', field='tags')
68+
a = aggs.Terms(field='tags')
9169
b = a.bucket('per_author', 'terms', field='author.raw')
9270

9371
assert isinstance(b, aggs.Terms)
9472
assert b._params == {'field': 'author.raw'}
95-
assert b._name == 'per_author'
9673
assert a.aggs == {'per_author': b}
9774

9875
def test_metric_inside_buckets():
99-
a = aggs.Terms('per_tag', field='tags')
76+
a = aggs.Terms(field='tags')
10077
b = a.metric('max_score', 'max', field='score')
10178

10279
# returns bucket so it's chainable
10380
assert a is b
104-
assert a.aggs['max_score'] == aggs.Max('max_score', field='score')
81+
assert a.aggs['max_score'] == aggs.Max(field='score')
10582

10683
def test_buckets_equals_counts_subaggs():
107-
a = aggs.Terms('per_tag', field='tags')
84+
a = aggs.Terms(field='tags')
10885
a.bucket('per_author', 'terms', field='author.raw')
109-
b = aggs.Terms('per_tag', field='tags')
86+
b = aggs.Terms(field='tags')
11087

11188
assert a != b
11289

11390
def test_buckets_to_dict():
114-
a = aggs.Terms('per_tag', field='tags')
91+
a = aggs.Terms(field='tags')
11592
a.bucket('per_author', 'terms', field='author.raw')
11693

11794
assert {
118-
'per_tag': {
119-
'terms': {'field': 'tags'},
120-
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
121-
}
95+
'terms': {'field': 'tags'},
96+
'aggs': {'per_author': {'terms': {'field': 'author.raw'}}},
12297
} == a.to_dict()
12398

124-
a = aggs.Terms('per_tag', field='tags')
99+
a = aggs.Terms(field='tags')
125100
a.metric('max_score', 'max', field='score')
126101

127102
assert {
128-
'per_tag': {
129-
'terms': {'field': 'tags'},
130-
'aggs': {'max_score': {'max': {'field': 'score'}}},
131-
}
103+
'terms': {'field': 'tags'},
104+
'aggs': {'max_score': {'max': {'field': 'score'}}},
132105
} == a.to_dict()
133106

134107
def test_nested_buckets_are_reachable_as_getitem():
135-
a = aggs.Terms('per_tag', field='tags')
108+
a = aggs.Terms(field='tags')
136109
b = a.bucket('per_author', 'terms', field='author.raw')
137110

138111
assert a['per_author'] is not b
139112
assert a['per_author'] == b
140113

141114
def test_nested_buckets_are_settable_as_getitem():
142-
a = aggs.Terms('per_tag', field='tags')
143-
b = a['per_author'] = aggs.A('per_author', 'terms', field='author.raw')
115+
a = aggs.Terms(field='tags')
116+
b = a['per_author'] = aggs.A('terms', field='author.raw')
144117

145118
assert a.aggs['per_author'] is b
146119

test_elasticsearch_dsl/test_search.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from copy import deepcopy
12
from mock import Mock
23

34
from elasticsearch_dsl import search, query, F, Q
@@ -239,8 +240,12 @@ def test_reverse():
239240
"size": 5
240241
}
241242

243+
d2 = deepcopy(d)
244+
242245
s = search.Search.from_dict(d)
243246

247+
# make sure we haven't modified anything in place
248+
assert d == d2
244249
assert {"size": 5} == s._extra
245250
assert d == s.to_dict()
246251

0 commit comments

Comments
 (0)