ckanext-search-tweaks 0.4.12__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ckanext/search_tweaks/__init__.py +1 -17
- ckanext/search_tweaks/advanced_search/plugin.py +16 -10
- ckanext/search_tweaks/cli.py +3 -3
- ckanext/search_tweaks/config.py +37 -0
- ckanext/search_tweaks/field_relevance/plugin.py +7 -10
- ckanext/search_tweaks/field_relevance/views.py +12 -19
- ckanext/search_tweaks/interfaces.py +16 -9
- ckanext/search_tweaks/plugin.py +19 -44
- ckanext/search_tweaks/query_popularity/__init__.py +0 -0
- ckanext/search_tweaks/query_popularity/config.py +30 -0
- ckanext/search_tweaks/query_popularity/logic/__init__.py +0 -0
- ckanext/search_tweaks/query_popularity/logic/action.py +43 -0
- ckanext/search_tweaks/query_popularity/logic/auth.py +23 -0
- ckanext/search_tweaks/query_popularity/plugin.py +47 -0
- ckanext/search_tweaks/query_popularity/score.py +165 -0
- ckanext/search_tweaks/query_relevance/__init__.py +1 -2
- ckanext/search_tweaks/query_relevance/cli.py +3 -7
- ckanext/search_tweaks/query_relevance/plugin.py +18 -24
- ckanext/search_tweaks/query_relevance/score.py +1 -1
- ckanext/search_tweaks/query_relevance/storage.py +7 -14
- ckanext/search_tweaks/shared.py +13 -0
- ckanext/search_tweaks/spellcheck/helpers.py +15 -23
- ckanext/search_tweaks/spellcheck/plugin.py +1 -1
- ckanext/search_tweaks/tests/query_relevance/test_plugin.py +2 -3
- ckanext/search_tweaks/tests/query_relevance/test_storage.py +4 -4
- ckanext/search_tweaks/tests/spellcheck/test_plugin.py +7 -15
- ckanext/search_tweaks/tests/test_plugin.py +21 -32
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/METADATA +5 -4
- ckanext_search_tweaks-0.6.0.dist-info/RECORD +52 -0
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/WHEEL +1 -1
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/entry_points.txt +1 -0
- ckanext_search_tweaks-0.4.12.dist-info/RECORD +0 -43
- /ckanext_search_tweaks-0.4.12-py3.10-nspkg.pth → /ckanext_search_tweaks-0.6.0-py3.8-nspkg.pth +0 -0
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/LICENSE +0 -0
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/namespace_packages.txt +0 -0
- {ckanext_search_tweaks-0.4.12.dist-info → ckanext_search_tweaks-0.6.0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,3 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
import ckan.plugins.toolkit as tk
|
5
|
-
|
6
|
-
CONFIG_PREFER_BOOST = "ckanext.search_tweaks.common.prefer_boost"
|
7
|
-
DEFAULT_PREFER_BOOST = True
|
8
|
-
|
9
|
-
|
10
|
-
def boost_preffered() -> bool:
|
11
|
-
return tk.asbool(tk.config.get(CONFIG_PREFER_BOOST, DEFAULT_PREFER_BOOST))
|
12
|
-
|
13
|
-
|
14
|
-
def feature_disabled(feature: str, search_params: dict[str, Any]) -> bool:
|
15
|
-
return tk.asbool(
|
16
|
-
search_params.get("extras", {}).get(
|
17
|
-
f"ext_search_tweaks_disable_{feature}", False
|
18
|
-
)
|
19
|
-
)
|
3
|
+
from .shared import feature_disabled
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
import json
|
3
4
|
from typing import Any
|
5
|
+
|
4
6
|
import ckan.plugins as p
|
5
7
|
import ckan.plugins.toolkit as tk
|
6
8
|
from ckan.exceptions import CkanConfigurationException
|
@@ -35,14 +37,14 @@ DEFAULT_FORM_DEFINITION = json.dumps(
|
|
35
37
|
{"value": "private", "label": "Private"},
|
36
38
|
],
|
37
39
|
},
|
38
|
-
}
|
40
|
+
},
|
39
41
|
)
|
40
42
|
DEFAULT_FIELD_ORDER = None
|
41
43
|
|
42
44
|
|
43
45
|
def form_config():
|
44
46
|
definition = json.loads(
|
45
|
-
tk.config.get(CONFIG_FORM_DEFINITION, DEFAULT_FORM_DEFINITION)
|
47
|
+
tk.config.get(CONFIG_FORM_DEFINITION, DEFAULT_FORM_DEFINITION),
|
46
48
|
)
|
47
49
|
order = tk.aslist(tk.config.get(CONFIG_FIELD_ORDER, DEFAULT_FIELD_ORDER))
|
48
50
|
if not order:
|
@@ -72,17 +74,18 @@ class AdvancedSearchPlugin(p.SingletonPlugin):
|
|
72
74
|
from ckanext.composite_search.interfaces import ICompositeSearch
|
73
75
|
except ImportError:
|
74
76
|
raise CkanConfigurationException(
|
75
|
-
"ckanext-composite-search is not installed"
|
77
|
+
"ckanext-composite-search is not installed",
|
76
78
|
)
|
77
79
|
if not p.plugin_loaded("composite_search"):
|
78
|
-
|
79
|
-
|
80
|
-
)
|
80
|
+
msg = "Advanced search requires `composite_search` plugin"
|
81
|
+
raise CkanConfigurationException(msg)
|
81
82
|
if not list(p.PluginImplementations(ICompositeSearch)):
|
82
|
-
|
83
|
-
"Advanced search requires plugin that implements
|
84
|
-
+ " Consider enabling
|
83
|
+
msg = (
|
84
|
+
"Advanced search requires plugin that implements"
|
85
|
+
+ " ICompositeSearch. Consider enabling "
|
86
|
+
+ "`default_composite_search` plugins."
|
85
87
|
)
|
88
|
+
raise CkanConfigurationException(msg)
|
86
89
|
|
87
90
|
# ITemplateHelpers
|
88
91
|
|
@@ -93,9 +96,12 @@ class AdvancedSearchPlugin(p.SingletonPlugin):
|
|
93
96
|
|
94
97
|
# IPackageController
|
95
98
|
|
96
|
-
def
|
99
|
+
def before_dataset_search(self, search_params: dict[str, Any]):
|
97
100
|
solr_q = search_params.get("extras", {}).get("ext_solr_q", None)
|
98
101
|
if solr_q:
|
99
102
|
search_params.setdefault("q", "")
|
100
103
|
search_params["q"] += " " + solr_q
|
101
104
|
return search_params
|
105
|
+
|
106
|
+
if not tk.check_ckan_version("2.10"):
|
107
|
+
before_search = before_dataset_search
|
ckanext/search_tweaks/cli.py
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ckan.plugins.toolkit as tk
|
4
|
+
from ckan.lib.search.query import QUERY_FIELDS
|
5
|
+
|
6
|
+
CONFIG_QF = "ckanext.search_tweaks.common.qf"
|
7
|
+
DEFAULT_QF = QUERY_FIELDS
|
8
|
+
|
9
|
+
CONFIG_FUZZY = "ckanext.search_tweaks.common.fuzzy_search.enabled"
|
10
|
+
CONFIG_FUZZY_DISTANCE = "ckanext.search_tweaks.common.fuzzy_search.distance"
|
11
|
+
CONFIG_MM = "ckanext.search_tweaks.common.mm"
|
12
|
+
CONFIG_FUZZY_KEEP_ORIGINAL = "ckanext.search_tweaks.common.fuzzy_search.keep_original"
|
13
|
+
CONFIG_PREFER_BOOST = "ckanext.search_tweaks.common.prefer_boost"
|
14
|
+
|
15
|
+
|
16
|
+
def qf() -> str:
|
17
|
+
return tk.config[CONFIG_QF] or DEFAULT_QF
|
18
|
+
|
19
|
+
|
20
|
+
def fuzzy() -> bool:
|
21
|
+
return tk.config[CONFIG_FUZZY]
|
22
|
+
|
23
|
+
|
24
|
+
def fuzzy_distance() -> int:
|
25
|
+
return tk.config[CONFIG_FUZZY_DISTANCE]
|
26
|
+
|
27
|
+
|
28
|
+
def mm() -> str:
|
29
|
+
return tk.config[CONFIG_MM]
|
30
|
+
|
31
|
+
|
32
|
+
def fuzzy_with_original() -> bool:
|
33
|
+
return tk.config[CONFIG_FUZZY_KEEP_ORIGINAL]
|
34
|
+
|
35
|
+
|
36
|
+
def prefer_boost() -> bool:
|
37
|
+
return tk.config[CONFIG_PREFER_BOOST]
|
@@ -1,13 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
import ckan.plugins as p
|
6
6
|
import ckan.plugins.toolkit as tk
|
7
7
|
|
8
|
+
from ckanext.search_tweaks import feature_disabled
|
9
|
+
from ckanext.search_tweaks.interfaces import ISearchTweaks
|
8
10
|
from . import views
|
9
|
-
from ..interfaces import ISearchTweaks
|
10
|
-
from .. import feature_disabled
|
11
11
|
|
12
12
|
CONFIG_BOOST_FN = "ckanext.search_tweaks.field_relevance.boost_function"
|
13
13
|
|
@@ -21,11 +21,9 @@ class FieldRelevancePlugin(p.SingletonPlugin):
|
|
21
21
|
p.implements(p.IConfigurer, inherit=True)
|
22
22
|
|
23
23
|
# ISearchTweaks
|
24
|
-
def get_search_boost_fn(
|
25
|
-
self, search_params: dict[str, Any]
|
26
|
-
) -> Optional[str]:
|
24
|
+
def get_search_boost_fn(self, search_params: dict[str, Any]) -> str | None:
|
27
25
|
if feature_disabled("field_boost", search_params):
|
28
|
-
return
|
26
|
+
return None
|
29
27
|
|
30
28
|
return tk.config.get(CONFIG_BOOST_FN, DEFAULT_BOOST_FN)
|
31
29
|
|
@@ -40,15 +38,14 @@ class FieldRelevancePlugin(p.SingletonPlugin):
|
|
40
38
|
tk.add_template_directory(config, "templates")
|
41
39
|
tk.add_resource("assets", "search_tweaks_field_relevance")
|
42
40
|
|
43
|
-
|
44
41
|
# IAuthFunctions
|
45
42
|
def get_auth_functions(self):
|
46
43
|
return {
|
47
|
-
"search_tweaks_field_relevance_promote":
|
44
|
+
"search_tweaks_field_relevance_promote": promote_auth,
|
48
45
|
}
|
49
46
|
|
50
47
|
|
51
|
-
def
|
48
|
+
def promote_auth(context, data_dict):
|
52
49
|
try:
|
53
50
|
tk.check_access("package_update", context, data_dict)
|
54
51
|
except tk.NotAuthorized:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
from flask import Blueprint
|
5
6
|
from flask.views import MethodView
|
@@ -10,9 +11,7 @@ import ckan.plugins.toolkit as tk
|
|
10
11
|
CONFIG_ENABLE_PROMOTION_ROUTE = (
|
11
12
|
"ckanext.search_tweaks.field_relevance.blueprint.promotion.enabled"
|
12
13
|
)
|
13
|
-
CONFIG_PROMOTION_PATH =
|
14
|
-
"ckanext.search_tweaks.field_relevance.blueprint.promotion.path"
|
15
|
-
)
|
14
|
+
CONFIG_PROMOTION_PATH = "ckanext.search_tweaks.field_relevance.blueprint.promotion.path"
|
16
15
|
CONFIG_MAX_PROMOTION = (
|
17
16
|
"ckanext.search_tweaks.field_relevance.blueprint.promotion.max_value"
|
18
17
|
)
|
@@ -30,14 +29,10 @@ field_relevance = Blueprint("search_tweaks_field_relevance", __name__)
|
|
30
29
|
|
31
30
|
def get_blueprints():
|
32
31
|
if tk.asbool(
|
33
|
-
tk.config.get(
|
34
|
-
CONFIG_ENABLE_PROMOTION_ROUTE, DEFAULT_ENABLE_PROMOTION_ROUTE
|
35
|
-
)
|
32
|
+
tk.config.get(CONFIG_ENABLE_PROMOTION_ROUTE, DEFAULT_ENABLE_PROMOTION_ROUTE),
|
36
33
|
):
|
37
34
|
path = tk.config.get(CONFIG_PROMOTION_PATH, DEFAULT_PROMOTION_PATH)
|
38
|
-
field_relevance.add_url_rule(
|
39
|
-
path, view_func=PromoteView.as_view("promote")
|
40
|
-
)
|
35
|
+
field_relevance.add_url_rule(path, view_func=PromoteView.as_view("promote"))
|
41
36
|
|
42
37
|
return [field_relevance]
|
43
38
|
|
@@ -57,9 +52,9 @@ class PromoteView(MethodView):
|
|
57
52
|
tk.get_validator("convert_int"),
|
58
53
|
tk.get_validator("natural_number_validator"),
|
59
54
|
tk.get_validator("limit_to_configured_maximum")(
|
60
|
-
CONFIG_MAX_PROMOTION, DEFAULT_MAX_PROMOTION
|
55
|
+
CONFIG_MAX_PROMOTION, DEFAULT_MAX_PROMOTION,
|
61
56
|
),
|
62
|
-
]
|
57
|
+
],
|
63
58
|
}
|
64
59
|
|
65
60
|
data, errors = tk.navl_validate(
|
@@ -72,7 +67,7 @@ class PromoteView(MethodView):
|
|
72
67
|
return self.get(id, data, errors)
|
73
68
|
try:
|
74
69
|
pkg_dict = tk.get_action("package_patch")(
|
75
|
-
{}, {"id": id, field: data[field]}
|
70
|
+
{}, {"id": id, field: data[field]},
|
76
71
|
)
|
77
72
|
except tk.ValidationError as e:
|
78
73
|
for k, v in e.error_summary.items():
|
@@ -84,8 +79,8 @@ class PromoteView(MethodView):
|
|
84
79
|
def get(
|
85
80
|
self,
|
86
81
|
id,
|
87
|
-
data:
|
88
|
-
errors:
|
82
|
+
data: dict[str, Any] | None = None,
|
83
|
+
errors: dict[str, Any] | None = None,
|
89
84
|
):
|
90
85
|
self._check_access(id)
|
91
86
|
field = tk.config.get(CONFIG_PROMOTION_FIELD, DEFAULT_PROMOTION_FIELD)
|
@@ -95,11 +90,9 @@ class PromoteView(MethodView):
|
|
95
90
|
"errors": errors or {},
|
96
91
|
"data": data or pkg_dict,
|
97
92
|
"max_promotion": tk.asint(
|
98
|
-
tk.config.get(CONFIG_MAX_PROMOTION, DEFAULT_MAX_PROMOTION)
|
93
|
+
tk.config.get(CONFIG_MAX_PROMOTION, DEFAULT_MAX_PROMOTION),
|
99
94
|
),
|
100
95
|
"field_name": field,
|
101
96
|
}
|
102
97
|
|
103
|
-
return tk.render(
|
104
|
-
"search_tweaks/field_relevance/promote.html", extra_vars
|
105
|
-
)
|
98
|
+
return tk.render("search_tweaks/field_relevance/promote.html", extra_vars)
|
@@ -1,25 +1,32 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
from ckan.plugins.interfaces import Interface
|
5
|
-
from . import CONFIG_PREFER_BOOST
|
6
6
|
|
7
7
|
|
8
8
|
class ISearchTweaks(Interface):
|
9
|
-
def get_search_boost_fn(
|
10
|
-
|
11
|
-
) -> Optional[str]:
|
12
|
-
f"""Return Solr's boost function applicable to the current search.
|
9
|
+
def get_search_boost_fn(self, search_params: dict[str, Any]) -> str | None:
|
10
|
+
"""Return Solr's boost function applicable to the current search.
|
13
11
|
|
14
|
-
Note: it will be applied as `boost` when
|
15
|
-
enabled and as `bf`
|
12
|
+
Note: it will be applied as `boost` when
|
13
|
+
`ckanext.search_tweaks.common.prefer_boost` enabled and as `bf`
|
14
|
+
otherwise.
|
16
15
|
|
17
16
|
"""
|
18
17
|
return None
|
19
18
|
|
20
|
-
def get_extra_qf(self, search_params: dict[str, Any]) ->
|
19
|
+
def get_extra_qf(self, search_params: dict[str, Any]) -> str | None:
|
21
20
|
"""Return an additional fragment of the Solr's qf.
|
22
21
|
|
23
22
|
This fragment will be appended to the current qf
|
24
23
|
"""
|
25
24
|
return None
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
class IQueryPopularity(Interface):
|
29
|
+
def skip_query_popularity(self, params: dict[str, Any]) -> bool:
|
30
|
+
"""Do not index search query.
|
31
|
+
"""
|
32
|
+
return False
|
ckanext/search_tweaks/plugin.py
CHANGED
@@ -1,56 +1,37 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import logging
|
4
|
+
from typing import Any
|
4
5
|
|
5
|
-
from typing import Any, Dict
|
6
6
|
import ckan.plugins as plugins
|
7
7
|
import ckan.plugins.toolkit as tk
|
8
|
-
from
|
9
|
-
|
10
|
-
from . import cli, boost_preffered, feature_disabled
|
8
|
+
from . import feature_disabled, config
|
11
9
|
from .interfaces import ISearchTweaks
|
12
10
|
|
13
11
|
log = logging.getLogger(__name__)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
CONFIG_QF = "ckanext.search_tweaks.common.qf"
|
18
|
-
CONFIG_FUZZY = "ckanext.search_tweaks.common.fuzzy_search.enabled"
|
19
|
-
CONFIG_FUZZY_DISTANCE = "ckanext.search_tweaks.common.fuzzy_search.distance"
|
20
|
-
CONFIG_MM = "ckanext.search_tweaks.common.mm"
|
21
|
-
CONFIG_FUZZY_KEEP_ORIGINAL = (
|
22
|
-
"ckanext.search_tweaks.common.fuzzy_search.keep_original"
|
23
|
-
)
|
24
|
-
|
25
|
-
DEFAULT_QF = QUERY_FIELDS
|
26
|
-
DEFAULT_FUZZY = False
|
27
|
-
DEFAULT_FUZZY_DISTANCE = 1
|
28
|
-
DEFAULT_MM = "1"
|
29
|
-
DEFAULT_FUZZY_KEEP_ORIGINAL = True
|
12
|
+
CONFIG_PREFER_BOOST = "ckanext.search_tweaks.common.prefer_boost"
|
13
|
+
DEFAULT_PREFER_BOOST = True
|
30
14
|
|
31
15
|
|
16
|
+
@tk.blanket.cli
|
17
|
+
@tk.blanket.config_declarations
|
32
18
|
class SearchTweaksPlugin(plugins.SingletonPlugin):
|
33
|
-
plugins.implements(plugins.IClick)
|
34
19
|
plugins.implements(plugins.IPackageController, inherit=True)
|
35
20
|
|
36
|
-
# IClick
|
37
|
-
|
38
|
-
def get_commands(self):
|
39
|
-
return cli.get_commands()
|
40
|
-
|
41
21
|
# IPackageController
|
42
22
|
|
43
|
-
def before_dataset_search(self, search_params:
|
23
|
+
def before_dataset_search(self, search_params: dict[str, Any]):
|
44
24
|
if feature_disabled("everything", search_params):
|
45
25
|
return search_params
|
46
26
|
|
47
|
-
search_params.setdefault("mm",
|
27
|
+
search_params.setdefault("mm", config.mm())
|
48
28
|
|
49
29
|
if "defType" not in search_params:
|
50
30
|
search_params["defType"] = "edismax"
|
51
31
|
|
52
|
-
if
|
32
|
+
if config.prefer_boost() and search_params["defType"] == "edismax":
|
53
33
|
_set_boost(search_params)
|
34
|
+
|
54
35
|
else:
|
55
36
|
_set_bf(search_params)
|
56
37
|
|
@@ -60,7 +41,7 @@ class SearchTweaksPlugin(plugins.SingletonPlugin):
|
|
60
41
|
return search_params
|
61
42
|
|
62
43
|
|
63
|
-
def _set_boost(search_params:
|
44
|
+
def _set_boost(search_params: dict[str, Any]) -> None:
|
64
45
|
boost: list[str] = search_params.setdefault("boost", [])
|
65
46
|
for plugin in plugins.PluginImplementations(ISearchTweaks):
|
66
47
|
extra = plugin.get_search_boost_fn(search_params)
|
@@ -69,7 +50,7 @@ def _set_boost(search_params: SearchParams) -> None:
|
|
69
50
|
boost.append(extra)
|
70
51
|
|
71
52
|
|
72
|
-
def _set_bf(search_params:
|
53
|
+
def _set_bf(search_params: dict[str, Any]) -> None:
|
73
54
|
default_bf: str = search_params.get("bf") or "0"
|
74
55
|
search_params.setdefault("bf", default_bf)
|
75
56
|
for plugin in plugins.PluginImplementations(ISearchTweaks):
|
@@ -79,13 +60,11 @@ def _set_bf(search_params: SearchParams) -> None:
|
|
79
60
|
search_params["bf"] = f"sum({search_params['bf']},{extra_bf})"
|
80
61
|
|
81
62
|
|
82
|
-
def _set_qf(search_params:
|
63
|
+
def _set_qf(search_params: dict[str, Any]) -> None:
|
83
64
|
if feature_disabled("qf", search_params):
|
84
65
|
return
|
85
66
|
|
86
|
-
default_qf: str = search_params.get("qf") or
|
87
|
-
CONFIG_QF, DEFAULT_QF
|
88
|
-
)
|
67
|
+
default_qf: str = search_params.get("qf") or config.qf()
|
89
68
|
search_params.setdefault("qf", default_qf)
|
90
69
|
for plugin in plugins.PluginImplementations(ISearchTweaks):
|
91
70
|
extra_qf = plugin.get_extra_qf(search_params)
|
@@ -94,8 +73,8 @@ def _set_qf(search_params: SearchParams) -> None:
|
|
94
73
|
search_params["qf"] += " " + extra_qf
|
95
74
|
|
96
75
|
|
97
|
-
def _set_fuzzy(search_params:
|
98
|
-
if not
|
76
|
+
def _set_fuzzy(search_params: dict[str, Any]) -> None:
|
77
|
+
if not config.fuzzy():
|
99
78
|
return
|
100
79
|
|
101
80
|
if feature_disabled("fuzzy", search_params):
|
@@ -118,20 +97,16 @@ def _set_fuzzy(search_params: SearchParams) -> None:
|
|
118
97
|
if s.isalpha() and s not in ("AND", "OR", "TO")
|
119
98
|
else s,
|
120
99
|
q.split(),
|
121
|
-
)
|
100
|
+
),
|
122
101
|
)
|
123
|
-
if
|
124
|
-
tk.config.get(CONFIG_FUZZY_KEEP_ORIGINAL, DEFAULT_FUZZY_KEEP_ORIGINAL)
|
125
|
-
):
|
102
|
+
if config.fuzzy_with_original():
|
126
103
|
search_params["q"] = f"({fuzzy_q}) OR ({q})"
|
127
104
|
else:
|
128
105
|
search_params["q"] = fuzzy_q
|
129
106
|
|
130
107
|
|
131
108
|
def _get_fuzzy_distance() -> int:
|
132
|
-
distance =
|
133
|
-
tk.config.get(CONFIG_FUZZY_DISTANCE, DEFAULT_FUZZY_DISTANCE)
|
134
|
-
)
|
109
|
+
distance = config.fuzzy_distance()
|
135
110
|
if distance < 0:
|
136
111
|
log.warning("Cannot use negative fuzzy distance: %s.", distance)
|
137
112
|
distance = 0
|
File without changes
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import ckan.plugins.toolkit as tk
|
3
|
+
|
4
|
+
|
5
|
+
def skip_irrefutable() -> bool:
|
6
|
+
return tk.config["ckanext.search_tweaks.query_popularity.skip_irrefutable_search"]
|
7
|
+
|
8
|
+
|
9
|
+
def ignored_symbols() -> set[str]:
|
10
|
+
return set(tk.config["ckanext.search_tweaks.query_popularity.ignored_symbols"])
|
11
|
+
|
12
|
+
|
13
|
+
def ignored_terms() -> list[str]:
|
14
|
+
return tk.config["ckanext.search_tweaks.query_popularity.ignored_terms"]
|
15
|
+
|
16
|
+
|
17
|
+
def throttle() -> int:
|
18
|
+
return tk.config["ckanext.search_tweaks.query_popularity.query_throttle"]
|
19
|
+
|
20
|
+
|
21
|
+
def max_age() -> int:
|
22
|
+
return tk.config["ckanext.search_tweaks.query_popularity.max_age"]
|
23
|
+
|
24
|
+
|
25
|
+
def obsoletion_period() -> int:
|
26
|
+
return tk.config["ckanext.search_tweaks.query_popularity.obsoletion_period"]
|
27
|
+
|
28
|
+
|
29
|
+
def tracked_endpoints() -> list[str]:
|
30
|
+
return tk.config["ckanext.search_tweaks.query_popularity.tracked_endpoints"]
|
File without changes
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Any
|
3
|
+
from ckan import types
|
4
|
+
import ckan.plugins.toolkit as tk
|
5
|
+
|
6
|
+
from ckanext.search_tweaks.query_popularity.score import Score
|
7
|
+
|
8
|
+
|
9
|
+
@tk.side_effect_free
|
10
|
+
def search_tweaks_query_popularity_list(
|
11
|
+
context: types.Context, data_dict: dict[str, Any]
|
12
|
+
) -> list[dict[str, Any]]:
|
13
|
+
score = Score()
|
14
|
+
|
15
|
+
if tk.asbool(data_dict.get("refresh")):
|
16
|
+
score.refresh()
|
17
|
+
|
18
|
+
limit = tk.asint(data_dict.get("limit", 10))
|
19
|
+
|
20
|
+
return list(score.stats(limit))
|
21
|
+
|
22
|
+
|
23
|
+
@tk.side_effect_free
|
24
|
+
def search_tweaks_query_popularity_export(
|
25
|
+
context: types.Context, data_dict: dict[str, Any]
|
26
|
+
) -> dict[str, Any]:
|
27
|
+
score = Score()
|
28
|
+
|
29
|
+
results = score.export()
|
30
|
+
return {"results": results, "count": len(results)}
|
31
|
+
|
32
|
+
|
33
|
+
@tk.side_effect_free
|
34
|
+
def search_tweaks_query_popularity_ignore(
|
35
|
+
context: types.Context, data_dict: dict[str, Any]
|
36
|
+
):
|
37
|
+
q = tk.get_or_bust(data_dict, "q")
|
38
|
+
score = Score()
|
39
|
+
result = score.ignore(q)
|
40
|
+
if tk.asbool(data_dict.get("remove")):
|
41
|
+
score.drop(q)
|
42
|
+
|
43
|
+
return result
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Any
|
3
|
+
from ckan import types
|
4
|
+
|
5
|
+
from ckan.authz import is_authorized
|
6
|
+
|
7
|
+
|
8
|
+
def search_tweaks_query_popularity_list(
|
9
|
+
context: types.Context, data_dict: dict[str, Any]
|
10
|
+
) -> types.AuthResult:
|
11
|
+
return is_authorized("sysadmin", context, data_dict)
|
12
|
+
|
13
|
+
|
14
|
+
def search_tweaks_query_popularity_export(
|
15
|
+
context: types.Context, data_dict: dict[str, Any]
|
16
|
+
) -> types.AuthResult:
|
17
|
+
return is_authorized("sysadmin", context, data_dict)
|
18
|
+
|
19
|
+
|
20
|
+
def search_tweaks_query_popularity_ignore(
|
21
|
+
context: types.Context, data_dict: dict[str, Any]
|
22
|
+
) -> types.AuthResult:
|
23
|
+
return is_authorized("sysadmin", context, data_dict)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Any
|
3
|
+
import ckan.plugins as p
|
4
|
+
import ckan.plugins.toolkit as tk
|
5
|
+
from ckanext.search_tweaks.interfaces import IQueryPopularity
|
6
|
+
from . import config, score
|
7
|
+
|
8
|
+
|
9
|
+
@tk.blanket.actions
|
10
|
+
@tk.blanket.auth_functions
|
11
|
+
@tk.blanket.config_declarations
|
12
|
+
class QueryPopularityPlugin(p.SingletonPlugin):
|
13
|
+
p.implements(p.IConfigurable)
|
14
|
+
p.implements(p.IPackageController, inherit=True)
|
15
|
+
p.implements(IQueryPopularity, inherit=True)
|
16
|
+
|
17
|
+
def after_dataset_search(self, results: dict[str, Any], params: dict[str, Any]):
|
18
|
+
bp, view = tk.get_endpoint()
|
19
|
+
if bp and view and f"{bp}.{view}" in config.tracked_endpoints():
|
20
|
+
if not any(
|
21
|
+
plugin.skip_query_popularity(params)
|
22
|
+
for plugin in p.PluginImplementations(IQueryPopularity)
|
23
|
+
):
|
24
|
+
self.score.save(params["q"])
|
25
|
+
|
26
|
+
return results
|
27
|
+
|
28
|
+
def configure(self, config: Any):
|
29
|
+
self.score = score.Score()
|
30
|
+
|
31
|
+
def skip_query_popularity(self, params: dict[str, Any]) -> bool:
|
32
|
+
q = params["q"]
|
33
|
+
|
34
|
+
if q == "*:*":
|
35
|
+
return config.skip_irrefutable()
|
36
|
+
|
37
|
+
symbols = config.ignored_symbols()
|
38
|
+
if symbols and set(q) & symbols:
|
39
|
+
return True
|
40
|
+
|
41
|
+
terms = config.ignored_terms()
|
42
|
+
|
43
|
+
for term in terms:
|
44
|
+
if term in q:
|
45
|
+
return True
|
46
|
+
|
47
|
+
return False
|