elasticsearch/dsl/function.py (117 lines of code) (raw):
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import collections.abc
from copy import deepcopy
from typing import (
Any,
ClassVar,
Dict,
Literal,
MutableMapping,
Optional,
Union,
overload,
)
from elastic_transport.client_utils import DEFAULT, DefaultType
from .utils import AttrDict, DslBase
@overload
def SF(name_or_sf: MutableMapping[str, Any]) -> "ScoreFunction": ...
@overload
def SF(name_or_sf: "ScoreFunction") -> "ScoreFunction": ...
@overload
def SF(name_or_sf: str, **params: Any) -> "ScoreFunction": ...
def SF(
name_or_sf: Union[str, "ScoreFunction", MutableMapping[str, Any]],
**params: Any,
) -> "ScoreFunction":
# {"script_score": {"script": "_score"}, "filter": {}}
if isinstance(name_or_sf, collections.abc.MutableMapping):
if params:
raise ValueError("SF() cannot accept parameters when passing in a dict.")
kwargs: Dict[str, Any] = {}
sf = deepcopy(name_or_sf)
for k in ScoreFunction._param_defs:
if k in name_or_sf:
kwargs[k] = sf.pop(k)
# not sf, so just filter+weight, which used to be boost factor
sf_params = params
if not sf:
name = "boost_factor"
# {'FUNCTION': {...}}
elif len(sf) == 1:
name, sf_params = sf.popitem()
else:
raise ValueError(f"SF() got an unexpected fields in the dictionary: {sf!r}")
# boost factor special case, see elasticsearch #6343
if not isinstance(sf_params, collections.abc.Mapping):
sf_params = {"value": sf_params}
# mix known params (from _param_defs) and from inside the function
kwargs.update(sf_params)
return ScoreFunction.get_dsl_class(name)(**kwargs)
# ScriptScore(script="_score", filter=Q())
if isinstance(name_or_sf, ScoreFunction):
if params:
raise ValueError(
"SF() cannot accept parameters when passing in a ScoreFunction object."
)
return name_or_sf
# "script_score", script="_score", filter=Q()
return ScoreFunction.get_dsl_class(name_or_sf)(**params)
class ScoreFunction(DslBase):
_type_name = "score_function"
_type_shortcut = staticmethod(SF)
_param_defs = {
"query": {"type": "query"},
"filter": {"type": "query"},
"weight": {},
}
name: ClassVar[Optional[str]] = None
def to_dict(self) -> Dict[str, Any]:
d = super().to_dict()
# filter and query dicts should be at the same level as us
for k in self._param_defs:
if self.name is not None:
val = d[self.name]
if isinstance(val, dict) and k in val:
d[k] = val.pop(k)
return d
class ScriptScore(ScoreFunction):
name = "script_score"
class BoostFactor(ScoreFunction):
name = "boost_factor"
def to_dict(self) -> Dict[str, Any]:
d = super().to_dict()
if self.name is not None:
val = d[self.name]
if isinstance(val, dict):
if "value" in val:
d[self.name] = val.pop("value")
else:
del d[self.name]
return d
class RandomScore(ScoreFunction):
name = "random_score"
class FieldValueFactorScore(ScoreFunction):
name = "field_value_factor"
class FieldValueFactor(FieldValueFactorScore): # alias of the above
pass
class Linear(ScoreFunction):
name = "linear"
class Gauss(ScoreFunction):
name = "gauss"
class Exp(ScoreFunction):
name = "exp"
class DecayFunction(AttrDict[Any]):
def __init__(
self,
*,
decay: Union[float, "DefaultType"] = DEFAULT,
offset: Any = DEFAULT,
scale: Any = DEFAULT,
origin: Any = DEFAULT,
multi_value_mode: Union[
Literal["min", "max", "avg", "sum"], "DefaultType"
] = DEFAULT,
**kwargs: Any,
):
if decay != DEFAULT:
kwargs["decay"] = decay
if offset != DEFAULT:
kwargs["offset"] = offset
if scale != DEFAULT:
kwargs["scale"] = scale
if origin != DEFAULT:
kwargs["origin"] = origin
if multi_value_mode != DEFAULT:
kwargs["multi_value_mode"] = multi_value_mode
super().__init__(kwargs)