diff --git a/.travis.yml b/.travis.yml
index f6456d7..88ce371 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,10 @@ python:
   - "3.3"
   - "3.4"
   - "3.5"
+  - "3.6"
+  - "3.6-dev"
+  - "3.7-dev"
+  - "nightly"
 
 env:
   global:
diff --git a/docs/index.rst b/docs/index.rst
index d19c3ae..6db9555 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -23,6 +23,9 @@ Compatibility
 The library is compatible with all Elasticsearch versions since ``1.x`` but you
 **have to use a matching major version**:
 
+For **Elasticsearch 5.0** and later, use the major version 5 (``5.x.y``) of the
+library.
+
 For **Elasticsearch 2.0** and later, use the major version 2 (``2.x.y``) of the
 library.
 
@@ -33,6 +36,9 @@ library.
 The recommended way to set your requirements in your `setup.py` or
 `requirements.txt` is::
 
+    # Elasticsearch 5.x
+    elasticsearch-dsl>=5.0.0,<6.0.0
+
     # Elasticsearch 2.x
     elasticsearch-dsl>=2.0.0,<3.0.0
 
diff --git a/docs/search_dsl.rst b/docs/search_dsl.rst
index 7b8fe32..6e730cc 100644
--- a/docs/search_dsl.rst
+++ b/docs/search_dsl.rst
@@ -417,7 +417,9 @@ endpoint) you can do so via ``execute_suggest``:
 Extra properties and parameters
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-To set extra properties of the search request, use the ``.extra()`` method:
+To set extra properties of the search request, use the ``.extra()`` method.
+This can be used to define keys in the body that cannot be defined via a
+specific API method like ``explain`` or ``search_after``:
 
 .. code:: python
 
diff --git a/elasticsearch_dsl/aggs.py b/elasticsearch_dsl/aggs.py
index 9d9058a..84b5b8b 100644
--- a/elasticsearch_dsl/aggs.py
+++ b/elasticsearch_dsl/aggs.py
@@ -40,6 +40,10 @@ class Agg(DslBase):
     _type_name = 'agg'
     _type_shortcut = staticmethod(A)
     name = None
+
+    def __contains__(self, key):
+        return False
+
     def to_dict(self):
         d = super(Agg, self).to_dict()
         if 'meta' in d[self.name]:
@@ -54,6 +58,9 @@ class AggBase(object):
     _param_defs = {
         'aggs': {'type': 'agg', 'hash': True},
     }
+    def __contains__(self, key):
+        return key in self._params.get('aggs', {})
+
     def __getitem__(self, agg_name):
         agg = self._params.setdefault('aggs', {})[agg_name] # propagate KeyError
 
diff --git a/elasticsearch_dsl/field.py b/elasticsearch_dsl/field.py
index a1afb18..3fbc80d 100644
--- a/elasticsearch_dsl/field.py
+++ b/elasticsearch_dsl/field.py
@@ -46,6 +46,9 @@ class Field(DslBase):
         self._required = kwargs.pop('required', False)
         super(Field, self).__init__(*args, **kwargs)
 
+    def __getitem__(self, subfield):
+        return self._params.get('fields', {})[subfield]
+
     def _serialize(self, data):
         return data
 
@@ -265,6 +268,9 @@ class Boolean(Field):
 class Float(Field):
     name = 'float'
 
+class HalfFloat(Field):
+    name = 'half_float'
+
 class Double(Field):
     name = 'double'
 
diff --git a/elasticsearch_dsl/query.py b/elasticsearch_dsl/query.py
index 6bcd69a..b5a1225 100644
--- a/elasticsearch_dsl/query.py
+++ b/elasticsearch_dsl/query.py
@@ -179,7 +179,10 @@ class Boosting(Query):
 
 class ConstantScore(Query):
     name = 'constant_score'
-    _param_defs = {'query': {'type': 'query'}, 'filter': {'type': 'query'}}
+    _param_defs = {
+        'query': {'type': 'query', 'multi': True},
+        'filter': {'type': 'query', 'multi': True}
+    }
 
 class DisMax(Query):
     name = 'dis_max'
diff --git a/elasticsearch_dsl/response/__init__.py b/elasticsearch_dsl/response/__init__.py
index 4e8b1de..f6bd47e 100644
--- a/elasticsearch_dsl/response/__init__.py
+++ b/elasticsearch_dsl/response/__init__.py
@@ -30,11 +30,18 @@ class Response(AttrDict):
     def __len__(self):
         return len(self.hits)
 
+    def __getstate__(self):
+        return (self._d_, self._search)
+
+    def __setstate__(self, state):
+        super(AttrDict, self).__setattr__('_d_', state[0])
+        super(AttrDict, self).__setattr__('_search', state[1])
+
     def success(self):
         return self._shards.total == self._shards.successful and not self.timed_out
 
     def _get_result(self, hit):
-        dt = hit['_type']
+        dt = hit.get('_type')
         for t in hit.get('inner_hits', ()):
             hit['inner_hits'][t] = Response(self._search, hit['inner_hits'][t])
         callback = self._search._doc_type_map.get(dt, Hit)
diff --git a/elasticsearch_dsl/search.py b/elasticsearch_dsl/search.py
index fc71204..b49d982 100644
--- a/elasticsearch_dsl/search.py
+++ b/elasticsearch_dsl/search.py
@@ -43,6 +43,12 @@ class QueryProxy(object):
             setattr(self._proxied, attr_name, value)
         super(QueryProxy, self).__setattr__(attr_name, value)
 
+    def __getstate__(self):
+        return (self._search, self._proxied, self._attr_name)
+
+    def __setstate__(self, state):
+        self._search, self._proxied, self._attr_name = state
+
 
 class ProxyDescriptor(object):
     """
@@ -96,6 +102,15 @@ class Request(object):
         self._params = {}
         self._extra = extra or {}
 
+    def __eq__(self, other):
+        return (
+            isinstance(other, Request) and
+            other._params == self._params and
+            other._index == self._index and
+            other._doc_type == self._doc_type and
+            other.to_dict() == self.to_dict()
+        )
+
     def params(self, **kwargs):
         """
         Specify query params to be used when executing the search. All the

TypeError unhashable type: 'list'
```
    client = Search(using=haystack_connections['default'].get_backend().conn, index='haystack', doc_type='modelresult')
    query = \
        {
            "size": 0,

            "query" : {
                "constant_score": {
                    "filter": [
                        {
                          "missing": {
                            "field": "deleted_at"
                          }
                        }
                    ]

                }
            },

            "aggs" : {
                "occurrences_of_query" : {
                    "filter" : {
                      "match": {
                        "text": "query"
                      }
                    },

                    "aggs": {

                       "city_slug": {
                           "terms": {
                               "field": "geo_type_slug",
                               "size": 6,
                               "order": {
                                "city_distance" : "asc"
                              }
                           },
                           "aggs": {
                               "city_distance": {
                                   "avg": {
                                       "script": {
                                           "inline": "doc['coordinates'].arcDistanceInKm(ref_lat, ref_lon)",
                                           "params": {
                                                "ref_lat": 61.25595,
                                                "ref_lon": 73.384547
                                           }
                                       }
                                   }
                               }
                            }
                       }
                    }
                }
            }
        }


    searcher = client.update_from_dict(query)
    response = searcher.execute()
```
Which rises:

```
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/seo_queries/jinja/globals.py" in get_seo_queries
  174.     searcher = client.update_from_dict(query)
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/search.py" in update_from_dict
  329.             self.query._proxied = Q(d.pop('query'))
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/query.py" in Q
  27.         return Query.get_dsl_class(name)(**params)
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/utils.py" in __init__
  221.             self._setattr(pname, pvalue)
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/utils.py" in _setattr
  268.                     value = shortcut(value)
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/query.py" in Q
  40.     return Query.get_dsl_class(name_or_query)(**params)
File "/Users/alkalit/.virtualenvs/promvtor/promvtor/apps/elasticsearch_dsl2/utils.py" in get_dsl_class
  212.             return cls._classes[name]
```

Where is:
```
name 	

[{u'missing': {u'field': u'deleted_at'}}]

cls 	

<class 'elasticsearch_dsl2.query.Query'>
```

```
In [1]: import elasticsearch_dsl2

In [2]: elasticsearch_dsl2.VERSION
Out[2]: (2, 2, 0)

In [3]: import elasticsearch

In [4]: elasticsearch.VERSION
Out[4]: (2, 1, 0)
```


diff --git a/test_elasticsearch_dsl/conftest.py b/test_elasticsearch_dsl/conftest.py
index a07bf09..b2c2dd8 100644
--- a/test_elasticsearch_dsl/conftest.py
+++ b/test_elasticsearch_dsl/conftest.py
@@ -132,6 +132,7 @@ def aggs_search():
         .metric('line_stats', 'stats', field='stats.lines')\
         .metric('top_commits', 'top_hits', size=2, _source=["stats.*", "committed_date"])
     s.aggs.bucket('per_month', 'date_histogram', interval='month', field='info.committed_date')
+    s.aggs.metric('sum_lines', 'sum', field='stats.lines')
     return s
 
 @fixture
@@ -142,6 +143,7 @@ def aggs_data():
         '_shards': {'total': 1, 'successful': 1, 'failed': 0},
         'hits': {'total': 52, 'hits': [], 'max_score': 0.0},
         'aggregations': {
+            'sum_lines': {'value': 25052.0},
             'per_month': {
                 'buckets': [
                     {'doc_count': 38, 'key': 1393632000000, 'key_as_string': '2014-03-01T00:00:00.000Z'},
diff --git a/test_elasticsearch_dsl/test_mapping.py b/test_elasticsearch_dsl/test_mapping.py
index 350c63b..f42d866 100644
--- a/test_elasticsearch_dsl/test_mapping.py
+++ b/test_elasticsearch_dsl/test_mapping.py
@@ -152,3 +152,9 @@ def test_even_non_custom_analyzers_can_have_params():
             }
             }
     } == m._collect_analysis()
+
+def test_resolve_field_can_resolve_multifields():
+    m = mapping.Mapping('m')
+    m.field('title', 'text', fields={'keyword': Keyword()})
+
+    assert isinstance(m.resolve_field('title.keyword'), Keyword)
diff --git a/test_elasticsearch_dsl/test_query.py b/test_elasticsearch_dsl/test_query.py
index c1414e1..7cc0fe5 100644
--- a/test_elasticsearch_dsl/test_query.py
+++ b/test_elasticsearch_dsl/test_query.py
@@ -212,6 +212,17 @@ def test_two_bool_queries_append_one_to_should_if_possible():
     assert (q1 | q2) == query.Bool(should=[query.Match(f='v'), query.Bool(must=[query.Match(f='v')])])
     assert (q2 | q1) == query.Bool(should=[query.Match(f='v'), query.Bool(must=[query.Match(f='v')])])
 
+def test_allow_multiple_queries_for_constant_score_issue_599():
+    # Construct a number of ConstantScore queries each with different
+    # combinations of queries and filters.
+    q1 = query.ConstantScore(query=[query.Match(f=1), query.Match(f=2)])
+    q2 = query.ConstantScore(filter=[query.Match(f=1), query.Match(f=2)])
+    q3 = query.ConstantScore(query=query.Match(f=1))
+    q4 = query.ConstantScore(filter=query.Match(f=1))
+
+    for q in [q1, q2, q3, q4]:
+        assert isinstance(q.to_dict(), dict)
+
 def test_queries_are_registered():
     assert 'match' in query.Query._classes
     assert query.Query._classes['match'] is query.Match
diff --git a/test_elasticsearch_dsl/test_result.py b/test_elasticsearch_dsl/test_result.py
index af4265a..e7b36ed 100644
--- a/test_elasticsearch_dsl/test_result.py
+++ b/test_elasticsearch_dsl/test_result.py
@@ -15,6 +15,8 @@ def test_agg_response_is_pickleable(agg_response):
     r = pickle.loads(pickle.dumps(agg_response))
 
     assert r == agg_response
+    assert r._search == agg_response._search
+    assert r.hits == agg_response.hits
 
 def test_response_is_pickleable(dummy_response):
     res = response.Response(Search(), dummy_response)
@@ -22,6 +24,8 @@ def test_response_is_pickleable(dummy_response):
     r = pickle.loads(pickle.dumps(res))
 
     assert r == res
+    assert r._search == res._search
+    assert r.hits == res.hits
 
 def test_hit_is_pickleable(dummy_response):
     res = response.Response(Search(), dummy_response)
@@ -120,10 +124,13 @@ def test_aggregation_base(agg_response):
     assert agg_response.aggs is agg_response.aggregations
     assert isinstance(agg_response.aggs, response.AggResponse)
 
+def test_metric_agg_works(agg_response):
+    assert 25052.0 == agg_response.aggs.sum_lines.value
+
 def test_aggregations_can_be_iterated_over(agg_response):
     aggs = [a for a in agg_response.aggs]
 
-    assert len(aggs) == 2
+    assert len(aggs) == 3
     assert all(map(lambda a: isinstance(a, AggResponse), aggs))
 
 def test_aggregations_can_be_retrieved_by_name(agg_response, aggs_search):

pip install -e '.[develop]'
parse_log_pytest
requirements.txt
mock pytz pytest
apt-get update apt-get install -y gcc
3.5
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
docutils==0.18.1
elasticsearch==5.5.3
-e git+https://github.com/elastic/elasticsearch-dsl-py.git@7c1dad0f99957db9ebb9bfe10cf72cf1a8ec3fb3#egg=elasticsearch_dsl
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
mock==5.2.0
packaging==21.3
pluggy==1.0.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: elasticsearch-dsl-py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - coverage==6.2
      - docutils==0.18.1
      - elasticsearch==5.5.3
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - mock==5.2.0
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/elasticsearch-dsl-py

test_elasticsearch_dsl/test_mapping.py::test_resolve_field_can_resolve_multifields test_elasticsearch_dsl/test_query.py::test_allow_multiple_queries_for_constant_score_issue_599 test_elasticsearch_dsl/test_result.py::test_agg_response_is_pickleable test_elasticsearch_dsl/test_result.py::test_response_is_pickleable test_elasticsearch_dsl/test_result.py::test_metric_agg_works
test_elasticsearch_dsl/test_mapping.py::test_mapping_can_has_fields test_elasticsearch_dsl/test_mapping.py::test_mapping_update_is_recursive test_elasticsearch_dsl/test_mapping.py::test_properties_can_iterate_over_all_the_fields test_elasticsearch_dsl/test_mapping.py::test_mapping_can_collect_all_analyzers test_elasticsearch_dsl/test_mapping.py::test_mapping_can_collect_multiple_analyzers test_elasticsearch_dsl/test_mapping.py::test_even_non_custom_analyzers_can_have_params test_elasticsearch_dsl/test_query.py::test_empty_Q_is_match_all test_elasticsearch_dsl/test_query.py::test_match_to_dict test_elasticsearch_dsl/test_query.py::test_match_to_dict_extra test_elasticsearch_dsl/test_query.py::test_fuzzy_to_dict test_elasticsearch_dsl/test_query.py::test_prefix_to_dict test_elasticsearch_dsl/test_query.py::test_term_to_dict test_elasticsearch_dsl/test_query.py::test_bool_to_dict test_elasticsearch_dsl/test_query.py::test_dismax_to_dict test_elasticsearch_dsl/test_query.py::test_bool_from_dict_issue_318 test_elasticsearch_dsl/test_query.py::test_repr test_elasticsearch_dsl/test_query.py::test_query_clone test_elasticsearch_dsl/test_query.py::test_bool_converts_its_init_args_to_queries test_elasticsearch_dsl/test_query.py::test_two_queries_make_a_bool test_elasticsearch_dsl/test_query.py::test_other_and_bool_appends_other_to_must test_elasticsearch_dsl/test_query.py::test_bool_and_other_appends_other_to_must test_elasticsearch_dsl/test_query.py::test_bool_and_other_sets_min_should_match_if_needed test_elasticsearch_dsl/test_query.py::test_query_and_query_creates_bool test_elasticsearch_dsl/test_query.py::test_match_all_and_query_equals_other test_elasticsearch_dsl/test_query.py::test_bool_and_bool test_elasticsearch_dsl/test_query.py::test_inverted_query_becomes_bool_with_must_not test_elasticsearch_dsl/test_query.py::test_inverted_query_with_must_not_become_should test_elasticsearch_dsl/test_query.py::test_inverted_query_with_must_and_must_not test_elasticsearch_dsl/test_query.py::test_double_invert_returns_original_query test_elasticsearch_dsl/test_query.py::test_bool_query_gets_inverted_internally test_elasticsearch_dsl/test_query.py::test_match_all_or_something_is_match_all test_elasticsearch_dsl/test_query.py::test_or_produces_bool_with_should test_elasticsearch_dsl/test_query.py::test_or_bool_doesnt_loop_infinitely_issue_37 test_elasticsearch_dsl/test_query.py::test_or_bool_doesnt_loop_infinitely_issue_96 test_elasticsearch_dsl/test_query.py::test_bool_will_append_another_query_with_or test_elasticsearch_dsl/test_query.py::test_bool_queries_with_only_should_get_concatenated test_elasticsearch_dsl/test_query.py::test_two_bool_queries_append_one_to_should_if_possible test_elasticsearch_dsl/test_query.py::test_queries_are_registered test_elasticsearch_dsl/test_query.py::test_defining_query_registers_it test_elasticsearch_dsl/test_query.py::test_Q_passes_query_through test_elasticsearch_dsl/test_query.py::test_Q_constructs_query_by_name test_elasticsearch_dsl/test_query.py::test_Q_translates_double_underscore_to_dots_in_param_names test_elasticsearch_dsl/test_query.py::test_Q_doesn_translate_double_underscore_to_dots_in_param_names test_elasticsearch_dsl/test_query.py::test_Q_constructs_simple_query_from_dict test_elasticsearch_dsl/test_query.py::test_Q_constructs_compound_query_from_dict test_elasticsearch_dsl/test_query.py::test_Q_raises_error_when_passed_in_dict_and_params test_elasticsearch_dsl/test_query.py::test_Q_raises_error_when_passed_in_query_and_params test_elasticsearch_dsl/test_query.py::test_Q_raises_error_on_unknown_query test_elasticsearch_dsl/test_query.py::test_match_all_and_anything_is_anything test_elasticsearch_dsl/test_query.py::test_function_score_with_functions test_elasticsearch_dsl/test_query.py::test_function_score_with_no_function_is_boost_factor test_elasticsearch_dsl/test_query.py::test_function_score_to_dict test_elasticsearch_dsl/test_query.py::test_function_score_with_single_function test_elasticsearch_dsl/test_query.py::test_function_score_from_dict test_elasticsearch_dsl/test_result.py::test_hit_is_pickleable test_elasticsearch_dsl/test_result.py::test_response_stores_search test_elasticsearch_dsl/test_result.py::test_attribute_error_in_hits_is_not_hidden test_elasticsearch_dsl/test_result.py::test_interactive_helpers test_elasticsearch_dsl/test_result.py::test_empty_response_is_false test_elasticsearch_dsl/test_result.py::test_len_response test_elasticsearch_dsl/test_result.py::test_iterating_over_response_gives_you_hits test_elasticsearch_dsl/test_result.py::test_hits_get_wrapped_to_contain_additional_attrs test_elasticsearch_dsl/test_result.py::test_hits_provide_dot_and_bracket_access_to_attrs test_elasticsearch_dsl/test_result.py::test_slicing_on_response_slices_on_hits test_elasticsearch_dsl/test_result.py::test_aggregation_base test_elasticsearch_dsl/test_result.py::test_aggregations_can_be_iterated_over test_elasticsearch_dsl/test_result.py::test_aggregations_can_be_retrieved_by_name test_elasticsearch_dsl/test_result.py::test_bucket_response_can_be_iterated_over test_elasticsearch_dsl/test_result.py::test_bucket_keys_get_deserialized
elasticsearch_dsl/response/__init__.py elasticsearch_dsl/field.py .travis.yml elasticsearch_dsl/aggs.py elasticsearch_dsl/search.py docs/index.rst elasticsearch_dsl/query.py docs/search_dsl.rst
elasticsearch_dsl/response/__init__.py elasticsearch_dsl/field.py .travis.yml elasticsearch_dsl/aggs.py elasticsearch_dsl/search.py docs/index.rst elasticsearch_dsl/query.py docs/search_dsl.rst